Backbone.js: Getting The Model For A Clicked Element

I see variations of these questions on StackOverflow quite frequently:

I have an HTML element rendered for each model in my collection. How do I get the model for the item that I clicked?

or

I rendered all my models using a template, but when I click on one of them my event handler fires for all of them. What am I doing wrong?

The usual source of this question is that the developer has decided to use a single View object and template to render the entire list of Backbone Models. There’s nothing necessarily wrong with this approach. It has it’s advantages but it also presents a set of challenges and limitations. There is another way of approaching this which eliminates some of those limitations, but also has it’s own challenges: rendering one View object per Model. Instead of trying to choose one or the other all the time, it’s best to understand each of these approaches and be able to recognize the scenarios where each fits best.

One View To Rule Them All

Consider the following code:

Item = Backbone.Model.extend({});
ItemCollection = Backbone.Collection.extend({
    model: Item
});

ItemListView = Backbone.View.extend({
    tagName: "ul",
    events: {
        "click a": "clicked"
    },
    
    clicked: function(e){
        e.preventDefault();
        var item = // ??? how do we get the item?!
        var name = item.get("name");
        alert(name);
    },
    
    render: function(){
        var template = $("#item-template");
        var el = $(this.el);
        this.collection.each(function(model){
            var html = template.tmpl(model.toJSON());
            el.append(html);
        });
    }
});

var items = new ItemCollection([
    {id: 1, name: "item 1"},
    {id: 2, name: "item 2"},
    {id: 3, name: "item 3"}
]);

var view = new ItemListView({collection: items});
view.render();
$("#showIt").html(view.el);

With this HTML template:

<script id="item-template" type="text/x-jquery-tmpl">
    <li><a href="#">${name}</a></li>
</script>

In this example, we are rendering a list of items inside of a view. We’re looping through each of the models in our collection and rendering an item to produce a nice little list. We are also setting up a click event for the <a> tags in our list.

The Problem: Which Item Did I Click?

The goal of this sample code is to show an alert box stating the item’s `name` attribute when we click on a link. But how do we do this? The click event that we set up listens to any and all clicks for any and all <a> tags in our list. This is because the events declared in a view use the view’s `el` element to wire up the events. Since the `el` in this example is being generated by the view, as a `ul` tag, the click event is wired up to all of the <a> tags in each of the <li> tags.

The Solution: Data-Id Attributes

To know which item we clicked, we need to attach some meta-data to the <a> tag that tells us exactly which model we are trying to deal with. The “best” approach for this currently is to use the HTML5 data-* attributes. This is a set of attributes that lets us add whatever data we want, and still be valid (some browsers ignore attributes they don’t know about). In this case, we’ll add a data-id attribute and store the id of the model, to our template.

<script id="item-template" type="text/x-jquery-tmpl">
    <li><a href="#" data-id="${id}">${name}</a></li>
</script>

Now that we have a data-id attribute in place, we can grab it from the <a> element that we clicked. Then we can get the model from the collection, get the name and show an alert box.

Item = Backbone.Model.extend({});
ItemCollection = Backbone.Collection.extend({
    model: Item
});

ItemListView = Backbone.View.extend({
    tagName: "ul",
    events: {
        "click a": "clicked"
    },
    
    clicked: function(e){
        e.preventDefault();
        var id = $(e.currentTarget).data("id");
        var item = this.collection.get(id);
        var name = item.get("name");
        alert(name);
    },
    
    render: function(){
        var template = $("#item-template");
        var el = $(this.el);
        this.collection.each(function(model){
            var html = template.tmpl(model.toJSON());
            el.append(html);
        });
    }
});

var items = new ItemCollection([
    {id: 1, name: "item 1"},
    {id: 2, name: "item 2"},
    {id: 3, name: "item 3"}
]);

var view = new ItemListView({collection: items});
view.render();
$("#showIt").html(view.el);

Here’s the code running in a JSFiddle

The Problem Introduced By The Solution

There’s a few problems that we introduced by taking the route of using data-* attributes, all of which can be summed up in one statement: stop using Backbone as if it were a stateless web server.

By using a data-id attribute, we’re forced to look up the model based on the id of the model as shown in the previous code snippet. This wreaks of using backbone as a stateless web server – having to “post back” some data with an id and then looking up the actual item so we can use it. In this case we’re using an event in a Backbone view, but the concept is the same. We have to get the id, find the model from that id, and then we can do stuff with it. This is going to be a small performance hit and also cause a lot of extra code to be written in views and a lot of additional data-id attributes in our HTML elements. Neither of which sounds like fun to maintain, in my opinion.

When To Use This Approach

