blog

Using a virtual DOM with Backbone views

| categories: virtual-dom, backbone.js, converse.js

Backbone.js, first released in 2010, provided one-way event binding with unidirectional data-flow and therefore a means to do reactive programming before it was cool.

Backbone views provide a render method, which is supposed to insert HTML into the DOM. How this was actually done was left up to the user as a homework assignment.

Generally, jQuery's html method was used. Like so:

const MyView = Backbone.View.extend({

    initialize () {
        this.model.on('change', this.render, this);
    },

    render () {
        this.$el.html(this.template(this.model.toJSON()));
        return this;
    }
});

Each view has an associated DOM element, this.$el if you're using jQuery or this.el otherwise. In the above example, the element's contents would be replaced.

What this approach however lacks, is a way to rerender views containing forms without losing the input focus or user-provided form values.

This is because back in 2010, the virtual DOM as popularized by React wasn't yet around.

Additionally, since rendering the HTML and inserting it into the DOM were both done inside the render method, and the implementation details were left up to the user, enough rope was provided for you to hang yourself by putting things inside the render method which shouldn't be there.

Contrast this to a React component's render method, which is a "pure function" that doesn't modify the DOM, and instead simply returns JSX:

class MyComponent extends React.Component {

        render() {
                return <p>Hello World!</p>;
        }
}

The markup is then inserted into the DOM separately with ReactDOM.render

ReactDOM.render(
        <MyComponent />,
        document.getElementById('root')
);

The fact that a component's render method must return JSX, means that they are pure functions without side-effects.

Backbone Views which use a virtual DOM

In converse.js we still use Backbone Models and Views, and for the most part, Backbone still works beautifully. It's a small, lightweight library that's fit for purpose and doesn't get in the way.

However, we've been missing out on that sweet sweet virtual DOM.

No more!

To make our Backbone Views more reactive, as is done in React, I wrote Backbone.VDOMView.

This library adds a new view, called Backbone.VDOMView, which uses a Virtual DOM (provided by Snabbdom) to only update those DOM elements (within the view) as necessary.

Instead of writing a render method, for Backbone.VDOMView you need to implement a toHTML method, which is a pure function that simply returns the view's HTML.

Here's what it looks like:

const MyView = Backbone.VDOMView.extend({

    initialize () {
        this.model.on('change', this.render, this);
    },

    toHTML () {
        return this.template(this.model.toJSON());
    }
});

Backbone.VDOMView takes the output of toHTML and converts it into virtual DOM nodes and automatically diffs and patches the DOM.

When the view's associated Backbone.Model (referenced via this.model) changes, we can simply call render again, knowing that only the changed DOM elements will be updated.

We write less to the DOM and we prevent the loss of form input values and loss of element focus.

Include the root element in your HTML

There's one important difference with normal Backbone.Views and that is that toHTML (as opposed to render) must including the view's root element in the returned HTML.

With normal Backbone.View, you don't include the view's root element when you render a template.

React has a similar requirement to this. JSX returned in the render method of a component must have a root node which contains everything else.

jQuery not required

With Backbone, you can use Backbone.NativeView to remove the default dependency on jQuery.

Backbone.VDOMView checks whether you are using Backbone.NativeView and will then subclass it instead of Backbone.View.

Usage in converse.js

In release 3.3.0 of Converse.js I've removed jQuery as dependency and started integrating Backbone.VDOMView.

Various Backbone.View components from Converse.js have already been migrated over to Backbone.VDOMView.

So far, I'm very happy with the results, and haven't run into any issues.

If you're using Backbone.VDOMView or have any feedback, feel free to leave a comment or contact me here.