UNPKG

backbone.marionette

Version:
3,000 lines 85.6 kB
import Backbone from 'backbone';
import _ from 'underscore';
import Radio from 'backbone.radio';

var version = "4.1.3";

//Internal utility for creating context style global utils
var proxy = function proxy(method) {
  return function (context) {
    for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
      args[_key - 1] = arguments[_key];
    }

    return method.apply(context, args);
  };
};

// Marionette.extend

var extend = Backbone.Model.extend;

// ----------------------
// Pass in a mapping of events => functions or function names
// and return a mapping of events => functions

var normalizeMethods = function normalizeMethods(hash) {
  var _this = this;

  if (!hash) {
    return;
  }

  return _.reduce(hash, function (normalizedHash, method, name) {
    if (!_.isFunction(method)) {
      method = _this[method];
    }

    if (method) {
      normalizedHash[name] = method;
    }

    return normalizedHash;
  }, {});
};

// Error
var errorProps = ['description', 'fileName', 'lineNumber', 'name', 'message', 'number', 'url'];
var MarionetteError = extend.call(Error, {
  urlRoot: "http://marionettejs.com/docs/v".concat(version, "/"),
  url: '',
  constructor: function constructor(options) {
    var error = Error.call(this, options.message);

    _.extend(this, _.pick(error, errorProps), _.pick(options, errorProps));

    if (Error.captureStackTrace) {
      this.captureStackTrace();
    }

    this.url = this.urlRoot + this.url;
  },
  captureStackTrace: function captureStackTrace() {
    Error.captureStackTrace(this, MarionetteError);
  },
  toString: function toString() {
    return "".concat(this.name, ": ").concat(this.message, " See: ").concat(this.url);
  }
});

// Bind Entity Events & Unbind Entity Events

function normalizeBindings(context, bindings) {
  if (!_.isObject(bindings)) {
    throw new MarionetteError({
      message: 'Bindings must be an object.',
      url: 'common.html#bindevents'
    });
  }

  return normalizeMethods.call(context, bindings);
}

function bindEvents(entity, bindings) {
  if (!entity || !bindings) {
    return this;
  }

  this.listenTo(entity, normalizeBindings(this, bindings));
  return this;
}

function unbindEvents(entity, bindings) {
  if (!entity) {
    return this;
  }

  if (!bindings) {
    this.stopListening(entity);
    return this;
  }

  this.stopListening(entity, normalizeBindings(this, bindings));
  return this;
} // Export Public API

// Bind/Unbind Radio Requests

function normalizeBindings$1(context, bindings) {
  if (!_.isObject(bindings)) {
    throw new MarionetteError({
      message: 'Bindings must be an object.',
      url: 'common.html#bindrequests'
    });
  }

  return normalizeMethods.call(context, bindings);
}

function bindRequests(channel, bindings) {
  if (!channel || !bindings) {
    return this;
  }

  channel.reply(normalizeBindings$1(this, bindings), this);
  return this;
}

function unbindRequests(channel, bindings) {
  if (!channel) {
    return this;
  }

  if (!bindings) {
    channel.stopReplying(null, null, this);
    return this;
  }

  channel.stopReplying(normalizeBindings$1(this, bindings), this);
  return this;
}

// Marionette.getOption
// --------------------
// Retrieve an object, function or other value from the
// object or its `options`, with `options` taking precedence.
var getOption = function getOption(optionName) {
  if (!optionName) {
    return;
  }

  if (this.options && this.options[optionName] !== undefined) {
    return this.options[optionName];
  } else {
    return this[optionName];
  }
};

var mergeOptions = function mergeOptions(options, keys) {
  var _this = this;

  if (!options) {
    return;
  }

  _.each(keys, function (key) {
    var option = options[key];

    if (option !== undefined) {
      _this[key] = option;
    }
  });
};

// DOM Refresh

function triggerMethodChildren(view, event, shouldTrigger) {
  if (!view._getImmediateChildren) {
    return;
  }

  _.each(view._getImmediateChildren(), function (child) {
    if (!shouldTrigger(child)) {
      return;
    }

    child.triggerMethod(event, child);
  });
}

function shouldTriggerAttach(view) {
  return !view._isAttached;
}

function shouldAttach(view) {
  if (!shouldTriggerAttach(view)) {
    return false;
  }

  view._isAttached = true;
  return true;
}

function shouldTriggerDetach(view) {
  return view._isAttached;
}

function shouldDetach(view) {
  view._isAttached = false;
  return true;
}

function triggerDOMRefresh(view) {
  if (view._isAttached && view._isRendered) {
    view.triggerMethod('dom:refresh', view);
  }
}

function triggerDOMRemove(view) {
  if (view._isAttached && view._isRendered) {
    view.triggerMethod('dom:remove', view);
  }
}

function handleBeforeAttach() {
  triggerMethodChildren(this, 'before:attach', shouldTriggerAttach);
}

function handleAttach() {
  triggerMethodChildren(this, 'attach', shouldAttach);
  triggerDOMRefresh(this);
}

function handleBeforeDetach() {
  triggerMethodChildren(this, 'before:detach', shouldTriggerDetach);
  triggerDOMRemove(this);
}

function handleDetach() {
  triggerMethodChildren(this, 'detach', shouldDetach);
}

function handleBeforeRender() {
  triggerDOMRemove(this);
}

function handleRender() {
  triggerDOMRefresh(this);
} // Monitor a view's state, propagating attach/detach events to children and firing dom:refresh
// whenever a rendered view is attached or an attached view is rendered.


function monitorViewEvents(view) {
  if (view._areViewEventsMonitored || view.monitorViewEvents === false) {
    return;
  }

  view._areViewEventsMonitored = true;
  view.on({
    'before:attach': handleBeforeAttach,
    'attach': handleAttach,
    'before:detach': handleBeforeDetach,
    'detach': handleDetach,
    'before:render': handleBeforeRender,
    'render': handleRender
  });
}

// Trigger Method

var splitter = /(^|:)(\w)/gi; // Only calc getOnMethodName once

var methodCache = {}; // take the event section ("section1:section2:section3")
// and turn it in to uppercase name onSection1Section2Section3

function getEventName(match, prefix, eventName) {
  return eventName.toUpperCase();
}

var getOnMethodName = function getOnMethodName(event) {
  if (!methodCache[event]) {
    methodCache[event] = 'on' + event.replace(splitter, getEventName);
  }

  return methodCache[event];
}; // Trigger an event and/or a corresponding method name. Examples:
//
// `this.triggerMethod("foo")` will trigger the "foo" event and
// call the "onFoo" method.
//
// `this.triggerMethod("foo:bar")` will trigger the "foo:bar" event and
// call the "onFooBar" method.


function triggerMethod(event) {
  // get the method name from the event name
  var methodName = getOnMethodName(event);
  var method = getOption.call(this, methodName);
  var result; // call the onMethodName if it exists

  if (_.isFunction(method)) {
    // pass all args, except the event name
    result = method.apply(this, _.drop(arguments));
  } // trigger the event


  this.trigger.apply(this, arguments);
  return result;
}

var Events = {
  triggerMethod: triggerMethod
};

var CommonMixin = {
  // Imports the "normalizeMethods" to transform hashes of
  // events=>function references/names to a hash of events=>function references
  normalizeMethods: normalizeMethods,
  _setOptions: function _setOptions(options, classOptions) {
    this.options = _.extend({}, _.result(this, 'options'), options);
    this.mergeOptions(options, classOptions);
  },
  // A handy way to merge passed-in options onto the instance
  mergeOptions: mergeOptions,
  // Enable getting options from this or this.options by name.
  getOption: getOption,
  // Enable binding view's events from another entity.
  bindEvents: bindEvents,
  // Enable unbinding view's events from another entity.
  unbindEvents: unbindEvents,
  // Enable binding view's requests.
  bindRequests: bindRequests,
  // Enable unbinding view's requests.
  unbindRequests: unbindRequests,
  triggerMethod: triggerMethod
};

_.extend(CommonMixin, Backbone.Events);

var DestroyMixin = {
  _isDestroyed: false,
  isDestroyed: function isDestroyed() {
    return this._isDestroyed;
  },
  destroy: function destroy(options) {
    if (this._isDestroyed) {
      return this;
    }

    this.triggerMethod('before:destroy', this, options);
    this._isDestroyed = true;
    this.triggerMethod('destroy', this, options);
    this.stopListening();
    return this;
  }
};

// - channelName
// - radioEvents
// - radioRequests

var RadioMixin = {
  _initRadio: function _initRadio() {
    var channelName = _.result(this, 'channelName');

    if (!channelName) {
      return;
    }
    /* istanbul ignore next */


    if (!Radio) {
      throw new MarionetteError({
        message: 'The dependency "backbone.radio" is missing.',
        url: 'backbone.radio.html#marionette-integration'
      });
    }

    var channel = this._channel = Radio.channel(channelName);

    var radioEvents = _.result(this, 'radioEvents');

    this.bindEvents(channel, radioEvents);

    var radioRequests = _.result(this, 'radioRequests');

    this.bindRequests(channel, radioRequests);
    this.on('destroy', this._destroyRadio);
  },
  _destroyRadio: function _destroyRadio() {
    this._channel.stopReplying(null, null, this);
  },
  getChannel: function getChannel() {
    return this._channel;
  }
};

// Object
var ClassOptions = ['channelName', 'radioEvents', 'radioRequests']; // Object borrows many conventions and utilities from Backbone.

var MarionetteObject = function MarionetteObject(options) {
  this._setOptions(options, ClassOptions);

  this.cid = _.uniqueId(this.cidPrefix);

  this._initRadio();

  this.initialize.apply(this, arguments);
};

MarionetteObject.extend = extend; // Object Methods
// --------------

_.extend(MarionetteObject.prototype, CommonMixin, DestroyMixin, RadioMixin, {
  cidPrefix: 'mno',
  // This is a noop method intended to be overridden
  initialize: function initialize() {}
});

// Implementation of the invoke method (http://underscorejs.org/#invoke) with support for
var _invoke = _.invokeMap || _.invoke;

// - behaviors
// Takes care of getting the behavior class
// given options and a key.
// If a user passes in options.behaviorClass
// default to using that.
// If a user passes in a Behavior Class directly, use that
// Otherwise an error is thrown

