Tejas Rana

Getting Started with Your First Ember.js App

[pt_text]As modern web applications do more and more on the client-side (the fact itself that we now refer to them as “web applications” as opposed to “web sites” is quite telling), there has been rising interest in client-side frameworks. There are a lot of players in this field but for applications with lots of functionality and many moving parts, two of them stand out in particular: Angular.js and Ember.js.
We already published a comprehensive [Angular.js tutorial][https://www.toptal.com/angular-js/a-step-by-step-guide-to-your-first-angularjs-app], so we’re going to focus on Ember.js in this post, in which we’ll build a simple Ember application to catalog your music collection. You’ll be introduced to the framework’s main building blocks and get a glimpse into its design principles. Should you want to see the source code while reading, it’s available as rock-and-roll on Github.

What are we going to build?

Here’s what our Rock & Roll app is going to look like in its final version:
Final version of app with ember.js
On the left, you’ll see that we have a list of artists and, on the right, a list of songs by the selected artist (you can also see that I have good taste in music, but I digress). New artists and songs can be added simply by typing in the text box and pressing the adjacent button. The stars beside each song serve to rate it, à la iTunes.
We could break down the app’s rudimentary functionality into the following steps:

  1. Clicking ‘Add’ adds a new artist to the list, with a name specified by the ‘New Artist’ field (same goes for songs for a given artist).
  2. Emptying the ‘New Artist’ field disables the ‘Add’ button (same goes for songs for a given artist).
  3. Clicking an artist’s name lists their songs on the right.
  4. Clicking the stars rates a given song.

We have a long way to go to get this working, so let’s start.

Routes: the key to the Ember.js app

One of the distinguishing features of Ember is the heavy emphasis it puts on URLs. In many other frameworks, having separate URLs for separate screens is either lacking or is tacked on as an afterthought. In Ember, the router—the component that manages urls and transitions between them—is the central piece that coordinates work between building blocks. Consequently, it is also the key to understanding the innerworkings of Ember applications.
Here are the routes for our application:

App.Router.map(function() {
  this.resource('artists', function() {
    this.route('songs', { path: ':slug' });
  });
});

We define a resource route, artists, and a songs route nested inside it. That definition will give us the following routes:

I used the great Ember Inspector plugin (it exists both for Chrome and Firefox) to show you the generated routes in an easily readable way. Here are the basic rules for Ember routes, which you can verify for our particular case with the help of the above table:

  1. There is an implicit application route.This gets activated for all requests (transitions).
  2. There is an implicit index route.This gets entered when the user navigates to the root of the application.
  3. Each resource route creates a route with the same name and implicitly creates an index route below it.This index route gets activated when the user navigates to the route. In our case, artists.index gets triggered when the user navigates to /artists.
  4. A simple (non-resource), nested route will have its parent route name as its prefix.The route we defined as this.route('songs', ...) will have artists.songs as its name. It gets triggered when the user navigates to /artists/pearl-jam or /artists/radiohead.
  5. If the path is not given, it is assumed to be equal to the route name.
  6. If the path contains a :, it is considered a dynamic segment.The name assigned to it (in our case, slug) will match the value in the appropriate segment of the url. Theslug segment above will have the value pearl-jam, radiohead or any other value that was extracted from the URL.

Display the list of artists

As a first step, we’ll build the screen which displays the list of artists on the left. This screen should be shown to the users when they navigate to /artists/:

To understand how that screen is rendered, it’s time to introduce another overarching Ember design principle:convention over configuration. In the above section, we saw that /artists activates the artists route. By convention, the name of that route object is ArtistsRoute. It’s this route object’s responsibility to fetch data for the app to render. That happens in the route’s model hook:

App.ArtistsRoute = Ember.Route.extend({
  model: function() {
    var artistObjects = [];
    Ember.$.getJSON('https://localhost:9393/artists', function(artists) {
      artists.forEach(function(data) {
        artistObjects.pushObject(App.Artist.createRecord(data));
      });
    });
    return artistObjects;
  }
});

In this snippet, the data is fetched via an XHR call from the back-end and—after conversion to a model object—pushed to an array which we can subsequently display. However, the responsbilities of the route do not extend to providing display logic, which is handled by the controller. Let’s take a look.
Hmmm—in fact, we do not need to define the controller at this point! Ember is smart enough to generate the controller when needed and set the controller’s M.odel attribute to the return value of the model hook itself, namely, the list of artists. (Again, this is a result of the ‘convention over configuration’ paradigm.) We can step one layer down and create a template to display the list:

<script type="text/x-handlebars" data-template-name="artists">
  <div class="col-md-4">
    <div class="list-group">
      {{#each model}}
        {{#link-to "artists.songs" this class="list-group-item artist-link"}}
          {{name}}
          <span class="pointer glyphicon glyphicon-chevron-right"></span>
        {{/link-to}}
      {{/each}}
    </div>
  </div>
  <div class="col-md-8">
    <div class="list-group">
      {{outlet}}
    </div>
  </div>
</script>

If this looks familiar, it’s because Ember.js uses Handlebars templates, which have a very simple syntax and helpers but do not allow for non-trivial logic (e.g., ORing or ANDing terms in a conditional).
In the above template, we iterate through the model (set up earlier by the route to an array that contains all artists) and for each item in it, we render a link that takes us to the artists.songs route for that artist. The link contains the artist name. The #each helper in Handlebars changes the scope inside it to the current item, so {{name}} will always refer to the name of the artist that is currently under iteration.

Nested routes for nested views

Another point of interest in the above snippet: {{outlet}}, which specifies slots in the template where content can be rendered. When nesting routes, the template for the outer, resource route is rendered first, followed by the inner route, which renders its template content into the {{outlet}} defined by the outer route. This is exactly what happens here.
By convention, all routes render their content into the template of the same name. Above, the data-template-name attribute of the above template is artists which means that it will get rendered for the outer route, artists. It specifies an outlet for the content of the right panel, into which the inner route, artists.index renders its content:

<script type="text/x-handlebars" data-template-name="artists/index">
  <div class="list-group-item empty-list">
    <div class="empty-message">
      Select an artist.
    </div>
  </div>
</script>

In summary, one route (artists) renders its content in the left sidebar, its model being the list of artists. Another route, artists.index renders its own content into the slot provided by the artists template. It could fetch some data to serve as its model but in this case all we want to display is static text, so we don’t need to.

Create an artist

Part 1: Data binding

Next, we want to be able to create artists, not just look at a boring list.
When I showed that artists template that renders the list of artists, I cheated a bit. I cut out the top part to focus on what’s important. Now, I’ll add that back:

<script type="text/x-handlebars" data-template-name="artists">
  <div class="col-md-4">
    <div class="list-group">
      <div class="list-group-item">
        {{input type="text" class="new-artist" placeholder="New Artist" value=newName}}
        <button class="btn btn-primary btn-sm new-artist-button" {{action "createArtist"}}
          {{bind-attr disabled=disabled}}>Add</button>
      </div>
    < this is where the list of artists is rendered >
  ...
</script>

We use an Ember helper, input, with type text to render a simple text input. In it, we bind the value of the text input to the newName property of the controller that backs up this template, ArtistsController. By consequence, when the value property of the input changes (in other words, when the user types text into it) the newName property on the controller will be kept in sync.
We also make it known that the createArtist action should be fired when the button is clicked. Finally, we bind the disabled property of the button to the disabled property of the controller. So what does the controller look like?

App.ArtistsController = Ember.ArrayController.extend({
  newName: '',
  disabled: function() {
    return Ember.isEmpty(this.get('newName'));
  }.property('newName')
});

newName is set to empty at the start which means that the text input is going to be blank. (Remember what I told about bindings? Try to change newName and see it get reflected as the text in the input field.)
disabled is implemented such that when there is no text in the input box, it is going to return true and thus the button will be disabled. The .property call at the end makes this a “computed property”, another scrumptious slice of the Ember cake.
Computed properties are properties that depend on other properties, which can themselves be “normal” or computed. Ember caches the value of these until one of the dependent properties change. It then recalculates the value of the computed property and caches it again.
Here’s a visual representation of the above process. To summarize: when the user inputs an artist’s name, thenewName property updates, followed by the disabled property and, finally, the artist’s name is added to the list.

Detour: A single source of truth

Think about that for a moment. With the help of bindings and computed properties, we can establish (model) data as the single source of truth. Above, a change in the name of the new artist triggers a change in the controller property, which in turn triggers a change in the disabled property. As the user starts to type the name of the new artist, the button becomes enabled, as if by magic.
The bigger the system, the more leverage we gain from the ‘single source of truth’ principle. It keeps our code clean and robust, and our property definitions, more declarative.
Some other frameworks also put emphasis on having model data be the single source of truth but either do not go as far as Ember or fail to do such a thorough a job. Angular, for example, has two-way bindings—but does not have computed properties. It can “emulate” computed properties through simple functions; the problem here is that it has no way of knowing when to refresh a “computed property” and thus resorts to dirty checking and, in turn, leads to a performance loss, especially notable in bigger applications.
Should you wish to learn more about the topic, I recommend you read eviltrout’s blog post for a shorter version or this Quora question for a lengthier discussion in which core developers from both sides weigh in.

Part 2: Action handlers

Let’s go back to see how the createArtist action is created after it’s fired (following the press of the button):

App.ArtistsRoute = Ember.Route.extend({
  ...
  actions: {
    createArtist: function() {
      var name = this.get('controller').get('newName');
      Ember.$.ajax('https://localhost:9393/artists', {
        type: 'POST',
        dataType: 'json',
        data: { name: name },
        context: this,
        success: function(data) {
          var artist = App.Artist.createRecord(data);
          this.modelFor('artists').pushObject(artist);
          this.get('controller').set('newName', '');
          this.transitionTo('artists.songs', artist);
        },
        error: function() {
          alert('Failed to save artist');
        }
      });
    }
  }
});

Action handlers need to be wrapped in an actions object and can be defined on the route, controller or view. I chose to define it on the route here because the result of the action is not confined to the controller but rather, “global”.
There is nothing fancy going on here. After the back-end informed us that the saving operation completed successfully, we do three things, in order:

  1. Add the new artist to the model of the template (all artists) so that it gets re-rendered and the new artist appears as the last item of the list.
  2. Clear the input field via the newName binding, saving us from having to manipulate the DOM directly.
  3. Transition to a new route (artists.songs), passing in the freshly created artist as the model for that route. transitionTo is the way to move between routes internally. (The link-to helper serves to do that via user action.)

Display songs for an artist

We can display the songs for an artist either by clicking on the artist’s name. We also pass in the artist that is going to become the model of the new route. If the model object is thusly passed in, the model hook of the route will not be called since there is no need to resolve the model.
The active route here is artists.songs and thus the controller and template are going to be ArtistsSongsController and artists/songs, respectively. We already saw how the template gets rendered into the outlet provided by the artists template so we can focus on just the template at hand:

<script type="text/x-handlebars" data-template-name="artists/songs">
  (...)
  {{#each songs}}
    <div class="list-group-item">
      {{title}}
      {{view App.StarRating maxRating=5}}
    </div>
  {{/each}}
</script>

Note that I stripped out the code to create a new song as it would be exactly the same as that for creating a new artist.
The songs property is set up in all artist objects from the data returned by the server. The exact mechanism by which it is done holds little interest to the current discussion. For now, it’s sufficient for us to know that each song has a title and a rating.
The title is displayed directly in the template and the rating gets represented by stars, via the StarRatingview. Let’s see that now.

Star rating widget

A song’s rating falls between 1 and 5 and gets shown to the user through a view, App.StarRating. Views have access to their context (in this case, the song) and their controller. This means they can read and modify its properties. This is in contrast to another Ember building block, components, which are isolated, reusable controls with access to only what has been passed in to them. (We could use a star rating component in this example, too.)
Let’s see how the view displays the number of stars and sets the song’s rating when the user clicks on one of the stars:

App.StarRating = Ember.View.extend({
  classNames: ['rating-panel'],
  templateName: 'star-rating',
  rating: Ember.computed.alias('context.rating'),
  fullStars: Ember.computed.alias('rating'),
  numStars:  Ember.computed.alias('maxRating'),
  stars: function() {
    var ratings = [];
    var fullStars = this.starRange(1, this.get('fullStars'), 'full');
    var emptyStars = this.starRange(this.get('fullStars') + 1, this.get('numStars'), 'empty');
    Array.prototype.push.apply(ratings, fullStars);
    Array.prototype.push.apply(ratings, emptyStars);
    return ratings;
  }.property('fullStars', 'numStars'),
  starRange: function(start, end, type) {
    var starsData = [];
    for (i = start; i <= end; i++) {
      starsData.push({ rating: i, full: type === 'full' });
    };
    return starsData;
  },
  (...)
});

rating, fullStars and numStars are computed properties which we discussed previously with the disabledproperty of the ArtistsController. Above, I used a so-called computed property macro, about a dozen of which are defined in Ember. They make typical computed properties more succinct and less error-prone (to write). I set rating to be the rating of the context (and thus the song), while I defined both the fullStars and numStars properties so that they read better in the context of the star rating widget.
The stars method is the main attraction. It returns an array of data for the stars in which each item contains arating property (from 1 to 5) and a flag (full) to indicate whether the star is full. This makes it exceedingly simple to walk through them in the template:

<script type="text/x-handlebars" data-template-name="star-rating">
  {{#each view.stars}}
    <span {{bind-attr data-rating=rating}}
      {{bind-attr class=":star-rating :glyphicon full:glyphicon-star:glyphicon-star-empty"}}
      {{action "setRating" target=view}}>
    </span>
  {{/each}}
</script>

This snippet contains several points of note:

  1. First, the each helper designates that it uses a view property (as opposed to a property on the controller) by prefixing the property name with view.
  2. Second, the class attribute of the span tag has mixed dynamic and static classes assigned. Anything prefixed by a : becomes a static class, while the full:glyphicon-star:glyphicon-star-empty notation is like a ternary operator in JavaScript: if the full property is truthy, the first class should be assigned; if not, the second.
  3. Finally, when the tag is clicked, the setRating action should be fired—but Ember will look it up on the view, not the route or controller, as in the case of creating a new artist.

The action is thus defined on the view:

App.StarRating = Ember.View.extend({
  (...)
  actions: {
    setRating: function() {
      var newRating = $(event.target).data('rating');
      this.set('rating', newRating);
    }
  }
});

We get the rating from the rating data attribute we assigned in the template and then set that as the ratingfor the song. Note that the new rating is not persisted on the back-end. It would not be difficult to implement this functionality based on how we created an artist and is left as an exercise for the motivated reader.

Wrapping it all up

We have tasted several ingredients of the aforementioned Ember cake:

  • We have seen how routes are the crux of Ember applications and how they serve as the basis of naming conventions.
  • We have seen how two-way data bindings and computed properties make our model data the single source of truth and allow us to avoid direct DOM manipulation.
  • And we have seen how to fire and handle actions in several ways and build a custom view to create a control that is not part of our HTML.

Beautiful, isn’t it?

[/pt_text]

[pt_text]
[/pt_text][pt_testimonials_balloon name=”About this post” quote=”This is a shared post. Originally posted on “https://www.toptal.com/“. All rights reserved to their editors. I just shared this post on Original Author’s demand to share this post on my blog.” minheight=”50″ css_animation=”fadeIn”]
Exit mobile version