Ember.js Workshop

Greg Malcolm github.com/gregmalcolm @gregmalcolm

Preparation

See README.md for details of things to install

Starting project

We're going to be changing the following files:

  • File: index.html - Handlebar templates


Ember javascript code:

  • js/app.js
  • js/models.js
  • js/fixtures.js
  • js/controllers.js
  • js/routes.js

Starting Project

We need a simple http server for Ember to work correctly.



Start the server by running this:

$ bin/server

... or...

C:\ember-beginner-workshop> bin\server.cmd

Then go look at the project in localhost:3000 in Chrome

Starting Project

Starting Project

For reference consult the Ember Guides:

Objective 1

List stories in the Index Route

Customize the Index Route to show stories instead of colors

Objective 1

List stories in the Index Route

Tasks

  • Personalize the page design
  • Change Application Template to show a simple navbar
  • Change the Index Route to use story names as the Model
  • Show stories in the Index Template

List stories in the Index Route

Task - Personalize the page design

Personalize the title

...
<title>Choose Your Own Adventure</title>
...File: index.html

List stories in the Index Route

Task - Personalize the page design

Change out the Application Template for one containing a Bootstrap navbar.


Replace this:

<script type="text/x-handlebars">
  <h2>Welcome to Ember.js</h2>
</script>File: index.html

List stories in the Index Route

Task - Personalize the page design

Change out the Application Template for one containing a Bootstrap navbar.


... With this:

<!--== Templates ==-->
<script type="text/x-handlebars">
<nav class="navbar navbar-inverse navbar-fixed-top" role="navigation"> <div class="navbar-header"> <a class="navbar-brand" href="#">Choose your own Adventure</a> </div> </nav> {{outlet}}</script>
File: index.html

List stories in the Index Route

Task - Change the Index Route to use story names as the Model

Change the Index Route in js/routes.js to look like this:

App.IndexRoute = Ember.Route.extend({  model: function() {
    return ['Temple Quest', 'Adventure!', 'Zombies', 'Death Race!'];
  }
});File: js/routes.js

List stories in the Index Route

Task - Show stories in the Index Template

In index.html, find the Index Template (it contains data-template-name="index").


Replace it with this

