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?
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:
With this HTML template:
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 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 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 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 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.
Now that we have a data-id attribute in place, we can grab it from the element that we clicked. Then we can get the model from the collection, get the name and show an alert box.
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 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.
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.
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”.