Ok, I’m convinced… Ember-cli is Awesome! Now, how do I use it? (Part 2)

Our app works now… sort of… but we still need to be able to check off each todo item as it’s completed, make changes to our todo’s, and eliminate our old todo’s with extreme prejudice.

This thing is pretty ugly, I’ll admit–even for a nerd. If you want to take a detour and Bootstrap, Compass, Foundation, or otherwise spiffy up your app, feel free. Details for how to get started doing this can be found at Ember-cli’s asset compilation page here.

As you might guess, we import the necessary assets in the ember-cli-build.js file. If we just wanted to add our own custom css changes, we could do so in app/styles/app.css.

Done yet? Good! Let’s continue.

Let’s add an action to our checkboxes in our todos template and a handler to our ‘todos’ route so we can check off our todos We will also need to add a “checked” attribute to our todo model.

In app/templates/todos.js:

1 <h1>Todos</h1>
2 <div class="nav">{{#link-to "new"}}New Todo{{/link-to}}</div>
3 {{#each model as |todo|}} 
4 {{#if todo.checked }} <label class="strikethrough"><input checked="checked" type="checkbox" />{{#link-to "todo" todo}}{{todo.name}}{{/link-to}}</label>
5 {{else}} 
6 <label><input type="checkbox" />{{#link-to "todo" todo}}{{todo.name}}{{/link-to}}</label>
7 {{/if}} 
8 {{/each}}

In app/routes/todos.hbs:

  1 import Ember from 'ember';                                                                                         
  3 export default Ember.Route.extend({                                                                                
  4         model: function () {                                                                                       
  5                 return this.store.findAll('todo');                                                                 
  6         },                                                                                                         
  7        actions: {                                                                                                  
  9                checkOff: function (todo) {                                                                         
 10                        todo.toggleProperty('checked');                                                             
 11                        todo.save();                                                                                                                                                                                                       
 12                }                                                                                                   
 13        }                                                                                                           
 14 }); 

In app/models/todo.js:

  1 import DS from 'ember-data';                                                                                                                                                                                                              
  3 export default DS.Model.extend({
  4        name: DS.attr('string'),
  5        details: DS.attr('string'),
  6        checked: DS.attr('boolean')
  8 });

Alrighty. Good bit going on here. We have a handlebars if/else block that changes what is displayed based on the truthiness of our model’s checked attribute. There is probably a better way to do that, but finding a more D.R.Y. way to do this is something you can do as a homework exercise. If our todo is checked off, we want the todos route to display a checked checkbox. I also added the ‘strikethrough’ class to the label to add the line-through text decoration. In my app.css, my strikethrough class looks like this:

.strikethrough {
    text-decoration: line-through;

Now, we need to handle editing our todos. We can do this in a separate edit route, or we can go ahead and do it on the todo route. There are probably still more ways we could implement this, but those are the two that come to mind. Let’s go ahead and add our edit feature. We will use an edit button with an ‘edit’ action. We will then redirect to the ‘edit’ route, reusing the ‘new’ template after we make a few minor changes to it. We could have the ‘edit’ action called when the text is clicked on, but then we would need to provide some sort of indication that clicking on the text would allow you to edit it, probably in the form of a :hover CSS rule. You can do whatever you want. We will start in app/templates/todo.hbs:

3 <button {{action 'edit' model}}>Edit</button>

Here, we pass the model to the edit action, and we’ll see why when we get there. But first, we need to create our edit route:

ember g route edit

Now, let’s edit the edit route (app/routes/edit.js):

  1 import Ember from 'ember';
  3 export default Ember.Route.extend({
  4         renderTemplate: function () {
  5                 this.render('new');
  6         },
  7         actions: {
  9                 save: function (todo) {
 10                        var name = $('input').val();
 11                        var details = $('textarea').val();
 12                        var model = this.get('model');
 13                        todo.set('name', name);
 14                        todo.set('details', details);
 15                        todo.save();
 16                        this.transitionTo('todo', todo);                                                                                                                                                                                   
 17                 }
 18         }
 19 });

Notice renderTemplate in the above route. Our ‘edit’ route will reuse our ‘new’ template, with a few minor but important changes. Take a look (app/templates/new.hbs):

1 <h1>Todo</h1>
3 <input type="text" value="{{model.name}}" placeholder="Todo Name" /> 
4 <hr />
5 <textarea placeholder="Todo Details">{{model.details}}</textarea> 
6 <hr />
7 <button>Done</button>

We are passing the model to the save action now. This will not affect how our ‘new’ route handles the save action since it did not receive a model in the first place.

Congrats! We can now edit our todos!

Now, let’s handle deleting checked-off todos. We need to start by editing the todos template and adding a delete checked button with an appropriately-named action handler.

In app/templates/todos.hbs:

  1 <h1>Todos</h1>                                                                                                                                                                                                                            
  2 <div class="nav">{{#link-to "new"}}New Todo{{/link-to}}</div>
  3 {{#each model as |todo|}}
  4 {{#if todo.checked }} <label {{action "checkOff" todo}} class="strikethrough"><input type="checkbox" checked>{{#link-to "todo" todo}}{{todo.name}}{{/link-to}}</label><br>
  5 {{else}}
  6 <label {{action "checkOff" todo}}><input type="checkbox">{{#link-to "todo" todo}}{{todo.name}}{{/link-to}}</label><br>
  7 {{/if}}
  8 {{/each}}
  9 <button {{action 'deleteTodos'}}>Delete checked todos</button>

Nothing we haven’t seen before here. We have a button with an action handler that currently doesn’t have an action to match it. All we have left to do is add another action and we are done. We will get all the todos and loop through them using forEach. If their checked attribute is true, we will delete them (app/routes/todos.js):

  1 import Ember from 'ember';                                                                                                                                                                                                                
  3 export default Ember.Route.extend({                                                                                
  4         model: function () {
  5                 return this.store.findAll('todo');
  6         },
  7        actions: {
  8                checkOff: function (todo) {                                                                         
  9                        todo.toggleProperty('checked');
 10                        todo.save();
 11                },
 12        deleteTodos: function () {
 13                this.store.find('todo').then(function (items) {
 14                        console.log(items);
 15                        items.forEach(function (item) {
 16                                if(item.get('checked')) {
 17                                        item.deleteRecord();
 18                                        item.save();
 19                                }
 20                        });
 21                });
 22        }
 23        }
 24 });

That’s it! We are finished! Congratulations! You are now an Ember master. Go forth and use your powers for good. There are plenty of improvements we could (read: probably should) make to this example, but I will leave that to you. Good luck, have fun, and feel free to ask for help when you need it. If you are stuck, you can get the source code here.

Leave a Reply

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