RoundingWellOS/marionette.toolkit

View on GitHub
src/mixins/state.js

Summary

Maintainability
A
0 mins
Test Coverage
import _ from 'underscore';
import Backbone from 'backbone';

const ClassOptions = [
  'StateModel',
  'stateEvents'
];

/**
 * This provides methods used for keeping state using a Backbone.Model. It's meant to
 * be used with either a Marionette.MnObject or Backbone.View.
 *
 * @mixin
 */
export default {

  /**
   * The model class for _stateModel.
   * @type {Backbone.Model}
   * @default Backbone.Model
   */
  StateModel: Backbone.Model,

  /**
   * @public
   * @method initState
   * @param {Object} [options] - Settings for the StateMixin.
   * @param {Object} [options.stateEvents] - Event hash bound from _stateModel to StateMixin.
   * @param {Backbone.Model} [options.StateModel] - Model class for _stateModel.
   */
  initState(options = {}) {
    this._initState(options);
    this.delegateStateEvents();

    return this;
  },

  /**
   * @private
   * @method _initState
   * @param {Object} [options] - Settings for the StateMixin.
   */
  _initState(options) {
    // Make defaults available to this
    this.mergeOptions(options, ClassOptions);

    // Remove event handlers from previous state
    this._removeEventHandlers();

    const StateModel = this._getStateModel(options);

    this._stateModel = new StateModel(options.state);

    this._setEventHandlers();
  },

  /**
   * Bind events from the _stateModel defined in stateEvents hash
   *
   * @public
   * @method delegateStateEvents
   */
  delegateStateEvents() {
    this.undelegateStateEvents();
    this.bindEvents(this._stateModel, _.result(this, 'stateEvents'));

    return this;
  },

  /**
   * Unbind all entity events on _stateModel
   *
   * @public
   * @method undelegateStateEvents
   */
  undelegateStateEvents() {
    this.unbindEvents(this._stateModel);

    return this;
  },

  /**
   * Setup destroy event handle
   *
   * @private
   * @method _setEventHandlers
   */
  _setEventHandlers() {
    this.on('destroy', this._destroyState);
  },

  /**
   * Clean up destroy event handler, remove any listeners on _stateModel
   *
   * @private
   * @method _removeEventHandlers
   */
  _removeEventHandlers() {
    if (!this._stateModel) {return;}

    this.undelegateStateEvents();
    this._stateModel.stopListening();
    this.off('destroy', this._destroyState);
  },


  /**
   * Get the StateMixin StateModel class.
   * Checks if the `StateModel` is a model class (the common case)
   * Then check if it's a function (which we assume that returns a model class)
   *
   * @private
   * @method _getStateModel
   * @param {Object} [options] - Options that can be used to determine the StateModel.
   * @returns {Backbone.Model}
   */
  _getStateModel(options) {
    if (this.StateModel.prototype instanceof Backbone.Model || this.StateModel === Backbone.Model) {
      return this.StateModel;
    } else if (_.isFunction(this.StateModel)) {
      return this.StateModel.call(this, options);
    }

    throw new Error('"StateModel" must be a model class or a function that returns a model class');
  },

  /**
   * Set a property on the _stateModel.
   *
   * @public
   * @method setState
   * @param {String|Object} key - Attribute name or Hash of any number of key value pairs.
   * @param {*} [value] - Attribute value if key is String, replaces options param otherwise.
   * @param {Object} [options] - Backbone.Model options.
   * @returns {Backbone.Model} - The _stateModel
   */
  setState() {
    return this._stateModel.set.apply(this._stateModel, arguments);
  },


  /**
   *  Reset _stateModel to defined defaults
   *
   * @public
   * @method resetStateDefaults
   * @param {Object} [newState] - Hash of any number of key value pairs.
   * @returns {Backbone.Model|*} - The _stateModel or the attribute value of the _stateModel
   */
  resetStateDefaults() {
    const defaults = _.result(this._stateModel, 'defaults');

    return this._stateModel.set(defaults);
  },

  /**
   * Get a property from the _stateModel, or return the _stateModel
   *
   * @public
   * @method getState
   * @param {String} [attr] - Attribute name of stateModel.
   * @returns {Backbone.Model|*} - The _stateModel or the attribute value of the _stateModel
   */
  getState(attr) {
    if (!attr) {
      return this._stateModel;
    }

    return this._stateModel.get.apply(this._stateModel, arguments);
  },

  /**
   * Toggle a property on the _stateModel.
   *
   * @public
   * @method toggleState
   * @param {String} attr - Attribute name of stateModel.
   * @param {val} [value] - Attribute value.
   * @returns {Backbone.Model} - The _stateModel or attribute value.
   */
  toggleState(attr, val) {
    if (arguments.length > 1) {return this._stateModel.set(attr, !!val);}

    return this._stateModel.set(attr, !this._stateModel.get(attr));
  },

  /**
   * Check if _stateModel has a property
   *
   * @public
   * @method hasState
   * @param {String} [attr] - Attribute name of stateModel.
   * @returns {Boolean}
   */
  hasState(attr) {
    return this._stateModel.has(attr);
  },

  /**
   * Clean up any listeners on the _stateModel.
   *
   * @private
   * @method _destroyState
   */
  _destroyState() {
    this._stateModel.stopListening();
  }
};