function getBehaviorClass(options) {
  if (options.behaviorClass) {
    return {
      BehaviorClass: options.behaviorClass,
      options: options
    };
  } //treat functions as a Behavior constructor


  if (_.isFunction(options)) {
    return {
      BehaviorClass: options,
      options: {}
    };
  }

  throw new MarionetteError({
    message: 'Unable to get behavior class. A Behavior constructor should be passed directly or as behaviorClass property of options',
    url: 'marionette.behavior.html#defining-and-attaching-behaviors'
  });
} // Iterate over the behaviors object, for each behavior
// instantiate it and get its grouped behaviors.
// This accepts a list of behaviors in either an object or array form


function parseBehaviors(view, behaviors, allBehaviors) {
  return _.reduce(behaviors, function (reducedBehaviors, behaviorDefiniton) {
    var _getBehaviorClass = getBehaviorClass(behaviorDefiniton),
        BehaviorClass = _getBehaviorClass.BehaviorClass,
        options = _getBehaviorClass.options;

    var behavior = new BehaviorClass(options, view);
    reducedBehaviors.push(behavior);
    return parseBehaviors(view, _.result(behavior, 'behaviors'), reducedBehaviors);
  }, allBehaviors);
}

var BehaviorsMixin = {
  _initBehaviors: function _initBehaviors() {
    this._behaviors = parseBehaviors(this, _.result(this, 'behaviors'), []);
  },
  _getBehaviorTriggers: function _getBehaviorTriggers() {
    var triggers = _invoke(this._behaviors, '_getTriggers');

    return _.reduce(triggers, function (memo, _triggers) {
      return _.extend(memo, _triggers);
    }, {});
  },
  _getBehaviorEvents: function _getBehaviorEvents() {
    var events = _invoke(this._behaviors, '_getEvents');

    return _.reduce(events, function (memo, _events) {
      return _.extend(memo, _events);
    }, {});
  },
  // proxy behavior $el to the view's $el.
  _proxyBehaviorViewProperties: function _proxyBehaviorViewProperties() {
    _invoke(this._behaviors, 'proxyViewProperties');
  },
  // delegate modelEvents and collectionEvents
  _delegateBehaviorEntityEvents: function _delegateBehaviorEntityEvents() {
    _invoke(this._behaviors, 'delegateEntityEvents');
  },
  // undelegate modelEvents and collectionEvents
  _undelegateBehaviorEntityEvents: function _undelegateBehaviorEntityEvents() {
    _invoke(this._behaviors, 'undelegateEntityEvents');
  },
  _destroyBehaviors: function _destroyBehaviors(options) {
    // Call destroy on each behavior after
    // destroying the view.
    // This unbinds event listeners
    // that behaviors have registered for.
    _invoke(this._behaviors, 'destroy', options);
  },
  // Remove a behavior
  _removeBehavior: function _removeBehavior(behavior) {
    // Don't worry about the clean up if the view is destroyed
    if (this._isDestroyed) {
      return;
    } // Remove behavior-only triggers and events


    this.undelegate(".trig".concat(behavior.cid, " .").concat(behavior.cid));
    this._behaviors = _.without(this._behaviors, behavior);
  },
  _bindBehaviorUIElements: function _bindBehaviorUIElements() {
    _invoke(this._behaviors, 'bindUIElements');
  },
  _unbindBehaviorUIElements: function _unbindBehaviorUIElements() {
    _invoke(this._behaviors, 'unbindUIElements');
  },
  _triggerEventOnBehaviors: function _triggerEventOnBehaviors(eventName, view, options) {
    _invoke(this._behaviors, 'triggerMethod', eventName, view, options);
  }
};

// - collectionEvents
// - modelEvents

var DelegateEntityEventsMixin = {
  // Handle `modelEvents`, and `collectionEvents` configuration
  _delegateEntityEvents: function _delegateEntityEvents(model, collection) {
    if (model) {
      this._modelEvents = _.result(this, 'modelEvents');
      this.bindEvents(model, this._modelEvents);
    }

    if (collection) {
      this._collectionEvents = _.result(this, 'collectionEvents');
      this.bindEvents(collection, this._collectionEvents);
    }
  },
  // Remove any previously delegate entity events
  _undelegateEntityEvents: function _undelegateEntityEvents(model, collection) {
    if (this._modelEvents) {
      this.unbindEvents(model, this._modelEvents);
      delete this._modelEvents;
    }

    if (this._collectionEvents) {
      this.unbindEvents(collection, this._collectionEvents);
      delete this._collectionEvents;
    }
  },
  // Remove cached event handlers
  _deleteEntityEventHandlers: function _deleteEntityEventHandlers() {
    delete this._modelEvents;
    delete this._collectionEvents;
  }
};

// - template
// - templateContext

var TemplateRenderMixin = {
  // Internal method to render the template with the serialized data
  // and template context
  _renderTemplate: function _renderTemplate(template) {
    // Add in entity data and template context
    var data = this.mixinTemplateContext(this.serializeData()) || {}; // Render and add to el

    var html = this._renderHtml(template, data);

    if (typeof html !== 'undefined') {
      this.attachElContent(html);
    }
  },
  // Get the template for this view instance.
  // You can set a `template` attribute in the view definition
  // or pass a `template: TemplateFunction` parameter in
  // to the constructor options.
  getTemplate: function getTemplate() {
    return this.template;
  },
  // Mix in template context methods. Looks for a
  // `templateContext` attribute, which can either be an
  // object literal, or a function that returns an object
  // literal. All methods and attributes from this object
  // are copies to the object passed in.
  mixinTemplateContext: function mixinTemplateContext(serializedData) {
    var templateContext = _.result(this, 'templateContext');

    if (!templateContext) {
      return serializedData;
    }

    if (!serializedData) {
      return templateContext;
    }

    return _.extend({}, serializedData, templateContext);
  },
  // Serialize the view's model *or* collection, if
  // it exists, for the template
  serializeData: function serializeData() {
    // If we have a model, we serialize that
    if (this.model) {
      return this.serializeModel();
    } // Otherwise, we serialize the collection,
    // making it available under the `items` property


    if (this.collection) {
      return {
        items: this.serializeCollection()
      };
    }
  },
  // Prepares the special `model` property of a view
  // for being displayed in the template. Override this if
  // you need a custom transformation for your view's model
  serializeModel: function serializeModel() {
    return this.model.attributes;
  },
  // Serialize a collection
  serializeCollection: function serializeCollection() {
    return _.map(this.collection.models, function (model) {
      return model.attributes;
    });
  },
  // Renders the data into the template
  _renderHtml: function _renderHtml(template, data) {
    return template(data);
  },
  // Attaches the content of a given view.
  // This method can be overridden to optimize rendering,
  // or to render in a non standard way.
  //
  // For example, using `innerHTML` instead of `$el.html`
  //
  // ```js
  // attachElContent(html) {
  //   this.el.innerHTML = html;
  // }
  // ```
  attachElContent: function attachElContent(html) {
    this.Dom.setContents(this.el, html, this.$el);
  }
};

// Borrow event splitter from Backbone
var delegateEventSplitter = /^(\S+)\s*(.*)$/; // Set event name to be namespaced using a unique index
// to generate a non colliding event namespace
// http://api.jquery.com/event.namespace/

var getNamespacedEventName = function getNamespacedEventName(eventName, namespace) {
  var match = eventName.match(delegateEventSplitter);
  return "".concat(match[1], ".").concat(namespace, " ").concat(match[2]);
};

// Add Feature flags here
// e.g. 'class' => false
var FEATURES = {
  childViewEventPrefix: false,
  triggersStopPropagation: true,
  triggersPreventDefault: true,
  DEV_MODE: false
};

function isEnabled(name) {
  return !!FEATURES[name];
}

function setEnabled(name, state) {
  return FEATURES[name] = state;
}

// 'click:foo'

function buildViewTrigger(view, triggerDef) {
  if (_.isString(triggerDef)) {
    triggerDef = {
      event: triggerDef
    };
  }

  var eventName = triggerDef.event;
  var shouldPreventDefault = !!triggerDef.preventDefault;

  if (isEnabled('triggersPreventDefault')) {
    shouldPreventDefault = triggerDef.preventDefault !== false;
  }

  var shouldStopPropagation = !!triggerDef.stopPropagation;

  if (isEnabled('triggersStopPropagation')) {
    shouldStopPropagation = triggerDef.stopPropagation !== false;
  }

  return function (event) {
    if (shouldPreventDefault) {
      event.preventDefault();
    }

    if (shouldStopPropagation) {
      event.stopPropagation();
    }

    for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
      args[_key - 1] = arguments[_key];
    }

    view.triggerMethod.apply(view, [eventName, view, event].concat(args));
  };
}

var TriggersMixin = {
  // Configure `triggers` to forward DOM events to view
  // events. `triggers: {"click .foo": "do:foo"}`
  _getViewTriggers: function _getViewTriggers(view, triggers) {
    var _this = this;

    // Configure the triggers, prevent default
    // action and stop propagation of DOM events
    return _.reduce(triggers, function (events, value, key) {
      key = getNamespacedEventName(key, "trig".concat(_this.cid));
      events[key] = buildViewTrigger(view, value);
      return events;
    }, {});
  }
};

// a given key for triggers and events
// swaps the @ui with the associated selector.
// Returns a new, non-mutated, parsed events hash.

var _normalizeUIKeys = function normalizeUIKeys(hash, ui) {
  return _.reduce(hash, function (memo, val, key) {
    var normalizedKey = _normalizeUIString(key, ui);

    memo[normalizedKey] = val;
    return memo;
  }, {});
};

var uiRegEx = /@ui\.[a-zA-Z-_$0-9]*/g; // utility method for parsing @ui. syntax strings
// into associated selector

var _normalizeUIString = function normalizeUIString(uiString, ui) {
  return uiString.replace(uiRegEx, function (r) {
    return ui[r.slice(4)];
  });
}; // allows for the use of the @ui. syntax within
// a given value for regions
// swaps the @ui with the associated selector


