Skip to content

Routes Templates Controllers

stephen white edited this page Mar 4, 2015 · 41 revisions

Exercise 3 - The Goal

In this exercise you will become more familiar with routing, controllers (and their actions) partial templates and finally the RESTAdapter for communicating to the REST API by replacing the fixture adapter.

Proceed by following the steps below.

1. Get the code from Github

By downloading the code from our Github repository you will get all the exercises. In this part of the tutorial we will be working on Exercise3

You can get the code either by running git clone ... OR by downloading the ZIP.

Github URL is: cadec-2015-ember

2. Install Node and Bower dependencies

Before continuing with the exercise you must get all the needed dependencies for our new Ember app. Assuming that npm proxy settings are properly configured, you should be able to issue following commands with success:

   cd exercise3
   ember install

As a result you will now see 2 new folders under your Exercise3: node_modules and bower_components Now you are ready to start the ember application:

      ember server

3. Generate a new post route for URL /posts/new

Go into the exercise3 dir( if not already there): cd exercise3/ Create the new route by running following command

ember g route posts/new

This will create a route, a template and a test :

installing
  create app/routes/posts/new.js
  create app/templates/posts/new.hbs
installing
  create tests/unit/routes/posts/new-test.js

Now open the main router exercise3/app/router.js. You should see that a new sub route has been amended to the routing map :

import Ember from 'ember';
import config from './config/environment';

var Router = Ember.Router.extend({
  location: config.locationType
});

Router.map( function() {
  this.resource("posts", function() {
    this.route("new");
    this.resource("posts.post", {
      path: ":post_id"
    }, function() {
      this.route("comments");
    });
  });
});

export default Router;

3.1 Add heading to the new.hbs template

Open exercise3/app/templates/posts/new.hbs edit it like follows:

<h2>New Post</h2>
{{outlet}}

3.2 Add New Post button

Lets create a button that will link us to the /posts/new URL. Open exercise3/app/templates/posts.hbs and add a link to the /posts/new URL like follows :

<div>
  <h1>Posts</h1>
  <hr>
