Return-Path: X-Original-To: apmail-ambari-commits-archive@www.apache.org Delivered-To: apmail-ambari-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 535FB1821D for ; Fri, 22 May 2015 19:41:20 +0000 (UTC) Received: (qmail 69282 invoked by uid 500); 22 May 2015 19:41:20 -0000 Delivered-To: apmail-ambari-commits-archive@ambari.apache.org Received: (qmail 69207 invoked by uid 500); 22 May 2015 19:41:20 -0000 Mailing-List: contact commits-help@ambari.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: ambari-dev@ambari.apache.org Delivered-To: mailing list commits@ambari.apache.org Received: (qmail 68424 invoked by uid 99); 22 May 2015 19:41:19 -0000 Received: from git1-us-west.apache.org (HELO git1-us-west.apache.org) (140.211.11.23) by apache.org (qpsmtpd/0.29) with ESMTP; Fri, 22 May 2015 19:41:19 +0000 Received: by git1-us-west.apache.org (ASF Mail Server at git1-us-west.apache.org, from userid 33) id 9DA24DFBD9; Fri, 22 May 2015 19:41:19 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: srimanth@apache.org To: commits@ambari.apache.org Date: Fri, 22 May 2015 19:41:41 -0000 Message-Id: <45f0e068428d4ab89b786d50da871139@git.apache.org> In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [23/25] ambari git commit: AMBARI-10064. Storm Ambari view (Sriharsha Chintalapani via srimanth) http://git-wip-us.apache.org/repos/asf/ambari/blob/cd6398a1/contrib/views/storm/src/main/resources/libs/bower/backbone.marionette/js/backbone.marionette.js ---------------------------------------------------------------------- diff --git a/contrib/views/storm/src/main/resources/libs/bower/backbone.marionette/js/backbone.marionette.js b/contrib/views/storm/src/main/resources/libs/bower/backbone.marionette/js/backbone.marionette.js new file mode 100644 index 0000000..8714cd7 --- /dev/null +++ b/contrib/views/storm/src/main/resources/libs/bower/backbone.marionette/js/backbone.marionette.js @@ -0,0 +1,3128 @@ +// MarionetteJS (Backbone.Marionette) +// ---------------------------------- +// v2.3.2 +// +// Copyright (c)2015 Derick Bailey, Muted Solutions, LLC. +// Distributed under MIT license +// +// http://marionettejs.com + +(function(root, factory) { + + if (typeof define === 'function' && define.amd) { + define(['backbone', 'underscore', 'backbone.wreqr', 'backbone.babysitter'], function(Backbone, _) { + return (root.Marionette = root.Mn = factory(root, Backbone, _)); + }); + } else if (typeof exports !== 'undefined') { + var Backbone = require('backbone'); + var _ = require('underscore'); + var Wreqr = require('backbone.wreqr'); + var BabySitter = require('backbone.babysitter'); + module.exports = factory(root, Backbone, _); + } else { + root.Marionette = root.Mn = factory(root, root.Backbone, root._); + } + +}(this, function(root, Backbone, _) { + 'use strict'; + + var previousMarionette = root.Marionette; + var previousMn = root.Mn; + + var Marionette = Backbone.Marionette = {}; + + Marionette.VERSION = '2.3.2'; + + Marionette.noConflict = function() { + root.Marionette = previousMarionette; + root.Mn = previousMn; + return this; + }; + + // Get the Deferred creator for later use + Marionette.Deferred = Backbone.$.Deferred; + + /* jshint unused: false *//* global console */ + + // Helpers + // ------- + + // Marionette.extend + // ----------------- + + // Borrow the Backbone `extend` method so we can use it as needed + Marionette.extend = Backbone.Model.extend; + + // Marionette.isNodeAttached + // ------------------------- + + // Determine if `el` is a child of the document + Marionette.isNodeAttached = function(el) { + return Backbone.$.contains(document.documentElement, el); + }; + + + // Marionette.getOption + // -------------------- + + // Retrieve an object, function or other value from a target + // object or its `options`, with `options` taking precedence. + Marionette.getOption = function(target, optionName) { + if (!target || !optionName) { return; } + if (target.options && (target.options[optionName] !== undefined)) { + return target.options[optionName]; + } else { + return target[optionName]; + } + }; + + // Proxy `Marionette.getOption` + Marionette.proxyGetOption = function(optionName) { + return Marionette.getOption(this, optionName); + }; + + // Similar to `_.result`, this is a simple helper + // If a function is provided we call it with context + // otherwise just return the value. If the value is + // undefined return a default value + Marionette._getValue = function(value, context, params) { + if (_.isFunction(value)) { + // We need to ensure that params is not undefined + // to prevent `apply` from failing in ie8 + params = params || []; + + value = value.apply(context, params); + } + return value; + }; + + // Marionette.normalizeMethods + // ---------------------- + + // Pass in a mapping of events => functions or function names + // and return a mapping of events => functions + Marionette.normalizeMethods = function(hash) { + return _.reduce(hash, function(normalizedHash, method, name) { + if (!_.isFunction(method)) { + method = this[method]; + } + if (method) { + normalizedHash[name] = method; + } + return normalizedHash; + }, {}, this); + }; + + // utility method for parsing @ui. syntax strings + // into associated selector + Marionette.normalizeUIString = function(uiString, ui) { + return uiString.replace(/@ui\.[a-zA-Z_$0-9]*/g, function(r) { + return ui[r.slice(4)]; + }); + }; + + // allows for the use of the @ui. syntax within + // a given key for triggers and events + // swaps the @ui with the associated selector. + // Returns a new, non-mutated, parsed events hash. + Marionette.normalizeUIKeys = function(hash, ui) { + return _.reduce(hash, function(memo, val, key) { + var normalizedKey = Marionette.normalizeUIString(key, ui); + memo[normalizedKey] = val; + return memo; + }, {}); + }; + + // allows for the use of the @ui. syntax within + // a given value for regions + // swaps the @ui with the associated selector + Marionette.normalizeUIValues = function(hash, ui) { + _.each(hash, function(val, key) { + if (_.isString(val)) { + hash[key] = Marionette.normalizeUIString(val, ui); + } + }); + return hash; + }; + + // Mix in methods from Underscore, for iteration, and other + // collection related features. + // Borrowing this code from Backbone.Collection: + // http://backbonejs.org/docs/backbone.html#section-121 + Marionette.actAsCollection = function(object, listProperty) { + 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']; + + _.each(methods, function(method) { + object[method] = function() { + var list = _.values(_.result(this, listProperty)); + var args = [list].concat(_.toArray(arguments)); + return _[method].apply(_, args); + }; + }); + }; + + var deprecate = Marionette.deprecate = function(message, test) { + if (_.isObject(message)) { + message = ( + message.prev + ' is going to be removed in the future. ' + + 'Please use ' + message.next + ' instead.' + + (message.url ? ' See: ' + message.url : '') + ); + } + + if ((test === undefined || !test) && !deprecate._cache[message]) { + deprecate._warn('Deprecation warning: ' + message); + deprecate._cache[message] = true; + } + }; + + deprecate._warn = typeof console !== 'undefined' && (console.warn || console.log) || function() {}; + deprecate._cache = {}; + + /* jshint maxstatements: 14, maxcomplexity: 7 */ + + // Trigger Method + // -------------- + + + Marionette._triggerMethod = (function() { + // split the event name on the ":" + var splitter = /(^|:)(\w)/gi; + + // take the event section ("section1:section2:section3") + // and turn it in to uppercase name + function getEventName(match, prefix, eventName) { + return eventName.toUpperCase(); + } + + return function(context, event, args) { + var noEventArg = arguments.length < 3; + if (noEventArg) { + args = event; + event = args[0]; + } + + // get the method name from the event name + var methodName = 'on' + event.replace(splitter, getEventName); + var method = context[methodName]; + var result; + + // call the onMethodName if it exists + if (_.isFunction(method)) { + // pass all args, except the event name + result = method.apply(context, noEventArg ? _.rest(args) : args); + } + + // trigger the event, if a trigger method exists + if (_.isFunction(context.trigger)) { + if (noEventArg + args.length > 1) { + context.trigger.apply(context, noEventArg ? args : [event].concat(_.rest(args, 0))); + } else { + context.trigger(event); + } + } + + return result; + }; + })(); + + // 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. + Marionette.triggerMethod = function(event) { + return Marionette._triggerMethod(this, arguments); + }; + + // triggerMethodOn invokes triggerMethod on a specific context + // + // e.g. `Marionette.triggerMethodOn(view, 'show')` + // will trigger a "show" event or invoke onShow the view. + Marionette.triggerMethodOn = function(context) { + var fnc = _.isFunction(context.triggerMethod) ? + context.triggerMethod : + Marionette.triggerMethod; + + return fnc.apply(context, _.rest(arguments)); + }; + + // DOM Refresh + // ----------- + + // Monitor a view's state, and after it has been rendered and shown + // in the DOM, trigger a "dom:refresh" event every time it is + // re-rendered. + + Marionette.MonitorDOMRefresh = function(view) { + + // track when the view has been shown in the DOM, + // using a Marionette.Region (or by other means of triggering "show") + function handleShow() { + view._isShown = true; + triggerDOMRefresh(); + } + + // track when the view has been rendered + function handleRender() { + view._isRendered = true; + triggerDOMRefresh(); + } + + // Trigger the "dom:refresh" event and corresponding "onDomRefresh" method + function triggerDOMRefresh() { + if (view._isShown && view._isRendered && Marionette.isNodeAttached(view.el)) { + if (_.isFunction(view.triggerMethod)) { + view.triggerMethod('dom:refresh'); + } + } + } + + view.on({ + show: handleShow, + render: handleRender + }); + }; + + /* jshint maxparams: 5 */ + + // Bind Entity Events & Unbind Entity Events + // ----------------------------------------- + // + // These methods are used to bind/unbind a backbone "entity" (e.g. collection/model) + // to methods on a target object. + // + // The first parameter, `target`, must have the Backbone.Events module mixed in. + // + // The second parameter is the `entity` (Backbone.Model, Backbone.Collection or + // any object that has Backbone.Events mixed in) to bind the events from. + // + // The third parameter is a hash of { "event:name": "eventHandler" } + // configuration. Multiple handlers can be separated by a space. A + // function can be supplied instead of a string handler name. + + (function(Marionette) { + 'use strict'; + + // Bind the event to handlers specified as a string of + // handler names on the target object + function bindFromStrings(target, entity, evt, methods) { + var methodNames = methods.split(/\s+/); + + _.each(methodNames, function(methodName) { + + var method = target[methodName]; + if (!method) { + throw new Marionette.Error('Method "' + methodName + + '" was configured as an event handler, but does not exist.'); + } + + target.listenTo(entity, evt, method); + }); + } + + // Bind the event to a supplied callback function + function bindToFunction(target, entity, evt, method) { + target.listenTo(entity, evt, method); + } + + // Bind the event to handlers specified as a string of + // handler names on the target object + function unbindFromStrings(target, entity, evt, methods) { + var methodNames = methods.split(/\s+/); + + _.each(methodNames, function(methodName) { + var method = target[methodName]; + target.stopListening(entity, evt, method); + }); + } + + // Bind the event to a supplied callback function + function unbindToFunction(target, entity, evt, method) { + target.stopListening(entity, evt, method); + } + + + // generic looping function + function iterateEvents(target, entity, bindings, functionCallback, stringCallback) { + if (!entity || !bindings) { return; } + + // type-check bindings + if (!_.isObject(bindings)) { + throw new Marionette.Error({ + message: 'Bindings must be an object or function.', + url: 'marionette.functions.html#marionettebindentityevents' + }); + } + + // allow the bindings to be a function + bindings = Marionette._getValue(bindings, target); + + // iterate the bindings and bind them + _.each(bindings, function(methods, evt) { + + // allow for a function as the handler, + // or a list of event names as a string + if (_.isFunction(methods)) { + functionCallback(target, entity, evt, methods); + } else { + stringCallback(target, entity, evt, methods); + } + + }); + } + + // Export Public API + Marionette.bindEntityEvents = function(target, entity, bindings) { + iterateEvents(target, entity, bindings, bindToFunction, bindFromStrings); + }; + + Marionette.unbindEntityEvents = function(target, entity, bindings) { + iterateEvents(target, entity, bindings, unbindToFunction, unbindFromStrings); + }; + + // Proxy `bindEntityEvents` + Marionette.proxyBindEntityEvents = function(entity, bindings) { + return Marionette.bindEntityEvents(this, entity, bindings); + }; + + // Proxy `unbindEntityEvents` + Marionette.proxyUnbindEntityEvents = function(entity, bindings) { + return Marionette.unbindEntityEvents(this, entity, bindings); + }; + })(Marionette); + + + // Error + // ----- + + var errorProps = ['description', 'fileName', 'lineNumber', 'name', 'message', 'number']; + + Marionette.Error = Marionette.extend.call(Error, { + urlRoot: 'http://marionettejs.com/docs/v' + Marionette.VERSION + '/', + + constructor: function(message, options) { + if (_.isObject(message)) { + options = message; + message = options.message; + } else if (!options) { + options = {}; + } + + var error = Error.call(this, message); + _.extend(this, _.pick(error, errorProps), _.pick(options, errorProps)); + + this.captureStackTrace(); + + if (options.url) { + this.url = this.urlRoot + options.url; + } + }, + + captureStackTrace: function() { + if (Error.captureStackTrace) { + Error.captureStackTrace(this, Marionette.Error); + } + }, + + toString: function() { + return this.name + ': ' + this.message + (this.url ? ' See: ' + this.url : ''); + } + }); + + Marionette.Error.extend = Marionette.extend; + + // Callbacks + // --------- + + // A simple way of managing a collection of callbacks + // and executing them at a later point in time, using jQuery's + // `Deferred` object. + Marionette.Callbacks = function() { + this._deferred = Marionette.Deferred(); + this._callbacks = []; + }; + + _.extend(Marionette.Callbacks.prototype, { + + // Add a callback to be executed. Callbacks added here are + // guaranteed to execute, even if they are added after the + // `run` method is called. + add: function(callback, contextOverride) { + var promise = _.result(this._deferred, 'promise'); + + this._callbacks.push({cb: callback, ctx: contextOverride}); + + promise.then(function(args) { + if (contextOverride){ args.context = contextOverride; } + callback.call(args.context, args.options); + }); + }, + + // Run all registered callbacks with the context specified. + // Additional callbacks can be added after this has been run + // and they will still be executed. + run: function(options, context) { + this._deferred.resolve({ + options: options, + context: context + }); + }, + + // Resets the list of callbacks to be run, allowing the same list + // to be run multiple times - whenever the `run` method is called. + reset: function() { + var callbacks = this._callbacks; + this._deferred = Marionette.Deferred(); + this._callbacks = []; + + _.each(callbacks, function(cb) { + this.add(cb.cb, cb.ctx); + }, this); + } + }); + + // Controller + // ---------- + + // A multi-purpose object to use as a controller for + // modules and routers, and as a mediator for workflow + // and coordination of other objects, views, and more. + Marionette.Controller = function(options) { + this.options = options || {}; + + if (_.isFunction(this.initialize)) { + this.initialize(this.options); + } + }; + + Marionette.Controller.extend = Marionette.extend; + + // Controller Methods + // -------------- + + // Ensure it can trigger events with Backbone.Events + _.extend(Marionette.Controller.prototype, Backbone.Events, { + destroy: function() { + Marionette._triggerMethod(this, 'before:destroy', arguments); + Marionette._triggerMethod(this, 'destroy', arguments); + + this.stopListening(); + this.off(); + return this; + }, + + // import the `triggerMethod` to trigger events with corresponding + // methods if the method exists + triggerMethod: Marionette.triggerMethod, + + // Proxy `getOption` to enable getting options from this or this.options by name. + getOption: Marionette.proxyGetOption + + }); + + // Object + // ------ + + // A Base Class that other Classes should descend from. + // Object borrows many conventions and utilities from Backbone. + Marionette.Object = function(options) { + this.options = _.extend({}, _.result(this, 'options'), options); + + this.initialize.apply(this, arguments); + }; + + Marionette.Object.extend = Marionette.extend; + + // Object Methods + // -------------- + + // Ensure it can trigger events with Backbone.Events + _.extend(Marionette.Object.prototype, Backbone.Events, { + + //this is a noop method intended to be overridden by classes that extend from this base + initialize: function() {}, + + destroy: function() { + this.triggerMethod('before:destroy'); + this.triggerMethod('destroy'); + this.stopListening(); + }, + + // Import the `triggerMethod` to trigger events with corresponding + // methods if the method exists + triggerMethod: Marionette.triggerMethod, + + // Proxy `getOption` to enable getting options from this or this.options by name. + getOption: Marionette.proxyGetOption, + + // Proxy `bindEntityEvents` to enable binding view's events from another entity. + bindEntityEvents: Marionette.proxyBindEntityEvents, + + // Proxy `unbindEntityEvents` to enable unbinding view's events from another entity. + unbindEntityEvents: Marionette.proxyUnbindEntityEvents + }); + + /* jshint maxcomplexity: 16, maxstatements: 45, maxlen: 120 */ + + // Region + // ------ + + // Manage the visual regions of your composite application. See + // http://lostechies.com/derickbailey/2011/12/12/composite-js-apps-regions-and-region-managers/ + + Marionette.Region = Marionette.Object.extend({ + constructor: function (options) { + + // set options temporarily so that we can get `el`. + // options will be overriden by Object.constructor + this.options = options || {}; + 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; + + if (!this.el) { + throw new Marionette.Error({ + name: 'NoElError', + message: 'An "el" must be specified for a region.' + }); + } + + this.$el = this.getEl(this.el); + Marionette.Object.call(this, options); + }, + + // Displays a backbone view instance inside of the region. + // Handles calling the `render` method for you. Reads content + // directly from the `el` attribute. Also calls an optional + // `onShow` and `onDestroy` method on your view, just after showing + // or just before destroying the view, respectively. + // The `preventDestroy` option can be used to prevent a view from + // the old view being destroyed on show. + // The `forceShow` option can be used to force a view to be + // re-rendered if it's already shown in the region. + show: function(view, options){ + if (!this._ensureElement()) { + return; + } + + this._ensureViewIsIntact(view); + + var showOptions = options || {}; + var isDifferentView = view !== this.currentView; + var preventDestroy = !!showOptions.preventDestroy; + var forceShow = !!showOptions.forceShow; + + // We are only changing the view if there is a current view to change to begin with + var isChangingView = !!this.currentView; + + // Only destroy the current view if we don't want to `preventDestroy` and if + // the view given in the first argument is different than `currentView` + var _shouldDestroyView = isDifferentView && !preventDestroy; + + // Only show the view given in the first argument if it is different than + // the current view or if we want to re-show the view. Note that if + // `_shouldDestroyView` is true, then `_shouldShowView` is also necessarily true. + var _shouldShowView = isDifferentView || forceShow; + + if (isChangingView) { + this.triggerMethod('before:swapOut', this.currentView, this, options); + } + + if (this.currentView) { + delete this.currentView._parent; + } + + if (_shouldDestroyView) { + this.empty(); + + // A `destroy` event is attached to the clean up manually removed views. + // We need to detach this event when a new view is going to be shown as it + // is no longer relevant. + } else if (isChangingView && _shouldShowView) { + this.currentView.off('destroy', this.empty, this); + } + + if (_shouldShowView) { + + // 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.once('destroy', this.empty, this); + view.render(); + + view._parent = this; + + if (isChangingView) { + this.triggerMethod('before:swap', view, this, options); + } + + this.triggerMethod('before:show', view, this, options); + Marionette.triggerMethodOn(view, 'before:show', view, this, options); + + if (isChangingView) { + this.triggerMethod('swapOut', this.currentView, this, options); + } + + // An array of views that we're about to display + var attachedRegion = Marionette.isNodeAttached(this.el); + + // The views that we're about to attach to the document + // It's important that we prevent _getNestedViews from being executed unnecessarily + // as it's a potentially-slow method + var displayedViews = []; + + var triggerBeforeAttach = showOptions.triggerBeforeAttach || this.triggerBeforeAttach; + var triggerAttach = showOptions.triggerAttach || this.triggerAttach; + + if (attachedRegion && triggerBeforeAttach) { + displayedViews = this._displayedViews(view); + this._triggerAttach(displayedViews, 'before:'); + } + + this.attachHtml(view); + this.currentView = view; + + if (attachedRegion && triggerAttach) { + displayedViews = this._displayedViews(view); + this._triggerAttach(displayedViews); + } + + if (isChangingView) { + this.triggerMethod('swap', view, this, options); + } + + this.triggerMethod('show', view, this, options); + Marionette.triggerMethodOn(view, 'show', view, this, options); + + return this; + } + + return this; + }, + + triggerBeforeAttach: true, + triggerAttach: true, + + _triggerAttach: function(views, prefix) { + var eventName = (prefix || '') + 'attach'; + _.each(views, function(view) { + Marionette.triggerMethodOn(view, eventName, view, this); + }, this); + }, + + _displayedViews: function(view) { + return _.union([view], _.result(view, '_getNestedViews') || []); + }, + + _ensureElement: function(){ + if (!_.isObject(this.el)) { + this.$el = this.getEl(this.el); + this.el = this.$el[0]; + } + + if (!this.$el || this.$el.length === 0) { + if (this.getOption('allowMissingEl')) { + return false; + } else { + throw new Marionette.Error('An "el" ' + this.$el.selector + ' must exist in DOM'); + } + } + return true; + }, + + _ensureViewIsIntact: function(view) { + if (!view) { + throw new Marionette.Error({ + name: 'ViewNotValid', + message: 'The view passed is undefined and therefore invalid. You must pass a view instance to show.' + }); + } + + if (view.isDestroyed) { + throw new Marionette.Error({ + name: 'ViewDestroyedError', + message: 'View (cid: "' + view.cid + '") has already been destroyed and cannot be used.' + }); + } + }, + + // 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(el) { + return Backbone.$(el, Marionette._getValue(this.options.parentEl, this)); + }, + + // Override this method to change how the new view is + // appended to the `$el` that the region is managing + attachHtml: function(view) { + this.$el.contents().detach(); + + this.el.appendChild(view.el); + }, + + // Destroy the current view, if there is one. If there is no + // current view, it does nothing and returns immediately. + empty: function() { + var view = this.currentView; + + // If there is no view in the region + // we should not remove anything + if (!view) { return; } + + view.off('destroy', this.empty, this); + this.triggerMethod('before:empty', view); + this._destroyView(); + this.triggerMethod('empty', view); + + // Remove region pointer to the currentView + delete this.currentView; + return this; + }, + + // call 'destroy' or 'remove', depending on which is found + // on the view (if showing a raw Backbone view or a Marionette View) + _destroyView: function() { + var view = this.currentView; + + if (view.destroy && !view.isDestroyed) { + view.destroy(); + } else if (view.remove) { + view.remove(); + + // appending isDestroyed to raw Backbone View allows regions + // to throw a ViewDestroyedError for this view + view.isDestroyed = true; + } + }, + + // Attach an existing view to the region. This + // will not call `render` or `onShow` for the new view, + // and will not replace the current HTML for the `el` + // of the region. + attachView: function(view) { + this.currentView = view; + return this; + }, + + // Checks whether a view is currently present within + // the region. Returns `true` if there is and `false` if + // no view is present. + hasView: function() { + 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() { + this.empty(); + + if (this.$el) { + this.el = this.$el.selector; + } + + delete this.$el; + return this; + } + + }, + + // Static Methods + { + + // Build an instance of a region by passing in a configuration object + // and a default region class to use if none is specified in the config. + // + // The config object should either be a string as a jQuery DOM selector, + // a Region class directly, or an object literal that specifies a selector, + // a custom regionClass, and any options to be supplied to the region: + // + // ```js + // { + // selector: "#foo", + // regionClass: MyCustomRegion, + // allowMissingEl: false + // } + // ``` + // + buildRegion: function(regionConfig, DefaultRegionClass) { + if (_.isString(regionConfig)) { + return this._buildRegionFromSelector(regionConfig, DefaultRegionClass); + } + + if (regionConfig.selector || regionConfig.el || regionConfig.regionClass) { + return this._buildRegionFromObject(regionConfig, DefaultRegionClass); + } + + if (_.isFunction(regionConfig)) { + return this._buildRegionFromRegionClass(regionConfig); + } + + throw new Marionette.Error({ + message: 'Improper region configuration type.', + url: 'marionette.region.html#region-configuration-types' + }); + }, + + // Build the region from a string selector like '#foo-region' + _buildRegionFromSelector: function(selector, DefaultRegionClass) { + return new DefaultRegionClass({ el: selector }); + }, + + // Build the region from a configuration object + // ```js + // { selector: '#foo', regionClass: FooRegion, allowMissingEl: false } + // ``` + _buildRegionFromObject: function(regionConfig, DefaultRegionClass) { + var RegionClass = regionConfig.regionClass || DefaultRegionClass; + var options = _.omit(regionConfig, 'selector', 'regionClass'); + + if (regionConfig.selector && !options.el) { + options.el = regionConfig.selector; + } + + return new RegionClass(options); + }, + + // Build the region directly from a given `RegionClass` + _buildRegionFromRegionClass: function(RegionClass) { + return new RegionClass(); + } + }); + + // Region Manager + // -------------- + + // Manage one or more related `Marionette.Region` objects. + Marionette.RegionManager = Marionette.Controller.extend({ + constructor: function(options) { + this._regions = {}; + + Marionette.Controller.call(this, options); + + this.addRegions(this.getOption('regions')); + }, + + // Add multiple regions using an object literal or a + // function that returns an object literal, where + // each key becomes the region name, and each value is + // the region definition. + addRegions: function(regionDefinitions, defaults) { + regionDefinitions = Marionette._getValue(regionDefinitions, this, arguments); + + return _.reduce(regionDefinitions, function(regions, definition, name) { + if (_.isString(definition)) { + definition = {selector: definition}; + } + if (definition.selector) { + definition = _.defaults({}, definition, defaults); + } + + regions[name] = this.addRegion(name, definition); + return regions; + }, {}, this); + }, + + // Add an individual region to the region manager, + // and return the region instance + addRegion: function(name, definition) { + var region; + + if (definition instanceof Marionette.Region) { + region = definition; + } else { + region = Marionette.Region.buildRegion(definition, Marionette.Region); + } + + this.triggerMethod('before:add:region', name, region); + + region._parent = this; + this._store(name, region); + + this.triggerMethod('add:region', name, region); + return region; + }, + + // Get a region by name + get: function(name) { + return this._regions[name]; + }, + + // Gets all the regions contained within + // the `regionManager` instance. + getRegions: function(){ + return _.clone(this._regions); + }, + + // Remove a region by name + removeRegion: function(name) { + var region = this._regions[name]; + this._remove(name, region); + + return region; + }, + + // Empty all regions in the region manager, and + // remove them + removeRegions: function() { + var regions = this.getRegions(); + _.each(this._regions, function(region, name) { + this._remove(name, region); + }, this); + + return regions; + }, + + // Empty all regions in the region manager, but + // leave them attached + emptyRegions: function() { + var regions = this.getRegions(); + _.invoke(regions, 'empty'); + return regions; + }, + + // Destroy all regions and shut down the region + // manager entirely + destroy: function() { + this.removeRegions(); + return Marionette.Controller.prototype.destroy.apply(this, arguments); + }, + + // internal method to store regions + _store: function(name, region) { + this._regions[name] = region; + this._setLength(); + }, + + // internal method to remove a region + _remove: function(name, region) { + this.triggerMethod('before:remove:region', name, region); + region.empty(); + region.stopListening(); + + delete region._parent; + delete this._regions[name]; + this._setLength(); + this.triggerMethod('remove:region', name, region); + }, + + // set the number of regions current held + _setLength: function() { + this.length = _.size(this._regions); + } + }); + + Marionette.actAsCollection(Marionette.RegionManager.prototype, '_regions'); + + + // Template Cache + // -------------- + + // Manage templates stored in `