Using Ember.js Part 3: Custom Views

Apr 1 2013

This article belongs to a series of tutorials about Ember.js and Discourse:


You might want to also check out:

Who needs views?

In an Ember application, the “View” part of your MVC will often just be plain old handlebars templates. You might have noticed that in the first two parts of this tutorial series, I created Controller classes but I didn’t have to create equivalent View classes.

So when is a View necessary? The Ember View Guide explains it nicely:

Views in Ember.js are typically only created for the following reasons:

  • When you need sophisticated handling of user events
  • When you want to create a re-usable component

Creating a reusable component is one of the most interesting things you can do in an Ember app. Let me show you an example of one I added to Discourse recently.

The “hotness” control

In Discourse, if a moderator clicks ‘edit’ on a category, they are presented with a modal that looks like this:

Editing a Category

The “Hotness” attribute is there to allow the moderators of a site to choose the topics they want featured on the “Hot” list. The idea is that categories that have a higher hotness will appear more frequently than those with a lower hotness.

As you can see, our scale goes up to eleven:



Embedding a View

You can embed a view in any handlebars template by using the {{view}} markup. Here’s how we can include a custom control called Discourse.HotnessView in our edit_category template:

{{view Discourse.HotnessView hotnessBinding="hotness"}}

The hotnessBinding attribute tells Ember that we want to create a binding between our control and the category’s hotness property. If the category model’s hotness changes, the control will be updated to that value automatically. Conversely, if the control updates the property, the category model will be updated to the new value as well.

Implementing our View

To implement our view, we’ll create a file called hotness_view.js and put it under discourse/app/views in our javascripts folder.

Let’s start with something basic:

Discourse.HotnessView = Discourse.View.extend({
  classNames: ['hotness-control'],
  templateName: 'hotness'
});

If we create a view like this, Ember will render the ‘hotness’ handlebars template where we added our {{view Discourse.HotnessView}} markup.

If you open your web browser’s inspector you’ll notice that all views are rendered into HTML as <div> tags. (You’ll also see a bunch of views you didn’t create. All handlebars templates end up in views in Ember’s internals.) You can also change the type of tag Ember creates by adding a tagName property, but in this case a div will work just fine.

Ember allows you to add custom class names to the div container by using to the classNames property. In our case, we wanted our hotness view to have a class name of .hotness-control for styling purposes.

We can now implement a template for our view. Here’s how our HTML should look:

<button value="1">1</button>
<button value="2">2</button>
<button value="3">3</button>
<button value="4">4</button>
<button value="5" class="selected">5</button>
<button value="6">6</button>
<button value="7">7</button>
<button value="8">8</button>
<button value="9">9</button>
<button value="10">10</button>
<button value="11">11</button>

The selected class will render current hotness button in red.

Now we find ourselves in a bit of a quandary: Handlebars has a really useful {{each}} helper for iterating through a list of items, but in this case our items are simply a number range.

We could model this by creating an array of objects in our view to represent numbers 1 through 11. We could then use {{each}} to iterate through them all, but it seems quite wasteful to allocate a bunch of objects when all we’re doing is listing basic numbers.

Handlebars is great for rendering most things you throw at it, but this is one of the few cases where it feels like you’re fighting against the abstraction it provides. Fortunately, there’s a way we can avoid using handlebars altogether for our view.

Custom Rendering

If you create a method called render in your view, Ember will use its result instead of a template for rendering. It will be called with a buffer that accepts a push call with a string value. Here’s an example:

Discourse.HelloView = Discourse.View.extend({
  render: function (buffer) {
    buffer.push('hello');
    buffer.push(' eviltrout');
  }
});

If you embedded it using {{view Discourse.HelloView}} you’d see “hello eviltrout” instead of a handlebars template.

Let’s use this render method to create our buttons, adding the selected class where necessary:

Discourse.HotnessView = Discourse.View.extend({
  classNames: ['hotness-control'],

  render: function(buffer) {
    for (var i=1; i<12; i++) {
      buffer.push("<button value='" + i + "'");
      if (this.get('hotness') === i) {
        buffer.push(" class='selected'");
      }
      buffer.push(">" + i + "</button>");
    }
  }
});

And now if we reload our page, we’ll see our hotness control! The correct button will be highlighted thanks to a comparison with the hotness property of the view, which is bound to the hotness property of our model.

Potential Drawbacks

Rendering using the buffer like this can be quite useful, but it does some tradeoffs.

The first is that your code will be uglier. Appending strings is no where near as easy to read or write as a simple handlebars template. If you want to divide up responsibilities on your team between front end developers and designers, you’ll probably find that they are much more comfortable changing a handlebars template than the above.

The second is you have to remember to escape user supplied values yourself to avoid XSS issues. You can do this by including the handlebars escape function and calling it (thanks @krisselden):

var escape = Handlebars.Utils.escapeExpression;
buffer.push(escape(user_value));

The third is that your bound properties wont automatically update. Consider this: what would happen if you changed the hotness property within your category model? Since it’s bound to the HotnessView the hotness view will receive the updated value, but it won’t know to render again. We’ve lost some of the magic that makes Ember a pleasure to work with.

There’s a way around this. We can add the following method to Discourse.HotnessView:

hotnessChanged: function() {
  this.rerender();
}.observes('hotness')

The hotnessChanged method is now set up as an observer on the hotness property of the view. Whenever the hotness property changes, the observer will be called. In this case, all we want to happen is for the view to render again. this.rerender() tells Ember to do just that.

Now our control will properly update if the hotness changes.

An Aside: Rendering Performance

There is another benefit to rendering controls using the buffer rather than handlebars: it can be considerably faster. Ember is never quite sure what properties in your template will change and how frequently that will happen. By default, it assumes anything can and will change, and spends a considerable amount of rendering time setting up the structures it needs to observe them.

This is less pronounced on simple templates with few properties, but on large templates with thousands of properties you might start to notice.

There are cases where, if you know that your properties don’t need to update, you can take advantage of the performance of buffer based rendering. It will make your code uglier and less flexible, so I suggest you think twice before converting your handlebars templates to buffer.push calls.

Responding to Button Presses

