From 33f4401065f6edba7890bb70acdc578dd992e427 Mon Sep 17 00:00:00 2001 From: Is Isilon Date: Sun, 31 Jan 2016 15:41:39 +0800 Subject: [PATCH] Added deps --- .../lib/backbone.marionette.js | 3958 +++++++++++++++++ bower_components/underscore/underscore-min.js | 6 + javascripts/app.js | 2 +- 3 files changed, 3965 insertions(+), 1 deletion(-) create mode 100644 bower_components/backbone.marionette/lib/backbone.marionette.js create mode 100644 bower_components/underscore/underscore-min.js diff --git a/bower_components/backbone.marionette/lib/backbone.marionette.js b/bower_components/backbone.marionette/lib/backbone.marionette.js new file mode 100644 index 0000000..1b2bb68 --- /dev/null +++ b/bower_components/backbone.marionette/lib/backbone.marionette.js @@ -0,0 +1,3958 @@ +// MarionetteJS (Backbone.Marionette) +// ---------------------------------- +// v2.4.4 +// +// Copyright (c)2015 Derick Bailey, Muted Solutions, LLC. +// Distributed under MIT license +// +// http://marionettejs.com + + +/*! + * Includes BabySitter + * https://github.com/marionettejs/backbone.babysitter/ + * + * Includes Wreqr + * https://github.com/marionettejs/backbone.wreqr/ + */ + + +(function(root, factory) { + + /* istanbul ignore next */ + if (typeof define === 'function' && define.amd) { + define(['backbone', 'underscore'], function(Backbone, _) { + return (root.Marionette = root.Mn = factory(root, Backbone, _)); + }); + } else if (typeof exports !== 'undefined') { + var Backbone = require('backbone'); + var _ = require('underscore'); + module.exports = factory(root, Backbone, _); + } else { + root.Marionette = root.Mn = factory(root, root.Backbone, root._); + } + +}(this, function(root, Backbone, _) { + 'use strict'; + + /* istanbul ignore next */ + // Backbone.BabySitter + // ------------------- + // v0.1.10 + // + // Copyright (c)2015 Derick Bailey, Muted Solutions, LLC. + // Distributed under MIT license + // + // http://github.com/marionettejs/backbone.babysitter + (function(Backbone, _) { + "use strict"; + var previousChildViewContainer = Backbone.ChildViewContainer; + // BabySitter.ChildViewContainer + // ----------------------------- + // + // Provide a container to store, retrieve and + // shut down child views. + Backbone.ChildViewContainer = function(Backbone, _) { + // Container Constructor + // --------------------- + var Container = function(views) { + this._views = {}; + this._indexByModel = {}; + this._indexByCustom = {}; + this._updateLength(); + _.each(views, this.add, this); + }; + // Container Methods + // ----------------- + _.extend(Container.prototype, { + // Add a view to this container. Stores the view + // by `cid` and makes it searchable by the model + // cid (and model itself). Optionally specify + // a custom key to store an retrieve the view. + add: function(view, customIndex) { + var viewCid = view.cid; + // store the view + this._views[viewCid] = view; + // index it by model + if (view.model) { + this._indexByModel[view.model.cid] = viewCid; + } + // index by custom + if (customIndex) { + this._indexByCustom[customIndex] = viewCid; + } + this._updateLength(); + return this; + }, + // Find a view by the model that was attached to + // it. Uses the model's `cid` to find it. + findByModel: function(model) { + return this.findByModelCid(model.cid); + }, + // Find a view by the `cid` of the model that was attached to + // it. Uses the model's `cid` to find the view `cid` and + // retrieve the view using it. + findByModelCid: function(modelCid) { + var viewCid = this._indexByModel[modelCid]; + return this.findByCid(viewCid); + }, + // Find a view by a custom indexer. + findByCustom: function(index) { + var viewCid = this._indexByCustom[index]; + return this.findByCid(viewCid); + }, + // Find by index. This is not guaranteed to be a + // stable index. + findByIndex: function(index) { + return _.values(this._views)[index]; + }, + // retrieve a view by its `cid` directly + findByCid: function(cid) { + return this._views[cid]; + }, + // Remove a view + remove: function(view) { + var viewCid = view.cid; + // delete model index + if (view.model) { + delete this._indexByModel[view.model.cid]; + } + // delete custom index + _.any(this._indexByCustom, function(cid, key) { + if (cid === viewCid) { + delete this._indexByCustom[key]; + return true; + } + }, this); + // remove the view from the container + delete this._views[viewCid]; + // update the length + this._updateLength(); + return this; + }, + // Call a method on every view in the container, + // passing parameters to the call method one at a + // time, like `function.call`. + call: function(method) { + this.apply(method, _.tail(arguments)); + }, + // Apply a method on every view in the container, + // passing parameters to the call method one at a + // time, like `function.apply`. + apply: function(method, args) { + _.each(this._views, function(view) { + if (_.isFunction(view[method])) { + view[method].apply(view, args || []); + } + }); + }, + // Update the `.length` attribute on this container + _updateLength: function() { + this.length = _.size(this._views); + } + }); + // Borrowing this code from Backbone.Collection: + // http://backbonejs.org/docs/backbone.html#section-106 + // + // Mix in methods from Underscore, for iteration, and other + // collection related features. + 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" ]; + _.each(methods, function(method) { + Container.prototype[method] = function() { + var views = _.values(this._views); + var args = [ views ].concat(_.toArray(arguments)); + return _[method].apply(_, args); + }; + }); + // return the public API + return Container; + }(Backbone, _); + Backbone.ChildViewContainer.VERSION = "0.1.10"; + Backbone.ChildViewContainer.noConflict = function() { + Backbone.ChildViewContainer = previousChildViewContainer; + return this; + }; + return Backbone.ChildViewContainer; + })(Backbone, _); + + /* istanbul ignore next */ + // Backbone.Wreqr (Backbone.Marionette) + // ---------------------------------- + // v1.3.5 + // + // Copyright (c)2015 Derick Bailey, Muted Solutions, LLC. + // Distributed under MIT license + // + // http://github.com/marionettejs/backbone.wreqr + (function(Backbone, _) { + "use strict"; + var previousWreqr = Backbone.Wreqr; + var Wreqr = Backbone.Wreqr = {}; + Backbone.Wreqr.VERSION = "1.3.5"; + Backbone.Wreqr.noConflict = function() { + Backbone.Wreqr = previousWreqr; + return this; + }; + // Handlers + // -------- + // A registry of functions to call, given a name + Wreqr.Handlers = function(Backbone, _) { + "use strict"; + // Constructor + // ----------- + var Handlers = function(options) { + this.options = options; + this._wreqrHandlers = {}; + if (_.isFunction(this.initialize)) { + this.initialize(options); + } + }; + Handlers.extend = Backbone.Model.extend; + // Instance Members + // ---------------- + _.extend(Handlers.prototype, Backbone.Events, { + // Add multiple handlers using an object literal configuration + setHandlers: function(handlers) { + _.each(handlers, function(handler, name) { + var context = null; + if (_.isObject(handler) && !_.isFunction(handler)) { + context = handler.context; + handler = handler.callback; + } + this.setHandler(name, handler, context); + }, this); + }, + // Add a handler for the given name, with an + // optional context to run the handler within + setHandler: function(name, handler, context) { + var config = { + callback: handler, + context: context + }; + this._wreqrHandlers[name] = config; + this.trigger("handler:add", name, handler, context); + }, + // Determine whether or not a handler is registered + hasHandler: function(name) { + return !!this._wreqrHandlers[name]; + }, + // Get the currently registered handler for + // the specified name. Throws an exception if + // no handler is found. + getHandler: function(name) { + var config = this._wreqrHandlers[name]; + if (!config) { + return; + } + return function() { + return config.callback.apply(config.context, arguments); + }; + }, + // Remove a handler for the specified name + removeHandler: function(name) { + delete this._wreqrHandlers[name]; + }, + // Remove all handlers from this registry + removeAllHandlers: function() { + this._wreqrHandlers = {}; + } + }); + return Handlers; + }(Backbone, _); + // Wreqr.CommandStorage + // -------------------- + // + // Store and retrieve commands for execution. + Wreqr.CommandStorage = function() { + "use strict"; + // Constructor function + var CommandStorage = function(options) { + this.options = options; + this._commands = {}; + if (_.isFunction(this.initialize)) { + this.initialize(options); + } + }; + // Instance methods + _.extend(CommandStorage.prototype, Backbone.Events, { + // Get an object literal by command name, that contains + // the `commandName` and the `instances` of all commands + // represented as an array of arguments to process + getCommands: function(commandName) { + var commands = this._commands[commandName]; + // we don't have it, so add it + if (!commands) { + // build the configuration + commands = { + command: commandName, + instances: [] + }; + // store it + this._commands[commandName] = commands; + } + return commands; + }, + // Add a command by name, to the storage and store the + // args for the command + addCommand: function(commandName, args) { + var command = this.getCommands(commandName); + command.instances.push(args); + }, + // Clear all commands for the given `commandName` + clearCommands: function(commandName) { + var command = this.getCommands(commandName); + command.instances = []; + } + }); + return CommandStorage; + }(); + // Wreqr.Commands + // -------------- + // + // A simple command pattern implementation. Register a command + // handler and execute it. + Wreqr.Commands = function(Wreqr, _) { + "use strict"; + return Wreqr.Handlers.extend({ + // default storage type + storageType: Wreqr.CommandStorage, + constructor: function(options) { + this.options = options || {}; + this._initializeStorage(this.options); + this.on("handler:add", this._executeCommands, this); + Wreqr.Handlers.prototype.constructor.apply(this, arguments); + }, + // Execute a named command with the supplied args + execute: function(name) { + name = arguments[0]; + var args = _.rest(arguments); + if (this.hasHandler(name)) { + this.getHandler(name).apply(this, args); + } else { + this.storage.addCommand(name, args); + } + }, + // Internal method to handle bulk execution of stored commands + _executeCommands: function(name, handler, context) { + var command = this.storage.getCommands(name); + // loop through and execute all the stored command instances + _.each(command.instances, function(args) { + handler.apply(context, args); + }); + this.storage.clearCommands(name); + }, + // Internal method to initialize storage either from the type's + // `storageType` or the instance `options.storageType`. + _initializeStorage: function(options) { + var storage; + var StorageType = options.storageType || this.storageType; + if (_.isFunction(StorageType)) { + storage = new StorageType(); + } else { + storage = StorageType; + } + this.storage = storage; + } + }); + }(Wreqr, _); + // Wreqr.RequestResponse + // --------------------- + // + // A simple request/response implementation. Register a + // request handler, and return a response from it + Wreqr.RequestResponse = function(Wreqr, _) { + "use strict"; + return Wreqr.Handlers.extend({ + request: function(name) { + if (this.hasHandler(name)) { + return this.getHandler(name).apply(this, _.rest(arguments)); + } + } + }); + }(Wreqr, _); + // Event Aggregator + // ---------------- + // A pub-sub object that can be used to decouple various parts + // of an application through event-driven architecture. + Wreqr.EventAggregator = function(Backbone, _) { + "use strict"; + var EA = function() {}; + // Copy the `extend` function used by Backbone's classes + EA.extend = Backbone.Model.extend; + // Copy the basic Backbone.Events on to the event aggregator + _.extend(EA.prototype, Backbone.Events); + return EA; + }(Backbone, _); + // Wreqr.Channel + // -------------- + // + // An object that wraps the three messaging systems: + // EventAggregator, RequestResponse, Commands + Wreqr.Channel = function(Wreqr) { + "use strict"; + var Channel = function(channelName) { + this.vent = new Backbone.Wreqr.EventAggregator(); + this.reqres = new Backbone.Wreqr.RequestResponse(); + this.commands = new Backbone.Wreqr.Commands(); + this.channelName = channelName; + }; + _.extend(Channel.prototype, { + // Remove all handlers from the messaging systems of this channel + reset: function() { + this.vent.off(); + this.vent.stopListening(); + this.reqres.removeAllHandlers(); + this.commands.removeAllHandlers(); + return this; + }, + // Connect a hash of events; one for each messaging system + connectEvents: function(hash, context) { + this._connect("vent", hash, context); + return this; + }, + connectCommands: function(hash, context) { + this._connect("commands", hash, context); + return this; + }, + connectRequests: function(hash, context) { + this._connect("reqres", hash, context); + return this; + }, + // Attach the handlers to a given message system `type` + _connect: function(type, hash, context) { + if (!hash) { + return; + } + context = context || this; + var method = type === "vent" ? "on" : "setHandler"; + _.each(hash, function(fn, eventName) { + this[type][method](eventName, _.bind(fn, context)); + }, this); + } + }); + return Channel; + }(Wreqr); + // Wreqr.Radio + // -------------- + // + // An object that lets you communicate with many channels. + Wreqr.radio = function(Wreqr, _) { + "use strict"; + var Radio = function() { + this._channels = {}; + this.vent = {}; + this.commands = {}; + this.reqres = {}; + this._proxyMethods(); + }; + _.extend(Radio.prototype, { + channel: function(channelName) { + if (!channelName) { + throw new Error("Channel must receive a name"); + } + return this._getChannel(channelName); + }, + _getChannel: function(channelName) { + var channel = this._channels[channelName]; + if (!channel) { + channel = new Wreqr.Channel(channelName); + this._channels[channelName] = channel; + } + return channel; + }, + _proxyMethods: function() { + _.each([ "vent", "commands", "reqres" ], function(system) { + _.each(messageSystems[system], function(method) { + this[system][method] = proxyMethod(this, system, method); + }, this); + }, this); + } + }); + var messageSystems = { + vent: [ "on", "off", "trigger", "once", "stopListening", "listenTo", "listenToOnce" ], + commands: [ "execute", "setHandler", "setHandlers", "removeHandler", "removeAllHandlers" ], + reqres: [ "request", "setHandler", "setHandlers", "removeHandler", "removeAllHandlers" ] + }; + var proxyMethod = function(radio, system, method) { + return function(channelName) { + var messageSystem = radio._getChannel(channelName)[system]; + return messageSystem[method].apply(messageSystem, _.rest(arguments)); + }; + }; + return new Radio(); + }(Wreqr, _); + return Backbone.Wreqr; + })(Backbone, _); + + var previousMarionette = root.Marionette; + var previousMn = root.Mn; + + var Marionette = Backbone.Marionette = {}; + + Marionette.VERSION = '2.4.4'; + + Marionette.noConflict = function() { + root.Marionette = previousMarionette; + root.Mn = previousMn; + return this; + }; + + Backbone.Marionette = Marionette; + + // 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); + }; + + // Merge `keys` from `options` onto `this` + Marionette.mergeOptions = function(options, keys) { + if (!options) { return; } + _.extend(this, _.pick(options, keys)); + }; + + // 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)) { + value = params ? value.apply(context, params) : value.call(context); + } + 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, properties) { + _.each(hash, function(val, key) { + if (_.isString(val)) { + hash[key] = Marionette.normalizeUIString(val, ui); + } else if (_.isObject(val) && _.isArray(properties)) { + _.extend(val, Marionette.normalizeUIValues(_.pick(val, properties), ui)); + /* Value is an object, and we got an array of embedded property names to normalize. */ + _.each(properties, function(property) { + var propertyVal = val[property]; + if (_.isString(propertyVal)) { + val[property] = Marionette.normalizeUIString(propertyVal, 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(_.drop(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) { + if (view._isDomRefreshMonitored) { return; } + view._isDomRefreshMonitored = true; + + // 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)) { + Marionette.triggerMethodOn(view, 'dom:refresh', view); + } + } + + 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, + + // A handy way to merge options onto the instance + mergeOptions: Marionette.mergeOptions, + + // 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(options) { + options = options || {}; + + this.triggerMethod('before:destroy', options); + this.triggerMethod('destroy', options); + this.stopListening(); + + return this; + }, + + // Import the `triggerMethod` to trigger events with corresponding + // methods if the method exists + triggerMethod: Marionette.triggerMethod, + + // A handy way to merge options onto the instance + mergeOptions: Marionette.mergeOptions, + + // 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); + Marionette.MonitorDOMRefresh(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); + + // make this region the view's parent, + // It's important that this parent binding happens before rendering + // so that any events the child may trigger during render can also be + // triggered on the child's ancestor views + view._parent = this; + this._renderView(view); + + 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 attachOptions = _.extend({ + triggerBeforeAttach: this.triggerBeforeAttach, + triggerAttach: this.triggerAttach + }, showOptions); + + if (attachedRegion && attachOptions.triggerBeforeAttach) { + displayedViews = this._displayedViews(view); + this._triggerAttach(displayedViews, 'before:'); + } + + this.attachHtml(view); + this.currentView = view; + + if (attachedRegion && attachOptions.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') || []); + }, + + _renderView: function(view) { + if (!view.supportsRenderLifecycle) { + Marionette.triggerMethodOn(view, 'before:render', view); + } + view.render(); + if (!view.supportsRenderLifecycle) { + Marionette.triggerMethodOn(view, 'render', view); + } + }, + + _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(options) { + var view = this.currentView; + + var emptyOptions = options || {}; + var preventDestroy = !!emptyOptions.preventDestroy; + // If there is no view in the region + // we should not remove anything + if (!view) { return this; } + + view.off('destroy', this.empty, this); + this.triggerMethod('before:empty', view); + if (!preventDestroy) { + this._destroyView(); + } + this.triggerMethod('empty', view); + + // Remove region pointer to the currentView + delete this.currentView; + + if (preventDestroy) { + this.$el.contents().detach(); + } + + 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.isDestroyed) { return; } + + if (!view.supportsDestroyLifecycle) { + Marionette.triggerMethodOn(view, 'before:destroy', view); + } + if (view.destroy) { + view.destroy(); + } else { + view.remove(); + + // appending isDestroyed to raw Backbone View allows regions + // to throw a ViewDestroyedError for this view + view.isDestroyed = true; + } + if (!view.supportsDestroyLifecycle) { + Marionette.triggerMethodOn(view, 'destroy', view); + } + }, + + // 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) { + if (this.currentView) { + delete this.currentView._parent; + } + view._parent = this; + 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 = {}; + this.length = 0; + + 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) { + if (!this._regions[name]) { + this.length++; + } + + this._regions[name] = region; + }, + + // 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.length--; + this.triggerMethod('remove:region', name, region); + } + }); + + Marionette.actAsCollection(Marionette.RegionManager.prototype, '_regions'); + + + // Template Cache + // -------------- + + // Manage templates stored in `