There are some benefits to using this approach, in spite of the above problem. If you’re careful when you head down this path, you can limit it’s use to simple lists that don’t need much (if any) action taken on the individual model. Some good examples would be :

  • A list of items for display purposes only
  • A list of items with <a> tags that link to a route or another url / page entirely
  • Other similarly simple lists, where there is very little action taken on individual items

Beyond simple lists like this, though, this pattern can get out of hand quickly. There is another way to handle this scenario, though it has it’s own cost.

One View Per Model

The other approach that we can take, to know which of the models we are trying to work with when we click an HTML element, is to have one View object per Model rendered. That is, each model in our collection has it’s own view object to render that mode’s data. This does not remove the need to have a View object that iterates the collection and populates the list, though. It only moves the implementation for each individual model down to a View for that Model.

Knowing Which Model’s Link You Clicked

The primary advantage that we get from using a View per Model is knowing exactly which model instance we are working with, when an event fires. This happens because we have one view instance for every model instance, and each view holds a direct reference to that model.

Item = Backbone.Model.extend({});
ItemCollection = Backbone.Collection.extend({
    model: Item
});

ItemListView = Backbone.View.extend({
    tagName: "ul",
    
    initialize: function(){
        _.bindAll(this, "renderItem");
    },
    
    renderItem: function(model){
        var itemView = new ItemView({model: model});
        itemView.render();
        $(this.el).append(itemView.el);
    },
    
    render: function(){
        this.collection.each(this.renderItem);
    }
});

ItemView = Backbone.View.extend({
    tagName: "li",
    events: {
        "click a": "clicked"
    },
    
    clicked: function(e){
        e.preventDefault();
        var name = this.model.get("name");
        alert(name);
    },

    render: function(){
        var template = $("#item-template");
        var html = template.tmpl(this.model.toJSON());
        $(this.el).append(html);
    }  
});

var items = new ItemCollection([
    {id: 1, name: "item 1"},
    {id: 2, name: "item 2"},
    {id: 3, name: "item 3"}
]);

var view = new ItemListView({collection: items});
view.render();
$("#showIt").html(view.el);

Our HTML template for each model is almost the same. We need to remove the <li> tag from the template because each view instance for each model will generate the <li> tag for us (using the `tagName` attribute in the above code). We also don’t need the `data-id` attribute anymore.

<script id="item-template" type="text/x-jquery-tmpl">
    <a href="#">${name}</a>
</script>

Now when we render, we will iterate over the list and then render one model at a time. Once the model is rendered and the resulting HTML is stuffed into the `el` of the view instance, the `events` for that view will be scoped to the HTML for that view and model. When we click the link for a given item, then, we have direct access to `this.model` instead of having to look it up via an id. This means we can get right to our actions and code that works with the model.

Here’s a running version of the updated code, via JSFiddle

As you can see from this demo, it’s functionally equivalent to the first JSFiddle demo. The difference is in how we get access to the model in question.

Complexity Is Not Destroyed, Only Moved Around

There’s this little law of thermodynamics that says energy is neither created nor destroy, it just changes form. This law almost holds true for complexity in software development – except that we can and do create complexity in our systems. And it seems that once we create the complexity, it never seems to get destroyed. It only gets moved around.

Ultimately, we haven’t removed any complexity in this new version of the code. Instead we’ve only moved it around. In the first version of the code, we had multiple concerns inside of our one View object: rendering the collection, rendering individual models, figuring out which model is being acted on and acting on it. In the new version of the code, we still have all of these concerns, but now we have separated objects to deal with them. We have one View object to render the collection and one View object to render the model and deal with acting on it.

It may look like we’ve removed the need to figure out which model we’re dealing with when we want to take action. However, that concern is still alive and well in the system. The difference is that we’ve encapsulated it into the View itself, taking advantage of the functionality that Backbone provides for keeping a reference to the model in our view instances.

Additionally, as we expand the number of objects in our system, the topology of our system increases. It now takes a little more effort to understand “the big picture” as we have to deal with a collection view and instance views.

If you’re dealing with a large number of models you may run into performance problems having to instantiate a new view for every object.  I haven’t found a good rule of thumb yet to say when you’ll run into problem, but I’m going to guess that you would need hundreds if not thousands (or more) of models and views in memory to cause serious performance problems. This is, of course, dependent on the platform your app runs on. You’ll likely have less memory in a mobile browser than a desktop browser. Either way, you’ll run into zombie problems before memory usage problems, and cleaning these up will greatly improve your app’s ability to deal with larger lists of items.