var _normalizeUIValues = function normalizeUIValues(hash, ui, property) {
  _.each(hash, function (val, key) {
    if (_.isString(val)) {
      hash[key] = _normalizeUIString(val, ui);
    } else if (val) {
      var propertyVal = val[property];

      if (_.isString(propertyVal)) {
        val[property] = _normalizeUIString(propertyVal, ui);
      }
    }
  });

  return hash;
};

var UIMixin = {
  // normalize the keys of passed hash with the views `ui` selectors.
  // `{"@ui.foo": "bar"}`
  normalizeUIKeys: function normalizeUIKeys(hash) {
    var uiBindings = this._getUIBindings();

    return _normalizeUIKeys(hash, uiBindings);
  },
  // normalize the passed string with the views `ui` selectors.
  // `"@ui.bar"`
  normalizeUIString: function normalizeUIString(uiString) {
    var uiBindings = this._getUIBindings();

    return _normalizeUIString(uiString, uiBindings);
  },
  // normalize the values of passed hash with the views `ui` selectors.
  // `{foo: "@ui.bar"}`
  normalizeUIValues: function normalizeUIValues(hash, property) {
    var uiBindings = this._getUIBindings();

    return _normalizeUIValues(hash, uiBindings, property);
  },
  _getUIBindings: function _getUIBindings() {
    var uiBindings = _.result(this, '_uiBindings');

    return uiBindings || _.result(this, 'ui');
  },
  // This method binds the elements specified in the "ui" hash inside the view's code with
  // the associated jQuery selectors.
  _bindUIElements: function _bindUIElements() {
    var _this = this;

    if (!this.ui) {
      return;
    } // store the ui hash in _uiBindings so they can be reset later
    // and so re-rendering the view will be able to find the bindings


    if (!this._uiBindings) {
      this._uiBindings = this.ui;
    } // get the bindings result, as a function or otherwise


    var bindings = _.result(this, '_uiBindings'); // empty the ui so we don't have anything to start with


    this._ui = {}; // bind each of the selectors

    _.each(bindings, function (selector, key) {
      _this._ui[key] = _this.$(selector);
    });

    this.ui = this._ui;
  },
  _unbindUIElements: function _unbindUIElements() {
    var _this2 = this;

    if (!this.ui || !this._uiBindings) {
      return;
    } // delete all of the existing ui bindings


    _.each(this.ui, function ($el, name) {
      delete _this2.ui[name];
    }); // reset the ui element to the original bindings configuration


    this.ui = this._uiBindings;
    delete this._uiBindings;
    delete this._ui;
  },
  _getUI: function _getUI(name) {
    return this._ui[name];
  }
};

// DomApi

function _getEl(el) {
  return el instanceof Backbone.$ ? el : Backbone.$(el);
} // Static setter


function setDomApi(mixin) {
  this.prototype.Dom = _.extend({}, this.prototype.Dom, mixin);
  return this;
}
var DomApi = {
  // Returns a new HTML DOM node instance
  createBuffer: function createBuffer() {
    return document.createDocumentFragment();
  },
  // Returns the document element for a given DOM element
  getDocumentEl: function getDocumentEl(el) {
    return el.ownerDocument.documentElement;
  },
  // Lookup the `selector` string
  // Selector may also be a DOM element
  // Returns an array-like object of nodes
  getEl: function getEl(selector) {
    return _getEl(selector);
  },
  // Finds the `selector` string with the el
  // Returns an array-like object of nodes
  findEl: function findEl(el, selector) {
    return _getEl(el).find(selector);
  },
  // Returns true if the el contains the node childEl
  hasEl: function hasEl(el, childEl) {
    return el.contains(childEl && childEl.parentNode);
  },
  // Detach `el` from the DOM without removing listeners
  detachEl: function detachEl(el) {
    var _$el = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _getEl(el);

    _$el.detach();
  },
  // Remove `oldEl` from the DOM and put `newEl` in its place
  replaceEl: function replaceEl(newEl, oldEl) {
    if (newEl === oldEl) {
      return;
    }

    var parent = oldEl.parentNode;

    if (!parent) {
      return;
    }

    parent.replaceChild(newEl, oldEl);
  },
  // Swaps the location of `el1` and `el2` in the DOM
  swapEl: function swapEl(el1, el2) {
    if (el1 === el2) {
      return;
    }

    var parent1 = el1.parentNode;
    var parent2 = el2.parentNode;

    if (!parent1 || !parent2) {
      return;
    }

    var next1 = el1.nextSibling;
    var next2 = el2.nextSibling;
    parent1.insertBefore(el2, next1);
    parent2.insertBefore(el1, next2);
  },
  // Replace the contents of `el` with the HTML string of `html`
  setContents: function setContents(el, html) {
    var _$el = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : _getEl(el);

    _$el.html(html);
  },
  // Takes the DOM node `el` and appends the DOM node `contents`
  // to the end of the element's contents.
  appendContents: function appendContents(el, contents) {
    var _ref = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {},
        _ref$_$el = _ref._$el,
        _$el = _ref$_$el === void 0 ? _getEl(el) : _ref$_$el,
        _ref$_$contents = _ref._$contents,
        _$contents = _ref$_$contents === void 0 ? _getEl(contents) : _ref$_$contents;

    _$el.append(_$contents);
  },
  // Does the el have child nodes
  hasContents: function hasContents(el) {
    return !!el && el.hasChildNodes();
  },
  // Remove the inner contents of `el` from the DOM while leaving
  // `el` itself in the DOM.
  detachContents: function detachContents(el) {
    var _$el = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : _getEl(el);

    _$el.contents().detach();
  }
};

// ViewMixin
// - behaviors
// - childViewEventPrefix
// - childViewEvents
// - childViewTriggers
// - collectionEvents
// - modelEvents
// - triggers
// - ui

var ViewMixin = {
  Dom: DomApi,
  _isElAttached: function _isElAttached() {
    return !!this.el && this.Dom.hasEl(this.Dom.getDocumentEl(this.el), this.el);
  },
  supportsRenderLifecycle: true,
  supportsDestroyLifecycle: true,
  _isDestroyed: false,
  isDestroyed: function isDestroyed() {
    return !!this._isDestroyed;
  },
  _isRendered: false,
  isRendered: function isRendered() {
    return !!this._isRendered;
  },
  _isAttached: false,
  isAttached: function isAttached() {
    return !!this._isAttached;
  },
  // Overriding Backbone.View's `delegateEvents` to handle
  // `events` and `triggers`
  delegateEvents: function delegateEvents(events) {
    this._proxyBehaviorViewProperties();

    this._buildEventProxies();

    var combinedEvents = _.extend({}, this._getBehaviorEvents(), this._getEvents(events), this._getBehaviorTriggers(), this._getTriggers());

    Backbone.View.prototype.delegateEvents.call(this, combinedEvents);
    return this;
  },
  // Allows Backbone.View events to utilize `@ui.` selectors
  _getEvents: function _getEvents(events) {
    if (events) {
      return this.normalizeUIKeys(events);
    }

    if (!this.events) {
      return;
    }

    return this.normalizeUIKeys(_.result(this, 'events'));
  },
  // Configure `triggers` to forward DOM events to view
  // events. `triggers: {"click .foo": "do:foo"}`
  _getTriggers: function _getTriggers() {
    if (!this.triggers) {
      return;
    } // Allow `triggers` to be configured as a function


    var triggers = this.normalizeUIKeys(_.result(this, 'triggers')); // Configure the triggers, prevent default
    // action and stop propagation of DOM events

    return this._getViewTriggers(this, triggers);
  },
  // Handle `modelEvents`, and `collectionEvents` configuration
  delegateEntityEvents: function delegateEntityEvents() {
    this._delegateEntityEvents(this.model, this.collection); // bind each behaviors model and collection events


    this._delegateBehaviorEntityEvents();

    return this;
  },
  // Handle unbinding `modelEvents`, and `collectionEvents` configuration
  undelegateEntityEvents: function undelegateEntityEvents() {
    this._undelegateEntityEvents(this.model, this.collection); // unbind each behaviors model and collection events


    this._undelegateBehaviorEntityEvents();

    return this;
  },
  // Handle destroying the view and its children.
  destroy: function destroy(options) {
    if (this._isDestroyed || this._isDestroying) {
      return this;
    }

    this._isDestroying = true;
    var shouldTriggerDetach = this._isAttached && !this._disableDetachEvents;
    this.triggerMethod('before:destroy', this, options);

    if (shouldTriggerDetach) {
      this.triggerMethod('before:detach', this);
    } // unbind UI elements


    this.unbindUIElements(); // remove the view from the DOM

    this._removeElement();

    if (shouldTriggerDetach) {
      this._isAttached = false;
      this.triggerMethod('detach', this);
    } // remove children after the remove to prevent extra paints


    this._removeChildren();

    this._isDestroyed = true;
    this._isRendered = false; // Destroy behaviors after _isDestroyed flag

    this._destroyBehaviors(options);

    this._deleteEntityEventHandlers();

    this.triggerMethod('destroy', this, options);

    this._triggerEventOnBehaviors('destroy', this, options);

    this.stopListening();
    return this;
  },
  // Equates to this.$el.remove
  _removeElement: function _removeElement() {
    this.$el.off().removeData();
    this.Dom.detachEl(this.el, this.$el);
  },
  // This method binds the elements specified in the "ui" hash
  bindUIElements: function bindUIElements() {
    this._bindUIElements();

    this._bindBehaviorUIElements();

    return this;
  },
  // This method unbinds the elements specified in the "ui" hash
  unbindUIElements: function unbindUIElements() {
    this._unbindUIElements();

    this._unbindBehaviorUIElements();

    return this;
  },
  getUI: function getUI(name) {
    return this._getUI(name);
  },
  // Cache `childViewEvents` and `childViewTriggers`
  _buildEventProxies: function _buildEventProxies() {
    this._childViewEvents = this.normalizeMethods(_.result(this, 'childViewEvents'));
    this._childViewTriggers = _.result(this, 'childViewTriggers');
    this._eventPrefix = this._getEventPrefix();
  },
  _getEventPrefix: function _getEventPrefix() {
    var defaultPrefix = isEnabled('childViewEventPrefix') ? 'childview' : false;

    var prefix = _.result(this, 'childViewEventPrefix', defaultPrefix);

    return prefix === false ? prefix : prefix + ':';
  },
  _proxyChildViewEvents: function _proxyChildViewEvents(view) {
    if (this._childViewEvents || this._childViewTriggers || this._eventPrefix) {
      this.listenTo(view, 'all', this._childViewEventHandler);
    }
  },
  _childViewEventHandler: function _childViewEventHandler(eventName) {
    var childViewEvents = this._childViewEvents; // call collectionView childViewEvent if defined

    for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
      args[_key - 1] = arguments[_key];
    }

    if (childViewEvents && childViewEvents[eventName]) {
      childViewEvents[eventName].apply(this, args);
    } // use the parent view's proxyEvent handlers


    var childViewTriggers = this._childViewTriggers; // Call the event with the proxy name on the parent layout

    if (childViewTriggers && childViewTriggers[eventName]) {
      this.triggerMethod.apply(this, [childViewTriggers[eventName]].concat(args));
    }

    if (this._eventPrefix) {
      this.triggerMethod.apply(this, [this._eventPrefix + eventName].concat(args));
    }
  }
};

