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 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
In release 3.3.0 of Converse I’ve removed jQuery as dependency and started integrating Backbone.VDOMView.
Various Backbone.View
components from Converse 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.