(function (root, factory) {Backbone.React.Component v0.10.0
(c) 2014, 2015 "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 state and achieve UI binding through reactive updates.
Basic Usage
var MyComponent = React.createClass({
  mixins: [Backbone.React.Component.mixin],
  render: function () {
    return <div>{this.state.model.foo}</div>;
  }
});
var model = new Backbone.Model({foo: 'bar'});
ReactDOM.render(<MyComponent model={model} />, document.body);
            
(function (root, factory) {Universal module definition
  if (typeof define === 'function' && define.amd) {
    define(['react', 'react-dom', 'backbone', 'underscore'], factory);
  } else if (typeof module !== 'undefined' && module.exports) {
    module.exports = factory(require('react'), require('react-dom'), require('backbone'), require('underscore'));
  } else {
    factory(root.React, root.ReactDOM, root.Backbone, root._);
  }
}(this, function (React, ReactDOM, Backbone, _) {
  'use strict';
  if (!Backbone.React) {
    Backbone.React = {};
  }
  if (!Backbone.React.Component) {
    Backbone.React.Component = {};
  }Mixin used in all component instances. Exported through Backbone.React.Component.mixin.
  var mixin = Backbone.React.Component.mixin = {Types of the context passed to child components. Only
hasParentBackboneMixin is required all of the others are optional.
    childContextTypes: {
      hasParentBackboneMixin: React.PropTypes.bool.isRequired,
      parentModel: React.PropTypes.any,
      parentCollection: React.PropTypes.any
    },Types of the context received from the parent component. All of them are optional.
    contextTypes: {
      hasParentBackboneMixin: React.PropTypes.bool,
      parentModel: React.PropTypes.any,
      parentCollection: React.PropTypes.any
    },Passes data to our child components.
    getChildContext: function () {
      return {
        hasParentBackboneMixin: true,
        parentModel: this.getModel(),
        parentCollection: this.getCollection()
      };
    },Sets this.el and this.$el when the component mounts.
    componentDidMount: function () {
      this.setElement(ReactDOM.findDOMNode(this));
    },Sets this.el and this.$el when the component updates.
    componentDidUpdate: function () {
      this.setElement(ReactDOM.findDOMNode(this));
    },When the component gets the initial state, instance a Wrapper to take
care of models and collections binding with this.state.
    getInitialState: function () {
      var initialState = {};
      if (!this.wrapper) {
        this.wrapper = new Wrapper(this, initialState);
      }
      return initialState;
    },When the component mounts, instance a Wrapper to take care
of models and collections binding with this.state.
    componentWillMount: function () {
      if (!this.wrapper) {
        this.wrapper = new Wrapper(this);
      }
    },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;
      if (this.wrapper.model && model) {
        if (this.wrapper.model !== model) {
          this.wrapper.stopListening();
          this.wrapper = new Wrapper(this, void 0, nextProps);
        }
      } else if (model) {
        this.wrapper = new Wrapper(this, void 0, nextProps);
      }
      if (this.wrapper.collection && collection) {
        if (this.wrapper.collection !== collection) {
          this.wrapper.stopListening();
          this.wrapper = new Wrapper(this, void 0, nextProps);
        }
      } else if (collection) {
        this.wrapper = new Wrapper(this, void 0, nextProps);
      }
    },Shortcut to @$el.find if jQuery ins present, else if fallbacks to DOM
native querySelector. Inspired by Backbone.View.
    $: function () {
      var els;
      if (this.$el) {
        els = this.$el.find.apply(this.$el, arguments);
      } else {
        var el = ReactDOM.findDOMNode(this);
        els = el.querySelector.apply(el, arguments);
      }
      return els;
    },Grabs the collection from @wrapper.collection or @context.parentCollection
    getCollection: function () {
      return this.wrapper.collection || this.context.parentCollection;
    },Grabs the model from @wrapper.model or @context.parentModel
    getModel: function () {
      return this.wrapper.model || this.context.parentModel;
    },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;
        if (Backbone.$) {
          this.$el = Backbone.$(el);
        }
      }
      return this;
    }
  };Binds models and collections to a React.Component. It mixes Backbone.Events.
  function Wrapper (component, initialState, nextProps) {Object to store wrapper state (not the component state)
    this.state = {};1:1 relation with the component
    this.component = component;Use nextProps or component.props and grab model and collection
from there
    var props = nextProps || component.props || {};
    var model, collection;
    if (component.overrideModel && typeof component.overrideModel === 'function'){Define overrideModel() method on your React class to programatically supply a model object
Will override this.props.model
      model = component.overrideModel();
    } else {
      model = props.model;
    }
    if (component.overrideCollection && typeof component.overrideCollection === 'function'){Define overrideCollection() method on your React class to programatically supply a collection object
Will override this.props.collection
      collection = component.overrideCollection();
    } else {
      collection = props.collection;
    }
    this.setModels(model, initialState);
    this.setCollections(collection, initialState);
  }Mixing Backbone.Events into Wrapper.prototype
  _.extend(Wrapper.prototype, Backbone.Events, {Sets this.state when a model/collection request results in error. It delegates
to this.setState. It listens to Backbone.Model#error and Backbone.Collection#error.
    onError: function (modelOrCollection, res, options) {Set state only if there’s no silent option
      if (!options.silent) {
        this.component.setState({
          isRequesting: false,
          hasError: true,
          error: res
        });
      }
    },
    onInvalid: function (model, res, options) {
      if (!options.silent) {
        this.component.setState({
          isInvalid: true
        });
      }
    },Sets this.state when a model/collection request starts. It delegates to
this.setState. It listens to Backbone.Model#request and
Backbone.Collection#request.
    onRequest: function (modelOrCollection, xhr, options) {Set state only if there’s no silent option
      if (!options.silent) {
        this.component.setState({
          isRequesting: true,
          hasError: false,
          isInvalid: false
        });
      }
    },Sets this.state when a model/collection syncs. It delegates to this.setState.
It listens to Backbone.Model#sync and Backbone.Collection#sync
    onSync: function (modelOrCollection, res, options) {Calls setState only if there’s no silent option
      if (!options.silent) {
        this.component.setState({isRequesting: false});
      }
    },Check if models is a Backbone.Model or an hashmap of them, sets them
to the component state and binds to update on any future changes
    setModels: function (models, initialState, isDeferred) {
      var isValid = typeof models !== 'undefined';
      if (isValid) {
        if (!models.attributes) {
          if (typeof models === 'object') {
            var _values = _.values(models);
            isValid = _values.length > 0 && _values[0].attributes;
          } else {
            isValid = false;
          }
        }
      }
      if (isValid) {
        this.model = models;Set model(s) attributes on initialState for the first render
        this.setStateBackbone(models, void 0, initialState, isDeferred);
        this.startModelListeners(models);
      }
    },Check if collections is a Backbone.Model or an hashmap of them,
sets them to the component state and binds to update on any future changes
    setCollections: function (collections, initialState, isDeferred) {
      if (typeof collections !== 'undefined' && (collections.models ||
          typeof collections === 'object' && _.values(collections)[0].models)) {The collection(s) bound to this component
        this.collection = collections;Set collection(s) models on initialState for the first render
        this.setStateBackbone(collections, void 0, initialState, isDeferred);
        this.startCollectionListeners(collections);
      }
    },Used internally to set this.collection or this.model on this.state. Delegates to
this.setState. It listens to Backbone.Collection events such as update,
change, sort, reset and to Backbone.Model change.
    setStateBackbone: function (modelOrCollection, key, target, isDeferred) {
      if (!(modelOrCollection.models || modelOrCollection.attributes)) {
        for (key in modelOrCollection)
            this.setStateBackbone(modelOrCollection[key], key, target);
        return;
      }
      this.setState.apply(this, arguments);
    },Get the attributes for the collection or model as array or hash
    getAttributes: function (modelOrCollection){
      var attrs = [];if a collection, get the attributes of each, otherwise return modelOrCollection
      if (modelOrCollection instanceof Backbone.Collection) {
        for (var i = 0; i < modelOrCollection.models.length; i++) {
          attrs.push(modelOrCollection.models[i].attributes);
        }
        return attrs;
      } else {
        return modelOrCollection.attributes
      }
    },Sets a model, collection or object into state by delegating to this.component.setState.
    setState: function (modelOrCollection, key, target, isDeferred) {
      var state = {};
      var newState = this.getAttributes(modelOrCollection);
      if (key) {
        state[key] = newState;
      } else if (modelOrCollection.models) {
        state.collection = newState;
      } else {
        state.model = newState;
      }
      if (target) {
        _.extend(target, state);
      } else if (isDeferred) {
        this.nextState = _.extend(this.nextState || {}, state);
        _.defer(_.bind(function () {
          if (this.nextState) {
            this.component.setState(this.nextState);
            this.nextState = null;
          }
        }, this));
      } else {
        this.component.setState(state);
      }
    },Binds the component to any collection changes.
    startCollectionListeners: function (collection, key) {
      if (!collection) collection = this.collection;
      if (collection) {
        if (collection.models)
          this
            .listenTo(collection, 'update change sort reset',
              _.partial(this.setStateBackbone, collection, key, void 0, true))
            .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.setStateBackbone, model, key, void 0, true))
            .listenTo(model, 'error', this.onError)
            .listenTo(model, 'request', this.onRequest)
            .listenTo(model, 'sync', this.onSync)
            .listenTo(model, 'invalid', this.onInvalid);
        else if (typeof model === 'object')
          for (key in model)
            this.startModelListeners(model[key], key);
      }
    }
  });Facade method to bypass the mixin usage. For use cases such as ES6
classes or else. It binds any Backbone.Model and Backbone.Collection
instance found inside backboneInstances.models and
backboneInstances.collections (single instances or objects of them)
  mixin.on = function (component, backboneInstances) {
    var wrapper;
    if (!component.wrapper) {
      wrapper = new Wrapper(component);
    } else {
      wrapper = component.wrapper;
    }
    if (backboneInstances.models) {
      wrapper.setModels(backboneInstances.models);
    }
    if (backboneInstances.collections) {
      wrapper.setCollections(backboneInstances.collections);
    }
    component.wrapper = wrapper;
  };Shortcut method to bind a model or multiple models
  mixin.onModel = function (component, models) {
    mixin.on(component, {models: models});
  };Shortcut method to bind a collection or multiple collections
  mixin.onCollection = function (component, collections) {
    mixin.on(component, {collections: collections});
  };Facade method to dispose of a component.wrapper
  mixin.off = function (component, modelOrCollection) {
    if (arguments.length === 2) {
      if (component.wrapper) {
        component.wrapper.stopListening(modelOrCollection);TODO Remove modelOrCollection from component.state?
      }
    } else {
      mixin.componentWillUnmount.call(component);
    }
  };Expose Backbone.React.Component.mixin.
  return mixin;
}));