emberjs is a new JavaScript framework for building interactive/responsive web applications. Compared to "classical" approach it removes the need to write code for AJAX requests fetching, modifying data or for example code responsible for DOM manipulation. Like in good framework we write only the code implementing our app functionality/idea.

In the same category we can also find other similar frameworks like Backboje.js or Angular.js. Each has its strong and weak points.

In this article I'll showcase basic ember features and ember application development process for a Django-Tastypie-Ember application. You need at least basic Django and django-tastypie knowledge (REST API-making application for your models). The whole application developed in this article can also be found on github.

Installation and configuration

On the Python side we will need Django 1.5 or newer (or older with backported "verbatime" template tag). We start with a fresh Django project and application:

django-admin.py startproject ember_showcaser
cd ember_showcaser/
django-admin.py startapp quotes
mkdir quotes/static
mkdir quotes/templates
Next step is to install (pip) django-tastypie and south. Tastypie makes a REST API which ember can use to manipulate the data stored in application models. South is handy for model changes migrations (semi-optional). Add those apps to INSTALLED_APPS:
'tastypie',
'south',

Add also the quotes application created in previous step.

And configure database settings as you desire. I used a SQLite3 database:
manage.py syncdb
manage.py migrate
At this point we should have a working django project.

ember.js installation

Ember.js framework as a whole consists of few JavaScript files that we have to download and build if needed (or use those provided in this article).

From emberjs.com download ember.js and ember-data.js. From handlebarsjs.com download handlebars.js. From a github ember-data-tastypie-adapter repository fetch (packages/ember-data-tastypie-adapter/lib) tastypie_adapter.js and tastypie_serializer.js.

When we have all the JS files we can start making our first Ember/Django application. The initial step is to make a plain template view showing a basic template with all those JavaScript files included.

Basic view

In quotes/views.py create a typical TemplateView:
from django.views import generic


class MainPageView(generic.TemplateView):
    template_name = 'index.html'

main_page = MainPageView.as_view()
In urls.py hook it under the primary (/) url:
url(r'^$', 'quotes.views.main_page'),
The most important part is the HTML template, which looks like so:
<!doctype html>
<html>
<head>
    <title>Ember.js showcase</title>
    <meta charset="utf-8"/>
    <script src="{{ STATIC_URL }}js/vendor/jquery-1.11.0.min.js" type="text/javascript"></script>
    <script src="{{ STATIC_URL }}js/vendor/handlebars.js" type="text/javascript"></script>
    <script src="{{ STATIC_URL }}js/vendor/ember.js" type="text/javascript"></script>
    <script src="{{ STATIC_URL }}js/vendor/ember-data.js" type="text/javascript"></script>
    <script src="{{ STATIC_URL }}js/vendor/tastypie_serializer.js" type="text/javascript"></script>
    <script src="{{ STATIC_URL }}js/vendor/tastypie_adapter.js" type="text/javascript"></script>
    <script src="{{ STATIC_URL }}js/quotes.js" type="text/javascript"></script>
</head>
<body>
<h1>Ember.js Showcase Quotes App</h1>
</body>
</html>
All third-party JavaScript files are placed in quites/static/js/vendor/ (/quotes/static/ is a part of Django /static/ files). Empty "quotes.js" is for our ember application code.

At this point we should have a working view that has all the JS files and no errors in the browser JavaScript console. Note that file order must be kept as shown to satisfy dependencies.

Ember.js Application

Structure of an ember application

As you can read on ember website the framework has few layers, similar to Django or other frameworks. I won't repeat what's written there. I'll just give a quick overview from a Django developer point of view.

Templates are managed by the handlebars library, which uses quite similar tags to those from django templates. The newly added verbatim template tag prevents rendering of Django template tags - thus preserving handlebars templates used by the ember application. As handlebars can't include templates from external files usually all handlebars templates are in one file - one after another (although you could use Django to include verbatimed file-based-templates into the primary template file).

Controller is something like a view in Django. This object stores the state of the application (a part of it). Compared to Django view this object is "alive" as it can respond to events or cause template re-rendering etc. There is a lot of event-driven (asynchronous) behavior in ember application in general.

