Building complex user interfaces with Backbone views can seem like a herculean task, especially when taking your first swing at the framework. However, it's critical to remember that Backbone is only here to help. Rather than counting on Backbone views as the building blocks of our user interface, I propose we start with an easily achievable solution to the problem and work backwards.
Update: Thanks for the feeback! You can now play around with the code in this post via plunkr http://plnkr.co/edit/VQiIT3.
Without breaking a sweat, we could whip up the exact user interface we are shooting for and style it to our heart's content. For illustrative purposes, we will use this sweet soccer field that displays a team full of players. Again, we are starting with the solution here and working backwards to take advantage of Backbone views. With just comments to start, we can easily assign Backbone views to existing aspects of the HTML.
This manual process of assigning views to DOM elements is exactly how the the 'el' property works. Backbone utilizes
this.el. Using attributes at the top of the Backbone.View.extend constructor function below, we can build out a DOM element corresponding to our static implementation and then then have access to it using
With the current implementation we've accounted for the outer DIV, but there's no team on the field (
<ul> not rendered inside the <div>). A simple solution would be to render the
<ul> DOM element inside this.el utilizing jQuery. Great idea! Without further ado, meet the $el property.
The creators of Backbone implemented the $el property to function exactly the same as el, both working in harmony as pointers to the same DOM element. However, $el has the incredibly useful benefit of being a jQuery DOM element, and therefore possessing all of the great jQuery methods we've come to know and love. This includes some serious talent, like .append() .next() and .html().
$el is the exact same thing as el, but with all the goodness of jQuery.
Now, we know we can build out the teamView in the exact same way we built the fieldView; construct the
<ul class="team"> DOM element utilizing TeamView's attributes (same as 'the el property!' diagram above) and then provide access to it with a render function which returns this.el, the DOM element's reference. Finally, in the fieldView where we want the team to exist we can simply:
As we continue down the DOM tree, the process remains the same. The
<li class="player"> DOM elements should be built with a view and rendered inside of it's parent's render function by appending the child DOM element inside the parent DOM element. Checkout this chaining action below:
This solution won't actually work, since our reference to this.teamView doesn't exist yet. In fact, the teamView doesn't exist at all until we utilize the TeamView constructor function to instantiate an instance of the TeamView. The same problem exists when referencing a playerView inside of the TeamView. This gap is easily closed by instantiating children views inside the parent views.
In FieldView's initialize function we can set this.teamView by instantiating a single TeamView, since we know we're working with a single team. TeamView represents a collection, so we'll grab that collection from the associated FieldModel and pass that in as an initial parameter. Doing so will provide TeamView access to all the individual player models, which are required to generate each unique list item. Check out the complete solution below:
There's a few new things going on here as we finalize our solution. Since TeamView references a collection, we want to instantiate a new PlayerView for every player in the collection. This is easily accomplished inside TeamView's render function with the map method. Map will loop through the collection, giving us access to every player as the first argument of the callback function, and returns an array of values generated by the callback function. So, the result of the map function is an array of rendered playerViews (DOM elements), where each playerView was instantiated and rendered using a unique player model provided by the map method. This result works perfectly with jQuery's append method, which accepts an array of DOM elements gracefully and places them all inside this.$el.
Lastly, since each playerView has reference to a playerModel, we can grab the position attribute off the model and set the list item's html with jQuery.
That's it! - sort of. None of this is possible without our models. There's a lot of shorthand utilized below, and general lack of separating concerns, but I didn't want to leave you hanging on the models side of this equation.
After generating all the models, the last two lines of the index page complete our view logic by doing the following:
- Instantiating a fieldView with access to the fieldModel (which then has access to the team collection!)
- Rendering the fieldView (which kicks off our chain of renders) and appending the parent with all of its children to the page.
Really, that's it. Backbone is a fantastic framework that delivers tremendous flexibility for developers. If views were intimidating at first, you can rest easy knowing they are only here to help!