</div>
<div class="row">

  <div class="col-md-3">
    {{link-to 'New Post' 'posts.new' class="btn btn-default btn-lg"}}
    {{#each post in model}}
      <div>
        <h4>
          {{#link-to 'posts.post.comments' post}}
            {{post.title}} {{/link-to}}
          <p></p>
        </h4>
        <hr>
      </div>
    {{/each}}
  </div>
  	
  <div class="col-sm-8 col-sm-offset-1">
    {{outlet}}
  </div>

</div>

If your server is not running then start it by following command:

exercise3/ember server

Navigate to http://localhost:4200/posts in your browser. You should now see the 'New Post' button.

When you click the 'New Post' button you should be redirected to the posts/new route.

We have now created a subroute to the posts resource ( route ). Remember that a resource is a route that can contain nested routes/resources.

4. Override model in the posts/new route

A controller always gets it's model from the router by overriding the routes model function or setting it directly by overriding the routes setUpController function. In our case we're going to override the Routes model function and return a newly created post model.

Open route exercise3/app/routes/posts/new.js and edit it like follows:

import Ember from 'ember';

export default Ember.Route.extend({
  model: function() {
    return this.store.createRecord('post');
  }
});

Then open template exercise3/app/templates/posts/new.hbs and edit it like follows:

<form>
  <div class="form-group">
    <label for="title">Title</label>
    {{input type='text' value=title class="form-control" id="title" placeholder='Enter Title'}}
  </div>
  <div class="form-group">
    <label for="body">Body</label>
    {{textarea value=body cols="50" rows="6" class="form-control" id="body" placeholder='Enter Body'}}
  </div>

  <button {{action "done" this}} class="btn btn-default">Done</button><button {{action "cancel" this}} class="btn btn-default">Cancel</button>
</form>

What we're doing here is setting up double bindings between the new post model returned by the route into the input and text area helper by setting the respective value properties.

Once this is done make sure your running ember server ( exercise3 dir) and navigate to the URL http://localhost:4200/posts

Click on the New Post button, you should now see a simple new post form.

If you type a title you should see it showing up in the posts list, this is the double binding at work.

5. Generate New post Controller

We have a problem, in that if you click on the done or cancel button nothing happens or more like you'll get an error in the console : Nothing handled the action 'done'.

To solve this we'll need to create a new controller, note that ember is automagically creating a controller, if we want anymore some usable functionality we need to create our own but following the conventions. As we have a route of posts/new we need to create a controller that reflects this naming standard, so open your terminal, make sure your in the exercise3 directory and run following command:

  ember g controller posts/new

You should see the following output :

installing
  create app/controllers/posts/new.js
installing
  create tests/unit/controllers/posts/new-test.js

Lets go and create some actions in the exercise3/app/controllers/posts/new.js controller to handle actions done and cancel. Open the new controller exercise3/app/controllers/posts/new.js and edit as follows:

import Ember from 'ember';

// needs to be an ObjectController because we're working on 1 object!!
export default Ember.ObjectController.extend({
  actions: {
    done: function() {
      var self = this;
      this.get('model').save().then(function(post){
        self.transitionToRoute('posts.post.comments', post.get('id'));
      }, function(err) {
        console.log(err);
      });
    },
    cancel : function(post){
      post.destroyRecord();
      this.transitionToRoute('posts');
    }
  }
});

5.1 Test and verify the new post action

Go back to the browser, click the 'New Post' button write a post and click done ... Your post should now show up in your list and be saved by ember-data. Try this again and press cancel, you should see that the new post model has been destroyed.

The done action

here we retrieve the current model ( new posts model ) and save it, simple ... although you'll notice we're dealing with a deferred hence the .then closure. When saving a record we would normally do a post/put to the server as a result we need to defer further execution. so in our case we don't transition until the post has been completed.

5.2 Test and verify the cancel action

With the destroyRecord we can move on immediately and let the destroy happen in the background.

6. Add comments

At this point you'll probably thinking about the comments that we're redirected to.

6.1. Comments controller

Lets go ahead and create the Add comment button and a comments controller. We've already created the route in the previous exercise so just continue with the following :

ember g controller posts/post/comments

Open exercise3/app/controllers/posts/post/comments.js change controller type to ArrayController ( it contains an array of comments !):

export default Ember.ArrayController.extend({

Amend template exercise3/app/templates/posts/post/comments.hbs by adding an Add Comment button:

<div class="page-header">
  <h1>Comments</h1>
</div>
<ul class='comments'>
  {{#each comment in model}}
    <li class="alert alert-info">{{comment.body}}</li>
  {{/each}}
</ul>
<hr>
{{#if isCommenting}}
  <form>
    <div class="form-group">
      <label for="commentText">Comment :</label>
      {{textarea value=commentText cols="50" rows="6" class="form-control" id="commentText"}}
    </div>
    <button {{action "post" this}} class="btn btn-default">Post</button>
  </form>
{{else}}
  <button {{action "addComment" this}} class="btn btn-default">Add Comment</button>
{{/if}}
{{outlet}}

Verify that the Add Comment button apears by browsing to http://localhost:4200/posts/1

6.2. Add Comment Action

We need to cover the action of the Add Comment button, to amend the controller.

Open exercise3/app/controllers/posts/post/comments.js and amend like follows:

import Ember from 'ember';

export default Ember.ArrayController.extend({
  // retrieve the post model
  needs: "posts/post",
  postModel: Ember.computed.alias("controllers.posts/post.model"),

  // controlls the visibility of the comments text area
  isCommenting : false,

  commentText : '',
  actions : {
    post : function(){
      var self = this;
      var postModel = this.get('postModel');
      var now = new Date();
      var comment = self.store.createRecord('comment', {
        body : self.get('commentText'),
        date: now,
        post: postModel
      });


      comment.save().then(function(){
        postModel.get('comments').then(function(comments){
          comments.addObject(comment);
          postModel.save().then(
            function(){
              self.set('commentText', '');
              self.set('isCommenting', false);
            },
            function (error) {
              console.log("API error occured - " + error.responseText);
              alert("An error occured - REST API not available - Please try again");
            });
        });
      }, function(err) {
        console.log(err);
      });

    },
    addComment : function(){
      this.set('isCommenting', true);
    }
  }
});

Test in your browser that you can add new comments on existing posts.

7. Edit post

To edit a post we first need an edit button. Open exercise3/app/templates/posts/post.hbs and the Edit button like follows:

{{#if isEditing}}
  {{partial "posts/p-edit"}}
{{else}}
  <button {{action "edit" this}} class="btn btn-default">Edit</button>
{{/if}}
<h3>{{title}}</h3>
<p>{{body}}</p>
<hr>
{{outlet}}

7.1 Add Edit post action handler

We need to add a custom action to the posts/post controller that will handle Edit action from the posts/post route. First let's create the posts/post controller :

  ember g controller posts/post

Open exercise3/app/controllers/posts/post.js and amend like follows:

import Ember from 'ember';

export default Ember.ObjectController.extend({
  isEditing : false,
  actions : {
    edit : function(){
      this.set('isEditing', true);
    },
    done : function(post){
      this.set('isEditing', false);
      post.save();
    },
    cancel : function(post){
      post.rollback();
      this.set('isEditing', false);
      this.transitionToRoute('posts.post.comments', post.get('id'));
    }
  }
});

This will cover the actions of the edit button.

7.2 Partial template

We have added handler that will cover the actions of the edit button, although if you closely look at the _exercise3/app/templates/posts/post.hbs _template you'll notice :

{{#if isEditing}}
  {{partial "posts/p-edit"}}
{{else}}

We'll create a partial template that includes the post form, which we can share between the new post and edit post. To create the partial template run following command:

ember g template posts/p-edit

Open exercise3/app/templates/posts/p-edit.hbs and paste the following :

<form>
  <div class="form-group">
    <label for="title">Title</label> with de
    {{input type='text' value=title class="form-control" id="title" placeholder='Enter Title'}}
  </div>
  <div class="form-group">
    <label for="body">Body</label>
    {{textarea value=body cols="50" rows="6" class="form-control" id="body" placeholder='Enter Body'}}
  </div>

  <button {{action "done" this}} class="btn btn-default">Done</button><button {{action "cancel" this}} class="btn btn-default">Cancel</button>
</form>

Add the p-edit.hbs partial to the exercise3/app/templates/posts/new.hbs. The template should look like following:

<h2>New Post</h2>
<hr>
{{partial "posts/p-edit"}}

{{outlet}}

Notice that we're replacing the form with the partial.

At this point we now have a fully editable blog with comments.

We have one problem though, in that when we refresh the page our edits vanish ... It's time to talk to the backend. This is done by changing the application adapter to a rest adapter.

8. Switching to the RestAdapter

open : exercise3/app/adapters/application.js

and paste the following over the file :

import DS from 'ember-data';

export default DS.RESTAdapter.extend({
    namespace: 'api'
});

Going back to the browser the data may have vanished.. in which case we need to reset the database. Navigate to : http://localhost:4200/api/data/reset

This will reset the mongodb default data.

Go back to : http://localhost:4200/posts/

and you should see the default data. Try adding a post and then refreshing after clicking the done button. The post should have been persisted ..