Views are a layer between a controller and a template. They are used to translate primitive events like click, submit into meaningful evens handled later by the controller (like "createQuote" and not "submit"). In simple cases we won't even have to define a view for given template-controller set.

Models are quite similar layer to Django models. In Django-Ember applications they will reflect your Django models quite close. As ember-data can't do Python Tastypie is used. It creates a REST JSON API which is used by the ember-data tastypie adapter to provide read/write functionality for the ember models. Models in ember are also asynchronous. Executing Model.find() to fetch all entries won't return them but an object that is fetching them and will have them "in a moment". That asynchronous nature affects how controllers or in general ember applications work and are codded - something a Django developer may not be used to.

Routing or route-objects are used to map templates (like urls.py), but also to match (by keeping matching naming) a template with a controller and a model object. In a full blown configuration router tells the controller to represent a data object from the model and render the template for it. In a simple case it's just something like urls.py, while in extended version it can do some extra features like doing stuff on init.

With bigger apps it's good to split the JavaScript code into separate files - for models, controllers, views and one or two for routers (one can have the url map, the second one extended routers). In this article I used one file to make it easier to show the code of a simple application.

Creating an ember application

We will use quotes.js for JavaScript code and index.html for handlebars templates. Starting with the template we define the primary application template:

<body>
{% verbatim %}
    <script type="text/x-handlebars">
        <h1>Ember.js Showcase Quotes App</h1>
        {{outlet}}
    </script>
{% endverbatim %}
</body>

Application renders the primary template and then puts the HTML code of the url-specific template in the place of "outlet" tag. In this template we can create stuff like header, footer, some navigation. Without verbatim tag the outlet would have been render by Django.

In JavaScript we have a short create call:

(function($, undefined ) {
    Quotes = Ember.Application.create();

}(jQuery));
At this moment we have a Ember application that does nothing. In the browser JavaScript console you should see debug info - versions of the ember libraries (and no errors).

Django models and Tastypie resources

tastypie is a handy app that will make an REST API for use to use in ember application. To make a Tastypie resource we first need to have a Django model, like this one:

from django.db import models


class Quote(models.Model):
    quote = models.CharField(max_length=300, unique=True)
    poster = models.ForeignKey('auth.user')
    posted_date = models.DateTimeField(auto_now_add=True)

    def __unicode__(self):
        return self.quote
A simple quotes model. To make a resource create quotes/resources.py file with:
from django.contrib.auth.models import User
from tastypie.resources import fields
from tastypie.resources import ModelResource

from quotes import models


class UserResource(ModelResource):
    class Meta:
        queryset = User.objects.all()
        allowed_methods = ['get']
        always_return_data = True
        fields = ['username']


class QuoteResource(ModelResource):
    poster_id = fields.ForeignKey(UserResource, 'poster')

    class Meta:
        queryset = models.Quote.objects.all()
        allowed_methods = ['get']
        always_return_data = True
Our quotes resource needed that we also define a resource for the User model used in relation. If we plan to use data from related objects we have to define resources for them and define those relations also in those resources. We also have to include those resources in urls.py:
from django.conf.urls import patterns, include, url
from django.contrib import admin
from tastypie.api import Api

from quotes import resources
admin.autodiscover()

v1_api = Api(api_name='v1')
v1_api.register(resources.UserResource())
v1_api.register(resources.QuoteResource())

urlpatterns = patterns(
    '',
    url(r'^$', 'quotes.views.main_page'),
    url(r'^admin/', include(admin.site.urls)),
    (r'^api/', include(v1_api.urls)),
)
So now we should have a working Tastypie API. URL like http://127.0.0.1:8000/api/v1/user/?format=json should show a list of users. If you add some quotes via the admin then http://127.0.0.1:8000/api/v1/quote/?format=json should list them too.

Next step is to make Ember models that will use those resources to get/put data.

Creating Ember.js models

When we have working Tastypie resources we can start creating models in ember application. Data aspects in ember are handled by ember-data. Here we have models for our resources:

(function($, undefined ) {
    Quotes = Ember.Application.create();
    Quotes.ApplicationAdapter = DS.DjangoTastypieAdapter.extend({});
    Quotes.ApplicationSerializer = DS.DjangoTastypieSerializer.extend({});

    var attr = DS.attr;

    Quotes.Quote = DS.Model.extend({
        quote: attr('string'),
        poster: DS.belongsTo('user'),
        posted_date: attr('date')
    });
    Quotes.User = DS.Model.extend({
        username: attr('string')
    });

}(jQuery));

At start we define a data adapter and serializer which will be used by ember-data. Model names are matched to the API resources names. For longer names made of more than one word (like a FooBarResource) we may need to add resource_name that matches what the adapter wants (you will see requests in the browser console).

This is the third place where we have to describe our models. Each field has a type like string, number, boolean, or date. Relations are done via DS.belongsTo, or DS.hasMany (which needs a reverse DS.belongsTo relation defined too).

We now have working models. this.store.find('quote') will return a promise that at some point in time it will have all quotes. It's different from synchronous Django applications making. We will use that now to show a list of quotes on the main page.

Routing and templates

Lets start by adding a new template:
{% verbatim %}
    <script type="text/x-handlebars">
        <h1>Ember.js Showcase Quotes App</h1>
        {{outlet}}
    </script>
    <script type="text/x-handlebars" data-template-name="quotes-list">
        {{#each quote in content}}
            <article>
                {{quote.quote}}
                <p>Added by: {{ quote.poster.username }}</p>
            </article>
        {{/each}}
    </script>
{% endverbatim %}

PSo we made a "quotes-list" template. In that template we iterate over "content" - a list of quotes. Based on the template name the URL is generated - "/#/quotes-list" (if we don't change that behavior).

Now we have somehow set a list of quotes under that variable. Also we have to add this template to the templates map. Routing is the place for that:

    Quotes.Router.map(function() {
        this.route('quotes-list');
    });

    Quotes.IndexRoute = Ember.Route.extend({
        redirect: function() {
            this.transitionTo('quotes-list');
        }
    });
    Quotes.QuotesListRoute = Ember.Route.extend({
        model: function() {
            return this.store.find('quote');
        }
    });

Router.map is a "simple" map of all used templates - in our case one template. Next in line are two extended routes - primary Quotes.IndexRoute and Quotes.QuotesListRoute for the template (note the naming reflecting template name). When we enter the root URL (/) we will be redirected to the quotes-list template URL. When the routing for that template is launched it will assign the list of quotes (object that will have it when it fetches it) to the variable content used in the template.

If you look closely you will notice that the username shows after the quotes texts are shown. This is caused by the relation. When we want to display the username in the template ember makes a query to the API for the user data with username. If we had a tree of related objects those would load one after another and template would notice changes (object fetched) and render data from them as they are fetched.

Template controller (built in controller for now) ability to watch for changes and take actions/re-render part of the template without reloading the page and without the need for use to code all the annoying stuff is one of the key features of this framework.

List of quotes displayed by ember

List of quotes displayed by ember

At first many things in ember may look like magic (events and asynchronous actions), but when you look how this basic app is being made and how it works - the magic will become clear (or you will become a sorcerer which is cool anyway :)). If you have questions google for solutions or ask directly on stackoverlow, check the documentation or visit #emberjs IRC channel on Freenode.

Navigation

Lets play with something simple, yet cool - navigation and making links to templates with "linkTo" tag. To do that I'll created two simple templates:
    <script type="text/x-handlebars" data-template-name="about">
        <h2>About the application</h2>
        <p>This application rocks!</p>
    </script>
    <script type="text/x-handlebars" data-template-name="contact">
        <h2>Contact</h2>
        <p>Call Houston in case of troubles.</p>
    </script>
I've added them to the map:
    Quotes.Router.map(function() {
        this.route('quotes-list');
        this.route('about');
        this.route('contact');
    });
And now I created the navigation bar (HTML) in the primary template:
    <script type="text/x-handlebars">
        <h1>Ember.js Showcase Quotes App</h1>
        <nav>
            {{#link-to "quotes-list"}}Quotes{{/link-to}} |
            {{#link-to "about"}}About{{/link-to}} |
            {{#link-to "contact"}}Contact{{/link-to}}
        </nav>
        {{outlet}}
    </script>
As a result we get a nice working links that allows to change pages quickly and with no page reloading. A link to current template will also have an "active" class which can be used in CSS.
Navigation in action

Navigation in action

Creating data records

Now something harder - lets make a template for adding new quotes. This can be done in pure ember-way or the worse, old way with jQuery and DOM manipulation. Knowing the ember way may not be always obvious (that need some experience and more examples in the docs). In this template we will have a form with an input, so I'll use a TextField view embedded in ember. The advantage of using this view is that it binds the input value with a variable available in the controller (in both ways), which makes it easy to fetch/modify the value from the controller (and in other cases to for example watch for changes). Here is the template with the view used:

    <script type="text/x-handlebars" data-template-name="add-quote">
        <h2>Add a quote</h2>
        <form>
            {{view Ember.TextField valueBinding="quote" placeholder="Add a quote"}}
        </form>
    </script>
Ember.TextField is a simple view rendering a text input. The "valueBinding" defines the variable under which we will have the input value in AddQuoteController. Here is the controller/template view code (add the template to the map too):
    Quotes.AddQuoteController = Em.Controller.extend({
        quote: '',
        actions: {
            saveQuote: function(text) {
                if (text) {
                    this.store.createRecord('quote', {'quote': text}).save();
                    this.set('quote', '');
                    this.transitionToRoute('quotes-list');
                }
            }
        }
    });

    Quotes.AddQuoteView = Ember.View.extend({
        submit: function() {
            var text = this.get('controller.quote');
            this.get('controller').send('saveQuote', text);
        }
    });

Let us start with AddQuoteView view. As you can see it "translates" a submit event coming from the template into a "saveQuote" event passed to the controller. The controller creates a new Quote object, commits changes to the backend and then it clears the input value (two way binding). this.set/this.get is the ember way to set/get things. At the end we use "transitionToRoute" to redirect the user to the quotes list where he will see his quote (while save request is probably still not finished). Yet another ember-in-action nice feature.

The save operation will fail in the Tastypie resource as we didn't allowed POST requests for object creation. We have to add it to "allowed_methods" as well add some input validation:

from django.contrib.auth.models import User
from tastypie.authorization import Authorization
from tastypie.http import HttpUnauthorized
from tastypie.resources import fields
from tastypie.resources import ImmediateHttpResponse
from tastypie.resources import ModelResource

from quotes import models


class UserResource(ModelResource):
    class Meta:
        queryset = User.objects.all()
        allowed_methods = ['get']
        always_return_data = True
        fields = ['username']


class QuoteResource(ModelResource):
    poster = fields.ForeignKey(UserResource, 'poster')

    class Meta:
        queryset = models.Quote.objects.all()
        allowed_methods = ['get', 'post']
        always_return_data = True
        authorization = Authorization()

    def obj_create(self, bundle, **kwargs):
        if bundle.request.user.is_authenticated():
            return super(QuoteResource, self).obj_create(bundle,
                                                         poster=bundle.request.user,
                                                         **kwargs)
        raise ImmediateHttpResponse(HttpUnauthorized('Not authenticated'))

obj_create method is called when the record is beign created. We override it to set the correct user as poster or return API error if he isn't authenticated (this isn't an exception so logging for production apps is a very good thing to add).

In upcoming articles we will add more features and Tastypie resources will grow in size. More methods will get overridden and more validation will be applied. Note that this API may not return data that current user shouldn't see/get. That's why we override Tastpie methods to for example filter data only to that the current user should see. Security for a public database query tool like this API is an important issue.

The end

This is the end of this article (although there will be more). I've tried to show a quick start with ember, the initial steps to make an application and some big picture on how ember applications work and differ from pure Django apps. You can also get the code from github. I'll add new features in upcoming tutorials.

blog comments powered by Disqus

Categories