• ¶

    Backbone React Component

  • ¶
    Backbone.React.Component v0.7.1
    
    (c) 2014 "Magalhas" José Magalhães <magalhas@gmail.com>
    Backbone.React.Component can be freely distributed under the MIT license.
    

    Backbone.React.Component is a mixin that glues Backbone models and collections into React components.

    When the component is mounted, a wrapper starts listening to models and collections changes to automatically set your component props and achieve UI binding through reactive updates.

    Basic Usage

    var MyComponent = React.createClass({
      mixins: [Backbone.React.Component.mixin],
      render: function () {
        return <div>{this.props.foo}</div>;
      }
    });
    var model = new Backbone.Model({foo: 'bar'});
    React.renderComponent(<MyComponent model={model} />, document.body);
    
    
    'use strict';
    (function (root, factory) {
  • ¶

    Universal module definition

      if (typeof define === 'function' && define.amd)
        define(['react', 'backbone', 'underscore'], factory);
      else if (typeof module !== 'undefined' && module.exports) {
        var React = require('react');
        var Backbone = require('backbone');
        var _ = require('underscore');
        module.exports = factory(React, Backbone, _);
      } else
        factory(root.React, root.Backbone, root._);
    }(this, function (React, Backbone, _) {
      !Backbone.React && (Backbone.React = {});
      !Backbone.React.Component && (Backbone.React.Component = {});
  • ¶

    Mixin used in all component instances. Exported through Backbone.React.Component.mixin.

      Backbone.React.Component.mixin = {
  • ¶

    Sets this.el and this.$el when the component mounts.

        componentDidMount: function () {
          this.setElement(this.getDOMNode());
        },
  • ¶

    Sets this.el and this.$el when the component updates.

        componentDidUpdate: function () {
          this.setElement(this.getDOMNode());
        },
  • ¶

    When the component mounts, instance a Wrapper to take care of models and collections binding with this.props.

        componentWillMount: function () {
          if (!this.wrapper) {
            this.wrapper = new Wrapper(this, this.props);
          }
        },
  • ¶

    When the component unmounts, dispose listeners and delete this.wrapper reference.

        componentWillUnmount: function () {
          if (this.wrapper) {
            this.wrapper.stopListening();
            delete this.wrapper;
          }
        },
  • ¶

    In order to allow passing nested models and collections as reference we filter nextProps.model and nextProps.collection.

        componentWillReceiveProps: function (nextProps) {
          var model = nextProps.model;
          var collection = nextProps.collection;
    
          var key;
    
          if (this.wrapper.model && model) {
            delete nextProps.model;
            if (this.wrapper.model.attributes) {
              this.wrapper.setProps(model, void 0, nextProps);
            } else {
              for (key in model) {
                this.wrapper.setProps(model[key], key, nextProps);
              }
            }
          }
          if (this.wrapper.collection && collection && !(collection instanceof Array)) {
            delete nextProps.collection;
            if (this.wrapper.collection.models) {
              this.wrapper.setProps(collection, void 0, nextProps);
            } else {
              for (key in collection) {
                this.wrapper.setProps(collection[key], key, nextProps);
              }
            }
          }
        },
  • ¶

    Shortcut to this.$el.find. Inspired by Backbone.View.

        $: function () {
          return this.$el && this.$el.find.apply(this.$el, arguments);
        },
  • ¶

    Crawls up to the owner of the component searching for a collection.

        getCollection: function () {
          var owner = this;
          var lookup = owner.wrapper;
          while (!lookup.collection) {
            owner = owner._owner;
            if (!owner) {
              throw new Error('Collection not found');
            }
            lookup = owner.wrapper;
          }
          return lookup.collection;
        },
  • ¶

    Crawls up to the owner of the component searching for a model.

        getModel: function () {
          var owner = this;
          var lookup = owner.wrapper;
          while (!lookup.model) {
            owner = owner._owner;
            if (!owner) {
              throw new Error('Model not found');
            }
            lookup = owner.wrapper;
          }
          return lookup.model;
        },
  • ¶

    Crawls this._owner recursively until it finds the owner of this component. In case of being a parent component (no owners) it returns itself.

        getOwner: function () {
          var owner = this;
          while (owner._owner) {
            owner = owner._owner;
          }
          return owner;
        },
  • ¶

    Sets a DOM element to render/mount this component on this.el and this.$el.

        setElement: function (el) {
          if (el && Backbone.$ && el instanceof Backbone.$) {
            if (el.length > 1) {
              throw new Error('You can only assign one element to a component');
            }
            this.el = el[0];
            this.$el = el;
          } else if (el) {
            this.el = el;
            Backbone.$ && (this.$el = Backbone.$(el));
          }
          return this;
        }
      };
  • ¶

    Binds models and collections to a React.Component. It mixes Backbone.Events.

      function Wrapper (component, props) {
        props = props || {};
        var model = props.model, collection = props.collection;
  • ¶

    Check if props.model is a Backbone.Model or an hashmap of them

        if (typeof model !== 'undefined' && (model.attributes ||
            typeof model === 'object' && _.values(model)[0].attributes)) {
          delete props.model;
  • ¶

    The model(s) bound to this component

          this.model = model;
  • ¶

    Set model(s) attributes on props for the first render

          this.setPropsBackbone(model, void 0, props);
        }
  • ¶

    Check if props.collection is a Backbone.Collection or an hashmap of them

        if (typeof collection !== 'undefined' && (collection.models ||
            typeof collection === 'object' && _.values(collection)[0].models)) {
          delete props.collection;
  • ¶

    The collection(s) bound to this component

          this.collection = collection;
  • ¶

    Set collection(s) models on props for the first render

          this.setPropsBackbone(collection, void 0, props);
        }
  • ¶

    1:1 relation with the component

        this.component = component;
  • ¶

    Start listeners if this is a root node and if there’s DOM

        if (!component._owner && typeof document !== 'undefined') {
          this.startModelListeners();
          this.startCollectionListeners();
        }
      }
  • ¶

    Mixing Backbone.Events into Wrapper.prototype

      _.extend(Wrapper.prototype, Backbone.Events, {
  • ¶

    Sets this.props when a model/collection request results in error. It delegates to this.setProps. It listens to Backbone.Model#error and Backbone.Collection#error.

        onError: function (modelOrCollection, res, options) {
  • ¶

    Set props only if there’s no silent option

          if (!options.silent)
            this.setProps({
              isRequesting: false,
              hasError: true,
              error: res
            });
        },
  • ¶

    Sets this.props when a model/collection request starts. It delegates to this.setProps. It listens to Backbone.Model#request and Backbone.Collection#request.

        onRequest: function (modelOrCollection, xhr, options) {
  • ¶

    Set props only if there’s no silent option

          if (!options.silent)
            this.setProps({
              isRequesting: true,
              hasError: false
            });
        },
  • ¶

    Sets this.props when a model/collection syncs. It delegates to this.setProps. It listens to Backbone.Model#sync and Backbone.Collection#sync

        onSync: function (modelOrCollection, res, options) {
  • ¶

    Set props only if there’s no silent option

          if (!options.silent)
            this.setProps({isRequesting: false});
        },
  • ¶

    Used internally to set this.collection or this.model on this.props. Delegates to this.setProps. It listens to Backbone.Collection events such as add, remove, change, sort, reset and to Backbone.Model change.

        setPropsBackbone: function (modelOrCollection, key, target) {
          if (!(modelOrCollection.models || modelOrCollection.attributes)) {
            for (key in modelOrCollection)
                this.setPropsBackbone(modelOrCollection[key], key, target);
            return;
          }
          this.setProps.apply(this, arguments);
        },
  • ¶

    Sets a model, collection or object into props by delegating to this.setProps.

        setProps: function (modelOrCollection, key, target) {
          var props = {};
          var newProps = modelOrCollection.toJSON ? modelOrCollection.toJSON() : modelOrCollection;
    
          if (key) {
            props[key] = newProps;
          } else if (modelOrCollection instanceof Backbone.Collection) {
            props.collection = newProps;
          } else {
            props = newProps;
          }
    
          if (target) {
            _.extend(target, props);
          } else {
            this.nextProps = _.extend(this.nextProps || {}, props);
            _.defer(_.bind(function () {
              if (this.nextProps) {
                this.component && this.component.setProps(this.nextProps);
                delete this.nextProps;
              }
            }, this));
          }
        },
  • ¶

    Binds the component to any collection changes.

        startCollectionListeners: function (collection, key) {
          if (!collection) collection = this.collection;
          if (collection) {
            if (collection.models)
              this
                .listenTo(collection, 'add remove change sort reset',
                  _.partial(this.setPropsBackbone, collection, key, void 0))
                .listenTo(collection, 'error', this.onError)
                .listenTo(collection, 'request', this.onRequest)
                .listenTo(collection, 'sync', this.onSync);
            else if (typeof collection === 'object')
              for (key in collection)
                if (collection.hasOwnProperty(key))
                  this.startCollectionListeners(collection[key], key);
          }
        },
  • ¶

    Binds the component to any model changes.

        startModelListeners: function (model, key) {
          if (!model) model = this.model;
          if (model) {
            if (model.attributes)
              this
                .listenTo(model, 'change',
                  _.partial(this.setPropsBackbone, model, key, void 0))
                .listenTo(model, 'error', this.onError)
                .listenTo(model, 'request', this.onRequest)
                .listenTo(model, 'sync', this.onSync);
            else if (typeof model === 'object')
              for (key in model)
                this.startModelListeners(model[key], key);
          }
        }
      });
  • ¶

    Expose Backbone.React.Component.mixin.

      return Backbone.React.Component.mixin;
    }));
  • ¶

    Fork me on GitHub