Ember JS: MVC in the browser. Part 2 – Templates

In part one we added some routes defining our posts resource and for an individual post. We also learned how to inspect our Router object to see what we had defined using App.Router.router.recognizer.names in the web console. Now to create some templates to display when we transition through our routes. Download the code here. In part one we defined an application template with a handlebars {{outlet}}. We will add templates to render in this outlet.

Go back to index.html and add the following:

<script type="text/x-handlebars" data-template-name="index">
  {{#linkTo 'posts'}}Posts{{/linkTo}}

<script type="text/x-handlebars" data-template-name="posts">

Refresh the page and you should see a link to ‘Posts’ under the title, clicking on it will take you to a page with the title and the word ‘Posts’. Looking at the console you can see that we transitioned from index to posts. Both of these templates were rendered in the main application template in the {{outlet}} block. Next we can add a link to individual Posts inside the Posts template and create a template for these individual Posts.

Change the ‘posts’ template to the following

<script type="text/x-handlebars" data-template-name="posts">
    {{#each model}}
      <li>{{#linkTo post this}}{{title}}{{/linkTo}}</li>

However, if you refresh the ‘/posts’ page you will no longer see anything. That is because we have no models and we definitely have no data. To remedy this we will add some. We start by creating a store. Go to app.js and add the following

App.Store = DS.Store.extend({
  revision: 12,
  adapter: 'DS.FixtureAdapter'

If you refresh the page you will probably see an error along the lines of ‘Undefined variable: DS’. This is because we need to add the ember-data library. To get it you can either clone the ember-data repository and run rake dist (https://github.com/emberjs/data#getting-ember-data) or download the latest build from http://builds.emberjs.com). In the future ember-data may be bundled along with ember but until then this is the way to get it.

So, download ember-data, add a <script> tag to index.html to load it, placing it after ember but before app.js, and refresh again. Now, the revision: 12 part is to check that it is compatible with the emberjs you are using, if not it will be reported in the console. The adapter part is optional but we are telling it to load data from fixtures that we will specify in javascript. Reload and all should be ok. We will now define a post model and some fixtures. Add the following to app.js

  title: DS.attr('string')

App.Post.FIXTURES=[{ id: "1", title: "First Post" }, { id: "2", title: "Second Post" }, { id: "3", title: "Third Post" }];

Refresh the page and…..nothing. We have to tell ember what to do when it gets to the posts route. Add the following to app.js, after the main routes definition.

  model: function(){
    return App.Post.find();

Here we tell ember that when we transition to ‘posts’ we need to find all the Post models that exist, in this case we return all the fixtures we have created. Refresh and all the posts should be shown using the title as a link. If you look at the definition for the posts template we created earlier you can see that we iterate over each of the models that the PostsRoute returns and add a link for each with the title as the text. {{title}} is a handlebars helper which tells it to get the attribute title from the current model in scope. Refresh the page and you should see links to 3 posts, hover over them to see the urls they link to. They should be linking to /posts/1 etc. Click on one of the links and you will see that we transition into post.index but there is nothing shown. Surprise, surprise we need a to define a template. Add the following to index.html after the currently defined templates:

<script type="text/x-handlebars" data-template-name="post/index">

Refresh the page and you will see this warning in the console:

WARNING: the immediate parent route ('a') did not render into the main outlet and the default 'into' option ('p') may not be expected

Rember earlier we defined the {{outlet}} in the application template, ember expects every parent template to have an {{outlet}} into which it can render its children. In this case it is saying that it wants to render post.index into post but it can’t find it. We need to define a post template with the following in index.html

<script type="text/x-handlebars" data-template-name="post">

Don’t forget to add the ‘Post’ text in the template, you will see why in a second. Refresh and you will see the ‘Post’ text but no title for the post. Well, it turns out there is a ‘feature’ (polite term for a bug) which means that in the current version the ‘default’ behaviour for a parameterised route does not work out of the box. Ember should know that because we defined the route with /posts/:post_id that it should return the model with id equal to :post_id when we transition to post.index with posts/1 etc but for the moment we have to tell it. We need to define a PostIndexController to get things back on track. Add the following code to app.js so we can see what is happening:

  model: function(params) {
    return App.Post.find(params.post_id);

Refresh and….still nothing. Ok, I knew that was going to happen, unfortunately the bug means that empty params are passed to a nested route. Use your debugger and examine the params object yourself. All is not lost however, change the model hook to

model:function(params) {
  return this.modelFor('post');

Refresh. Yes, you should now see the text ‘Post First Post’. You can remove the unneeded ‘Post’ text from the post template and refresh again. Try going to posts/2 and posts/3 to convince yourself that it is all working.

So to recap. We have created an ember application which logs transitions between routes. It has 3 routes: index, posts and post.index. We have 5 templates: the main application one with {{outlet}}. The index template ie ‘/’. The posts template. A post template with an outlet for its nested routes and a post.index template to ‘show’ each post. In part 3 we will look at nested routes.

Leave a Reply

Your email address will not be published. Required fields are marked *