The last thing our view has to do is respond to clicks so we can update the hotness property when the user clicks on a new number. The other powerful aspect of Ember views is they expose user events that you can deal with in a clean and elegant way. For example, we can define a click method like so:

click: function(e) {
  console.log("The view was clicked! Here's the jQuery event:" + e);
}

It will be called whenever the user clicks on the view. If you’ve ever written jQuery code to handle events you should feel right at home.

Note: we didn’t have to create the jQuery binding to the click event. Ember knew we wanted it when we created the click method. This also means we don’t have to unbind the click event. You can let Ember do that automatically when the View is no longer used, which is great for avoiding programmer errors that leak memory.

As you can see, the click method is called with a reference to the jQuery event of what was clicked on. We can use this to figure out what button the user clicked like so:

click: function(e) {

  var $target = $(e.target);

  // Return if something in the View was clicked on that wasn't a button
  if (!$target.is('button')) return;

  // Set our hotness value to the value of the button
  this.set('hotness', parseInt($target.val(), 10));

  // Don't bubble up any more events
  return false;
}

And we’re done!

Now we have a nice looking reusable control in about 38 sloc.

In the future, we may allow users to set their own hotness preferences for categories to their own tastes. We could embed the same control on their user preferences using one line of handlebars. That’s pretty sweet!

If you’re the type who’s been following upcoming HTML standards, you might be familiar with web components. The great thing about the above implementation is that it provides an abstraction that can easily be refactored into proper web components when more browsers support them and the timing is right.

Happy coding!

Are you interested in learning more about Ember.js?

I'll be teaching at Embergarten in Toronto on May 18 + 19. It's going to be a great way to learn and socialize with other awesome developers. You should consider coming!

View the full article to comment on it

Ember without Ember Data

Mar 23 2013

Ember Data is a persistence layer for Ember.Js. Unlike Ember, which currently has a candidate for a 1.0 release, Ember Data is still very much a work in progress. This has been a source of confusion for people who are learning Ember, as the two frameworks are complimentary but currently exist in different realms of stability.

Ember Data has ambitious goals, and it has come a long way in the last year. If you’re the kind of programmer who loves working on upcoming stuff, you might find it exhilarating. On the other hand, it is completely understandable if you’d want to avoid it. Deprecations and changing APIs can be frustrating and time consuming.

One thing that is not always clear to people starting with Ember is that Ember works perfectly well without Ember Data! Trust me on this: Discourse doesn’t use Ember Data for persistence and it’s working quite well. Moreover, using AJAX with Ember is something that is not difficult to do.

Ember Models that are just Objects

Ember includes an object model that most people with an OOP background should find familiar. A subclass of Ember.Object works very well for a data model.

Here’s what a class might look like to represent a link from reddit:

App.RedditLink = Ember.Object.extend({});

You could then easily instantiate it and use getters and setters to access its properties:

var link = App.RedditLink.create();
link.set('url', 'http://eviltrout.com');
console.log(link.get('url')); // http://eviltrout.com

If you like, when you construct your model instance, you can pass it a regular Javascript object with the properties rather than setting them one at a time:

var discourseLink = App.RedditLink.create({
  title: "Discourse",
  url: "http://www.discourse.org"
});

console.log(discourseLink.get('title')); // Discourse

Here’s how you’d bind those properties to a handlebars template:

Title: {{title}}
Url: {{url}}

Once bound to a template like this, if you called set on your model, it would automatically update the HTML.

Accessing Reddit via JSONP

Data models are a lot more exciting when you fill them real data. Let’s write a method that finds the links from a subreddit. Reddit provides a JSONP API that we can access via jQuery:

$.getJSON("http://www.reddit.com/r/" + subreddit + "/.json?jsonp=?", function(response) {
  // response contains the JSON result
});

The response from reddit’s API includes the colleciton of links under data.children, but their properties are under an additional data attribute. We can loop through them like so, creating instances of RedditLink as we go:

var links = [];
response.data.children.forEach(function (child) {
  links.push(App.RedditLink.create(child.data));
});
// links now contains all our `RedditLink` objects!

$.getJSON is an asynchronous call. It follows that our model’s finder method will have to be asynchronous as well. One common approach to dealing with this is to pass a callback function to our finder method. When the $.getJSON call finishes, it can execute the callback with the result. What happens, though, when you need to handle the errors? You’d have to supply two callbacks: one for the error callback and one for the success callback.

Promises

This is all much cleaner to do with Promises. Promises are objects you return from your functions. They contain a then method that you can call when the operation is complete.

The nice thing about this is you don’t have to supply your callbacks to your function - you just attach them to the Promise object that your function returns. It ends up being a lot cleaner and simpler to follow. Additionally, Promises can be chained, so that the result of one promise is only passed through to the next function in the chain once it is complete.

jQuery conveniently return promises from all its AJAX calls, so we can just make use of it. Here’s how our finder looks, returning a promise:

App.RedditLink.reopenClass({

  findAll: function(subreddit) {
    return $.getJSON("http://www.reddit.com/r/" + subreddit + "/.json?jsonp=?").then(
      function(response) {
        var links = [];
        response.data.children.forEach(function (child) {
          links.push(App.RedditLink.create(child.data));
        });
        return links;
      }
    );
  }

});

Notice that we’re returning the result of $.getJSON, but also calling then on it. This means that the Promise that our findAll method returns will eventually resolve to our list of RedditLink objects. Here’s how you could you could call it and log the results from the subreddit /r/aww:

App.RedditLink.findAll('aww').then(function (links) {
  console.log(links); // logs the array of links after it loads
});

Putting it all together

I’ve created a github project that puts all the code from this blog entry together. You can also try it in your browser.

The code for the application is quite short, which I think reflects Ember’s greatest strength: as a developer you have to write less code to get stuff done.

I implore you to not be scared off by Ember Data’s current state. Ember itself is quite stable, and it’s easy to get started with AJAX calls like this today.

Are you interested in learning more about Ember.js?

I'll be teaching at Embergarten in Toronto on May 18 + 19. It's going to be a great way to learn and socialize with other awesome developers. You should consider coming!

View the full article to comment on it

Adding to Discourse using Ember.js Part 2: Controllers

