Getting Started With EmberJS
In this tutorial we are going to take a look at the excellent EmberJS framework & how to get started using it. This tutorial is aimed at those who have never used EmberJS before, but do have minor to intermediate knowledge of jQuery. I also assume good knowledge of CSS & HTML.
EmberJS is exceptionally helpful because it can create persistent binds between variables and the templating system. This means that when a variable is changed Ember will automatically bubble the change throughout the code. To most this may seem like magic, but hopefully as we build the small application I have planned it will become clear how it works.
Why Use EmberJS?
I’m trying to keep this tutorial as newbie friendly as possible, but this is a question I’ve been struggling to answer with newbie’s in mind. Basically it makes things quicker & saves you from repeating the same code multiple times. Take the sample application we are going to make. I can’t envision how many lines of standard jQuery it would take to build it. However it was made with about 80-90 lines of Ember.
The Sample EmberJS Application
Our sample application is going to be, originally enough, a Twitter application. I know, I know. It’s overused, but I’ve tried to make it as different and as exciting as possible. So let’s get going.
First I know you guys/gals love to see a working example, so here is a demo.
Now you’ve had time to play around with that a little let’s get onto how it was created.
Starting With Our HTML
To start with we are going to set up our HTML file. It’s a simple HTML 5 based file. Here is what you might use:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 |
<!doctype html> <html> <head> <title>Ember Based Twitter Search</title> <meta name="viewport" content="width=device-width, initial-scale=1.0"> <link rel="stylesheet" href="css/bootstrap.min.css"> <link rel="stylesheet" href="css/bootstrap.override.css"> <!--[if lt IE 9]> <script src="http://html5shim.googlecode.com/svn/trunk/html5.js"></script> <![endif]--> <script src="http://cdnjs.cloudflare.com/ajax/libs/jquery/1.7.2/jquery.min.js"></script> <script src="http://cdnjs.cloudflare.com/ajax/libs/jquery-timeago/0.9.3/jquery.timeago.js"></script> <script src="http://cdnjs.cloudflare.com/ajax/libs/ember.js/0.9.8/ember-0.9.8.min.js"></script> <script src="js/app.js"></script> </head> <body> </body> </html> |
As you can see I’m using a standard HTML 5 setup with the HTML Shim to help IE work correctly. I’ve also attached the needed scripts (jQuery, TimeAgo, and EmberJS) from the excellent CDN at cdnjs.com.
I am also using Twitter Bootstrap to allow me to quickly & easily create a layout for our little application. I advise using the Bootstrap too, if you are following allong, or you can use an alternative or go from scratch.
Getting To Grips With Ember
In this example I’m putting all the Javascript (relating to Ember) in one file and separating the parts by comments. However in the case of larger applications I would advise splitting the code into four files called model, view, controller & a final one (possibly called init.js) for initialization code, and other random bits & bobs. As I said in this small example I’m using a single file, in my case, this is the file called app.js
which you may have noticed attached via a script tag in the HTML I showed earlier.
Also please note that I’m not going to go into too much detail as to not confuse things. Instead I’m going to try and explain everything needed to understand the code without getting too complicated.
First up we need to deal with the application code, this would go in your init file. Please note that Em
is a shorthand way to refer to the main Ember object called Ember
. It is used for brevity.
1 2 3 4 5 6 7 |
App = Em.Application.create(); App.SearchTextField = Em.TextField.extend({ insertNewline: function() { App.tweetsC.loadTweets(); } }); |
Here we are creating our application & using one of Ember’s views to create a text field. App
could be thought of as a container for all of the methods, properties and data we are going to use in this application. This is known as our namespace. There is one important caveat relating to the naming of namespaces, it must start with a capital letter to allow bindings (more on that later). Obviously you’d call it something more original that App, but I named it that (original, I know) while I was playing around with Ember & just never changed it.
The second part creates a text field using the special views Ember has already embedded within it’s code. This one creates a text field & allows you to attach events to it extremely simply. In this case we attach an event to the insertNewline
event, also known as hitting enter. When an event that inserts a new line, as it doesn’t strictly have to be the enter key, occurs it will trigger the function I have named. We’ll discuss what that function does soon.
Next we can move onto the model.
1 2 3 4 5 6 |
App.Tweet = Em.Object.extend({ profile_pic: null, user_name: null, date: null, text: null }); |
This is pretty simple code, but slightly difficult to understand as a concept. We are simply creating a simple object that will store each Tweet’s data. Think of this as the blueprint which we will create copies of to contain the tweet data. This is actually an awesome way to store data as later we can create a new ‘Tweet’ and store our data inside it, then we have extremely easy access to it later in a nice neat package (object).
Next we move to our views.
1 2 3 4 5 |
App.tweetsV = Em.View.extend({ loaderBinding: 'App.tweetsC.loader', firstRunBinding: 'App.tweetsC.firstRun', limitBinding: 'App.tweetsC.limit' }); |
This is where we start to get to the meat of things. This bit of code deals with our views a little & introduces us nicely to bindings. Bindings are exceptionally clever and allow Ember to dynamically change your template whenever the value of the bound variable changes. Here we are using it for two parts of our code. The first, to display a loader while our tweets are loaded from Twitter, the second is to allow us to show a small message before Tweets are loaded. The Third is mainly for the demo & just allows us to show a message if we run out of API requests.
We’ll discuss bindings a bit more later, but for now please note a few things. First you must end the property with Binding
. The second is that it doesn’t have to match the name of the property you are binding, but when you request the value in your template you must use the new name, minus the Binding
part. Finally the value should be the object based path to the property you wish to bind to.
Finally let’s move onto our controller.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 |
App.tweetsC = Em.ArrayController.create({ content: [], term: '', loader: false, firstRun: true, limit: false, loadTweets: function() { var me = this; var term = me.get('term'); var first = me.get('firstRun'); //show loader me.set('loader', true); //no longer the first run. if(first == true) me.set('firstRun', false); if ( term ) { var url = 'http://search.twitter.com/search.json?q=%@&rpp=15&include_entities=true&result_type=recent&callback=?'.fmt(encodeURIComponent(me.get('term'))); $.ajax({ url: url, dataType: 'JSON', success: function(data) { var tweets = data.results; me.set('content', []); $(tweets).each(function(index,tweet){ //Reformat date var d = new Date(tweet.created_at); var ago = $.timeago(d); var t = App.Tweet.create({ profile_pic: tweet.profile_image_url, user_name: tweet.from_user, date: ago, text: tweet.text }); me.pushObject(t); }); me.set('loader', false); }, complete: function(xhr) { if(xhr.status == 400 || xhr.status == 420) { me.set('limit', true); } } }); } } }); |
This huge block of code is the main function. It’s grabs the tweets and hands them over to Handlebars, Ember’s templating system. Let’s go through the code and take a look at how it works.
A controller can be just another object, created using Em.Object.create()
but in this case we want to output a list of data. An ArrayController
is perfect for that job, so we create a one of those instead. Without going into too much detail it contains an automatically bound content
property which can store data, it can then be extremely easily looped through via our Handlebars template and whenever that content changes, it is immediately updated in our template.
The next few lines simply set things up. We make sure our content
variable is empty, create a empty string called term
to hold the search term, set loader
to false since we aren’t loading yet, our firstRun
to true since on page load it will always be our first run, and set limit
to false since we haven’t hit Twitters API limit yet.
The final property is a function. This is the heart of the code & loads our tweets. Let’s look through it. First we create a variable (me
) that will contain a reference to this
this is important as we need access to the classes methods such as get
& set
to grab the value & set the value of properties contained within the object & maintain bindings. this
is a reference to the current object and allows you access to all the properties & methods of the object you are currently working within.
Next we store our search term, and first run status in variables for convenience. As mentioned we get the values from our class using get
. You may be wondering how term
isn’t empty, I’ll explain later when I go through explaining the template code. Now we are going to start getting our Tweets so we set loader
to true. We also check to see if first
is true, if it is we can now set it to false as we are now in the middle of running the code so the next run won’t be the first run. Because these are bound properties they will instantly update the template (when we create it) meaning you will be able to see feedback straight away.
We next check if we have a term
to use. If we have we can continue. You might want to extend this a little by adding a optional action (such as showing an error) if a term hasn’t been provided. We also create our URL, we do this here to keep things neat instead of cluttering our AJAX request. You may be wondering what fmt()
does. Well it is similar to a find and replace. It takes the given data and when attached to a string, finds and replaces any %@
found with the string it is concatenated to and replaces them with the data you passed to it. The encodeURIComponent()
is used simply to prepare the term for use in a URL.
Next is a simple JSONP based AJAX request. Please bare in mind the callback=?
in the URL, this is important as it makes the AJAX request a JSONP request. It you miss this out you will more than likely get a cross domain security warning in your browser’s console. We pass our URL, set the data type to JSON and then create a function to be ran on success.
The success functions takes the data received via our request to Twitter, we then make a new variable which contains our tweets which are found in data.results
. We then delete any Tweets we may already have parsed by setting our content
property to a empty array. Next we use jQuery’s each function to loop through each tweet we gathered from Twitter. We reformat the date into ‘Time Ago’ format using the jQuery plugin we attached at the start of the tutorial by handing it a JavaScript date object. We also create a new ‘Tweet’ from our model storing each item of information we need inside it, then we push that into our controller using pushObject
, which automatically pushes it onto the content
property. Finally we turn off the loader.
The final part is a function just used to check if we have ran our of API requests. Twitter send a 400 or 420 (depending on which part of the API you are using) HTTP code. We detect that & set the limit
property which is bound to show a message in our template, which we are going to create next.
Handlebars & EmberJS
Next we can move onto creating the template for Ember to throw it’s data into.
I’m still getting to grips with templates myself so I’m sure there are better ways to do this, but this seems to work for me & I haven’t had any trouble so far. If you can think of any way to improve things please drop me a comment, I’d love to see what you’ve come up with.
Please note that a lot of this code is just related to Twitter Bootstrap & layout. The important parts are those surrounded by Handlebars (curly braces).
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
<script type="text/x-handlebars"> <div id="frm" class="span12 text-center"> <strong>Search Twitter:</strong> {{view App.SearchTextField placeholder="Search..." valueBinding="App.tweetsC.term"}} <button {{action "loadTweets" target="App.tweetsC"}}>Go!</button> </div> <div id="content" class="span12"> <div class="row"> <div id="tweets" class="span12"> <h3 class="text-center">Tweets</h3> {{#view App.tweetsV}} <div class="row"> {{#if firstRun}} <div class="span6 offset3"> <p>To display some Tweets, please use the search box above.</p> </div> {{else}} {{#if loader}} <div class="span12 text-center loader"> <img src="css/loading.gif" width="32" height="32" /> <br /> loading </div> {{else}} {{#if limit}} <div class="span6 offset3"> <p>Oh Dear! It appears we've hit Twitter's API limit. Please try again in an hour</p> </div> {{else}} <div class="span6 offset3"> {{#each App.tweetsC}} {{#view App.tweetV contentBinding="this"}} <div class="row"> <div class="span6 clearfix tweet"> <div class="pull-left tweet_profile_pic"> <img {{bindAttr src="content.profile_pic"}} /> </div> <div class="pull-right tweet_data"> <div class="clearfix tweet_meta"> <div class="pull-left"><strong>{{content.user_name}}</strong></div> <div class="pull-right"><small>{{content.date}}</small></div> </div> <p>{{content.text}}</p> </div> </div> </div> {{/view}} {{/each}} </div> {{/if}} {{/if}} {{/if}} </div> {{/view}} </div> </div> </div> </script> |
This is quite a large block of code, but please don’t despair. We only need to touch on those handlebars, as I mentioned above. First though you may notice the HTML is inside script tags. This is because for Ember to parse a template it needs to be inside special Handlebars script tags.
First we use an Ember helper, view, to display our text field that we created way back at the start of this tutorial. We also add a placeholder, which uses HTML5 so please be aware of degradation in older browsers, and also a value binding. This binds the term property, from our controller, to the value of the text field. This live updates the value held by our controller in real time as the user types. As I mentioned in the controller, this is how term
is not empty. Ember automatically updates the term in the controller with the value held in this text field.
We also use the action helper on our button. This binds a click event to our button which triggers our loadTweets
method. The target attribute tells the helper which object the method we want to call is held in, in this case loadTweets
is inside our controller (App.tweetsC
).
Next is the block of code that displays the Tweets. First we define a view, remember this should be the same name as the one we created in our Ember code earlier. This will allow our binds to be used. We next use simple logic blocks to show different messages based on the bound properties. Because the properties update in real time, the values will bubble up all the way through to our template & the correct message for the properties value will be shown. Isn’t it awesome?
The next important part is really {{#each App.tweetsC}}
. This is really like writing {{#each App.tweetsC.content}}
but the controller knows you want the content & uses that. This simply loops through each tweet we stored in our content property and prints the information you provide for each one. We also create a new view for each tweet this is for various reasons, including the ability to count which item you are up to (for grid based layouts), but is a little outside of the scope of this tutorial.
Finally inside our ‘each’ we output our data. The only stand out piece here is the bindAttr
helper. This allows you to bind HTML attributes. You might think: “Why do I need a helper for that?”. Well Ember binds by creating script tags around the bound element to help locate it’s position for updating. This is a problem inside HTML tags as a script tag will be placed in-between the wakas of an HTML tag & break your code. This helper allows you to add attributes to your HTML tags without breaking them. It’s usage should be fairly self evident to those familiar with HTML, except for the fact you can use variables from the current context inside it’s values.
Are We All Done?
You know, I think that might be it. This has been quite a large tutorial and a little exhausting to try and explain. I have tried my best to keep things simple and understandable without losing too much of the knowledge you need to start working with EmberJS.
My advice would be to follow the tutorial all the way through, then download the source files & see if you can play around with it. Change things, see what happens. Try to add new features. If you break it, undo or re-download the source files. Trial & error is, in my opinion, one of the best ways to learn. Provided you have a backup, there is nothing you can lose.
If you are having trouble understanding anything in this tutorial please feel free to leave a comment. Also bare in mind I am not an advanced Ember user, I’m a WordPress/PHP guy by nature and have only just recently started learning it myself. There may be some things I could have done better. If you can see anything please let me know.
A Small Modification
I got a tweet from Trek on Twitter who has modified my Tweets controller (App.tweetsC
) slightly to show that you can create this code without using closures. This new code looks a little like this:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 |
App.tweetsC = Em.ArrayController.create({ content: [], term: '', loader: false, firstRun: true, limit: false, loadTweetsCompleted: function(xhr){ if(xhr.status == 400 || xhr.status == 420) { this.set('limit', true); } }, loadTweetsSucceeded: function(data){ var tweets = data.results; this.set('content', []); tweets.forEach(function(tweetData){ var ago = $.timeago(d); var d = new Date(tweetData.created_at); var tweet = App.Tweet.create({ profile_pic: tweetData.profile_image_url, user_name: tweetData.from_user, date: ago, text: tweetData.text }); this.pushObject(tweet); }, this); this.set('loader', false); }, loadTweets: function() { var term = this.get('term'), first = this.get('firstRun'); //show loader this.set('loader', true); //no longer the first run. if(first == true) this.set('firstRun', false); if ( term ) { var url = 'http://search.twitter.com/search.json?q=%@&rpp=15&include_entities=true&result_type=recent&callback=?'.fmt(encodeURIComponent(me.get('term'))); $.ajax({ url: url, dataType: 'JSON', context: this, success: this.loadTweetsSucceeded, complete: this.loadTweetsCompleted, }); } } }); |
See how Trek has created two new methods and handed those to the $.ajax
call instead. This stops us having to create functions on-the-fly (closures) and helps a little with performance. Now the performance gain is not really that much, but it is good practice to avoid creating unnecessary closures. I didn’t know this, hence why my code uses them. Now I do.
Also you may notice me
has gone. Trek solved this by using the context option of $.ajax
. This provides our controller as the context for any callbacks called by $.ajax
, meaning we can just use this
. It’s also important to note the use of forEach
instead of $.each
. With forEach
you define a callback to be ran for each item, and the second parameter is the context with which to run the function. Again this
is used to keep it within the context of our main controller..
A big thank you to Trek for sending me this it’s really helped me understand more about closures. I hope it has also helped you too.
8 Comments
Elias
Thanks, this was very helpfull!
However, please note that this doesn’t work with latest ember & handlebars (1.0.0 & 1.0.0.beta.6)
The controller seems to be OK, but something’s wrong with the view and the handlebars template. Controller attributes such as {{firstRun}} etc seem to be empty/undefined
Paul Robinson
Hi Elias,
Thanks for letting me know. Not sure why that would be, Ember/Handlebars must have changed either the scope or how Controller attributes are accessed. Sadly I don’t really use Ember any more as I had to use KnockoutJS for a project & found it suited my workflow better than Ember.
Justin
I took your code and got it working with ember 1.0-pre. I don’t really know ember.js, I’m just learning, but I’ll put a link to the code here in case anybody is interested. http://github.com/justinbkay/emberTwitterTutorial
Andrés
Very helpful. Thanks
🙂
Justin
I reworked this example to use an ember router. Works with ember 1.0-pre.2.
https://github.com/justinbkay/emberTwitterRedux
Ovini
A great article for the beginners. Thank you.
Tarun Chaudhary
A more advanced thought search engine based on atitude.. check it out
Tarun Chaudhary
Sorry for the previous comment .. i forgot to mention the link of my ember.js thought search engine .. it is more advanced real time thought search engine based on atitude .. check it out..
http://tweetsearch.site90.com