Using Django with MVC JavaScript Frameworks

Sean gave a talk at the last Django Boston meetup, on using Django as a backend for Javascript MVC apps like Ember.  In the talk, he discusses the advantages of moving the interface rendering and interaction to the client, as well as things to keep in mind while building an API for the client to retrieve application data.  This modularity makes it easier to built other interfaces for the application, like a mobile app, since each would be built on top of the same API. 

Posted 7 months ago by whichlight
In this three part series, we examine DataSift, and how using Embedly can enhance the data provided by DataSift. In the first post we examined DataSift data and how Embedly can augment URL meta-data. In this post, we’ll start writing some code to supplement DataSift data with Embedly data in Node.js, pushing the stream to clients with Pusher. In the third and final post, we’ll create a client side application that leverages DataSift, Embedly and Pusher to show a live stream of the most popular URLs being tweeted about any subject.
http://i.imgur.com/S9VlRxE.jpg

As we all know, The Internets are a Series of Tubes. In this post we’ll create some glue to connect some of those tubes together, pulling data from DataSift and Embedly Extract, and delivering it to our happy customers via Pusher.

If you didn’t read the first post, DataSift is the simplest way to get a live stream of data from The Social Internets. It’s as simple as defining a query, and performing an HTTP GET.

Pusher, in case you haven’t heard, is the simplest way to get one stream of data into the hands of a bunch of consumers as quickly as possible. Think of it as a giant splitter for the Internet.

We’ll use Nodejs for our glue. Nodejs is one of the most excellent Internet glues available. It speaks tons of protocols, and it’s especially good at HTTP. It’s great for demos like this one, because everyone knows Javascript.

We’ll also get started on the basics of what will eventually be the incredibly beautiful UI for our twitter link stream. We’ll need something to test our tubes, anyway. We won’t do the beautifying until the third and final post, that is where Embedly will really shine.

Of course, the source for the entire app is publicly available on Github. We’ll focus on the implementation of the backend in this post, which can be found in the server directory. This code is not production ready, it’s merely a demonstration of the basic structure of such an app.

Overview

http://i.imgur.com/OIdaD2b.jpg

The glue we’ll make is a Nodejs server that is composed of three main parts. A webserver, a stream collector and a stream worker. The web server simply exposes an API for the frontend to manage the subjects. We use DataSift’s REST API to compile DataSift’s Curated Stream Definition Language for each of our subjects. The collector consumes streams from DataSift. It also listens for events from the web server so it knows when to start and stop listening to particular subjects. The worker consumes the DataSift events, adds Embedly extract data, and pushes them to Pusher. We’ll use Redis to communicate between the components.

HTTP API

As mentioned, we’ll use DataSift’s REST API to compile CSDLs for each subject. DataSift’s API returns a hash for successfully compiled CSDLs that is used with their Streaming API to consume the stream. Each subject will have an associated Pusher channel, which is named according to the hash of the DataSift CSDL.

The subjects API has the following pattern:

/1/subject(/:subject)

Performing a GET on /1/subject will return a JSON object mapping known subjects to DataSift hashes. Performing a GET on a particular subject will return on object mapping just that subject to it’s hash. POSTing to a subject will create it if it doesn’t exist, and return an object containing a hash field. DELETEing will simply remove the subject from our store. Both the POST and DELETE methods publish an event over a Redis channel so that the collector can take the appropriate action, whether it is subscribing or unsubscribing from the given subject.

The csdl.js module encapsulates the work of validating and compiling the CSDL. It reads as follows:

var _ = require('underscore'),
    settings = require('./settings'),
    request = require('superagent'),
    sprintf = require('sprintf').sprintf;

var CSDL = function(opts) {
  if (!(this instanceof CSDL)) {
    return new CSDL(opts);
  }

  _.defaults(this, opts, {
    username: settings.datasiftUsername,
    apiKey: settings.datasiftKey,
    host: 'http://api.datasift.com',
    validateEndpoint: '/v1/validate',
    compileEndpoint: '/v1/compile'
  });
}

/**
 * compile csdl. See http://dev.datasift.com/docs/rest-api/compile
 * for details. Callback takes (error, res). See
 * https://github.com/visionmedia/superagent for details.
 */
