Before we begin, you can try out a live demo of the app here.

Github repo here.

Setting up Meteor

In order to this tutorial you’ve to have Meteor running on your machine. We’ve already installed it in the part 1 so jump straight to 6 in 6 Challenge Week 1 — Yik Yak Web Clone to see an installation process. Install Meteor and create your app.

To create an app’s basic structure type in the terminal the following :

mkdir client server lib public stylesheet

Let’s add all the Meteor packages we’ll need in the process :

meteor add iron:router twbs:bootstrap natestrauser:font-awesome accounts-password ian:accounts-ui-bootstrap-3 sjors:meteor-masonry

Creating the interface

As usual we need to set up main.html :

<head>

<title>All My Books</title>

<! — To support mobile-devices →

<meta name=”viewport” content=”width=device-width, initial- scale=1.0 user-scalable=no”>

</head>

and header.html :

<template name=”header”>

<div class=”navbar”>

<div class=”container”>

<div class=”navbar-inner”>

<a class=”brand btn” href=”/”><img style=”height: 40px; width: 40px;” src=”/book-icon.png”/> <p>Books</p></a>

<div id=”login-button” class=”btn btn-default pull-right”>

{{> loginButtons}}

</div>

</div>

</div>

</div>

</template>

To insert the navbar book icon we need to put it in the public folder of our application. Also add style.ccs code on github to the stylesheet folder.

Now we’ll start with the configuration. Create defaults.js and put this line in :

Session.setDefault(‘category’, null);

Basically it’s our switch for changing categories. Everytime we switch over the category, we want to show something else in category Session. Null means no category selected. In router.js type the following :

Router.configure({

layoutTemplate:'layout',

yieldTemplates:{

//each yield going to different templates

'books':{to:'books'},

'categories':{to:'categories'}

}

});

Router.map(function(){

this.route('/','layout');

this.route('books', {

layoutTemplate:'layout',

path:'/:name',

data: function() {

console.log(this.params.name);

Session.set('category',this.params.name);

},

template:'layout'

});

});

We configure layout template as our yield template and then define two templates — books and categories because we want to use both at one page yield template. Then we do our mapping. If someone comes to our app we want to show our layout template.

But with the books it gets little more complicated. We need to switch based on category so that we pull the link out of url. Name of the template is the path for the router.

Now create other templates. We’ll start with layout template :

<template name='layout'>

{{> header}}

<div class='container-fluid'>

<div class='row'>

<div class='col-xs-12 text-center'>

{{> yield 'categories'}}

</div>

<div class='col-xs-10 col-xs-offset-1 text-center'>

{{> yield 'books'}}

</div>

</div>

</div>

</template>

We just told layout template to yield both to categories and books on the single page. Next we need to define those two templates. At first categories.html :

<template name='categories'>

<ul>

