Backbone.js and Underscore.js in Drupal 8

Drupal 8

The Drupal 8 development cycle has definitely been a long one. There are several exciting features on the way, but the improvements to the authoring experience in Drupal 8 have definitely drawn a lot of attention. (I know Amber is clamoring for in-place editing for this blog.) The Spark project is the home to much of this work. Several new core modules that contribute to these improvements (Contextual, Quickedit, Toolbar, CKEditor and to a lesser extent, Tour) leverage a pair of popular javascript libraries Backbone.js and Underscore.js.

Both Backbone.js, and its dependency Underscore.js were committed to Drupal 8 two years ago! Let's take a quick look at both Backbone.js and Underscore.js, how they're used in core, and how you might be able to use them to simplify some javascript for your site.

Back what now?

Backbone.js is a relatively established and popular library for enriching web applications. In their own words, it:

Gives structure to web applications by providing models with key-value binding and custom events, collections with a rich API of enumerable functions, views with declarative event handling, and connects it all to your existing API over a RESTful JSON interface.

What does that actually mean for Drupal? At the moment Drupal is primarily using Backbone.js to add data binding to complex front-end interactions. Data binding is a fancy way of saying that Backbone.js helps us keep our application data and the user interface in sync easily. First, we set up Backbone Models to represent the internal structure and state we need to track, then corresponding Backbone Views can handle rendering (and updating) the UI when it's appropriate to do so.

Backbone.js, like many other javascript frameworks, doesn't strictly follow the Model-View-Controller pattern. Many of the frameworks that fit this pattern are referred to with other acronyms, or MV for short. In short, Backbone Models are used to represent data objects (like Drupal's Toolbar). Backbone Views typically compose the actual user interface, they depend on models for their data but don't necessarily directly interact with them. For a more thorough introduction to MV (especially in how it relates to Backbone.js) I highly recommend Addy Osmani's book Developing Backbone.js Applications.

It's worth noting that Backbone.js can also be used more like a framework for developing Single Page Applications (or SPAs). The Backbone.Router and Backbone.sync components can help handle navigation throughout an application and syncing data back to a server. However, neither of these are currently in use in Drupal 8.

Under who?

Underscore.js, which comes along as a dependency of Backbone.js, is used more widely throughout core. In their own words, Underscore is:

The tie to go along with jQuery's tux and Backbone's suspenders.

Underscore.js is essentially a library of handy utility helpers. It provides some handy functional helpers (map, reduce, filter) as well as some additional syntactical sugar for Arrays and Objects (has, isMatch, extend, union).

Data Binding, how does it work?

The two important pieces in leveraging Backbone's data binding are Models and Views.

Models are the heart of any JavaScript application, containing the interactive data as well as a large part of the logic surrounding it: conversions, validations, computed properties, and access control.
- http://backbonejs.org/#Model

A fairly straightforward example of a Backbone Model in Drupal 8 can be found in the Contextual module. This model manages the internal state for the contextual link behavior, ie: these handy little edit icons:

contextual icons

Here is the code snippet from core/modules/contextual/js/models/StateModel.js:

  /**
   * Models the state of a contextual link's trigger, list & region.
   */
  Drupal.contextual.StateModel = Backbone.Model.extend({

    defaults: {
      // The title of the entity to which these contextual links apply.
      title: '',
      // Represents if the contextual region is being hovered.
      regionIsHovered: false,
      // Represents if the contextual trigger or options have focus.
      hasFocus: false,
      // Represents if the contextual options for an entity are available to
      // be selected (i.e. whether the list of options is visible).
      isOpen: false,
      // When the model is locked, the trigger remains active.
      isLocked: false
    },

    /**
     * Opens or closes the contextual link.
     *
     * If it is opened, then also give focus.
     */
    toggleOpen: function () {
      var newIsOpen = !this.get('isOpen');
      this.set('isOpen', newIsOpen);
      if (newIsOpen) {
        this.focus();
      }
      return this;
    },

    /**
     * Closes this contextual link.
     *
     * Does not call blur() because we want to allow a contextual link to have
     * focus, yet be closed for example when hovering.
     */
    close: function () {
      this.set('isOpen', false);
      return this;
    },

    /**
     * Gives focus to this contextual link.
     *
     * Also closes + removes focus from every other contextual link.
     */
    focus: function () {
      this.set('hasFocus', true);
      var cid = this.cid;
      this.collection.each(function (model) {
        if (model.cid !== cid) {
          model.close().blur();
        }
      });
      return this;
    },

    /**
     * Removes focus from this contextual link, unless it is open.
     */
    blur: function () {
      if (!this.get('isOpen')) {
        this.set('hasFocus', false);
      }
      return this;
    }

  });

Notice how the model handles both managing internal data (is this contextual link open? hovered over? does it have focus?) and behavior (toggleOpen, close, blur). It's also worth pointing out that the behavior is only setting state for the model, it's not interacting with the DOM in any way.

Contextual module provides four views for this one Backbone Model (in core/modules/contextual/js/views/). AuralView.js provides screen reader support. KeyboardView.js handles keyboard interaction for contextual links. RegionView.js renders the visual region associated with the contextual link. VisualView.js handles rendering the contextual link itself.