CSDL.prototype.compile = function(src, fn) {
  request
    .post(sprintf("%s%s", this.host, this.compileEndpoint))
    .set('Accept', 'application/json')
    .send({csdl: src, username: this.username, api_key: this.apiKey})
    .timeout(3000)
    .end(fn);
}

/**
 * validate csdl. See http://dev.datasift.com/docs/rest-api/validate
 * for details. Callback takes (error, res). See
 * https://github.com/visionmedia/superagent for details.
 */
CSDL.prototype.validate = function(src, fn) {
  request
    .post(sprintf("%s%s", this.host, this.validateEndpoint))
    .set('Accept', 'application/json')
    .send({csdl: src, username: this.username, api_key: this.apiKey})
    .timeout(3000)
    .end(fn);
}

exports = module.exports = CSDL;

subject.js manages our subjects:

var _ = require('underscore'),
    settings = require('./settings'),
    sprintf = require('sprintf').sprintf,
    step = require('step'),
    util = require('util'),
    events = require('events'),
    redis = require('redis'),
    CSDL = require('./csdl');

Subject = function(opts) {
  if (!(this instanceof Subject)) {
    return new Subject(opts);
  }

  _.defaults(this, opts, {
    redis: redis.createClient(),
    key: 'DS_CSDLS',
    channel: 'DS_CSDL_EVENT',
    api: CSDL(),
    pubsub: redis.createClient()
  });

  if (!this.redis) {
    throw new Error('redis not specified');
  }

  var self = this;

  self.pubsub.on('message', function(key, evraw) {
    var ev = JSON.parse(evraw);
    self.emit(ev.type, ev.hash);
  });
  self.pubsub.subscribe(self.channel);
}

util.inherits(Subject, events.EventEmitter);

Subject.prototype.find = function(query, fn) {
  if (!fn && query instanceof Function) {
    fn = query;
    query = null;
  }
  if (query) {
    this.redis.hget(this.key, query, fn);
  } else {
    this.redis.hgetall(this.key, fn);
  }
}

Subject.prototype.delete = function(subject, fn) {
  var self = this;
  self.redis.hget(self.key, subject, function(err, res) {
    if (res) {
      self.redis.hdel(self.key, subject, function(err) {
        if (!err) {
          self.redis.publish(self.channel,
            JSON.stringify({type: 'unsubscribe', hash: res}));
        }
        fn(err);
      });
    } else {
      fn(err);
    }
  });
}

Subject.prototype.add = function(subject, fn) {
  var csdl = _.template(settings.csdlTemplate, {subject: subject}),
      self = this;

  step(
    function() {
      self.find(subject, this);
    },
    function(err, res) {
      if (res) {
        fn(null, {hash: res});
      } else {
        self.api.validate(csdl, this);
      }
    },
    function(res) {
      if (res) {
        if (res && res.status == 200) {
          self.api.compile(csdl, this);
        } else {
          console.error(res);
          fn(new Error(sprintf('failed to verify CSDL for %s', subject)));
        }
      }
    },
    function(res) {
      if (res) {
        if (res && res.status == 200) {
          self.redis.hset(self.key, subject, res.body.hash, function(err) {
            if (err) {
              console.error(err && err.stack);
              fn(new Error(sprintf('failed to save hash for %s', subject)));
            } else {
              self.redis.publish(self.channel,
                JSON.stringify({type: 'subscribe', hash: res.body.hash}));
              fn(null, res.body);
            }
          });
        } else {
          console.error(res);
          fn(new Error(sprintf('failed to compile CSDL for %s', subject)));
        }
      }
    }
  );
}

exports = module.exports = Subject;

Key points are that it uses a template CSDL defined in settings.js and that it publishes POSTs and DELETEs over the Redis channel. It also keeps track of what subjects we are subscribed to in the event of a restart and to respond to the /1/subject query. Here is the CSDL template that we used:

twitter.links exists
AND
twitter.text contains_any "<%= subject %>"
AND
twitter.user.lang in "en"

The Subject class will inject subject into the template and use the CSDL module to compile the CSDL.

The Collector

The collector is an simple module that subscribes and unsubscribes to/from CSDLs in response to events on our web frontend. It also subscribes to any existing channels persisted in Redis on start. Any events consumed will simply be pushed to a list in Redis for consumption by the worker. Here is the listing:

