BackboneJs

and

Web Components

Mario Jurić

Software Analyst

@

 

Topics

  • Intro to Web Components
  • (Very) basic BackboneJs overview
  • Demo app showing the integration between the two

Web Components

  • Templates
  • Shadow DOM
  • Custom Elements
  • HTML Imports

Templates

  • Its content is effectively inert until activated. Essentially, your markup is hidden DOM and does not render.

  • Any content within a template won't have side effects. Script doesn't run, images don't load, audio doesn't play,...until the template is used.

  • Templates are basically a document fragment

  • activate with document.ImportNode( sometemplate.content, true )

Shadow DOM

  • Hide Presentation Details

  • Separating Content from Presentation

  • Advanced Projection

  • Multiple shadow roots in the same shadow host

Custom Elements

  • Define new HTML/DOM elements

  • Create elements that extend from other elements

  • Logically bundle together custom functionality into a single tag

  • Extend the API of existing DOM elements

HTML Imports

  • include HTML documents in other HTML documents

  • Sub-imports

  • Content is useful only when you add it

  • resources are loaded only once

  • Imports block rendering of the main page

  • Imports don't block parsing of the main page

Backbone.js

  • Backbone.Model

  • Backbone.Collection

  • Backbone.Router

  • Backbone.Events

  • Backbone.View

Backbone.View

  // Backbone.View
  // -------------

  // Backbone Views are almost more convention than they are actual code. A View
  // is simply a JavaScript object that represents a logical chunk of UI in the
  // DOM. This might be a single item, an entire list, a sidebar or panel, or
  // even the surrounding frame which wraps your whole app. Defining a chunk of
  // UI as a **View** allows you to define your DOM events declaratively, without
  // having to worry about render order ... and makes it easy for the view to
  // react to specific changes in the state of your models.

  // Creating a Backbone.View creates its initial element outside of the DOM,
  // if an existing element is not provided...
  var View = Backbone.View = function(options) {
    this.cid = _.uniqueId('view');
    options || (options = {});
    _.extend(this, _.pick(options, viewOptions));
    this._ensureElement();
    this.initialize.apply(this, arguments);
    this.delegateEvents();
  };

  // Cached regex to split keys for `delegate`.
  var delegateEventSplitter = /^(\S+)\s*(.*)$/;

  // List of view options to be merged as properties.
  var viewOptions = ['model', 'collection', 'el', 'id', 'attributes', 'className', 'tagName', 'events'];

  // Set up all inheritable **Backbone.View** properties and methods.
  _.extend(View.prototype, Events, {

    // The default `tagName` of a View's element is `"div"`.
    tagName: 'div',

    // jQuery delegate for element lookup, scoped to DOM elements within the
    // current view. This should be preferred to global lookups where possible.
    $: function(selector) {
      return this.$el.find(selector);
    },

    // Initialize is an empty function by default. Override it with your own
    // initialization logic.
    initialize: function(){},

    // **render** is the core function that your view should override, in order
    // to populate its element (`this.el`), with the appropriate HTML. The
    // convention is for **render** to always return `this`.
    render: function() {
      return this;
    },

    // Remove this view by taking the element out of the DOM, and removing any
    // applicable Backbone.Events listeners.
    remove: function() {
      this.$el.remove();
      this.stopListening();
      return this;
    },

    // Change the view's element (`this.el` property), including event
    // re-delegation.
    setElement: function(element, delegate) {
      if (this.$el) this.undelegateEvents();
      this.$el = element instanceof Backbone.$ ? element : Backbone.$(element);
      this.el = this.$el[0];
      if (delegate !== false) this.delegateEvents();
      return this;
    },

    // Set callbacks, where `this.events` is a hash of
    //
    // *{"event selector": "callback"}*
    //
    //     {
    //       'mousedown .title':  'edit',
    //       'click .button':     'save',
    //       'click .open':       function(e) { ... }
    //     }
    //
    // pairs. Callbacks will be bound to the view, with `this` set properly.
    // Uses event delegation for efficiency.
    // Omitting the selector binds the event to `this.el`.
    // This only works for delegate-able events: not `focus`, `blur`, and
    // not `change`, `submit`, and `reset` in Internet Explorer.
    delegateEvents: function(events) {
      if (!(events || (events = _.result(this, 'events')))) return this;
      this.undelegateEvents();
      for (var key in events) {
        var method = events[key];
        if (!_.isFunction(method)) method = this[events[key]];
        if (!method) continue;

        var match = key.match(delegateEventSplitter);
        var eventName = match[1], selector = match[2];
        method = _.bind(method, this);
        eventName += '.delegateEvents' + this.cid;
        if (selector === '') {
          this.$el.on(eventName, method);
        } else {
          this.$el.on(eventName, selector, method);
        }
      }
      return this;
    },

    // Clears all callbacks previously bound to the view with `delegateEvents`.
    // You usually don't need to use this, but may wish to if you have multiple
    // Backbone views attached to the same DOM element.
    undelegateEvents: function() {
      this.$el.off('.delegateEvents' + this.cid);
      return this;
    },

    // Ensure that the View has a DOM element to render into.
    // If `this.el` is a string, pass it through `$()`, take the first
    // matching element, and re-assign it to `el`. Otherwise, create
    // an element from the `id`, `className` and `tagName` properties.
    _ensureElement: function() {
      if (!this.el) {
        var attrs = _.extend({}, _.result(this, 'attributes'));
        if (this.id) attrs.id = _.result(this, 'id');
        if (this.className) attrs['class'] = _.result(this, 'className');
        var $el = Backbone.$('<' + _.result(this, 'tagName') + '>').attr(attrs);
        this.setElement($el, false);
      } else {
        this.setElement(_.result(this, 'el'), false);
      }
    }

  });

Demo time

How I see them working

  • Web components (custom elements) should try so solve one problem and do it right
  • Backbone has simple but powerful concepts to bring structure to a set of separate components
  • Backbone has a very (very!) slim view layer and 3/4 Web Components pieces fill that gap  

Ten Principles for Great General Purpose Web Components

  1. Address a common need.
  2. Do one job really well.
  3. Work predictably in a wide variety of circumstances.
  4. Be useful right out of the box.
  5. Be composable.
  6. Be styleable.
  7. Be extensible.
  8. Think small.
  9. Adapt to the user and device.
  10. Deliver the key benefit to HTML authors, not just coders.

... and some of mine

  • Create a clear input/output API for your components

  • DOCUMENT the API

  • Use custom events Backbone (and others) can easily connect to

FAQ

Reference

BackboneJs and Web Components - make 'em dance

By jurza

BackboneJs and Web Components - make 'em dance

  • 1,417