Attaching Backbone and Marionette views to existing HTML

HTML5 web applications frequently need to attach a view class to existing HTML. For example, the server-side piece of the application often renders a navigation bar and some other basic page elements. Some applications even render the first page’s content so the Marionette application won’t have to make a separate request for its initial content.

In this situation we’ll usually want to attach a Marionette or plain Backbone view class to some of the existing HTML. The view is a convenient high-level mechanism for handling events and performing other UI-related tasks. That’s enough of a reason to create a view even if we don’t need the view to render new HTML. A navigation bar view class, for example, might handle click events, dynamically update the navbar’s menu items, and maybe set up a handler for a search box.

With Backbone & Marionette, it’s easy to accomplish this.

There are actually a couple of ways to do it, depending on the situation. This current post covers the first way, and I’ll discuss the second option in a subsequent post.

Using a simple view by itself

The first case is when we won’t need to replace the existing HTML fragment that our view takes over. This is often the right approach for a navigation bar: the HTML structure doesn’t change, but we do want to add some event handlers and other logic.

The key piece of this solution is to use the el option to pass the existing element’s selector to the new view’s constructor. This is a Backbone mechanism, and it works for both Backbone & Marionette views:

// attach to static html at element "#navbar-links"
var myview = new MyView( {el: $('#navbar-links')} );

The Backbone view’s constructor will still set up event handlers specified in the view’s events map, if any. In fact, this automatic event handling is usually the main reason I want to use a view in the first place; it’s a terrific convenience.

A navbar example

Let’s hook up a view to a navbar. Here’s how it will look in the browser:

navbar example

Here is the markup, which uses Bootstrap’s navbar mechanism:

<nav class="navbar navbar-default" role="navigation">
    <div class="container-fluid">
        <div class="navbar-header">
            <a class="navbar-brand" href="#">My Application</a>
        </div>

        <div class="collapse navbar-collapse" id="navbar-links">
            <ul class="nav navbar-nav">
                <li class="active"><a href="#">Monkeys</a></li>
                <li><a href="#gorillas">Gorillas</a></li>
            </ul>
        </div>
    </div>
</nav>

Right now, clicking the navbar links doesn’t change the active link – for that, we need some JavaScript code to set the active CSS class on the selected element only. This is why we want to attach a view class to this existing markup: a view is the perfect place for that JavaScript, and it will let us interact with the page using the same Marionette constructs like View and Controller that we use throughout the rest of the application.

We’ll start by attaching a minimal view. This code uses a Marionette Controller class for convenience. The code comes from the working example code mentioned at the end of this post. In that example code, the startup code calls the controller’s start function when the application is ready to run, and that’s where we create the view instance and pass it the #navbar-links selector to connect to the existing HTML navbar fragment.

Module.Controller = Marionette.Controller.extend({
    start: function(){
        this.view = new Module.View( {el: $('#navbar-links')} ); // attach to static html @ that element
    }
});

Module.View = Marionette.ItemView.extend({
    // override: don't really render, since this view just attaches to existing navbar html.
    render: function() {
    }
});

That connects our view to the #navbar-links element, but the view doesn’t actually do anything yet – there is no functionality at this point. So now let’s put it to work.

Adding event listeners

We want our view to listen for click events in the links’ a tags, and change the active CSS class so that clicking a navbar link will visually toggle the state of the navbar buttons.

We’ll use Backbone’s events map to add a listener for the click event. Here’s the new version of our View class:

Module.View = Marionette.ItemView.extend({
    events: {
        'click a': 'onClickLink',
    },

    onClickLink: function(e) {
        this.$('li.active').toggleClass('active', false); // turn previously-selected nav link off
        $(e.target).blur()
            .closest('li').toggleClass('active', true); // turn on new link
    },

    // custom render: don't really render, since this view just attaches to existing navbar html.
    render: function() {
    },
});

The events map specifies a handler for click events coming from any a tags. For Backbone & Marionette views, these event are only within the scope of the view’s root element, so we won’t be affected by any a tags outside this narrow section of the navbar.

The click handler function onClickLink first toggles the active li element into an unselected state by removing the active CSS class. Then the next statement blurs the newly-selected link and marks its parent li element as active so it will appear with Bootstrap’s visual decoration for the selected link. (The blur step removes the annoying dotted-rectangle marker that FireFox shows for selected a tags.)

That’s all it takes; now we have a functioning navbar in our small Marionette application!

Optionally binding a ui map

In my production code, I usually add one more thing to my render functions that attach themselves to existing HTML:

// override: don't really render, since this view just attaches to existing navbar html.
render: function() {
    this.bindUIElements(); // wire up this.ui, if any.
}

Instead of an empty render, I add a call to the view’s bindUIElements function. Marionette views support a ui map that binds view variables to UI elements for us, saving us the trouble of using jQuery ourselves to access them. This is a handy shortcut for elements we need to work with frequently. The view’s built-in bindUIElements function does this for us, and it is called in Marionette’s default render function. Since we’re overriding render ourselves, we have to call bindUIElements manually if we want it. More often than not, I end up using the ui map in my navbar views & other views that attach to existing HTML, so I’ve gotten in the habit of always adding this call to bindUIElements. Doing this saves me – or another programmer who comes after me – from going through a frustrating debugging session when I later add a ui map to this view, only to find it doesn’t work because I’ve overridden render and the default call to bindUIElements isn’t being made anymore. It’s safe to add the call to bindUIElements even if I’m not using a ui map; the call is essentially a no-op if my view doesn’t have a ui map, so it’s a cheap insurance.

I encourage you to check out the runnable example on github. I’ve frequently made use of this construct in Backbone & Marionette projects, as well as another method of attaching a view to existing HTML. I’ll cover the second way next time in part two of this post.