Mar 17 2013

This article belongs to a series of tutorials about Ember.js and Discourse:


You might want to also check out:

Ember.Js + Client Side MVC

You might have heard Ember.JS described as a Client-Side MVC Framework, where MVC refers to “Model-View-Controller.” In the first part of our tutorial, we talked about how Ember.js uses convention over configuration to prevent developers from having to create boilerplate and get up and running quickly.

Previously, we created an adminReports resource to handle URLs in the form of /admin/reports/:type. Ember automatically looked for our class called Discourse.AdminReportsRoute. However, that’s not the only thing Ember looked for! It will also find Discourse.AdminReportsController and use that as our controller to handle that URL.

You might be thinking: “We never created an AdminReportsController though! What’s up with that?”

Ember did something very convenient for us. We didn’t define a controller, so it couldn’t find one when it wired things up. Instead, it created one for us and gave it the default behavior. And for our purposes the default behavior was enough, so things just worked and we had to write less code!

Controllers in Ember.JS

Controllers serve a few major purposes in an Ember.js application.

  1. To respond to user interactions: When a user interacts with a template, say by clicking a button, you need to declare a method that will respond to that action.

  2. To expose data to your templatess: Usually you will be exposing one model or a collection of models to a template. The controller is responsible for making a model’s data available for display in your template.

  3. To maintain state outside your models: Sometimes you need to work with data that doesn’t have anything to do with your models. A good example would if a user clicked on a column heading to change the sort order of a table. You could store the current sort order in the controller as a property.

If you’ve used a server side MVC framework such as Rails before, you should know that Ember.js controllers work a little differently. In server side MVC, controllers lose all state between requests. If you set an instance variable in one controller method and then call another in another request, the variable won’t be there!

In an Ember.js application such as Discourse, your controllers stay around. If you set a property in a controller, it will be there as long as the controller is still in use.

The AdminReportsController

How did Ember know what data to expose to our template if we never created a controller? If you recall, in our AdminReportRoute, we had a method called model:

model: function(params) {
  return(Discourse.Report.find(params.type));
},

When we entered our route, the model function was called and returned a single Discourse.Report object. Ember then inferred that our controller was dealing with a single model (rather than say, an array of models), and created an ObjectController for us.

An ObjectController is very simple. It exposes all the properties of your model as properties on the controller itself. In our template, we were able to say {{title}} and the controller knew to route that property to our model.

Adding controller functionality

The default behaviors of Ember can last you a while. But what if we wanted to add a bar chart display of the data in addition to our tabular display? We’ll have to define our own controller to handle that.

Let’s create the controller Ember needs and put it in admin/controllers/admin_reports_controller.js

Discourse.AdminReportsController = Ember.ObjectController.extend({
  viewMode: 'table',

  // true if we're viewing the table mode
  viewingTable: function() {
    return this.get('viewMode') === 'table';
  }.property('viewMode'),

  // true if we're viewing the bar chart mode
  viewingBarChart: function() {
    return this.get('viewMode') === 'barChart';
  }.property('viewMode'),

  // Changes the current view mode to 'table'
  viewAsTable: function() {
    this.set('viewMode', 'table');
  },

  // Changes the current view mode to 'barChart'
  viewAsBarChart: function() {
    this.set('viewMode', 'barChart');
  }

});

Let’s do a quick run through. The first thing we do is declare a property called viewMode and set it to be ‘table’.

After that, we have two computed properties, viewingTable and viewingBarChart. As you can see, their implementation is very easy. They return booleans depending on the current view mode. The reason we do this is because handlebars is designed to be stupidly simple, so its #if statements can only respond to true or false values.

Finally, we have two methods, viewAsTable and viewAsBarChart that will be called when the user hits the buttons in the template.

A quick update to the model

In order to display a bar chart, we’ll need to add a percentage property to each row of data in our report. This is easily done in our find method.

Discourse.Report = Discourse.Model.extend({});

Discourse.Report.reopenClass({
  find: function(type) {
    var model = Discourse.Report.create({type: type});
    $.ajax("/admin/reports/" + type, {
      type: 'GET',
      success: function(json) {

        // Add a percent field to each tuple
        var maxY = 0;
        json.report.data.forEach(function (row) {
          if (row.y > maxY) maxY = row.y;
        })
        if (maxY > 0) {
          json.report.data.forEach(function (row) {
            row.percentage = Math.round((row.y / maxY) * 100);
          })
        }

        model.mergeAttributes(json.report);
        model.set('loaded', true);
      }
    });
    return(model);
  }
});

All we’re doing here is figuring out the maximum y property, and then updating each report object to have a percetage field which is their y divided by the maximum.

Finally, let’s set up our template:

{{#if loaded}}
  <h3>{{title}}</h3>

  <button class='btn'
          {{action viewAsTable}}
          {{bindAttr disabled="viewingTable"}}>View as Table</button>

  <button class='btn'
          {{action viewAsBarChart}}
          {{bindAttr disabled="viewingBarChart"}}>View as Bar Chart</button>

  <table class='table report'>
    <tr>
      <th>{{xaxis}}</th>
      <th>{{yaxis}}</th>
    </tr>

    {{#each data}}
      <tr>
        <td>{{x}}</td>
        <td>
          {{#if controller.viewingTable}}
            {{y}}
          {{/if}}
          {{#if controller.viewingBarChart}}
            <div class='bar-container'>
              <div class='bar' style="width: {{unbound percentage}}%">{{y}}</div>
            </div>
          {{/if}}
        </td>
      </tr>
    {{/each}}
  </table>

{{else}}
  {{i18n loading}}
{{/if}}

Near the top, you can see there are two <button> tags for switching the mode. The {{action}} helper wires things up so that when the user interacts with that button, the appropriate method on the controller will be called.

The next helper, {{bindAttr}} allows you to bind an HTML attribute to a property. In this case, we’ve bound the disabled property of the button tag to the opposite viewMode than the button represents. By doing this, the buttons will automatically enable and disable depending on the current state.

Try it out!

If you open up /admin/reports/active you’ll now see an interface that allows you to switch between views at the touch a button.

Visits by Day

Think about all the code we didn’t have to write to make this work.

  • We have separated our concerns. We have a simple template that a front end designer can modify easily. The controllers and models are cleanly set apart.
  • Our code loads data asyncronously and only displays it when it is ready.
  • The user can switch the view style without having to contact the server for the data again.

In the next installment of this series, I plan to show how to create a custom view and the advantages it can give us!

Are you interested in learning more about Ember.js?

I'll be teaching at Embergarten in Toronto on May 18 + 19. It's going to be a great way to learn and socialize with other awesome developers. You should consider coming!

View the full article to comment on it

Adding to Discourse using Ember.js Part 1: Routing and Templates

Feb 27 2013

This article belongs to a series of tutorials about Ember.js and Discourse:


You might want to also check out:

I’ve heard from a lot of people in the process of learning EmberJS that there’s quite a jump from the Getting Started guide to actually contributing to a project like Discourse. I thought it might be useful to step through the creation of a new feature in detail and write out how it was done so others can get an idea of how things fit together. Let’s get started!

Feature Idea: A report of Visits by Day

It would be nice if there was a place in the admin section of Discourse that would show how many visits a forum receives in a day. We are already tracking this in the user_visits table in the database, but there’s no place in the user interface to view it.

Getting started: The URL Structure

When I start a new feature, I find it’s best to begin with its URL and expand outwards from there.

My first inclination would be to access the report at /admin/visits. That would certainly work, but what if we wanted to add more reports in the future? It’s’ likely there could be some common code shared between them, so it makes more sense to separate them under a common resource such as /admin/reports/visits.

I now find myself thinking - are there commonalities between all reports that we can advantage of? They all have a title and some tuples of data to display at the very least. We could ask the server for a report of type visits, and it could return an object that the front end would know how to display.

If our URL was /admin/reports/:type (where :type is what identifies the report), for example, that gives us a lot of flexibility and potential for code re-use. Let’s go with that.

The Server Side

Since I’d like to focus this tutorial on the Ember side of things, I’m not going to go into detail about how the Rails portion of this feature is implemented. Let’s establish that if we make an AJAX request to the server at /admin/reports/visits.json we’ll get back a JSON response with the report data to be rendered in the following format:

{
  "report": {
    "type": "visits",
    "title": "Users Visits by Day",
    "xaxis": "Day",
    "yaxis": "Users",
    "data": [
      {"x": "2013-02-01", "y": 7},
      {"x": "2013-02-02", "y": 13},
      {"x": "2013-02-03", "y": 15},
      ...
    ]
  }
}

(If curious to see how the server side component was implemented, you may view the source on github.)

Adding our new Route

The first thing we have to do is tell our Ember app about the new URL. In Ember, this is known as routing.

The Discourse admin section client code is located in its own directory. The first file we’ll need to edit is routes/admin_routes.js. Let’s add a resource with a dynamic segment to match the URL we came up with. We’ll place the following line inside the admin resource declaration.

this.resource('adminReports', { path: '/reports/:type' });

What does the above do? Our EmberJS application now knows that whenever a user navigates to /admin/reports/visits, it needs to invoke the part of the application we’ve called adminReports to handle the request. There is a little magic going on under the hood so this is worth explaining in detail.

One thing EmberJS borrows heavily from Rails is convention over configuration. Instead of asking you to wire up every component manually, the Ember router asks you for a very small piece of information, in this case our adminReports string, and it will use that to look up the classes it needs to respond to the user.

There is a convention in Ember where a resource defined as adminReports will tell Ember to look for a class called Discourse.AdminReportsRoute. If you’ve created this class, Ember will use it when the user visits our URL.

Your Route classes should be short and simple. Ours will start by just rendering a handlebars template that we’ll fill in later with some data.

Let’s create a file in admin/routes called admin_reports_route.js and place the following content into it:

Discourse.AdminReportsRoute = Discourse.Route.extend({
  renderTemplate: function() {
    this.render('admin/templates/reports', {into: 'admin/templates/admin'});
  }
});

This tells Ember to render the template called admin/templates/report into our main admin template. Why into? Every view in our admin section has a simple menu up top that we want to display. We want our template to be rendered inside the template with the menu so the user doesn’t get lost. We’ll start by definining a placeholder template in the file: admin/templates/reports.js.handlebars:

<h1>This will display our report!</h1>

Now when we navigate to /admin/reports/active, we see this:

Basic Ember Template

Retrieving Data from the Server

Our feature would be a lot more exciting by if it contacted the server to get some data. To do this, we’ll define a Report model. In Discourse, models are simple classes. Let’s create a file, admin/models/report.js and create a basic model:

Discourse.Report = Discourse.Model.extend({});

Discourse uses plain old jQuery AJAX calls to communicate with the server. Let’s add an AJAX call to find a report by type. We can use Ember’s reopenClass to add a find class method:

Discourse.Report.reopenClass({
  find: function(type) {
    var model = Discourse.Report.create();
    jQuery.ajax("/admin/reports/" + type, {
      type: 'GET',
      success: function(json) {
        model.mergeAttributes(json.report);
        model.set('loaded', true);
      },
    });
    return(model);
  }
});

This find method makes an AJAX call to /admin/reports/:type on the server side and places the resulting data into an instance of Discourse.Report. Note that since AJAX is asynchronous, the find call will return a Discourse.Report immediately. When the AJAX call is completed, it merges in the result of the JSON response, and sets the loaded boolean on the object to be true.

Let’s now tell our Route to load the model when a user navigates to it. This can be done by implementing the model method. Here’s our new admin_reports_route.js:

Discourse.AdminReportsRoute = Discourse.Route.extend({
  model: function(params) {
    return(Discourse.Report.find(params.type));
  },

  renderTemplate: function() {
    this.render('admin/templates/reports', {into: 'admin/templates/admin'});
  }
});

When the model method is defined, Ember will call it when the route is entered to retrieve the model and automatically make it available in our template.

Rendering the template

Let’s add a simple HTML table to display the results in admin/templates/reports.js.handlebars:

{{#if loaded}}
  <h3>{{title}}</h3>

  <table class='table'>
    <tr>
      <th>{{xaxis}}</th>
      <th>{{yaxis}}</th>
    </tr>

    {{#each data}}
      <tr>
        <td>{{x}}</td>
        <td>{{y}}</td>
      </tr>
    {{/each}}
  </table>

{{else}}
  {{i18n loading}}
{{/if}}

Ember uses Handlebars to render its templates. The really cool thing about this is if the properties you’ve put into your template change, Ember is smart enough to update them in the web browser too. You don’t have to write any extra code to make sure the HTML re-renders, it just works!

For example, in the above template, the first line is an {{#if}} statement that checks to see if the model is loaded. Remember earlier I said that the call to Discourse.Report.find returns a model right away? At first the loaded property will be false, but when it is finished loading it will be set to true.

Ember will keep your template and model in sync. When the URL is first visited, it will display “Loading…”. When the model has finished loading, the HTML table will be displayed.

If you change the title property of your report, Ember is smart enough to only update that part of the HTML. This is known as binding, and it’s at the core of what makes Ember a really useful tool for developing web applications. It allows you to cleanly separate your data from its presentation, and offers you a universal way to access it.

What’s next?

In the next part of this tutorial, I’m going to show how to set up a controller to respond to actions that the user performs in your template. In particular, I’ll show how you can present the same data that your application already has in a different way (a bar chart) without having to contact the server.

All of the above code is now checked in to our github repository, so feel free to browse it.

Are you interested in learning more about Ember.js?

I'll be teaching at Embergarten in Toronto on May 18 + 19. It's going to be a great way to learn and socialize with other awesome developers. You should consider coming!

View the full article to comment on it

Generating IIFEs in Rails

Feb 25 2013

Recently we ported Discourse from CoffeeScript to plain old Javascript. The process was straightforward since CoffeeScript spits out fairly good Javascript, although I did have to spend the better part of a day cleaning it up afterwards. (Note: we’d love any patches to further tidy up the generated Javascript.)

One thing that CoffeeScript does by default that’s nice is it wraps everything in an Immediately Invoked Function Expression (IIFE). If you’re a Javascript developer you’ve almost certainly seen IIFEs before even if you haven’t used the acroynm – they’re very useful to ensure that any of your defined variables don’t leak out.

After I’d converted the code base, every single one of our .js files had a function closure wrapping everything. It seemed awfully repetitive so I wondered if there was a way to automatically generate them. As it turns out, you can do i fairly easily using Rails’ asset pipeline! I had a hard time figuring this out so I figured I’d quickly blog about it in case anyone else wants to know how it’s done.

First, create a class, for example lib/generate_iife.rb

class GenerateIIFE < Sprockets::Processor

  # Add a IIFE around our javascript
  def evaluate(context, locals)
    "(function () {\n\n#{data}\n\n})(this);"
  end

end

Then all you have to do is create an initializer to activate it, for example config/initializers/enable_iifes.rb

require 'generate_iife'

Rails.application.assets.register_preprocessor('application/javascript', GenerateIIFE)

And then you’re good to go! Any Javascript file you load via the asset pipeline will have the IIFE surrounding its content.

Note you can use this to wrap any type of file that passes through the preprocessor with content. One caveat – if you change the IIFE code, you’ll have to clear your tmp directory in order to get your assets to recompile.

View the full article to comment on it

Infinite Scrolling that Works

Feb 16 2013

Shortly after we began working together on Discourse, Jeff wrote a post about infinite scrolling. At first, I was surprised at how many people claimed to hate sites that used it. However, after reading through many comments about it, I realized that most didn’t hate the scrolling itself, they hated how it broke their browser!

Infinite Scrolling done wrong: Twitter

When I visit Twitter, I am presented with a list of tweets in reverse chronological order. If I scroll down far enough, Twitter will automatically load more tweets so I don’t have to stop reading. Initially, their implementation seems great. I can keep scrolling until I’m done reading.

Twitter’s infinite scrolling has a huge limitation – it only works while you keep your tab open. Try this: open a Twitter window, and scroll down a fair bit. Remember the tweet at the top of your browser window. Restart your browser. If you’re using Chrome, it should re-open all your tabs automatically. Is the tweet you remembered anywhere near the top of your screen? No. Personally, when I do this, I get stuck at the bottom of the first set of tweets Twitter loads.

The engineers at Twitter seem aware of this flaw. Their whole UI is designed around making sure you never lose state in that one tab. All links in tweets open in new tabs when clicked. Your private messages load in a pop-up. Clicking on a user shows a modal with their most recent tweets. If you want to see more of their tweets, it reloads the whole tab with a view of just that user.

After you’ve read a few tweets, what if you want to go back to where you left off in your initial stream? Sorry! Your back button only goes back to the top.

How Discourse deals with these issues

Discourse has infinite scrolling, but it doesn’t have any of the aforementioned issues Twitter does. If you scroll down in a topic and close your browser, you’ll end up right back where you left off. We don’t force links to open in new tabs by default: if users want to do that, they can do it themselves.

How do we do this this? By taking advantage of HTML5’s History API.

Compare the URL

You might have noticed that as you scroll through a topic in Discourse that the URL changes. At first, a URL in a topic looks like this:

http://meta.discourse.org/t/is-it-better-for-discourse-to-use-javascript-or-coffeescript/3153

After you’ve scrolled down a little bit, it will look like this:

http://meta.discourse.org/t/is-it-better-for-discourse-to-use-javascript-or-coffeescript/3153/4

The /4 that is appended to the end refers to the current post number you’re looking at in the topic. In this case, your screen is positioned near the top of the 4th post of topic id 3153.

The replaceState function in the History API allows us to do this. replaceState tells the browser that the URL has changed and the new one should be used if the user hits the back button or reopens a closed tab.

To complete the back button functionality, we then have to support incoming URLs with a post number in them, so we can restore the view the user saw before they left. In Discourse, a URL with a post number doesn’t mean “only show me post x”, it actually means “give me a bunch of posts with x near the top.”

The rest of our infinite scrolling implementation is fairly straightforward. When you reach the bottom of the posts in memory, we trigger a call to load posts after the last post we know. If you scroll upwards to the top, we load the posts before the first post.

The URL is the serialized state of your web application

URLs are meant to represent the location of resources - but I often think people are too focused on resources as documents or videos. Why shouldn’t a URL mean “the posts near post 100”? Twitter’s URL is almost always twitter.com, and their user experience suffers for it.

I won’t claim that Discourse’s approach to infinite scrolling is perfect. There is certainly room for improvement and of course we’d love contributions to our source code. However, I do feel we’ve taken the right approach to infinite scrolling so far by persisting user state in the URL as they scroll.

View the full article to comment on it

Why Discourse uses Ember.js

Feb 10 2013

This week, I was delighted to finally reveal Discourse, the app I’ve been working on for most of the last year in secrecy with awesome people. The launch got a lot of attention - we were featured on Hacker News, Slashdot, Wired, Reddit, Techcrunch and countless other places. Personally I’ve been floored with the amount of feedback so far. It’s going to take quite some time to get through it all!

One question people keep asking me is “Why did you choose Ember.js?”. It’s a good one, and one that I think can be considered in two ways: “Why use a a client side MVC Framework?” and “Why Ember out of all the frameworks?” Here’s my answer to both of those questions.


Why use a client side MVC Framework?

Interactivity

There will never be a “one size fits all” approach to web development. This blog for example is a series of static HTML pages generated by Middleman. I deploy it using rsync. Most readers will probably visit the blog via a link or RSS, will read what I have to say and bounce out. Very few will browse my other blog entries, and even fewer still will leave a comment.

It would be silly to give them a heavy Javascript interface just to do this because there is so little interactivity. Their experience would be made much worse by having to download a Javascript payload before they could read what I’ve written.

Ask yourself how interactive your web application needs to be. On the less interactive side of the scale, there are huge wins with server side rendered HTML. The more interactive your application becomes, the more you’ll benefit from a client side MVC framework.

Why not just use plain old jQuery - it’s simpler!

jQuery works well if you just want to add light interactivity to your app. It has some failings though when you introduce more state to your application. You end up having to store data in your DOM (usually in data-* attributes), and having to remember how to find those attributes based on where the event was triggered.

For example, on the bottom of every discourse post there is a button a user can click to like a post. When clicked, it vanishes and adds a footer below the post saying you liked it.

If you implementing this in jQuery, you might add a data-post-id to the post. Then you’d bind a click event on your button element to a function that would make the AJAX call to the server. However, the click function passes a reference to the button, not the post. So you then have to traverse the DOM upwards to find the post the button belongs to and grab the id from there. Once you have it, you can make your XHR request. If the XHR succeeds, you then have to traverse the DOM downward from the post to the footer, and add in the text.

At this point it works, but you’ve tied your implementation of the button click to a particular DOM structure. If you ever want to change your HTML around, you might have to adjust all the jQuery methods that accessed it.

If this example seems simple - consider that in the footer we offer you a link to undo your like. When clicked, the footer text vanishes and the button appears again. Now you’re implementing the opposite operation against the DOM, only in reverse of what you did before.

Discourse even takes it a step further - we know that 99% of the time when you click the like button the request is going to work, so we hide the button and show the footer text right away, even before waiting for the server to reply. In the infrequent event that request fails, we’ll show an error message and pop the UI back to the state it was in before. If we were doing that in jQuery, we’d have to have a callback on our AJAX request that knew how to put the UI back into the state it was in before.

A prudent programmer might say, okay, well I’ll have a render function that can rebuild the DOM to the current UI state of whether the post is liked or not. Then both ‘undo’ and ‘like’ can call it. If ‘like’ fails it can call it again. Oh, and we have to store the current state of whether the post is liked somewhere. So maybe we add another data-liked="true" attribute. ACK! Just typing this all out is giving me a headache!.

Congratulations, your code is now spaghetti, your data is strewn out in the DOM and your logic is tied to a particular layout of HTML elements.

How a client side MVC framework can help

The Ember.js approach to the above is to have a simple Javascript class that represents a Post.

Discourse.Post = Ember.Object.extend({
  liked: false
});

var post = Discourse.Post.create();

You then bind that post to a template, and the template can have simple logic like:

<div class='toolbar'>
  {{#unless liked}}
    <button {{action likePost this}}>Like this Post</button>
  {{/unless}}
</div>
<footer>
  {{#if liked}}
    You liked the post! <a href='#' {{action undoLike this}}>Undo like</a>
  {{/if}}
</footer>

Thanks to the binding, if you ever change the ‘liked’ attribute, it knows to re-render the HTML for you. There’s no need to call a render method. You don’t have to worry about traversing the DOM to get at the data you need. A front end developer can change the template around and things will continue to work just fine.

Client Side MVC Performance

I saw a popular tweet recently referring to client side MVC frameworks:

Despite the hundreds of retweets, you should be aware that it’s mostly FUD.

  • Ember.js is not that big. It’s 47k minified and gzipped. If you think that’s too big, consider the image of Thomas on his own web site is 49k.

  • Discourse only requests JSON when you change views. On your initial visit it actually includes the JSON payload in the body so it doesn’t need to make an additional request.

  • Nobody compiles templates in production. In development mode while you are changing them, they get compiled, but when you deploy you pre-compile them for performance.

  • I’m not sure why you’d have to query data once you’ve already requested the JSON, but I suppose some times you might download a list of objects and then only show ones that are active or something. This isn’t a common case, and even if it was, isn’t it better than a server round trip to change the filter?

  • Generating the HTML string is something that has to happen. And he’s not wrong, it is slower than just getting the HTML from the server. In particular, on low powered devices you will notice that rendering is slower than regular HTML. However, I’d offer two rebuttals to this: 1) Javascript and devices are only going to get faster in the future – the latest iPad renders discourse very smoothly – and 2) It can often be faster even with slow rendering to send much less data across the wire. Consider a table view with many controls on each row. Why send the html for all those buttons across the wire for every row? You can send the basic row data and the renderer decorate it.

The truth is, Discourse is fast. Try it out yourself. And our Javascript payloads work really well with CDNs. Try it from somewhere not in North America. I think you might be surprised how fast client side MVC can be.

API-First Development

One amazing side effect of a rich client side app is you end up with a battle tested API. Our app has consumed our own API since day one, so we know it works.

Note: We haven’t documented it yet because we plan on major changes over the next few months, but after things stabilize we certainly will provide a more rigid interface.

If we want to create a native client for Android or iOS, it would be a lot easier because we already speak JSON fluently. If people want to build services that use Discourse, they won’t have to result to screen scraping. It’s a huge win for us and the developers that use our platform.


Out of all the Client Side MVC Frameworks, why do you prefer Ember.js?

I’m actually a fan of all major efforts in client side MVC. The philosophy of binding templates to variables and communicating over an API is what I like the most, and you can get that in many different frameworks. I implore you to investigate them all and come to your own conclusions. Despite that, I am a huge fan of Ember.js and here are some of my reasons:

The documentation was simple to understand

Here’s some text right out of the Angular.JS guides, about a feature called Transclusion.

The advantage of transclusion is that the linking function receives a transclusion function which is pre-bound to the correct scope. In a typical setup the widget creates an isolate scope, but the transclusion is not a child, but a sibling of the isolate scope. This makes it possible for the widget to have private state, and the transclusion to be bound to the parent (pre-isolate) scope.

That text isn’t describing an Angular internal - it’s a template directive you need to use when creating reusable components! Compare it to the Ember guides. I find the Ember ones much clearer and easier to follow. I am much more likely as a developer to pick a framework when the framework makes an effort to be easy to understand.

It worked well early on and has improved a lot in a short time

Meteor had an amazing tech demo at launch, but it was missing very important security features (they have since addressed this). What impressed me about Ember was that even early on it was very functional. If I had to launch Discourse with the first version of EmberJS I started using that would have still been a big win. Having said that, the API has improved tremendously since then. It is much faster. It also has a new router that involves a lot less boilerplate code. The community has done amazing work over the last year. Discourse is on the latest Ember 1.0 prerelease and it’s fantastic.

The Team has a proven track record with Open Source

Yehuda Katz has done amazing work on Rails 3 and Bundler. When he tells me that he’s not going to abandon Ember.JS, I believe him, because he has a track record proving so. Angular, for example, is sponsored by Google, and while I think the project would continue even if they abandoned it, it is important to me that the Ember community didn’t spring out of a corporate sponsorship.

String Templates vs. DOM templates

EmberJS uses string templates instead of the DOM-based templates found in AngularJS. This is a more personal thing and comes down to aesthetics, but I prefer the syntax of handlebars over adding attributes to the DOM. Additionally, we do some server side rendering, which is much easier with string templates because we don’t have to boot a whole PhantomJS environment.

The Run Loop

One of the more complicated pieces of EmberJS is the run loop. Usually you aren’t aware of it, but behind the scenes Ember batches up updates to the DOM and bindings for performance. In some other frameworks if you have a list of 100 items, and iterate through them changing them all, you will end up with 100 separate DOM updates. Ember will batch them and update all at once, providing a better user experience.


Ember.JS has been a huge win for Discourse. Our application really benefited from the framework it provides and the excellent support of the community behind it. I would recommend it to anyone wanting to create a web application that needs a lot of interactivity. You’ll find your front end code a lot easier to manage and modify, which is a huge win for productivity. If you have some time, check out our Discourse’s Ember App’s source code and tell us what you think.

Are you interested in learning more about Ember.js?

I'll be teaching at Embergarten in Toronto on May 18 + 19. It's going to be a great way to learn and socialize with other awesome developers. You should consider coming!

View the full article to comment on it

Ember 1.0 preview upgrade notes

Feb 4 2013

This is just a quick follow up to a post I made recently about upgrading Ember to the new router. A couple small things have changed that I feel are worth mentioning. In particular they deprecated one piece of advice I gave so I wanted to correct it!

this.controllerFor() inside Controllers has been deprecated

If you want a controller to speak to another in your app, previously you could do this.controllerFor('foo') to get a FooController. That’s been deprecated for a much nicer way of doing things. Simply include a needs property in your declaration for the controllers you need access to. It will then be available under controllers.bar:

App.FooController = Ember.ObjectController.extend({
  needs: ['bar', 'baz'],

  someMethod: function() {
    this.get('controllers.bar').hello();
    this.get('controllers.baz').goodbye();
  }
});

A cool side effect of this new API is you can see exactly what relationships your controllers have to each other. If you see more than one or two controllers in your needs property, you might want to raise an eyebrow. It’s a sign that perhaps your controllers are too dependant and maybe you could rewrite things in a better way.

Also to be clear: you can still use this.controllerFor() in your router. It’s just deprecrated inside controllers.

ContainerView’s childViews is deprecated

This is another change that will result in a lot less code. Previously, if you had a ContainerView, you could add and remove views from it using the childViews proeprty. Now, the childViews property has been deprecated as the ContainerView itself acts like an Array. So instead of:

// The old way
this.get('childViews').pushObject(App.CoolView.create());

You can just do:

// The new way
this.pushObject(App.CoolView.create());

Pretty slick!

Are you interested in learning more about Ember.js?

I'll be teaching at Embergarten in Toronto on May 18 + 19. It's going to be a great way to learn and socialize with other awesome developers. You should consider coming!

View the full article to comment on it

Ember Router v2 Upgrade Notes

Jan 27 2013

I’ve been working on an Ember project for a while now. When I started, Ember had no official router (although you could plug in the SproutCore one) so I ended up rolling my own with a bunch of Javascript regular expressions. Since then they’ve released a great router, and more recently a huge overhaul to it.

On one hand, it’s been frustrating to replace the router twice during this project’s lifecycle. But on the other hand, each time I’ve seen a serious improvement in API quality. While upgrading from what is now known as routerv1 to routerv2, I took some notes. If you’re starting a new Ember app on 1.0 this might not be of interest, but if you are looking to upgrade from routerv1 to v2, the following might come in handy!

Calls to {{action}} no longer pass the jQuery event

On routerv1, if you had an {{action show post}} call, it would call the show method and pass a jQuery event object for the click. The event would have a context attribute that pointed at your post. In routerv2, the jQuery event is no longer passed.

Most of the time this will clarify your code as you can stop referencing e.context everywhere. However, I found a few cases in my app where I cared about the jQuery event, and I no longer had access to it. One workaround is to create a new view. In Ember.Views, you can implement a click method, and it will receive the event as a parameter.

Routes versus Resources

When creating links in your app, you refer to them by route name. In routerv2, you group your routes under resources. This is well documented in the routing guide.

The route tweets.trending would be the resource named tweets and the route underneath it called trending. One thing that confused me is when you create your class for the Tweets resource is it should be called App.TweetsRoute not an App.TweetsResource.

When you link to resources, even if they are declared in a map hierarchy, you start with the last resource name. So that means that even you have a router like this:

this.resource('admin', {path: '/admin'}, function() {
  this.resource('users', {path: '/users'}, function() {
    this.route('active', {path: '/active'});
  });
});

The path to the your active users in admin is users.active, and not admin.users.active as I expected.

A corollary to this is your resource names have to be unique. So you can’t have something like this:

this.resource('admin', {path: '/admin'}, function() {
  this.resource('users', {path: '/users'}, function() {
    this.route('active', {path: '/active'});
  });
});
this.resource('users', {path: '/users'}, function() {
  this.route('active', {path: '/active'});
});

The better way to define these routes would be like this:

this.resource('admin', {path: '/admin'}, function() {
  this.resource('adminUsers', {path: '/users'}, function() {
    this.route('active', {path: '/active'});
  });
});
this.resource('users', {path: '/users'}, function() {
  this.route('active', {path: '/active'});
});

That way, you’ll have an adminUsers.active route name and a users.active route name.

You can no longer bind to the router directly

Previously, you could access the router as if it were a regular Ember object. You could use this to bind one controller to another using a binding like: fooBinding: 'App.router.fooController.content'. You can no longer do this.

Instead, you can use the setupController call in your router to pass it the models it needs to work on.

Update: There is a cool way to access other controllers now and I’ve written it up in a follow-up.

{{render}} can replace single use {{outlet}}s

I had a few {{outlet}}s that were being used to instantiate a single component once. For example, a drop down search menu was an outlet that I’d only ever connect once in the routerv1. In routerv2, you get really nice {{render}} helper that will wire things up automatically!

For example {{render search}} will find a SearchController, SearchView and template and wire it all up for you. If neither of those things are defined, it’ll still work anyway. You could even just have a template if you want.

Are you interested in learning more about Ember.js?

I'll be teaching at Embergarten in Toronto on May 18 + 19. It's going to be a great way to learn and socialize with other awesome developers. You should consider coming!

View the full article to comment on it

Crawling the Downvote Brigades of Reddit

Jan 16 2013

ShitRedditSays and The Downvote Brigades of Reddit

(note: if you’re familiar with reddit and ShitRedditSays, you can skip to the next section.)

As you probably know, Reddit is a site that revolves around voting. All users are encouraged to vote on things, which are then prioritized based on their total scores.

Over time, Reddit’s userbase has grown a lot. It is now one of the most popular sites on the Internet, and nobody has enough time to read through all its submissions and comments. As in many communities, the users have found themselves splitting off into various sub factions, or subreddits as they are known. (One that I really like is /r/aww – check it out if you are feeling blue.)

One of the most infamous of these subreddits is /r/ShitRedditSays, (SRS for short). SRS is a meta community. They exist solely to describe the goings on of reddit itself. Here’s how they describe the type of content they are looking for:

Have you recently read an upvoted Reddit comment that was bigoted, creepy, misogynistic, transphobic, unsettling, racist, homophobic, or just reeking of unexamined, toxic privilege? Of course you have! Post it here.

SRS is a place for redditors to mock the behavior they consider reprehensible on the rest of the site. As you can imagine, the people being made fun of often don’t appreciate it. To say that SRS has many enemies is an understatement. It is almost certainly the most despised of all subreddits. It is also quite popular: there are over 30k members.

The hatred for SRS goes so deep that people have even created subreddits simply to oppose them. Here’s how one, /r/SRSSucks describes itself:

They started out as a subreddit dedicated to calling out bigoted jokes, and they morphed into a large network of subreddits dedicated to a radical, mutated form of 2nd and 3rd wave feminism.

When discussing SRS, the cardinal sin that is often brought up is how they are a “downvote brigade.” The idea is, when SRS links to a comment of yours, their members spill through and downvote you into oblivion. Indeed this concern is addressed right on the SRS homepage:

Do not downvote any comments in the threads linked from here! Pretend the rest of Reddit is a museum of poop. Don’t touch the poop.

Are the brigades really downvoting en masse?

Many of reddit’s users consider SRS to be a downvote brigade regardless of their statements to the contrary. In fact, SRSSucks even makes a jab at them while telling their users not to downvote content. Emphasis mine:

No downvote brigading or promotion of such activities please! We do not support fighting SRS by sinking to their level and anyone caught brigading may be banned.

Recently, I got to wondering about how much of an effect being linked to on SRS actually has on your comment. It’s one thing to say “hey, SRS downvotes everything,” but can it be proven? I decided to create a program to put downvote brigading to the test. Here’s my methodology:

  1. Scrape the subreddit homepage frequently.
  2. Record all links to comments, and their scores at the initial time seen. (If the post is a self.post, grab all links from the text instead.)
  3. Crawl the links again over time, and record their new values.

I ran the scripts on /r/ShitRedditSays, /r/SRSSucks and /r/SubRedditDrama, three subreddits accused of brigading.

The Results

I collected data on 198 links over a period of 48 hours. Here’s what I found:

  ShitRedditSays SRSSucks SubredditDrama
Links Crawled 140 32 26
Avg. Score Change +49 +7 +8
% of Links Downvoted 37% 25% 35%
  • On average, the scores increased after being featured on the “downvote brigades”. Maybe they should be called upvote brigades? :)

  • Still, the differences were mostly small. It seems, on average, if you are linked to, your vote won’t change in a noticeable way.

  • ShitRedditSays is a lot more popular than the other two! It had 4x as many submissions on its front page.

  • The most downvoted link was this image of a duck. It lost 443 points after being linked in SRS.

  • The most upvoted link was graffiti. It gained 1139 points after being linked in SRS.

The Data

View the full article to comment on it