var DataSift = require('datasift'),
    step = require('step'),
    utils = require('./utils'),
    settings = require('./settings'),
    consumer = new DataSift(settings.datasiftUsername, settings.datasiftKey),
    connected = false,
    Subject = require('./subject'),
    subject = new Subject(),
    _ = require('underscore'),
    debug = require('debug')('subject-server:collector');

step(
  function() {
    utils.redis(this);
  },
  function(err, redis) {
    subject.on('subscribe', function(hash) {
      if (connected) {
        debug('subscribing %s', hash)
        consumer.subscribe(hash)
      }
    });
    subject.on('unsubscribe', function(hash) {
      if (connected) {
        debug('unsubscribing %s', hash)
        consumer.unsubscribe(hash)
      }
    });
    consumer.on("connect", function(){
      debug('connected');
      subject.find(function(err, res) {
        _.values(res).forEach(function(hash) {
          debug('subscribing %s', hash);
          consumer.subscribe(hash);
        });
      });
      connected = true;
    });
    consumer.on('interaction', function(data) {
      debug('interaction', data.hash);
      redis.lpush(settings.inqueueKey, JSON.stringify(data));
    });
    consumer.on('disconnect', function() {
      debug('disconnected');
      connected = false;
    });
  }
);

exports = module.exports = {
  start: function() {
    consumer.connect();
  },
  stop: function() {
    consumer.disconnect();
  },
  stat: function() {
    return connected ? 'connected' : 'disconnected';
  }
}

Worker

The worker is where the three services intersect. The events that we publish over Pusher will be URL based and not tweet based. That means that if a tweet contains multiple URLs, it will translate to multiple Pusher events. We’ll take the tweet we received from DataSift, scrape it for URLs, call Embedly extract on the URLs, then publish both the DataSift data and the extract data via Pusher. Here is the meat of the worker module:

step(
  // get next event from the queue
  function() {
    debug('fetching from ' + settings.inqueueKey);
    redis.brpoplpush(settings.inqueueKey, settings.outqueueKey,
                     1000000, this);
  },
  // fetch extract info
  function(err, data) {
    if (err) {
      console.error(err.stack);
      return setTimeout(utils.partial(redis, embedlyApi, procNext));
    }

    debug('got data')
    try {
      var ev = JSON.parse(data),
          urls = findUrls(jpath(ev, 'data.interaction.content'));

      embedlyApi.extract({urls: urls}, utils.partial(ev, this));
    } catch(e) {
      console.error(e.stack);
      return setTimeout(utils.partial(redis, embedlyApi, procNext));
    }
  },
  function(err, args) {
    if (err) {
      console.error(err.stack);
      return setTimeout(utils.partial(redis, embedlyApi, procNext));
    }

    var extracts = args[0],
        ev = args[1];

    var self = this,
        procCount = 0;

    if (extracts.length > 0) {
      extracts.forEach(function(extract) {
        try {
          if (extract.url && extract.type != 'error') {
            procCount += 1;
            debug('got extract for %s', ev.hash);

            pusher.trigger(ev.hash, 'tweet', {
              'event': ev,
              'embedly': extract
            });
          }
        } catch(e) {
          console.error(e.stack);
        }
      });
    }
    if (procCount == 0) {
      debug('no extracts processed');
      return setTimeout(utils.partial(redis, embedlyApi, procNext));
    }
    this();
  },
  // next iteration
  function(err) {
    // don't overflow stack
    debug('next');
    return setTimeout(utils.partial(redis, embedlyApi, procNext));
  }
);

Conclusion

http://i.imgur.com/lG6mpLM.jpg

That’s all folks. If you are considering using this as the starting point of a real app, please take a look at Pusher’s Server API. You can do things like authorize users and register web hooks to be notified when channels are occupied or vacated. This could be used to cleanup DataSift channels when they aren’t being used.

If you want to try the app out, feel free to clone the repo and follow the instructions in the README. It isn’t turnkey, since you have to sign up for accounts on the three services, but they all offer free accounts and it shouldn’t take to long to sign up.

In the next post we’ll finish building the frontend of the app to create a beautiful frontend for our twitter URL stream.

Posted 8 months ago by dokipen
Embed This

The third post in our series on Ember will continue to work from the development environment that we worked on in the first and second posts.

As promised last time, this post will dive into views and controllers. Here is where the magic happens. We’ll also touch on models, but we’ll save the bulk of the models work for our next post.

Controllers