_.extend(ViewMixin, BehaviorsMixin, CommonMixin, DelegateEntityEventsMixin, TemplateRenderMixin, TriggersMixin, UIMixin);

function renderView(view) {
  if (view._isRendered) {
    return;
  }

  if (!view.supportsRenderLifecycle) {
    view.triggerMethod('before:render', view);
  }

  view.render();
  view._isRendered = true;

  if (!view.supportsRenderLifecycle) {
    view.triggerMethod('render', view);
  }
}
function destroyView(view, disableDetachEvents) {
  if (view.destroy) {
    // Attach flag for public destroy function internal check
    view._disableDetachEvents = disableDetachEvents;
    view.destroy();
    return;
  } // Destroy for non-Marionette Views


  if (!view.supportsDestroyLifecycle) {
    view.triggerMethod('before:destroy', view);
  }

  var shouldTriggerDetach = view._isAttached && !disableDetachEvents;

  if (shouldTriggerDetach) {
    view.triggerMethod('before:detach', view);
  }

  view.remove();

  if (shouldTriggerDetach) {
    view._isAttached = false;
    view.triggerMethod('detach', view);
  }

  view._isDestroyed = true;

  if (!view.supportsDestroyLifecycle) {
    view.triggerMethod('destroy', view);
  }
}

// Region
var classErrorName = 'RegionError';
var ClassOptions$1 = ['allowMissingEl', 'parentEl', 'replaceElement'];

var Region = function Region(options) {
  this._setOptions(options, ClassOptions$1);

  this.cid = _.uniqueId(this.cidPrefix); // getOption necessary because options.el may be passed as undefined

  this._initEl = this.el = this.getOption('el'); // Handle when this.el is passed in as a $ wrapped element.

  this.el = this.el instanceof Backbone.$ ? this.el[0] : this.el;
  this.$el = this._getEl(this.el);
  this.initialize.apply(this, arguments);
};

Region.extend = extend;
Region.setDomApi = setDomApi; // Region Methods
// --------------

_.extend(Region.prototype, CommonMixin, {
  Dom: DomApi,
  cidPrefix: 'mnr',
  replaceElement: false,
  _isReplaced: false,
  _isSwappingView: false,
  // This is a noop method intended to be overridden
  initialize: function initialize() {},
  // Displays a view instance inside of the region. If necessary handles calling the `render`
  // method for you. Reads content directly from the `el` attribute.
  show: function show(view, options) {
    if (!this._ensureElement(options)) {
      return;
    }

    view = this._getView(view, options);

    if (view === this.currentView) {
      return this;
    }

    if (view._isShown) {
      throw new MarionetteError({
        name: classErrorName,
        message: 'View is already shown in a Region or CollectionView',
        url: 'marionette.region.html#showing-a-view'
      });
    }

    this._isSwappingView = !!this.currentView;
    this.triggerMethod('before:show', this, view, options); // Assume an attached view is already in the region for pre-existing DOM

    if (this.currentView || !view._isAttached) {
      this.empty(options);
    }

    this._setupChildView(view);

    this.currentView = view;
    renderView(view);

    this._attachView(view, options);

    this.triggerMethod('show', this, view, options);
    this._isSwappingView = false;
    return this;
  },
  _getEl: function _getEl(el) {
    if (!el) {
      throw new MarionetteError({
        name: classErrorName,
        message: 'An "el" must be specified for a region.',
        url: 'marionette.region.html#additional-options'
      });
    }

    return this.getEl(el);
  },
  _setEl: function _setEl() {
    this.$el = this._getEl(this.el);

    if (this.$el.length) {
      this.el = this.$el[0];
    } // Make sure the $el contains only the el


    if (this.$el.length > 1) {
      this.$el = this.Dom.getEl(this.el);
    }
  },
  // Set the `el` of the region and move any current view to the new `el`.
  _setElement: function _setElement(el) {
    if (el === this.el) {
      return this;
    }

    var shouldReplace = this._isReplaced;

    this._restoreEl();

    this.el = el;

    this._setEl();

    if (this.currentView) {
      var view = this.currentView;

      if (shouldReplace) {
        this._replaceEl(view);
      } else {
        this.attachHtml(view);
      }
    }

    return this;
  },
  _setupChildView: function _setupChildView(view) {
    monitorViewEvents(view);

    this._proxyChildViewEvents(view); // We need to listen for if a view is destroyed in a way other than through the region.
    // If this happens we need to remove the reference to the currentView since once a view
    // has been destroyed we can not reuse it.


    view.on('destroy', this._empty, this);
  },
  _proxyChildViewEvents: function _proxyChildViewEvents(view) {
    var parentView = this._parentView;

    if (!parentView) {
      return;
    }

    parentView._proxyChildViewEvents(view);
  },
  // If the regions parent view is not monitoring its attach/detach events
  _shouldDisableMonitoring: function _shouldDisableMonitoring() {
    return this._parentView && this._parentView.monitorViewEvents === false;
  },
  _isElAttached: function _isElAttached() {
    return this.Dom.hasEl(this.Dom.getDocumentEl(this.el), this.el);
  },
  _attachView: function _attachView(view) {
    var _ref = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
        replaceElement = _ref.replaceElement;

    var shouldTriggerAttach = !view._isAttached && this._isElAttached() && !this._shouldDisableMonitoring();
    var shouldReplaceEl = typeof replaceElement === 'undefined' ? !!_.result(this, 'replaceElement') : !!replaceElement;

    if (shouldTriggerAttach) {
      view.triggerMethod('before:attach', view);
    }

    if (shouldReplaceEl) {
      this._replaceEl(view);
    } else {
      this.attachHtml(view);
    }

    if (shouldTriggerAttach) {
      view._isAttached = true;
      view.triggerMethod('attach', view);
    } // Corresponds that view is shown in a marionette Region or CollectionView


    view._isShown = true;
  },
  _ensureElement: function _ensureElement() {
    var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {};

    if (!_.isObject(this.el)) {
      this._setEl();
    }

    if (!this.$el || this.$el.length === 0) {
      var allowMissingEl = typeof options.allowMissingEl === 'undefined' ? !!_.result(this, 'allowMissingEl') : !!options.allowMissingEl;

      if (allowMissingEl) {
        return false;
      } else {
        throw new MarionetteError({
          name: classErrorName,
          message: "An \"el\" must exist in DOM for this region ".concat(this.cid),
          url: 'marionette.region.html#additional-options'
        });
      }
    }

    return true;
  },
  _getView: function _getView(view) {
    if (!view) {
      throw new MarionetteError({
        name: classErrorName,
        message: 'The view passed is undefined and therefore invalid. You must pass a view instance to show.',
        url: 'marionette.region.html#showing-a-view'
      });
    }

    if (view._isDestroyed) {
      throw new MarionetteError({
        name: classErrorName,
        message: "View (cid: \"".concat(view.cid, "\") has already been destroyed and cannot be used."),
        url: 'marionette.region.html#showing-a-view'
      });
    }

    if (view instanceof Backbone.View) {
      return view;
    }

    var viewOptions = this._getViewOptions(view);

    return new View(viewOptions);
  },
  // This allows for a template or a static string to be
  // used as a template
  _getViewOptions: function _getViewOptions(viewOptions) {
    if (_.isFunction(viewOptions)) {
      return {
        template: viewOptions
      };
    }

    if (_.isObject(viewOptions)) {
      return viewOptions;
    }

    var template = function template() {
      return viewOptions;
    };

    return {
      template: template
    };
  },
  // Override this method to change how the region finds the DOM element that it manages. Return
  // a jQuery selector object scoped to a provided parent el or the document if none exists.
  getEl: function getEl(el) {
    var context = _.result(this, 'parentEl');

    if (context && _.isString(el)) {
      return this.Dom.findEl(context, el);
    }

    return this.Dom.getEl(el);
  },
  _replaceEl: function _replaceEl(view) {
    // Always restore the el to ensure the regions el is present before replacing
    this._restoreEl();

    view.on('before:destroy', this._restoreEl, this);
    this.Dom.replaceEl(view.el, this.el);
    this._isReplaced = true;
  },
  // Restore the region's element in the DOM.
  _restoreEl: function _restoreEl() {
    // There is nothing to replace
    if (!this._isReplaced) {
      return;
    }

    var view = this.currentView;

    if (!view) {
      return;
    }

    this._detachView(view);

    this._isReplaced = false;
  },
  // Check to see if the region's el was replaced.
  isReplaced: function isReplaced() {
    return !!this._isReplaced;
  },
  // Check to see if a view is being swapped by another
  isSwappingView: function isSwappingView() {
    return !!this._isSwappingView;
  },
  // Override this method to change how the new view is appended to the `$el` that the
  // region is managing
  attachHtml: function attachHtml(view) {
    this.Dom.appendContents(this.el, view.el, {
      _$el: this.$el,
      _$contents: view.$el
    });
  },
  // Destroy the current view, if there is one. If there is no current view,
  // it will detach any html inside the region's `el`.
  empty: function empty() {
    var options = arguments.length > 0 && arguments[0] !== undefined ? arguments[0] : {
      allowMissingEl: true
    };
    var view = this.currentView; // If there is no view in the region we should only detach current html

    if (!view) {
      if (this._ensureElement(options)) {
        this.detachHtml();
      }

      return this;
    }

    this._empty(view, true);

    return this;
  },
  _empty: function _empty(view, shouldDestroy) {
    view.off('destroy', this._empty, this);
    this.triggerMethod('before:empty', this, view);

    this._restoreEl();

    delete this.currentView;

    if (!view._isDestroyed) {
      if (shouldDestroy) {
        this.removeView(view);
      } else {
        this._detachView(view);
      }

      view._isShown = false;

      this._stopChildViewEvents(view);
    }

    this.triggerMethod('empty', this, view);
  },
  _stopChildViewEvents: function _stopChildViewEvents(view) {
    var parentView = this._parentView;

    if (!parentView) {
      return;
    }

    this._parentView.stopListening(view);
  },
  // Non-Marionette safe view.destroy
  destroyView: function destroyView$1(view) {
    if (view._isDestroyed) {
      return view;
    }

    destroyView(view, this._shouldDisableMonitoring());

    return view;
  },
  // Override this method to determine what happens when the view
  // is removed from the region when the view is not being detached
  removeView: function removeView(view) {
    this.destroyView(view);
  },
  // Empties the Region without destroying the view
  // Returns the detached view
  detachView: function detachView() {
    var view = this.currentView;

    if (!view) {
      return;
    }

    this._empty(view);

    return view;
  },
  _detachView: function _detachView(view) {
    var shouldTriggerDetach = view._isAttached && !this._shouldDisableMonitoring();
    var shouldRestoreEl = this._isReplaced;

    if (shouldTriggerDetach) {
      view.triggerMethod('before:detach', view);
    }

    if (shouldRestoreEl) {
      this.Dom.replaceEl(this.el, view.el);
    } else {
      this.detachHtml();
    }

    if (shouldTriggerDetach) {
      view._isAttached = false;
      view.triggerMethod('detach', view);
    }
  },
  // Override this method to change how the region detaches current content
  detachHtml: function detachHtml() {
    this.Dom.detachContents(this.el, this.$el);
  },
  // Checks whether a view is currently present within the region. Returns `true` if there is
  // and `false` if no view is present.
  hasView: function hasView() {
    return !!this.currentView;
  },
  // Reset the region by destroying any existing view and clearing out the cached `$el`.
  // The next time a view is shown via this region, the region will re-query the DOM for
  // the region's `el`.
  reset: function reset(options) {
    this.empty(options);
    this.el = this._initEl;
    delete this.$el;
    return this;
  },
  _isDestroyed: false,
  isDestroyed: function isDestroyed() {
    return this._isDestroyed;
  },
  // Destroy the region, remove any child view
  // and remove the region from any associated view
  destroy: function destroy(options) {
    if (this._isDestroyed) {
      return this;
    }

    this.triggerMethod('before:destroy', this, options);
    this._isDestroyed = true;
    this.reset(options);

    if (this._name) {
      this._parentView._removeReferences(this._name);
    }

    delete this._parentView;
    delete this._name;
    this.triggerMethod('destroy', this, options);
    this.stopListening();
    return this;
  }
});

