-

Yik Yak Web Clone

We’ll build a small social thoughts sharing app (let’s say Yik Yak clone for better imagination) in 30 minutes as a first part of Challenge to build 6 apps in 6 weeks in Meteor. Obviously, we won’t build it with the whole functionally. It will be rather easy to build and you can further extend it as you like. In this app you can submit a yak (your thought), login/log out to vote up or down on a yak and also comment on submitted yaks.

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

Installing Meteor and creating the application

If you are on Mac or Linux, install Meteor itself by running the following in your terminal :

If you use Windows, jump over to Meteor site, to find out how to install on Windows. Once you have Meteor installed, we need to create a new project. Type the following to create it :

meteor create week-1

This command will create a simple app with the following structure :

To start the app we’ve created in the previous step, type what terminal shows you :

cd week-1

meteor

Meteor creates a simple demo for you. Now check up your newly created app at http://localhost:3000/. You can play with it as you like, but after that delete week-1.html and week-1.js. We want to use our own code and folders.

Creating interface

In meteor code from one JavaScript file can run both on the client and the server, but we will now adjust project structure to separate code which runs on client and on server. Open up new terminal window and create client and server folders inside week-1, with following command in terminal:

mkdir client server

Obviously, all code in client directory will be available for client (in the browser), while code from server folder will remain on server side. There’s no need to do this, but it makes everything more transparent. Now create main.html file inside client to store our basic HTML.

cd client

touch main.html

Before we get to display anything, we need to add <head> and <body>. This will be our main app template. So open main.html and add the following :

<head>

<title>Yak</title>

<!-- To support mobile-devices -->

<meta name="viewport" content="width=device-width, initial-scale=1.0">

</head> <body>

<div class="navbar navbar-static-top">

<div class="navbar-inner">

<div class="container">

<a class="brand" href="/">Yak</a>

<a class="btn btn-primary pull-right" href="/submit" type="button">Send Yak</a>

</div>

</div>

</div>

</body>

Before we go any further, let’s make our app look better by adding Bootstrap and some raw CSS. The simplest way to download Bootstrap in Meteor is to download it as a Meteor package. With the following command Bootstrap install itself :

meteor add twbs:bootstrap

That’s it ! You don’t have to include any files and links. Meteor just knows where to look for it. You can find more packages available at Atmosphere. Now we can see the reactive side of Meteor. After adding Bootstrap our app changes design without us pushing, deploying or uploading anything.

Since this tutorial is focused on HTML and JavaScript in Meteor, here is a raw CSS to copy in our css file. App will work without it, but why not make it much nicer ? Just don’t try to go through it, because it’s not based on any conventions at all. It was just me randomly styling the app in less time possible.

https://github.com/mhlavacka/Week-1/blob/master/style.css

It’s just very basic design, because I like to make things simple as possible. For example when most people start using cloud storage, they did so because Dropbox made it that simple to use, that 3 years old kid could use it.

Alright, looking good now, let’s implement some functionality.

Here we’ll start building the user interface for our application. We need to create first templates, that are used to create a connection between our interface and JavaScript code.

To display a list of yak items, we have the following template named yaks_list.html in client folder :

<template name="yaksList">

<div class="yaks">

<div class="form-style">

<div class="container">

<ul class="list-group">

<h3>Live feed of what people are saying around you</h3>