So how are these views connected to a single model? In the case of the region view, it's initialize method is using the listenTo event:

  Drupal.contextual.RegionView = Backbone.View.extend({
    /**
     * {@inheritdoc}
     */
    initialize: function () {
      this.listenTo(this.model, 'change:hasFocus', this.render);
    },

Any time the StateModel's hasFocus property changes, this change will automatically propagate to this view. This pattern of listening for a particular change event and then re-rendering the view is essentially all there is to Backbone's data binding (at least in terms of how it's being used in Drupal 8).

There's one other small piece of glue that's needed to tie the model(s) and view(s) together. Taking a look at core/modules/contextual/js/contextual.js we find the code that handles this:

function initContextual($contextual, html) {
    var contextual = Drupal.contextual;
    ...
    // Create a model and the appropriate views.
    var model = new contextual.StateModel({
      title: $region.find('h2:first').text().trim()
    });
    var viewOptions = $.extend({el: $contextual, model: model}, options);
    contextual.views.push({
      visual: new contextual.VisualView(viewOptions),
      aural: new contextual.AuralView(viewOptions),
      keyboard: new contextual.KeyboardView(viewOptions)
    });
    contextual.regionViews.push(new contextual.RegionView(
      $.extend({el: $region, model: model}, options))
    );
    contextual.regionViews.push(new contextual.RegionView(
      $.extend({el: $region, model: model}, options))
    );
    // Add the model to the collection. This must happen after the views have been
    // associated with it, otherwise collection change event handlers can't
    // trigger the model change event handler in its views.
    contextual.collection.add(model);

Now our Backbone Model and our Backbone Views are connected, and changes in the model will be updated in the UI appropriately.

Where else is it used?

Now that we have an idea how Backbone's data binding works, where else is Drupal core using it? The Spark project was one of the main drivers for including Backbone.js and Underscore.js in core. It's probably not too surprising, then, that many of the current uses of Backbone involve the editorial experience and interface. Here are a few examples:

For Drupal 7, it's worth mentioning there is a Backbone integration module. While this module hasn't seen any new commits in a year, it contains some examples for working with Services or RESTWS to handle rendering and updating nodes using Backbone (rather than Drupal's native node forms).

Where do we go from here?

Hopefully by now it's apparent that Backbone.js and Underscore.js provide some useful utilities to help manage complex front-end interactions. With Backbone we can take incoming JSON (or any other API data source) set up models and views to manage keeping the data in sync without relying solely on DOM manipulation.

While Drupal core itself isn't (yet) making heavy use of Backbone, there are many examples available online. I'll also be covering Backbone in a bit more depth as part of What's the fuss with all this JS at DrupalCon Los Angeles. If you're already using either of these libraries with your Drupal site, I'd love to hear about it!

Related Topics: 

Comments

So Backbone is in Drupal 8 and it's technically available if you want it. But it's also important to note that because of Drupal 8's library-centric asset managment, it can also be not present on the page if you don't need it. Just don't define any libraries that includes it as a dependency and you won't load it (unless you're loading core things that need it.)

And why would you want to use it. You could:

Define a custom UI who's job it is to modify the data of something. Like a chat block, or advanded settings for a plugin, or who knows what people will think of.

The most common use has already been handled by Drupal 8 in the quick edit module. But if your application needs model binding, and you're fine with using Backbone, it's easier for you get started.

If you want model binding and you DON'T want backbone, you can easily include your own library and list it as a dependency.

Absolutely! Thanks for pointing this out Chris. I'm sure, as Drupal 8 continues to evolve, there will be several clever uses (especially in contrib modules).

Figuring out how to choose when to use Backbone (or Angular, or React, or ???) will be one of the main things I'm planning to talk about in "What's the fuss with all this JS" at DrupalCon next month. I'm looking forward to lots of these conversations in Los Angeles. Hopefully I'll see you there!

Great blog post! :)

If you want to read the rationale for adding Backbone.js to Drupal core and not some other library, see the issue where it was introduced. Basically, it is the least opinionated, the most lightweight, the simplest to learn from te bunch. And therefore likely the one to remain relevant the longest.
So far, that reasoning seems to have been correct: quite a few other frameworks have already disappeared into irrelevance, but not Backbone.

Thanks Wim!

I agree, and the community around Backbone seems quite vibrant as well.

I really like how Mark Sonnabaum summed it up in the original issue, "we aren't committing to Backbone as a framework, we're just using the parts of it we need"

I'm quite interested in helping folks sort through some of these new front end frameworks to see through hype and figure out how they can actually help build better websites.

Shipping Backbone with Drupal core is at very least a modest endorsement, and I'm looking forward to seeing how people make use of it.

Ugh, no-one's using Backbone.js these days everyone has moved on to React and Angular JS

Backbone.js is the granite countertops of Web development: _so dated_.

Backbone.js certainly isn't the latest javascript framework; it seems like a new one is released every few weeks.

Based on the current needs for Drupal core I still think it was a solid choice. Time will tell if Angular or React prove to be a better fit, but there's nothing stopping anyone from using whichever library or framework they choose. I've worked on projects using both. There's an appropriate tool for every job, and it'll be interesting to see how the community makes use of this one.

Add new comment