function buildRegion (definition, defaults) {
  if (definition instanceof Region) {
    return definition;
  }

  if (_.isString(definition)) {
    return buildRegionFromObject(defaults, {
      el: definition
    });
  }

  if (_.isFunction(definition)) {
    return buildRegionFromObject(defaults, {
      regionClass: definition
    });
  }

  if (_.isObject(definition)) {
    return buildRegionFromObject(defaults, definition);
  }

  throw new MarionetteError({
    message: 'Improper region configuration type.',
    url: 'marionette.region.html#defining-regions'
  });
}

function buildRegionFromObject(defaults, definition) {
  var options = _.extend({}, defaults, definition);

  var RegionClass = options.regionClass;
  delete options.regionClass;
  return new RegionClass(options);
}

// - regions
// - regionClass

var RegionsMixin = {
  regionClass: Region,
  // Internal method to initialize the regions that have been defined in a
  // `regions` attribute on this View.
  _initRegions: function _initRegions() {
    // init regions hash
    this.regions = this.regions || {};
    this._regions = {};
    this.addRegions(_.result(this, 'regions'));
  },
  // Internal method to re-initialize all of the regions by updating
  // the `el` that they point to
  _reInitRegions: function _reInitRegions() {
    _invoke(this._regions, 'reset');
  },
  // Add a single region, by name, to the View
  addRegion: function addRegion(name, definition) {
    var regions = {};
    regions[name] = definition;
    return this.addRegions(regions)[name];
  },
  // Add multiple regions as a {name: definition, name2: def2} object literal
  addRegions: function addRegions(regions) {
    // If there's nothing to add, stop here.
    if (_.isEmpty(regions)) {
      return;
    } // Normalize region selectors hash to allow
    // a user to use the @ui. syntax.


    regions = this.normalizeUIValues(regions, 'el'); // Add the regions definitions to the regions property

    this.regions = _.extend({}, this.regions, regions);
    return this._addRegions(regions);
  },
  // internal method to build and add regions
  _addRegions: function _addRegions(regionDefinitions) {
    var _this = this;

    var defaults = {
      regionClass: this.regionClass,
      parentEl: _.partial(_.result, this, 'el')
    };
    return _.reduce(regionDefinitions, function (regions, definition, name) {
      regions[name] = buildRegion(definition, defaults);

      _this._addRegion(regions[name], name);

      return regions;
    }, {});
  },
  _addRegion: function _addRegion(region, name) {
    this.triggerMethod('before:add:region', this, name, region);
    region._parentView = this;
    region._name = name;
    this._regions[name] = region;
    this.triggerMethod('add:region', this, name, region);
  },
  // Remove a single region from the View, by name
  removeRegion: function removeRegion(name) {
    var region = this._regions[name];

    this._removeRegion(region, name);

    return region;
  },
  // Remove all regions from the View
  removeRegions: function removeRegions() {
    var regions = this._getRegions();

    _.each(this._regions, this._removeRegion.bind(this));

    return regions;
  },
  _removeRegion: function _removeRegion(region, name) {
    this.triggerMethod('before:remove:region', this, name, region);
    region.destroy();
    this.triggerMethod('remove:region', this, name, region);
  },
  // Called in a region's destroy
  _removeReferences: function _removeReferences(name) {
    delete this.regions[name];
    delete this._regions[name];
  },
  // Empty all regions in the region manager, but
  // leave them attached
  emptyRegions: function emptyRegions() {
    var regions = this.getRegions();

    _invoke(regions, 'empty');

    return regions;
  },
  // Checks to see if view contains region
  // Accepts the region name
  // hasRegion('main')
  hasRegion: function hasRegion(name) {
    return !!this.getRegion(name);
  },
  // Provides access to regions
  // Accepts the region name
  // getRegion('main')
  getRegion: function getRegion(name) {
    if (!this._isRendered) {
      this.render();
    }

    return this._regions[name];
  },
  _getRegions: function _getRegions() {
    return _.clone(this._regions);
  },
  // Get all regions
  getRegions: function getRegions() {
    if (!this._isRendered) {
      this.render();
    }

    return this._getRegions();
  },
  showChildView: function showChildView(name, view, options) {
    var region = this.getRegion(name);
    region.show(view, options);
    return view;
  },
  detachChildView: function detachChildView(name) {
    return this.getRegion(name).detachView();
  },
  getChildView: function getChildView(name) {
    return this.getRegion(name).currentView;
  }
};

// Static setter for the renderer
function setRenderer(renderer) {
  this.prototype._renderHtml = renderer;
  return this;
}

// View
var ClassOptions$2 = ['behaviors', 'childViewEventPrefix', 'childViewEvents', 'childViewTriggers', 'collectionEvents', 'events', 'modelEvents', 'regionClass', 'regions', 'template', 'templateContext', 'triggers', 'ui']; // Used by _getImmediateChildren

function childReducer(children, region) {
  if (region.currentView) {
    children.push(region.currentView);
  }

  return children;
} // The standard view. Includes view events, automatic rendering
// templates, nested views, and more.


var View = Backbone.View.extend({
  constructor: function constructor(options) {
    this._setOptions(options, ClassOptions$2);

    monitorViewEvents(this);

    this._initBehaviors();

    this._initRegions();

    Backbone.View.prototype.constructor.apply(this, arguments);
    this.delegateEntityEvents();

    this._triggerEventOnBehaviors('initialize', this, options);
  },
  // Overriding Backbone.View's `setElement` to handle
  // if an el was previously defined. If so, the view might be
  // rendered or attached on setElement.
  setElement: function setElement() {
    Backbone.View.prototype.setElement.apply(this, arguments);
    this._isRendered = this.Dom.hasContents(this.el);
    this._isAttached = this._isElAttached();

    if (this._isRendered) {
      this.bindUIElements();
    }

    return this;
  },
  // If a template is available, renders it into the view's `el`
  // Re-inits regions and binds UI.
  render: function render() {
    var template = this.getTemplate();

    if (template === false || this._isDestroyed) {
      return this;
    }

    this.triggerMethod('before:render', this); // If this is not the first render call, then we need to
    // re-initialize the `el` for each region

    if (this._isRendered) {
      this._reInitRegions();
    }

    this._renderTemplate(template);

    this.bindUIElements();
    this._isRendered = true;
    this.triggerMethod('render', this);
    return this;
  },
  // called by ViewMixin destroy
  _removeChildren: function _removeChildren() {
    this.removeRegions();
  },
  _getImmediateChildren: function _getImmediateChildren() {
    return _.reduce(this._regions, childReducer, []);
  }
}, {
  setRenderer: setRenderer,
  setDomApi: setDomApi
});

_.extend(View.prototype, ViewMixin, RegionsMixin);

// shut down child views.

var Container = function Container() {
  this._init();
}; // Mix in methods from Underscore, for iteration, and other
// collection related features.
// Borrowing this code from Backbone.Collection:
// https://github.com/jashkenas/backbone/blob/1.1.2/backbone.js#L962