{{#each Categories}}

<li id="categories"><a class="btn btn-default" href="/{{name}}">{{name}}</a></li>

{{/each}}

</ul>

</template>

Name of a category is basically link pointing to the url of certain category. Router will use it to take our users to category they clicked. Then add books.html :

<template name='books'>

<div id='books'>

<div class='page-header'>

<h1>

<small>Books</small>

</h1>

</div>

</div>

</template>

Now we need to define our collections. Make sure they are stored both on client and server ( not in the client or server folder ) :

Books = new Mongo.Collection('books'); Categories = new Mongo.Collection('categories'); Likes = new Mongo.Collection('likes');

Let’s add some categories to our database. Create server.js and store it in the server folder. We’ll add some dummy categories that will be inserted when we start our application and there is no other data inserted :

Meteor.startup(function() {

if(Books.find().count() === 0) {

// Books

}

if(Categories.find().count() === 0) {

// Categories

Categories.insert({name:'Tech'});

Categories.insert({name:'Design'});

Categories.insert({name:'Business'});

Categories.insert({name:'Science'});

Categories.insert({name:'Scifi'});

Categories.insert({name:'Psychology'});

}

});

Now when we go to the localhost:3000 in the browser we still can’t see our recently added categories. The reason is that we didn’t write the helper function to retrieve categories from the database. Create helpers.js on client and type the following :

Template.categories.helpers({

Categories:function() {

return Categories.find();

}

});

We successfully displayed the categories. Now we need to do the same for books. We’ll loop through each book and for every we find book template will be called. Also we will put else statement there, because there might be two additional things that could happen. There are no books found in the category or our user haven’t selected the category yet. Let’s put that into the code :

<template name='books'>

<div id='books'>

<div class='page-header'>

<h1>

<small>{{#if catnotselected}}Books{{else}}{{category}} Books{{/if}}</small>

</h1>

</div>

<div id='mainContent'>

{{#each booklist}}

{{>book}}

{{else}}

{{#if catnotselected}}

<div class='page-header'><h1><small>Select a category.</small></h1></div>

{{else}}

<div class='page-header'><h1><small>No books in this category.</small></h1></div>

{{/if}}

{{/each}}

</div>

</div>

</template>

We need to state what catnotselected means. We basically need to tell the computer — look at category Session and if it’s null, the category isn’t selected yet. In helpers.js :

Template.books.helpers({

catnotselected:function() {

return Session.equals(‘category’,null);

},

category:function() {

return Session.get('category');

}

});

Now it’s supposed to display Select a category message as default and when you click on the category, it should display No books in this category message. Also with the second function we get the current category to display in HTML. Now we can see at what category of books we’re at this point of time. Let’s add book template :

<template name='book'>

<div class='item'>

<ul id='{{_id}}' class='nav nav-list text-left'>

<li class='thumbnail'> <img src='{{url}}'/> </li>

<div class='thumbnail content'>

<li><strong>{{title}}</strong></li>

<li>{{format}}</li>

</div>

</ul>

</div>

</template>

Now we’re able to add books through the server as we did with categories earlier. Edit server.js with the following code :

Now we need to retrieve them from the database to display those properly :

Template.books.helpers({

booklist:function(){

return Books.find({catName:Session.get('category')});

}

});

We find and display books based on a category name assigned to the particular book.

Adding like button

Let’s make our application more social and give our users the way to like particular books. At first edit book template as following :

...

<div class='thumbnail content'>

{{#if currentUser}}

<div class='like-bar'>

<a href='#' class='like'>

<i class='fa fa-heart-o'></i>

</a>

<span class='label label-danger'>{{numLikes}}</span>

<span class='badge badge-default'>{{likesThis}}</span>

</div>

{{/if}}

<li><strong>{{title}}</strong></li>

...

App now displays like buttons only when the user is logged in. Let’s edit our sign up method and use username as credential instead of an email as a way to login. Add the following to helpers.js :

Accounts.ui.config({

passwordSignupFields: "USERNAME_ONLY"

});

Now we need to display number of likes and whether users have already liked it using helpers :

Template.bok.helpers({

numLikes:function() {

return Likes.find({book:this._id}).count();

},

likesThis:function() {

var doeslike = Likes.findOne({muser:Meteor.userId(),book:this._id});

if (doeslike) {

return 'You liked this';

}

}

});

In the first function we return number of likes for the current book and in the second function we return wheter the current user liked a current book. Also we need to create an event to add likes. In event.js type :

Template.book.events({

‘click .like’:function(event, template){

var curlike = Likes.findOne({ muser:Meteor.userId(), book:template.data._id});

if (!curlike) {

Likes.insert({muser:Meteor.userId(),book:template.data._id});

}

Session.set(‘updated’,new Date());

}

});

In english language — if current user already liked the book, do nothing, if not, like the book.

Pinterest like interface

The last thing to add to our humble app is Pinterest like grid interface. It makes all elements fit together in the best way they could. At the beggining we added masonry Meteor package let’s use it now. In helpers.js type the following :

function masonize(callback) {

var container = $('#mainContent');

container.masonry({

itemSelector: '.item',

gutter:10

})

if(callback){callback()};

}

Here we have masonize function. We define items where it should be executed and gutter or margin between each column. Also we need to call it somewhere. As soon as category template renders the function gets called. Add the following to call masonize function :

Template.books.rendered = function() {

setTimeout(function(){

masonize(function(){

})

}, 3500);

};

Now we are ready to deploy our application for everyone to see. To deploy an Meteor app, think of the name for your app and run the following in the terminal :

meteor deploy yourappname.meteor.com

Congrats ! That’s all for now. I almost forgot to mention that I used this tutorial as a outline. Go check the guy out on Youtube, he made some really interesting and helpful tutorials in Meteor.