This time let’s try and fill in some details on the about page. Since it needs to be about something, let’s create a fictitious organization called Jubarian. We’ll create a simple controller to display information about the organization and add it to index.html.

    // app/scripts/modules/about.js
    App.AboutController = Em.Controller.extend({
      name: 'jubarian.org',
      description: 'A non-profit organization of peopled dedicating to spreading joy.'
    });

    // app/index.html
    __snip__
    <script src="scripts/modules/about.js"></script>
    __snip__

    // app/templates/about.handlebars
    <h1>About {{name}}</h1>
    <p>{{description}}</p>

This works, but it’s not very MVC. We shouldn’t be adding our data directly to our controller. Our controller should only contain data directly relevant to display concerns.

Controllers in Ember are a little different then what you may be used to in more traditional MVC frameworks. Usually, they are implemented as proxies to a model and passed to the view using an ObjectController or ArrayController. The view will never interact with the model directly, only through the controller proxy. This allows us to have a clean separation of data concerns and display concerns. The controller decorates the model with display logic. Let’s implement something to make things more clear.

ObjectController

There are two main types of controllers that are most common in Ember applications, the ObjectController and the ArrayController. In this case, we’ll use an ObjectController, since there is only a single organization being displayed. We’ll create a naively simple model to hold our data and connect it to the controller in the router.

     // app/scripts/modules/about.js
     App.Fixtures = App.Fixtures || {};

     // our model
     App.Fixtures.Org = {
       name: 'jubarian.org',
       description: 'A non-profit organization of peopled dedicating to spreading joy.'
     }

     App.AboutController = Em.ObjectController.extend({})

     App.AboutRoute = Em.Route.extend({
       model: function() {
         return App.Fixtures.Org;
       }
     });

Properties of Ember objects aren’t set directly. We always use set(name) and get(name) functions to allow Ember to notify the objects observers of property changes. This is what allows Ember to automatically update views when properties change. Since our Org object is not an Ember object, Ember won’t be able to detect changes to it. We’ll cover a more robust way of doing thing in the next post, when we cover models and ember-data.

We’ll need to wire up our Org to the AboutController. We do this by defining a method function property on our AboutRoute object. ObjectControllers are different then a regular controller because they proxy all of their undefined properties to the object defined as their content property. Ember will automatically call the model function and set the result to the content property of the controller.

Our controller, as it stands, doesn’t do anything. Before we created it we were using an auto-generated controller of type Controller. Since it wasn’t an ObjectController, it didn’t proxy to the content property. Now that we’ve defined it as an ObjectController, all undefined properties pass through to the content property, our org fixture.

Let’s add some state to the display so the controller has something to do. We’ll add a list of team members with a toggle for displaying or hiding it. This will also give us a chance to setup an ArrayController and see how that works. But first let’s just create a static view. And modify the AboutController to handle the team display logic.

      // app/templates/team.handlebars
      <ul>
        <li>Bob</li>
        <li>Andy</li>
      </ul>

      // app/templates/about.handlebars
      _snip_
      <p>
        <a href="#" {{action toggleTeam}}>{{toggleTeamLabel}}</a>
      </p>

      {{view App.TeamView isVisibleBinding="teamVisible"}}


      // app/scripts/modules/about.js
      _snip_
      App.AboutController = Em.ObjectController.extend({
        teamVisible: false,
        toggleTeam: function() {
          this.set('teamVisible', !this.get('teamVisible'));
        },
        toggleTeamLabel: function() {
          var vis = this.get('teamVisible');
          return vis ? 'hide team' : 'show team';
        }.property('teamVisible')
      });

      App.TeamView = Em.View.extend({
        templateName: 'team'
      })

You’ll notice that we had to explicitly create the TeamView object. That is because it wasn’t generated through the router so the template name isn’t automatically discovered.

Properties

toggleTeamLabel is a good example of a dynamic property. When controller.get('toggleTeamLabel') is called, the function will be used to generate toggleTeamLabel’s value. To denote this, the property function is called on the function. The property function takes a list of arguments that define it’s dependencies. This is important for observers of the property, and to allow Ember to cache the property result. Ember knows that when teamVisible changes, so does toggleTeamLabel.

