Handling DOM Events With EmberJS Views And Controllers

Hot on the heels of picking up Ember and starting to learn it, I think I’ve actually learned something useful! I certainly hope I have, at least. Otherwise, I’m going to be in trouble soon.

In my EmberCloneMail app, I have an <ul> tag with <li> tags that represent the list of emails you can click on and view – and that’s precisely the problem I ran in to. How do I handle a DOM “click” on each of the <li> elements, and know which of the Email objects is the one being clicked? Of course I know how to do this in Backbone already and I expected it to be as easy in Ember. It is just as easy – it’s just different and presents some options not available in Backbone.

The View And Template Setup

The basics of getting this set up are the same no matter how I decide to handle the specific click. There will be some details of this that change with the different options, but this is a good starting point to render the view with a template.

The Handlebars template for the list and items looks like this:

<script data-template-name="email-list-view" type="text/x-handlebars">
  {{#each EmberCloneMail.emailListController}}
      {{view EmberCloneMail.EmailPreview contentBinding="this"}}
  {{/each}}
</script>

<script data-template-name="email-preview" type="text/x-handlebars">
  {{#with content}}
    <span class="from">{{from}}</span>
    <span class="subject">{{subject}}</span>
    <span class="date">{{date}}</span>
  {{/with}}
</script>

Note that there’s two templates in this – one of the <ul> and the “collection” information, and one for individual <li> items that represent the emails I’ll be displaying. This follows the same idea that I’ve talked about with Backbone’s collection / child rendering, but it puts the loop directly in the template instead of in the JavaScript view object.

The Ember view then looks like this:

EmberCloneMail.EmailListView = Em.View.extend({
  tagName: "ul",
  classNames: ["email-list", "email-view"],
  templateName: "email-list-view"
});

EmberCloneMail.EmailPreview = Em.View.extend({
  tagName: "li"
  templateName: "email-preview"
});

There are also two views – one for the <ul> list and one for each of the <li> items.

You can either use another Handlebars template block to render the list in to the HTML directly, or use JavaScript to instantiate, render and display the view in the right spot. How you do that makes no difference in handling the DOM clicks.

Option 1: Let The View Handle It

This is pretty much “the Backbone way” and it can be done in Ember in much the same manner. In the view, you add a “click” method and it receives the standard jQuery “events” argument for jQuery DOM events.

EmberCloneMail.EmailPreview = Em.View.extend({
  tagName: "li"
  templateName: "email-preview",

  click: function(e){
    // handle the view click here
  }
});

From there, you can do things as you would expect – manipulating the view, manipulating the model and generally getting on about the job of working with the view and model. This is very much the “presenter” approach to view objects, creating the vertical path between Model -> Presenters -> View (HTML in this case) in an MVP style.

The problem with this approach is that it pretty listens to all of the clicks for any HTML element within the view template. If you have a very simple template and you want the click to behave this way, it works out well. But if you want to listen to a click on a specific element within the view, that’s where the next option comes in to play.

Option 2: {{action}} Helpers

Handlebars lets you register view helper functions that can pretty much do anything you want. One of the helpers that comes with Ember is the {{action}} function that let’s you specify a method on your view to handle on a DOM event.

<script data-template-name="email-preview" type="text/x-handlebars">
  {{#with content}}

    <span class="from" {{action "doStuff" on="click"}}>{{from}}</span>

    <span class="subject">{{subject}}</span>
    <span class="date">{{date}}</span>
  {{/with}}
</script>

In this case, we’re putting the {{action}} on the “from” portion of the view, so that when you click the email sender’s name it will be handled by the view. Like the first option, the methods that you specify with an {{action}} are methods on the view, directly:

EmberCloneMail.EmailPreview = Em.View.extend({
  tagName: "li"
  templateName: "email-preview",

  doStuff: function(){
    // do stuff here, based on the click of the HTML element
  }
});

You can also specify a different view to handle the event using {{action target=”someOtherView”}} in the template.

The benefit here, as stated above, is that you can handle any DOM event you want with any method in the view that you want. This is largely the functionality as Backbone’s declarative “events” hash on a view, but has the added advantage that you can target a different view. You’re also specifying this in the template directly – which may or may not be an advantage, depending on your perspective.

Option 3: Targeting A Controller Action

In looking through some of the example apps, I noticed that the several of the views included sub-view rendering of “Ember.Button” in order to create a button on the screen. These sub-views were then told to target a specific controller and action for the clicking of the button. I thought this sounded like a pretty cool option and I wanted to see what it would take to make my EmailPreview view work this way. Turns out, it was very easy.

I found the Ember.Button view in the source code with the help of Github’s awesome search feature (just hit “t” and then start typing. It will find file names in the current repo for you). From that code, I learned about the Ember.TargetActionSupport object. This is the object that let’s you specify the controller and action to target when you are setting up the view render in Handlebars templates.

To get your view to work with this, just extend the view with this object:

EmberCloneMail.EmailPreview = Em.View.extend(Ember.TargetActionSupport, {
  // tag, template, etc here
});

Then in your templates, when you call in to this view, you specify the target controller and action:

<script data-template-name="email-list-view" type="text/x-handlebars">
  {{#each EmberCloneMail.emailListController}}

      {{view EmberCloneMail.EmailPreview contentBinding="this" 

        target="EmberCloneMail.emailController" 
        action="showEmail"

      }}

  {{/each}}
</script>

<script data-template-name="email-preview" type="text/x-handlebars">
  {{#with content}}
    <span class="from">{{from}}</span>
    <span class="subject">{{subject}}</span>
    <span class="date">{{date}}</span>
  {{/with}}
</script>

To finish this out, you need to handle the DOM event that will trigger the controller / action the way you would use option #1 above. In my case, I added a “click” method. Within this method, you call out to the target action:

EmberCloneMail.EmailPreview = Em.View.extend(Ember.TargetActionSupport, {
  // tag, template, etc her

  click: function(e){

    // call the targeted controller / action
    this.triggerAction();

  }
});

The benefit in this case is that you can re-use a view and template across multiple behaviors in your app. When you specify which view you are rendering in the template, you tell it which controller and action to use. Therefore, when you are using the same view in multiple places, you can target different controllers and actions, based on what’s appropriate for the current use.

The drawback to this, though, is that you’re just about limited to a single controller and action target. This makes more sense when you consider that an HTML button typically only has the one click event. In the case of an HTML <li> like I’m using, it might not make quite as much sense to limit  your template and view to a single action. My use case called for this, though. I want to handle the click of the <li> and only the click of the <li>, so I was able to properly use the TargetActionSupport correctly.

Option ???: Something Else ???

At this point, I’ve identified these three options for handling a DOM event. There’s obviously a lot of flexibility available, here. You aren’t stuck with one way to handle a DOM event when a given scenario may call for something different.

Are there any other options (other than handling the click w/ plain jQuery)? What am I missing?


Post Footer automatically generated by Add Post Footer Plugin for wordpress.

About Derick Bailey

Derick Bailey is an entrepreneur, problem solver (and creator? :P ), software developer, screecaster, writer, blogger, speaker and technology leader in central Texas (north of Austin). He runs SignalLeaf.com - the amazingly awesome podcast audio hosting service that everyone should be using, and WatchMeCode.net where he throws down the JavaScript gauntlets to get you up to speed. He has been a professional software developer since the late 90's, and has been writing code since the late 80's. Find me on twitter: @derickbailey, @mutedsolutions, @backbonejsclass Find me on the web: SignalLeaf, WatchMeCode, Kendo UI blog, MarionetteJS, My Github profile, On Google+.
This entry was posted in DOM, Ember, Javascript, JQuery, Model-View-Controller, Model-View-Presenter. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • http://twitter.com/dagda1 dagda1

    A better way of defining templates is in separate files and then using the templateName attribute to point to that file, e.g:

    templateName: ‘app/templates/url_search/show’ 

    You can load the templates into the Ember templates collection by subclassing a Tilt::Template subclass like this:

    https://gist.github.com/1780841 

    • http://mutedsolutions.com Derick Bailey

      Nice! I was going to look into separating the templates in to external files soon. Glad to know it’s that easy. :) 

      Am I correct in seeing that this gives you server side compilation of the template, which is going to improve the performance of the JS at runtime?

      • http://twitter.com/dagda1 dagda1

        Yes, it is loaded via Ajax when you create the Enber app. I think when the Ember application object is created or maybe when the view is first referenced.  They are loaded into the Ember.TEMPLATES collection.

        You also need the following lines in a rails initializer:

        require ‘sprockets/ember_handlebars’

        Rails.application.assets.register_engine ‘hjs’, EmberHandlebars

        • Jeroen van Wissen

          And what about non-rails Ember apps? How do we put the handlebar templates in seperate files?

          • Guillaume Bartolini

            ( a year later )
            Here is what i came up with

            var tpl = {

            // Hash of preloaded templates for the app

            templates:{},

            // Recursively pre-load all the templates for the app.

            // This implementation should be changed in a production environment. All the template files should be

            // concatenated in a single file.

            loadTemplates:function (names, callback) {

            var that = this;

            var loadTemplate = function (index) {

            var name = names[index];

            //console.log(‘Loading template: ‘ + name);

            $.get(‘./templates/’ + name + ‘.html’, function (data) {

            that.templates[name] = data;

            index++;

            if (index < names.length) {

            loadTemplate(index);

            } else {

            callback();

            }

            });

            }

            loadTemplate(0);

            },

            // Get template by name from hash of preloaded templates

            get:function (name) {

            return this.templates[name];

            }

            };

            tpl.loadTemplates(['index', 'demo'], function () {

            App.advanceReadiness();

            Ember.TEMPLATES["index"] = Ember.Handlebars.compile(tpl.get('index'));

            Ember.TEMPLATES["demo"] = Ember.Handlebars.compile(tpl.get('demo'));

            });

  • http://twitter.com/dagda1 dagda1

    Another interesting plugin I have found for Ember is the Ember.StateChart 
    https://github.com/emberjs-addons/sproutcore-statechart,  Whenever you switch states, you get some nice handlers for entering states (enterState) and exiting states (exitState).  

    Examples of switching states might be something simple like switching a tab control or another use case I found was to go from a non-streaming state to a streaming state via a websocket connection.

    It is an interesting add on to the usual MVC paradigm.  Still not sure why they called in StateChart instead of StateMachine.

    • http://mutedsolutions.com Derick Bailey

      If rumor and speculation serve me right, I think “StateChart” is a leftover name from the original SproutCore. 

      This definitely looks like something I need to dig into. I’ve been trying to find good ways to handle workflow / state changes.

      Thanks again! :)

  • http://twitter.com/dagda1 dagda1

    I think the main difference between backbone  and Ember is that backbone uses Dom events while Ember uses bindings.

    A binding in Ember is anything with the binding suffix.  For example, here is an example of one of my controllers:

    https://gist.github.com/1885960

    You can see that I am specifying a urlSearchBinding on line 11 which I can then reference in my template.

    Here is the show template at the templateName path:

    https://gist.github.com/1885979

    I am referencing the urlSearchBinding on line 2 and on line 3, I am speciying the targetBinding as the view but I could very easily point to a controller or any object.  For example in another template, I have the following Ember button:

    {{#view Ember.Button targetBinding=”leads” action=”showMore” isVisibleBinding=”leads.showMoreIsVisible”}}Show More{{/view}}.

    Bindings are the key difference between Ember and Backbone IMO.

    The Ember runloop is also something to be aware of and caught me out at the start.