diff options
| author | mo khan <mo@mokhan.ca> | 2014-06-26 19:02:00 -0600 |
|---|---|---|
| committer | mo khan <mo@mokhan.ca> | 2014-06-26 19:02:00 -0600 |
| commit | 19d554e17410d57e5a45e2fadeb04d73143989cd (patch) | |
| tree | 72ebabea9fa4f7e17991640568c554e4b90eac75 /vendor | |
| parent | 241f8161d34bbd815db54e3d106186a3fadcbb71 (diff) | |
remove marionette.
Diffstat (limited to 'vendor')
| -rw-r--r-- | vendor/assets/javascripts/backbone.marionette.js | 3253 |
1 files changed, 0 insertions, 3253 deletions
diff --git a/vendor/assets/javascripts/backbone.marionette.js b/vendor/assets/javascripts/backbone.marionette.js deleted file mode 100644 index d1ebfdf6..00000000 --- a/vendor/assets/javascripts/backbone.marionette.js +++ /dev/null @@ -1,3253 +0,0 @@ -// MarionetteJS (Backbone.Marionette) -// ---------------------------------- -// v2.0.1 -// -// Copyright (c)2014 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) { - if (typeof define === 'function' && define.amd) { - define(['backbone', 'underscore'], function(Backbone, _) { - return (root.Marionette = factory(root, Backbone, _)); - }); - } else if (typeof exports !== 'undefined') { - var Backbone = require('backbone'); - var _ = require('underscore'); - module.exports = factory(root, Backbone, _); - } else { - root.Marionette = factory(root, root.Backbone, root._); - } - -}(this, function(root, Backbone, _) { - 'use strict'; - - // Backbone.BabySitter - // ------------------- - // v0.1.4 - // - // Copyright (c)2014 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" ]; - _.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.4"; - Backbone.ChildViewContainer.noConflict = function() { - Backbone.ChildViewContainer = previousChildViewContainer; - return this; - }; - return Backbone.ChildViewContainer; - })(Backbone, _); - // Backbone.Wreqr (Backbone.Marionette) - // ---------------------------------- - // v1.3.1 - // - // Copyright (c)2014 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.1"; - 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() { - var args = Array.prototype.slice.apply(arguments); - return config.callback.apply(config.context, args); - }; - }, - // 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); - var args = Array.prototype.slice.call(arguments); - Wreqr.Handlers.prototype.constructor.apply(this, args); - }, - // Execute a named command with the supplied args - execute: function(name, args) { - name = arguments[0]; - args = Array.prototype.slice.call(arguments, 1); - 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() { - var name = arguments[0]; - var args = Array.prototype.slice.call(arguments, 1); - if (this.hasHandler(name)) { - return this.getHandler(name).apply(this, args); - } - } - }); - }(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]; - var args = Array.prototype.slice.call(arguments, 1); - return messageSystem[method].apply(messageSystem, args); - }; - }; - return new Radio(); - }(Wreqr); - return Backbone.Wreqr; - })(Backbone, _); - - var previousMarionette = root.Marionette; - - var Marionette = Backbone.Marionette = {}; - - Marionette.VERSION = '2.0.1'; - - Marionette.noConflict = function() { - root.Marionette = previousMarionette; - return this; - }; - - Backbone.Marionette = Marionette; - - // Get the Deferred creator for later use - Marionette.Deferred = Backbone.$.Deferred; - - /* jshint unused: false */ - - // Helpers - // ------- - - // For slicing `arguments` in functions - var slice = Array.prototype.slice; - - function throwError(message, name) { - var error = new Error(message); - error.name = name || 'Error'; - throw error; - } - - // Marionette.extend - // ----------------- - - // Borrow the Backbone `extend` method so we can use it as needed - Marionette.extend = Backbone.Model.extend; - - // 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; } - var value; - - if (target.options && (target.options[optionName] !== undefined)) { - value = target.options[optionName]; - } else { - value = target[optionName]; - } - - return value; - }; - - // Proxy `Marionette.getOption` - Marionette.proxyGetOption = function(optionName) { - return Marionette.getOption(this, optionName); - }; - - // Marionette.normalizeMethods - // ---------------------- - - // Pass in a mapping of events => functions or function names - // and return a mapping of events => functions - Marionette.normalizeMethods = function(hash) { - var normalizedHash = {}, method; - _.each(hash, function(fn, name) { - method = fn; - if (!_.isFunction(method)) { - method = this[method]; - } - if (!method) { - return; - } - normalizedHash[name] = method; - }, this); - return normalizedHash; - }; - - - // allows for the use of the @ui. syntax within - // a given key for triggers and events - // swaps the @ui with the associated selector - Marionette.normalizeUIKeys = function(hash, ui) { - if (typeof(hash) === 'undefined') { - return; - } - - _.each(_.keys(hash), function(v) { - var pattern = /@ui.[a-zA-Z_$0-9]*/g; - if (v.match(pattern)) { - hash[v.replace(pattern, function(r) { - return ui[r.slice(4)]; - })] = hash[v]; - delete hash[v]; - } - }); - - 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-106 - 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); - }; - }); - }; - - // 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() { - - // 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(); - } - - // actual triggerMethod implementation - var triggerMethod = function(event) { - // get the method name from the event name - var methodName = 'on' + event.replace(splitter, getEventName); - var method = this[methodName]; - var result; - - // call the onMethodName if it exists - if (_.isFunction(method)) { - // pass all arguments, except the event name - result = method.apply(this, _.tail(arguments)); - } - - // trigger the event, if a trigger method exists - if (_.isFunction(this.trigger)) { - this.trigger.apply(this, arguments); - } - - return result; - }; - - return triggerMethod; - })(); - - // DOMRefresh - // ---------- - // - // 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(documentElement) { - // track when the view has been shown in the DOM, - // using a Marionette.Region (or by other means of triggering "show") - function handleShow(view) { - view._isShown = true; - triggerDOMRefresh(view); - } - - // track when the view has been rendered - function handleRender(view) { - view._isRendered = true; - triggerDOMRefresh(view); - } - - // Trigger the "dom:refresh" event and corresponding "onDomRefresh" method - function triggerDOMRefresh(view) { - if (view._isShown && view._isRendered && isInDOM(view)) { - if (_.isFunction(view.triggerMethod)) { - view.triggerMethod('dom:refresh'); - } - } - } - - function isInDOM(view) { - return documentElement.contains(view.el); - } - - // Export public API - return function(view) { - view.listenTo(view, 'show', function() { - handleShow(view); - }); - - view.listenTo(view, 'render', function() { - handleRender(view); - }); - }; - })(document.documentElement); - - - /* jshint maxparams: 5 */ - - // Marionette.bindEntityEvents & unbindEntityEvents - // --------------------------- - // - // These methods are used to bind/unbind a backbone "entity" (collection/model) - // to methods on a target object. - // - // The first parameter, `target`, must have a `listenTo` method from the - // EventBinder object. - // - // The second parameter is the entity (Backbone.Model or Backbone.Collection) - // 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) { - throwError('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; } - - // allow the bindings to be a function - if (_.isFunction(bindings)) { - bindings = bindings.call(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); - - - // 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); - } - }); - - // Marionette 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.triggerMethod = Marionette.triggerMethod; - 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() { - var args = Array.prototype.slice.call(arguments); - this.triggerMethod.apply(this, ['before:destroy'].concat(args)); - this.triggerMethod.apply(this, ['destroy'].concat(args)); - - this.stopListening(); - this.off(); - }, - - // 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 - - }); - - /* jshint maxcomplexity: 10, maxstatements: 27 */ - - // 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 = function(options) { - 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) { - throwError('An "el" must be specified for a region.', 'NoElError'); - } - - this.$el = this.getEl(this.el); - - if (this.initialize) { - var args = Array.prototype.slice.apply(arguments); - this.initialize.apply(this, args); - } - }; - - - // Region Class methods - // ------------------- - - _.extend(Marionette.Region, { - - // 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 both - // a selector and regionClass: - // - // ```js - // { - // selector: "#foo", - // regionClass: MyCustomRegion - // } - // ``` - // - buildRegion: function(regionConfig, defaultRegionClass) { - var regionIsString = _.isString(regionConfig); - var regionSelectorIsString = _.isString(regionConfig.selector); - var regionClassIsUndefined = _.isUndefined(regionConfig.regionClass); - var regionIsClass = _.isFunction(regionConfig); - - if (!regionIsClass && !regionIsString && !regionSelectorIsString) { - throwError('Region must be specified as a Region class,' + - 'a selector string or an object with selector property'); - } - - var selector, RegionClass; - - // get the selector for the region - - if (regionIsString) { - selector = regionConfig; - } - - if (regionConfig.selector) { - selector = regionConfig.selector; - delete regionConfig.selector; - } - - // get the class for the region - - if (regionIsClass) { - RegionClass = regionConfig; - } - - if (!regionIsClass && regionClassIsUndefined) { - RegionClass = defaultRegionClass; - } - - if (regionConfig.regionClass) { - RegionClass = regionConfig.regionClass; - delete regionConfig.regionClass; - } - - if (regionIsString || regionIsClass) { - regionConfig = {}; - } - - regionConfig.el = selector; - - // build the region instance - var region = new RegionClass(regionConfig); - - // override the `getEl` function if we have a parentEl - // this must be overridden to ensure the selector is found - // on the first use of the region. if we try to assign the - // region's `el` to `parentEl.find(selector)` in the object - // literal to build the region, the element will not be - // guaranteed to be in the DOM already, and will cause problems - if (regionConfig.parentEl) { - region.getEl = function(el) { - if (_.isObject(el)) { - return Backbone.$(el); - } - var parentEl = regionConfig.parentEl; - if (_.isFunction(parentEl)) { - parentEl = parentEl(); - } - return parentEl.find(el); - }; - } - - return region; - } - - }); - - // Region Instance Methods - // ----------------------- - - _.extend(Marionette.Region.prototype, Backbone.Events, { - - // 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){ - this._ensureElement(); - - 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 view to change to begin with - var isChangingView = !!this.currentView; - - // only destroy the view if we don't want to preventDestroy and the view is different - var _shouldDestroyView = !preventDestroy && isDifferentView; - - if (_shouldDestroyView) { - this.empty(); - } - - // show the view if the view is different or if you want to re-show the view - var _shouldShowView = isDifferentView || forceShow; - - if (_shouldShowView) { - view.render(); - - if (isChangingView) { - this.triggerMethod('before:swap', view); - } - - this.triggerMethod('before:show', view); - this.triggerMethod.call(view, 'before:show'); - - this.attachHtml(view); - this.currentView = view; - - if (isChangingView) { - this.triggerMethod('swap', view); - } - - this.triggerMethod('show', view); - - if (_.isFunction(view.triggerMethod)) { - view.triggerMethod('show'); - } else { - this.triggerMethod.call(view, 'show'); - } - - return this; - } - - return this; - }, - - _ensureElement: function(){ - if (!_.isObject(this.el)) { - this.$el = this.getEl(this.el); - this.el = this.$el[0]; - } - - if (!this.$el || this.$el.length === 0) { - throwError('An "el" ' + this.$el.selector + ' must exist in DOM'); - } - }, - - // Override this method to change how the region finds the - // DOM element that it manages. Return a jQuery selector object. - getEl: function(el) { - return Backbone.$(el); - }, - - // Override this method to change how the new view is - // appended to the `$el` that the region is managing - attachHtml: function(view) { - // empty the node and append new view - this.el.innerHTML=''; - 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 (!view || view.isDestroyed) { return; } - - this.triggerMethod('before:empty', view); - - // call 'destroy' or 'remove', depending on which is found - if (view.destroy) { view.destroy(); } - else if (view.remove) { view.remove(); } - - this.triggerMethod('empty', view); - - delete this.currentView; - }, - - // 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; - }, - - // 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; - }, - - // Proxy `getOption` to enable getting options from this or this.options by name. - getOption: Marionette.proxyGetOption, - - // import the `triggerMethod` to trigger events with corresponding - // methods if the method exists - triggerMethod: Marionette.triggerMethod - }); - - // Copy the `extend` function used by Backbone's classes - Marionette.Region.extend = Marionette.extend; - - // Marionette.RegionManager - // ------------------------ - // - // Manage one or more related `Marionette.Region` objects. - Marionette.RegionManager = (function(Marionette) { - - var RegionManager = Marionette.Controller.extend({ - constructor: function(options) { - this._regions = {}; - Marionette.Controller.call(this, options); - }, - - // Add multiple regions using an object literal, where - // each key becomes the region name, and each value is - // the region definition. - addRegions: function(regionDefinitions, defaults) { - var regions = {}; - - _.each(regionDefinitions, function(definition, name) { - if (_.isString(definition)) { - definition = {selector: definition}; - } - - if (definition.selector) { - definition = _.defaults({}, definition, defaults); - } - - var region = this.addRegion(name, definition); - regions[name] = region; - }, this); - - return regions; - }, - - // Add an individual region to the region manager, - // and return the region instance - addRegion: function(name, definition) { - var region; - - var isObject = _.isObject(definition); - var isString = _.isString(definition); - var hasSelector = !!definition.selector; - - if (isString || (isObject && hasSelector)) { - region = Marionette.Region.buildRegion(definition, Marionette.Region); - } else if (_.isFunction(definition)) { - region = Marionette.Region.buildRegion(definition, Marionette.Region); - } else { - region = definition; - } - - this.triggerMethod('before:add:region', name, region); - - 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); - }, - - // Empty all regions in the region manager, and - // remove them - removeRegions: function() { - _.each(this._regions, function(region, name) { - this._remove(name, region); - }, this); - }, - - // Empty all regions in the region manager, but - // leave them attached - emptyRegions: function() { - _.each(this._regions, function(region) { - region.empty(); - }, this); - }, - - // Destroy all regions and shut down the region - // manager entirely - destroy: function() { - this.removeRegions(); - 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 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(RegionManager.prototype, '_regions'); - - return RegionManager; - })(Marionette); - - - // Template Cache - // -------------- - - // Manage templates stored in `<script>` blocks, - // caching them for faster access. - Marionette.TemplateCache = function(templateId) { - this.templateId = templateId; - }; - - // TemplateCache object-level methods. Manage the template - // caches from these method calls instead of creating - // your own TemplateCache instances - _.extend(Marionette.TemplateCache, { - templateCaches: {}, - - // Get the specified template by id. Either - // retrieves the cached version, or loads it - // from the DOM. - get: function(templateId) { - var cachedTemplate = this.templateCaches[templateId]; - - if (!cachedTemplate) { - cachedTemplate = new Marionette.TemplateCache(templateId); - this.templateCaches[templateId] = cachedTemplate; - } - - return cachedTemplate.load(); - }, - - // Clear templates from the cache. If no arguments - // are specified, clears all templates: - // `clear()` - // - // If arguments are specified, clears each of the - // specified templates from the cache: - // `clear("#t1", "#t2", "...")` - clear: function() { - var i; - var args = slice.call(arguments); - var length = args.length; - - if (length > 0) { - for (i = 0; i < length; i++) { - delete this.templateCaches[args[i]]; - } - } else { - this.templateCaches = {}; - } - } - }); - - // TemplateCache instance methods, allowing each - // template cache object to manage its own state - // and know whether or not it has been loaded - _.extend(Marionette.TemplateCache.prototype, { - - // Internal method to load the template - load: function() { - // Guard clause to prevent loading this template more than once - if (this.compiledTemplate) { - return this.compiledTemplate; - } - - // Load the template and compile it - var template = this.loadTemplate(this.templateId); - this.compiledTemplate = this.compileTemplate(template); - - return this.compiledTemplate; - }, - - // Load a template from the DOM, by default. Override - // this method to provide your own template retrieval - // For asynchronous loading with AMD/RequireJS, consider - // using a template-loader plugin as described here: - // https://github.com/marionettejs/backbone.marionette/wiki/Using-marionette-with-requirejs - loadTemplate: function(templateId) { - var template = Backbone.$(templateId).html(); - - if (!template || template.length === 0) { - throwError('Could not find template: "' + templateId + '"', 'NoTemplateError'); - } - - return template; - }, - - // Pre-compile the template before caching it. Override - // this method if you do not need to pre-compile a template - // (JST / RequireJS for example) or if you want to change - // the template engine used (Handebars, etc). - compileTemplate: function(rawTemplate) { - return _.template(rawTemplate); - } - }); - - // Renderer - // -------- - - // Render a template with data by passing in the template - // selector and the data to render. - Marionette.Renderer = { - - // Render a template with data. The `template` parameter is - // passed to the `TemplateCache` object to retrieve the - // template function. Override this method to provide your own - // custom rendering and template handling for all of Marionette. - render: function(template, data) { - if (!template) { - throwError('Cannot render the template since its false, null or undefined.', - 'TemplateNotFoundError'); - } - - var templateFunc; - if (typeof template === 'function') { - templateFunc = template; - } else { - templateFunc = Marionette.TemplateCache.get(template); - } - - return templateFunc(data); - } - }; - - - /* jshint maxlen: 114, nonew: false */ - // Marionette.View - // --------------- - - // The core view class that other Marionette views extend from. - Marionette.View = Backbone.View.extend({ - - constructor: function(options) { - _.bindAll(this, 'render'); - - // this exposes view options to the view initializer - // this is a backfill since backbone removed the assignment - // of this.options - // at some point however this may be removed - this.options = _.extend({}, _.result(this, 'options'), _.isFunction(options) ? options.call(this) : options); - // parses out the @ui DSL for events - this.events = this.normalizeUIKeys(_.result(this, 'events')); - - if (_.isObject(this.behaviors)) { - new Marionette.Behaviors(this); - } - - Backbone.View.apply(this, arguments); - - Marionette.MonitorDOMRefresh(this); - this.listenTo(this, 'show', this.onShowCalled); - }, - - // Get the template for this view - // instance. You can set a `template` attribute in the view - // definition or pass a `template: "whatever"` parameter in - // to the constructor options. - getTemplate: function() { - return this.getOption('template'); - }, - - // Mix in template helper methods. Looks for a - // `templateHelpers` 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. - mixinTemplateHelpers: function(target) { - target = target || {}; - var templateHelpers = this.getOption('templateHelpers'); - if (_.isFunction(templateHelpers)) { - templateHelpers = templateHelpers.call(this); - } - return _.extend(target, templateHelpers); - }, - - - normalizeUIKeys: function(hash) { - var ui = _.result(this, 'ui'); - var uiBindings = _.result(this, '_uiBindings'); - return Marionette.normalizeUIKeys(hash, uiBindings || ui); - }, - - // Configure `triggers` to forward DOM events to view - // events. `triggers: {"click .foo": "do:foo"}` - configureTriggers: function() { - if (!this.triggers) { return; } - - var triggerEvents = {}; - - // 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 - _.each(triggers, function(value, key) { - - var hasOptions = _.isObject(value); - var eventName = hasOptions ? value.event : value; - - // build the event handler function for the DOM event - triggerEvents[key] = function(e) { - - // stop the event in its tracks - if (e) { - var prevent = e.preventDefault; - var stop = e.stopPropagation; - - var shouldPrevent = hasOptions ? value.preventDefault : prevent; - var shouldStop = hasOptions ? value.stopPropagation : stop; - - if (shouldPrevent && prevent) { prevent.apply(e); } - if (shouldStop && stop) { stop.apply(e); } - } - - // build the args for the event - var args = { - view: this, - model: this.model, - collection: this.collection - }; - - // trigger the event - this.triggerMethod(eventName, args); - }; - - }, this); - - return triggerEvents; - }, - - // Overriding Backbone.View's delegateEvents to handle - // the `triggers`, `modelEvents`, and `collectionEvents` configuration - delegateEvents: function(events) { - this._delegateDOMEvents(events); - this.bindEntityEvents(this.model, this.getOption('modelEvents')); - this.bindEntityEvents(this.collection, this.getOption('collectionEvents')); - }, - - // internal method to delegate DOM events and triggers - _delegateDOMEvents: function(events) { - events = events || this.events; - if (_.isFunction(events)) { events = events.call(this); } - - // normalize ui keys - events = this.normalizeUIKeys(events); - - var combinedEvents = {}; - - // look up if this view has behavior events - var behaviorEvents = _.result(this, 'behaviorEvents') || {}; - var triggers = this.configureTriggers(); - - // behavior events will be overriden by view events and or triggers - _.extend(combinedEvents, behaviorEvents, events, triggers); - - Backbone.View.prototype.delegateEvents.call(this, combinedEvents); - }, - - // Overriding Backbone.View's undelegateEvents to handle unbinding - // the `triggers`, `modelEvents`, and `collectionEvents` config - undelegateEvents: function() { - var args = Array.prototype.slice.call(arguments); - Backbone.View.prototype.undelegateEvents.apply(this, args); - this.unbindEntityEvents(this.model, this.getOption('modelEvents')); - this.unbindEntityEvents(this.collection, this.getOption('collectionEvents')); - }, - - // Internal method, handles the `show` event. - onShowCalled: function() {}, - - // Internal helper method to verify whether the view hasn't been destroyed - _ensureViewIsIntact: function() { - if (this.isDestroyed) { - var err = new Error('Cannot use a view thats already been destroyed.'); - err.name = 'ViewDestroyedError'; - throw err; - } - }, - - // Default `destroy` implementation, for removing a view from the - // DOM and unbinding it. Regions will call this method - // for you. You can specify an `onDestroy` method in your view to - // add custom code that is called after the view is destroyed. - destroy: function() { - if (this.isDestroyed) { return; } - - var args = Array.prototype.slice.call(arguments); - - this.triggerMethod.apply(this, ['before:destroy'].concat(args)); - - // mark as destroyed before doing the actual destroy, to - // prevent infinite loops within "destroy" event handlers - // that are trying to destroy other views - this.isDestroyed = true; - this.triggerMethod.apply(this, ['destroy'].concat(args)); - - // unbind UI elements - this.unbindUIElements(); - - // remove the view from the DOM - this.remove(); - }, - - // This method binds the elements specified in the "ui" hash inside the view's code with - // the associated jQuery selectors. - bindUIElements: function() { - 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(_.keys(bindings), function(key) { - var selector = bindings[key]; - this.ui[key] = this.$(selector); - }, this); - }, - - // This method unbinds the elements specified in the "ui" hash - unbindUIElements: function() { - if (!this.ui || !this._uiBindings) { return; } - - // delete all of the existing ui bindings - _.each(this.ui, function($el, name) { - delete this.ui[name]; - }, this); - - // reset the ui element to the original bindings configuration - this.ui = this._uiBindings; - delete this._uiBindings; - }, - - // import the `triggerMethod` to trigger events with corresponding - // methods if the method exists - triggerMethod: Marionette.triggerMethod, - - // Imports the "normalizeMethods" to transform hashes of - // events=>function references/names to a hash of events=>function references - normalizeMethods: Marionette.normalizeMethods, - - // Proxy `getOption` to enable getting options from this or this.options by name. - getOption: Marionette.proxyGetOption, - - // Proxy `unbindEntityEvents` 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 - }); - - // Item View - // --------- - - // A single item view implementation that contains code for rendering - // with underscore.js templates, serializing the view's model or collection, - // and calling several methods on extended views, such as `onRender`. - Marionette.ItemView = Marionette.View.extend({ - - // Setting up the inheritance chain which allows changes to - // Marionette.View.prototype.constructor which allows overriding - constructor: function() { - Marionette.View.apply(this, arguments); - }, - - // Serialize the model or collection for the view. If a model is - // found, `.toJSON()` is called. If a collection is found, `.toJSON()` - // is also called, but is used to populate an `items` array in the - // resulting data. If both are found, defaults to the model. - // You can override the `serializeData` method in your own view - // definition, to provide custom serialization for your view's data. - serializeData: function() { - var data = {}; - - if (this.model) { - data = this.model.toJSON(); - } - else if (this.collection) { - data = {items: this.collection.toJSON()}; - } - - return data; - }, - - // Render the view, defaulting to underscore.js templates. - // You can override this in your view definition to provide - // a very specific rendering for your view. In general, though, - // you should override the `Marionette.Renderer` object to - // change how Marionette renders views. - render: function() { - this._ensureViewIsIntact(); - - this.triggerMethod('before:render', this); - - var data = this.serializeData(); - data = this.mixinTemplateHelpers(data); - - var template = this.getTemplate(); - var html = Marionette.Renderer.render(template, data); - this.attachElContent(html); - this.bindUIElements(); - - this.triggerMethod('render', this); - - return this; - }, - - // Attaches the content of a given view. - // This method can be overriden to optimize rendering, - // or to render in a non standard way. - // - // For example, using `innerHTML` instead of `$el.html` - // - // ```js - // attachElContent: function(html) { - // this.el.innerHTML = html; - // return this; - // } - // ``` - attachElContent: function(html) { - this.$el.html(html); - - return this; - }, - - // Override the default destroy event to add a few - // more events that are triggered. - destroy: function() { - if (this.isDestroyed) { return; } - - Marionette.View.prototype.destroy.apply(this, arguments); - } - }); - - /* jshint maxstatements: 14 */ - - // Collection View - // --------------- - - // A view that iterates over a Backbone.Collection - // and renders an individual child view for each model. - Marionette.CollectionView = Marionette.View.extend({ - - // used as the prefix for child view events - // that are forwarded through the collectionview - childViewEventPrefix: 'childview', - - // constructor - // option to pass `{sort: false}` to prevent the `CollectionView` from - // maintaining the sorted order of the collection. - // This will fallback onto appending childView's to the end. - constructor: function(options){ - var initOptions = options || {}; - this.sort = _.isUndefined(initOptions.sort) ? true : initOptions.sort; - - this._initChildViewStorage(); - - Marionette.View.apply(this, arguments); - - this._initialEvents(); - this.initRenderBuffer(); - }, - - // Instead of inserting elements one by one into the page, - // it's much more performant to insert elements into a document - // fragment and then insert that document fragment into the page - initRenderBuffer: function() { - this.elBuffer = document.createDocumentFragment(); - this._bufferedChildren = []; - }, - - startBuffering: function() { - this.initRenderBuffer(); - this.isBuffering = true; - }, - - endBuffering: function() { - this.isBuffering = false; - this._triggerBeforeShowBufferedChildren(); - this.attachBuffer(this, this.elBuffer); - this._triggerShowBufferedChildren(); - this.initRenderBuffer(); - }, - - _triggerBeforeShowBufferedChildren: function() { - if (this._isShown) { - _.invoke(this._bufferedChildren, 'triggerMethod', 'before:show'); - } - }, - - _triggerShowBufferedChildren: function() { - if (this._isShown) { - _.each(this._bufferedChildren, function (child) { - if (_.isFunction(child.triggerMethod)) { - child.triggerMethod('show'); - } else { - Marionette.triggerMethod.call(child, 'show'); - } - }); - this._bufferedChildren = []; - } - }, - - // Configured the initial events that the collection view - // binds to. - _initialEvents: function() { - if (this.collection) { - this.listenTo(this.collection, 'add', this._onCollectionAdd); - this.listenTo(this.collection, 'remove', this._onCollectionRemove); - this.listenTo(this.collection, 'reset', this.render); - - if (this.sort) { - this.listenTo(this.collection, 'sort', this._sortViews); - } - } - }, - - // Handle a child added to the collection - _onCollectionAdd: function(child, collection, options) { - this.destroyEmptyView(); - var ChildView = this.getChildView(child); - var index = this.collection.indexOf(child); - this.addChild(child, ChildView, index); - }, - - // get the child view by model it holds, and remove it - _onCollectionRemove: function(model) { - var view = this.children.findByModel(model); - this.removeChildView(view); - this.checkEmpty(); - }, - - // Override from `Marionette.View` to trigger show on child views - onShowCalled: function(){ - this.children.each(function(child){ - if (_.isFunction(child.triggerMethod)) { - child.triggerMethod('show'); - } else { - Marionette.triggerMethod.call(child, 'show'); - } - }); - }, - - // Render children views. Override this method to - // provide your own implementation of a render function for - // the collection view. - render: function() { - this._ensureViewIsIntact(); - this.triggerMethod('before:render', this); - this._renderChildren(); - this.triggerMethod('render', this); - return this; - }, - - // Internal method. This checks for any changes in the order of the collection. - // If the index of any view doesn't match, it will render. - _sortViews: function(){ - // check for any changes in sort order of views - var orderChanged = this.collection.find(function(item, index){ - var view = this.children.findByModel(item); - return view && view._index !== index; - }, this); - - if (orderChanged) { - this.render(); - } - }, - - // Internal method. Separated so that CompositeView can have - // more control over events being triggered, around the rendering - // process - _renderChildren: function() { - this.startBuffering(); - - this.destroyEmptyView(); - this.destroyChildren(); - - if (!this.isEmpty(this.collection)) { - this.triggerMethod('before:render:collection', this); - this.showCollection(); - this.triggerMethod('render:collection', this); - } else { - this.showEmptyView(); - } - - this.endBuffering(); - }, - - // Internal method to loop through collection and show each child view. - showCollection: function() { - var ChildView; - this.collection.each(function(child, index) { - ChildView = this.getChildView(child); - this.addChild(child, ChildView, index); - }, this); - }, - - // Internal method to show an empty view in place of - // a collection of child views, when the collection is empty - showEmptyView: function() { - var EmptyView = this.getEmptyView(); - - if (EmptyView && !this._showingEmptyView) { - this.triggerMethod('before:render:empty'); - - this._showingEmptyView = true; - var model = new Backbone.Model(); - this.addEmptyView(model, EmptyView); - - this.triggerMethod('render:empty'); - } - }, - - // Internal method to destroy an existing emptyView instance - // if one exists. Called when a collection view has been - // rendered empty, and then a child is added to the collection. - destroyEmptyView: function() { - if (this._showingEmptyView) { - this.destroyChildren(); - delete this._showingEmptyView; - } - }, - - // Retrieve the empty view class - getEmptyView: function() { - return this.getOption('emptyView'); - }, - - // Render and show the emptyView. Similar to addChild method - // but "child:added" events are not fired, and the event from - // emptyView are not forwarded - addEmptyView: function(child, EmptyView){ - - // get the emptyViewOptions, falling back to childViewOptions - var emptyViewOptions = this.getOption('emptyViewOptions') || - this.getOption('childViewOptions'); - - if (_.isFunction(emptyViewOptions)){ - emptyViewOptions = emptyViewOptions.call(this); - } - - // build the empty view - var view = this.buildChildView(child, EmptyView, emptyViewOptions); - - // trigger the 'before:show' event on `view` if the collection view - // has already been shown - if (this._isShown){ - this.triggerMethod.call(view, 'before:show'); - } - - // Store the `emptyView` like a `childView` so we can properly - // remove and/or close it later - this.children.add(view); - - // Render it and show it - this.renderChildView(view, -1); - - // call the 'show' method if the collection view - // has already been shown - if (this._isShown){ - this.triggerMethod.call(view, 'show'); - } - }, - - // Retrieve the childView class, either from `this.options.childView` - // or from the `childView` in the object definition. The "options" - // takes precedence. - getChildView: function(child) { - var childView = this.getOption('childView'); - - if (!childView) { - throwError('A "childView" must be specified', 'NoChildViewError'); - } - - return childView; - }, - - // Render the child's view and add it to the - // HTML for the collection view at a given index. - // This will also update the indices of later views in the collection - // in order to keep the children in sync with the collection. - addChild: function(child, ChildView, index) { - var childViewOptions = this.getOption('childViewOptions'); - if (_.isFunction(childViewOptions)) { - childViewOptions = childViewOptions.call(this, child, index); - } - - var view = this.buildChildView(child, ChildView, childViewOptions); - - // increment indices of views after this one - this._updateIndices(view, true, index); - - this._addChildView(view, index); - - return view; - }, - - // Internal method. This decrements or increments the indices of views after the - // added/removed view to keep in sync with the collection. - _updateIndices: function(view, increment, index) { - if (!this.sort) { - return; - } - - if (increment) { - // assign the index to the view - view._index = index; - - // increment the index of views after this one - this.children.each(function (laterView) { - if (laterView._index >= view._index) { - laterView._index++; - } - }); - } - else { - // decrement the index of views after this one - this.children.each(function (laterView) { - if (laterView._index >= view._index) { - laterView._index--; - } - }); - } - }, - - - // Internal Method. Add the view to children and render it at - // the given index. - _addChildView: function(view, index) { - // set up the child view event forwarding - this.proxyChildEvents(view); - - this.triggerMethod('before:add:child', view); - - // Store the child view itself so we can properly - // remove and/or destroy it later - this.children.add(view); - this.renderChildView(view, index); - - if (this._isShown && !this.isBuffering){ - if (_.isFunction(view.triggerMethod)) { - view.triggerMethod('show'); - } else { - Marionette.triggerMethod.call(view, 'show'); - } - } - - this.triggerMethod('add:child', view); - }, - - // render the child view - renderChildView: function(view, index) { - view.render(); - this.attachHtml(this, view, index); - }, - - // Build a `childView` for a model in the collection. - buildChildView: function(child, ChildViewClass, childViewOptions) { - var options = _.extend({model: child}, childViewOptions); - return new ChildViewClass(options); - }, - - // Remove the child view and destroy it. - // This function also updates the indices of - // later views in the collection in order to keep - // the children in sync with the collection. - removeChildView: function(view) { - - if (view) { - this.triggerMethod('before:remove:child', view); - // call 'destroy' or 'remove', depending on which is found - if (view.destroy) { view.destroy(); } - else if (view.remove) { view.remove(); } - - this.stopListening(view); - this.children.remove(view); - this.triggerMethod('remove:child', view); - - // decrement the index of views after this one - this._updateIndices(view, false); - } - - }, - - // check if the collection is empty - isEmpty: function(collection) { - return !this.collection || this.collection.length === 0; - }, - - // If empty, show the empty view - checkEmpty: function() { - if (this.isEmpty(this.collection)) { - this.showEmptyView(); - } - }, - - // You might need to override this if you've overridden attachHtml - attachBuffer: function(collectionView, buffer) { - collectionView.$el.append(buffer); - }, - - // Append the HTML to the collection's `el`. - // Override this method to do something other - // than `.append`. - attachHtml: function(collectionView, childView, index) { - if (collectionView.isBuffering) { - // buffering happens on reset events and initial renders - // in order to reduce the number of inserts into the - // document, which are expensive. - collectionView.elBuffer.appendChild(childView.el); - collectionView._bufferedChildren.push(childView); - } - else { - // If we've already rendered the main collection, append - // the new child into the correct order if we need to. Otherwise - // append to the end. - if (!collectionView._insertBefore(childView, index)){ - collectionView._insertAfter(childView); - } - } - }, - - // Internal method. Check whether we need to insert the view into - // the correct position. - _insertBefore: function(childView, index) { - var currentView; - var findPosition = this.sort && (index < this.children.length - 1); - if (findPosition) { - // Find the view after this one - currentView = this.children.find(function (view) { - return view._index === index + 1; - }); - } - - if (currentView) { - currentView.$el.before(childView.el); - return true; - } - - return false; - }, - - // Internal method. Append a view to the end of the $el - _insertAfter: function(childView) { - this.$el.append(childView.el); - }, - - // Internal method to set up the `children` object for - // storing all of the child views - _initChildViewStorage: function() { - this.children = new Backbone.ChildViewContainer(); - }, - - // Handle cleanup and other destroying needs for the collection of views - destroy: function() { - if (this.isDestroyed) { return; } - - this.triggerMethod('before:destroy:collection'); - this.destroyChildren(); - this.triggerMethod('destroy:collection'); - - Marionette.View.prototype.destroy.apply(this, arguments); - }, - - // Destroy the child views that this collection view - // is holding on to, if any - destroyChildren: function() { - this.children.each(this.removeChildView, this); - this.checkEmpty(); - }, - - // Set up the child view event forwarding. Uses a "childview:" - // prefix in front of all forwarded events. - proxyChildEvents: function(view) { - var prefix = this.getOption('childViewEventPrefix'); - - // Forward all child view events through the parent, - // prepending "childview:" to the event name - this.listenTo(view, 'all', function() { - var args = Array.prototype.slice.call(arguments); - var rootEvent = args[0]; - var childEvents = this.normalizeMethods(_.result(this, 'childEvents')); - - args[0] = prefix + ':' + rootEvent; - args.splice(1, 0, view); - - // call collectionView childEvent if defined - if (typeof childEvents !== 'undefined' && _.isFunction(childEvents[rootEvent])) { - childEvents[rootEvent].apply(this, args.slice(1)); - } - - this.triggerMethod.apply(this, args); - }, this); - } - }); - - /* jshint maxstatements: 17, maxlen: 117 */ - - // Composite View - // -------------- - - // Used for rendering a branch-leaf, hierarchical structure. - // Extends directly from CollectionView and also renders an - // a child view as `modelView`, for the top leaf - Marionette.CompositeView = Marionette.CollectionView.extend({ - - // Setting up the inheritance chain which allows changes to - // Marionette.CollectionView.prototype.constructor which allows overriding - // option to pass '{sort: false}' to prevent the CompositeView from - // maintaining the sorted order of the collection. - // This will fallback onto appending childView's to the end. - constructor: function() { - Marionette.CollectionView.apply(this, arguments); - }, - - // Configured the initial events that the composite view - // binds to. Override this method to prevent the initial - // events, or to add your own initial events. - _initialEvents: function() { - - // Bind only after composite view is rendered to avoid adding child views - // to nonexistent childViewContainer - this.once('render', function() { - if (this.collection) { - this.listenTo(this.collection, 'add', this._onCollectionAdd); - this.listenTo(this.collection, 'remove', this._onCollectionRemove); - this.listenTo(this.collection, 'reset', this._renderChildren); - - if (this.sort) { - this.listenTo(this.collection, 'sort', this._sortViews); - } - } - }); - - }, - - // Retrieve the `childView` to be used when rendering each of - // the items in the collection. The default is to return - // `this.childView` or Marionette.CompositeView if no `childView` - // has been defined - getChildView: function(child) { - var childView = this.getOption('childView') || this.constructor; - - if (!childView) { - throwError('A "childView" must be specified', 'NoChildViewError'); - } - - return childView; - }, - - // Serialize the collection for the view. - // You can override the `serializeData` method in your own view - // definition, to provide custom serialization for your view's data. - serializeData: function() { - var data = {}; - - if (this.model) { - data = this.model.toJSON(); - } - - return data; - }, - - // Renders the model once, and the collection once. Calling - // this again will tell the model's view to re-render itself - // but the collection will not re-render. - render: function() { - this._ensureViewIsIntact(); - this.isRendered = true; - this.resetChildViewContainer(); - - this.triggerMethod('before:render', this); - - this._renderRoot(); - this._renderChildren(); - - this.triggerMethod('render', this); - return this; - }, - - _renderChildren: function() { - if (this.isRendered) { - Marionette.CollectionView.prototype._renderChildren.call(this); - } - }, - - // Render the root template that the children - // views are appended to - _renderRoot: function() { - var data = {}; - data = this.serializeData(); - data = this.mixinTemplateHelpers(data); - - this.triggerMethod('before:render:template'); - - var template = this.getTemplate(); - var html = Marionette.Renderer.render(template, data); - this.attachElContent(html); - - // the ui bindings is done here and not at the end of render since they - // will not be available until after the model is rendered, but should be - // available before the collection is rendered. - this.bindUIElements(); - this.triggerMethod('render:template'); - }, - - // Attaches the content of the root. - // This method can be overriden to optimize rendering, - // or to render in a non standard way. - // - // For example, using `innerHTML` instead of `$el.html` - // - // ```js - // attachElContent: function(html) { - // this.el.innerHTML = html; - // return this; - // } - // ``` - attachElContent: function(html) { - this.$el.html(html); - - return this; - }, - - // You might need to override this if you've overridden attachHtml - attachBuffer: function(compositeView, buffer) { - var $container = this.getChildViewContainer(compositeView); - $container.append(buffer); - }, - - // Internal method. Append a view to the end of the $el. - // Overidden from CollectionView to ensure view is appended to - // childViewContainer - _insertAfter: function (childView) { - var $container = this.getChildViewContainer(this); - $container.append(childView.el); - }, - - // Internal method to ensure an `$childViewContainer` exists, for the - // `attachHtml` method to use. - getChildViewContainer: function(containerView) { - if ('$childViewContainer' in containerView) { - return containerView.$childViewContainer; - } - - var container; - var childViewContainer = Marionette.getOption(containerView, 'childViewContainer'); - if (childViewContainer) { - - var selector = _.isFunction(childViewContainer) ? childViewContainer.call(containerView) : childViewContainer; - - if (selector.charAt(0) === '@' && containerView.ui) { - container = containerView.ui[selector.substr(4)]; - } else { - container = containerView.$(selector); - } - - if (container.length <= 0) { - throwError('The specified "childViewContainer" was not found: ' + - containerView.childViewContainer, 'ChildViewContainerMissingError'); - } - - } else { - container = containerView.$el; - } - - containerView.$childViewContainer = container; - return container; - }, - - // Internal method to reset the `$childViewContainer` on render - resetChildViewContainer: function() { - if (this.$childViewContainer) { - delete this.$childViewContainer; - } - } - }); - - // LayoutView - // ---------- - - // Used for managing application layoutViews, nested layoutViews and - // multiple regions within an application or sub-application. - // - // A specialized view class that renders an area of HTML and then - // attaches `Region` instances to the specified `regions`. - // Used for composite view management and sub-application areas. - Marionette.LayoutView = Marionette.ItemView.extend({ - regionClass: Marionette.Region, - - // Ensure the regions are available when the `initialize` method - // is called. - constructor: function(options) { - options = options || {}; - - this._firstRender = true; - this._initializeRegions(options); - - Marionette.ItemView.call(this, options); - }, - - // LayoutView's render will use the existing region objects the - // first time it is called. Subsequent calls will destroy the - // views that the regions are showing and then reset the `el` - // for the regions to the newly rendered DOM elements. - render: function() { - this._ensureViewIsIntact(); - - if (this._firstRender) { - // if this is the first render, don't do anything to - // reset the regions - this._firstRender = false; - } else { - // If this is not the first render call, then we need to - // re-initialize the `el` for each region - this._reInitializeRegions(); - } - - return Marionette.ItemView.prototype.render.apply(this, arguments); - }, - - // Handle destroying regions, and then destroy the view itself. - destroy: function() { - if (this.isDestroyed) { return; } - - this.regionManager.destroy(); - Marionette.ItemView.prototype.destroy.apply(this, arguments); - }, - - // Add a single region, by name, to the layoutView - addRegion: function(name, definition) { - this.triggerMethod('before:region:add', name); - var regions = {}; - regions[name] = definition; - return this._buildRegions(regions)[name]; - }, - - // Add multiple regions as a {name: definition, name2: def2} object literal - addRegions: function(regions) { - this.regions = _.extend({}, this.regions, regions); - return this._buildRegions(regions); - }, - - // Remove a single region from the LayoutView, by name - removeRegion: function(name) { - this.triggerMethod('before:region:remove', name); - delete this.regions[name]; - return this.regionManager.removeRegion(name); - }, - - // Provides alternative access to regions - // Accepts the region name - // getRegion('main') - getRegion: function(region) { - return this.regionManager.get(region); - }, - - // Get all regions - getRegions: function(){ - return this.regionManager.getRegions(); - }, - - // internal method to build regions - _buildRegions: function(regions) { - var that = this; - - var defaults = { - regionClass: this.getOption('regionClass'), - parentEl: function() { return that.$el; } - }; - - return this.regionManager.addRegions(regions, defaults); - }, - - // Internal method to initialize the regions that have been defined in a - // `regions` attribute on this layoutView. - _initializeRegions: function(options) { - var regions; - this._initRegionManager(); - - if (_.isFunction(this.regions)) { - regions = this.regions(options); - } else { - regions = this.regions || {}; - } - - // Enable users to define `regions` as instance options. - var regionOptions = this.getOption.call(options, 'regions'); - - // enable region options to be a function - if (_.isFunction(regionOptions)) { - regionOptions = regionOptions.call(this, options); - } - - _.extend(regions, regionOptions); - - this.addRegions(regions); - }, - - // Internal method to re-initialize all of the regions by updating the `el` that - // they point to - _reInitializeRegions: function() { - this.regionManager.emptyRegions(); - this.regionManager.each(function(region) { - region.reset(); - }); - }, - - // Enable easy overiding of the default `RegionManager` - // for customized region interactions and buisness specific - // view logic for better control over single regions. - getRegionManager: function() { - return new Marionette.RegionManager(); - }, - - // Internal method to initialize the region manager - // and all regions in it - _initRegionManager: function() { - this.regionManager = this.getRegionManager(); - - this.listenTo(this.regionManager, 'before:add:region', function(name) { - this.triggerMethod('before:add:region', name); - }); - - this.listenTo(this.regionManager, 'add:region', function(name, region) { - this[name] = region; - this.triggerMethod('add:region', name, region); - }); - - this.listenTo(this.regionManager, 'before:remove:region', function(name) { - this.triggerMethod('before:remove:region', name); - }); - - this.listenTo(this.regionManager, 'remove:region', function(name, region) { - delete this[name]; - this.triggerMethod('remove:region', name, region); - }); - } - }); - - - // Behavior - // ----------- - - // A Behavior is an isolated set of DOM / - // user interactions that can be mixed into any View. - // Behaviors allow you to blackbox View specific interactions - // into portable logical chunks, keeping your views simple and your code DRY. - - Marionette.Behavior = (function(_, Backbone) { - 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.defaults = _.result(this, 'defaults') || {}; - this.options = _.extend({}, this.defaults, options); - - // proxy behavior $ method to the view - // this is useful for doing jquery DOM lookups - // scoped to behaviors view. - this.$ = function() { - return this.view.$.apply(this.view, arguments); - }; - - // Call the initialize method passing - // the arguments from the instance constructor - this.initialize.apply(this, arguments); - } - - _.extend(Behavior.prototype, Backbone.Events, { - initialize: function() {}, - - // stopListening to behavior `onListen` events. - destroy: function() { - 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 `unbindEntityEvents` 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 - }); - - // Borrow Backbones extend implementation - // this allows us to setup a proper - // inheritence pattern that follow in suite - // with the rest of Marionette views. - Behavior.extend = Marionette.extend; - - return Behavior; - })(_, Backbone); - - /* jshint maxlen: 143, nonew: false */ - // Marionette.Behaviors - // -------- - - // Behaviors is a utility class that takes care of - // glueing your behavior instances to their given View. - // The most important part of this class is that you - // **MUST** override the class level behaviorsLookup - // method for things to work properly. - - Marionette.Behaviors = (function(Marionette, _) { - - function Behaviors(view, behaviors) { - // Behaviors defined on a view can be a flat object literal - // or it can be a function that returns an object. - behaviors = Behaviors.parseBehaviors(view, behaviors || _.result(view, 'behaviors')); - - // Wraps several of the view's methods - // calling the methods first on each behavior - // and then eventually calling the method on the view. - Behaviors.wrap(view, behaviors, [ - 'bindUIElements', 'unbindUIElements', - 'delegateEvents', 'undelegateEvents', - 'behaviorEvents', 'triggerMethod', - 'setElement', 'destroy' - ]); - } - - var methods = { - setElement: function(setElement, behaviors) { - setElement.apply(this, _.tail(arguments, 2)); - - // proxy behavior $el to the view's $el. - // This is needed because a view's $el proxy - // is not set until after setElement is called. - _.each(behaviors, function(b) { - b.$el = this.$el; - }, this); - }, - - destroy: function(destroy, behaviors) { - var args = _.tail(arguments, 2); - destroy.apply(this, args); - - // Call destroy on each behavior after - // destroying the view. - // This unbinds event listeners - // that behaviors have registerd for. - _.invoke(behaviors, 'destroy', args); - }, - - bindUIElements: function(bindUIElements, behaviors) { - bindUIElements.apply(this); - _.invoke(behaviors, bindUIElements); - }, - - unbindUIElements: function(unbindUIElements, behaviors) { - unbindUIElements.apply(this); - _.invoke(behaviors, unbindUIElements); - }, - - triggerMethod: function(triggerMethod, behaviors) { - var args = _.tail(arguments, 2); - triggerMethod.apply(this, args); - - _.each(behaviors, function(b) { - triggerMethod.apply(b, args); - }); - }, - - delegateEvents: function(delegateEvents, behaviors) { - var args = _.tail(arguments, 2); - delegateEvents.apply(this, args); - - _.each(behaviors, function(b) { - Marionette.bindEntityEvents(b, this.model, Marionette.getOption(b, 'modelEvents')); - Marionette.bindEntityEvents(b, this.collection, Marionette.getOption(b, 'collectionEvents')); - }, this); - }, - - undelegateEvents: function(undelegateEvents, behaviors) { - var args = _.tail(arguments, 2); - undelegateEvents.apply(this, args); - - _.each(behaviors, function(b) { - Marionette.unbindEntityEvents(b, this.model, Marionette.getOption(b, 'modelEvents')); - Marionette.unbindEntityEvents(b, this.collection, Marionette.getOption(b, 'collectionEvents')); - }, this); - }, - - behaviorEvents: function(behaviorEvents, behaviors) { - var _behaviorsEvents = {}; - var viewUI = _.result(this, 'ui'); - - _.each(behaviors, function(b, i) { - var _events = {}; - var behaviorEvents = _.clone(_.result(b, 'events')) || {}; - var behaviorUI = _.result(b, 'ui'); - - // Construct an internal UI hash first using - // the views UI hash and then the behaviors UI hash. - // This allows the user to use UI hash elements - // defined in the parent view as well as those - // defined in the given behavior. - var ui = _.extend({}, viewUI, behaviorUI); - - // Normalize behavior events hash to allow - // a user to use the @ui. syntax. - behaviorEvents = Marionette.normalizeUIKeys(behaviorEvents, ui); - - _.each(_.keys(behaviorEvents), function(key) { - // Append white-space at the end of each key to prevent behavior key collisions. - // This is relying on the fact that backbone events considers "click .foo" the same as - // "click .foo ". - - // +2 is used because new Array(1) or 0 is "" and not " " - var whitespace = (new Array(i + 2)).join(' '); - var eventKey = key + whitespace; - var handler = _.isFunction(behaviorEvents[key]) ? behaviorEvents[key] : b[behaviorEvents[key]]; - - _events[eventKey] = _.bind(handler, b); - }); - - _behaviorsEvents = _.extend(_behaviorsEvents, _events); - }); - - return _behaviorsEvents; - } - }; - - _.extend(Behaviors, { - - // Placeholder method to be extended by the user. - // The method should define the object that stores the behaviors. - // i.e. - // - // ```js - // Marionette.Behaviors.behaviorsLookup: function() { - // return App.Behaviors - // } - // ``` - behaviorsLookup: function() { - throw new Error('You must define where your behaviors are stored.' + - 'See https://github.com/marionettejs/backbone.marionette' + - '/blob/master/docs/marionette.behaviors.md#behaviorslookup'); - }, - - // Takes care of getting the behavior class - // given options and a key. - // If a user passes in options.behaviorClass - // default to using that. Otherwise delegate - // the lookup to the users `behaviorsLookup` implementation. - getBehaviorClass: function(options, key) { - if (options.behaviorClass) { - return options.behaviorClass; - } - - // Get behavior class can be either a flat object or a method - return _.isFunction(Behaviors.behaviorsLookup) ? Behaviors.behaviorsLookup.apply(this, arguments)[key] : Behaviors.behaviorsLookup[key]; - }, - - // Iterate over the behaviors object, for each behavior - // instantiate it and get its grouped behaviors. - parseBehaviors: function(view, behaviors) { - return _.chain(behaviors).map(function(options, key) { - var BehaviorClass = Behaviors.getBehaviorClass(options, key); - - var behavior = new BehaviorClass(options, view); - var nestedBehaviors = Behaviors.parseBehaviors(view, _.result(behavior, 'behaviors')); - - return [behavior].concat(nestedBehaviors); - }).flatten().value(); - }, - - // Wrap view internal methods so that they delegate to behaviors. For example, - // `onDestroy` should trigger destroy on all of the behaviors and then destroy itself. - // i.e. - // - // `view.delegateEvents = _.partial(methods.delegateEvents, view.delegateEvents, behaviors);` - wrap: function(view, behaviors, methodNames) { - _.each(methodNames, function(methodName) { - view[methodName] = _.partial(methods[methodName], view[methodName], behaviors); - }); - } - }); - - return Behaviors; - - })(Marionette, _); - - - // AppRouter - // --------- - - // Reduce the boilerplate code of handling route events - // and then calling a single method on another object. - // Have your routers configured to call the method on - // your object, directly. - // - // Configure an AppRouter with `appRoutes`. - // - // App routers can only take one `controller` object. - // It is recommended that you divide your controller - // objects in to smaller pieces of related functionality - // and have multiple routers / controllers, instead of - // just one giant router and controller. - // - // You can also add standard routes to an AppRouter. - - Marionette.AppRouter = Backbone.Router.extend({ - - constructor: function(options) { - Backbone.Router.apply(this, arguments); - - this.options = options || {}; - - var appRoutes = this.getOption('appRoutes'); - var controller = this._getController(); - this.processAppRoutes(controller, appRoutes); - this.on('route', this._processOnRoute, this); - }, - - // Similar to route method on a Backbone Router but - // method is called on the controller - appRoute: function(route, methodName) { - var controller = this._getController(); - this._addAppRoute(controller, route, methodName); - }, - - // process the route event and trigger the onRoute - // method call, if it exists - _processOnRoute: function(routeName, routeArgs) { - // find the path that matched - var routePath = _.invert(this.appRoutes)[routeName]; - - // make sure an onRoute is there, and call it - if (_.isFunction(this.onRoute)) { - this.onRoute(routeName, routePath, routeArgs); - } - }, - - // Internal method to process the `appRoutes` for the - // router, and turn them in to routes that trigger the - // specified method on the specified `controller`. - processAppRoutes: function(controller, appRoutes) { - if (!appRoutes) { return; } - - var routeNames = _.keys(appRoutes).reverse(); // Backbone requires reverted order of routes - - _.each(routeNames, function(route) { - this._addAppRoute(controller, route, appRoutes[route]); - }, this); - }, - - _getController: function() { - return this.getOption('controller'); - }, - - _addAppRoute: function(controller, route, methodName) { - var method = controller[methodName]; - - if (!method) { - throwError('Method "' + methodName + '" was not found on the controller'); - } - - this.route(route, methodName, _.bind(method, controller)); - }, - - // Proxy `getOption` to enable getting options from this or this.options by name. - getOption: Marionette.proxyGetOption - }); - - // Application - // ----------- - - // Contain and manage the composite application as a whole. - // Stores and starts up `Region` objects, includes an - // event aggregator as `app.vent` - Marionette.Application = function(options) { - this._initRegionManager(); - this._initCallbacks = new Marionette.Callbacks(); - var globalCh = Backbone.Wreqr.radio.channel('global'); - this.vent = globalCh.vent; - this.commands = globalCh.commands; - this.reqres = globalCh.reqres; - this.submodules = {}; - - _.extend(this, options); - }; - - _.extend(Marionette.Application.prototype, Backbone.Events, { - // Command execution, facilitated by Backbone.Wreqr.Commands - execute: function() { - this.commands.execute.apply(this.commands, arguments); - }, - - // Request/response, facilitated by Backbone.Wreqr.RequestResponse - request: function() { - return this.reqres.request.apply(this.reqres, arguments); - }, - - // Add an initializer that is either run at when the `start` - // method is called, or run immediately if added after `start` - // has already been called. - addInitializer: function(initializer) { - this._initCallbacks.add(initializer); - }, - - // kick off all of the application's processes. - // initializes all of the regions that have been added - // to the app, and runs all of the initializer functions - start: function(options) { - this.triggerMethod('before:start', options); - this._initCallbacks.run(options, this); - this.triggerMethod('start', options); - }, - - // Add regions to your app. - // Accepts a hash of named strings or Region objects - // addRegions({something: "#someRegion"}) - // addRegions({something: Region.extend({el: "#someRegion"}) }); - addRegions: function(regions) { - return this._regionManager.addRegions(regions); - }, - - // Empty all regions in the app, without removing them - emptyRegions: function() { - this._regionManager.emptyRegions(); - }, - - // Removes a region from your app, by name - // Accepts the regions name - // removeRegion('myRegion') - removeRegion: function(region) { - this._regionManager.removeRegion(region); - }, - - // Provides alternative access to regions - // Accepts the region name - // getRegion('main') - getRegion: function(region) { - return this._regionManager.get(region); - }, - - // Get all the regions from the region manager - getRegions: function(){ - return this._regionManager.getRegions(); - }, - - // Create a module, attached to the application - module: function(moduleNames, moduleDefinition) { - - // Overwrite the module class if the user specifies one - var ModuleClass = Marionette.Module.getClass(moduleDefinition); - - // slice the args, and add this application object as the - // first argument of the array - var args = slice.call(arguments); - args.unshift(this); - - // see the Marionette.Module object for more information - return ModuleClass.create.apply(ModuleClass, args); - }, - - // Internal method to set up the region manager - _initRegionManager: function() { - this._regionManager = new Marionette.RegionManager(); - - this.listenTo(this._regionManager, 'before:add:region', function(name) { - this.triggerMethod('before:add:region', name); - }); - - this.listenTo(this._regionManager, 'add:region', function(name, region) { - this[name] = region; - this.triggerMethod('add:region', name, region); - }); - - this.listenTo(this._regionManager, 'before:remove:region', function(name) { - this.triggerMethod('before:remove:region', name); - }); - - this.listenTo(this._regionManager, 'remove:region', function(name, region) { - delete this[name]; - this.triggerMethod('remove:region', name, region); - }); - }, - - // import the `triggerMethod` to trigger events with corresponding - // methods if the method exists - triggerMethod: Marionette.triggerMethod - }); - - // Copy the `extend` function used by Backbone's classes - Marionette.Application.extend = Marionette.extend; - - /* jshint maxparams: 9 */ - - // Module - // ------ - - // A simple module system, used to create privacy and encapsulation in - // Marionette applications - Marionette.Module = function(moduleName, app, options) { - this.moduleName = moduleName; - this.options = _.extend({}, this.options, options); - // Allow for a user to overide the initialize - // for a given module instance. - this.initialize = options.initialize || this.initialize; - - // Set up an internal store for sub-modules. - this.submodules = {}; - - this._setupInitializersAndFinalizers(); - - // Set an internal reference to the app - // within a module. - this.app = app; - - // By default modules start with their parents. - this.startWithParent = true; - - if (_.isFunction(this.initialize)) { - this.initialize(moduleName, app, this.options); - } - }; - - Marionette.Module.extend = Marionette.extend; - - // Extend the Module prototype with events / listenTo, so that the module - // can be used as an event aggregator or pub/sub. - _.extend(Marionette.Module.prototype, Backbone.Events, { - - // Initialize is an empty function by default. Override it with your own - // initialization logic when extending Marionette.Module. - initialize: function() {}, - - // Initializer for a specific module. Initializers are run when the - // module's `start` method is called. - addInitializer: function(callback) { - this._initializerCallbacks.add(callback); - }, - - // Finalizers are run when a module is stopped. They are used to teardown - // and finalize any variables, references, events and other code that the - // module had set up. - addFinalizer: function(callback) { - this._finalizerCallbacks.add(callback); - }, - - // Start the module, and run all of its initializers - start: function(options) { - // Prevent re-starting a module that is already started - if (this._isInitialized) { return; } - - // start the sub-modules (depth-first hierarchy) - _.each(this.submodules, function(mod) { - // check to see if we should start the sub-module with this parent - if (mod.startWithParent) { - mod.start(options); - } - }); - - // run the callbacks to "start" the current module - this.triggerMethod('before:start', options); - - this._initializerCallbacks.run(options, this); - this._isInitialized = true; - - this.triggerMethod('start', options); - }, - - // Stop this module by running its finalizers and then stop all of - // the sub-modules for this module - stop: function() { - // if we are not initialized, don't bother finalizing - if (!this._isInitialized) { return; } - this._isInitialized = false; - - this.triggerMethod('before:stop'); - - // stop the sub-modules; depth-first, to make sure the - // sub-modules are stopped / finalized before parents - _.each(this.submodules, function(mod) { mod.stop(); }); - - // run the finalizers - this._finalizerCallbacks.run(undefined, this); - - // reset the initializers and finalizers - this._initializerCallbacks.reset(); - this._finalizerCallbacks.reset(); - - this.triggerMethod('stop'); - }, - - // Configure the module with a definition function and any custom args - // that are to be passed in to the definition function - addDefinition: function(moduleDefinition, customArgs) { - this._runModuleDefinition(moduleDefinition, customArgs); - }, - - // Internal method: run the module definition function with the correct - // arguments - _runModuleDefinition: function(definition, customArgs) { - // If there is no definition short circut the method. - if (!definition) { return; } - - // build the correct list of arguments for the module definition - var args = _.flatten([ - this, - this.app, - Backbone, - Marionette, - Backbone.$, _, - customArgs - ]); - - definition.apply(this, args); - }, - - // Internal method: set up new copies of initializers and finalizers. - // Calling this method will wipe out all existing initializers and - // finalizers. - _setupInitializersAndFinalizers: function() { - this._initializerCallbacks = new Marionette.Callbacks(); - this._finalizerCallbacks = new Marionette.Callbacks(); - }, - - // import the `triggerMethod` to trigger events with corresponding - // methods if the method exists - triggerMethod: Marionette.triggerMethod - }); - - // Class methods to create modules - _.extend(Marionette.Module, { - - // Create a module, hanging off the app parameter as the parent object. - create: function(app, moduleNames, moduleDefinition) { - var module = app; - - // get the custom args passed in after the module definition and - // get rid of the module name and definition function - var customArgs = slice.call(arguments); - customArgs.splice(0, 3); - - // Split the module names and get the number of submodules. - // i.e. an example module name of `Doge.Wow.Amaze` would - // then have the potential for 3 module definitions. - moduleNames = moduleNames.split('.'); - var length = moduleNames.length; - - // store the module definition for the last module in the chain - var moduleDefinitions = []; - moduleDefinitions[length - 1] = moduleDefinition; - - // Loop through all the parts of the module definition - _.each(moduleNames, function(moduleName, i) { - var parentModule = module; - module = this._getModule(parentModule, moduleName, app, moduleDefinition); - this._addModuleDefinition(parentModule, module, moduleDefinitions[i], customArgs); - }, this); - - // Return the last module in the definition chain - return module; - }, - - _getModule: function(parentModule, moduleName, app, def, args) { - var options = _.extend({}, def); - var ModuleClass = this.getClass(def); - - // Get an existing module of this name if we have one - var module = parentModule[moduleName]; - - if (!module) { - // Create a new module if we don't have one - module = new ModuleClass(moduleName, app, options); - parentModule[moduleName] = module; - // store the module on the parent - parentModule.submodules[moduleName] = module; - } - - return module; - }, - - // ## Module Classes - // - // Module classes can be used as an alternative to the define pattern. - // The extend function of a Module is identical to the extend functions - // on other Backbone and Marionette classes. - // This allows module lifecyle events like `onStart` and `onStop` to be called directly. - getClass: function(moduleDefinition) { - var ModuleClass = Marionette.Module; - - if (!moduleDefinition) { - return ModuleClass; - } - - // If all of the module's functionality is defined inside its class, - // then the class can be passed in directly. `MyApp.module("Foo", FooModule)`. - if (moduleDefinition.prototype instanceof ModuleClass) { - return moduleDefinition; - } - - return moduleDefinition.moduleClass || ModuleClass; - }, - - // Add the module definition and add a startWithParent initializer function. - // This is complicated because module definitions are heavily overloaded - // and support an anonymous function, module class, or options object - _addModuleDefinition: function(parentModule, module, def, args) { - var fn = this._getDefine(def); - var startWithParent = this._getStartWithParent(def, module); - - if (fn) { - module.addDefinition(fn, args); - } - - this._addStartWithParent(parentModule, module, startWithParent); - }, - - _getStartWithParent: function(def, module) { - var swp; - - if (_.isFunction(def) && (def.prototype instanceof Marionette.Module)) { - swp = module.constructor.prototype.startWithParent; - return _.isUndefined(swp) ? true : swp; - } - - if (_.isObject(def)) { - swp = def.startWithParent; - return _.isUndefined(swp) ? true : swp; - } - - return true; - }, - - _getDefine: function(def) { - if (_.isFunction(def) && !(def.prototype instanceof Marionette.Module)) { - return def; - } - - if (_.isObject(def)) { - return def.define; - } - - return null; - }, - - _addStartWithParent: function(parentModule, module, startWithParent) { - module.startWithParent = module.startWithParent && startWithParent; - - if (!module.startWithParent || !!module.startWithParentIsConfigured) { - return; - } - - module.startWithParentIsConfigured = true; - - parentModule.addInitializer(function(options) { - if (module.startWithParent) { - module.start(options); - } - }); - } - }); - - - return Marionette; -})); |