In our about template, we have created an anchor tag that fires toggleTeam on AboutController whenever it is clicked using the action helper. We are using the toggleTeamLabel as the display name of the tag. We use the view helper to insert the TeamView into our template. isVisibleBinding="teamVisible" is a special attribute that binds the isVisible attribute of TeamView to the teamVisible property of the current scope, which happens to be AboutController. Now, whenever teamVisible changes, so will isVisible. isVisible is a builtin property of every View that controls whether or not it is visible.

ArrayController

Now let’s convert the static template, team, into a proper ArrayController. Since we want the view to have it’s own controller, and not share a controller with AboutView, we’ll change it from a {{view}} to a {{render}}. This will allow us to wire up the array of team members to an ArrayController and associate it with the view.

    // app/modules/about.js
    App.Fixtures.Team = [
      { name: 'Bob' },
      { name: 'Andy' },
      { name: 'John' },
      { name: 'Kawan' },
      { name: 'Nina' },
      { name: 'Sean' },
      { name: 'Art' },
    ];
    
    _snip_

    App.Team Controller = Em.ArrayController.extend({
      needs: 'about',
      about Binding: 'controllers.about'
    });

    App.TeamView = Em.View.extend({
      isVisibleBinding: 'controller.about.teamVisible'
    });

    // app/template/about.handlebars
    _snip_

    {{render "team" App.Fixtures.Team}}

    _snip_

    // app/templates/team.handlebars
    <ul>
      {{#each controller}}
        <li>{{name}}</li>
      {{/each}}
    </ul>

The render helper will automatically find the correct view and controller based on the first parameter, the template name. The second parameter will be used as the model.

The way we are binding AboutController’s teamVisible property to TeamView is a bit contrived, but it shows how controllers can be bound together. We declare that TeamController needs AboutController and that the about property of TeamController is bound the AboutController. We then bind TeamView’s isVisible property to AboutController’s teamVisible property.

Since TeamController is now an ArrayController, we can iterate over it’s content with the each helper. each will iterate over the collection putting it in scope for the contained template, in this case <li>{{name}}</li>.

Conclusion

With a few lines of code we’ve made a dynamic, data driven about page and demonstrated the power of Ember views and controllers. You can find the source for the project on github  This wraps up our brief intro to views and controllers. Our next blog in the series will give an introduction to ember-data, a web ORM for ember. See you next time!

Posted 1 year ago by dokipen
Embed This

This is the second post in our series on Ember. We will be building off the development environment we set up in the first post.

In practice, everything is based off the router and templates. Ember handles the dynamic creation of views and controllers. You really don’t need them until you start dealing with data and events.

By default Ember sets up two routes for you: application and index. application is the container for your entire application while index is the route for the root of your site ‘/’. You can create the two templates for these routes in the templates directory.

$ touch app/templates/application.handlebars
$ touch app/templates/index.handlebars

And for good measure we will create a global nav.

$ touch app/templates/nav.handlebars

The application template is a good place to create scaffolding; things that need to be on every page and also inside the scope of the application. Here is our simple application.handlebars:

{{ render "nav" }}
<div class="row">
 <div class="large-12 columns">
  {{outlet}}
 </div>
</div>

We just introduced two handlebars helpers that Ember uses: render and outletHandlebars helpers are a part of the Handlebars template language that is included with Ember. The render and outlet helpers are added by Ember.

About render from the docs:

Renders the named template in the current context using the singleton instance of the same-named controller.

render allows you to include one template into another. It’s much like the template helper, but also allows you to pass data along. In this case you could use either.

outlet is a placeholder that allows you to specify where the child node’s content will be rendered. They are much like {% block %} tags in Django or <%= yield ==%> in ERB.

Since application.handlebars is the base route the content from index.handlebars will be rendered where this outlet is. We can create a simple index.handlebars to prove this.

$ echo "<h1>Welcome To My App</h1>" > app/templates/index.handlebars

Routes

Routes are URL paths. To add a new page, you need to add a new route. So to add our about page, we need to add a route to our router.

App.Router.map(function(){
  this.route('about');
});

Ember is creating AboutRoute, AboutView, and AboutController. We don’t really need those for this exercise, but we do need a template with some content.

$ echo "<h1>About To My App</h1>" > app/templates/about.handlebars

If you visit ‘/#/about’ you will see the content.

Resources

When you want to start grouping routes, i.e. “/company/contact” and “/company/team” resources come in to play. The benefit here is that they share common templates. While you could so something like this:

App.Router.map(function(){
  this.route('contact', {path:'/company/contact'});
  this.route('team', {path:'/company/team'});
});

It becomes a mess really quickly. Instead you should use a resource.

App.Router.map(function(){
  this.resource('company', function(){
    this.route('contact');
    this.route('team');
  });
});

It is however a little more confusing when it comes to templates. Like the application route, we need to create a base container for all the leaf routes. This could be as simple as:

$ echo "{{outlet}}" > app/templates/company.handlebars

In this case, everything will just be added to the outlet in the application.handlebars. If you want every company page to have common display attributes, you can change the template:

<h1>Company</h1>
<div class="row">
  <div class="large-9 columns">
    {{outlet}}
  </div>
  <div id="sidebar" class="large-3 columns">
    {{outlet sidebar}}
  </div>
</div>

You’ll notice here that we added a named outlet. By default all content from the leaf routes will go into the {{outlet}} unless we tell it to go somewhere else instead.

We can then create the leaf templates.

$ mkdir app/templates/company
$ touch app/templates/company/index.html
$ touch app/templates/company/contact.html
$ touch app/templates/company/team.html

This is one of the nice things about having ember_templates set up and compiling templates for you. Ember expects the templates to be put into EM.TEMPLATES hash as ‘resource/route’ or you need to declare the templateName on the view. The former is less code.

Custom Routes

We set up that outlet earlier, it’s time that we use it. In order to use named outlets we need to modify the Routes behavior. Here we will change that Company Team Route to add a sidebar. In the main.js file we are going to add:

App.CompanyTeamRoute = Em.Route.extend({
  renderTemplate: function(controller, model){
    // Render the base template
    this._super(controller, model);
    // Render the bios template into the sidebar
    this.render('bios', {outlet: 'sidebar'});
  }
});

And then add the bios template:

$ echo "<h2>Bios</h2>" > app/templates/bios.handlebars

When you visit /#/company/team you will see the bios section in the template.

Alright, that was a quick primer into the Router and Templates, you can see the final code here: https://github.com/screeley/ember-demo-environment/tree/router. Next week, we’ll go into using View and Controllers.

Posted 1 year ago by screeley
Embed This

Every time I see Ember on Hacker News it’s being trashed, like MongoDB trashed. It sucks for an open source project when the herd mentality kicks in and it becomes cool to hate. Sadly, I’m not one of the cool kids.

Ember is excellent. We use it extensively here at Embedly for our developer dashboard. While none of us here are JavaScript experts, Ember has made us incredibly productive.

We are going to do a series of posts on Ember at Embedly. Mostly tips and tricks that have made us successful. These are not aimed at the pro, but the amateur.

A lot goes in to baking a production-quality website, and Ember adds to that. As a result, there is a lot of boilerplate when it comes to setting up the development environment. Rest assured that once you fight through the boilerplate, you’ll be able to iterate at an impressive rate.

Okay, so you have a new awesome idea of what you are going to build, how do you get started?

Yeoman/Grunt

Grunt is the Javascript Task Runner. It makes the set up of common tools like jshint, compass, uglify and development servers dead simple. We use it in almost every project now and it has made development a whole lot easier.

Yeoman is a thin wrapper around Grunt that really just builds out a directory structure and a few files. One member of your team will use it once and never think about it again.

So, let’s get started.

$ mkdir demo
$ cd demo
$ npm install -g yo grunt-cli bower

At the time of writing, the ember generator for yeoman is hosed, so you can do what you wish with that information. We can just use the default webapp generator.

$ yo webapp
$ npm install & bower install

This will build a pretty simple directory structure.

  app /
    index.html
    scripts/
      main.js
    styles/
      main.css
  Gruntfile.js
  package.json
  test /

Yeoman sets up the project so it can immediately be used. Run grunt’s server to launch your new web app.

$ grunt server

Tada, a browser will open to http://localhost:9000/ and show a nice little Allo message.

Getting Ember

Bower is Twitter’s package management tool for the Web. Let’s get Ember installed using bower.

$ bower install ember

Bower will handle getting all the right dependencies and install them in a components directory. You will then need to add Ember and Handlebars to your index.html file.

$ vim app/index.html

You can put the following lines near the bottom but above main.js:

<script src="components/handlebars/handlebars.js"></script>
<script src="components/ember/ember.js"></script>

Grunt will live reload your browser. You now have all the dependencies for a simple Ember app. You can prove it by opening up the console and running Em.

To recap, that was about 5 commands to get Ember completely ready to go:

$ npm install -g yo grunt-cli bower
$ yo webapp
$ npm install && bower install
$ bower install ember
$ vim app/index.html

If you complain about how hard Ember is to set up you are using the wrong tools.

Templates

Here on out is optional, but will probably save you some headaches later down the road. We will set up grunt-ember-templates, an Application and the Router.

The grunt-ember-templates module pre-compiles all your templates on the fly and adds them to Em.Templates, which is where Ember keeps your templates so it can match them to your views, based on naming convention. More on this later.

$ npm install grunt-ember-templates --save-dev
# Create the templates directory.
$ mkdir app/templates
$ touch app/templates/index.handlebars

We are going to have to update the Gruntfile to run ember_templates

Grunt is configured by a Gruntfile.js, which uses JSON to define a bunch of different actions that Grunt can perform for you. This is where we’ll hook up grunt-ember-templates to Grunt.

$ vim Gruntfile.js

In the Gruntfile, you are going to load the task before, I generally put it before the grunt.initConfig

grunt.loadNpmTasks('grunt-ember-templates');

Inside the grunt.initConfig you will need to set up the compile task to point at all the templates.

ember_templates: {
  compile: {
    options: {
      templateName: function(sourceFile) {
        return sourceFile.replace(/app\/templates\//, '');
      }
    },
    files: {
      "<%= yeoman.app %>/scripts/templates.js": ["<%= yeoman.app %>/templates/**/*.handlebars"]
    }
  }
},

Next tell the watch command to look for changes in the templates directory to make the live reload work correctly:

watch {
  ...
  ember_templates: {
      files: '<%= yeoman.app %>/templates/**/*.handlebars',
      tasks: ['ember_templates']
  },
  ...
}

Lastly, add ember_templates to the server task, you are looking for this line:

grunt.registerTask('server', function (target) {
    ...
    grunt.task.run([
        ...
        'compass:server',
        'ember_templates', // Add this line.
        'livereload-start',
        ...
    ]);
});

Once you restart grunt server, you can take a gander at the app/scripts/templates.js file in scripts. It should look something like this:

Ember.TEMPLATES["index"] = Ember.Handlebars.template
...

Add that script to the index.html file to make sure the browser loads it.

<script src="scripts/templates.js"></script>

That was kind of a detour, you don’t really need to set up ember_templates, you can use inline script tags or Em.Handlebars.compile, but this method offers a nice separation of HTML and javascript. For a quick reference you can see the completed Gruntfile here.

Application

The last step here will be to set up a simple App and Router to take advantage of our setup.

You can add the main.js to the end of index.html and delete whatever is in that file.

<script src="scripts/main.js"></script>

Next set up a rootElement for Ember to use, this will make sure that Ember puts the html where you want it to. Replace the div with the class container with:

<div id="app"></div>

Next add a simple Application to main.js:

App = Em.Application.create({
  rootElement: $('#app'),
});

You can then edit index.handlebars with a simple h1

$ echo "<h1>My App</h1>" > app/templates/index.handlebars

When grunt reloads the server, your browser will have My App as the header. There is a little magic going on here. Ember is creating a Router for you and by default creates and Application Route and an Index Route. This is why just adding an index.handlebars works.

The Router is dead simple to set up as well. In main.js add

App.Router.map(function(){
  this.route('about');
});

And then create the about template

$ echo "<h1>About</h1>" > app/templates/about.handlebars

If you navigate to /#/about, boom, you are now in the About route. You can also link to the about page from index by adding:

{{#linkTo "about"}}About{{/linkTo}}

This all works based on naming conventions. You can create an AboutController, AboutRoute and AboutView, but Ember will take care of that for these simple routes. More on that later.

Recap

Setting up Ember is a lot of boiler plate code, and there are some really good tools out there that do it for you. Nothing I have described above is earth shattering, but it should get you set up for your next Ember project.

I put the complete demo on Github for reference you can find it at github.com/screeley/ember-demo-environment.

For our next post in the Ember series, we will go into Ember basics; Views, Routing and Outlets. Stay tuned.

- Sean

Discuss on Hacker News

Posted 1 year ago by screeley
Embed This