The trade-offs for this approach are usually worth it, though. Moving the complexity to higher level system design means that lower level components and implementation details need not be bothered with the complexity. Reducing complexity at lower levels will yield a benefit to the system overall, as it’s the details that continuously get in our way and cause problems, in the long run.

When To Use This Approach

Unless you’re dealing with a very simple list of displayed items, with maybe 1 or 2 actions taken on those items, this approach is almost always going to be beneficial. The ability to keep your collection vs model concerns separated is critical when dealing with large code bases, and the potential for performance problems can often be addressed by “make it right, then make it fast“.

Conclusion: Context Is Still King

Developers that are new to Backbone tend to see some of the introduction examples floating around the web and think “this is how Backbone should be done”. The truth is that this is only one way of developing a very small Backbone application. While these example do show off some of Backbone’s potential, they can lead people down paths that don’t scale well.

I’m not saying we shouldn’t use the patterns we find in small apps and simple examples. I am saying we need to understand the context in which this sample code exists, though. We need to think about the problems that this sample code attempts to solve. We need to be deliberate in our approach to solving our own problems as well. We should learn from the sample code – that’s why it exists. But the lessons we need to learn from it are along the lines of context, problem statements and solutions for specific scenarios.

Think less about “this is how we should do it” and more about “this is when and why we should do it this way”.


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 Backbone, HTML5, Javascript. Bookmark the permalink. Follow any comments here with the RSS feed for this post.
  • Ryan

    Great article! Quick question about the second method and “zombie problems”.
    Does this mean if we were to clean up the ItemListView that we would have to iterate over each ItemView and unbind, remove, onClose individually before finally unbinding, removing and onClosing the ItemListView?

    • http://mutedsolutions.com Derick Bailey

      Thanks! :)

      The short answer is “it depends”. 

      It depends on what you need to do with that list, and when. 

      Say your building a list of images that always sit on a row or in a column somewhere, and you allow people to click on the image or perform other actions. That list is loaded when the app starts up and always stays there. The only time it goes away is when the user moves to a different web page / web site. Otherwise, the user is allowed to continue working with images in the list while they are on that page. In this example, there’s no need to do all of that work to clean things up because the only time any cleanup needs to happen – when the user navigates away from that app – the browser will do it for them when the page unloads entirely.

      Now say the user is allowed to delete items in that list. When the user deletes an item, that item’s view and model must be cleaned up, like I showed in the zombies article.

      Now say the entire list can be swapped out – for example, you can view a list of products in a category. Every time the user selects a different category, the entire list of products must be cleaned up as you described. 

      The good news is that with good encapsulation, this is simple for you as a developer to manage. Make sure every View and Model know hows to clean itself up and coordinate that through the collection or collection’s view. There are a lot of things you can do to make it easier.

      hope that helps!

      • Ryan

        Yeah that helps and clears things up.

        Currently I use a viewPointers object within the “container” view to save a reference to any created views within the container. I then iterate over viewPointers calling their “cleanup” methods. Is there a better way to manage this?

        • http://mutedsolutions.com Derick Bailey

          I’ve done the same thing in a few cases. Another way I’ve done it is to iterate the models that I already have in a collection and call .destroy() on each of them. Then each view that I render for the model listens to the “destroy” event from the model, and cleans itself up. I’m sure there are other good ways to do this, too.

          • Ryan

            Yes I prefer this method of calling some sort of cleanup method on the model that a view is listening for that you speak of.

            I’ve got another question about zombies and I don’t know if you’ve encountered this before. Basically I have a model that is initialized with a view to record the view’s state. I have a attribute of the model that is an array, push values into that array it works fine. However if I destroy the view and recreate it even though the view recreates it’s model for some reason the model’s previous attribute values are still used / left behind so it just doubles. It only seems to do this with an array / object (with random key values) however if I do the same task with a string and just concatenate into the attribute value and recreate the view it doesn’t have the previous attribute left behind.

            Any idea?

          • http://mutedsolutions.com Derick Bailey

            hmmm… the last time i saw that, the model had a set of defaults defined on it as an object literal. the solution for that is to assign the defaults as a function. do you have defaults defined on your model? if not, best to toss this up on StackOverflow or the Backbone mailing list.

          • Ryan

            Default was an empty array with [] – I’m just treating it as a string concatenated by ‘:’ that I can split / join for now which works :)

          • http://mutedsolutions.com Derick Bailey

            if i understand you correctly, change the defaults to a function:

            MyModel = Backbone.Model.extend({
              defaults: function(){
                return {“foo”: []}
              }
            });

            http://stackoverflow.com/questions/7673791/backbone-defaults-being-referenced-on-property-change

          • Ryan

            That works perfectly, thanks again! :)

  • Peter Rust

    Great article!

    On performance: from my experience, this approach scales to hundreds of views on a modern (desktop) browser. You probably wouldn’t want to show hundreds of items in a mobile UI anyways. When I had to display 1,000+ items in a very tight grid, I opted for “one view to rule them all” approach, especially since it was (mostly) a tight read-only display of data. My hunch is that thousands of views wouldn’t scale too well, but I don’t know that for sure.

  • http://twitter.com/ojohnnyo Johnny Oshika

    Another great article, Derick.  Thank you.

  • http://pedroborg.es Pedro Borges

    Very helpful artitle @derickbailey:disqus . Thanks for sharing good Backbone.js resource!

  • Gagara

    Good approaches to solving a common use case.

  • http://www.facebook.com/kethatril Daniel Bonham

    Another possibility would to be the jQuery .data() method to associate the model with the element. This would cut down on the number of views while still being able to get the model easily. It also allows you to use only one event handler to handle clicks.

    One issue I was having with “One view per model” was propagating the model clicked on to its parent view without binding an event for every view. This seems to be one way to solve that problem.

    Of course if you were manipulating the models data within the view then one view per model would be better in my opinion. 

  • Michael Rienstra

    Minor typo: “This wreaks of” should be “This reeks of”.

    • http://twitter.com/LazyBucksOnline LazyBucks.net

      And pretty much every instance of “it’s” should be “its.” “It’s” = “it is.”

      I guess that can be considered nit-picky but it’s really distracting.

  • Calvin

    This is the best article I’ve read on this topic!  My team and I have been struggling with this for a few weeks now.  But this still leaves us with a few follow-up question:

    Suppose we have a list of editable bullet points, and users can click on a “save” button for each bullet point.  And suppose we go with the “one view per model” approach because the encapsulation lets us do a few other things cleanly.  Now after a user clicks a “save” button on one of ItemViews, the ItemView alerts the user with the name of the item that was saved.  Additionally, we want to propagate the event to the ItemListView so that it can show some sort of a notification above the list (this logic should really go in the ItemListView since the notification is managed by the list, and not the individual list items).  Additionally, we might have multiple ItemListViews on a page, so each ItemView needs to be able to communicate with its parent.

    We’ve thought about passing a parent reference to each child view, but this seems pretty opposed to the “Backbone way” of doing things.  And what happens in the case when we have a child with two logical parents?

    What would be your recommendation for managing this type of communication?

    • http://mutedsolutions.com Derick Bailey

      Hi Calvin, and thanks! :)

      There’s not necessarily anything wrong with parent/child views talking to each other. If that helps keep the code simple, then do that. If you do need to do that, though, I’d recommend using events. You can have the parent/child views subscribe to events that the other triggers in order to get things done.

      Once you step out of the parent/child relationship, though, you need to find another way to do that communication. There are a lot of patterns that can be used, such as a mediator pattern that Addy Osmani talks about in large scale JS apps. I also like the event aggregator / dispatcher pattern that I’ve talked about here http://lostechies.com/derickbailey/2011/07/19/references-routing-and-the-event-aggregator-coordinating-views-in-backbone-js/

      As for getting all of this rendered on to the screen, you might benefit from something like Tim Branyen’s “LayoutManager” plugin https://github.com/tbranyen/backbone.layoutmanager or my own “Marionette” plugin https://github.com/derickbailey/backbone.marionette – both of these plugins basically provide the same thing: an application architecture on top of backbone. they just have different opinions on how to accomplish that. pick the one that best fits your opinions.

      • Calvin

        Hi Derick,

        Thanks for the thorough response!  We’ve been playing around a lot with various ways of implementing this communication, and we’ve pretty much decided on using a global event aggregator.  It’s clean, lightweight, and ridiculously easy to implement/use.

        Thanks a ton for all of your contributions to the Backbone community :)

  • Andreas

    In the last example – how would you use the link rendered in the li to remove the rendered model from the collection? 

    I really dont want to use the data-id pattern but I also dont want to store a reference to the parent collection view in each model view.  Jeez. This should be the most simple thing.

    • http://mutedsolutions.com Derick Bailey

      it is easy :)

      have the view listen to the “destroy” event of the model.

      when the ‘destroy’ event is triggered, remove the view by calling “this.remove()”

      call “this.model.destroy()” from the click event handler, which will destroy ithere’s a working example: http://jsfiddle.net/derickbailey/KgszU/

      • Andreas

        S0 destroying a model thats in a collection automatically removes it from the collection – there will be no stale state of the collection?

        • http://mutedsolutions.com Derick Bailey

          correct. you need to clean up the views yourself, though. that’s where this post comes in to play: http://lostechies.com/derickbailey/2011/09/15/zombies-run-managing-page-transitions-in-backbone-apps/

  • Andreas

    Why does it seem to be the death penalty on having render methods in models?
    Imagine I have an item model with   price and cost,  then a method renderMargin
    which does the following:

    var label = (margin <= 0) ? 'red' : 'green';return '’+this.get(price-cost)+”;

    and if pass a collection instead of collection.toJSON()  to an underscore templae i could do  

                   

    • http://mutedsolutions.com Derick Bailey

      the same reason we don’t mix html, css and javascript amongst each other – separation of concerns, maintainability, and simplicity.

      backbone doesn’t give you one giant object to do everything. that in itself should be enough evidence to say that each piece has it’s own set of responsibilities, and you should work within those.

      http://lostechies.com/derickbailey/2011/12/27/the-responsibilities-of-the-various-pieces-of-backbone-js/

      • Andreas

        I guess the correct way then would be to have one itemview with a renderMargin function,  and then subclass that itemview for different kind of views that needs the renderMargin func.

        • http://mutedsolutions.com Derick Bailey

          Yeah, I do that a lot. One of my current projects has a base “FormView” that has some core functionality that all of my forms need. I extend from that every time I need a form, so that I don’t have to repeat the code over and over:

          MyBaseView = Backbone.View.extend({
            // code here
          });

          MySpecificView = MyBaseView.extend({…});

          • Andreas

            so you always pass your models and collections as json to your templates?

          • http://mutedsolutions.com Derick Bailey

            yes. passing the raw model to a template is just going to make things difficult. you’ll end up writing a lot of “get(‘whatever’)” code in your templates, which will get old and frustrating fast.

            fwiw, i’ve wrapped up template rendering in my Backbone.Marionette framework so that I never have to think about this anymore: https://github.com/derickbailey/backbone.marionette

          • Andreas

            Thanks.

            I’ve seen and read about your marionette project before but right now I think I need to get a better understanding of backbone it self, having to taste the boilerplate stuff you have gone through that led you to building marionette, before I could take advantage of it.

  • Ryan Craftman

    Great article – thanks. I am a complete newbie when it comes to Backbone.js. MVC and all of that is fine and we understand it, but to wrap how Backbone works in that world is taking some getting used to and I can say its definitely not easy even for someone experienced and knows everything about MVC.

    Quick question because I think am a little lost.

    renderItem: function(model) takes model parameter when called.

    But it is invoked like so: this.collection.each(this.renderItem);

    No model object was passed into renderItem. What am I missing?

    Thanks for the great article again.

    • http://mutedsolutions.com Derick Bailey

      the call to “this.collection.each” takes in a function as a callback to process each of the items: this.collection.each(function(item){ … }); instead of providing an inline function directly, though, I’m using “this.renderItem” as a callback function – basically, a method pointer. So, when the “collection.each” iterates over each function, it calls the “renderItem” function, passing the “item” argument along to it.

      Pardon the self-promotion for a moment: if you’re interested in seeing more about how this works, and all the surrounding, I have a (paid) screencast that discusses callback functions and many other ways to execute a function, here: http://www.watchmecode.net/javascript-context There are, of course, a thousand other resources for learning more about this, online :)

  • Rob Nugen

    Your articles are really helpful, and they could be even better if “its” was used at it’s intended. http://how-to-spell-its.com/ for reference.

  • Jed

    This is a great demo of two approaches. Two things that are important to mention about the second approach, however, are: 1) It requires rendering in a loop, which is bad, especially with larger lists. It should be a major priority to minimize writes to the DOM; 2) It binds events to every rendered dom element in a collection, rather than binding once to the closest common ancestor and delegating from there. With a large list, this is a ton of extra noise and management. If you want to unbind the click temporarily, for example, you have to unbind many elements instead of one. The tagName thing is also a little suspect in an otherwise template-based system.

    • Jed

      Also, it makes debugging a bit more difficult. It’s nice to be able to see the model id that the DOM element is associated with, without having to compare attributes or pause a click event.

  • Alex Pavlov

    Is it possible to link (with “a href=’someView/{{id}}’” which is defined on Router) each list item to view?

  • James

    It’s a shame there’s no immediate way of using event delegation in these examples. Coping with the overhead of a click event on each view seems to outweigh the benefits of rendering each item in the list as a view. So if I handle the item click events from my collection view, I still have no way of knowing what item was selected without having some kind of hash map.

  • محمد نبی زاده

    perfect :)
    thanks a lot dude