API Docs for: 0.9.3
Show:

File: scripts/lateralus.component.view.js

define([

  'jquery'
  ,'underscore'
  ,'backbone'
  ,'mustache'

  ,'./lateralus.mixins'

], function (

  $
  ,_
  ,Backbone
  ,Mustache

  ,mixins

) {
  'use strict';

  var fn = {};

  /**
   * The DOM template to be used with this View.
   * @type {string|null}
   * @default {null}
   */
  fn.template = null;

  // jshint maxlen:100
  /**
   * The constructor for this class should not be called by application code,
   * it is used by the `{{#crossLink "Lateralus.Component"}}{{/crossLink}}`
   * constructor.
   * @private
   * @param {Lateralus} lateralus
   * @param {Lateralus.Component} component
   * @param {Object} [options] Gets passed to
   * [Backbone.View#initialize](http://backbonejs.org/#Collection-constructor).
   * @param {Lateralus.Component.View} [opt_parentView]
   * @uses Lateralus.mixins
   * @constructor
   */
  fn.constructor = function (lateralus, component, options, opt_parentView) {
    this.lateralus = lateralus;

    /**
     * If this is a subview of another `{{#crossLink
     * "Lateralus.Component.View"}}{{/crossLink}}`, this property is a
     * reference to the parent `{{#crossLink
     * "Lateralus.Component.View"}}{{/crossLink}}`.
     * @property parentView
     * @type {Lateralus.Component.View|null}
     * @default null
     */
    this.parentView = opt_parentView || null;

    /**
     * A reference to the `{{#crossLink "Lateralus.Component"}}{{/crossLink}}`
     * to which this `{{#crossLink "Lateralus.Component.View"}}{{/crossLink}}`
     * belongs.
     * @property component
     * @type {Lateralus.Component}
     * @final
     */
    this.component = component;

    if (options.model) {
      // Attach the model a bit early here so that the modelEvents map is
      // properly bound in the delegateLateralusEvents call below.
      this.model = options.model;
    }

    this.delegateLateralusEvents();
    Backbone.View.call(this, options);
  };

  /**
   * This is called when a `{{#crossLink
   * "Lateralus.Component.View"}}{{/crossLink}}` is initialized, it is not
   * called directly.
   *
   * `{{#crossLink "Lateralus.Component.View"}}{{/crossLink}}` subclasses that
   * override `initialize` must call this base method:
   *
   *     var Base = Lateralus.Component.View;
   *     var baseProto = Base.prototype;
   *
   *     var ExtendedComponentView = Base.extend({
   *       initialize: function () {
   *         baseProto.initialize.apply(this, arguments);
   *         // Other logic...
   *       }
   *     });
   * @method initialize
   * @param {Object} [opts] Any properties or methods to attach to this
   * `{{#crossLink "Lateralus.Component.View"}}{{/crossLink}}` instance.
   */
  fn.initialize = function (opts) {
    // this.toString references the central Component constructor, so don't
    // attach the class for it here.
    if (!this.parentView) {
      this.$el.addClass(this.toString());
    }

    /**
     * The CSS class names specified by this property will be attached to `$el`
     * when this `{{#crossLink "Lateralus.Component.View"}}{{/crossLink}}` is
     * initialized.
     * @property className
     * @type {string|undefined}
     * @default undefined
     */
    if (this.className) {
      this.$el.addClass(this.className);
    }

    _.extend(this, _.defaults(_.clone(opts), this.attachDefaultOptions));
    this.renderTemplate();

    /**
     * A function to be called in the JavaScript thread that follows the one
     * that ran `{{#crossLink
     * "Lateralus.Component.View/initialize:method"}}{{/crossLink}}`.  This can
     * be necessary for situations where setup logic needs to happen after a
     * View has been rendered.
     *
     * In other words, `{{#crossLink
     * "Lateralus.Component.View/initialize:method"}}{{/crossLink}}` runs
     * before the View has been rendered to the DOM, and `{{#crossLink
     * "Lateralus.Component.View/deferredInitialize:method"}}{{/crossLink}}`
     * runs immediately after it has been rendered.
     * @method deferredInitialize
     */
    if (this.deferredInitialize) {
      _.defer(_.bind(this.deferredInitialize, this));
    }
  };

  /**
   * Meant to be overridden in subclasses.  With `attachDefaultOptions`, you
   * can provide an object of default parameters that should be attached to the
   * View instance when the base `initialize` method is called.  These values
   * can be overridden by the [`options` values that are provided to the View
   * constructor](http://backbonejs.org/#View-constructor).
   * @property attachDefaultOptions
   * @type {Object}
   */
  fn.attachDefaultOptions = {};

  /**
   * Adds a subview.  Subviews are lighter than subcomponents.  It is
   * preferable to use a subview rather than a subcomponent when there is clear
   * interdependency between two Views.  This pattern is useful when you want
   * to keep display logic well-organized into several Views, but have it
   * compartmentalized within a single component.
   * @method addSubview
   * @param {Lateralus.Component.View} Subview A constructor, not an instance.
   * @param {Object} [subviewOptions] Backbone.View [constructor
   * options](http://backbonejs.org/#View-constructor) to pass along to the
   * subview when it is instantiated.
   * @return {Lateralus.Component.View} The instantiated subview.
   */
  fn.addSubview = function (Subview, subviewOptions) {
    if (!this.subviews) {
      /**
       * The subviews of this object.  Do not modify this property directly, it
       * is managed by Lateralus.
       * @property subviews
       * @type {Array(Lateralus.Component.View)}
       */
      this.subviews = [];
    }

    var subview = new Subview(
      this.lateralus
      ,this.component
      ,subviewOptions
      ,this
    );

    this.subviews.push(subview);

    return subview;
  };

  /**
   * This method returns the object whose properties are used as render
   * variables in `{{#crossLink
   * "Lateralus.Component.View/renderTemplate"}}{{/crossLink}}`.  The method
   * can be overridden.
   * @method getTemplateRenderData
   * @return {Object} The [raw `Backbone.Model`
   * data](http://backbonejs.org/#Model-toJSON), if this View has a Model.
   * Otherwise, an empty object is returned.
   */
  fn.getTemplateRenderData = function () {
    var renderData = {};

    if (this.model) {
      _.extend(renderData, this.model.toJSON());
    }

    _.extend(renderData, this.lateralus.globalRenderData);

    return renderData;
  };

  fn.getTemplatePartials = function () {
    /**
     * An optional map of template partials to be passed to the
     * `Mustache.render` call for this View.
     *
     *     Lateralus.Component.View.extend({
     *       templatePartials: {
     *         myNamePartial: 'Hello my name is {{name}}.'
     *       }
     *     });
     *
     * @property templatePartials
     * @type {Object(String)|undefined}
     * @default undefined
     */
    return _.extend(this.templatePartials || {}, this.lateralus.globalPartials);
  };

  /**
   * Meant to be called by `{{#crossLink
   * "Lateralus.Component.View/initialize"}}{{/crossLink}}` and infrequently
   * thereafter, this method empties out
   * [`$el`](http://backbonejs.org/#View-$el) and does a full re-render.
   * [`render`](http://backbonejs.org/#View-render) should only be used for
   * partial renders.
   * @method renderTemplate
   */
  fn.renderTemplate = function () {
    if (!this.template) {
      return;
    }

    this.$el.children().remove();
    this.$el.html(
      Mustache.render(
        this.template
        ,this.getTemplateRenderData()
        ,this.getTemplatePartials()
      )
    );

    this.bindToDOM();
  };

  /**
   * Look for any DOM elements within [`$el`](http://backbonejs.org/#View-$el)
   * that have a class that looks like _`$this`_ and create a property on this
   * instance with the same name.  The attached property is a jQuery object
   * that references the corresponding DOM element.
   * @method bindToDOM
   * @private
   */
  fn.bindToDOM = function () {
    this.$el.find('[class]').each(_.bind(function (i, el) {
      var $el = $(el);
      var classes = $el.attr('class').split(/\s+/);

      _.each(classes, function (_class) {
        if (_class.match(/^\$/)) {
          this[_class] = $el;
          return false;
        }
      }, this);
    }, this));
  };

  /**
   * Remove this `{{#crossLink "Lateralus.Component.View"}}{{/crossLink}}` from
   * the DOM and cleanly dispose of any references.
   * @method dispose
   * @chainable
   */
  fn.dispose = function () {
    this.remove();

    var parentView = this.parentView;
    if (parentView) {
      parentView.subviews = _.without(parentView.subviews, this);
    }

    return this;
  };

  _.extend(fn, mixins);

  /**
   * This class builds on the ideas and APIs of
   * [`Backbone.View`](http://backbonejs.org/#View).
   * @class Lateralus.Component.View
   * @extends {Backbone.View}
   */
  var ComponentView = Backbone.View.extend(fn);

  /**
   * @method toString
   * @return {string} The name of this View.  This is used internally by
   * Lateralus.
   */
  ComponentView.prototype.toString = function () {
    return this.component.toString() + '-view';
  };

  return ComponentView;
});