var methods = ['forEach', 'each', 'map', 'find', 'detect', 'filter', 'select', 'reject', 'every', 'all', 'some', 'any', 'include', 'contains', 'invoke', 'toArray', 'first', 'initial', 'rest', 'last', 'without', 'isEmpty', 'pluck', 'reduce', 'partition'];

_.each(methods, function (method) {
  Container.prototype[method] = function () {
    for (var _len = arguments.length, args = new Array(_len), _key = 0; _key < _len; _key++) {
      args[_key] = arguments[_key];
    }

    return _[method].apply(_, [this._views].concat(args));
  };
});

function stringComparator(comparator, view) {
  return view.model && view.model.get(comparator);
} // Container Methods
// -----------------


_.extend(Container.prototype, {
  // Initializes an empty container
  _init: function _init() {
    this._views = [];
    this._viewsByCid = {};
    this._indexByModel = {};

    this._updateLength();
  },
  // Add a view to this container. Stores the view
  // by `cid` and makes it searchable by the model
  // cid (and model itself). Additionally it stores
  // the view by index in the _views array
  _add: function _add(view) {
    var index = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : this._views.length;

    this._addViewIndexes(view); // add to end by default


    this._views.splice(index, 0, view);

    this._updateLength();
  },
  _addViewIndexes: function _addViewIndexes(view) {
    // store the view
    this._viewsByCid[view.cid] = view; // index it by model

    if (view.model) {
      this._indexByModel[view.model.cid] = view;
    }
  },
  // Sort (mutate) and return the array of the child views.
  _sort: function _sort(comparator, context) {
    if (typeof comparator === 'string') {
      comparator = _.partial(stringComparator, comparator);
      return this._sortBy(comparator);
    }

    if (comparator.length === 1) {
      return this._sortBy(comparator.bind(context));
    }

    return this._views.sort(comparator.bind(context));
  },
  // Makes `_.sortBy` mutate the array to match `this._views.sort`
  _sortBy: function _sortBy(comparator) {
    var sortedViews = _.sortBy(this._views, comparator);

    this._set(sortedViews);

    return sortedViews;
  },
  // Replace array contents without overwriting the reference.
  // Should not add/remove views
  _set: function _set(views, shouldReset) {
    this._views.length = 0;

    this._views.push.apply(this._views, views.slice(0));

    if (shouldReset) {
      this._viewsByCid = {};
      this._indexByModel = {};

      _.each(views, this._addViewIndexes.bind(this));

      this._updateLength();
    }
  },
  // Swap views by index
  _swap: function _swap(view1, view2) {
    var view1Index = this.findIndexByView(view1);
    var view2Index = this.findIndexByView(view2);

    if (view1Index === -1 || view2Index === -1) {
      return;
    }

    var swapView = this._views[view1Index];
    this._views[view1Index] = this._views[view2Index];
    this._views[view2Index] = swapView;
  },
  // Find a view by the model that was attached to it.
  // Uses the model's `cid` to find it.
  findByModel: function findByModel(model) {
    return this.findByModelCid(model.cid);
  },
  // Find a view by the `cid` of the model that was attached to it.
  findByModelCid: function findByModelCid(modelCid) {
    return this._indexByModel[modelCid];
  },
  // Find a view by index.
  findByIndex: function findByIndex(index) {
    return this._views[index];
  },
  // Find the index of a view instance
  findIndexByView: function findIndexByView(view) {
    return this._views.indexOf(view);
  },
  // Retrieve a view by its `cid` directly
  findByCid: function findByCid(cid) {
    return this._viewsByCid[cid];
  },
  hasView: function hasView(view) {
    return !!this.findByCid(view.cid);
  },
  // Remove a view and clean up index references.
  _remove: function _remove(view) {
    if (!this._viewsByCid[view.cid]) {
      return;
    } // delete model index


    if (view.model) {
      delete this._indexByModel[view.model.cid];
    } // remove the view from the container


    delete this._viewsByCid[view.cid];
    var index = this.findIndexByView(view);

    this._views.splice(index, 1);

    this._updateLength();
  },
  // Update the `.length` attribute on this container
  _updateLength: function _updateLength() {
    this.length = this._views.length;
  }
});

// Collection View
var classErrorName$1 = 'CollectionViewError';
var ClassOptions$3 = ['behaviors', 'childView', 'childViewContainer', 'childViewEventPrefix', 'childViewEvents', 'childViewOptions', 'childViewTriggers', 'collectionEvents', 'emptyView', 'emptyViewOptions', 'events', 'modelEvents', 'sortWithCollection', 'template', 'templateContext', 'triggers', 'ui', 'viewComparator', 'viewFilter']; // A view that iterates over a Backbone.Collection
// and renders an individual child view for each model.

