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

I thought you would never ask…

Getting into Ember-cli is easier than one might expect, especially for those who are comfortable with Rails, and are comfortable working in a Unix-based terminal. First, you need to install it, and in order to install it, we will need node and npm. My environment is Linux, so these instructions are heavily biased in that direction. We will need to start off by running these commands as root in the terminal:

apt-get install node
apt-get install npm

Now, the real fun begins. Using the Node Package Manager (npm) that we just installed, we can install Ember-cli:

npm install -g ember-cli

If we wanted Bower (we do, by the way), we could install it too.

npm install -g bower

That should be enough to get started. There are more detailed instructions on how to install Ember-cli available on the user guide here.

Next, we will want to create a new project. Navigate in the terminal to the directory we would like to create our new project in (in my case, it’s ~/MyProjects), and let Ember-cli do all the boilerplating for us:

ember new my-new-app

Navigating into the my-new-app/ directory should reveal a set of directories and a few files. The app/ directory is where we will be spending most, if not all of our time. Let’s go ahead and make sure everything is good to go. Enter the command:

ember serve

Navigating to the URL (by default, it’s http://localhost:4200) that we are directed to in a web browser should show us a “Welcome to Ember” message if everything is working properly. We could stop the server by hitting ctrl+C in the terminal, but let’s just leave it running and open another terminal instead. Hitting File > Open Terminal in the terminal window(or shift+ctrl+N) will open a new terminal in our current working directory.

Now, let’s go ahead and create a resource. Let’s make an awesome and innovative app… why not a todo-list app? I’m sure no one has thought of doing that before! Ember makes this super-easy:

ember g resource todo

The ‘g’ in the previous command stands for generate. We could of course type it out, but why bother with those seven extra keystrokes? There are a few other minor things we need to take care of before we can really get our hands dirty. We need a ‘todos’ route so that we can see all of our todos at once:

ember g route todos

Once we have everything set up properly, we will then be able to go to localhost:4200/todos (well, localhost:4200 once we set the path, but I’m getting ahead of myself) and see all the todos that we have created. Let’s go ahead and create a ‘new’ route as well.

ember g route new

Now, we can get our hands dirty. In app/models/todo.js, let’s go ahead and set up our todo model. We can always go back later and add/remove things as necessary.

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

Great job! That should do for now. While we don’t ‘have’ to use Ember Data, it’s pretty sweet. The next step is going to involve router.js. This file will be in the app/ directory. The router handles URL’s for our app. We will want to set up a dynamic route for ‘todo.’ To do this, all we have to do is pass in an id for the todo we want in our path. We also want our base URL to be the ‘todos’ route. We will take care of this in a similar manner–by specifying a path:

  1 import Ember from 'ember';
  2 import config from './config/environment';
  3 
  4 var Router = Ember.Router.extend({
  5   location: config.locationType
  6 });
  7                                                                             
  8 Router.map(function() {
  9   //The only lines we change are the two below
 10   this.route('todo', {path: 'todo/:todo_id'});
 11   this.route('todos', {path: '/'});
 12   this.route('new');
 13 });
 14 
 15 export default Router;

Alright. Now, we’re getting somewhere! We’re still a ways away from a working app, though. At this point, we could stop and get the front-end looking acceptable and add a few link-to helpers so everything is interconnected. There would be nothing wrong with that, and it can actually improve our workflow by helping us eliminate cruft from our app. However, since it’s just us and this is a pretty simple example, let’s just skip that until later. Right now, all we have are routes and a model. Unless we want to roll our own adapter or use the default REST adapter, we will need to install an adapter. In our terminal, we need to run:

bower install --save-dev ember-localstorage-adapter

The localstorage adapter will allow our Ember app to make requests for data and save data to our web browser’s localstorage. The setup isn’t too bad. First, in ember-cli-build.js, we need to add an app.import call for the adapter:

  1 /* global require, module */
  2 var EmberApp = require('ember-cli/lib/broccoli/ember-app');
  3 
  4 module.exports = function(defaults) {
  5   var app = new EmberApp(defaults, {
  6     // Add options here
  7   });
  8   app.import('bower_components/ember-localstorage-adapter/localstorage_adapter.js');                                                                                                                                                      
  9 
 10   // Use `app.import` to add additional libraries to the generated
 11   // output files.
 12   //
 13   // If you need to use different assets in different
 14   // environments, specify an object as the first parameter. That
 15   // object's keys should be the environment name and the values
 16   // should be the asset to use in that environment.
 17   //
 18   // If the library that you are including contains AMD or ES6
 19   // modules that you would like to import into your application
 20   // please specify an object with the list of modules as keys
 21   // along with the exports of each module as its value.
 22 
 23   return app.toTree();
 24 };

Next, we have to generate an adapter using the ember generator, and then add a namespace to it.

In the terminal:

ember g adapter application

In app/adapters/application.js:

  1 import DS from 'ember-data';
  2 
  3 export default DS.RESTAdapter.extend({
  4         namespace: 'my-new-app'                                    
  5 });

Our adapter is now ready and willing to serve up data for us. We now need to make some templates for that data to go into, and a few ways for the user to interact with said data. Ember leverages the handlebars templating language, so you will see a lot of code in {{curly braces}}. You will also notice that the file extensions for our templates are *.hbs and not *.html. You may notice that syntax highlighting doesn’t work for .hbs files in your text editor of choice. Since we are using Vim as our text editor (ok, you don’t have to use Vim… but if you haven’t, consider this your final warning… It’s addictive), we will probably want to add a line to our .vimrc file to beautify our code a little. This is a workaround, not an ideal fix:

au BufReadPost *.hbs set syntax=html

In app/templates, we see a listing of our templates. Let’s make a few changes to our todos.hbs file. Nothing too fancy. We are all nerds here after all…

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

The site should automagically reload after these changes are saved. ‘Hey, where’s mah data?’ Well, there is none… yet. We could add a model hook to our todos route with some dummy data, or add a test that throws in some dummy data just to make sure everything works, but I’m going to skip that for the sake of brevity. Before we move any further, we need to understand what’s going on here. The {{#each}} handlebars helper allows us to loop through the data passed to our route and do awesome stuff with said data. Pretty nifty, huh? The {{#link-to}} helper allows us to add links to our other routes. Since our ‘todo’ route is a dynamic route, we need to specify which todo the link goes to by passing in a todo. Ember will pull the ID from that todo and pass it through whenever we request that route. We need to knock out a few more things before things start working the way one would expect:

(1) add a model hook to our ‘todos’ route
(2) finish our new.hbs template
(3) add an action handler to our ‘new’ route
(4) edit the todo route.

Alright. Let’s start with app/routes/todos.js:

  1 import Ember from 'ember';
  2 
  3 export default Ember.Route.extend({
  4         model: function () {
  5                 return this.store.findAll('todo');
  6         }                                                                                                                                                                                                                                 
  7 });

That was pretty straightforward. All we need to do in the ‘todos’ model hook is find all the todo’s and return them. Now, let’s finish our new.hbs template. The full path from within our project directory is app/templates/new.hbs:

  1 <h1>New Todo</h1>                                                                                                                                                                                                                         
  2                                                                                                                    
  3 <input type="text" placeholder="Todo Name">
  4 <hr>   
  5 <textarea placeholder="Todo Details"></textarea>
  6 <hr>
  7 <button {{action "save"}}>Create Todo</button>

In the button, we see an action handler called “save.”

If we go to our ‘new’ route within our app now, we can’t create a new todo. In our console, we would see an error message stating that nothing handled the action “save” after we click the button. Let’s fix that. We need to open up app/routes/new.js in a text editor.

  1 import Ember from 'ember';                                                                                                                                                                                                                
  2                                                                                                                    
  3 export default Ember.Route.extend({
  4        actions: {                                                                                                  
  5                save: function () {
  6                        var newtodo = this.store.createRecord('todo',
  7                                {
  8                                        name: $("input").val(),
  9                                        details: $("textarea").val()
 10                                });
 11                        newtodo.save();
 12                        console.log("model saved");
 13                }
 14        }
 15 });

Now, the ‘save’ action creates a new todo for us. We could even make a slight improvement to this. Suppose we wanted the app to redirect us to the ‘todos’ route after a new todo was created. All we need for that is a single line added to the end of our action handler: this.transitionToRoute(‘todos’);

It’s not necessary by any means, but it could be a pretty nice UX feature if our users typically only add one todo at a time. Another addition that may make sense is adding an alert message or some other form of feedback to the user when the new todo is created. It could even be as simple as clearing the input and textarea fields, and focusing on the input field. In fact, that makes a lot of sense. Let’s go ahead and implement that:

  1 import Ember from 'ember';                                                                                         
  2                                                                                                                    
  3 export default Ember.Route.extend({                                                                                
  4        actions: {                                                                                                  
  5                save: function () {                                                                                 
  6                        var newtodo = this.store.createRecord('todo',                                               
  7                                {                                                                                   
  8                                        name: $("input").val(),                                                     
  9                                        details: $("textarea").val()                                                
 10                                });                                                                                 
 11                        newtodo.save();                                                                             
 12                        $("textarea").val("");                                                                      
 13                        $("input").val("").focus();                                                                                                                                                                                        
 14                                                                                                                    
 15                }                                                                                                   
 16        }                                                                                                           
 17 });  

That’s a lot better! Now, let’s go ahead and change our todo route and template so we can view each todo we created and read the details from them:

app/templates/todo.js:

  1 <h1>{{model.name}}</h1>
  2 
  3 <h2>Details</h2>                                                                                                                                                                                                                          
  4 <p>{{model.details}}</p>

app/routes/todo.js:

  1 import Ember from 'ember';
  2 
  3 export default Ember.Route.extend({                                                                                                                                                                                                       
  4         model: function (params) {
  5                 this.store.find('todo', params.todo_id);
  6         }
  7 });

Yippie! Once we create a few todos, we can view them! There’s our details, and the name of the todo. We are a lot closer now. Still, there’s the ‘UD’ in our CRUD app to knock out.

This is a good stopping point for now. Next time, we will go over a few possibilities for editing and deleting todos, and implement that functionality. If you would like to access the source code for this project, I will have it up on Github hopefully before the end of the week, along with the second half of this brief introduction to Ember.

Leave a Reply

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