balmet.com

Unnamed repository; edit this file 'description' to name the repository.
Log | Files | Refs

backbone.marionette.js (133156B)


      1 /*! elementor - v3.0.15 - 2020-12-20 */
      2 // MarionetteJS (Backbone.Marionette)
      3 // ----------------------------------
      4 // v2.4.5.e1
      5 // Change Log:
      6 // 	e1: Fix - Compatibility with jQuery 3. (`Marionette.Region.reset`).
      7 //
      8 // Copyright (c)2016 Derick Bailey, Muted Solutions, LLC.
      9 // Distributed under MIT license
     10 //
     11 // http://marionettejs.com
     12 
     13 
     14 /*!
     15  * Includes BabySitter
     16  * https://github.com/marionettejs/backbone.babysitter/
     17  *
     18  * Includes Wreqr
     19  * https://github.com/marionettejs/backbone.wreqr/
     20  */
     21 
     22 
     23 (function(root, factory) {
     24 
     25   /* istanbul ignore next */
     26   if (typeof define === 'function' && define.amd) {
     27     define(['backbone', 'underscore'], function(Backbone, _) {
     28       return (root.Marionette = root.Mn = factory(root, Backbone, _));
     29     });
     30   } else if (typeof exports !== 'undefined') {
     31     var Backbone = require('backbone');
     32     var _ = require('underscore');
     33     module.exports = factory(root, Backbone, _);
     34   } else {
     35     root.Marionette = root.Mn = factory(root, root.Backbone, root._);
     36   }
     37 
     38 }(this, function(root, Backbone, _) {
     39   'use strict';
     40 
     41   /* istanbul ignore next */
     42   // Backbone.BabySitter
     43   // -------------------
     44   // v0.1.11
     45   //
     46   // Copyright (c)2016 Derick Bailey, Muted Solutions, LLC.
     47   // Distributed under MIT license
     48   //
     49   // http://github.com/marionettejs/backbone.babysitter
     50   (function(Backbone, _) {
     51     "use strict";
     52     var previousChildViewContainer = Backbone.ChildViewContainer;
     53     // BabySitter.ChildViewContainer
     54     // -----------------------------
     55     //
     56     // Provide a container to store, retrieve and
     57     // shut down child views.
     58     Backbone.ChildViewContainer = function(Backbone, _) {
     59       // Container Constructor
     60       // ---------------------
     61       var Container = function(views) {
     62         this._views = {};
     63         this._indexByModel = {};
     64         this._indexByCustom = {};
     65         this._updateLength();
     66         _.each(views, this.add, this);
     67       };
     68       // Container Methods
     69       // -----------------
     70       _.extend(Container.prototype, {
     71         // Add a view to this container. Stores the view
     72         // by `cid` and makes it searchable by the model
     73         // cid (and model itself). Optionally specify
     74         // a custom key to store an retrieve the view.
     75         add: function(view, customIndex) {
     76           var viewCid = view.cid;
     77           // store the view
     78           this._views[viewCid] = view;
     79           // index it by model
     80           if (view.model) {
     81             this._indexByModel[view.model.cid] = viewCid;
     82           }
     83           // index by custom
     84           if (customIndex) {
     85             this._indexByCustom[customIndex] = viewCid;
     86           }
     87           this._updateLength();
     88           return this;
     89         },
     90         // Find a view by the model that was attached to
     91         // it. Uses the model's `cid` to find it.
     92         findByModel: function(model) {
     93           return this.findByModelCid(model.cid);
     94         },
     95         // Find a view by the `cid` of the model that was attached to
     96         // it. Uses the model's `cid` to find the view `cid` and
     97         // retrieve the view using it.
     98         findByModelCid: function(modelCid) {
     99           var viewCid = this._indexByModel[modelCid];
    100           return this.findByCid(viewCid);
    101         },
    102         // Find a view by a custom indexer.
    103         findByCustom: function(index) {
    104           var viewCid = this._indexByCustom[index];
    105           return this.findByCid(viewCid);
    106         },
    107         // Find by index. This is not guaranteed to be a
    108         // stable index.
    109         findByIndex: function(index) {
    110           return _.values(this._views)[index];
    111         },
    112         // retrieve a view by its `cid` directly
    113         findByCid: function(cid) {
    114           return this._views[cid];
    115         },
    116         // Remove a view
    117         remove: function(view) {
    118           var viewCid = view.cid;
    119           // delete model index
    120           if (view.model) {
    121             delete this._indexByModel[view.model.cid];
    122           }
    123           // delete custom index
    124           _.any(this._indexByCustom, function(cid, key) {
    125             if (cid === viewCid) {
    126               delete this._indexByCustom[key];
    127               return true;
    128             }
    129           }, this);
    130           // remove the view from the container
    131           delete this._views[viewCid];
    132           // update the length
    133           this._updateLength();
    134           return this;
    135         },
    136         // Call a method on every view in the container,
    137         // passing parameters to the call method one at a
    138         // time, like `function.call`.
    139         call: function(method) {
    140           this.apply(method, _.tail(arguments));
    141         },
    142         // Apply a method on every view in the container,
    143         // passing parameters to the call method one at a
    144         // time, like `function.apply`.
    145         apply: function(method, args) {
    146           _.each(this._views, function(view) {
    147             if (_.isFunction(view[method])) {
    148               view[method].apply(view, args || []);
    149             }
    150           });
    151         },
    152         // Update the `.length` attribute on this container
    153         _updateLength: function() {
    154           this.length = _.size(this._views);
    155         }
    156       });
    157       // Borrowing this code from Backbone.Collection:
    158       // http://backbonejs.org/docs/backbone.html#section-106
    159       //
    160       // Mix in methods from Underscore, for iteration, and other
    161       // collection related features.
    162       var methods = [ "forEach", "each", "map", "find", "detect", "filter", "select", "reject", "every", "all", "some", "any", "include", "contains", "invoke", "toArray", "first", "initial", "rest", "last", "without", "isEmpty", "pluck", "reduce" ];
    163       _.each(methods, function(method) {
    164         Container.prototype[method] = function() {
    165           var views = _.values(this._views);
    166           var args = [ views ].concat(_.toArray(arguments));
    167           return _[method].apply(_, args);
    168         };
    169       });
    170       // return the public API
    171       return Container;
    172     }(Backbone, _);
    173     Backbone.ChildViewContainer.VERSION = "0.1.11";
    174     Backbone.ChildViewContainer.noConflict = function() {
    175       Backbone.ChildViewContainer = previousChildViewContainer;
    176       return this;
    177     };
    178     return Backbone.ChildViewContainer;
    179   })(Backbone, _);
    180 
    181   /* istanbul ignore next */
    182   // Backbone.Wreqr (Backbone.Marionette)
    183   // ----------------------------------
    184   // v1.3.6
    185   //
    186   // Copyright (c)2016 Derick Bailey, Muted Solutions, LLC.
    187   // Distributed under MIT license
    188   //
    189   // http://github.com/marionettejs/backbone.wreqr
    190   (function(Backbone, _) {
    191     "use strict";
    192     var previousWreqr = Backbone.Wreqr;
    193     var Wreqr = Backbone.Wreqr = {};
    194     Backbone.Wreqr.VERSION = "1.3.6";
    195     Backbone.Wreqr.noConflict = function() {
    196       Backbone.Wreqr = previousWreqr;
    197       return this;
    198     };
    199     // Handlers
    200     // --------
    201     // A registry of functions to call, given a name
    202     Wreqr.Handlers = function(Backbone, _) {
    203       "use strict";
    204       // Constructor
    205       // -----------
    206       var Handlers = function(options) {
    207         this.options = options;
    208         this._wreqrHandlers = {};
    209         if (_.isFunction(this.initialize)) {
    210           this.initialize(options);
    211         }
    212       };
    213       Handlers.extend = Backbone.Model.extend;
    214       // Instance Members
    215       // ----------------
    216       _.extend(Handlers.prototype, Backbone.Events, {
    217         // Add multiple handlers using an object literal configuration
    218         setHandlers: function(handlers) {
    219           _.each(handlers, function(handler, name) {
    220             var context = null;
    221             if (_.isObject(handler) && !_.isFunction(handler)) {
    222               context = handler.context;
    223               handler = handler.callback;
    224             }
    225             this.setHandler(name, handler, context);
    226           }, this);
    227         },
    228         // Add a handler for the given name, with an
    229         // optional context to run the handler within
    230         setHandler: function(name, handler, context) {
    231           var config = {
    232             callback: handler,
    233             context: context
    234           };
    235           this._wreqrHandlers[name] = config;
    236           this.trigger("handler:add", name, handler, context);
    237         },
    238         // Determine whether or not a handler is registered
    239         hasHandler: function(name) {
    240           return !!this._wreqrHandlers[name];
    241         },
    242         // Get the currently registered handler for
    243         // the specified name. Throws an exception if
    244         // no handler is found.
    245         getHandler: function(name) {
    246           var config = this._wreqrHandlers[name];
    247           if (!config) {
    248             return;
    249           }
    250           return function() {
    251             return config.callback.apply(config.context, arguments);
    252           };
    253         },
    254         // Remove a handler for the specified name
    255         removeHandler: function(name) {
    256           delete this._wreqrHandlers[name];
    257         },
    258         // Remove all handlers from this registry
    259         removeAllHandlers: function() {
    260           this._wreqrHandlers = {};
    261         }
    262       });
    263       return Handlers;
    264     }(Backbone, _);
    265     // Wreqr.CommandStorage
    266     // --------------------
    267     //
    268     // Store and retrieve commands for execution.
    269     Wreqr.CommandStorage = function() {
    270       "use strict";
    271       // Constructor function
    272       var CommandStorage = function(options) {
    273         this.options = options;
    274         this._commands = {};
    275         if (_.isFunction(this.initialize)) {
    276           this.initialize(options);
    277         }
    278       };
    279       // Instance methods
    280       _.extend(CommandStorage.prototype, Backbone.Events, {
    281         // Get an object literal by command name, that contains
    282         // the `commandName` and the `instances` of all commands
    283         // represented as an array of arguments to process
    284         getCommands: function(commandName) {
    285           var commands = this._commands[commandName];
    286           // we don't have it, so add it
    287           if (!commands) {
    288             // build the configuration
    289             commands = {
    290               command: commandName,
    291               instances: []
    292             };
    293             // store it
    294             this._commands[commandName] = commands;
    295           }
    296           return commands;
    297         },
    298         // Add a command by name, to the storage and store the
    299         // args for the command
    300         addCommand: function(commandName, args) {
    301           var command = this.getCommands(commandName);
    302           command.instances.push(args);
    303         },
    304         // Clear all commands for the given `commandName`
    305         clearCommands: function(commandName) {
    306           var command = this.getCommands(commandName);
    307           command.instances = [];
    308         }
    309       });
    310       return CommandStorage;
    311     }();
    312     // Wreqr.Commands
    313     // --------------
    314     //
    315     // A simple command pattern implementation. Register a command
    316     // handler and execute it.
    317     Wreqr.Commands = function(Wreqr, _) {
    318       "use strict";
    319       return Wreqr.Handlers.extend({
    320         // default storage type
    321         storageType: Wreqr.CommandStorage,
    322         constructor: function(options) {
    323           this.options = options || {};
    324           this._initializeStorage(this.options);
    325           this.on("handler:add", this._executeCommands, this);
    326           Wreqr.Handlers.prototype.constructor.apply(this, arguments);
    327         },
    328         // Execute a named command with the supplied args
    329         execute: function(name) {
    330           name = arguments[0];
    331           var args = _.rest(arguments);
    332           if (this.hasHandler(name)) {
    333             this.getHandler(name).apply(this, args);
    334           } else {
    335             this.storage.addCommand(name, args);
    336           }
    337         },
    338         // Internal method to handle bulk execution of stored commands
    339         _executeCommands: function(name, handler, context) {
    340           var command = this.storage.getCommands(name);
    341           // loop through and execute all the stored command instances
    342           _.each(command.instances, function(args) {
    343             handler.apply(context, args);
    344           });
    345           this.storage.clearCommands(name);
    346         },
    347         // Internal method to initialize storage either from the type's
    348         // `storageType` or the instance `options.storageType`.
    349         _initializeStorage: function(options) {
    350           var storage;
    351           var StorageType = options.storageType || this.storageType;
    352           if (_.isFunction(StorageType)) {
    353             storage = new StorageType();
    354           } else {
    355             storage = StorageType;
    356           }
    357           this.storage = storage;
    358         }
    359       });
    360     }(Wreqr, _);
    361     // Wreqr.RequestResponse
    362     // ---------------------
    363     //
    364     // A simple request/response implementation. Register a
    365     // request handler, and return a response from it
    366     Wreqr.RequestResponse = function(Wreqr, _) {
    367       "use strict";
    368       return Wreqr.Handlers.extend({
    369         request: function(name) {
    370           if (this.hasHandler(name)) {
    371             return this.getHandler(name).apply(this, _.rest(arguments));
    372           }
    373         }
    374       });
    375     }(Wreqr, _);
    376     // Event Aggregator
    377     // ----------------
    378     // A pub-sub object that can be used to decouple various parts
    379     // of an application through event-driven architecture.
    380     Wreqr.EventAggregator = function(Backbone, _) {
    381       "use strict";
    382       var EA = function() {};
    383       // Copy the `extend` function used by Backbone's classes
    384       EA.extend = Backbone.Model.extend;
    385       // Copy the basic Backbone.Events on to the event aggregator
    386       _.extend(EA.prototype, Backbone.Events);
    387       return EA;
    388     }(Backbone, _);
    389     // Wreqr.Channel
    390     // --------------
    391     //
    392     // An object that wraps the three messaging systems:
    393     // EventAggregator, RequestResponse, Commands
    394     Wreqr.Channel = function(Wreqr) {
    395       "use strict";
    396       var Channel = function(channelName) {
    397         this.vent = new Backbone.Wreqr.EventAggregator();
    398         this.reqres = new Backbone.Wreqr.RequestResponse();
    399         this.commands = new Backbone.Wreqr.Commands();
    400         this.channelName = channelName;
    401       };
    402       _.extend(Channel.prototype, {
    403         // Remove all handlers from the messaging systems of this channel
    404         reset: function() {
    405           this.vent.off();
    406           this.vent.stopListening();
    407           this.reqres.removeAllHandlers();
    408           this.commands.removeAllHandlers();
    409           return this;
    410         },
    411         // Connect a hash of events; one for each messaging system
    412         connectEvents: function(hash, context) {
    413           this._connect("vent", hash, context);
    414           return this;
    415         },
    416         connectCommands: function(hash, context) {
    417           this._connect("commands", hash, context);
    418           return this;
    419         },
    420         connectRequests: function(hash, context) {
    421           this._connect("reqres", hash, context);
    422           return this;
    423         },
    424         // Attach the handlers to a given message system `type`
    425         _connect: function(type, hash, context) {
    426           if (!hash) {
    427             return;
    428           }
    429           context = context || this;
    430           var method = type === "vent" ? "on" : "setHandler";
    431           _.each(hash, function(fn, eventName) {
    432             this[type][method](eventName, _.bind(fn, context));
    433           }, this);
    434         }
    435       });
    436       return Channel;
    437     }(Wreqr);
    438     // Wreqr.Radio
    439     // --------------
    440     //
    441     // An object that lets you communicate with many channels.
    442     Wreqr.radio = function(Wreqr, _) {
    443       "use strict";
    444       var Radio = function() {
    445         this._channels = {};
    446         this.vent = {};
    447         this.commands = {};
    448         this.reqres = {};
    449         this._proxyMethods();
    450       };
    451       _.extend(Radio.prototype, {
    452         channel: function(channelName) {
    453           if (!channelName) {
    454             throw new Error("Channel must receive a name");
    455           }
    456           return this._getChannel(channelName);
    457         },
    458         _getChannel: function(channelName) {
    459           var channel = this._channels[channelName];
    460           if (!channel) {
    461             channel = new Wreqr.Channel(channelName);
    462             this._channels[channelName] = channel;
    463           }
    464           return channel;
    465         },
    466         _proxyMethods: function() {
    467           _.each([ "vent", "commands", "reqres" ], function(system) {
    468             _.each(messageSystems[system], function(method) {
    469               this[system][method] = proxyMethod(this, system, method);
    470             }, this);
    471           }, this);
    472         }
    473       });
    474       var messageSystems = {
    475         vent: [ "on", "off", "trigger", "once", "stopListening", "listenTo", "listenToOnce" ],
    476         commands: [ "execute", "setHandler", "setHandlers", "removeHandler", "removeAllHandlers" ],
    477         reqres: [ "request", "setHandler", "setHandlers", "removeHandler", "removeAllHandlers" ]
    478       };
    479       var proxyMethod = function(radio, system, method) {
    480         return function(channelName) {
    481           var messageSystem = radio._getChannel(channelName)[system];
    482           return messageSystem[method].apply(messageSystem, _.rest(arguments));
    483         };
    484       };
    485       return new Radio();
    486     }(Wreqr, _);
    487     return Backbone.Wreqr;
    488   })(Backbone, _);
    489 
    490   var previousMarionette = root.Marionette;
    491   var previousMn = root.Mn;
    492 
    493   var Marionette = Backbone.Marionette = {};
    494 
    495   Marionette.VERSION = '2.4.5';
    496 
    497   Marionette.noConflict = function() {
    498     root.Marionette = previousMarionette;
    499     root.Mn = previousMn;
    500     return this;
    501   };
    502 
    503   Backbone.Marionette = Marionette;
    504 
    505   // Get the Deferred creator for later use
    506   Marionette.Deferred = Backbone.$.Deferred;
    507 
    508   /* jshint unused: false *//* global console */
    509   
    510   // Helpers
    511   // -------
    512   
    513   // Marionette.extend
    514   // -----------------
    515   
    516   // Borrow the Backbone `extend` method so we can use it as needed
    517   Marionette.extend = Backbone.Model.extend;
    518   
    519   // Marionette.isNodeAttached
    520   // -------------------------
    521   
    522   // Determine if `el` is a child of the document
    523   Marionette.isNodeAttached = function(el) {
    524     return Backbone.$.contains(document.documentElement, el);
    525   };
    526   
    527   // Merge `keys` from `options` onto `this`
    528   Marionette.mergeOptions = function(options, keys) {
    529     if (!options) { return; }
    530     _.extend(this, _.pick(options, keys));
    531   };
    532   
    533   // Marionette.getOption
    534   // --------------------
    535   
    536   // Retrieve an object, function or other value from a target
    537   // object or its `options`, with `options` taking precedence.
    538   Marionette.getOption = function(target, optionName) {
    539     if (!target || !optionName) { return; }
    540     if (target.options && (target.options[optionName] !== undefined)) {
    541       return target.options[optionName];
    542     } else {
    543       return target[optionName];
    544     }
    545   };
    546   
    547   // Proxy `Marionette.getOption`
    548   Marionette.proxyGetOption = function(optionName) {
    549     return Marionette.getOption(this, optionName);
    550   };
    551   
    552   // Similar to `_.result`, this is a simple helper
    553   // If a function is provided we call it with context
    554   // otherwise just return the value. If the value is
    555   // undefined return a default value
    556   Marionette._getValue = function(value, context, params) {
    557     if (_.isFunction(value)) {
    558       value = params ? value.apply(context, params) : value.call(context);
    559     }
    560     return value;
    561   };
    562   
    563   // Marionette.normalizeMethods
    564   // ----------------------
    565   
    566   // Pass in a mapping of events => functions or function names
    567   // and return a mapping of events => functions
    568   Marionette.normalizeMethods = function(hash) {
    569     return _.reduce(hash, function(normalizedHash, method, name) {
    570       if (!_.isFunction(method)) {
    571         method = this[method];
    572       }
    573       if (method) {
    574         normalizedHash[name] = method;
    575       }
    576       return normalizedHash;
    577     }, {}, this);
    578   };
    579   
    580   // utility method for parsing @ui. syntax strings
    581   // into associated selector
    582   Marionette.normalizeUIString = function(uiString, ui) {
    583     return uiString.replace(/@ui\.[a-zA-Z-_$0-9]*/g, function(r) {
    584       return ui[r.slice(4)];
    585     });
    586   };
    587   
    588   // allows for the use of the @ui. syntax within
    589   // a given key for triggers and events
    590   // swaps the @ui with the associated selector.
    591   // Returns a new, non-mutated, parsed events hash.
    592   Marionette.normalizeUIKeys = function(hash, ui) {
    593     return _.reduce(hash, function(memo, val, key) {
    594       var normalizedKey = Marionette.normalizeUIString(key, ui);
    595       memo[normalizedKey] = val;
    596       return memo;
    597     }, {});
    598   };
    599   
    600   // allows for the use of the @ui. syntax within
    601   // a given value for regions
    602   // swaps the @ui with the associated selector
    603   Marionette.normalizeUIValues = function(hash, ui, properties) {
    604     _.each(hash, function(val, key) {
    605       if (_.isString(val)) {
    606         hash[key] = Marionette.normalizeUIString(val, ui);
    607       } else if (_.isObject(val) && _.isArray(properties)) {
    608         _.extend(val, Marionette.normalizeUIValues(_.pick(val, properties), ui));
    609         /* Value is an object, and we got an array of embedded property names to normalize. */
    610         _.each(properties, function(property) {
    611           var propertyVal = val[property];
    612           if (_.isString(propertyVal)) {
    613             val[property] = Marionette.normalizeUIString(propertyVal, ui);
    614           }
    615         });
    616       }
    617     });
    618     return hash;
    619   };
    620   
    621   // Mix in methods from Underscore, for iteration, and other
    622   // collection related features.
    623   // Borrowing this code from Backbone.Collection:
    624   // http://backbonejs.org/docs/backbone.html#section-121
    625   Marionette.actAsCollection = function(object, listProperty) {
    626     var methods = ['forEach', 'each', 'map', 'find', 'detect', 'filter',
    627       'select', 'reject', 'every', 'all', 'some', 'any', 'include',
    628       'contains', 'invoke', 'toArray', 'first', 'initial', 'rest',
    629       'last', 'without', 'isEmpty', 'pluck'];
    630   
    631     _.each(methods, function(method) {
    632       object[method] = function() {
    633         var list = _.values(_.result(this, listProperty));
    634         var args = [list].concat(_.toArray(arguments));
    635         return _[method].apply(_, args);
    636       };
    637     });
    638   };
    639   
    640   var deprecate = Marionette.deprecate = function(message, test) {
    641     if (_.isObject(message)) {
    642       message = (
    643         message.prev + ' is going to be removed in the future. ' +
    644         'Please use ' + message.next + ' instead.' +
    645         (message.url ? ' See: ' + message.url : '')
    646       );
    647     }
    648   
    649     if ((test === undefined || !test) && !deprecate._cache[message]) {
    650       deprecate._warn('Deprecation warning: ' + message);
    651       deprecate._cache[message] = true;
    652     }
    653   };
    654   
    655   deprecate._console = typeof console !== 'undefined' ? console : {};
    656   deprecate._warn = function() {
    657     var warn = deprecate._console.warn || deprecate._console.log || function() {};
    658     return warn.apply(deprecate._console, arguments);
    659   };
    660   deprecate._cache = {};
    661   
    662   /* jshint maxstatements: 14, maxcomplexity: 7 */
    663   
    664   // Trigger Method
    665   // --------------
    666   
    667   Marionette._triggerMethod = (function() {
    668     // split the event name on the ":"
    669     var splitter = /(^|:)(\w)/gi;
    670   
    671     // take the event section ("section1:section2:section3")
    672     // and turn it in to uppercase name
    673     function getEventName(match, prefix, eventName) {
    674       return eventName.toUpperCase();
    675     }
    676   
    677     return function(context, event, args) {
    678       var noEventArg = arguments.length < 3;
    679       if (noEventArg) {
    680         args = event;
    681         event = args[0];
    682       }
    683   
    684       // get the method name from the event name
    685       var methodName = 'on' + event.replace(splitter, getEventName);
    686       var method = context[methodName];
    687       var result;
    688   
    689       // call the onMethodName if it exists
    690       if (_.isFunction(method)) {
    691         // pass all args, except the event name
    692         result = method.apply(context, noEventArg ? _.rest(args) : args);
    693       }
    694   
    695       // trigger the event, if a trigger method exists
    696       if (_.isFunction(context.trigger)) {
    697         if (noEventArg + args.length > 1) {
    698           context.trigger.apply(context, noEventArg ? args : [event].concat(_.drop(args, 0)));
    699         } else {
    700           context.trigger(event);
    701         }
    702       }
    703   
    704       return result;
    705     };
    706   })();
    707   
    708   // Trigger an event and/or a corresponding method name. Examples:
    709   //
    710   // `this.triggerMethod("foo")` will trigger the "foo" event and
    711   // call the "onFoo" method.
    712   //
    713   // `this.triggerMethod("foo:bar")` will trigger the "foo:bar" event and
    714   // call the "onFooBar" method.
    715   Marionette.triggerMethod = function(event) {
    716     return Marionette._triggerMethod(this, arguments);
    717   };
    718   
    719   // triggerMethodOn invokes triggerMethod on a specific context
    720   //
    721   // e.g. `Marionette.triggerMethodOn(view, 'show')`
    722   // will trigger a "show" event or invoke onShow the view.
    723   Marionette.triggerMethodOn = function(context) {
    724     var fnc = _.isFunction(context.triggerMethod) ?
    725                   context.triggerMethod :
    726                   Marionette.triggerMethod;
    727   
    728     return fnc.apply(context, _.rest(arguments));
    729   };
    730   
    731   // DOM Refresh
    732   // -----------
    733   
    734   // Monitor a view's state, and after it has been rendered and shown
    735   // in the DOM, trigger a "dom:refresh" event every time it is
    736   // re-rendered.
    737   
    738   Marionette.MonitorDOMRefresh = function(view) {
    739     if (view._isDomRefreshMonitored) { return; }
    740     view._isDomRefreshMonitored = true;
    741   
    742     // track when the view has been shown in the DOM,
    743     // using a Marionette.Region (or by other means of triggering "show")
    744     function handleShow() {
    745       view._isShown = true;
    746       triggerDOMRefresh();
    747     }
    748   
    749     // track when the view has been rendered
    750     function handleRender() {
    751       view._isRendered = true;
    752       triggerDOMRefresh();
    753     }
    754   
    755     // Trigger the "dom:refresh" event and corresponding "onDomRefresh" method
    756     function triggerDOMRefresh() {
    757       if (view._isShown && view._isRendered && Marionette.isNodeAttached(view.el)) {
    758         Marionette.triggerMethodOn(view, 'dom:refresh', view);
    759       }
    760     }
    761   
    762     view.on({
    763       show: handleShow,
    764       render: handleRender
    765     });
    766   };
    767   
    768   /* jshint maxparams: 5 */
    769   
    770   // Bind Entity Events & Unbind Entity Events
    771   // -----------------------------------------
    772   //
    773   // These methods are used to bind/unbind a backbone "entity" (e.g. collection/model)
    774   // to methods on a target object.
    775   //
    776   // The first parameter, `target`, must have the Backbone.Events module mixed in.
    777   //
    778   // The second parameter is the `entity` (Backbone.Model, Backbone.Collection or
    779   // any object that has Backbone.Events mixed in) to bind the events from.
    780   //
    781   // The third parameter is a hash of { "event:name": "eventHandler" }
    782   // configuration. Multiple handlers can be separated by a space. A
    783   // function can be supplied instead of a string handler name.
    784   
    785   (function(Marionette) {
    786     'use strict';
    787   
    788     // Bind the event to handlers specified as a string of
    789     // handler names on the target object
    790     function bindFromStrings(target, entity, evt, methods) {
    791       var methodNames = methods.split(/\s+/);
    792   
    793       _.each(methodNames, function(methodName) {
    794   
    795         var method = target[methodName];
    796         if (!method) {
    797           throw new Marionette.Error('Method "' + methodName +
    798             '" was configured as an event handler, but does not exist.');
    799         }
    800   
    801         target.listenTo(entity, evt, method);
    802       });
    803     }
    804   
    805     // Bind the event to a supplied callback function
    806     function bindToFunction(target, entity, evt, method) {
    807       target.listenTo(entity, evt, method);
    808     }
    809   
    810     // Bind the event to handlers specified as a string of
    811     // handler names on the target object
    812     function unbindFromStrings(target, entity, evt, methods) {
    813       var methodNames = methods.split(/\s+/);
    814   
    815       _.each(methodNames, function(methodName) {
    816         var method = target[methodName];
    817         target.stopListening(entity, evt, method);
    818       });
    819     }
    820   
    821     // Bind the event to a supplied callback function
    822     function unbindToFunction(target, entity, evt, method) {
    823       target.stopListening(entity, evt, method);
    824     }
    825   
    826     // generic looping function
    827     function iterateEvents(target, entity, bindings, functionCallback, stringCallback) {
    828       if (!entity || !bindings) { return; }
    829   
    830       // type-check bindings
    831       if (!_.isObject(bindings)) {
    832         throw new Marionette.Error({
    833           message: 'Bindings must be an object or function.',
    834           url: 'marionette.functions.html#marionettebindentityevents'
    835         });
    836       }
    837   
    838       // allow the bindings to be a function
    839       bindings = Marionette._getValue(bindings, target);
    840   
    841       // iterate the bindings and bind them
    842       _.each(bindings, function(methods, evt) {
    843   
    844         // allow for a function as the handler,
    845         // or a list of event names as a string
    846         if (_.isFunction(methods)) {
    847           functionCallback(target, entity, evt, methods);
    848         } else {
    849           stringCallback(target, entity, evt, methods);
    850         }
    851   
    852       });
    853     }
    854   
    855     // Export Public API
    856     Marionette.bindEntityEvents = function(target, entity, bindings) {
    857       iterateEvents(target, entity, bindings, bindToFunction, bindFromStrings);
    858     };
    859   
    860     Marionette.unbindEntityEvents = function(target, entity, bindings) {
    861       iterateEvents(target, entity, bindings, unbindToFunction, unbindFromStrings);
    862     };
    863   
    864     // Proxy `bindEntityEvents`
    865     Marionette.proxyBindEntityEvents = function(entity, bindings) {
    866       return Marionette.bindEntityEvents(this, entity, bindings);
    867     };
    868   
    869     // Proxy `unbindEntityEvents`
    870     Marionette.proxyUnbindEntityEvents = function(entity, bindings) {
    871       return Marionette.unbindEntityEvents(this, entity, bindings);
    872     };
    873   })(Marionette);
    874   
    875 
    876   // Error
    877   // -----
    878   
    879   var errorProps = ['description', 'fileName', 'lineNumber', 'name', 'message', 'number'];
    880   
    881   Marionette.Error = Marionette.extend.call(Error, {
    882     urlRoot: 'http://marionettejs.com/docs/v' + Marionette.VERSION + '/',
    883   
    884     constructor: function(message, options) {
    885       if (_.isObject(message)) {
    886         options = message;
    887         message = options.message;
    888       } else if (!options) {
    889         options = {};
    890       }
    891   
    892       var error = Error.call(this, message);
    893       _.extend(this, _.pick(error, errorProps), _.pick(options, errorProps));
    894   
    895       this.captureStackTrace();
    896   
    897       if (options.url) {
    898         this.url = this.urlRoot + options.url;
    899       }
    900     },
    901   
    902     captureStackTrace: function() {
    903       if (Error.captureStackTrace) {
    904         Error.captureStackTrace(this, Marionette.Error);
    905       }
    906     },
    907   
    908     toString: function() {
    909       return this.name + ': ' + this.message + (this.url ? ' See: ' + this.url : '');
    910     }
    911   });
    912   
    913   Marionette.Error.extend = Marionette.extend;
    914   
    915   // Callbacks
    916   // ---------
    917   
    918   // A simple way of managing a collection of callbacks
    919   // and executing them at a later point in time, using jQuery's
    920   // `Deferred` object.
    921   Marionette.Callbacks = function() {
    922     this._deferred = Marionette.Deferred();
    923     this._callbacks = [];
    924   };
    925   
    926   _.extend(Marionette.Callbacks.prototype, {
    927   
    928     // Add a callback to be executed. Callbacks added here are
    929     // guaranteed to execute, even if they are added after the
    930     // `run` method is called.
    931     add: function(callback, contextOverride) {
    932       var promise = _.result(this._deferred, 'promise');
    933   
    934       this._callbacks.push({cb: callback, ctx: contextOverride});
    935   
    936       promise.then(function(args) {
    937         if (contextOverride) { args.context = contextOverride; }
    938         callback.call(args.context, args.options);
    939       });
    940     },
    941   
    942     // Run all registered callbacks with the context specified.
    943     // Additional callbacks can be added after this has been run
    944     // and they will still be executed.
    945     run: function(options, context) {
    946       this._deferred.resolve({
    947         options: options,
    948         context: context
    949       });
    950     },
    951   
    952     // Resets the list of callbacks to be run, allowing the same list
    953     // to be run multiple times - whenever the `run` method is called.
    954     reset: function() {
    955       var callbacks = this._callbacks;
    956       this._deferred = Marionette.Deferred();
    957       this._callbacks = [];
    958   
    959       _.each(callbacks, function(cb) {
    960         this.add(cb.cb, cb.ctx);
    961       }, this);
    962     }
    963   });
    964   
    965   // Controller
    966   // ----------
    967   
    968   // A multi-purpose object to use as a controller for
    969   // modules and routers, and as a mediator for workflow
    970   // and coordination of other objects, views, and more.
    971   Marionette.Controller = function(options) {
    972     this.options = options || {};
    973   
    974     if (_.isFunction(this.initialize)) {
    975       this.initialize(this.options);
    976     }
    977   };
    978   
    979   Marionette.Controller.extend = Marionette.extend;
    980   
    981   // Controller Methods
    982   // --------------
    983   
    984   // Ensure it can trigger events with Backbone.Events
    985   _.extend(Marionette.Controller.prototype, Backbone.Events, {
    986     destroy: function() {
    987       Marionette._triggerMethod(this, 'before:destroy', arguments);
    988       Marionette._triggerMethod(this, 'destroy', arguments);
    989   
    990       this.stopListening();
    991       this.off();
    992       return this;
    993     },
    994   
    995     // import the `triggerMethod` to trigger events with corresponding
    996     // methods if the method exists
    997     triggerMethod: Marionette.triggerMethod,
    998   
    999     // A handy way to merge options onto the instance
   1000     mergeOptions: Marionette.mergeOptions,
   1001   
   1002     // Proxy `getOption` to enable getting options from this or this.options by name.
   1003     getOption: Marionette.proxyGetOption
   1004   
   1005   });
   1006   
   1007   // Object
   1008   // ------
   1009   
   1010   // A Base Class that other Classes should descend from.
   1011   // Object borrows many conventions and utilities from Backbone.
   1012   Marionette.Object = function(options) {
   1013     this.options = _.extend({}, _.result(this, 'options'), options);
   1014   
   1015     this.initialize.apply(this, arguments);
   1016   };
   1017   
   1018   Marionette.Object.extend = Marionette.extend;
   1019   
   1020   // Object Methods
   1021   // --------------
   1022   
   1023   // Ensure it can trigger events with Backbone.Events
   1024   _.extend(Marionette.Object.prototype, Backbone.Events, {
   1025   
   1026     //this is a noop method intended to be overridden by classes that extend from this base
   1027     initialize: function() {},
   1028   
   1029     destroy: function(options) {
   1030       options = options || {};
   1031   
   1032       this.triggerMethod('before:destroy', options);
   1033       this.triggerMethod('destroy', options);
   1034       this.stopListening();
   1035   
   1036       return this;
   1037     },
   1038   
   1039     // Import the `triggerMethod` to trigger events with corresponding
   1040     // methods if the method exists
   1041     triggerMethod: Marionette.triggerMethod,
   1042   
   1043     // A handy way to merge options onto the instance
   1044     mergeOptions: Marionette.mergeOptions,
   1045   
   1046     // Proxy `getOption` to enable getting options from this or this.options by name.
   1047     getOption: Marionette.proxyGetOption,
   1048   
   1049     // Proxy `bindEntityEvents` to enable binding view's events from another entity.
   1050     bindEntityEvents: Marionette.proxyBindEntityEvents,
   1051   
   1052     // Proxy `unbindEntityEvents` to enable unbinding view's events from another entity.
   1053     unbindEntityEvents: Marionette.proxyUnbindEntityEvents
   1054   });
   1055   
   1056   /* jshint maxcomplexity: 16, maxstatements: 45, maxlen: 120 */
   1057   
   1058   // Region
   1059   // ------
   1060   
   1061   // Manage the visual regions of your composite application. See
   1062   // http://lostechies.com/derickbailey/2011/12/12/composite-js-apps-regions-and-region-managers/
   1063   
   1064   Marionette.Region = Marionette.Object.extend({
   1065     constructor: function(options) {
   1066   
   1067       // set options temporarily so that we can get `el`.
   1068       // options will be overriden by Object.constructor
   1069       this.options = options || {};
   1070       this.el = this.getOption('el');
   1071   
   1072       // Handle when this.el is passed in as a $ wrapped element.
   1073       this.el = this.el instanceof Backbone.$ ? this.el[0] : this.el;
   1074   
   1075       if (!this.el) {
   1076         throw new Marionette.Error({
   1077           name: 'NoElError',
   1078           message: 'An "el" must be specified for a region.'
   1079         });
   1080       }
   1081   
   1082       this.$el = this.getEl(this.el);
   1083       Marionette.Object.call(this, options);
   1084     },
   1085   
   1086     // Displays a backbone view instance inside of the region.
   1087     // Handles calling the `render` method for you. Reads content
   1088     // directly from the `el` attribute. Also calls an optional
   1089     // `onShow` and `onDestroy` method on your view, just after showing
   1090     // or just before destroying the view, respectively.
   1091     // The `preventDestroy` option can be used to prevent a view from
   1092     // the old view being destroyed on show.
   1093     // The `forceShow` option can be used to force a view to be
   1094     // re-rendered if it's already shown in the region.
   1095     show: function(view, options) {
   1096       if (!this._ensureElement()) {
   1097         return;
   1098       }
   1099   
   1100       this._ensureViewIsIntact(view);
   1101       Marionette.MonitorDOMRefresh(view);
   1102   
   1103       var showOptions     = options || {};
   1104       var isDifferentView = view !== this.currentView;
   1105       var preventDestroy  = !!showOptions.preventDestroy;
   1106       var forceShow       = !!showOptions.forceShow;
   1107   
   1108       // We are only changing the view if there is a current view to change to begin with
   1109       var isChangingView = !!this.currentView;
   1110   
   1111       // Only destroy the current view if we don't want to `preventDestroy` and if
   1112       // the view given in the first argument is different than `currentView`
   1113       var _shouldDestroyView = isDifferentView && !preventDestroy;
   1114   
   1115       // Only show the view given in the first argument if it is different than
   1116       // the current view or if we want to re-show the view. Note that if
   1117       // `_shouldDestroyView` is true, then `_shouldShowView` is also necessarily true.
   1118       var _shouldShowView = isDifferentView || forceShow;
   1119   
   1120       if (isChangingView) {
   1121         this.triggerMethod('before:swapOut', this.currentView, this, options);
   1122       }
   1123   
   1124       if (this.currentView && isDifferentView) {
   1125         delete this.currentView._parent;
   1126       }
   1127   
   1128       if (_shouldDestroyView) {
   1129         this.empty();
   1130   
   1131       // A `destroy` event is attached to the clean up manually removed views.
   1132       // We need to detach this event when a new view is going to be shown as it
   1133       // is no longer relevant.
   1134       } else if (isChangingView && _shouldShowView) {
   1135         this.currentView.off('destroy', this.empty, this);
   1136       }
   1137   
   1138       if (_shouldShowView) {
   1139   
   1140         // We need to listen for if a view is destroyed
   1141         // in a way other than through the region.
   1142         // If this happens we need to remove the reference
   1143         // to the currentView since once a view has been destroyed
   1144         // we can not reuse it.
   1145         view.once('destroy', this.empty, this);
   1146   
   1147         // make this region the view's parent,
   1148         // It's important that this parent binding happens before rendering
   1149         // so that any events the child may trigger during render can also be
   1150         // triggered on the child's ancestor views
   1151         view._parent = this;
   1152         this._renderView(view);
   1153   
   1154         if (isChangingView) {
   1155           this.triggerMethod('before:swap', view, this, options);
   1156         }
   1157   
   1158         this.triggerMethod('before:show', view, this, options);
   1159         Marionette.triggerMethodOn(view, 'before:show', view, this, options);
   1160   
   1161         if (isChangingView) {
   1162           this.triggerMethod('swapOut', this.currentView, this, options);
   1163         }
   1164   
   1165         // An array of views that we're about to display
   1166         var attachedRegion = Marionette.isNodeAttached(this.el);
   1167   
   1168         // The views that we're about to attach to the document
   1169         // It's important that we prevent _getNestedViews from being executed unnecessarily
   1170         // as it's a potentially-slow method
   1171         var displayedViews = [];
   1172   
   1173         var attachOptions = _.extend({
   1174           triggerBeforeAttach: this.triggerBeforeAttach,
   1175           triggerAttach: this.triggerAttach
   1176         }, showOptions);
   1177   
   1178         if (attachedRegion && attachOptions.triggerBeforeAttach) {
   1179           displayedViews = this._displayedViews(view);
   1180           this._triggerAttach(displayedViews, 'before:');
   1181         }
   1182   
   1183         this.attachHtml(view);
   1184         this.currentView = view;
   1185   
   1186         if (attachedRegion && attachOptions.triggerAttach) {
   1187           displayedViews = this._displayedViews(view);
   1188           this._triggerAttach(displayedViews);
   1189         }
   1190   
   1191         if (isChangingView) {
   1192           this.triggerMethod('swap', view, this, options);
   1193         }
   1194   
   1195         this.triggerMethod('show', view, this, options);
   1196         Marionette.triggerMethodOn(view, 'show', view, this, options);
   1197   
   1198         return this;
   1199       }
   1200   
   1201       return this;
   1202     },
   1203   
   1204     triggerBeforeAttach: true,
   1205     triggerAttach: true,
   1206   
   1207     _triggerAttach: function(views, prefix) {
   1208       var eventName = (prefix || '') + 'attach';
   1209       _.each(views, function(view) {
   1210         Marionette.triggerMethodOn(view, eventName, view, this);
   1211       }, this);
   1212     },
   1213   
   1214     _displayedViews: function(view) {
   1215       return _.union([view], _.result(view, '_getNestedViews') || []);
   1216     },
   1217   
   1218     _renderView: function(view) {
   1219       if (!view.supportsRenderLifecycle) {
   1220         Marionette.triggerMethodOn(view, 'before:render', view);
   1221       }
   1222       view.render();
   1223       if (!view.supportsRenderLifecycle) {
   1224         Marionette.triggerMethodOn(view, 'render', view);
   1225       }
   1226     },
   1227   
   1228     _ensureElement: function() {
   1229       if (!_.isObject(this.el)) {
   1230         this.$el = this.getEl(this.el);
   1231         this.el = this.$el[0];
   1232       }
   1233   
   1234       if (!this.$el || this.$el.length === 0) {
   1235         if (this.getOption('allowMissingEl')) {
   1236           return false;
   1237         } else {
   1238           throw new Marionette.Error('An "el" ' + this.$el.selector + ' must exist in DOM');
   1239         }
   1240       }
   1241       return true;
   1242     },
   1243   
   1244     _ensureViewIsIntact: function(view) {
   1245       if (!view) {
   1246         throw new Marionette.Error({
   1247           name: 'ViewNotValid',
   1248           message: 'The view passed is undefined and therefore invalid. You must pass a view instance to show.'
   1249         });
   1250       }
   1251   
   1252       if (view.isDestroyed) {
   1253         throw new Marionette.Error({
   1254           name: 'ViewDestroyedError',
   1255           message: 'View (cid: "' + view.cid + '") has already been destroyed and cannot be used.'
   1256         });
   1257       }
   1258     },
   1259   
   1260     // Override this method to change how the region finds the DOM
   1261     // element that it manages. Return a jQuery selector object scoped
   1262     // to a provided parent el or the document if none exists.
   1263     getEl: function(el) {
   1264       return Backbone.$(el, Marionette._getValue(this.options.parentEl, this));
   1265     },
   1266   
   1267     // Override this method to change how the new view is
   1268     // appended to the `$el` that the region is managing
   1269     attachHtml: function(view) {
   1270       this.$el.contents().detach();
   1271   
   1272       this.el.appendChild(view.el);
   1273     },
   1274   
   1275     // Destroy the current view, if there is one. If there is no
   1276     // current view, it does nothing and returns immediately.
   1277     empty: function(options) {
   1278       var view = this.currentView;
   1279   
   1280       var emptyOptions = options || {};
   1281       var preventDestroy  = !!emptyOptions.preventDestroy;
   1282       // If there is no view in the region
   1283       // we should not remove anything
   1284       if (!view) { return this; }
   1285   
   1286       view.off('destroy', this.empty, this);
   1287       this.triggerMethod('before:empty', view);
   1288       if (!preventDestroy) {
   1289         this._destroyView();
   1290       }
   1291       this.triggerMethod('empty', view);
   1292   
   1293       // Remove region pointer to the currentView
   1294       delete this.currentView;
   1295   
   1296       if (preventDestroy) {
   1297         this.$el.contents().detach();
   1298       }
   1299   
   1300       return this;
   1301     },
   1302   
   1303     // call 'destroy' or 'remove', depending on which is found
   1304     // on the view (if showing a raw Backbone view or a Marionette View)
   1305     _destroyView: function() {
   1306       var view = this.currentView;
   1307       if (view.isDestroyed) { return; }
   1308   
   1309       if (!view.supportsDestroyLifecycle) {
   1310         Marionette.triggerMethodOn(view, 'before:destroy', view);
   1311       }
   1312       if (view.destroy) {
   1313         view.destroy();
   1314       } else {
   1315         view.remove();
   1316   
   1317         // appending isDestroyed to raw Backbone View allows regions
   1318         // to throw a ViewDestroyedError for this view
   1319         view.isDestroyed = true;
   1320       }
   1321       if (!view.supportsDestroyLifecycle) {
   1322         Marionette.triggerMethodOn(view, 'destroy', view);
   1323       }
   1324     },
   1325   
   1326     // Attach an existing view to the region. This
   1327     // will not call `render` or `onShow` for the new view,
   1328     // and will not replace the current HTML for the `el`
   1329     // of the region.
   1330     attachView: function(view) {
   1331       if (this.currentView) {
   1332         delete this.currentView._parent;
   1333       }
   1334       view._parent = this;
   1335       this.currentView = view;
   1336       return this;
   1337     },
   1338   
   1339     // Checks whether a view is currently present within
   1340     // the region. Returns `true` if there is and `false` if
   1341     // no view is present.
   1342     hasView: function() {
   1343       return !!this.currentView;
   1344     },
   1345   
   1346     // Reset the region by destroying any existing view and
   1347     // clearing out the cached `$el`. The next time a view
   1348     // is shown via this region, the region will re-query the
   1349     // DOM for the region's `el`.
   1350     reset: function() {
   1351       this.empty();
   1352   
   1353       if (this.$el) {
   1354       	// 2020-12-20 Changed for compatibility with jQuery 3.
   1355         this.el = this.options.el;
   1356       }
   1357   
   1358       delete this.$el;
   1359       return this;
   1360     }
   1361   
   1362   },
   1363   
   1364   // Static Methods
   1365   {
   1366   
   1367     // Build an instance of a region by passing in a configuration object
   1368     // and a default region class to use if none is specified in the config.
   1369     //
   1370     // The config object should either be a string as a jQuery DOM selector,
   1371     // a Region class directly, or an object literal that specifies a selector,
   1372     // a custom regionClass, and any options to be supplied to the region:
   1373     //
   1374     // ```js
   1375     // {
   1376     //   selector: "#foo",
   1377     //   regionClass: MyCustomRegion,
   1378     //   allowMissingEl: false
   1379     // }
   1380     // ```
   1381     //
   1382     buildRegion: function(regionConfig, DefaultRegionClass) {
   1383       if (_.isString(regionConfig)) {
   1384         return this._buildRegionFromSelector(regionConfig, DefaultRegionClass);
   1385       }
   1386   
   1387       if (regionConfig.selector || regionConfig.el || regionConfig.regionClass) {
   1388         return this._buildRegionFromObject(regionConfig, DefaultRegionClass);
   1389       }
   1390   
   1391       if (_.isFunction(regionConfig)) {
   1392         return this._buildRegionFromRegionClass(regionConfig);
   1393       }
   1394   
   1395       throw new Marionette.Error({
   1396         message: 'Improper region configuration type.',
   1397         url: 'marionette.region.html#region-configuration-types'
   1398       });
   1399     },
   1400   
   1401     // Build the region from a string selector like '#foo-region'
   1402     _buildRegionFromSelector: function(selector, DefaultRegionClass) {
   1403       return new DefaultRegionClass({el: selector});
   1404     },
   1405   
   1406     // Build the region from a configuration object
   1407     // ```js
   1408     // { selector: '#foo', regionClass: FooRegion, allowMissingEl: false }
   1409     // ```
   1410     _buildRegionFromObject: function(regionConfig, DefaultRegionClass) {
   1411       var RegionClass = regionConfig.regionClass || DefaultRegionClass;
   1412       var options = _.omit(regionConfig, 'selector', 'regionClass');
   1413   
   1414       if (regionConfig.selector && !options.el) {
   1415         options.el = regionConfig.selector;
   1416       }
   1417   
   1418       return new RegionClass(options);
   1419     },
   1420   
   1421     // Build the region directly from a given `RegionClass`
   1422     _buildRegionFromRegionClass: function(RegionClass) {
   1423       return new RegionClass();
   1424     }
   1425   });
   1426   
   1427   // Region Manager
   1428   // --------------
   1429   
   1430   // Manage one or more related `Marionette.Region` objects.
   1431   Marionette.RegionManager = Marionette.Controller.extend({
   1432     constructor: function(options) {
   1433       this._regions = {};
   1434       this.length = 0;
   1435   
   1436       Marionette.Controller.call(this, options);
   1437   
   1438       this.addRegions(this.getOption('regions'));
   1439     },
   1440   
   1441     // Add multiple regions using an object literal or a
   1442     // function that returns an object literal, where
   1443     // each key becomes the region name, and each value is
   1444     // the region definition.
   1445     addRegions: function(regionDefinitions, defaults) {
   1446       regionDefinitions = Marionette._getValue(regionDefinitions, this, arguments);
   1447   
   1448       return _.reduce(regionDefinitions, function(regions, definition, name) {
   1449         if (_.isString(definition)) {
   1450           definition = {selector: definition};
   1451         }
   1452         if (definition.selector) {
   1453           definition = _.defaults({}, definition, defaults);
   1454         }
   1455   
   1456         regions[name] = this.addRegion(name, definition);
   1457         return regions;
   1458       }, {}, this);
   1459     },
   1460   
   1461     // Add an individual region to the region manager,
   1462     // and return the region instance
   1463     addRegion: function(name, definition) {
   1464       var region;
   1465   
   1466       if (definition instanceof Marionette.Region) {
   1467         region = definition;
   1468       } else {
   1469         region = Marionette.Region.buildRegion(definition, Marionette.Region);
   1470       }
   1471   
   1472       this.triggerMethod('before:add:region', name, region);
   1473   
   1474       region._parent = this;
   1475       this._store(name, region);
   1476   
   1477       this.triggerMethod('add:region', name, region);
   1478       return region;
   1479     },
   1480   
   1481     // Get a region by name
   1482     get: function(name) {
   1483       return this._regions[name];
   1484     },
   1485   
   1486     // Gets all the regions contained within
   1487     // the `regionManager` instance.
   1488     getRegions: function() {
   1489       return _.clone(this._regions);
   1490     },
   1491   
   1492     // Remove a region by name
   1493     removeRegion: function(name) {
   1494       var region = this._regions[name];
   1495       this._remove(name, region);
   1496   
   1497       return region;
   1498     },
   1499   
   1500     // Empty all regions in the region manager, and
   1501     // remove them
   1502     removeRegions: function() {
   1503       var regions = this.getRegions();
   1504       _.each(this._regions, function(region, name) {
   1505         this._remove(name, region);
   1506       }, this);
   1507   
   1508       return regions;
   1509     },
   1510   
   1511     // Empty all regions in the region manager, but
   1512     // leave them attached
   1513     emptyRegions: function() {
   1514       var regions = this.getRegions();
   1515       _.invoke(regions, 'empty');
   1516       return regions;
   1517     },
   1518   
   1519     // Destroy all regions and shut down the region
   1520     // manager entirely
   1521     destroy: function() {
   1522       this.removeRegions();
   1523       return Marionette.Controller.prototype.destroy.apply(this, arguments);
   1524     },
   1525   
   1526     // internal method to store regions
   1527     _store: function(name, region) {
   1528       if (!this._regions[name]) {
   1529         this.length++;
   1530       }
   1531   
   1532       this._regions[name] = region;
   1533     },
   1534   
   1535     // internal method to remove a region
   1536     _remove: function(name, region) {
   1537       this.triggerMethod('before:remove:region', name, region);
   1538       region.empty();
   1539       region.stopListening();
   1540   
   1541       delete region._parent;
   1542       delete this._regions[name];
   1543       this.length--;
   1544       this.triggerMethod('remove:region', name, region);
   1545     }
   1546   });
   1547   
   1548   Marionette.actAsCollection(Marionette.RegionManager.prototype, '_regions');
   1549   
   1550 
   1551   // Template Cache
   1552   // --------------
   1553   
   1554   // Manage templates stored in `<script>` blocks,
   1555   // caching them for faster access.
   1556   Marionette.TemplateCache = function(templateId) {
   1557     this.templateId = templateId;
   1558   };
   1559   
   1560   // TemplateCache object-level methods. Manage the template
   1561   // caches from these method calls instead of creating
   1562   // your own TemplateCache instances
   1563   _.extend(Marionette.TemplateCache, {
   1564     templateCaches: {},
   1565   
   1566     // Get the specified template by id. Either
   1567     // retrieves the cached version, or loads it
   1568     // from the DOM.
   1569     get: function(templateId, options) {
   1570       var cachedTemplate = this.templateCaches[templateId];
   1571   
   1572       if (!cachedTemplate) {
   1573         cachedTemplate = new Marionette.TemplateCache(templateId);
   1574         this.templateCaches[templateId] = cachedTemplate;
   1575       }
   1576   
   1577       return cachedTemplate.load(options);
   1578     },
   1579   
   1580     // Clear templates from the cache. If no arguments
   1581     // are specified, clears all templates:
   1582     // `clear()`
   1583     //
   1584     // If arguments are specified, clears each of the
   1585     // specified templates from the cache:
   1586     // `clear("#t1", "#t2", "...")`
   1587     clear: function() {
   1588       var i;
   1589       var args = _.toArray(arguments);
   1590       var length = args.length;
   1591   
   1592       if (length > 0) {
   1593         for (i = 0; i < length; i++) {
   1594           delete this.templateCaches[args[i]];
   1595         }
   1596       } else {
   1597         this.templateCaches = {};
   1598       }
   1599     }
   1600   });
   1601   
   1602   // TemplateCache instance methods, allowing each
   1603   // template cache object to manage its own state
   1604   // and know whether or not it has been loaded
   1605   _.extend(Marionette.TemplateCache.prototype, {
   1606   
   1607     // Internal method to load the template
   1608     load: function(options) {
   1609       // Guard clause to prevent loading this template more than once
   1610       if (this.compiledTemplate) {
   1611         return this.compiledTemplate;
   1612       }
   1613   
   1614       // Load the template and compile it
   1615       var template = this.loadTemplate(this.templateId, options);
   1616       this.compiledTemplate = this.compileTemplate(template, options);
   1617   
   1618       return this.compiledTemplate;
   1619     },
   1620   
   1621     // Load a template from the DOM, by default. Override
   1622     // this method to provide your own template retrieval
   1623     // For asynchronous loading with AMD/RequireJS, consider
   1624     // using a template-loader plugin as described here:
   1625     // https://github.com/marionettejs/backbone.marionette/wiki/Using-marionette-with-requirejs
   1626     loadTemplate: function(templateId, options) {
   1627       var $template = Backbone.$(templateId);
   1628   
   1629       if (!$template.length) {
   1630         throw new Marionette.Error({
   1631           name: 'NoTemplateError',
   1632           message: 'Could not find template: "' + templateId + '"'
   1633         });
   1634       }
   1635       return $template.html();
   1636     },
   1637   
   1638     // Pre-compile the template before caching it. Override
   1639     // this method if you do not need to pre-compile a template
   1640     // (JST / RequireJS for example) or if you want to change
   1641     // the template engine used (Handebars, etc).
   1642     compileTemplate: function(rawTemplate, options) {
   1643       return _.template(rawTemplate, options);
   1644     }
   1645   });
   1646   
   1647   // Renderer
   1648   // --------
   1649   
   1650   // Render a template with data by passing in the template
   1651   // selector and the data to render.
   1652   Marionette.Renderer = {
   1653   
   1654     // Render a template with data. The `template` parameter is
   1655     // passed to the `TemplateCache` object to retrieve the
   1656     // template function. Override this method to provide your own
   1657     // custom rendering and template handling for all of Marionette.
   1658     render: function(template, data) {
   1659       if (!template) {
   1660         throw new Marionette.Error({
   1661           name: 'TemplateNotFoundError',
   1662           message: 'Cannot render the template since its false, null or undefined.'
   1663         });
   1664       }
   1665   
   1666       var templateFunc = _.isFunction(template) ? template : Marionette.TemplateCache.get(template);
   1667   
   1668       return templateFunc(data);
   1669     }
   1670   };
   1671   
   1672 
   1673   /* jshint maxlen: 114, nonew: false */
   1674   // View
   1675   // ----
   1676   
   1677   // The core view class that other Marionette views extend from.
   1678   Marionette.View = Backbone.View.extend({
   1679     isDestroyed: false,
   1680     supportsRenderLifecycle: true,
   1681     supportsDestroyLifecycle: true,
   1682   
   1683     constructor: function(options) {
   1684       this.render = _.bind(this.render, this);
   1685   
   1686       options = Marionette._getValue(options, this);
   1687   
   1688       // this exposes view options to the view initializer
   1689       // this is a backfill since backbone removed the assignment
   1690       // of this.options
   1691       // at some point however this may be removed
   1692       this.options = _.extend({}, _.result(this, 'options'), options);
   1693   
   1694       this._behaviors = Marionette.Behaviors(this);
   1695   
   1696       Backbone.View.call(this, this.options);
   1697   
   1698       Marionette.MonitorDOMRefresh(this);
   1699     },
   1700   
   1701     // Get the template for this view
   1702     // instance. You can set a `template` attribute in the view
   1703     // definition or pass a `template: "whatever"` parameter in
   1704     // to the constructor options.
   1705     getTemplate: function() {
   1706       return this.getOption('template');
   1707     },
   1708   
   1709     // Serialize a model by returning its attributes. Clones
   1710     // the attributes to allow modification.
   1711     serializeModel: function(model) {
   1712       return model.toJSON.apply(model, _.rest(arguments));
   1713     },
   1714   
   1715     // Mix in template helper methods. Looks for a
   1716     // `templateHelpers` attribute, which can either be an
   1717     // object literal, or a function that returns an object
   1718     // literal. All methods and attributes from this object
   1719     // are copies to the object passed in.
   1720     mixinTemplateHelpers: function(target) {
   1721       target = target || {};
   1722       var templateHelpers = this.getOption('templateHelpers');
   1723       templateHelpers = Marionette._getValue(templateHelpers, this);
   1724       return _.extend(target, templateHelpers);
   1725     },
   1726   
   1727     // normalize the keys of passed hash with the views `ui` selectors.
   1728     // `{"@ui.foo": "bar"}`
   1729     normalizeUIKeys: function(hash) {
   1730       var uiBindings = _.result(this, '_uiBindings');
   1731       return Marionette.normalizeUIKeys(hash, uiBindings || _.result(this, 'ui'));
   1732     },
   1733   
   1734     // normalize the values of passed hash with the views `ui` selectors.
   1735     // `{foo: "@ui.bar"}`
   1736     normalizeUIValues: function(hash, properties) {
   1737       var ui = _.result(this, 'ui');
   1738       var uiBindings = _.result(this, '_uiBindings');
   1739       return Marionette.normalizeUIValues(hash, uiBindings || ui, properties);
   1740     },
   1741   
   1742     // Configure `triggers` to forward DOM events to view
   1743     // events. `triggers: {"click .foo": "do:foo"}`
   1744     configureTriggers: function() {
   1745       if (!this.triggers) { return; }
   1746   
   1747       // Allow `triggers` to be configured as a function
   1748       var triggers = this.normalizeUIKeys(_.result(this, 'triggers'));
   1749   
   1750       // Configure the triggers, prevent default
   1751       // action and stop propagation of DOM events
   1752       return _.reduce(triggers, function(events, value, key) {
   1753         events[key] = this._buildViewTrigger(value);
   1754         return events;
   1755       }, {}, this);
   1756     },
   1757   
   1758     // Overriding Backbone.View's delegateEvents to handle
   1759     // the `triggers`, `modelEvents`, and `collectionEvents` configuration
   1760     delegateEvents: function(events) {
   1761       this._delegateDOMEvents(events);
   1762       this.bindEntityEvents(this.model, this.getOption('modelEvents'));
   1763       this.bindEntityEvents(this.collection, this.getOption('collectionEvents'));
   1764   
   1765       _.each(this._behaviors, function(behavior) {
   1766         behavior.bindEntityEvents(this.model, behavior.getOption('modelEvents'));
   1767         behavior.bindEntityEvents(this.collection, behavior.getOption('collectionEvents'));
   1768       }, this);
   1769   
   1770       return this;
   1771     },
   1772   
   1773     // internal method to delegate DOM events and triggers
   1774     _delegateDOMEvents: function(eventsArg) {
   1775       var events = Marionette._getValue(eventsArg || this.events, this);
   1776   
   1777       // normalize ui keys
   1778       events = this.normalizeUIKeys(events);
   1779       if (_.isUndefined(eventsArg)) {this.events = events;}
   1780   
   1781       var combinedEvents = {};
   1782   
   1783       // look up if this view has behavior events
   1784       var behaviorEvents = _.result(this, 'behaviorEvents') || {};
   1785       var triggers = this.configureTriggers();
   1786       var behaviorTriggers = _.result(this, 'behaviorTriggers') || {};
   1787   
   1788       // behavior events will be overriden by view events and or triggers
   1789       _.extend(combinedEvents, behaviorEvents, events, triggers, behaviorTriggers);
   1790   
   1791       Backbone.View.prototype.delegateEvents.call(this, combinedEvents);
   1792     },
   1793   
   1794     // Overriding Backbone.View's undelegateEvents to handle unbinding
   1795     // the `triggers`, `modelEvents`, and `collectionEvents` config
   1796     undelegateEvents: function() {
   1797       Backbone.View.prototype.undelegateEvents.apply(this, arguments);
   1798   
   1799       this.unbindEntityEvents(this.model, this.getOption('modelEvents'));
   1800       this.unbindEntityEvents(this.collection, this.getOption('collectionEvents'));
   1801   
   1802       _.each(this._behaviors, function(behavior) {
   1803         behavior.unbindEntityEvents(this.model, behavior.getOption('modelEvents'));
   1804         behavior.unbindEntityEvents(this.collection, behavior.getOption('collectionEvents'));
   1805       }, this);
   1806   
   1807       return this;
   1808     },
   1809   
   1810     // Internal helper method to verify whether the view hasn't been destroyed
   1811     _ensureViewIsIntact: function() {
   1812       if (this.isDestroyed) {
   1813         throw new Marionette.Error({
   1814           name: 'ViewDestroyedError',
   1815           message: 'View (cid: "' + this.cid + '") has already been destroyed and cannot be used.'
   1816         });
   1817       }
   1818     },
   1819   
   1820     // Default `destroy` implementation, for removing a view from the
   1821     // DOM and unbinding it. Regions will call this method
   1822     // for you. You can specify an `onDestroy` method in your view to
   1823     // add custom code that is called after the view is destroyed.
   1824     destroy: function() {
   1825       if (this.isDestroyed) { return this; }
   1826   
   1827       var args = _.toArray(arguments);
   1828   
   1829       this.triggerMethod.apply(this, ['before:destroy'].concat(args));
   1830   
   1831       // mark as destroyed before doing the actual destroy, to
   1832       // prevent infinite loops within "destroy" event handlers
   1833       // that are trying to destroy other views
   1834       this.isDestroyed = true;
   1835       this.triggerMethod.apply(this, ['destroy'].concat(args));
   1836   
   1837       // unbind UI elements
   1838       this.unbindUIElements();
   1839   
   1840       this.isRendered = false;
   1841   
   1842       // remove the view from the DOM
   1843       this.remove();
   1844   
   1845       // Call destroy on each behavior after
   1846       // destroying the view.
   1847       // This unbinds event listeners
   1848       // that behaviors have registered for.
   1849       _.invoke(this._behaviors, 'destroy', args);
   1850   
   1851       return this;
   1852     },
   1853   
   1854     bindUIElements: function() {
   1855       this._bindUIElements();
   1856       _.invoke(this._behaviors, this._bindUIElements);
   1857     },
   1858   
   1859     // This method binds the elements specified in the "ui" hash inside the view's code with
   1860     // the associated jQuery selectors.
   1861     _bindUIElements: function() {
   1862       if (!this.ui) { return; }
   1863   
   1864       // store the ui hash in _uiBindings so they can be reset later
   1865       // and so re-rendering the view will be able to find the bindings
   1866       if (!this._uiBindings) {
   1867         this._uiBindings = this.ui;
   1868       }
   1869   
   1870       // get the bindings result, as a function or otherwise
   1871       var bindings = _.result(this, '_uiBindings');
   1872   
   1873       // empty the ui so we don't have anything to start with
   1874       this.ui = {};
   1875   
   1876       // bind each of the selectors
   1877       _.each(bindings, function(selector, key) {
   1878         this.ui[key] = this.$(selector);
   1879       }, this);
   1880     },
   1881   
   1882     // This method unbinds the elements specified in the "ui" hash
   1883     unbindUIElements: function() {
   1884       this._unbindUIElements();
   1885       _.invoke(this._behaviors, this._unbindUIElements);
   1886     },
   1887   
   1888     _unbindUIElements: function() {
   1889       if (!this.ui || !this._uiBindings) { return; }
   1890   
   1891       // delete all of the existing ui bindings
   1892       _.each(this.ui, function($el, name) {
   1893         delete this.ui[name];
   1894       }, this);
   1895   
   1896       // reset the ui element to the original bindings configuration
   1897       this.ui = this._uiBindings;
   1898       delete this._uiBindings;
   1899     },
   1900   
   1901     // Internal method to create an event handler for a given `triggerDef` like
   1902     // 'click:foo'
   1903     _buildViewTrigger: function(triggerDef) {
   1904   
   1905       var options = _.defaults({}, triggerDef, {
   1906         preventDefault: true,
   1907         stopPropagation: true
   1908       });
   1909   
   1910       var eventName = _.isObject(triggerDef) ? options.event : triggerDef;
   1911   
   1912       return function(e) {
   1913         if (e) {
   1914           if (e.preventDefault && options.preventDefault) {
   1915             e.preventDefault();
   1916           }
   1917   
   1918           if (e.stopPropagation && options.stopPropagation) {
   1919             e.stopPropagation();
   1920           }
   1921         }
   1922   
   1923         var args = {
   1924           view: this,
   1925           model: this.model,
   1926           collection: this.collection
   1927         };
   1928   
   1929         this.triggerMethod(eventName, args);
   1930       };
   1931     },
   1932   
   1933     setElement: function() {
   1934       var ret = Backbone.View.prototype.setElement.apply(this, arguments);
   1935   
   1936       // proxy behavior $el to the view's $el.
   1937       // This is needed because a view's $el proxy
   1938       // is not set until after setElement is called.
   1939       _.invoke(this._behaviors, 'proxyViewProperties', this);
   1940   
   1941       return ret;
   1942     },
   1943   
   1944     // import the `triggerMethod` to trigger events with corresponding
   1945     // methods if the method exists
   1946     triggerMethod: function() {
   1947       var ret = Marionette._triggerMethod(this, arguments);
   1948   
   1949       this._triggerEventOnBehaviors(arguments);
   1950       this._triggerEventOnParentLayout(arguments[0], _.rest(arguments));
   1951   
   1952       return ret;
   1953     },
   1954   
   1955     _triggerEventOnBehaviors: function(args) {
   1956       var triggerMethod = Marionette._triggerMethod;
   1957       var behaviors = this._behaviors;
   1958       // Use good ol' for as this is a very hot function
   1959       for (var i = 0, length = behaviors && behaviors.length; i < length; i++) {
   1960         triggerMethod(behaviors[i], args);
   1961       }
   1962     },
   1963   
   1964     _triggerEventOnParentLayout: function(eventName, args) {
   1965       var layoutView = this._parentLayoutView();
   1966       if (!layoutView) {
   1967         return;
   1968       }
   1969   
   1970       // invoke triggerMethod on parent view
   1971       var eventPrefix = Marionette.getOption(layoutView, 'childViewEventPrefix');
   1972       var prefixedEventName = eventPrefix + ':' + eventName;
   1973       var callArgs = [this].concat(args);
   1974   
   1975       Marionette._triggerMethod(layoutView, prefixedEventName, callArgs);
   1976   
   1977       // call the parent view's childEvents handler
   1978       var childEvents = Marionette.getOption(layoutView, 'childEvents');
   1979   
   1980       // since childEvents can be an object or a function use Marionette._getValue
   1981       // to handle the abstaction for us.
   1982       childEvents = Marionette._getValue(childEvents, layoutView);
   1983       var normalizedChildEvents = layoutView.normalizeMethods(childEvents);
   1984   
   1985       if (normalizedChildEvents && _.isFunction(normalizedChildEvents[eventName])) {
   1986         normalizedChildEvents[eventName].apply(layoutView, callArgs);
   1987       }
   1988     },
   1989   
   1990     // This method returns any views that are immediate
   1991     // children of this view
   1992     _getImmediateChildren: function() {
   1993       return [];
   1994     },
   1995   
   1996     // Returns an array of every nested view within this view
   1997     _getNestedViews: function() {
   1998       var children = this._getImmediateChildren();
   1999   
   2000       if (!children.length) { return children; }
   2001   
   2002       return _.reduce(children, function(memo, view) {
   2003         if (!view._getNestedViews) { return memo; }
   2004         return memo.concat(view._getNestedViews());
   2005       }, children);
   2006     },
   2007   
   2008     // Walk the _parent tree until we find a layout view (if one exists).
   2009     // Returns the parent layout view hierarchically closest to this view.
   2010     _parentLayoutView: function() {
   2011       var parent  = this._parent;
   2012   
   2013       while (parent) {
   2014         if (parent instanceof Marionette.LayoutView) {
   2015           return parent;
   2016         }
   2017         parent = parent._parent;
   2018       }
   2019     },
   2020   
   2021     // Imports the "normalizeMethods" to transform hashes of
   2022     // events=>function references/names to a hash of events=>function references
   2023     normalizeMethods: Marionette.normalizeMethods,
   2024   
   2025     // A handy way to merge passed-in options onto the instance
   2026     mergeOptions: Marionette.mergeOptions,
   2027   
   2028     // Proxy `getOption` to enable getting options from this or this.options by name.
   2029     getOption: Marionette.proxyGetOption,
   2030   
   2031     // Proxy `bindEntityEvents` to enable binding view's events from another entity.
   2032     bindEntityEvents: Marionette.proxyBindEntityEvents,
   2033   
   2034     // Proxy `unbindEntityEvents` to enable unbinding view's events from another entity.
   2035     unbindEntityEvents: Marionette.proxyUnbindEntityEvents
   2036   });
   2037   
   2038   // Item View
   2039   // ---------
   2040   
   2041   // A single item view implementation that contains code for rendering
   2042   // with underscore.js templates, serializing the view's model or collection,
   2043   // and calling several methods on extended views, such as `onRender`.
   2044   Marionette.ItemView = Marionette.View.extend({
   2045   
   2046     // Setting up the inheritance chain which allows changes to
   2047     // Marionette.View.prototype.constructor which allows overriding
   2048     constructor: function() {
   2049       Marionette.View.apply(this, arguments);
   2050     },
   2051   
   2052     // Serialize the model or collection for the view. If a model is
   2053     // found, the view's `serializeModel` is called. If a collection is found,
   2054     // each model in the collection is serialized by calling
   2055     // the view's `serializeCollection` and put into an `items` array in
   2056     // the resulting data. If both are found, defaults to the model.
   2057     // You can override the `serializeData` method in your own view definition,
   2058     // to provide custom serialization for your view's data.
   2059     serializeData: function() {
   2060       if (!this.model && !this.collection) {
   2061         return {};
   2062       }
   2063   
   2064       var args = [this.model || this.collection];
   2065       if (arguments.length) {
   2066         args.push.apply(args, arguments);
   2067       }
   2068   
   2069       if (this.model) {
   2070         return this.serializeModel.apply(this, args);
   2071       } else {
   2072         return {
   2073           items: this.serializeCollection.apply(this, args)
   2074         };
   2075       }
   2076     },
   2077   
   2078     // Serialize a collection by serializing each of its models.
   2079     serializeCollection: function(collection) {
   2080       return collection.toJSON.apply(collection, _.rest(arguments));
   2081     },
   2082   
   2083     // Render the view, defaulting to underscore.js templates.
   2084     // You can override this in your view definition to provide
   2085     // a very specific rendering for your view. In general, though,
   2086     // you should override the `Marionette.Renderer` object to
   2087     // change how Marionette renders views.
   2088     render: function() {
   2089       this._ensureViewIsIntact();
   2090   
   2091       this.triggerMethod('before:render', this);
   2092   
   2093       this._renderTemplate();
   2094       this.isRendered = true;
   2095       this.bindUIElements();
   2096   
   2097       this.triggerMethod('render', this);
   2098   
   2099       return this;
   2100     },
   2101   
   2102     // Internal method to render the template with the serialized data
   2103     // and template helpers via the `Marionette.Renderer` object.
   2104     // Throws an `UndefinedTemplateError` error if the template is
   2105     // any falsely value but literal `false`.
   2106     _renderTemplate: function() {
   2107       var template = this.getTemplate();
   2108   
   2109       // Allow template-less item views
   2110       if (template === false) {
   2111         return;
   2112       }
   2113   
   2114       if (!template) {
   2115         throw new Marionette.Error({
   2116           name: 'UndefinedTemplateError',
   2117           message: 'Cannot render the template since it is null or undefined.'
   2118         });
   2119       }
   2120   
   2121       // Add in entity data and template helpers
   2122       var data = this.mixinTemplateHelpers(this.serializeData());
   2123   
   2124       // Render and add to el
   2125       var html = Marionette.Renderer.render(template, data, this);
   2126       this.attachElContent(html);
   2127   
   2128       return this;
   2129     },
   2130   
   2131     // Attaches the content of a given view.
   2132     // This method can be overridden to optimize rendering,
   2133     // or to render in a non standard way.
   2134     //
   2135     // For example, using `innerHTML` instead of `$el.html`
   2136     //
   2137     // ```js
   2138     // attachElContent: function(html) {
   2139     //   this.el.innerHTML = html;
   2140     //   return this;
   2141     // }
   2142     // ```
   2143     attachElContent: function(html) {
   2144       this.$el.html(html);
   2145   
   2146       return this;
   2147     }
   2148   });
   2149   
   2150   /* jshint maxstatements: 20, maxcomplexity: 7 */
   2151   
   2152   // Collection View
   2153   // ---------------
   2154   
   2155   // A view that iterates over a Backbone.Collection
   2156   // and renders an individual child view for each model.
   2157   Marionette.CollectionView = Marionette.View.extend({
   2158   
   2159     // used as the prefix for child view events
   2160     // that are forwarded through the collectionview
   2161     childViewEventPrefix: 'childview',
   2162   
   2163     // flag for maintaining the sorted order of the collection
   2164     sort: true,
   2165   
   2166     // constructor
   2167     // option to pass `{sort: false}` to prevent the `CollectionView` from
   2168     // maintaining the sorted order of the collection.
   2169     // This will fallback onto appending childView's to the end.
   2170     //
   2171     // option to pass `{comparator: compFunction()}` to allow the `CollectionView`
   2172     // to use a custom sort order for the collection.
   2173     constructor: function(options) {
   2174       this.once('render', this._initialEvents);
   2175       this._initChildViewStorage();
   2176   
   2177       Marionette.View.apply(this, arguments);
   2178   
   2179       this.on({
   2180         'before:show':   this._onBeforeShowCalled,
   2181         'show':          this._onShowCalled,
   2182         'before:attach': this._onBeforeAttachCalled,
   2183         'attach':        this._onAttachCalled
   2184       });
   2185       this.initRenderBuffer();
   2186     },
   2187   
   2188     // Instead of inserting elements one by one into the page,
   2189     // it's much more performant to insert elements into a document
   2190     // fragment and then insert that document fragment into the page
   2191     initRenderBuffer: function() {
   2192       this._bufferedChildren = [];
   2193     },
   2194   
   2195     startBuffering: function() {
   2196       this.initRenderBuffer();
   2197       this.isBuffering = true;
   2198     },
   2199   
   2200     endBuffering: function() {
   2201       // Only trigger attach if already shown and attached, otherwise Region#show() handles this.
   2202       var canTriggerAttach = this._isShown && Marionette.isNodeAttached(this.el);
   2203       var nestedViews;
   2204   
   2205       this.isBuffering = false;
   2206   
   2207       if (this._isShown) {
   2208         this._triggerMethodMany(this._bufferedChildren, this, 'before:show');
   2209       }
   2210       if (canTriggerAttach && this._triggerBeforeAttach) {
   2211         nestedViews = this._getNestedViews();
   2212         this._triggerMethodMany(nestedViews, this, 'before:attach');
   2213       }
   2214   
   2215       this.attachBuffer(this, this._createBuffer());
   2216   
   2217       if (canTriggerAttach && this._triggerAttach) {
   2218         nestedViews = this._getNestedViews();
   2219         this._triggerMethodMany(nestedViews, this, 'attach');
   2220       }
   2221       if (this._isShown) {
   2222         this._triggerMethodMany(this._bufferedChildren, this, 'show');
   2223       }
   2224       this.initRenderBuffer();
   2225     },
   2226   
   2227     _triggerMethodMany: function(targets, source, eventName) {
   2228       var args = _.drop(arguments, 3);
   2229   
   2230       _.each(targets, function(target) {
   2231         Marionette.triggerMethodOn.apply(target, [target, eventName, target, source].concat(args));
   2232       });
   2233     },
   2234   
   2235     // Configured the initial events that the collection view
   2236     // binds to.
   2237     _initialEvents: function() {
   2238       if (this.collection) {
   2239         this.listenTo(this.collection, 'add', this._onCollectionAdd);
   2240         this.listenTo(this.collection, 'remove', this._onCollectionRemove);
   2241         this.listenTo(this.collection, 'reset', this.render);
   2242   
   2243         if (this.getOption('sort')) {
   2244           this.listenTo(this.collection, 'sort', this._sortViews);
   2245         }
   2246       }
   2247     },
   2248   
   2249     // Handle a child added to the collection
   2250     _onCollectionAdd: function(child, collection, opts) {
   2251       // `index` is present when adding with `at` since BB 1.2; indexOf fallback for < 1.2
   2252       var index = opts.at !== undefined && (opts.index || collection.indexOf(child));
   2253   
   2254       // When filtered or when there is no initial index, calculate index.
   2255       if (this.getOption('filter') || index === false) {
   2256         index = _.indexOf(this._filteredSortedModels(index), child);
   2257       }
   2258   
   2259       if (this._shouldAddChild(child, index)) {
   2260         this.destroyEmptyView();
   2261         var ChildView = this.getChildView(child);
   2262         this.addChild(child, ChildView, index);
   2263       }
   2264     },
   2265   
   2266     // get the child view by model it holds, and remove it
   2267     _onCollectionRemove: function(model) {
   2268       var view = this.children.findByModel(model);
   2269       this.removeChildView(view);
   2270       this.checkEmpty();
   2271     },
   2272   
   2273     _onBeforeShowCalled: function() {
   2274       // Reset attach event flags at the top of the Region#show() event lifecycle; if the Region's
   2275       // show() options permit onBeforeAttach/onAttach events, these flags will be set true again.
   2276       this._triggerBeforeAttach = this._triggerAttach = false;
   2277       this.children.each(function(childView) {
   2278         Marionette.triggerMethodOn(childView, 'before:show', childView);
   2279       });
   2280     },
   2281   
   2282     _onShowCalled: function() {
   2283       this.children.each(function(childView) {
   2284         Marionette.triggerMethodOn(childView, 'show', childView);
   2285       });
   2286     },
   2287   
   2288     // If during Region#show() onBeforeAttach was fired, continue firing it for child views
   2289     _onBeforeAttachCalled: function() {
   2290       this._triggerBeforeAttach = true;
   2291     },
   2292   
   2293     // If during Region#show() onAttach was fired, continue firing it for child views
   2294     _onAttachCalled: function() {
   2295       this._triggerAttach = true;
   2296     },
   2297   
   2298     // Render children views. Override this method to
   2299     // provide your own implementation of a render function for
   2300     // the collection view.
   2301     render: function() {
   2302       this._ensureViewIsIntact();
   2303       this.triggerMethod('before:render', this);
   2304       this._renderChildren();
   2305       this.isRendered = true;
   2306       this.triggerMethod('render', this);
   2307       return this;
   2308     },
   2309   
   2310     // Reorder DOM after sorting. When your element's rendering
   2311     // do not use their index, you can pass reorderOnSort: true
   2312     // to only reorder the DOM after a sort instead of rendering
   2313     // all the collectionView
   2314     reorder: function() {
   2315       var children = this.children;
   2316       var models = this._filteredSortedModels();
   2317       var anyModelsAdded = _.some(models, function(model) {
   2318         return !children.findByModel(model);
   2319       });
   2320   
   2321       // If there are any new models added due to filtering
   2322       // We need to add child views
   2323       // So render as normal
   2324       if (anyModelsAdded) {
   2325         this.render();
   2326       } else {
   2327         // get the DOM nodes in the same order as the models
   2328         var elsToReorder = _.map(models, function(model, index) {
   2329           var view = children.findByModel(model);
   2330           view._index = index;
   2331           return view.el;
   2332         });
   2333   
   2334         // find the views that were children before but arent in this new ordering
   2335         var filteredOutViews = children.filter(function(view) {
   2336           return !_.contains(elsToReorder, view.el);
   2337         });
   2338   
   2339         this.triggerMethod('before:reorder');
   2340   
   2341         // since append moves elements that are already in the DOM,
   2342         // appending the elements will effectively reorder them
   2343         this._appendReorderedChildren(elsToReorder);
   2344   
   2345         // remove any views that have been filtered out
   2346         _.each(filteredOutViews, this.removeChildView, this);
   2347         this.checkEmpty();
   2348   
   2349         this.triggerMethod('reorder');
   2350       }
   2351     },
   2352   
   2353     // Render view after sorting. Override this method to
   2354     // change how the view renders after a `sort` on the collection.
   2355     // An example of this would be to only `renderChildren` in a `CompositeView`
   2356     // rather than the full view.
   2357     resortView: function() {
   2358       if (Marionette.getOption(this, 'reorderOnSort')) {
   2359         this.reorder();
   2360       } else {
   2361         this.render();
   2362       }
   2363     },
   2364   
   2365     // Internal method. This checks for any changes in the order of the collection.
   2366     // If the index of any view doesn't match, it will render.
   2367     _sortViews: function() {
   2368       var models = this._filteredSortedModels();
   2369   
   2370       // check for any changes in sort order of views
   2371       var orderChanged = _.find(models, function(item, index) {
   2372         var view = this.children.findByModel(item);
   2373         return !view || view._index !== index;
   2374       }, this);
   2375   
   2376       if (orderChanged) {
   2377         this.resortView();
   2378       }
   2379     },
   2380   
   2381     // Internal reference to what index a `emptyView` is.
   2382     _emptyViewIndex: -1,
   2383   
   2384     // Internal method. Separated so that CompositeView can append to the childViewContainer
   2385     // if necessary
   2386     _appendReorderedChildren: function(children) {
   2387       this.$el.append(children);
   2388     },
   2389   
   2390     // Internal method. Separated so that CompositeView can have
   2391     // more control over events being triggered, around the rendering
   2392     // process
   2393     _renderChildren: function() {
   2394       this.destroyEmptyView();
   2395       this.destroyChildren({checkEmpty: false});
   2396   
   2397       if (this.isEmpty(this.collection)) {
   2398         this.showEmptyView();
   2399       } else {
   2400         this.triggerMethod('before:render:collection', this);
   2401         this.startBuffering();
   2402         this.showCollection();
   2403         this.endBuffering();
   2404         this.triggerMethod('render:collection', this);
   2405   
   2406         // If we have shown children and none have passed the filter, show the empty view
   2407         if (this.children.isEmpty() && this.getOption('filter')) {
   2408           this.showEmptyView();
   2409         }
   2410       }
   2411     },
   2412   
   2413     // Internal method to loop through collection and show each child view.
   2414     showCollection: function() {
   2415       var ChildView;
   2416   
   2417       var models = this._filteredSortedModels();
   2418   
   2419       _.each(models, function(child, index) {
   2420         ChildView = this.getChildView(child);
   2421         this.addChild(child, ChildView, index);
   2422       }, this);
   2423     },
   2424   
   2425     // Allow the collection to be sorted by a custom view comparator
   2426     _filteredSortedModels: function(addedAt) {
   2427       var viewComparator = this.getViewComparator();
   2428       var models = this.collection.models;
   2429       addedAt = Math.min(Math.max(addedAt, 0), models.length - 1);
   2430   
   2431       if (viewComparator) {
   2432         var addedModel;
   2433         // Preserve `at` location, even for a sorted view
   2434         if (addedAt) {
   2435           addedModel = models[addedAt];
   2436           models = models.slice(0, addedAt).concat(models.slice(addedAt + 1));
   2437         }
   2438         models = this._sortModelsBy(models, viewComparator);
   2439         if (addedModel) {
   2440           models.splice(addedAt, 0, addedModel);
   2441         }
   2442       }
   2443   
   2444       // Filter after sorting in case the filter uses the index
   2445       if (this.getOption('filter')) {
   2446         models = _.filter(models, function(model, index) {
   2447           return this._shouldAddChild(model, index);
   2448         }, this);
   2449       }
   2450   
   2451       return models;
   2452     },
   2453   
   2454     _sortModelsBy: function(models, comparator) {
   2455       if (typeof comparator === 'string') {
   2456         return _.sortBy(models, function(model) {
   2457           return model.get(comparator);
   2458         }, this);
   2459       } else if (comparator.length === 1) {
   2460         return _.sortBy(models, comparator, this);
   2461       } else {
   2462         return models.sort(_.bind(comparator, this));
   2463       }
   2464     },
   2465   
   2466     // Internal method to show an empty view in place of
   2467     // a collection of child views, when the collection is empty
   2468     showEmptyView: function() {
   2469       var EmptyView = this.getEmptyView();
   2470   
   2471       if (EmptyView && !this._showingEmptyView) {
   2472         this.triggerMethod('before:render:empty');
   2473   
   2474         this._showingEmptyView = true;
   2475         var model = new Backbone.Model();
   2476         this.addEmptyView(model, EmptyView);
   2477   
   2478         this.triggerMethod('render:empty');
   2479       }
   2480     },
   2481   
   2482     // Internal method to destroy an existing emptyView instance
   2483     // if one exists. Called when a collection view has been
   2484     // rendered empty, and then a child is added to the collection.
   2485     destroyEmptyView: function() {
   2486       if (this._showingEmptyView) {
   2487         this.triggerMethod('before:remove:empty');
   2488   
   2489         this.destroyChildren();
   2490         delete this._showingEmptyView;
   2491   
   2492         this.triggerMethod('remove:empty');
   2493       }
   2494     },
   2495   
   2496     // Retrieve the empty view class
   2497     getEmptyView: function() {
   2498       return this.getOption('emptyView');
   2499     },
   2500   
   2501     // Render and show the emptyView. Similar to addChild method
   2502     // but "add:child" events are not fired, and the event from
   2503     // emptyView are not forwarded
   2504     addEmptyView: function(child, EmptyView) {
   2505       // Only trigger attach if already shown, attached, and not buffering, otherwise endBuffer() or
   2506       // Region#show() handles this.
   2507       var canTriggerAttach = this._isShown && !this.isBuffering && Marionette.isNodeAttached(this.el);
   2508       var nestedViews;
   2509   
   2510       // get the emptyViewOptions, falling back to childViewOptions
   2511       var emptyViewOptions = this.getOption('emptyViewOptions') ||
   2512                             this.getOption('childViewOptions');
   2513   
   2514       if (_.isFunction(emptyViewOptions)) {
   2515         emptyViewOptions = emptyViewOptions.call(this, child, this._emptyViewIndex);
   2516       }
   2517   
   2518       // build the empty view
   2519       var view = this.buildChildView(child, EmptyView, emptyViewOptions);
   2520   
   2521       view._parent = this;
   2522   
   2523       // Proxy emptyView events
   2524       this.proxyChildEvents(view);
   2525   
   2526       view.once('render', function() {
   2527         // trigger the 'before:show' event on `view` if the collection view has already been shown
   2528         if (this._isShown) {
   2529           Marionette.triggerMethodOn(view, 'before:show', view);
   2530         }
   2531   
   2532         // Trigger `before:attach` following `render` to avoid adding logic and event triggers
   2533         // to public method `renderChildView()`.
   2534         if (canTriggerAttach && this._triggerBeforeAttach) {
   2535           nestedViews = this._getViewAndNested(view);
   2536           this._triggerMethodMany(nestedViews, this, 'before:attach');
   2537         }
   2538       }, this);
   2539   
   2540       // Store the `emptyView` like a `childView` so we can properly remove and/or close it later
   2541       this.children.add(view);
   2542       this.renderChildView(view, this._emptyViewIndex);
   2543   
   2544       // Trigger `attach`
   2545       if (canTriggerAttach && this._triggerAttach) {
   2546         nestedViews = this._getViewAndNested(view);
   2547         this._triggerMethodMany(nestedViews, this, 'attach');
   2548       }
   2549       // call the 'show' method if the collection view has already been shown
   2550       if (this._isShown) {
   2551         Marionette.triggerMethodOn(view, 'show', view);
   2552       }
   2553     },
   2554   
   2555     // Retrieve the `childView` class, either from `this.options.childView`
   2556     // or from the `childView` in the object definition. The "options"
   2557     // takes precedence.
   2558     // This method receives the model that will be passed to the instance
   2559     // created from this `childView`. Overriding methods may use the child
   2560     // to determine what `childView` class to return.
   2561     getChildView: function(child) {
   2562       var childView = this.getOption('childView');
   2563   
   2564       if (!childView) {
   2565         throw new Marionette.Error({
   2566           name: 'NoChildViewError',
   2567           message: 'A "childView" must be specified'
   2568         });
   2569       }
   2570   
   2571       return childView;
   2572     },
   2573   
   2574     // Render the child's view and add it to the
   2575     // HTML for the collection view at a given index.
   2576     // This will also update the indices of later views in the collection
   2577     // in order to keep the children in sync with the collection.
   2578     addChild: function(child, ChildView, index) {
   2579       var childViewOptions = this.getOption('childViewOptions');
   2580       childViewOptions = Marionette._getValue(childViewOptions, this, [child, index]);
   2581   
   2582       var view = this.buildChildView(child, ChildView, childViewOptions);
   2583   
   2584       // increment indices of views after this one
   2585       this._updateIndices(view, true, index);
   2586   
   2587       this.triggerMethod('before:add:child', view);
   2588       this._addChildView(view, index);
   2589       this.triggerMethod('add:child', view);
   2590   
   2591       view._parent = this;
   2592   
   2593       return view;
   2594     },
   2595   
   2596     // Internal method. This decrements or increments the indices of views after the
   2597     // added/removed view to keep in sync with the collection.
   2598     _updateIndices: function(view, increment, index) {
   2599       if (!this.getOption('sort')) {
   2600         return;
   2601       }
   2602   
   2603       if (increment) {
   2604         // assign the index to the view
   2605         view._index = index;
   2606       }
   2607   
   2608       // update the indexes of views after this one
   2609       this.children.each(function(laterView) {
   2610         if (laterView._index >= view._index) {
   2611           laterView._index += increment ? 1 : -1;
   2612         }
   2613       });
   2614     },
   2615   
   2616     // Internal Method. Add the view to children and render it at
   2617     // the given index.
   2618     _addChildView: function(view, index) {
   2619       // Only trigger attach if already shown, attached, and not buffering, otherwise endBuffer() or
   2620       // Region#show() handles this.
   2621       var canTriggerAttach = this._isShown && !this.isBuffering && Marionette.isNodeAttached(this.el);
   2622       var nestedViews;
   2623   
   2624       // set up the child view event forwarding
   2625       this.proxyChildEvents(view);
   2626   
   2627       view.once('render', function() {
   2628         // trigger the 'before:show' event on `view` if the collection view has already been shown
   2629         if (this._isShown && !this.isBuffering) {
   2630           Marionette.triggerMethodOn(view, 'before:show', view);
   2631         }
   2632   
   2633         // Trigger `before:attach` following `render` to avoid adding logic and event triggers
   2634         // to public method `renderChildView()`.
   2635         if (canTriggerAttach && this._triggerBeforeAttach) {
   2636           nestedViews = this._getViewAndNested(view);
   2637           this._triggerMethodMany(nestedViews, this, 'before:attach');
   2638         }
   2639       }, this);
   2640   
   2641       // Store the child view itself so we can properly remove and/or destroy it later
   2642       this.children.add(view);
   2643       this.renderChildView(view, index);
   2644   
   2645       // Trigger `attach`
   2646       if (canTriggerAttach && this._triggerAttach) {
   2647         nestedViews = this._getViewAndNested(view);
   2648         this._triggerMethodMany(nestedViews, this, 'attach');
   2649       }
   2650       // Trigger `show`
   2651       if (this._isShown && !this.isBuffering) {
   2652         Marionette.triggerMethodOn(view, 'show', view);
   2653       }
   2654     },
   2655   
   2656     // render the child view
   2657     renderChildView: function(view, index) {
   2658       if (!view.supportsRenderLifecycle) {
   2659         Marionette.triggerMethodOn(view, 'before:render', view);
   2660       }
   2661       view.render();
   2662       if (!view.supportsRenderLifecycle) {
   2663         Marionette.triggerMethodOn(view, 'render', view);
   2664       }
   2665       this.attachHtml(this, view, index);
   2666       return view;
   2667     },
   2668   
   2669     // Build a `childView` for a model in the collection.
   2670     buildChildView: function(child, ChildViewClass, childViewOptions) {
   2671       var options = _.extend({model: child}, childViewOptions);
   2672       var childView = new ChildViewClass(options);
   2673       Marionette.MonitorDOMRefresh(childView);
   2674       return childView;
   2675     },
   2676   
   2677     // Remove the child view and destroy it.
   2678     // This function also updates the indices of
   2679     // later views in the collection in order to keep
   2680     // the children in sync with the collection.
   2681     removeChildView: function(view) {
   2682       if (!view) { return view; }
   2683   
   2684       this.triggerMethod('before:remove:child', view);
   2685   
   2686       if (!view.supportsDestroyLifecycle) {
   2687         Marionette.triggerMethodOn(view, 'before:destroy', view);
   2688       }
   2689       // call 'destroy' or 'remove', depending on which is found
   2690       if (view.destroy) {
   2691         view.destroy();
   2692       } else {
   2693         view.remove();
   2694       }
   2695       if (!view.supportsDestroyLifecycle) {
   2696         Marionette.triggerMethodOn(view, 'destroy', view);
   2697       }
   2698   
   2699       delete view._parent;
   2700       this.stopListening(view);
   2701       this.children.remove(view);
   2702       this.triggerMethod('remove:child', view);
   2703   
   2704       // decrement the index of views after this one
   2705       this._updateIndices(view, false);
   2706   
   2707       return view;
   2708     },
   2709   
   2710     // check if the collection is empty
   2711     isEmpty: function() {
   2712       return !this.collection || this.collection.length === 0;
   2713     },
   2714   
   2715     // If empty, show the empty view
   2716     checkEmpty: function() {
   2717       if (this.isEmpty(this.collection)) {
   2718         this.showEmptyView();
   2719       }
   2720     },
   2721   
   2722     // You might need to override this if you've overridden attachHtml
   2723     attachBuffer: function(collectionView, buffer) {
   2724       collectionView.$el.append(buffer);
   2725     },
   2726   
   2727     // Create a fragment buffer from the currently buffered children
   2728     _createBuffer: function() {
   2729       var elBuffer = document.createDocumentFragment();
   2730       _.each(this._bufferedChildren, function(b) {
   2731         elBuffer.appendChild(b.el);
   2732       });
   2733       return elBuffer;
   2734     },
   2735   
   2736     // Append the HTML to the collection's `el`.
   2737     // Override this method to do something other
   2738     // than `.append`.
   2739     attachHtml: function(collectionView, childView, index) {
   2740       if (collectionView.isBuffering) {
   2741         // buffering happens on reset events and initial renders
   2742         // in order to reduce the number of inserts into the
   2743         // document, which are expensive.
   2744         collectionView._bufferedChildren.splice(index, 0, childView);
   2745       } else {
   2746         // If we've already rendered the main collection, append
   2747         // the new child into the correct order if we need to. Otherwise
   2748         // append to the end.
   2749         if (!collectionView._insertBefore(childView, index)) {
   2750           collectionView._insertAfter(childView);
   2751         }
   2752       }
   2753     },
   2754   
   2755     // Internal method. Check whether we need to insert the view into
   2756     // the correct position.
   2757     _insertBefore: function(childView, index) {
   2758       var currentView;
   2759       var findPosition = this.getOption('sort') && (index < this.children.length - 1);
   2760       if (findPosition) {
   2761         // Find the view after this one
   2762         currentView = this.children.find(function(view) {
   2763           return view._index === index + 1;
   2764         });
   2765       }
   2766   
   2767       if (currentView) {
   2768         currentView.$el.before(childView.el);
   2769         return true;
   2770       }
   2771   
   2772       return false;
   2773     },
   2774   
   2775     // Internal method. Append a view to the end of the $el
   2776     _insertAfter: function(childView) {
   2777       this.$el.append(childView.el);
   2778     },
   2779   
   2780     // Internal method to set up the `children` object for
   2781     // storing all of the child views
   2782     _initChildViewStorage: function() {
   2783       this.children = new Backbone.ChildViewContainer();
   2784     },
   2785   
   2786     // Handle cleanup and other destroying needs for the collection of views
   2787     destroy: function() {
   2788       if (this.isDestroyed) { return this; }
   2789   
   2790       this.triggerMethod('before:destroy:collection');
   2791       this.destroyChildren({checkEmpty: false});
   2792       this.triggerMethod('destroy:collection');
   2793   
   2794       return Marionette.View.prototype.destroy.apply(this, arguments);
   2795     },
   2796   
   2797     // Destroy the child views that this collection view
   2798     // is holding on to, if any
   2799     destroyChildren: function(options) {
   2800       var destroyOptions = options || {};
   2801       var shouldCheckEmpty = true;
   2802       var childViews = this.children.map(_.identity);
   2803   
   2804       if (!_.isUndefined(destroyOptions.checkEmpty)) {
   2805         shouldCheckEmpty = destroyOptions.checkEmpty;
   2806       }
   2807   
   2808       this.children.each(this.removeChildView, this);
   2809   
   2810       if (shouldCheckEmpty) {
   2811         this.checkEmpty();
   2812       }
   2813       return childViews;
   2814     },
   2815   
   2816     // Return true if the given child should be shown
   2817     // Return false otherwise
   2818     // The filter will be passed (child, index, collection)
   2819     // Where
   2820     //  'child' is the given model
   2821     //  'index' is the index of that model in the collection
   2822     //  'collection' is the collection referenced by this CollectionView
   2823     _shouldAddChild: function(child, index) {
   2824       var filter = this.getOption('filter');
   2825       return !_.isFunction(filter) || filter.call(this, child, index, this.collection);
   2826     },
   2827   
   2828     // Set up the child view event forwarding. Uses a "childview:"
   2829     // prefix in front of all forwarded events.
   2830     proxyChildEvents: function(view) {
   2831       var prefix = this.getOption('childViewEventPrefix');
   2832   
   2833       // Forward all child view events through the parent,
   2834       // prepending "childview:" to the event name
   2835       this.listenTo(view, 'all', function() {
   2836         var args = _.toArray(arguments);
   2837         var rootEvent = args[0];
   2838         var childEvents = this.normalizeMethods(_.result(this, 'childEvents'));
   2839   
   2840         args[0] = prefix + ':' + rootEvent;
   2841         args.splice(1, 0, view);
   2842   
   2843         // call collectionView childEvent if defined
   2844         if (typeof childEvents !== 'undefined' && _.isFunction(childEvents[rootEvent])) {
   2845           childEvents[rootEvent].apply(this, args.slice(1));
   2846         }
   2847   
   2848         this.triggerMethod.apply(this, args);
   2849       });
   2850     },
   2851   
   2852     _getImmediateChildren: function() {
   2853       return _.values(this.children._views);
   2854     },
   2855   
   2856     _getViewAndNested: function(view) {
   2857       // This will not fail on Backbone.View which does not have #_getNestedViews.
   2858       return [view].concat(_.result(view, '_getNestedViews') || []);
   2859     },
   2860   
   2861     getViewComparator: function() {
   2862       return this.getOption('viewComparator');
   2863     }
   2864   });
   2865   
   2866   /* jshint maxstatements: 17, maxlen: 117 */
   2867   
   2868   // Composite View
   2869   // --------------
   2870   
   2871   // Used for rendering a branch-leaf, hierarchical structure.
   2872   // Extends directly from CollectionView and also renders an
   2873   // a child view as `modelView`, for the top leaf
   2874   Marionette.CompositeView = Marionette.CollectionView.extend({
   2875   
   2876     // Setting up the inheritance chain which allows changes to
   2877     // Marionette.CollectionView.prototype.constructor which allows overriding
   2878     // option to pass '{sort: false}' to prevent the CompositeView from
   2879     // maintaining the sorted order of the collection.
   2880     // This will fallback onto appending childView's to the end.
   2881     constructor: function() {
   2882       Marionette.CollectionView.apply(this, arguments);
   2883     },
   2884   
   2885     // Configured the initial events that the composite view
   2886     // binds to. Override this method to prevent the initial
   2887     // events, or to add your own initial events.
   2888     _initialEvents: function() {
   2889   
   2890       // Bind only after composite view is rendered to avoid adding child views
   2891       // to nonexistent childViewContainer
   2892   
   2893       if (this.collection) {
   2894         this.listenTo(this.collection, 'add', this._onCollectionAdd);
   2895         this.listenTo(this.collection, 'remove', this._onCollectionRemove);
   2896         this.listenTo(this.collection, 'reset', this._renderChildren);
   2897   
   2898         if (this.getOption('sort')) {
   2899           this.listenTo(this.collection, 'sort', this._sortViews);
   2900         }
   2901       }
   2902     },
   2903   
   2904     // Retrieve the `childView` to be used when rendering each of
   2905     // the items in the collection. The default is to return
   2906     // `this.childView` or Marionette.CompositeView if no `childView`
   2907     // has been defined
   2908     getChildView: function(child) {
   2909       var childView = this.getOption('childView') || this.constructor;
   2910   
   2911       return childView;
   2912     },
   2913   
   2914     // Serialize the model for the view.
   2915     // You can override the `serializeData` method in your own view
   2916     // definition, to provide custom serialization for your view's data.
   2917     serializeData: function() {
   2918       var data = {};
   2919   
   2920       if (this.model) {
   2921         data = _.partial(this.serializeModel, this.model).apply(this, arguments);
   2922       }
   2923   
   2924       return data;
   2925     },
   2926   
   2927     // Renders the model and the collection.
   2928     render: function() {
   2929       this._ensureViewIsIntact();
   2930       this._isRendering = true;
   2931       this.resetChildViewContainer();
   2932   
   2933       this.triggerMethod('before:render', this);
   2934   
   2935       this._renderTemplate();
   2936       this._renderChildren();
   2937   
   2938       this._isRendering = false;
   2939       this.isRendered = true;
   2940       this.triggerMethod('render', this);
   2941       return this;
   2942     },
   2943   
   2944     _renderChildren: function() {
   2945       if (this.isRendered || this._isRendering) {
   2946         Marionette.CollectionView.prototype._renderChildren.call(this);
   2947       }
   2948     },
   2949   
   2950     // Render the root template that the children
   2951     // views are appended to
   2952     _renderTemplate: function() {
   2953       var data = {};
   2954       data = this.serializeData();
   2955       data = this.mixinTemplateHelpers(data);
   2956   
   2957       this.triggerMethod('before:render:template');
   2958   
   2959       var template = this.getTemplate();
   2960       var html = Marionette.Renderer.render(template, data, this);
   2961       this.attachElContent(html);
   2962   
   2963       // the ui bindings is done here and not at the end of render since they
   2964       // will not be available until after the model is rendered, but should be
   2965       // available before the collection is rendered.
   2966       this.bindUIElements();
   2967       this.triggerMethod('render:template');
   2968     },
   2969   
   2970     // Attaches the content of the root.
   2971     // This method can be overridden to optimize rendering,
   2972     // or to render in a non standard way.
   2973     //
   2974     // For example, using `innerHTML` instead of `$el.html`
   2975     //
   2976     // ```js
   2977     // attachElContent: function(html) {
   2978     //   this.el.innerHTML = html;
   2979     //   return this;
   2980     // }
   2981     // ```
   2982     attachElContent: function(html) {
   2983       this.$el.html(html);
   2984   
   2985       return this;
   2986     },
   2987   
   2988     // You might need to override this if you've overridden attachHtml
   2989     attachBuffer: function(compositeView, buffer) {
   2990       var $container = this.getChildViewContainer(compositeView);
   2991       $container.append(buffer);
   2992     },
   2993   
   2994     // Internal method. Append a view to the end of the $el.
   2995     // Overidden from CollectionView to ensure view is appended to
   2996     // childViewContainer
   2997     _insertAfter: function(childView) {
   2998       var $container = this.getChildViewContainer(this, childView);
   2999       $container.append(childView.el);
   3000     },
   3001   
   3002     // Internal method. Append reordered childView'.
   3003     // Overidden from CollectionView to ensure reordered views
   3004     // are appended to childViewContainer
   3005     _appendReorderedChildren: function(children) {
   3006       var $container = this.getChildViewContainer(this);
   3007       $container.append(children);
   3008     },
   3009   
   3010     // Internal method to ensure an `$childViewContainer` exists, for the
   3011     // `attachHtml` method to use.
   3012     getChildViewContainer: function(containerView, childView) {
   3013       if (!!containerView.$childViewContainer) {
   3014         return containerView.$childViewContainer;
   3015       }
   3016   
   3017       var container;
   3018       var childViewContainer = Marionette.getOption(containerView, 'childViewContainer');
   3019       if (childViewContainer) {
   3020   
   3021         var selector = Marionette._getValue(childViewContainer, containerView);
   3022   
   3023         if (selector.charAt(0) === '@' && containerView.ui) {
   3024           container = containerView.ui[selector.substr(4)];
   3025         } else {
   3026           container = containerView.$(selector);
   3027         }
   3028   
   3029         if (container.length <= 0) {
   3030           throw new Marionette.Error({
   3031             name: 'ChildViewContainerMissingError',
   3032             message: 'The specified "childViewContainer" was not found: ' + containerView.childViewContainer
   3033           });
   3034         }
   3035   
   3036       } else {
   3037         container = containerView.$el;
   3038       }
   3039   
   3040       containerView.$childViewContainer = container;
   3041       return container;
   3042     },
   3043   
   3044     // Internal method to reset the `$childViewContainer` on render
   3045     resetChildViewContainer: function() {
   3046       if (this.$childViewContainer) {
   3047         this.$childViewContainer = undefined;
   3048       }
   3049     }
   3050   });
   3051   
   3052   // Layout View
   3053   // -----------
   3054   
   3055   // Used for managing application layoutViews, nested layoutViews and
   3056   // multiple regions within an application or sub-application.
   3057   //
   3058   // A specialized view class that renders an area of HTML and then
   3059   // attaches `Region` instances to the specified `regions`.
   3060   // Used for composite view management and sub-application areas.
   3061   Marionette.LayoutView = Marionette.ItemView.extend({
   3062     regionClass: Marionette.Region,
   3063   
   3064     options: {
   3065       destroyImmediate: false
   3066     },
   3067   
   3068     // used as the prefix for child view events
   3069     // that are forwarded through the layoutview
   3070     childViewEventPrefix: 'childview',
   3071   
   3072     // Ensure the regions are available when the `initialize` method
   3073     // is called.
   3074     constructor: function(options) {
   3075       options = options || {};
   3076   
   3077       this._firstRender = true;
   3078       this._initializeRegions(options);
   3079   
   3080       Marionette.ItemView.call(this, options);
   3081     },
   3082   
   3083     // LayoutView's render will use the existing region objects the
   3084     // first time it is called. Subsequent calls will destroy the
   3085     // views that the regions are showing and then reset the `el`
   3086     // for the regions to the newly rendered DOM elements.
   3087     render: function() {
   3088       this._ensureViewIsIntact();
   3089   
   3090       if (this._firstRender) {
   3091         // if this is the first render, don't do anything to
   3092         // reset the regions
   3093         this._firstRender = false;
   3094       } else {
   3095         // If this is not the first render call, then we need to
   3096         // re-initialize the `el` for each region
   3097         this._reInitializeRegions();
   3098       }
   3099   
   3100       return Marionette.ItemView.prototype.render.apply(this, arguments);
   3101     },
   3102   
   3103     // Handle destroying regions, and then destroy the view itself.
   3104     destroy: function() {
   3105       if (this.isDestroyed) { return this; }
   3106       // #2134: remove parent element before destroying the child views, so
   3107       // removing the child views doesn't retrigger repaints
   3108       if (this.getOption('destroyImmediate') === true) {
   3109         this.$el.remove();
   3110       }
   3111       this.regionManager.destroy();
   3112       return Marionette.ItemView.prototype.destroy.apply(this, arguments);
   3113     },
   3114   
   3115     showChildView: function(regionName, view, options) {
   3116       var region = this.getRegion(regionName);
   3117       return region.show.apply(region, _.rest(arguments));
   3118     },
   3119   
   3120     getChildView: function(regionName) {
   3121       return this.getRegion(regionName).currentView;
   3122     },
   3123   
   3124     // Add a single region, by name, to the layoutView
   3125     addRegion: function(name, definition) {
   3126       var regions = {};
   3127       regions[name] = definition;
   3128       return this._buildRegions(regions)[name];
   3129     },
   3130   
   3131     // Add multiple regions as a {name: definition, name2: def2} object literal
   3132     addRegions: function(regions) {
   3133       this.regions = _.extend({}, this.regions, regions);
   3134       return this._buildRegions(regions);
   3135     },
   3136   
   3137     // Remove a single region from the LayoutView, by name
   3138     removeRegion: function(name) {
   3139       delete this.regions[name];
   3140       return this.regionManager.removeRegion(name);
   3141     },
   3142   
   3143     // Provides alternative access to regions
   3144     // Accepts the region name
   3145     // getRegion('main')
   3146     getRegion: function(region) {
   3147       return this.regionManager.get(region);
   3148     },
   3149   
   3150     // Get all regions
   3151     getRegions: function() {
   3152       return this.regionManager.getRegions();
   3153     },
   3154   
   3155     // internal method to build regions
   3156     _buildRegions: function(regions) {
   3157       var defaults = {
   3158         regionClass: this.getOption('regionClass'),
   3159         parentEl: _.partial(_.result, this, 'el')
   3160       };
   3161   
   3162       return this.regionManager.addRegions(regions, defaults);
   3163     },
   3164   
   3165     // Internal method to initialize the regions that have been defined in a
   3166     // `regions` attribute on this layoutView.
   3167     _initializeRegions: function(options) {
   3168       var regions;
   3169       this._initRegionManager();
   3170   
   3171       regions = Marionette._getValue(this.regions, this, [options]) || {};
   3172   
   3173       // Enable users to define `regions` as instance options.
   3174       var regionOptions = this.getOption.call(options, 'regions');
   3175   
   3176       // enable region options to be a function
   3177       regionOptions = Marionette._getValue(regionOptions, this, [options]);
   3178   
   3179       _.extend(regions, regionOptions);
   3180   
   3181       // Normalize region selectors hash to allow
   3182       // a user to use the @ui. syntax.
   3183       regions = this.normalizeUIValues(regions, ['selector', 'el']);
   3184   
   3185       this.addRegions(regions);
   3186     },
   3187   
   3188     // Internal method to re-initialize all of the regions by updating the `el` that
   3189     // they point to
   3190     _reInitializeRegions: function() {
   3191       this.regionManager.invoke('reset');
   3192     },
   3193   
   3194     // Enable easy overriding of the default `RegionManager`
   3195     // for customized region interactions and business specific
   3196     // view logic for better control over single regions.
   3197     getRegionManager: function() {
   3198       return new Marionette.RegionManager();
   3199     },
   3200   
   3201     // Internal method to initialize the region manager
   3202     // and all regions in it
   3203     _initRegionManager: function() {
   3204       this.regionManager = this.getRegionManager();
   3205       this.regionManager._parent = this;
   3206   
   3207       this.listenTo(this.regionManager, 'before:add:region', function(name) {
   3208         this.triggerMethod('before:add:region', name);
   3209       });
   3210   
   3211       this.listenTo(this.regionManager, 'add:region', function(name, region) {
   3212         this[name] = region;
   3213         this.triggerMethod('add:region', name, region);
   3214       });
   3215   
   3216       this.listenTo(this.regionManager, 'before:remove:region', function(name) {
   3217         this.triggerMethod('before:remove:region', name);
   3218       });
   3219   
   3220       this.listenTo(this.regionManager, 'remove:region', function(name, region) {
   3221         delete this[name];
   3222         this.triggerMethod('remove:region', name, region);
   3223       });
   3224     },
   3225   
   3226     _getImmediateChildren: function() {
   3227       return _.chain(this.regionManager.getRegions())
   3228         .pluck('currentView')
   3229         .compact()
   3230         .value();
   3231     }
   3232   });
   3233   
   3234 
   3235   // Behavior
   3236   // --------
   3237   
   3238   // A Behavior is an isolated set of DOM /
   3239   // user interactions that can be mixed into any View.
   3240   // Behaviors allow you to blackbox View specific interactions
   3241   // into portable logical chunks, keeping your views simple and your code DRY.
   3242   
   3243   Marionette.Behavior = Marionette.Object.extend({
   3244     constructor: function(options, view) {
   3245       // Setup reference to the view.
   3246       // this comes in handle when a behavior
   3247       // wants to directly talk up the chain
   3248       // to the view.
   3249       this.view = view;
   3250       this.defaults = _.result(this, 'defaults') || {};
   3251       this.options  = _.extend({}, this.defaults, options);
   3252       // Construct an internal UI hash using
   3253       // the views UI hash and then the behaviors UI hash.
   3254       // This allows the user to use UI hash elements
   3255       // defined in the parent view as well as those
   3256       // defined in the given behavior.
   3257       this.ui = _.extend({}, _.result(view, 'ui'), _.result(this, 'ui'));
   3258   
   3259       Marionette.Object.apply(this, arguments);
   3260     },
   3261   
   3262     // proxy behavior $ method to the view
   3263     // this is useful for doing jquery DOM lookups
   3264     // scoped to behaviors view.
   3265     $: function() {
   3266       return this.view.$.apply(this.view, arguments);
   3267     },
   3268   
   3269     // Stops the behavior from listening to events.
   3270     // Overrides Object#destroy to prevent additional events from being triggered.
   3271     destroy: function() {
   3272       this.stopListening();
   3273   
   3274       return this;
   3275     },
   3276   
   3277     proxyViewProperties: function(view) {
   3278       this.$el = view.$el;
   3279       this.el = view.el;
   3280     }
   3281   });
   3282   
   3283   /* jshint maxlen: 143 */
   3284   // Behaviors
   3285   // ---------
   3286   
   3287   // Behaviors is a utility class that takes care of
   3288   // gluing your behavior instances to their given View.
   3289   // The most important part of this class is that you
   3290   // **MUST** override the class level behaviorsLookup
   3291   // method for things to work properly.
   3292   
   3293   Marionette.Behaviors = (function(Marionette, _) {
   3294     // Borrow event splitter from Backbone
   3295     var delegateEventSplitter = /^(\S+)\s*(.*)$/;
   3296   
   3297     function Behaviors(view, behaviors) {
   3298   
   3299       if (!_.isObject(view.behaviors)) {
   3300         return {};
   3301       }
   3302   
   3303       // Behaviors defined on a view can be a flat object literal
   3304       // or it can be a function that returns an object.
   3305       behaviors = Behaviors.parseBehaviors(view, behaviors || _.result(view, 'behaviors'));
   3306   
   3307       // Wraps several of the view's methods
   3308       // calling the methods first on each behavior
   3309       // and then eventually calling the method on the view.
   3310       Behaviors.wrap(view, behaviors, _.keys(methods));
   3311       return behaviors;
   3312     }
   3313   
   3314     var methods = {
   3315       behaviorTriggers: function(behaviorTriggers, behaviors) {
   3316         var triggerBuilder = new BehaviorTriggersBuilder(this, behaviors);
   3317         return triggerBuilder.buildBehaviorTriggers();
   3318       },
   3319   
   3320       behaviorEvents: function(behaviorEvents, behaviors) {
   3321         var _behaviorsEvents = {};
   3322   
   3323         _.each(behaviors, function(b, i) {
   3324           var _events = {};
   3325           var behaviorEvents = _.clone(_.result(b, 'events')) || {};
   3326   
   3327           // Normalize behavior events hash to allow
   3328           // a user to use the @ui. syntax.
   3329           behaviorEvents = Marionette.normalizeUIKeys(behaviorEvents, getBehaviorsUI(b));
   3330   
   3331           var j = 0;
   3332           _.each(behaviorEvents, function(behaviour, key) {
   3333             var match     = key.match(delegateEventSplitter);
   3334   
   3335             // Set event name to be namespaced using the view cid,
   3336             // the behavior index, and the behavior event index
   3337             // to generate a non colliding event namespace
   3338             // http://api.jquery.com/event.namespace/
   3339             var eventName = match[1] + '.' + [this.cid, i, j++, ' '].join('');
   3340             var selector  = match[2];
   3341   
   3342             var eventKey  = eventName + selector;
   3343             var handler   = _.isFunction(behaviour) ? behaviour : b[behaviour];
   3344             if (!handler) { return; }
   3345             _events[eventKey] = _.bind(handler, b);
   3346           }, this);
   3347   
   3348           _behaviorsEvents = _.extend(_behaviorsEvents, _events);
   3349         }, this);
   3350   
   3351         return _behaviorsEvents;
   3352       }
   3353     };
   3354   
   3355     _.extend(Behaviors, {
   3356   
   3357       // Placeholder method to be extended by the user.
   3358       // The method should define the object that stores the behaviors.
   3359       // i.e.
   3360       //
   3361       // ```js
   3362       // Marionette.Behaviors.behaviorsLookup: function() {
   3363       //   return App.Behaviors
   3364       // }
   3365       // ```
   3366       behaviorsLookup: function() {
   3367         throw new Marionette.Error({
   3368           message: 'You must define where your behaviors are stored.',
   3369           url: 'marionette.behaviors.html#behaviorslookup'
   3370         });
   3371       },
   3372   
   3373       // Takes care of getting the behavior class
   3374       // given options and a key.
   3375       // If a user passes in options.behaviorClass
   3376       // default to using that. Otherwise delegate
   3377       // the lookup to the users `behaviorsLookup` implementation.
   3378       getBehaviorClass: function(options, key) {
   3379         if (options.behaviorClass) {
   3380           return options.behaviorClass;
   3381         }
   3382   
   3383         // Get behavior class can be either a flat object or a method
   3384         return Marionette._getValue(Behaviors.behaviorsLookup, this, [options, key])[key];
   3385       },
   3386   
   3387       // Iterate over the behaviors object, for each behavior
   3388       // instantiate it and get its grouped behaviors.
   3389       parseBehaviors: function(view, behaviors) {
   3390         return _.chain(behaviors).map(function(options, key) {
   3391           var BehaviorClass = Behaviors.getBehaviorClass(options, key);
   3392   
   3393           var behavior = new BehaviorClass(options, view);
   3394           var nestedBehaviors = Behaviors.parseBehaviors(view, _.result(behavior, 'behaviors'));
   3395   
   3396           return [behavior].concat(nestedBehaviors);
   3397         }).flatten().value();
   3398       },
   3399   
   3400       // Wrap view internal methods so that they delegate to behaviors. For example,
   3401       // `onDestroy` should trigger destroy on all of the behaviors and then destroy itself.
   3402       // i.e.
   3403       //
   3404       // `view.delegateEvents = _.partial(methods.delegateEvents, view.delegateEvents, behaviors);`
   3405       wrap: function(view, behaviors, methodNames) {
   3406         _.each(methodNames, function(methodName) {
   3407           view[methodName] = _.partial(methods[methodName], view[methodName], behaviors);
   3408         });
   3409       }
   3410     });
   3411   
   3412     // Class to build handlers for `triggers` on behaviors
   3413     // for views
   3414     function BehaviorTriggersBuilder(view, behaviors) {
   3415       this._view      = view;
   3416       this._behaviors = behaviors;
   3417       this._triggers  = {};
   3418     }
   3419   
   3420     _.extend(BehaviorTriggersBuilder.prototype, {
   3421       // Main method to build the triggers hash with event keys and handlers
   3422       buildBehaviorTriggers: function() {
   3423         _.each(this._behaviors, this._buildTriggerHandlersForBehavior, this);
   3424         return this._triggers;
   3425       },
   3426   
   3427       // Internal method to build all trigger handlers for a given behavior
   3428       _buildTriggerHandlersForBehavior: function(behavior, i) {
   3429         var triggersHash = _.clone(_.result(behavior, 'triggers')) || {};
   3430   
   3431         triggersHash = Marionette.normalizeUIKeys(triggersHash, getBehaviorsUI(behavior));
   3432   
   3433         _.each(triggersHash, _.bind(this._setHandlerForBehavior, this, behavior, i));
   3434       },
   3435   
   3436       // Internal method to create and assign the trigger handler for a given
   3437       // behavior
   3438       _setHandlerForBehavior: function(behavior, i, eventName, trigger) {
   3439         // Unique identifier for the `this._triggers` hash
   3440         var triggerKey = trigger.replace(/^\S+/, function(triggerName) {
   3441           return triggerName + '.' + 'behaviortriggers' + i;
   3442         });
   3443   
   3444         this._triggers[triggerKey] = this._view._buildViewTrigger(eventName);
   3445       }
   3446     });
   3447   
   3448     function getBehaviorsUI(behavior) {
   3449       return behavior._uiBindings || behavior.ui;
   3450     }
   3451   
   3452     return Behaviors;
   3453   
   3454   })(Marionette, _);
   3455   
   3456 
   3457   // App Router
   3458   // ----------
   3459   
   3460   // Reduce the boilerplate code of handling route events
   3461   // and then calling a single method on another object.
   3462   // Have your routers configured to call the method on
   3463   // your object, directly.
   3464   //
   3465   // Configure an AppRouter with `appRoutes`.
   3466   //
   3467   // App routers can only take one `controller` object.
   3468   // It is recommended that you divide your controller
   3469   // objects in to smaller pieces of related functionality
   3470   // and have multiple routers / controllers, instead of
   3471   // just one giant router and controller.
   3472   //
   3473   // You can also add standard routes to an AppRouter.
   3474   
   3475   Marionette.AppRouter = Backbone.Router.extend({
   3476   
   3477     constructor: function(options) {
   3478       this.options = options || {};
   3479   
   3480       Backbone.Router.apply(this, arguments);
   3481   
   3482       var appRoutes = this.getOption('appRoutes');
   3483       var controller = this._getController();
   3484       this.processAppRoutes(controller, appRoutes);
   3485       this.on('route', this._processOnRoute, this);
   3486     },
   3487   
   3488     // Similar to route method on a Backbone Router but
   3489     // method is called on the controller
   3490     appRoute: function(route, methodName) {
   3491       var controller = this._getController();
   3492       this._addAppRoute(controller, route, methodName);
   3493     },
   3494   
   3495     // process the route event and trigger the onRoute
   3496     // method call, if it exists
   3497     _processOnRoute: function(routeName, routeArgs) {
   3498       // make sure an onRoute before trying to call it
   3499       if (_.isFunction(this.onRoute)) {
   3500         // find the path that matches the current route
   3501         var routePath = _.invert(this.getOption('appRoutes'))[routeName];
   3502         this.onRoute(routeName, routePath, routeArgs);
   3503       }
   3504     },
   3505   
   3506     // Internal method to process the `appRoutes` for the
   3507     // router, and turn them in to routes that trigger the
   3508     // specified method on the specified `controller`.
   3509     processAppRoutes: function(controller, appRoutes) {
   3510       if (!appRoutes) { return; }
   3511   
   3512       var routeNames = _.keys(appRoutes).reverse(); // Backbone requires reverted order of routes
   3513   
   3514       _.each(routeNames, function(route) {
   3515         this._addAppRoute(controller, route, appRoutes[route]);
   3516       }, this);
   3517     },
   3518   
   3519     _getController: function() {
   3520       return this.getOption('controller');
   3521     },
   3522   
   3523     _addAppRoute: function(controller, route, methodName) {
   3524       var method = controller[methodName];
   3525   
   3526       if (!method) {
   3527         throw new Marionette.Error('Method "' + methodName + '" was not found on the controller');
   3528       }
   3529   
   3530       this.route(route, methodName, _.bind(method, controller));
   3531     },
   3532   
   3533     mergeOptions: Marionette.mergeOptions,
   3534   
   3535     // Proxy `getOption` to enable getting options from this or this.options by name.
   3536     getOption: Marionette.proxyGetOption,
   3537   
   3538     triggerMethod: Marionette.triggerMethod,
   3539   
   3540     bindEntityEvents: Marionette.proxyBindEntityEvents,
   3541   
   3542     unbindEntityEvents: Marionette.proxyUnbindEntityEvents
   3543   });
   3544   
   3545   // Application
   3546   // -----------
   3547   
   3548   // Contain and manage the composite application as a whole.
   3549   // Stores and starts up `Region` objects, includes an
   3550   // event aggregator as `app.vent`
   3551   Marionette.Application = Marionette.Object.extend({
   3552     constructor: function(options) {
   3553       this._initializeRegions(options);
   3554       this._initCallbacks = new Marionette.Callbacks();
   3555       this.submodules = {};
   3556       _.extend(this, options);
   3557       this._initChannel();
   3558       Marionette.Object.apply(this, arguments);
   3559     },
   3560   
   3561     // Command execution, facilitated by Backbone.Wreqr.Commands
   3562     execute: function() {
   3563       this.commands.execute.apply(this.commands, arguments);
   3564     },
   3565   
   3566     // Request/response, facilitated by Backbone.Wreqr.RequestResponse
   3567     request: function() {
   3568       return this.reqres.request.apply(this.reqres, arguments);
   3569     },
   3570   
   3571     // Add an initializer that is either run at when the `start`
   3572     // method is called, or run immediately if added after `start`
   3573     // has already been called.
   3574     addInitializer: function(initializer) {
   3575       this._initCallbacks.add(initializer);
   3576     },
   3577   
   3578     // kick off all of the application's processes.
   3579     // initializes all of the regions that have been added
   3580     // to the app, and runs all of the initializer functions
   3581     start: function(options) {
   3582       this.triggerMethod('before:start', options);
   3583       this._initCallbacks.run(options, this);
   3584       this.triggerMethod('start', options);
   3585     },
   3586   
   3587     // Add regions to your app.
   3588     // Accepts a hash of named strings or Region objects
   3589     // addRegions({something: "#someRegion"})
   3590     // addRegions({something: Region.extend({el: "#someRegion"}) });
   3591     addRegions: function(regions) {
   3592       return this._regionManager.addRegions(regions);
   3593     },
   3594   
   3595     // Empty all regions in the app, without removing them
   3596     emptyRegions: function() {
   3597       return this._regionManager.emptyRegions();
   3598     },
   3599   
   3600     // Removes a region from your app, by name
   3601     // Accepts the regions name
   3602     // removeRegion('myRegion')
   3603     removeRegion: function(region) {
   3604       return this._regionManager.removeRegion(region);
   3605     },
   3606   
   3607     // Provides alternative access to regions
   3608     // Accepts the region name
   3609     // getRegion('main')
   3610     getRegion: function(region) {
   3611       return this._regionManager.get(region);
   3612     },
   3613   
   3614     // Get all the regions from the region manager
   3615     getRegions: function() {
   3616       return this._regionManager.getRegions();
   3617     },
   3618   
   3619     // Create a module, attached to the application
   3620     module: function(moduleNames, moduleDefinition) {
   3621   
   3622       // Overwrite the module class if the user specifies one
   3623       var ModuleClass = Marionette.Module.getClass(moduleDefinition);
   3624   
   3625       var args = _.toArray(arguments);
   3626       args.unshift(this);
   3627   
   3628       // see the Marionette.Module object for more information
   3629       return ModuleClass.create.apply(ModuleClass, args);
   3630     },
   3631   
   3632     // Enable easy overriding of the default `RegionManager`
   3633     // for customized region interactions and business-specific
   3634     // view logic for better control over single regions.
   3635     getRegionManager: function() {
   3636       return new Marionette.RegionManager();
   3637     },
   3638   
   3639     // Internal method to initialize the regions that have been defined in a
   3640     // `regions` attribute on the application instance
   3641     _initializeRegions: function(options) {
   3642       var regions = _.isFunction(this.regions) ? this.regions(options) : this.regions || {};
   3643   
   3644       this._initRegionManager();
   3645   
   3646       // Enable users to define `regions` in instance options.
   3647       var optionRegions = Marionette.getOption(options, 'regions');
   3648   
   3649       // Enable region options to be a function
   3650       if (_.isFunction(optionRegions)) {
   3651         optionRegions = optionRegions.call(this, options);
   3652       }
   3653   
   3654       // Overwrite current regions with those passed in options
   3655       _.extend(regions, optionRegions);
   3656   
   3657       this.addRegions(regions);
   3658   
   3659       return this;
   3660     },
   3661   
   3662     // Internal method to set up the region manager
   3663     _initRegionManager: function() {
   3664       this._regionManager = this.getRegionManager();
   3665       this._regionManager._parent = this;
   3666   
   3667       this.listenTo(this._regionManager, 'before:add:region', function() {
   3668         Marionette._triggerMethod(this, 'before:add:region', arguments);
   3669       });
   3670   
   3671       this.listenTo(this._regionManager, 'add:region', function(name, region) {
   3672         this[name] = region;
   3673         Marionette._triggerMethod(this, 'add:region', arguments);
   3674       });
   3675   
   3676       this.listenTo(this._regionManager, 'before:remove:region', function() {
   3677         Marionette._triggerMethod(this, 'before:remove:region', arguments);
   3678       });
   3679   
   3680       this.listenTo(this._regionManager, 'remove:region', function(name) {
   3681         delete this[name];
   3682         Marionette._triggerMethod(this, 'remove:region', arguments);
   3683       });
   3684     },
   3685   
   3686     // Internal method to setup the Wreqr.radio channel
   3687     _initChannel: function() {
   3688       this.channelName = _.result(this, 'channelName') || 'global';
   3689       this.channel = _.result(this, 'channel') || Backbone.Wreqr.radio.channel(this.channelName);
   3690       this.vent = _.result(this, 'vent') || this.channel.vent;
   3691       this.commands = _.result(this, 'commands') || this.channel.commands;
   3692       this.reqres = _.result(this, 'reqres') || this.channel.reqres;
   3693     }
   3694   });
   3695   
   3696   /* jshint maxparams: 9 */
   3697   
   3698   // Module
   3699   // ------
   3700   
   3701   // A simple module system, used to create privacy and encapsulation in
   3702   // Marionette applications
   3703   Marionette.Module = function(moduleName, app, options) {
   3704     this.moduleName = moduleName;
   3705     this.options = _.extend({}, this.options, options);
   3706     // Allow for a user to overide the initialize
   3707     // for a given module instance.
   3708     this.initialize = options.initialize || this.initialize;
   3709   
   3710     // Set up an internal store for sub-modules.
   3711     this.submodules = {};
   3712   
   3713     this._setupInitializersAndFinalizers();
   3714   
   3715     // Set an internal reference to the app
   3716     // within a module.
   3717     this.app = app;
   3718   
   3719     if (_.isFunction(this.initialize)) {
   3720       this.initialize(moduleName, app, this.options);
   3721     }
   3722   };
   3723   
   3724   Marionette.Module.extend = Marionette.extend;
   3725   
   3726   // Extend the Module prototype with events / listenTo, so that the module
   3727   // can be used as an event aggregator or pub/sub.
   3728   _.extend(Marionette.Module.prototype, Backbone.Events, {
   3729   
   3730     // By default modules start with their parents.
   3731     startWithParent: true,
   3732   
   3733     // Initialize is an empty function by default. Override it with your own
   3734     // initialization logic when extending Marionette.Module.
   3735     initialize: function() {},
   3736   
   3737     // Initializer for a specific module. Initializers are run when the
   3738     // module's `start` method is called.
   3739     addInitializer: function(callback) {
   3740       this._initializerCallbacks.add(callback);
   3741     },
   3742   
   3743     // Finalizers are run when a module is stopped. They are used to teardown
   3744     // and finalize any variables, references, events and other code that the
   3745     // module had set up.
   3746     addFinalizer: function(callback) {
   3747       this._finalizerCallbacks.add(callback);
   3748     },
   3749   
   3750     // Start the module, and run all of its initializers
   3751     start: function(options) {
   3752       // Prevent re-starting a module that is already started
   3753       if (this._isInitialized) { return; }
   3754   
   3755       // start the sub-modules (depth-first hierarchy)
   3756       _.each(this.submodules, function(mod) {
   3757         // check to see if we should start the sub-module with this parent
   3758         if (mod.startWithParent) {
   3759           mod.start(options);
   3760         }
   3761       });
   3762   
   3763       // run the callbacks to "start" the current module
   3764       this.triggerMethod('before:start', options);
   3765   
   3766       this._initializerCallbacks.run(options, this);
   3767       this._isInitialized = true;
   3768   
   3769       this.triggerMethod('start', options);
   3770     },
   3771   
   3772     // Stop this module by running its finalizers and then stop all of
   3773     // the sub-modules for this module
   3774     stop: function() {
   3775       // if we are not initialized, don't bother finalizing
   3776       if (!this._isInitialized) { return; }
   3777       this._isInitialized = false;
   3778   
   3779       this.triggerMethod('before:stop');
   3780   
   3781       // stop the sub-modules; depth-first, to make sure the
   3782       // sub-modules are stopped / finalized before parents
   3783       _.invoke(this.submodules, 'stop');
   3784   
   3785       // run the finalizers
   3786       this._finalizerCallbacks.run(undefined, this);
   3787   
   3788       // reset the initializers and finalizers
   3789       this._initializerCallbacks.reset();
   3790       this._finalizerCallbacks.reset();
   3791   
   3792       this.triggerMethod('stop');
   3793     },
   3794   
   3795     // Configure the module with a definition function and any custom args
   3796     // that are to be passed in to the definition function
   3797     addDefinition: function(moduleDefinition, customArgs) {
   3798       this._runModuleDefinition(moduleDefinition, customArgs);
   3799     },
   3800   
   3801     // Internal method: run the module definition function with the correct
   3802     // arguments
   3803     _runModuleDefinition: function(definition, customArgs) {
   3804       // If there is no definition short circut the method.
   3805       if (!definition) { return; }
   3806   
   3807       // build the correct list of arguments for the module definition
   3808       var args = _.flatten([
   3809         this,
   3810         this.app,
   3811         Backbone,
   3812         Marionette,
   3813         Backbone.$, _,
   3814         customArgs
   3815       ]);
   3816   
   3817       definition.apply(this, args);
   3818     },
   3819   
   3820     // Internal method: set up new copies of initializers and finalizers.
   3821     // Calling this method will wipe out all existing initializers and
   3822     // finalizers.
   3823     _setupInitializersAndFinalizers: function() {
   3824       this._initializerCallbacks = new Marionette.Callbacks();
   3825       this._finalizerCallbacks = new Marionette.Callbacks();
   3826     },
   3827   
   3828     // import the `triggerMethod` to trigger events with corresponding
   3829     // methods if the method exists
   3830     triggerMethod: Marionette.triggerMethod
   3831   });
   3832   
   3833   // Class methods to create modules
   3834   _.extend(Marionette.Module, {
   3835   
   3836     // Create a module, hanging off the app parameter as the parent object.
   3837     create: function(app, moduleNames, moduleDefinition) {
   3838       var module = app;
   3839   
   3840       // get the custom args passed in after the module definition and
   3841       // get rid of the module name and definition function
   3842       var customArgs = _.drop(arguments, 3);
   3843   
   3844       // Split the module names and get the number of submodules.
   3845       // i.e. an example module name of `Doge.Wow.Amaze` would
   3846       // then have the potential for 3 module definitions.
   3847       moduleNames = moduleNames.split('.');
   3848       var length = moduleNames.length;
   3849   
   3850       // store the module definition for the last module in the chain
   3851       var moduleDefinitions = [];
   3852       moduleDefinitions[length - 1] = moduleDefinition;
   3853   
   3854       // Loop through all the parts of the module definition
   3855       _.each(moduleNames, function(moduleName, i) {
   3856         var parentModule = module;
   3857         module = this._getModule(parentModule, moduleName, app, moduleDefinition);
   3858         this._addModuleDefinition(parentModule, module, moduleDefinitions[i], customArgs);
   3859       }, this);
   3860   
   3861       // Return the last module in the definition chain
   3862       return module;
   3863     },
   3864   
   3865     _getModule: function(parentModule, moduleName, app, def, args) {
   3866       var options = _.extend({}, def);
   3867       var ModuleClass = this.getClass(def);
   3868   
   3869       // Get an existing module of this name if we have one
   3870       var module = parentModule[moduleName];
   3871   
   3872       if (!module) {
   3873         // Create a new module if we don't have one
   3874         module = new ModuleClass(moduleName, app, options);
   3875         parentModule[moduleName] = module;
   3876         // store the module on the parent
   3877         parentModule.submodules[moduleName] = module;
   3878       }
   3879   
   3880       return module;
   3881     },
   3882   
   3883     // ## Module Classes
   3884     //
   3885     // Module classes can be used as an alternative to the define pattern.
   3886     // The extend function of a Module is identical to the extend functions
   3887     // on other Backbone and Marionette classes.
   3888     // This allows module lifecyle events like `onStart` and `onStop` to be called directly.
   3889     getClass: function(moduleDefinition) {
   3890       var ModuleClass = Marionette.Module;
   3891   
   3892       if (!moduleDefinition) {
   3893         return ModuleClass;
   3894       }
   3895   
   3896       // If all of the module's functionality is defined inside its class,
   3897       // then the class can be passed in directly. `MyApp.module("Foo", FooModule)`.
   3898       if (moduleDefinition.prototype instanceof ModuleClass) {
   3899         return moduleDefinition;
   3900       }
   3901   
   3902       return moduleDefinition.moduleClass || ModuleClass;
   3903     },
   3904   
   3905     // Add the module definition and add a startWithParent initializer function.
   3906     // This is complicated because module definitions are heavily overloaded
   3907     // and support an anonymous function, module class, or options object
   3908     _addModuleDefinition: function(parentModule, module, def, args) {
   3909       var fn = this._getDefine(def);
   3910       var startWithParent = this._getStartWithParent(def, module);
   3911   
   3912       if (fn) {
   3913         module.addDefinition(fn, args);
   3914       }
   3915   
   3916       this._addStartWithParent(parentModule, module, startWithParent);
   3917     },
   3918   
   3919     _getStartWithParent: function(def, module) {
   3920       var swp;
   3921   
   3922       if (_.isFunction(def) && (def.prototype instanceof Marionette.Module)) {
   3923         swp = module.constructor.prototype.startWithParent;
   3924         return _.isUndefined(swp) ? true : swp;
   3925       }
   3926   
   3927       if (_.isObject(def)) {
   3928         swp = def.startWithParent;
   3929         return _.isUndefined(swp) ? true : swp;
   3930       }
   3931   
   3932       return true;
   3933     },
   3934   
   3935     _getDefine: function(def) {
   3936       if (_.isFunction(def) && !(def.prototype instanceof Marionette.Module)) {
   3937         return def;
   3938       }
   3939   
   3940       if (_.isObject(def)) {
   3941         return def.define;
   3942       }
   3943   
   3944       return null;
   3945     },
   3946   
   3947     _addStartWithParent: function(parentModule, module, startWithParent) {
   3948       module.startWithParent = module.startWithParent && startWithParent;
   3949   
   3950       if (!module.startWithParent || !!module.startWithParentIsConfigured) {
   3951         return;
   3952       }
   3953   
   3954       module.startWithParentIsConfigured = true;
   3955   
   3956       parentModule.addInitializer(function(options) {
   3957         if (module.startWithParent) {
   3958           module.start(options);
   3959         }
   3960       });
   3961     }
   3962   });
   3963   
   3964 
   3965   return Marionette;
   3966 }));