var CollectionView = Backbone.View.extend({
  // flag for maintaining the sorted order of the collection
  sortWithCollection: true,
  // constructor
  constructor: function constructor(options) {
    this._setOptions(options, ClassOptions$3);

    monitorViewEvents(this);

    this._initChildViewStorage();

    this._initBehaviors();

    Backbone.View.prototype.constructor.apply(this, arguments); // Init empty region

    this.getEmptyRegion();
    this.delegateEntityEvents();

    this._triggerEventOnBehaviors('initialize', this, options);
  },
  // Internal method to set up the `children` object for storing all of the child views
  // `_children` represents all child views
  // `children` represents only views filtered to be shown
  _initChildViewStorage: function _initChildViewStorage() {
    this._children = new Container();
    this.children = new Container();
  },
  // Create an region to show the emptyView
  getEmptyRegion: function getEmptyRegion() {
    var $emptyEl = this.$container || this.$el;

    if (this._emptyRegion && !this._emptyRegion.isDestroyed()) {
      this._emptyRegion._setElement($emptyEl[0]);

      return this._emptyRegion;
    }

    this._emptyRegion = new Region({
      el: $emptyEl[0],
      replaceElement: false
    });
    this._emptyRegion._parentView = this;
    return this._emptyRegion;
  },
  // Configured the initial events that the collection view binds to.
  _initialEvents: function _initialEvents() {
    if (this._isRendered) {
      return;
    }

    this.listenTo(this.collection, {
      'sort': this._onCollectionSort,
      'reset': this._onCollectionReset,
      'update': this._onCollectionUpdate
    });
  },
  // Internal method. This checks for any changes in the order of the collection.
  // If the index of any view doesn't match, it will re-sort.
  _onCollectionSort: function _onCollectionSort(collection, _ref) {
    var add = _ref.add,
        merge = _ref.merge,
        remove = _ref.remove;

    if (!this.sortWithCollection || this.viewComparator === false) {
      return;
    } // If the data is changing we will handle the sort later in `_onCollectionUpdate`


    if (add || remove || merge) {
      return;
    } // If the only thing happening here is sorting, sort.


    this.sort();
  },
  _onCollectionReset: function _onCollectionReset() {
    this._destroyChildren();

    this._addChildModels(this.collection.models);

    this.sort();
  },
  // Handle collection update model additions and  removals
  _onCollectionUpdate: function _onCollectionUpdate(collection, options) {
    var changes = options.changes; // Remove first since it'll be a shorter array lookup.

    var removedViews = changes.removed.length && this._removeChildModels(changes.removed);

    this._addedViews = changes.added.length && this._addChildModels(changes.added);

    this._detachChildren(removedViews);

    this.sort(); // Destroy removed child views after all of the render is complete

    this._removeChildViews(removedViews);
  },
  _removeChildModels: function _removeChildModels(models) {
    var _this = this;

    return _.reduce(models, function (views, model) {
      var removeView = _this._removeChildModel(model);

      if (removeView) {
        views.push(removeView);
      }

      return views;
    }, []);
  },
  _removeChildModel: function _removeChildModel(model) {
    var view = this._children.findByModel(model);

    if (view) {
      this._removeChild(view);
    }

    return view;
  },
  _removeChild: function _removeChild(view) {
    this.triggerMethod('before:remove:child', this, view);

    this.children._remove(view);

    this._children._remove(view);

    this.triggerMethod('remove:child', this, view);
  },
  // Added views are returned for consistency with _removeChildModels
  _addChildModels: function _addChildModels(models) {
    return _.map(models, this._addChildModel.bind(this));
  },
  _addChildModel: function _addChildModel(model) {
    var view = this._createChildView(model);

    this._addChild(view);

    return view;
  },
  _createChildView: function _createChildView(model) {
    var ChildView = this._getChildView(model);

    var childViewOptions = this._getChildViewOptions(model);

    var view = this.buildChildView(model, ChildView, childViewOptions);
    return view;
  },
  _addChild: function _addChild(view, index) {
    this.triggerMethod('before:add:child', this, view);

    this._setupChildView(view);

    this._children._add(view, index);

    this.children._add(view, index);

    this.triggerMethod('add:child', this, view);
  },
  // Retrieve the `childView` class
  // The `childView` property can be either a view class or a function that
  // returns a view class. If it is a function, it will receive the model that
  // will be passed to the view instance (created from the returned view class)
  _getChildView: function _getChildView(child) {
    var childView = this.childView;

    if (!childView) {
      throw new MarionetteError({
        name: classErrorName$1,
        message: 'A "childView" must be specified',
        url: 'marionette.collectionview.html#collectionviews-childview'
      });
    }

    childView = this._getView(childView, child);

    if (!childView) {
      throw new MarionetteError({
        name: classErrorName$1,
        message: '"childView" must be a view class or a function that returns a view class',
        url: 'marionette.collectionview.html#collectionviews-childview'
      });
    }

    return childView;
  },
  // First check if the `view` is a view class (the common case)
  // Then check if it's a function (which we assume that returns a view class)
  _getView: function _getView(view, child) {
    if (view.prototype instanceof Backbone.View || view === Backbone.View) {
      return view;
    } else if (_.isFunction(view)) {
      return view.call(this, child);
    }
  },
  _getChildViewOptions: function _getChildViewOptions(child) {
    if (_.isFunction(this.childViewOptions)) {
      return this.childViewOptions(child);
    }

    return this.childViewOptions;
  },
  // Build a `childView` for a model in the collection.
  // Override to customize the build
  buildChildView: function buildChildView(child, ChildViewClass, childViewOptions) {
    var options = _.extend({
      model: child
    }, childViewOptions);

    return new ChildViewClass(options);
  },
  _setupChildView: function _setupChildView(view) {
    monitorViewEvents(view); // We need to listen for if a view is destroyed in a way other
    // than through the CollectionView.
    // If this happens we need to remove the reference to the view
    // since once a view has been destroyed we can not reuse it.

    view.on('destroy', this.removeChildView, this); // set up the child view event forwarding

    this._proxyChildViewEvents(view);
  },
  // used by ViewMixin's `_childViewEventHandler`
  _getImmediateChildren: function _getImmediateChildren() {
    return this.children._views;
  },
  // Overriding Backbone.View's `setElement` to handle
  // if an el was previously defined. If so, the view might be
  // attached on setElement.
  setElement: function setElement() {
    Backbone.View.prototype.setElement.apply(this, arguments);
    this._isAttached = this._isElAttached();
    return this;
  },
  // Render children views.
  render: function render() {
    if (this._isDestroyed) {
      return this;
    }

    this.triggerMethod('before:render', this);

    this._destroyChildren();

    if (this.collection) {
      this._addChildModels(this.collection.models);

      this._initialEvents();
    }

    var template = this.getTemplate();

    if (template) {
      this._renderTemplate(template);

      this.bindUIElements();
    }

    this._getChildViewContainer();

    this.sort();
    this._isRendered = true;
    this.triggerMethod('render', this);
    return this;
  },
  // Get a container within the template to add the children within
  _getChildViewContainer: function _getChildViewContainer() {
    var childViewContainer = _.result(this, 'childViewContainer');

    this.$container = childViewContainer ? this.$(childViewContainer) : this.$el;

    if (!this.$container.length) {
      throw new MarionetteError({
        name: classErrorName$1,
        message: "The specified \"childViewContainer\" was not found: ".concat(childViewContainer),
        url: 'marionette.collectionview.html#defining-the-childviewcontainer'
      });
    }
  },
  // Sorts the children then filters and renders the results.
  sort: function sort() {
    this._sortChildren();

    this.filter();
    return this;
  },
  // Sorts views by viewComparator and sets the children to the new order
  _sortChildren: function _sortChildren() {
    if (!this._children.length) {
      return;
    }

    var viewComparator = this.getComparator();

    if (!viewComparator) {
      return;
    } // If children are sorted prevent added to end perf


    delete this._addedViews;
    this.triggerMethod('before:sort', this);

    this._children._sort(viewComparator, this);

    this.triggerMethod('sort', this);
  },
  // Sets the view's `viewComparator` and applies the sort if the view is ready.
  // To prevent the render pass `{ preventRender: true }` as the 2nd argument.
  setComparator: function setComparator(comparator) {
    var _ref2 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
        preventRender = _ref2.preventRender;

    var comparatorChanged = this.viewComparator !== comparator;
    var shouldSort = comparatorChanged && !preventRender;
    this.viewComparator = comparator;

    if (shouldSort) {
      this.sort();
    }

    return this;
  },
  // Clears the `viewComparator` and follows the same rules for rendering as `setComparator`.
  removeComparator: function removeComparator(options) {
    return this.setComparator(null, options);
  },
  // If viewComparator is overridden it will be returned here.
  // Additionally override this function to provide custom
  // viewComparator logic
  getComparator: function getComparator() {
    if (this.viewComparator) {
      return this.viewComparator;
    }

    if (!this.sortWithCollection || this.viewComparator === false || !this.collection) {
      return false;
    }

    return this._viewComparator;
  },
  // Default internal view comparator that order the views by
  // the order of the collection
  _viewComparator: function _viewComparator(view) {
    return this.collection.indexOf(view.model);
  },
  // This method filters the children views and renders the results
  filter: function filter() {
    if (this._isDestroyed) {
      return this;
    }

    this._filterChildren();

    this._renderChildren();

    return this;
  },
  _filterChildren: function _filterChildren() {
    var _this2 = this;

    if (!this._children.length) {
      return;
    }

    var viewFilter = this._getFilter();

    if (!viewFilter) {
      var shouldReset = this.children.length !== this._children.length;

      this.children._set(this._children._views, shouldReset);

      return;
    } // If children are filtered prevent added to end perf


    delete this._addedViews;
    this.triggerMethod('before:filter', this);
    var attachViews = [];
    var detachViews = [];

    _.each(this._children._views, function (view, key, children) {
      (viewFilter.call(_this2, view, key, children) ? attachViews : detachViews).push(view);
    });

    this._detachChildren(detachViews); // reset children


    this.children._set(attachViews, true);

    this.triggerMethod('filter', this, attachViews, detachViews);
  },
  // This method returns a function for the viewFilter
  _getFilter: function _getFilter() {
    var viewFilter = this.getFilter();

    if (!viewFilter) {
      return false;
    }

    if (_.isFunction(viewFilter)) {
      return viewFilter;
    } // Support filter predicates `{ fooFlag: true }`


    if (_.isObject(viewFilter)) {
      var matcher = _.matches(viewFilter);

      return function (view) {
        return matcher(view.model && view.model.attributes);
      };
    } // Filter by model attribute


    if (_.isString(viewFilter)) {
      return function (view) {
        return view.model && view.model.get(viewFilter);
      };
    }

    throw new MarionetteError({
      name: classErrorName$1,
      message: '"viewFilter" must be a function, predicate object literal, a string indicating a model attribute, or falsy',
      url: 'marionette.collectionview.html#defining-the-viewfilter'
    });
  },
  // Override this function to provide custom
  // viewFilter logic
  getFilter: function getFilter() {
    return this.viewFilter;
  },
  // Sets the view's `viewFilter` and applies the filter if the view is ready.
  // To prevent the render pass `{ preventRender: true }` as the 2nd argument.
  setFilter: function setFilter(filter) {
    var _ref3 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
        preventRender = _ref3.preventRender;

    var filterChanged = this.viewFilter !== filter;
    var shouldRender = filterChanged && !preventRender;
    this.viewFilter = filter;

    if (shouldRender) {
      this.filter();
    }

    return this;
  },
  // Clears the `viewFilter` and follows the same rules for rendering as `setFilter`.
  removeFilter: function removeFilter(options) {
    return this.setFilter(null, options);
  },
  _detachChildren: function _detachChildren(detachingViews) {
    _.each(detachingViews, this._detachChildView.bind(this));
  },
  _detachChildView: function _detachChildView(view) {
    var shouldTriggerDetach = view._isAttached && this.monitorViewEvents !== false;

    if (shouldTriggerDetach) {
      view.triggerMethod('before:detach', view);
    }

    this.detachHtml(view);

    if (shouldTriggerDetach) {
      view._isAttached = false;
      view.triggerMethod('detach', view);
    }

    view._isShown = false;
  },
  // Override this method to change how the collectionView detaches a child view
  detachHtml: function detachHtml(view) {
    this.Dom.detachEl(view.el, view.$el);
  },
  _renderChildren: function _renderChildren() {
    // If there are unrendered views prevent add to end perf
    if (this._hasUnrenderedViews) {
      delete this._addedViews;
      delete this._hasUnrenderedViews;
    }

    var views = this._addedViews || this.children._views;
    this.triggerMethod('before:render:children', this, views);

    if (this.isEmpty()) {
      this._showEmptyView();
    } else {
      this._destroyEmptyView();

      var els = this._getBuffer(views);

      this._attachChildren(els, views);
    }

    delete this._addedViews;
    this.triggerMethod('render:children', this, views);
  },
  // Renders each view and creates a fragment buffer from them
  _getBuffer: function _getBuffer(views) {
    var _this3 = this;

    var elBuffer = this.Dom.createBuffer();

    _.each(views, function (view) {
      renderView(view); // corresponds that view is shown in a Region or CollectionView

      view._isShown = true;

      _this3.Dom.appendContents(elBuffer, view.el, {
        _$contents: view.$el
      });
    });

    return elBuffer;
  },
  _attachChildren: function _attachChildren(els, views) {
    var shouldTriggerAttach = this._isAttached && this.monitorViewEvents !== false;
    views = shouldTriggerAttach ? views : [];

    _.each(views, function (view) {
      if (view._isAttached) {
        return;
      }

      view.triggerMethod('before:attach', view);
    });

    this.attachHtml(els, this.$container);

    _.each(views, function (view) {
      if (view._isAttached) {
        return;
      }

      view._isAttached = true;
      view.triggerMethod('attach', view);
    });
  },
  // Override this method to do something other than `.append`.
  // You can attach any HTML at this point including the els.
  attachHtml: function attachHtml(els, $container) {
    this.Dom.appendContents($container[0], els, {
      _$el: $container
    });
  },
  isEmpty: function isEmpty() {
    return !this.children.length;
  },
  _showEmptyView: function _showEmptyView() {
    var EmptyView = this._getEmptyView();

    if (!EmptyView) {
      return;
    }

    var options = this._getEmptyViewOptions();

    var emptyRegion = this.getEmptyRegion();
    emptyRegion.show(new EmptyView(options));
  },
  // Retrieve the empty view class
  _getEmptyView: function _getEmptyView() {
    var emptyView = this.emptyView;

    if (!emptyView) {
      return;
    }

    return this._getView(emptyView);
  },
  // Remove the emptyView
  _destroyEmptyView: function _destroyEmptyView() {
    var emptyRegion = this.getEmptyRegion(); // Only empty if a view is show so the region
    // doesn't detach any other unrelated HTML

    if (emptyRegion.hasView()) {
      emptyRegion.empty();
    }
  },
  //
  _getEmptyViewOptions: function _getEmptyViewOptions() {
    var emptyViewOptions = this.emptyViewOptions || this.childViewOptions;

    if (_.isFunction(emptyViewOptions)) {
      return emptyViewOptions.call(this);
    }

    return emptyViewOptions;
  },
  swapChildViews: function swapChildViews(view1, view2) {
    if (!this._children.hasView(view1) || !this._children.hasView(view2)) {
      throw new MarionetteError({
        name: classErrorName$1,
        message: 'Both views must be children of the collection view to swap.',
        url: 'marionette.collectionview.html#swapping-child-views'
      });
    }

    this._children._swap(view1, view2);

    this.Dom.swapEl(view1.el, view2.el); // If the views are not filtered the same, refilter

    if (this.children.hasView(view1) !== this.children.hasView(view2)) {
      this.filter();
    } else {
      this.children._swap(view1, view2);
    }

    return this;
  },
  // Render the child's view and add it to the HTML for the collection view at a given index, based on the current sort
  addChildView: function addChildView(view, index) {
    var options = arguments.length > 2 && arguments[2] !== undefined ? arguments[2] : {};

    if (!view || view._isDestroyed) {
      return view;
    }

    if (view._isShown) {
      throw new MarionetteError({
        name: classErrorName$1,
        message: 'View is already shown in a Region or CollectionView',
        url: 'marionette.region.html#showing-a-view'
      });
    }

    if (_.isObject(index)) {
      options = index;
    } // If options has defined index we should use it


    if (options.index != null) {
      index = options.index;
    }

    if (!this._isRendered) {
      this.render();
    }

    this._addChild(view, index);

    if (options.preventRender) {
      this._hasUnrenderedViews = true;
      return view;
    }

    var hasIndex = typeof index !== 'undefined';
    var isAddedToEnd = !hasIndex || index >= this._children.length; // Only cache views if added to the end and there is no unrendered views

    if (isAddedToEnd && !this._hasUnrenderedViews) {
      this._addedViews = [view];
    }

    if (hasIndex) {
      this._renderChildren();
    } else {
      this.sort();
    }

    return view;
  },
  // Detach a view from the children.  Best used when adding a
  // childView from `addChildView`
  detachChildView: function detachChildView(view) {
    this.removeChildView(view, {
      shouldDetach: true
    });
    return view;
  },
  // Remove the child view and destroy it.  Best used when adding a
  // childView from `addChildView`
  // The options argument is for internal use only
  removeChildView: function removeChildView(view, options) {
    if (!view) {
      return view;
    }

    this._removeChildView(view, options);

    this._removeChild(view);

    if (this.isEmpty()) {
      this._showEmptyView();
    }

    return view;
  },
  _removeChildViews: function _removeChildViews(views) {
    _.each(views, this._removeChildView.bind(this));
  },
  _removeChildView: function _removeChildView(view) {
    var _ref4 = arguments.length > 1 && arguments[1] !== undefined ? arguments[1] : {},
        shouldDetach = _ref4.shouldDetach;

    view.off('destroy', this.removeChildView, this);

    if (shouldDetach) {
      this._detachChildView(view);
    } else {
      this._destroyChildView(view);
    }

    this.stopListening(view);
  },
  _destroyChildView: function _destroyChildView(view) {
    if (view._isDestroyed) {
      return;
    }

    var shouldDisableEvents = this.monitorViewEvents === false;
    destroyView(view, shouldDisableEvents);
  },
  // called by ViewMixin destroy
  _removeChildren: function _removeChildren() {
    this._destroyChildren();

    var emptyRegion = this.getEmptyRegion();
    emptyRegion.destroy();
    delete this._addedViews;
  },
  // Destroy the child views that this collection view is holding on to, if any
  _destroyChildren: function _destroyChildren() {
    if (!this._children.length) {
      return;
    }

    this.triggerMethod('before:destroy:children', this);

    if (this.monitorViewEvents === false) {
      this.Dom.detachContents(this.el, this.$el);
    }

    this._removeChildViews(this._children._views); // After all children have been destroyed re-init the container


    this._children._init();

    this.children._init();

    this.triggerMethod('destroy:children', this);
  }
}, {
  setDomApi: setDomApi,
  setRenderer: setRenderer
});

