How To: CRUD with Validation in Ember.js Using ember-easyForm, ember-validations, and Boostrap

Step 3. Display Products

In this step, we will display a list of all our products in the /products route.

First, we need to create a link to the new Products section in our application. Add this line after the Dashboard link in index.html:

<!-- <ul class="nav nav-pills nav-stacked sidebar"> -->
<!-- {{#link-to "index" tagName="li"}}{{#link-to "index"}}Dashboard{{/link-to}}{{/link-to}} -->
{{#link-to "products" tagName="li"}}{{#link-to "products"}}Products{{/link-to}}{{/link-to}}

Next, we need to create two templates that will display the product listing. The products template will serve as the master template for the whole Products section of our application. It will display the “Products” header at the top of every page in this section.

The products/index template will display a list of all the products in a table. For each product, the template will render:

  • The name of the product with a link to the corresponding /products/:product_id route
  • The author of the product
  • The price
  • Edit and delete buttons for that product

The template will also display productsCount, the total number of products, below the table. This property will be referenced from ProductsController, which we will implement below.

Add this to index.html. A good place to insert it would be before the set of Javascript includes at the end of the file.

<script type="text/x-handlebars" data-template-name="products">
  <h1>Products</h1>
  {{outlet}}
</script>

<script type="text/x-handlebars" data-template-name="products/index">
  <table id="products_table" class="table">
    <thead>
      <tr>
        <th>Name</th>
        <th>Author</th>
        <th>Price</th>
        <th><button type="button" class="btn btn-default new-button" {{action "new"}}><span class="glyphicon glyphicon-plus"></span></button></th>
      </tr>
    </thead>
    <tbody>
      {{#each product in model}}
        <tr {{bind-attr id=product.htmlID}}>
          <td class="name">{{#link-to "products.show" product}}{{product.name}}{{/link-to}}</td>
          <td class="author">{{product.author}}</td>
          <td class="price">{{product.price}}</td>
          <td class="action-buttons"><button type="button" class="btn btn-default edit-button" {{action "edit" product}}><span class="glyphicon glyphicon-pencil"></span></button>
              <button type="button" class="btn btn-default delete-button" {{action "delete" product}}><span class="glyphicon glyphicon-remove"></button></td>
        </tr>
      {{/each}}
    </tbody>
  </table>
  <p id="products_count">Total: {{controllers.products.productsCount}} products.</p>
</script>

Next, we need to wire up the template by creating the relevant routes and controllers. First, let us implement the route and controller for the /products path.

ProductsRoute will inherit the default route and take the whole collection of products as its model.

ProductsController will inherit the default controller, with an additional computed property productsCount, which keeps a tally of the number of products in the data store.

Append to app.js:

App.ProductsRoute = Ember.Route.extend({
  model: function() {
    return this.store.find('product');
  }
});

App.ProductsController = Ember.ArrayController.extend({
  productsCount: function() {
    return this.get('model.length');
  }.property('@each')
});

We also need to define the route and controller for the /products/index path.

ProductsIndexRoute will use the same model as ProductsRoute.

ProductsIndexController will need the properties of its parent ProductsController. This will enable the products/index template above to access the controllers.products.productsCount property. Finally, we want ProductsIndexController to sort the products by name.

Append to app.js:

App.ProductsIndexRoute = Ember.Route.extend({
  model: function() {
    return this.modelFor('products');
  }
});

App.ProductsIndexController = Ember.ArrayController.extend({
  needs: ['products'],
  sortProperties: ['name']
});

We could also have implemented the productsCount property directly in ProductsIndexController. However, since this is a property that we might want to use elsewhere in our application, it would be logical for it to belong to the ProductsController, which is the parent controller of the whole Products section of our application, rather than to ProductsIndexController, which controls a specific view in the Products section.

Next, let us add tests to make sure that the products are displayed correctly. Throughout this tutorial, we will organize our tests into modules, one for each major functionality of the application. Modules help us to keep our test code organized. They also cause QUnit to display the test results in a logical and organized manner.

We will focus on integration tests (rather than unit tests) in this tutorial since most of the code that we will develop involve route transitions and input / output of data through the user interface.

Here we will create a new test module called “Integration: Products Index” with just one test for now that ensures that the products table is rendered correctly. The test will count the number of rows displayed in the products table, and check whether it matches with the number of records in the application data store.

Append to tests.js:

module('Integration: Products Index', {
  setup: function() {
    App.reset();
    App.resetFixtures();
  }
});

test('products renders', function() {
  expect(5);

  visit('/products').then(function() {
    equal(find('h1').text(), 'Products');
    equal(find('table#products_table').length, 1);
    equal(find('table#products_table thead tr th').length, 4);
    var productCount = find('table#products_table tbody tr').length;
    equal(productCount, App.Product.FIXTURES.length);
    equal(find('p#products_count').text(), 'Total: ' + productCount + ' products.');
  });
});

Now, load index.html in your browser and make sure that the test passes.

You can get the code for this step by running:

git checkout step-3
Advertisements

5 thoughts on “How To: CRUD with Validation in Ember.js Using ember-easyForm, ember-validations, and Boostrap

Leave a Reply

Fill in your details below or click an icon to log in:

WordPress.com Logo

You are commenting using your WordPress.com account. Log Out / Change )

Twitter picture

You are commenting using your Twitter account. Log Out / Change )

Facebook photo

You are commenting using your Facebook account. Log Out / Change )

Google+ photo

You are commenting using your Google+ account. Log Out / Change )

Connecting to %s