<!--== Templates with routes ==-->
<!-- / -->
<script type="text/x-handlebars" data-template-name="index">
<h3>Stories:</h3> <ul> {{#each title in content}} <li>{{title}}</li> {{/each}} </ul> </script>File: index.html

List stories in the Index Route

You should now have this:

Problems? Try running this to compare against a working version:

$ git diff -w fbe2d index.html js

Objective 2

Use ember-data Models and Fixtures for data

Create a new Ember Route called stories that lists the available stories

They will be viewable from http://localhost:3000/#/stories

Using ember-data Models and Fixtures for data

Tasks

  • Create a new Route called 'stories'
  • Set the Stories Route model to return 'story' Models
  • Make the default Index Route reroute to Stories Route

Using ember-data Models and Fixtures for data

Task - Create a new Route called 'stories'

Configure the Router to support #/stories.


Replace the Router configuration with this:

App.Router.map(function() {
  this.resource('stories', function() {});
});
File: js/routes.js

Using ember-data Models and Fixtures for data

Task - Create a new Route called 'stories'

Get rid of the old Index Route implementation:

App.IndexRoute = Ember.Route.extend({
  model: function() {
    return ['Temple Quest', 'Adventure!', 'Zombies', 'Death Race!'];
  }
});
File: js/routes.js

Using ember-data Models and Fixtures for data

Task - Set the Stories Route model to return 'story' Models

Uncomment the Fixture Adapter and Story Model in js/models.js. Leave first_section attribute commented out:

App.ApplicationAdapter = DS.FixtureAdapter.extend();

App.Story = DS.Model.extend({
  title: DS.attr('string'),
  //first_section: DS.belongsTo('section')
});
File: js/models.js

Using ember-data Models and Fixtures for data

Task - Set the Stories Route model to return 'story' Models

Uncomment the Story Fixture in js/fixtures.js

App.Story.FIXTURES = [
  {
    id: 100,
    title: 'Temple Quest',
    first_section: 2000
  },
  ...
];
File: js/fixtures.js

Using ember-data Models and Fixtures for data

Task - Set the Stories Route model to return 'story' Models

Implement the StoriesIndex Route

App.StoriesIndexRoute = Ember.Route.extend({
  model: function() {
    return this.get("store").findAll("story");
  }
});
File: js/routes.js

Using ember-data Models and Fixtures for data

Task - Set the Stories Route model to return 'story' Models

Remove the Index Template from index.html

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

  <h3>Stories:</h3>
  <ul>
    {{#each title in content}}
      <li>{{title}}</li>
    {{/each}}
  </ul>
</script>
File: index.html

Using ember-data Models and Fixtures for data

Task - Set the Stories Route model to return 'story' Models

Insert the replacement stories/index page

<!-- /stories -->
<script type="text/x-handlebars"
        data-template-name="stories/index">
<h3>Stories:</h3> <ul> {{#each}} <li>{{title}}</li> {{/each}} </ul> </script>File: index.html

Using ember-data Models and Fixtures for data

Task - Make the default Index Route reroute to Stories Route

Implement a new Index Route in js/routes.js

App.IndexRoute = Ember.Route.extend({
  redirect: function(params) {
    this.transitionTo("stories");
  }
});
File: js/routes.js

Using ember-data Models and Fixtures for data

Done!

Problems? Compare with working version:

$ git diff -w 424ae index.html js

Objective 3

Create a Story Viewer page

Create a new Ember Route called story for reading a story

Create a Story Viewer page

Tasks

  • Create a 'show' Route
  • Create a 'show' Template
  • Configure the Story Show Route to load the Story Model

Create a Story Viewer page

Task - Create a 'show' Route

Update the Router object to include the 'show' entry:

App.Router.map(function() {
  this.resource('stories', function() {
    this.route('show', { path: ':story_id'});
  });
});
File: js/routes.js

Create a Story Viewer page

Task - Create a 'show' Route

In the stories/index Template replace the title binding with a link-to helper:

<!--== Templates with routes ==-->
<!-- /stories -->
<script type="text/x-handlebars"
        data-template-name="stories/index">
<h3>Stories:</h3> <ul> {{#each story in content}} <li>{{#link-to 'stories.show' story}} {{story.title}} {{/link-to}}</li> {{/each}} </ul> </script>File: index.html

Create a Story Viewer page

Task - Create a 'show' Template

Insert the following under the stories index template:

<!-- /stories/:story_id -->
<script type="text/x-handlebars"
        data-template-name="stories/show">
  <h3>{{title}}</h3>
</script>
File: index.html

Create a Story Viewer page

Task - Configure the Story Show Route to load the Story Model

Insert the following StoriesShow Route definition to js/routes.js:

App.StoriesShowRoute = Ember.Route.extend({
  model: function(params) {
    return this.get("store").find('story', params.story_id);
  }
});
File: js/routes.js

Create a Story Viewer page

Fini!

 

Problems? Compare with working version:

$ git diff -w 8745e index.html js

Objective 4

Story Editor

Editing would be nice!

Story Editor

Tasks

  • Create an edit route
  • Implement Save
  • Implement Cancel
  • Go to the Show page after saving
  • Update links in other pages

Story Editor

Task - Create an edit route

Add an 'edit' Route to the Router:

App.Router.map(function() {
  this.resource('stories', function() {
    this.route('show', { path: ':story_id'});
    this.route('edit', { path: ':story_id/edit'});
  });
});
File: js/routes.js

Story Editor

Task - Create an edit route

We're going to make the Show and Edit Route reuse the same base class.


First remove the old Show Route...

App.StoriesShowRoute = Ember.Route.extend({
  model: function(params) {
    return this.get("store").find('story', params.story_id);
  }
});
File: js/routes.js

Story Editor

Task - Create an edit route

... and replace it with this:

App.StoriesBaseRoute = Ember.Route.extend({
  model: function(params) {
    return this.get("store").find('story', params.story_id);
  }
});
App.StoriesShowRoute = App.StoriesBaseRoute.extend();
App.StoriesEditRoute = App.StoriesBaseRoute.extend();
File: js/routes.js

Story Editor

Task - Create an edit route

We're also going to need an edit template

<!-- /stories/:story_id/edit -->
<script type="text/x-handlebars" data-template-name="stories/edit">
  <button {{action save}} class="btn btn-success">Save</button>
  <button {{action cancel}} class="btn btn-danger">Cancel</button>
  <h3>Edit {{title}}</h3>

  <fieldset>
    <label>Title</label>
    {{input value=title size="80"}}
  </fieldset>
</script>
File: index.html

Story Editor

Task - Implement Save

To make the save button work we're going to implement our first Controller action

App.StoriesEditController = Ember.ObjectController.extend({
  actions: {
    save: function() {
      this.get("content").save();
    }
  },
});
File: js/controllers.js

Story Editor

Task - Implement Cancel

The same controller also needs a cancel action. Add it:

App.StoriesEditController = Ember.ObjectController.extend({
  actions: {
    save: function() {
      this.get("content").save();
    },
    cancel: function() {
      this.get("content").rollback();
      this.transitionToRoute("stories.show");
    }
  },
});
File: js/controllers.js

Story Editor

Task - Go to the Show page after saving

Add an after save callback:

App.StoriesEditController = Ember.ObjectController.extend({
  actions: {
    save: function() {
      this.get("content").save().then(
        this.didSave.bind(this)
      );
    },
    ...
  },

  didSave: function(story) {
    this.transitionToRoute("stories.show");
  }
});
File: js/controllers.js

Story Editor

Task - Update links in other pages

Add a link to the Edit Route from the Show Route:

<!-- /stories/:story_id -->
<script type="text/x-handlebars" data-template-name="stories/show">
  {{#link-to 'stories.edit' content}}
    <button class="btn btn-primary">Edit</button>
  {{/link-to}}
  <h3>{{title}}</h3>
</script>
File: index.html

Story Editor

Task - Update links in other pages

Replace the story links with a call to a new Partial Template in the Stories Template:

<!--== Templates with routes ==-->
<!-- /stories -->
<script type="text/x-handlebars" data-template-name="stories/index">

  <h3>Stories:</h3>
  {{partial "stories/storiesList"}}
</script>
File: index.html

Story Editor

Task - Update links in other pages

Insert the Partial just after the other Template declarations

<!--== Partials ==-->
<script type="text/x-handlebars"
        data-template-name="stories/_storiesList">
<ul> {{#each story in content}} <li> {{#link-to 'stories.edit' story}} <button class="btn btn-primary btn-xs">Edit</button> {{/link-to}} {{#link-to 'stories.show' story}}{{story.title}}{{/link-to}} </li> {{/each}} </ul> </script>File: index.html

Story Editor

Yay, did it!

Problems? Compare with working version:

$ git diff -w 04824 index.html js

Objective 5

Creating And Destroying Stories

I want to create my own stories!

Creating and Destroying Stories

Tasks

  • Create Story
  • Delete Story

Creating and Destroying Stories

Task - Create Story

Add a 'new' Route to the Router:

App.Router.map(function() {
  this.resource('stories', function() {
    this.route('new');
    this.route('show', { path: ':story_id'});
    this.route('edit', { path: ':story_id/edit'});
  });
});
File: js/routes.js

Creating and Destroying Stories

Task - Create Story

Implement the StoriesNew Route:

App.StoriesNewRoute = Ember.Route.extend({
  model: function(params) {
    return this.get("store").createRecord("story");
  }
});
File: js/routes.js

Creating and Destroying Stories

Task - Create Story

Create a new Template for Route stories/new:

<!-- /stories/:story_id -->
<script type="text/x-handlebars" data-template-name="stories/new">
  <h3>New Story</h3>
  <fieldset>
    <label>Title</label>
    {{input value=title size="80"}}
  </fieldset>
  <br>
  <button {{action create}} class="btn btn-success">Create</button>
  <button {{action cancel}} class="btn btn-danger">Cancel</button>
</script>
File: index.html

Creating and Destroying Stories

Task - Create Story

Add a button to the stories/index Template for creating stories:

<!-- /stories -->
<script type="text/x-handlebars" data-template-name="stories/index">
<h3>Stories:</h3> {{#link-to 'stories.new'}} <button class = "btn btn-success">New Story</button> {{/link-to}} <br> {{partial "stories/storiesList"}} </script>
File: index.html

Creating and Destroying Stories

Task - Create Story

Insert a new base class for editing and creating for 'cancel' actions:

App.StoriesModifyController = Ember.ObjectController.extend({
  actions: {
    cancel: function() {
      this.get("content").rollback();
      this.transitionToRoute("stories.index");
    }
  },
});
File: js/controllers.js

Creating and Destroying Stories

Task - Create Story

In StorieEditController change the base class and remove the 'cancel' action:

App.StoriesEditController = App.StoriesModifyController.extend({
  actions: {
    save: function() {
      this.get("content").save().then(
        this.didSave.bind(this)
      );
    }
  },

  didSave: function(story) {
    this.transitionToRoute("stories.show");
  }
});
File: js/controllers.js

Creating and Destroying Stories

Task - Create Story

Insert a new controller for new stories, also using the new base class:

App.StoriesNewController = App.StoriesModifyController.extend({
  actions: {
    create: function() {
      this.get("content").save().then(
        this.didCreate.bind(this)
      );
    }
  },

  didCreate: function(story) {
    this.transitionToRoute("stories.edit", story);
  }
});
File: js/controllers.js

Creating and Destroying Stories

Task - Delete Story

Add a button to the stories/index Template for deleting stories:

<!--== Partials ==-->
<script type="text/x-handlebars"
        data-template-name="stories/_storiesList">
<ul> {{#each story in content}} <li> {{#link-to 'stories.edit' story}} <button class="btn btn-primary btn-xs">Edit</button> {{/link-to}} <button {{action "destroy" story}} class="btn btn-danger btn-xs">Delete</button> {{#link-to 'stories.show' story}}{{story.title}}{{/link-to}} </li> {{/each}} </ul> </script>
File: index.html

Creating and Destroying Stories

Task - Delete Story

Implement StoriesIndex Controller to handle 'delete' action:

App.StoriesIndexController = Ember.ArrayController.extend({
  actions: {
    destroy: function(story) {
      story.deleteRecord();
      story.save();
    }
  }
});
File: js/controllers.js

Story Editor

CRUDtastic!

Problems? Compare with working version:

$ git diff -w 22cc2 index.html js

Objective 6

Showing Story Sections

Might be real nice if our stories had some content!

Showing Story Sections

Tasks

  • Show first story section
  • Display section choices
  • Render Section Route

Showing Story Sections

Task - Show first story section

Uncomment first_section attribute of Story Model:

App.Story = DS.Model.extend({
  title: DS.attr('string'),
  first_section: DS.belongsTo('section')
});
File: js/models.js

Showing Story Sections

Task - Show first story section

Uncomment Section Model (except choices attribute):

App.Section = DS.Model.extend({
  synopsis: DS.attr('string'),
  text: DS.attr('string'),
  story: DS.belongsTo('story'),
//  choices: DS.hasMany('choice', {async: true, inverse: 'section'})
});
File: js/models.js

Showing Story Sections

Task - Show first story section

Uncomment Section Fixtures:

App.Section.FIXTURES = [
  {
    id: 2000,
    synopsis: "Intro",
    text: "It is a lovely summers day. While lying on your back watching " +
          "clouds you notice a small dark dot in the sky above you.",
    story: 100,
    choices: [3000, 3001, 3002]
  },
  ...
}];
File: js/fixtures.js

Showing Story Sections

Task - Show first story section

Add a new Template (section-show):

<!--== Routeless Templates (used through 'render' helper)-->
<!-- section-show -->

<script type="text/x-handlebars" data-template-name="section-show">
  {{content.text}}
</script>
File: js/fixtures.js

Showing Story Sections

Task - Show first story section

Add Render Helper to stories/show Template:

<!-- /stories/:story_id -->
<script type="text/x-handlebars" data-template-name="stories/show">
  {{#link-to 'stories.edit' content}}
    <button class="btn btn-primary">Edit</button>
  {{/link-to}}
  <h3>{{title}}</h3>
  {{render section-show first_section}}
</script>
File: js/fixtures.js

Showing Story Sections

Task - Display section choices

Uncomment Section Model's choices attribute:

App.Section = DS.Model.extend({
  synopsis: DS.attr('string'),
  text: DS.attr('string'),
  story: DS.belongsTo('story'),
  choices: DS.hasMany('choice', {async: true, inverse: 'section'})
});
File: js/models.js

Showing Story Sections

Task - Display section choices

Uncomment Choice Model:

App.Choice = DS.Model.extend({
  wording: DS.attr('string'),
  section: DS.belongsTo('section'),
  goes_to: DS.belongsTo('section')
});
File: js/models.js

Showing Story Sections

Task - Display section choices

Uncomment Choice Fixtures:

App.Choice.FIXTURES = [
  {
      id: 3000,
      wording: "Go back to sleep",
      goes_to: 2010
  },
  ...
}];
File: js/fixtures.js

Showing Story Sections

Task - Display section choices

Replace Show Route with a Resource containing Sections route:

App.Router.map(function() {
  this.resource('stories', function() {
    this.route('new');
    this.resource('stories.show', { path: ':story_id'}, function() {
      this.route('sections', { path: '/sections/:section_id' });
    });
    this.route('edit', { path: ':story_id/edit'});
  });
});
File: js/routes.js

Showing Story Sections

Task - Display section choices

Implement new StoriesShowIndex Route:

App.StoriesShowIndexRoute = Ember.Route.extend({
  model: function(params) {
    model = this.modelFor("stories.show");
    return model;
  }
});
File: js/routes.js

Showing Story Sections

Task - Display section choices

Create a new stories/show/index Template:

<!-- /stories/:story_id/<index> -->
<script type="text/x-handlebars"
  data-template-name="stories/show/index">
  {{render section-show first_section}}
</script>
File: index.html

Showing Story Sections

Task - Display section choices

Inside stories/show Template replace the {{render}} declaration with {{outlet}}:

<!-- /stories/:story_id -->
<script type="text/x-handlebars" data-template-name="stories/show">
  {{#link-to 'stories.edit' content}}
    <button class="btn btn-primary">Edit</button>
  {{/link-to}}
  <h3>{{title}}</h3>
  {{outlet}}
</script>
File: index.html

Showing Story Sections

Task - Display section choices

Change section-show Template to show choices:

<script type="text/x-handlebars" data-template-name="section-show">
{{content.text}} <br> <br> {{#if content.choices}} <p>What do you want to do?<p> <ul> {{#each content.choices}} <li>{{#link-to 'stories.show.sections' goes_to}} {{wording}} {{/link-to}}</li> {{/each}} </ul> {{else}} <p><strong>The End</strong></p> {{/if}} </script>
File: index.html

Showing Story Sections

Task - Render Section Route

Insert a template for stories/show/sections:

<!-- /stories/:story_id/sections/:section_id -->
<script type="text/x-handlebars"
        data-template-name="stories/show/sections">
  {{render section-show content}}
</script>
File: index.html

Showing Story Sections

It's playable!

Problems? Compare with working version:

$ git diff -w 4b671 index.html js

Objective 7

Editing Story Sections

Great, but how do we make changes to sections?

Editing the first Section

Tasks

  • Edit routing for story sections
  • Apply choices
  • Removable choices
  • Adding choices

Editing the first Section

Task - Edit routing for story sections

Turn 'edit' route into a resource containing a section route:

App.Router.map(function() {
  this.resource('stories', function(){
    this.route('new');
    this.resource('stories.show', {path: ':story_id'}, function(){
      this.route('sections', {path: '/sections/:section_id' });
    });
    this.resource('stories.edit',{path: ':story_id/edit'}, function(){
      this.route('sections', {path: '/sections/:section_id' });
    });
  });
});
File: js/routes.js

Editing the first Section

Task - Edit routing for story sections

Insert a new Edit Index Route

App.StoriesEditIndexRoute = Ember.Route.extend({
  model: function(params) {
    return this.modelFor("stories.edit");
  }
});
File: js/routes.js

Editing the first Section

Task - Edit routing for story sections

Insert a new Template for stories/edit/index

<!-- /stories/:story_id/edit<index> -->
<script type="text/x-handlebars"
        data-template-name="stories/edit/index">
  {{render section-edit first_section}}
</script>
File: index.html

Editing the first Section

Task - Edit routing for story sections

Replace the {{render}} helper with {{outlet}} in stories/edit Template

<!-- /stories/:story_id/edit -->
<script type="text/x-handlebars" data-template-name="stories/edit">
  <button {{action save}} class="btn btn-success">Save</button>
  <button {{action cancel}} class="btn btn-danger">Cancel</button>
  <h3>Edit {{title}}</h3>
  <fieldset>
    <label>Title</label>
    {{input value=title size="80"}}
  </fieldset>
  {{outlet}}
</script>
File: index.html

Editing the first Section

Task - Edit routing for story sections

Insert a new Template for stories/edit/sections

<!-- /stories/:story_id/edit/sections/:section_id -->
<script type="text/x-handlebars"
        data-template-name="stories/edit/sections">
  {{render section-edit content}}
</script>
File: index.html

Editing the first Section

Task - Apply choices

Add {{render choices-edit content.choices}} to the bottom area section-edit Template

<!-- section-edit -->
<script type="text/x-handlebars" data-template-name="section-edit">
  <h4>Section: {{content.synopsis}}</h4>
  <div>
    <fieldset>
      <label>Short synopsis:</label>
      {{input value=content.synopsis}}
    </fieldset>
    <fieldset>
      <label>Content:</label><br>
      {{textarea value=content.text cols=100 rows=6}}
    </fieldset>
    {{render choices-edit content.choices}}
  </div>
</script>
File: index.html

Editing the first Section

Task - Apply choices

Insert a Template called choices-edit

<!-- choices-edit -->
<script type="text/x-handlebars" data-template-name="choices-edit">
<h4>Choices:</h4> <ul> {{#each choice in content}} <li> {{#link-to 'stories.edit.sections' choice.goes_to}} {{choice.wording}} {{/link-to}} </li> {{/each}} </ul> </script>
File: index.html

Editing the first Section

Task - Removable choices

Add a Remove button to the choices-edit

<!-- choices-edit -->
<script type="text/x-handlebars" data-template-name="choices-edit">
<h4>Choices:</h4> <ul> {{#each choice in content}} <li> <button {{action "remove" choice}} class="btn btn-danger btn-xs">Remove</button> {{#link-to 'stories.edit.sections' choice.goes_to}} {{choice.wording}} {{/link-to}} </li> {{/each}} </ul> </script>
File: index.html

Editing the first Section

Task - Removable choices

Insert a controller for choices-edit template to handle deletions

App.ChoicesEditController = Ember.ArrayController.extend({
  actions: {
    remove: function(choice) {
      choice.deleteRecord();
      choice.save().then(this.didRemove.bind(this),
                         this.didRejectRemove.bind(this));
    }
  },
  didRemove: function(choice) {
    choices = this.get("content")
    choices.removeObject(choice)
  },
  didRejectRemove: function(reason) {
    console.error("Failed to remove item", reason);
  }
});
File: js/controllers.js

Editing the first Section

Task - Adding choices

Place a {{render "choice-add"}} declaration at the bottom of choices-add Template

<!-- choices-edit -->
<script type="text/x-handlebars" data-template-name="choices-edit">
<h4>Choices:</h4> <ul> {{#each choice in content}} <li><button {{action "remove" choice}} class="btn btn-danger btn-xs">Remove</button> {{#link-to 'stories.edit.sections' choice.goes_to}} {{choice.wording}} {{/link-to}} </li> {{/each}} </ul> {{render "choice-add"}} </script>
File: index.html

Editing the first Section

Task - Adding choices

Add a new choices-edit Template

<!-- choices-add -->
<script type="text/x-handlebars" data-template-name="choice-add">
  <label>New choice</label><br>

  {{input value=wording
          placeholder="Add an extra choice here"
          size="60"}}
  <button {{action "add"}} class="btn btn-success btn-xs">Add</button>
</script>
File: index.html

Editing the first Section

Task - Adding choices

Add a new Choice Add controller (Part 1/2)

App.ChoiceAddController = Ember.ObjectController.extend({
  needs: "section-edit",

  content: function() {
    return this.createChoiceObject();
  }.property(''),

  actions: {
    add: function() {
      choice = this.get("content");
      choice.set("goes_to", this.newSection());
      this.parentController().pushObject(choice);
      choice.save().then(this.didAdd.bind(this));
    }
  },
...
File: js/controllers.js

Editing the first Section

Task - Adding choices

Add a new Choice Add controller (Part 2/2)

...
  didAdd: function() {
    this.set("content", this.createChoiceObject());
  },

  createChoiceObject: function() {
    return this.get("store").createRecord('choice');
  },

  parentController: function() {
    return this.get("controllers.section-edit.choices");
  },

  newSection: function() {
    return this.get("store").createRecord('section');
  }
});
File: js/controllers.js

Showing Story Sections

Almost there!

Problems? Compare with working version:

$ git diff -w 6d9df index.html js

Objective 8

Finishing Touches

Finishing Touches

Tasks

  • Make editing work from all Sections
  • Make 'New Story' work with choices

Finishing Touches

Task - Make editing work from all Sections

Refactor: Move StoriesShowIndex Route content into StoriesShowBaseRoute and inherit from it:

App.StoriesShowBaseRoute = Ember.Route.extend({
  model: function(params) {
    return this.modelFor("stories.show");
  }
});
App.StoriesShowIndexRoute    = App.StoriesShowBaseRoute.extend({});
File: js/routes.js

Finishing Touches

Task - Make editing work from all Sections

Implement Route StoriesShowSectionsRoute, extending functionality of StoriesShowBaseRoute:

App.StoriesShowSectionsRoute = App.StoriesShowBaseRoute.extend({
  model: function(params) {
    this._super(params);
    return this.store.find('section', params.section_id);
  }
});
File: js/routes.js

Finishing Touches

Task - Make editing work from all Sections

Change stories/show edit button to use a Controller Action

<!-- /stories/:story_id -->
<script type="text/x-handlebars" data-template-name="stories/show">
  <button {{action edit}} class="btn btn-primary">Edit</button>
  <h3>{{title}}</h3>
  {{outlet}}
</script>
File: index.html

Finishing Touches

Task - Make editing work from all Sections

Explictly define an ApplicationController (application scope controller)

App.ApplicationController = Ember.Controller.extend();
File: js/controllers.js

Finishing Touches

Task - Make editing work from all Sections

Implement a StoriesShow Controller which correctly transitions to the either stories.edit or stories.edit.sections Routes

App.StoriesShowController = Ember.ObjectController.extend({
  needs: "application",

  actions: {
    edit: function() {
      if (this.get("controllers.application.currentPath") ===
          "stories.show.sections") {
        this.transitionToRoute("stories.edit.sections");
      } else {
        this.transitionToRoute("stories.edit");
      }
    }
  },
})
File: js/controllers.js

Finishing Touches

Task - Make editing work from all Sections

Add Section Choices to section-edit or storiesEditSections depending on which section we're on.


Edit the top part of storiesEditSections to look like this:

App.StoriesEditSectionsController = Ember.ObjectController.extend();

App.ChoiceAddController = Ember.ObjectController.extend({
  needs: ["storiesEditSections","section-edit"],
...
File: js/controllers.js

Finishing Touches

Task - Make editing work from all Sections

Add Section Choices to section-edit or storiesEditSections depending on which section we're on.


... and change the parentController function to be this:

App.ChoiceAddController = Ember.ObjectController.extend({
...
  parentController: function() {
    section = this.get("controllers.storiesEditSections.choices");
    if (section === undefined) {
      section = this.get("controllers.section-edit.choices");
    }
    return section;
  },
...
File: js/controllers.js

Finishing Touches

Task - Make 'New Story' work with choices

Implement StoriesNew route

App.StoriesNewRoute = Ember.Route.extend({
  model: function(params) {
    story = this.get("store").createRecord("story");
    section = this.get("store").createRecord("section");
    story.set("first_section", section);
    return story;
  }
});
File: js/controllers.js

Finishing Touches

Finito!

Problems? Compare with working version:

$ git diff -w 77fc2 index.html js

Greg Malcolm github.com/gregmalcolm @gregmalcolm

So Long And Thanks For All The Javascripts!

Ember.js Workshop

By gregmalcolm

Ember.js Workshop

  • 2,937