_.extend(CollectionView.prototype, ViewMixin);

// Behavior
var ClassOptions$4 = ['collectionEvents', 'events', 'modelEvents', 'triggers', 'ui'];

var Behavior = function Behavior(options, view) {
  // Setup reference to the view.
  // this comes in handle when a behavior
  // wants to directly talk up the chain
  // to the view.
  this.view = view;

  this._setOptions(options, ClassOptions$4);

  this.cid = _.uniqueId(this.cidPrefix); // Construct an internal UI hash using the behaviors UI
  // hash combined and overridden by the view UI hash.
  // This allows the user to use UI hash elements defined
  // in the parent view as well as those defined in the behavior.
  // This order will help the reuse and share of a behavior
  // between multiple views, while letting a view override
  // a selector under an UI key.

  this.ui = _.extend({}, _.result(this, 'ui'), _.result(view, 'ui')); // Proxy view triggers

  this.listenTo(view, 'all', this.triggerMethod);
  this.initialize.apply(this, arguments);
};

Behavior.extend = extend; // Behavior Methods
// --------------

_.extend(Behavior.prototype, CommonMixin, DelegateEntityEventsMixin, TriggersMixin, UIMixin, {
  cidPrefix: 'mnb',
  // This is a noop method intended to be overridden
  initialize: function initialize() {},
  // proxy behavior $ method to the view
  // this is useful for doing jquery DOM lookups
  // scoped to behaviors view.
  $: function $() {
    return this.view.$.apply(this.view, arguments);
  },
  // Stops the behavior from listening to events.
  destroy: function destroy() {
    this.stopListening();

    this.view._removeBehavior(this);

    this._deleteEntityEventHandlers();

    return this;
  },
  proxyViewProperties: function proxyViewProperties() {
    this.$el = this.view.$el;
    this.el = this.view.el;
    return this;
  },
  bindUIElements: function bindUIElements() {
    this._bindUIElements();

    return this;
  },
  unbindUIElements: function unbindUIElements() {
    this._unbindUIElements();

    return this;
  },
  getUI: function getUI(name) {
    return this._getUI(name);
  },
  // Handle `modelEvents`, and `collectionEvents` configuration
  delegateEntityEvents: function delegateEntityEvents() {
    this._delegateEntityEvents(this.view.model, this.view.collection);

    return this;
  },
  undelegateEntityEvents: function undelegateEntityEvents() {
    this._undelegateEntityEvents(this.view.model, this.view.collection);

    return this;
  },
  _getEvents: function _getEvents() {
    var _this = this;

    if (!this.events) {
      return;
    } // Normalize behavior events hash to allow
    // a user to use the @ui. syntax.


    var behaviorEvents = this.normalizeUIKeys(_.result(this, 'events')); // binds the handler to the behavior and builds a unique eventName

    return _.reduce(behaviorEvents, function (events, behaviorHandler, key) {
      if (!_.isFunction(behaviorHandler)) {
        behaviorHandler = _this[behaviorHandler];
      }

      if (!behaviorHandler) {
        return events;
      }

      key = getNamespacedEventName(key, _this.cid);
      events[key] = behaviorHandler.bind(_this);
      return events;
    }, {});
  },
  // Internal method to build all trigger handlers for a given behavior
  _getTriggers: function _getTriggers() {
    if (!this.triggers) {
      return;
    } // Normalize behavior triggers hash to allow
    // a user to use the @ui. syntax.


    var behaviorTriggers = this.normalizeUIKeys(_.result(this, 'triggers'));
    return this._getViewTriggers(this.view, behaviorTriggers);
  }
});

// Application
var ClassOptions$5 = ['channelName', 'radioEvents', 'radioRequests', 'region', 'regionClass'];

var Application = function Application(options) {
  this._setOptions(options, ClassOptions$5);

  this.cid = _.uniqueId(this.cidPrefix);

  this._initRegion();

  this._initRadio();

  this.initialize.apply(this, arguments);
};

Application.extend = extend; // Application Methods
// --------------

_.extend(Application.prototype, CommonMixin, DestroyMixin, RadioMixin, {
  cidPrefix: 'mna',
  // This is a noop method intended to be overridden
  initialize: function initialize() {},
  // Kick off all of the application's processes.
  start: function start(options) {
    this.triggerMethod('before:start', this, options);
    this.triggerMethod('start', this, options);
    return this;
  },
  regionClass: Region,
  _initRegion: function _initRegion() {
    var region = this.region;

    if (!region) {
      return;
    }

    var defaults = {
      regionClass: this.regionClass
    };
    this._region = buildRegion(region, defaults);
  },
  getRegion: function getRegion() {
    return this._region;
  },
  showView: function showView(view) {
    var region = this.getRegion();

    for (var _len = arguments.length, args = new Array(_len > 1 ? _len - 1 : 0), _key = 1; _key < _len; _key++) {
      args[_key - 1] = arguments[_key];
    }

    region.show.apply(region, [view].concat(args));
    return view;
  },
  getView: function getView() {
    return this.getRegion().currentView;
  }
});

var bindEvents$1 = proxy(bindEvents);
var unbindEvents$1 = proxy(unbindEvents);
var bindRequests$1 = proxy(bindRequests);
var unbindRequests$1 = proxy(unbindRequests);
var mergeOptions$1 = proxy(mergeOptions);
var getOption$1 = proxy(getOption);
var normalizeMethods$1 = proxy(normalizeMethods);
var triggerMethod$1 = proxy(triggerMethod); // Configuration

var setDomApi$1 = function setDomApi(mixin) {
  CollectionView.setDomApi(mixin);
  Region.setDomApi(mixin);
  View.setDomApi(mixin);
};
var setRenderer$1 = function setRenderer(renderer) {
  CollectionView.setRenderer(renderer);
  View.setRenderer(renderer);
};
var backbone_marionette = {
  View: View,
  CollectionView: CollectionView,
  MnObject: MarionetteObject,
  Object: MarionetteObject,
  Region: Region,
  Behavior: Behavior,
  Application: Application,
  isEnabled: isEnabled,
  setEnabled: setEnabled,
  monitorViewEvents: monitorViewEvents,
  Events: Events,
  extend: extend,
  DomApi: DomApi,
  VERSION: version
};

export default backbone_marionette;
export { Application, Behavior, CollectionView, DomApi, Events, MarionetteObject as MnObject, Region, version as VERSION, View, bindEvents$1 as bindEvents, bindRequests$1 as bindRequests, extend, getOption$1 as getOption, isEnabled, mergeOptions$1 as mergeOptions, monitorViewEvents, normalizeMethods$1 as normalizeMethods, setDomApi$1 as setDomApi, setEnabled, setRenderer$1 as setRenderer, triggerMethod$1 as triggerMethod, unbindEvents$1 as unbindEvents, unbindRequests$1 as unbindRequests };