Example application

Now that we have discussed different aspects of the ember-data library, let's look at a real example for the same.

The source code for the example can be found at https://github.com/suchitpuri/emberjs-essentials/tree/master/chapter-6/example1. In this example, we are aiming to continue with the books model that was defined in the chapter, and work on the following functionality:

  1. Create a new book and its related author and publisher.
  2. View a book's information, including its related publisher, authors, and reviews.
  3. View the list of all books in our catalog.

Here, to communicate with the backend server, we have used the HTTP-mock generators provided by Ember CLI to generate mock responses. The HTTP-mock generator generates the Express.js (found at http://expressjs.com/)server scaffold for the resource. If you want to generate a REST compliant URL for your resource, you can do that by running the following command at your project's root directory. This is chapter-6/example1 in our case:

ember g http-mock <resource name>

For the current example, we have already generated the HTTP mock calls for our resources. You can see the mock calls at chapter-6/example1/server/mocks directory.

This is how the mock calls for the book resource look:

module.exports = function(app) {
  var express = require('express'),
  var booksRouter = express.Router();

  booksRouter.get('/', function(req, res) {
    res.send({
      "books": [{
        "id":1,
        "title": "Ember.js Essentials",
        "isbn": "ISBN1",
        "pages": 180,
        "description": "Ember.js essentials to master",
        "authors": [1],
        "publisher": 1,
        "reviews": [1,2,3]
      },
      {
        "id":2,
        "title": "Some Other Book On Ember.js",
        "isbn": "ISBN2",
        "pages": 200,
        "description": "Some Description",
        "authors": [2],
        "publisher": 1,
        "reviews": [4,5,6]
      }]
    });
  });
  booksRouter.post('/', function(req, res) {
    res.status(201);
    res.send({"book":{
      "id":Math.floor(Math.random()*1000)
    }});
  });
  booksRouter.get('/:id', function(req, res) {
    var reviews,authors, publisher;
    if(req.params.id == 1){
      reviews = [1,2,3]
      authors = [1]
      publisher = 1
    }else if(req.params.id == 2){
      reviews = [4,5,6]
      authors = [2]
      publisher = 2
    }
    res.send({
      "book": {
        "id":req.params.id,
        "title": "Ember.js Essentials",
        "isbn": "ISBN1",
        "pages": 180,
        "description": "Ember.js essentials to master",
        "authors": authors,
        "publisher": publisher,
        "reviews": reviews
      }
    });
  });
  booksRouter.put('/:id', function(req, res) {
    res.send({
      "book": {
        "id": req.params.id
      }
    });
  });
  booksRouter.delete('/:id', function(req, res) {
    res.status(204).end();
  });
  app.use('/api/books', booksRouter);
};

The mock calls for the book resource is present at chapter-6/example1/server/mocks/books.js

A full explanation of the above Express.js code is beyond the scope of the book, but as this is just JavaScript code, it is not that difficult to understand. The only thing to note here is that we have mapped different HTTP verbs, with different JSON responses here.

Similar to the above code, we have created mocked responses for authors, publishers, and reviews too.

Let us now see the Ember application code that is present at chapter-6/example1/app/ directory.

The adapter's directory contains the application adapter in application.js as follows:

import DS from 'ember-data';
export default DS.RESTAdapter.extend({
  namespace: 'api'
});

The application adapter is present at chapter-6/example1/app/adapters/application.js

Here, we just define the namespace of our server API.

All the model classes are present at chapter-6/example1/app/models/:

import DS from "ember-data";

export default DS.Model.extend({
  title: DS.attr('string'),
  isbn: DS.attr('string'),
  pages: DS.attr('number'),
  description: DS.attr('string'),
  authors: DS.hasMany('author',{ async: true }),
  publisher: DS.belongsTo('publisher',{ async: true }),
  reviews: DS.hasMany("review",{ async: true })
});

The book model is present at chapter-6/example1/app/models/book.js

Let us see the routes that we have defined in our application:

Router.map(function() {
  this.resource('books', function(){
    this.route('book',{path: "/:id"});
    this.route('new'),
  });
});

The router.js is present at chapter-6/example1/app/router.js

As you can see, we have defined a books resource, which has two additional routes: books.book and books.new.

The books.book route should show the information for a specific book and hence we should ask the store about the book with the matching ID to be returned from its model hook:

import Ember from 'ember';

export default Ember.Route.extend({
  model: function(params){
    return this.store.find('book',params.id);
  }
});

The books.book route is present at chapter-6/example1/app/routes/books/book.js

Similarly, in our books index route, we would want to return all the books in our collection. So, we should return an array of books from its model hook:

import Ember from 'ember';

export default Ember.Route.extend({
    model: function(){
        return this.store.find('book'),
    }
});

The books.index route is present at chapter-6/example1/app/routes/books/index.js

To create a new book, we need to define the books.new template and route. The template for creating a new book is a collection for different input fields to collect data for book, publisher, and author. It also has button that calls the action, createBook.

The template for books.new, along with books.book and books.index, can be found in the chapter-6/example1/app/templates/books/ directory.

The createBook action defined in book.new template can either be defined in the respective controller or route. But, as we are dealing with saving a resource on the server, the books.new route is more natural place to house the action, as follows:

import Ember from 'ember';

export default Ember.Route.extend({

  actions:{
    createBook: function(){
      var that = this;

      var publisher = that.store.createRecord("publisher",{
        "name": that.get("controller.name"),
        "organizationName": that.get("controller.organizationName"),
        "address": that.get("controller.address")
      });
      var author = that.store.createRecord("author",{
        "firstName": that.get("controller.firstName"),
        "lastName": that.get("controller.lastName"),
        "bio": that.get("controller.bio")
      });
      var book =  that.store.createRecord("book",{
        "title": that.get("controller.title"),
        "isbn": that.get("controller.isbn"),
        "pages": that.get("controller.pages"),
        "description": that.get("description")
      });
      publisher.save().then(function(publisherFromDB){ 
        book.set("publisher",publisherFromDB);
        author.save().then(function(authorFromServer){
          //Set The Author to the books
          book.get("authors").then(function(authors){
            authors.pushObject(authorFromServer);
          });
          //Save the book 
          book.save().then(function(book){
            that.transitionTo('books.book',book);
          });
        });
      });
    }
  }
});

The books.new route is defined in chapter-6/example1/app/routes/books/new.js

As you can see, we have defined one action, createBook, in the actions object of the route. This action is triggered when someone fills from the UI, the book, authors, and publishers information, and presses the Submit button.

Inside the createBook action, we first create the objects for publisher, author, and the book. Then we save the publisher by calling save on the record. This would make a POST call to /api/publishers, which would return a response that has a unique ID for the object. When the promise for the publisher.save() resolves successfully, we associate the publisher with the book we are creating.

We do a something similar with the author, and finally, when everything returns successfully for the author resource, we save the book and transition to the book.book route upon successful completion of the save request.

Here's how the books information page will look when you visit/books route:

Example application

The list of books fetched from the server

When you navigate to the /books/1, the book details page will look like this:

Example application

The books details page with all the information

And here's how the book creation form looks when you navigate to /books/new route:

Example application

The book creation page

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset
18.216.47.169