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:
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:
- 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).
- Emptying the ‘New Artist’ field disables the ‘Add’ button (same goes for songs for a given artist).
- Clicking an artist’s name lists their songs on the right.
- 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:
- There is an implicit
application
route.This gets activated for all requests (transitions). - There is an implicit
index
route.This gets entered when the user navigates to the root of the application. - 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
. - A simple (non-resource), nested route will have its parent route name as its prefix.The route we defined as
this.route('songs', ...)
will haveartists.songs
as its name. It gets triggered when the user navigates to/artists/pearl-jam
or/artists/radiohead
. - If the path is not given, it is assumed to be equal to the route name.
- 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 valuepearl-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.
[/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”]