{{#each yaks}}

{{> yakItem}}

{{/each}}

</ul>

</div>

</div>

</div>

</template>

The template is named yaksList as it displays a list of all yaks. We added all those classes to style it. You might wonder what is this :

{{#each yaks}}

{{> yakItem}}

{{/each}}

We use it to iterate over the array of yak posts and insert a yakItem template we are about to create for each value. Let’s say we have five yakItem elements inside the collection, yakItem element will appear 5 times within the interface.

Now we want to add font-awesome package to be able to use vote up and down icons in our app. Run the following in terminal :

meteor add natestrauser:font-awesome

Alright, now we add second template. We will implement interface of each individual yak in it. The content of template yakItem is as following :

<template name="yakItem">

<div class="yak">

<li class="list-group-item">

<a class="yakPost" href="#">

<p class="break-el">{{yak}}</p>

<a class="btn pull-right btn-small no" href="#"><i class="fa fa-chevron-down"></i></a>

<a class="btn pull-right btn-small yes" href="#"><i class="fa fa-chevron-up"></i></a>

</a>

</li>

</div>

</template>

It displays all yaks property using {{yak}} and two buttons with vote up and down icons.

Creating collections

In Meteor you save data in collections. We will now create Yaks collection to hold a list of yak items. Create main.js in the week-1 folder. Open it up and add the following line in it :

Yaks = new Mongo.Collection('yaks');

The above code creates a collection in MongoDB, which will store all our yaks. Now we want to display all stored yaks, into our template. To do that we need to create a new file under client called client.js.

Template.yaksList.helpers({

yaks: function() {

return Yaks.find();

}

});

You can pass variables to templates via helpers. Above function simply asks for list of yaks from Yaks collection we’ve just created and returns them using built in find() function.

Finally, we define a route for yaksList so that the template is loaded when someone visit “/”. We will use Meteor package called Iron Router to do this. Type the following into terminal :

meteor add iron:router

Once it is added, open up main.js and add the following code :

Router.route('/', {name: 'yaksList'})

Submitting yaks

We basically say, when user visits “/” show her template named yaksList. It’s really that simple. We don’t have any yak items yet, but let’s access http://localhost:3000 anyway. To add some yak items let’s open up MongoDB console and type :

meteor mongo

Type the following when the mongo console opens up :

db.yaks.insert({yak:"first yak ever"});

If you keep an eye on your web app, while you are adding this, you will see how site automatically refreshes itself. Try to add more items the same way, to see how it works in realtime.

Let’s see how we are looking at this point :

Our first submitted yaks

We could add stuff from database or client forever, but to make submitting available also for our users, we need to create different route and template. Create new yak_submit.html file on client and add in it :

<template name="yaksSubmit">

<div class="yaksSubmit">

<div class="form-style">

<div class="container">

<form class="yaksSubmitForm">

<h3>Send yak. It's anonymous</h3>

<input autofocus="autofocus" type="text" id="yak" class="yak-form form-control" name="yak" placeholder="Type your yak here"/>

<input type="submit" value="Send yak" class="btn btn-primary">

</form>

</div>

</div>

</div>

</template>

Now we have to again create route with Iron Router :

Router.route('/submit', {name: 'yaksSubmit'});

If you click on the send yak button in the navbar, it should take you to the “/submit” page. Let’s make that interface you see work, now that template and route are ready. When users create new yak, we need to make a record in the collection. This is done by taking help of events in Meteor. Create yak_submit.js on client and add the following into it :

Template.yaksSubmit.events({

'submit .yaksSubmitForm': function(event) { event.preventDefault();

var yak = event.target.yak.value; // get yak value // check if the value is empty

if (yak == "") {

alert("You can’t insert empty yak. Try to write something funny instead.");

} else {

Meteor.call('yakInsert', yak);

Router.go('yaksList');

}

}

});

Template events are similar to Template handlers. This event is the callback for all events triggered within yaksSubmit template. In our case we now need to catch the event and submit yak when user type something in the form and press enter or click on the submit button. Meteor.call(); is a method to call server to add a new item to our collection. We will get to that in second. Once everything is submitted, we load our main page so that user can see the newly created yak.

We will insert a new record via our custom method yakInsert. Create server.js in server folder and add the following :

Meteor.methods({

yakInsert: function(yak) {

var postId = Yaks.insert({

yak : yak,

score : 0,

submitted : new Date(),

});

}

});

This method yakInsert is called in our event handler on client, and store variable yak, that contains user submitted yak post. We insert new yaks securely on the server. Also, we added initial score 0 for every yak and time, when new yak is submitted.

Go back to localhost:3000 and try whole process of submitting new yak.

Adding score and vote up or down buttons

Now we need to count how many upvotes or downvotes each individual yak get. At first add the following line of bold code to yak_item.html :

<template name="yakItem">

…

<p class="break-el">{{yak}}</p>

<a class="btn" href="#"> {{score}} </a>

<a class="btn pull-right btn-small no" href="#"><i class="fa fa-chevron-down"></i></a>

…

</template>

To enable to vote up or down on submitted yaks, we need to add some functionality first. Let’s create yak_item.js and make events functions for upvoting and downvoting. At first we need to know where user clicked, so we’ll build an event to find out. Add the following code :

Template.yakItem.events({

'click':function() {

Session.set('selected_yak', this._id);

}, 'click a.yes':function() {

var yakId = Session.get('selected_yak');

Yaks.update(yakId, {$inc: {'score': 1 }});

}, 'click a.no':function() {

var yakId = Session.get('selected_yak');

Yaks.update(yakId, {$inc: {'score': -1 }});

var postId = Yaks.findOne({_id:this._id});

if (postId.score <= -3) {

Yaks.remove({_id:this._id})

}

}

});

Meteor is reactive framework so as data changes, things in app change without doing shitload of work. Session is a way to store and access data on client. It’s perfect for storing data relevant to current user’s version of the app, whether an element is shown or hidden or store currently selected item in a list as we are doing in the code above. At first we store _id or better said we set _id of this particular clicked yak to Session called selected_yak. Then we get data from Session called selected_yak and store it in variable. We update score value, what means initial score : 0 gets overwritten. We either increment or decrement by 1. In case of vote down function, we set if statement to remove all yaks that get negative score of 3. When people don’t like it, we don’t like it too.

It’s working pretty nice now, but the problem is that everyone could vote 1000 and more times up or down. That’s why we need user accounts, to be sure that our user can get only 1 vote per yak.

Adding User Accounts

Adding user accounts is really simple in Meteor. We will use two Meteor packages to create accounts. Type the following in terminal to add packages:

meteor add accounts-ui accounts-facebook

We want only logged in users to be able to vote on yaks, right ? Therefore we need to create accounts.html and insert the following code :

<template name="accounts">

{{#if currentUser}}

{{> yaksList}}

{{else}}

<div class="form-style">

<div class="container">

<div class="list-group-item">

<div class="pagination-centered">

<h2>Please login to do this</h2>

<div class="btn">

{{> loginButtons}}

</div>

</div>

</div>

</div>

</div>

{{/if}}

</template>

If you’re asking what this {{> loginButtons}} is, it automatically adds the Facebook login button to our template. Once the user successfully connects, the variable currentUser points to the connected user. So, we can do simple if..else check to show either login button or list of all yaks, if user is already connected.

Now we need to set routing to ‘/login’ template to display it when not logged in user try to vote :

Router.route('/login', {name: 'accounts'});

Go back to yak_item.html and edit as following :

<template name="yakItem">

<div class="yak">

<li class="list-group-item">

<a class="yakPost" href="#">

<p class="break-el">{{yak}}</p>

{{#if currentUser}}

<a class="btn" href="#"> {{score}} </a>

<a class="btn pull-right btn-small no" href="#"><i class="fa fa-chevron-down"></i></a>

<a class="btn pull-right btn-small yes" href="#"><i class="fa fa-chevron-up"></i></a>

{{else}}

<a class="btn" href="#"> {{score}} </a>

<a class="btn pull-right btn-small" href="/login"><i class=”fa fa-chevron-down”></i></a>

<a class="btn pull-right btn-small" href="/login"><i class="fa fa-chevron-up"></i></a>

{{/if}}

</a>

</li>

</div>

</template>

We simply say that if there is current user, enable vote up or down, else don’t allow user to vote and link to login template instead. Now go to login template and configure the app to use your Facebook app id and app secret key. Just follow instructions given by package developer and it should work. Once you work on that, it will look like usual Facebook login button.

But as you might noticed, we don’t have a way to logout, because if we are logged in, our login template shows only list of yaks. Let’s just simply add another login button to our submit form, to enable users to log out :

<template name="yaksSubmit">

...

<form class="yaksSubmitForm">

{{#if currentUser}}

<div class="pull-right">

{{> loginButtons}}

</div>

{{/if}}

<h3>Send yak. It’s anonymous</h3>

...

</template>

Now we can easily log out in submit form. It’s not the super user-friendly solution, but hey, it’s easy and good enough to do so.

Newly created Facebook login button

Now we need to finish work on voting. At first we need a way to check if user voted on particular yak or not. When she already did, we do nothing and when she did not, we update score value and memorize that she has already voted. Sounds easy, let’s rewrite yak_item.js to make that happen :

Template.yakItem.events({

'click':function() {

Session.set('selected_yak', this._id);

},

'click a.yes':function() {

if(Meteor.user()) {

var postId = Yaks.findOne({_id:this._id})

if ($.inArray(Meteor.userId(), postId.voted) !== -1) {

return "Voted";

} else {

var yakId = Session.get('selected_yak');

Yaks.update(yakId, {$inc: {'score': 1 }});

Yaks.update(yakId, {$addToSet: {voted : Meteor.userId()}});

}

}

}, 'click a.no':function() {

if (Meteor.user()) {

var postId = Yaks.findOne({_id:this._id})

if ($.inArray(Meteor.userId(), postId.voted) !== -1) {

return "Ok";

} else {

var yakId = Session.get('selected_yak');

Yaks.update(yakId, {$inc: {'score': -1 }});

Yaks.update(yakId, {$addToSet: {voted : Meteor.userId()}});

if (postId.score <= -3) {

Yaks.remove({_id:this._id})

}

}

}

}

});

This looks a bit tricky and it actually was. It took me quite longer (so long) to get right. I already decribed basic process above the code, but let’s go through it anyway. First we make sure user is logged in. If build in Meteor function Meteor.user() is true we are good to go. Make sure to check out official docs to learn such a things. Then we find and store this._id of particular post in variable postId and asks with jQuery function inArray() if userId of current user already voted on current post. If she hasn’t voted yet, we update value of score and add her userId to the array of users who voted called voted. Let’s run it to see how it works. It should allow us to vote only once per yak.

As yaks in the list might have different value of score. We need to align them in descending order. To do so, edit code in client.js :

Template.yaksList.helpers({

yaks: function() {

return Yaks.find({}, {sort : {score: -1}});

}

});

That’s it, now only one part left. Since we are build social app, there has to be a place for commenting on yaks. But before we do that, let’s try to deploy our humble app, to see it live, perhaps try it on mobile device too. To deploy Meteor app, create name for your app and run the following in the terminal :

meteor deploy yourappname.meteor.com

Adding comments

The goal of a social site is to create a community of users, what will be hard to do without providing a way for people to talk to each other. So now let’s add comments ! At first we need a new collection to store comments in. Open up main.js and add following line of code to create new collection :

Comments = new Mongo.Collection("comments");

To display comments on single yak post, we need to create routing to single yaks. Obviously, we need dynamic route to display different yaks. That’s why we create new template named yakPage which renders the same yakItem. Insert the following into the yak_page.html :

<template name="yakPage">

<div class="form-style">

<div class="container">

{{> yakItem}}

<div class="comment">

<h4>Comments</h4>

{{#each comments}}

{{> commentItem}}

{{/each}}

</div>

</div>

</div>

</template>

I guess this template is pretty straightforward now. We iterate over comments for every comment item. Let’s create yak_page.js and add the following code in it :

Template.yakPage.helpers({

comments: function() {

return Comments.find({postId:this._id});

}

});

To be sure we find the relevant comments, we only find those that are linked to current yak post via the postId attribute. Basically we are saying, display comments linked to this particular yak. We need to define interface for every inserted comment. It’s really easy to do, create new template in comment_item.html and add this :

<template name="commentItem">

<li class="list-group-item">

<p class="break-el">{{body}}</p>

</li>

</template>

We only insert the body of the comment to the comment item. Then we will show the number of comments on each posts, which will be also the link to the yakPage template, where we display comments. Open up and edit yakItem template the following way :

<template name="yakItem">

<div class="yak">

<li class="list-group-item">

<div class="yakPost">

<p class="break-el">{{yak}}</p>

{{#if currentUser}}

<a class="btn" href="{{pathFor 'yakPage'}}">{{commentsCount}} comments</a>

<a class="btn pull-right btn-small no" href="#"><i class="fa fa-chevron-down"></i></a>

<a class="btn pull-right" href="#"> {{score}} </a>

<a class="btn pull-right btn-small yes" href="#"><i class="fa fa-chevron-up"></i></a>

{{else}}

<a class="btn" href="{{pathFor 'yakPage'}}">{{commentsCount}} comments</a>

<a class="btn pull-right btn-small" href="{{pathFor 'accounts'}}"><i class="fa fa-chevron-down"></i></a>

<a class="btn pull-right" href="#"> {{score}} </a>

<a class="btn pull-right btn-small" href="{{pathFor 'accounts'}}"><i class="fa fa-chevron-up"></i></a>

{{/if}}

</div>

</li>

</div>

</template>

And add the commentsCount helper to yak_item.js :

Template.yakItem.helpers({

commentsCount: function() {

return Comments.find({postId:this._id}).count();

}

});

You should now see something similar to this :

If that’s the case we are good to continue, because there is still something that we’re missing here. You guessed it right, our users still don’t have a way to comment on submitted yaks. Let’s enable it by creating submit form, the process will be pretty similar to how we’ve already allowed users to create yaks. At first let’s add submit box in yakPage template :

<template name="yakPage">

...

{{/each}}

</div> {{> commentSubmit}}



</div>

</div>

</template>

And now we need to create comment form template in comment_submit.html :

<template name="commentSubmit">

<form name="comment" class="comment-form form">

<div class="form-group">

<input autofocus="autofocus" name="body" id="body" class="comment-input form-control" placeholder="Comment here">

<input type="submit" value="Add comment" class="btn btn-primary">

</div>

</form>

</template>

To submit our comments, create comment_submit.js and add following code, we will go through it step by step, because this one is kinda tricky :

Template.commentSubmit.events({

'submit form': function(e, template) {

e.preventDefault(); var $body = $(e.target).find('[name=body]');

var comment = {

body: $body.val(),

postId: template.data._id,

submitted: new Date()

}; var commentBody = e.target.body.value;

// Check if the comment is not empty

if (commentBody == "") {

alert("You can’t insert empty comment. Try to comment something nice instead.")

} else {

Meteor.call('commentInsert', comment);

} // clear input field

e.target.body.value = "";

}

});

The process here is to find element with the name body and store it in the $body variable. $ indicates it’s jQuery object and we are using jQuery function $.find(). Then we create variable comment and insert 3 different keys with values in it. At frist key body with value of the body element, second postId where we store _id of current yak. Template.data._id is basically the same as this._id. Third value is current time. Then we create an if function to deny users to submit empty comments and call server to insert comment when it’s not empty. Then just clear input field. Sounds pretty easy now, huh ?

Let’s insert our users’s comments now on server.js. It’s pretty easy, just add the bold code, don’t forget the coma:

Meteor.methods({

yakInsert: function(yak) {

var postId = Yaks.insert({

yak : yak,

score : 0,

submitted : new Date(),

});

},

commentInsert: function(comment) {

Comments.insert(comment);

}

});

I guess that’s it and only thing that left is route to individual yak post, where users comment. Let’s define it then :

Router.route('/yaks/:_id', {

name: 'yakPage',

data: function() {

return Yaks.findOne(this.params._id);

}

});

We define route in structure of ‘/yaks/<ID>’ where _id is the particular yak’s _id. Id is specific for every yak. Remember template.data._id from submit comment form ? Now we see how it’s used to create data function. We find one particular yak. To get better explanation jump over to this post on Stackoverflow.

If you are reading this and I haven’t made lot of typos, I’m sure you can see something similar to this :

Commenting works

Deploying our app

Let’s make our app live and deploy it on meteor provided free hosting as we did earlier :

meteor deploy yourappname.meteor.com

Congrats ! We are live and also done ! Our social sharing app still lacks many possible features so I encourage you to add more features or change things as you like and tweet me your creations at @mhlavacka. I would love to see it. For example it would be nice to add geolocation and get geolocated posts from people around you.

Few more thoughts about my challenge to build 6 simple apps in 6 weeks to learn Meteor.

This week was really hard to manage, I still have to visit classes at college and apparently I have social life too (I don’t). For example I set only half a day to write this tutorial and now I can tell how naive and wrong I was. Let’s say I drank 20 cups of coffee in my whole life and 15 of them just this week, while figuring things out. I built the app three times in total, because after building it for the first time I felt like :

Building the app for the first time

Then I built it for the second time while taking notes on the process to actually understand what I was doing and also to find a way to explain it in human language in this tutorial. Surprise surprise, it got easier and I think I look like this right now :

Finally I got it

Keep rocking till the next week and let me know what you think via comments or at @mhlavacka on twitter. See you in week with tutorial to the new app and thanks for following this. I appreciate it ❤