refactor code organization
[plstackapi.git] / planetstack / core / xoslib / static / js / vendor / backbone.marionette.js
1 // MarionetteJS (Backbone.Marionette)
2 // ----------------------------------
3 // v2.0.1
4 //
5 // Copyright (c)2014 Derick Bailey, Muted Solutions, LLC.
6 // Distributed under MIT license
7 //
8 // http://marionettejs.com
9
10 (function(root, factory) {
11
12   if (typeof define === 'function' && define.amd) {
13     define(['backbone', 'underscore', 'backbone.wreqr', 'backbone.babysitter'], function(Backbone, _) {
14       return (root.Marionette = factory(root, Backbone, _));
15     });
16   } else if (typeof exports !== 'undefined') {
17     var Backbone = require('backbone');
18     var _ = require('underscore');
19     var Wreqr = require('backbone.wreqr');
20     var BabySitter = require('backbone.babysitter');
21     module.exports = factory(root, Backbone, _);
22   } else {
23     root.Marionette = factory(root, root.Backbone, root._);
24   }
25
26 }(this, function(root, Backbone, _) {
27   'use strict';
28
29   var previousMarionette = root.Marionette;
30
31   var Marionette = Backbone.Marionette = {};
32
33   Marionette.VERSION = '2.0.1';
34
35   Marionette.noConflict = function() {
36     root.Marionette = previousMarionette;
37     return this;
38   };
39
40   // Get the Deferred creator for later use
41   Marionette.Deferred = Backbone.$.Deferred;
42
43   /* jshint unused: false */
44   
45   // Helpers
46   // -------
47   
48   // For slicing `arguments` in functions
49   var slice = Array.prototype.slice;
50   
51   function throwError(message, name) {
52     var error = new Error(message);
53     error.name = name || 'Error';
54     throw error;
55   }
56   
57   // Marionette.extend
58   // -----------------
59   
60   // Borrow the Backbone `extend` method so we can use it as needed
61   Marionette.extend = Backbone.Model.extend;
62   
63   // Marionette.getOption
64   // --------------------
65   
66   // Retrieve an object, function or other value from a target
67   // object or its `options`, with `options` taking precedence.
68   Marionette.getOption = function(target, optionName) {
69     if (!target || !optionName) { return; }
70     var value;
71   
72     if (target.options && (target.options[optionName] !== undefined)) {
73       value = target.options[optionName];
74     } else {
75       value = target[optionName];
76     }
77   
78     return value;
79   };
80   
81   // Proxy `Marionette.getOption`
82   Marionette.proxyGetOption = function(optionName) {
83     return Marionette.getOption(this, optionName);
84   };
85   
86   // Marionette.normalizeMethods
87   // ----------------------
88   
89   // Pass in a mapping of events => functions or function names
90   // and return a mapping of events => functions
91   Marionette.normalizeMethods = function(hash) {
92     var normalizedHash = {}, method;
93     _.each(hash, function(fn, name) {
94       method = fn;
95       if (!_.isFunction(method)) {
96         method = this[method];
97       }
98       if (!method) {
99         return;
100       }
101       normalizedHash[name] = method;
102     }, this);
103     return normalizedHash;
104   };
105   
106   
107   // allows for the use of the @ui. syntax within
108   // a given key for triggers and events
109   // swaps the @ui with the associated selector
110   Marionette.normalizeUIKeys = function(hash, ui) {
111     if (typeof(hash) === 'undefined') {
112       return;
113     }
114   
115     _.each(_.keys(hash), function(v) {
116       var pattern = /@ui.[a-zA-Z_$0-9]*/g;
117       if (v.match(pattern)) {
118         hash[v.replace(pattern, function(r) {
119           return ui[r.slice(4)];
120         })] = hash[v];
121         delete hash[v];
122       }
123     });
124   
125     return hash;
126   };
127   
128   // Mix in methods from Underscore, for iteration, and other
129   // collection related features.
130   // Borrowing this code from Backbone.Collection:
131   // http://backbonejs.org/docs/backbone.html#section-106
132   Marionette.actAsCollection = function(object, listProperty) {
133     var methods = ['forEach', 'each', 'map', 'find', 'detect', 'filter',
134       'select', 'reject', 'every', 'all', 'some', 'any', 'include',
135       'contains', 'invoke', 'toArray', 'first', 'initial', 'rest',
136       'last', 'without', 'isEmpty', 'pluck'];
137   
138     _.each(methods, function(method) {
139       object[method] = function() {
140         var list = _.values(_.result(this, listProperty));
141         var args = [list].concat(_.toArray(arguments));
142         return _[method].apply(_, args);
143       };
144     });
145   };
146   
147   // Trigger an event and/or a corresponding method name. Examples:
148   //
149   // `this.triggerMethod("foo")` will trigger the "foo" event and
150   // call the "onFoo" method.
151   //
152   // `this.triggerMethod("foo:bar")` will trigger the "foo:bar" event and
153   // call the "onFooBar" method.
154   Marionette.triggerMethod = (function() {
155   
156     // split the event name on the ":"
157     var splitter = /(^|:)(\w)/gi;
158   
159     // take the event section ("section1:section2:section3")
160     // and turn it in to uppercase name
161     function getEventName(match, prefix, eventName) {
162       return eventName.toUpperCase();
163     }
164   
165     // actual triggerMethod implementation
166     var triggerMethod = function(event) {
167       // get the method name from the event name
168       var methodName = 'on' + event.replace(splitter, getEventName);
169       var method = this[methodName];
170       var result;
171   
172       // call the onMethodName if it exists
173       if (_.isFunction(method)) {
174         // pass all arguments, except the event name
175         result = method.apply(this, _.tail(arguments));
176       }
177   
178       // trigger the event, if a trigger method exists
179       if (_.isFunction(this.trigger)) {
180         this.trigger.apply(this, arguments);
181       }
182   
183       return result;
184     };
185   
186     return triggerMethod;
187   })();
188   
189   // DOMRefresh
190   // ----------
191   //
192   // Monitor a view's state, and after it has been rendered and shown
193   // in the DOM, trigger a "dom:refresh" event every time it is
194   // re-rendered.
195   
196   Marionette.MonitorDOMRefresh = (function(documentElement) {
197     // track when the view has been shown in the DOM,
198     // using a Marionette.Region (or by other means of triggering "show")
199     function handleShow(view) {
200       view._isShown = true;
201       triggerDOMRefresh(view);
202     }
203   
204     // track when the view has been rendered
205     function handleRender(view) {
206       view._isRendered = true;
207       triggerDOMRefresh(view);
208     }
209   
210     // Trigger the "dom:refresh" event and corresponding "onDomRefresh" method
211     function triggerDOMRefresh(view) {
212       if (view._isShown && view._isRendered && isInDOM(view)) {
213         if (_.isFunction(view.triggerMethod)) {
214           view.triggerMethod('dom:refresh');
215         }
216       }
217     }
218   
219     function isInDOM(view) {
220       return documentElement.contains(view.el);
221     }
222   
223     // Export public API
224     return function(view) {
225       view.listenTo(view, 'show', function() {
226         handleShow(view);
227       });
228   
229       view.listenTo(view, 'render', function() {
230         handleRender(view);
231       });
232     };
233   })(document.documentElement);
234   
235
236   /* jshint maxparams: 5 */
237   
238   // Marionette.bindEntityEvents & unbindEntityEvents
239   // ---------------------------
240   //
241   // These methods are used to bind/unbind a backbone "entity" (collection/model)
242   // to methods on a target object.
243   //
244   // The first parameter, `target`, must have a `listenTo` method from the
245   // EventBinder object.
246   //
247   // The second parameter is the entity (Backbone.Model or Backbone.Collection)
248   // to bind the events from.
249   //
250   // The third parameter is a hash of { "event:name": "eventHandler" }
251   // configuration. Multiple handlers can be separated by a space. A
252   // function can be supplied instead of a string handler name.
253   
254   (function(Marionette) {
255     'use strict';
256   
257     // Bind the event to handlers specified as a string of
258     // handler names on the target object
259     function bindFromStrings(target, entity, evt, methods) {
260       var methodNames = methods.split(/\s+/);
261   
262       _.each(methodNames, function(methodName) {
263   
264         var method = target[methodName];
265         if (!method) {
266           throwError('Method "' + methodName +
267             '" was configured as an event handler, but does not exist.');
268         }
269   
270         target.listenTo(entity, evt, method);
271       });
272     }
273   
274     // Bind the event to a supplied callback function
275     function bindToFunction(target, entity, evt, method) {
276       target.listenTo(entity, evt, method);
277     }
278   
279     // Bind the event to handlers specified as a string of
280     // handler names on the target object
281     function unbindFromStrings(target, entity, evt, methods) {
282       var methodNames = methods.split(/\s+/);
283   
284       _.each(methodNames, function(methodName) {
285         var method = target[methodName];
286         target.stopListening(entity, evt, method);
287       });
288     }
289   
290     // Bind the event to a supplied callback function
291     function unbindToFunction(target, entity, evt, method) {
292       target.stopListening(entity, evt, method);
293     }
294   
295   
296     // generic looping function
297     function iterateEvents(target, entity, bindings, functionCallback, stringCallback) {
298       if (!entity || !bindings) { return; }
299   
300       // allow the bindings to be a function
301       if (_.isFunction(bindings)) {
302         bindings = bindings.call(target);
303       }
304   
305       // iterate the bindings and bind them
306       _.each(bindings, function(methods, evt) {
307   
308         // allow for a function as the handler,
309         // or a list of event names as a string
310         if (_.isFunction(methods)) {
311           functionCallback(target, entity, evt, methods);
312         } else {
313           stringCallback(target, entity, evt, methods);
314         }
315   
316       });
317     }
318   
319     // Export Public API
320     Marionette.bindEntityEvents = function(target, entity, bindings) {
321       iterateEvents(target, entity, bindings, bindToFunction, bindFromStrings);
322     };
323   
324     Marionette.unbindEntityEvents = function(target, entity, bindings) {
325       iterateEvents(target, entity, bindings, unbindToFunction, unbindFromStrings);
326     };
327   
328     // Proxy `bindEntityEvents`
329     Marionette.proxyBindEntityEvents = function(entity, bindings) {
330       return Marionette.bindEntityEvents(this, entity, bindings);
331     };
332   
333     // Proxy `unbindEntityEvents`
334     Marionette.proxyUnbindEntityEvents = function(entity, bindings) {
335       return Marionette.unbindEntityEvents(this, entity, bindings);
336     };
337   })(Marionette);
338   
339
340   // Callbacks
341   // ---------
342   
343   // A simple way of managing a collection of callbacks
344   // and executing them at a later point in time, using jQuery's
345   // `Deferred` object.
346   Marionette.Callbacks = function() {
347     this._deferred = Marionette.Deferred();
348     this._callbacks = [];
349   };
350   
351   _.extend(Marionette.Callbacks.prototype, {
352   
353     // Add a callback to be executed. Callbacks added here are
354     // guaranteed to execute, even if they are added after the
355     // `run` method is called.
356     add: function(callback, contextOverride) {
357       var promise = _.result(this._deferred, 'promise');
358   
359       this._callbacks.push({cb: callback, ctx: contextOverride});
360   
361       promise.then(function(args) {
362         if (contextOverride){ args.context = contextOverride; }
363         callback.call(args.context, args.options);
364       });
365     },
366   
367     // Run all registered callbacks with the context specified.
368     // Additional callbacks can be added after this has been run
369     // and they will still be executed.
370     run: function(options, context) {
371       this._deferred.resolve({
372         options: options,
373         context: context
374       });
375     },
376   
377     // Resets the list of callbacks to be run, allowing the same list
378     // to be run multiple times - whenever the `run` method is called.
379     reset: function() {
380       var callbacks = this._callbacks;
381       this._deferred = Marionette.Deferred();
382       this._callbacks = [];
383   
384       _.each(callbacks, function(cb) {
385         this.add(cb.cb, cb.ctx);
386       }, this);
387     }
388   });
389   
390   // Marionette Controller
391   // ---------------------
392   //
393   // A multi-purpose object to use as a controller for
394   // modules and routers, and as a mediator for workflow
395   // and coordination of other objects, views, and more.
396   Marionette.Controller = function(options) {
397     this.triggerMethod = Marionette.triggerMethod;
398     this.options = options || {};
399   
400     if (_.isFunction(this.initialize)) {
401       this.initialize(this.options);
402     }
403   };
404   
405   Marionette.Controller.extend = Marionette.extend;
406   
407   // Controller Methods
408   // --------------
409   
410   // Ensure it can trigger events with Backbone.Events
411   _.extend(Marionette.Controller.prototype, Backbone.Events, {
412     destroy: function() {
413       var args = Array.prototype.slice.call(arguments);
414       this.triggerMethod.apply(this, ['before:destroy'].concat(args));
415       this.triggerMethod.apply(this, ['destroy'].concat(args));
416   
417       this.stopListening();
418       this.off();
419     },
420   
421     // import the `triggerMethod` to trigger events with corresponding
422     // methods if the method exists
423     triggerMethod: Marionette.triggerMethod,
424   
425     // Proxy `getOption` to enable getting options from this or this.options by name.
426     getOption: Marionette.proxyGetOption
427   
428   });
429   
430   /* jshint maxcomplexity: 10, maxstatements: 27 */
431   
432   // Region
433   // ------
434   //
435   // Manage the visual regions of your composite application. See
436   // http://lostechies.com/derickbailey/2011/12/12/composite-js-apps-regions-and-region-managers/
437   
438   Marionette.Region = function(options) {
439     this.options = options || {};
440     this.el = this.getOption('el');
441   
442     // Handle when this.el is passed in as a $ wrapped element.
443     this.el = this.el instanceof Backbone.$ ? this.el[0] : this.el;
444   
445     if (!this.el) {
446       throwError('An "el" must be specified for a region.', 'NoElError');
447     }
448   
449     this.$el = this.getEl(this.el);
450   
451     if (this.initialize) {
452       var args = Array.prototype.slice.apply(arguments);
453       this.initialize.apply(this, args);
454     }
455   };
456   
457   
458   // Region Class methods
459   // -------------------
460   
461   _.extend(Marionette.Region, {
462   
463     // Build an instance of a region by passing in a configuration object
464     // and a default region class to use if none is specified in the config.
465     //
466     // The config object should either be a string as a jQuery DOM selector,
467     // a Region class directly, or an object literal that specifies both
468     // a selector and regionClass:
469     //
470     // ```js
471     // {
472     //   selector: "#foo",
473     //   regionClass: MyCustomRegion
474     // }
475     // ```
476     //
477     buildRegion: function(regionConfig, defaultRegionClass) {
478       var regionIsString = _.isString(regionConfig);
479       var regionSelectorIsString = _.isString(regionConfig.selector);
480       var regionClassIsUndefined = _.isUndefined(regionConfig.regionClass);
481       var regionIsClass = _.isFunction(regionConfig);
482   
483       if (!regionIsClass && !regionIsString && !regionSelectorIsString) {
484         throwError('Region must be specified as a Region class,' +
485           'a selector string or an object with selector property');
486       }
487   
488       var selector, RegionClass;
489   
490       // get the selector for the region
491   
492       if (regionIsString) {
493         selector = regionConfig;
494       }
495   
496       if (regionConfig.selector) {
497         selector = regionConfig.selector;
498         delete regionConfig.selector;
499       }
500   
501       // get the class for the region
502   
503       if (regionIsClass) {
504         RegionClass = regionConfig;
505       }
506   
507       if (!regionIsClass && regionClassIsUndefined) {
508         RegionClass = defaultRegionClass;
509       }
510   
511       if (regionConfig.regionClass) {
512         RegionClass = regionConfig.regionClass;
513         delete regionConfig.regionClass;
514       }
515   
516       if (regionIsString || regionIsClass) {
517         regionConfig = {};
518       }
519   
520       regionConfig.el = selector;
521   
522       // build the region instance
523       var region = new RegionClass(regionConfig);
524   
525       // override the `getEl` function if we have a parentEl
526       // this must be overridden to ensure the selector is found
527       // on the first use of the region. if we try to assign the
528       // region's `el` to `parentEl.find(selector)` in the object
529       // literal to build the region, the element will not be
530       // guaranteed to be in the DOM already, and will cause problems
531       if (regionConfig.parentEl) {
532         region.getEl = function(el) {
533           if (_.isObject(el)) {
534             return Backbone.$(el);
535           }
536           var parentEl = regionConfig.parentEl;
537           if (_.isFunction(parentEl)) {
538             parentEl = parentEl();
539           }
540           return parentEl.find(el);
541         };
542       }
543   
544       return region;
545     }
546   
547   });
548   
549   // Region Instance Methods
550   // -----------------------
551   
552   _.extend(Marionette.Region.prototype, Backbone.Events, {
553   
554     // Displays a backbone view instance inside of the region.
555     // Handles calling the `render` method for you. Reads content
556     // directly from the `el` attribute. Also calls an optional
557     // `onShow` and `onDestroy` method on your view, just after showing
558     // or just before destroying the view, respectively.
559     // The `preventDestroy` option can be used to prevent a view from
560     // the old view being destroyed on show.
561     // The `forceShow` option can be used to force a view to be
562     // re-rendered if it's already shown in the region.
563   
564     show: function(view, options){
565       this._ensureElement();
566   
567       var showOptions = options || {};
568       var isDifferentView = view !== this.currentView;
569       var preventDestroy =  !!showOptions.preventDestroy;
570       var forceShow = !!showOptions.forceShow;
571   
572       // we are only changing the view if there is a view to change to begin with
573       var isChangingView = !!this.currentView;
574   
575       // only destroy the view if we don't want to preventDestroy and the view is different
576       var _shouldDestroyView = !preventDestroy && isDifferentView;
577   
578       if (_shouldDestroyView) {
579         this.empty();
580       }
581   
582       // show the view if the view is different or if you want to re-show the view
583       var _shouldShowView = isDifferentView || forceShow;
584   
585       if (_shouldShowView) {
586         view.render();
587   
588         if (isChangingView) {
589           this.triggerMethod('before:swap', view);
590         }
591   
592         this.triggerMethod('before:show', view);
593         this.triggerMethod.call(view, 'before:show');
594   
595         this.attachHtml(view);
596         this.currentView = view;
597   
598         if (isChangingView) {
599           this.triggerMethod('swap', view);
600         }
601   
602         this.triggerMethod('show', view);
603   
604         if (_.isFunction(view.triggerMethod)) {
605           view.triggerMethod('show');
606         } else {
607           this.triggerMethod.call(view, 'show');
608         }
609   
610         return this;
611       }
612   
613       return this;
614     },
615   
616     _ensureElement: function(){
617       if (!_.isObject(this.el)) {
618         this.$el = this.getEl(this.el);
619         this.el = this.$el[0];
620       }
621   
622       if (!this.$el || this.$el.length === 0) {
623         throwError('An "el" ' + this.$el.selector + ' must exist in DOM');
624       }
625     },
626   
627     // Override this method to change how the region finds the
628     // DOM element that it manages. Return a jQuery selector object.
629     getEl: function(el) {
630       return Backbone.$(el);
631     },
632   
633     // Override this method to change how the new view is
634     // appended to the `$el` that the region is managing
635     attachHtml: function(view) {
636       // empty the node and append new view
637       this.el.innerHTML='';
638       this.el.appendChild(view.el);
639     },
640   
641     // Destroy the current view, if there is one. If there is no
642     // current view, it does nothing and returns immediately.
643     empty: function() {
644       var view = this.currentView;
645       if (!view || view.isDestroyed) { return; }
646   
647       this.triggerMethod('before:empty', view);
648   
649       // call 'destroy' or 'remove', depending on which is found
650       if (view.destroy) { view.destroy(); }
651       else if (view.remove) { view.remove(); }
652   
653       this.triggerMethod('empty', view);
654   
655       delete this.currentView;
656     },
657   
658     // Attach an existing view to the region. This
659     // will not call `render` or `onShow` for the new view,
660     // and will not replace the current HTML for the `el`
661     // of the region.
662     attachView: function(view) {
663       this.currentView = view;
664     },
665   
666     // Reset the region by destroying any existing view and
667     // clearing out the cached `$el`. The next time a view
668     // is shown via this region, the region will re-query the
669     // DOM for the region's `el`.
670     reset: function() {
671       this.empty();
672   
673       if (this.$el) {
674         this.el = this.$el.selector;
675       }
676   
677       delete this.$el;
678     },
679   
680     // Proxy `getOption` to enable getting options from this or this.options by name.
681     getOption: Marionette.proxyGetOption,
682   
683     // import the `triggerMethod` to trigger events with corresponding
684     // methods if the method exists
685     triggerMethod: Marionette.triggerMethod
686   });
687   
688   // Copy the `extend` function used by Backbone's classes
689   Marionette.Region.extend = Marionette.extend;
690   
691   // Marionette.RegionManager
692   // ------------------------
693   //
694   // Manage one or more related `Marionette.Region` objects.
695   Marionette.RegionManager = (function(Marionette) {
696   
697     var RegionManager = Marionette.Controller.extend({
698       constructor: function(options) {
699         this._regions = {};
700         Marionette.Controller.call(this, options);
701       },
702   
703       // Add multiple regions using an object literal, where
704       // each key becomes the region name, and each value is
705       // the region definition.
706       addRegions: function(regionDefinitions, defaults) {
707         var regions = {};
708   
709         _.each(regionDefinitions, function(definition, name) {
710           if (_.isString(definition)) {
711             definition = {selector: definition};
712           }
713   
714           if (definition.selector) {
715             definition = _.defaults({}, definition, defaults);
716           }
717   
718           var region = this.addRegion(name, definition);
719           regions[name] = region;
720         }, this);
721   
722         return regions;
723       },
724   
725       // Add an individual region to the region manager,
726       // and return the region instance
727       addRegion: function(name, definition) {
728         var region;
729   
730         var isObject = _.isObject(definition);
731         var isString = _.isString(definition);
732         var hasSelector = !!definition.selector;
733   
734         if (isString || (isObject && hasSelector)) {
735           region = Marionette.Region.buildRegion(definition, Marionette.Region);
736         } else if (_.isFunction(definition)) {
737           region = Marionette.Region.buildRegion(definition, Marionette.Region);
738         } else {
739           region = definition;
740         }
741   
742         this.triggerMethod('before:add:region', name, region);
743   
744         this._store(name, region);
745   
746         this.triggerMethod('add:region', name, region);
747         return region;
748       },
749   
750       // Get a region by name
751       get: function(name) {
752         return this._regions[name];
753       },
754   
755       // Gets all the regions contained within
756       // the `regionManager` instance.
757       getRegions: function(){
758         return _.clone(this._regions);
759       },
760   
761       // Remove a region by name
762       removeRegion: function(name) {
763         var region = this._regions[name];
764         this._remove(name, region);
765       },
766   
767       // Empty all regions in the region manager, and
768       // remove them
769       removeRegions: function() {
770         _.each(this._regions, function(region, name) {
771           this._remove(name, region);
772         }, this);
773       },
774   
775       // Empty all regions in the region manager, but
776       // leave them attached
777       emptyRegions: function() {
778         _.each(this._regions, function(region) {
779           region.empty();
780         }, this);
781       },
782   
783       // Destroy all regions and shut down the region
784       // manager entirely
785       destroy: function() {
786         this.removeRegions();
787         Marionette.Controller.prototype.destroy.apply(this, arguments);
788       },
789   
790       // internal method to store regions
791       _store: function(name, region) {
792         this._regions[name] = region;
793         this._setLength();
794       },
795   
796       // internal method to remove a region
797       _remove: function(name, region) {
798         this.triggerMethod('before:remove:region', name, region);
799         region.empty();
800         region.stopListening();
801         delete this._regions[name];
802         this._setLength();
803         this.triggerMethod('remove:region', name, region);
804       },
805   
806       // set the number of regions current held
807       _setLength: function() {
808         this.length = _.size(this._regions);
809       }
810   
811     });
812   
813     Marionette.actAsCollection(RegionManager.prototype, '_regions');
814   
815     return RegionManager;
816   })(Marionette);
817   
818
819   // Template Cache
820   // --------------
821   
822   // Manage templates stored in `<script>` blocks,
823   // caching them for faster access.
824   Marionette.TemplateCache = function(templateId) {
825     this.templateId = templateId;
826   };
827   
828   // TemplateCache object-level methods. Manage the template
829   // caches from these method calls instead of creating
830   // your own TemplateCache instances
831   _.extend(Marionette.TemplateCache, {
832     templateCaches: {},
833   
834     // Get the specified template by id. Either
835     // retrieves the cached version, or loads it
836     // from the DOM.
837     get: function(templateId) {
838       var cachedTemplate = this.templateCaches[templateId];
839   
840       if (!cachedTemplate) {
841         cachedTemplate = new Marionette.TemplateCache(templateId);
842         this.templateCaches[templateId] = cachedTemplate;
843       }
844   
845       return cachedTemplate.load();
846     },
847   
848     // Clear templates from the cache. If no arguments
849     // are specified, clears all templates:
850     // `clear()`
851     //
852     // If arguments are specified, clears each of the
853     // specified templates from the cache:
854     // `clear("#t1", "#t2", "...")`
855     clear: function() {
856       var i;
857       var args = slice.call(arguments);
858       var length = args.length;
859   
860       if (length > 0) {
861         for (i = 0; i < length; i++) {
862           delete this.templateCaches[args[i]];
863         }
864       } else {
865         this.templateCaches = {};
866       }
867     }
868   });
869   
870   // TemplateCache instance methods, allowing each
871   // template cache object to manage its own state
872   // and know whether or not it has been loaded
873   _.extend(Marionette.TemplateCache.prototype, {
874   
875     // Internal method to load the template
876     load: function() {
877       // Guard clause to prevent loading this template more than once
878       if (this.compiledTemplate) {
879         return this.compiledTemplate;
880       }
881   
882       // Load the template and compile it
883       var template = this.loadTemplate(this.templateId);
884       this.compiledTemplate = this.compileTemplate(template);
885   
886       return this.compiledTemplate;
887     },
888   
889     // Load a template from the DOM, by default. Override
890     // this method to provide your own template retrieval
891     // For asynchronous loading with AMD/RequireJS, consider
892     // using a template-loader plugin as described here:
893     // https://github.com/marionettejs/backbone.marionette/wiki/Using-marionette-with-requirejs
894     loadTemplate: function(templateId) {
895       var template = Backbone.$(templateId).html();
896   
897       if (!template || template.length === 0) {
898         throwError('Could not find template: "' + templateId + '"', 'NoTemplateError');
899       }
900   
901       return template;
902     },
903   
904     // Pre-compile the template before caching it. Override
905     // this method if you do not need to pre-compile a template
906     // (JST / RequireJS for example) or if you want to change
907     // the template engine used (Handebars, etc).
908     compileTemplate: function(rawTemplate) {
909       return _.template(rawTemplate);
910     }
911   });
912   
913   // Renderer
914   // --------
915   
916   // Render a template with data by passing in the template
917   // selector and the data to render.
918   Marionette.Renderer = {
919   
920     // Render a template with data. The `template` parameter is
921     // passed to the `TemplateCache` object to retrieve the
922     // template function. Override this method to provide your own
923     // custom rendering and template handling for all of Marionette.
924     render: function(template, data) {
925       if (!template) {
926         throwError('Cannot render the template since its false, null or undefined.',
927           'TemplateNotFoundError');
928       }
929   
930       var templateFunc;
931       if (typeof template === 'function') {
932         templateFunc = template;
933       } else {
934         templateFunc = Marionette.TemplateCache.get(template);
935       }
936   
937       return templateFunc(data);
938     }
939   };
940   
941
942   /* jshint maxlen: 114, nonew: false */
943   // Marionette.View
944   // ---------------
945   
946   // The core view class that other Marionette views extend from.
947   Marionette.View = Backbone.View.extend({
948   
949     constructor: function(options) {
950       _.bindAll(this, 'render');
951   
952       // this exposes view options to the view initializer
953       // this is a backfill since backbone removed the assignment
954       // of this.options
955       // at some point however this may be removed
956       this.options = _.extend({}, _.result(this, 'options'), _.isFunction(options) ? options.call(this) : options);
957       // parses out the @ui DSL for events
958       this.events = this.normalizeUIKeys(_.result(this, 'events'));
959   
960       if (_.isObject(this.behaviors)) {
961         new Marionette.Behaviors(this);
962       }
963   
964       Backbone.View.apply(this, arguments);
965   
966       Marionette.MonitorDOMRefresh(this);
967       this.listenTo(this, 'show', this.onShowCalled);
968     },
969   
970     // Get the template for this view
971     // instance. You can set a `template` attribute in the view
972     // definition or pass a `template: "whatever"` parameter in
973     // to the constructor options.
974     getTemplate: function() {
975       return this.getOption('template');
976     },
977   
978     // Mix in template helper methods. Looks for a
979     // `templateHelpers` attribute, which can either be an
980     // object literal, or a function that returns an object
981     // literal. All methods and attributes from this object
982     // are copies to the object passed in.
983     mixinTemplateHelpers: function(target) {
984       target = target || {};
985       var templateHelpers = this.getOption('templateHelpers');
986       if (_.isFunction(templateHelpers)) {
987         templateHelpers = templateHelpers.call(this);
988       }
989       return _.extend(target, templateHelpers);
990     },
991   
992   
993     normalizeUIKeys: function(hash) {
994       var ui = _.result(this, 'ui');
995       var uiBindings = _.result(this, '_uiBindings');
996       return Marionette.normalizeUIKeys(hash, uiBindings || ui);
997     },
998   
999     // Configure `triggers` to forward DOM events to view
1000     // events. `triggers: {"click .foo": "do:foo"}`
1001     configureTriggers: function() {
1002       if (!this.triggers) { return; }
1003   
1004       var triggerEvents = {};
1005   
1006       // Allow `triggers` to be configured as a function
1007       var triggers = this.normalizeUIKeys(_.result(this, 'triggers'));
1008   
1009       // Configure the triggers, prevent default
1010       // action and stop propagation of DOM events
1011       _.each(triggers, function(value, key) {
1012   
1013         var hasOptions = _.isObject(value);
1014         var eventName = hasOptions ? value.event : value;
1015   
1016         // build the event handler function for the DOM event
1017         triggerEvents[key] = function(e) {
1018   
1019           // stop the event in its tracks
1020           if (e) {
1021             var prevent = e.preventDefault;
1022             var stop = e.stopPropagation;
1023   
1024             var shouldPrevent = hasOptions ? value.preventDefault : prevent;
1025             var shouldStop = hasOptions ? value.stopPropagation : stop;
1026   
1027             if (shouldPrevent && prevent) { prevent.apply(e); }
1028             if (shouldStop && stop) { stop.apply(e); }
1029           }
1030   
1031           // build the args for the event
1032           var args = {
1033             view: this,
1034             model: this.model,
1035             collection: this.collection
1036           };
1037   
1038           // trigger the event
1039           this.triggerMethod(eventName, args);
1040         };
1041   
1042       }, this);
1043   
1044       return triggerEvents;
1045     },
1046   
1047     // Overriding Backbone.View's delegateEvents to handle
1048     // the `triggers`, `modelEvents`, and `collectionEvents` configuration
1049     delegateEvents: function(events) {
1050       this._delegateDOMEvents(events);
1051       this.bindEntityEvents(this.model, this.getOption('modelEvents'));
1052       this.bindEntityEvents(this.collection, this.getOption('collectionEvents'));
1053     },
1054   
1055     // internal method to delegate DOM events and triggers
1056     _delegateDOMEvents: function(events) {
1057       events = events || this.events;
1058       if (_.isFunction(events)) { events = events.call(this); }
1059   
1060       // normalize ui keys
1061       events = this.normalizeUIKeys(events);
1062   
1063       var combinedEvents = {};
1064   
1065       // look up if this view has behavior events
1066       var behaviorEvents = _.result(this, 'behaviorEvents') || {};
1067       var triggers = this.configureTriggers();
1068   
1069       // behavior events will be overriden by view events and or triggers
1070       _.extend(combinedEvents, behaviorEvents, events, triggers);
1071   
1072       Backbone.View.prototype.delegateEvents.call(this, combinedEvents);
1073     },
1074   
1075     // Overriding Backbone.View's undelegateEvents to handle unbinding
1076     // the `triggers`, `modelEvents`, and `collectionEvents` config
1077     undelegateEvents: function() {
1078       var args = Array.prototype.slice.call(arguments);
1079       Backbone.View.prototype.undelegateEvents.apply(this, args);
1080       this.unbindEntityEvents(this.model, this.getOption('modelEvents'));
1081       this.unbindEntityEvents(this.collection, this.getOption('collectionEvents'));
1082     },
1083   
1084     // Internal method, handles the `show` event.
1085     onShowCalled: function() {},
1086   
1087     // Internal helper method to verify whether the view hasn't been destroyed
1088     _ensureViewIsIntact: function() {
1089       if (this.isDestroyed) {
1090         var err = new Error('Cannot use a view thats already been destroyed.');
1091         err.name = 'ViewDestroyedError';
1092         throw err;
1093       }
1094     },
1095   
1096     // Default `destroy` implementation, for removing a view from the
1097     // DOM and unbinding it. Regions will call this method
1098     // for you. You can specify an `onDestroy` method in your view to
1099     // add custom code that is called after the view is destroyed.
1100     destroy: function() {
1101       if (this.isDestroyed) { return; }
1102   
1103       var args = Array.prototype.slice.call(arguments);
1104   
1105       this.triggerMethod.apply(this, ['before:destroy'].concat(args));
1106   
1107       // mark as destroyed before doing the actual destroy, to
1108       // prevent infinite loops within "destroy" event handlers
1109       // that are trying to destroy other views
1110       this.isDestroyed = true;
1111       this.triggerMethod.apply(this, ['destroy'].concat(args));
1112   
1113       // unbind UI elements
1114       this.unbindUIElements();
1115   
1116       // remove the view from the DOM
1117       this.remove();
1118     },
1119   
1120     // This method binds the elements specified in the "ui" hash inside the view's code with
1121     // the associated jQuery selectors.
1122     bindUIElements: function() {
1123       if (!this.ui) { return; }
1124   
1125       // store the ui hash in _uiBindings so they can be reset later
1126       // and so re-rendering the view will be able to find the bindings
1127       if (!this._uiBindings) {
1128         this._uiBindings = this.ui;
1129       }
1130   
1131       // get the bindings result, as a function or otherwise
1132       var bindings = _.result(this, '_uiBindings');
1133   
1134       // empty the ui so we don't have anything to start with
1135       this.ui = {};
1136   
1137       // bind each of the selectors
1138       _.each(_.keys(bindings), function(key) {
1139         var selector = bindings[key];
1140         this.ui[key] = this.$(selector);
1141       }, this);
1142     },
1143   
1144     // This method unbinds the elements specified in the "ui" hash
1145     unbindUIElements: function() {
1146       if (!this.ui || !this._uiBindings) { return; }
1147   
1148       // delete all of the existing ui bindings
1149       _.each(this.ui, function($el, name) {
1150         delete this.ui[name];
1151       }, this);
1152   
1153       // reset the ui element to the original bindings configuration
1154       this.ui = this._uiBindings;
1155       delete this._uiBindings;
1156     },
1157   
1158     // import the `triggerMethod` to trigger events with corresponding
1159     // methods if the method exists
1160     triggerMethod: Marionette.triggerMethod,
1161   
1162     // Imports the "normalizeMethods" to transform hashes of
1163     // events=>function references/names to a hash of events=>function references
1164     normalizeMethods: Marionette.normalizeMethods,
1165   
1166     // Proxy `getOption` to enable getting options from this or this.options by name.
1167     getOption: Marionette.proxyGetOption,
1168   
1169     // Proxy `unbindEntityEvents` to enable binding view's events from another entity.
1170     bindEntityEvents: Marionette.proxyBindEntityEvents,
1171   
1172     // Proxy `unbindEntityEvents` to enable unbinding view's events from another entity.
1173     unbindEntityEvents: Marionette.proxyUnbindEntityEvents
1174   });
1175   
1176   // Item View
1177   // ---------
1178   
1179   // A single item view implementation that contains code for rendering
1180   // with underscore.js templates, serializing the view's model or collection,
1181   // and calling several methods on extended views, such as `onRender`.
1182   Marionette.ItemView = Marionette.View.extend({
1183   
1184     // Setting up the inheritance chain which allows changes to
1185     // Marionette.View.prototype.constructor which allows overriding
1186     constructor: function() {
1187       Marionette.View.apply(this, arguments);
1188     },
1189   
1190     // Serialize the model or collection for the view. If a model is
1191     // found, `.toJSON()` is called. If a collection is found, `.toJSON()`
1192     // is also called, but is used to populate an `items` array in the
1193     // resulting data. If both are found, defaults to the model.
1194     // You can override the `serializeData` method in your own view
1195     // definition, to provide custom serialization for your view's data.
1196     serializeData: function() {
1197       var data = {};
1198   
1199       if (this.model) {
1200         data = this.model.toJSON();
1201       }
1202       else if (this.collection) {
1203         data = {items: this.collection.toJSON()};
1204       }
1205   
1206       return data;
1207     },
1208   
1209     // Render the view, defaulting to underscore.js templates.
1210     // You can override this in your view definition to provide
1211     // a very specific rendering for your view. In general, though,
1212     // you should override the `Marionette.Renderer` object to
1213     // change how Marionette renders views.
1214     render: function() {
1215       this._ensureViewIsIntact();
1216   
1217       this.triggerMethod('before:render', this);
1218   
1219       var data = this.serializeData();
1220       data = this.mixinTemplateHelpers(data);
1221   
1222       var template = this.getTemplate();
1223       var html = Marionette.Renderer.render(template, data);
1224       this.attachElContent(html);
1225       this.bindUIElements();
1226   
1227       this.triggerMethod('render', this);
1228   
1229       return this;
1230     },
1231   
1232     // Attaches the content of a given view.
1233     // This method can be overriden to optimize rendering,
1234     // or to render in a non standard way.
1235     //
1236     // For example, using `innerHTML` instead of `$el.html`
1237     //
1238     // ```js
1239     // attachElContent: function(html) {
1240     //   this.el.innerHTML = html;
1241     //   return this;
1242     // }
1243     // ```
1244     attachElContent: function(html) {
1245       this.$el.html(html);
1246   
1247       return this;
1248     },
1249   
1250     // Override the default destroy event to add a few
1251     // more events that are triggered.
1252     destroy: function() {
1253       if (this.isDestroyed) { return; }
1254   
1255       Marionette.View.prototype.destroy.apply(this, arguments);
1256     }
1257   });
1258   
1259   /* jshint maxstatements: 14 */
1260   
1261   // Collection View
1262   // ---------------
1263   
1264   // A view that iterates over a Backbone.Collection
1265   // and renders an individual child view for each model.
1266   Marionette.CollectionView = Marionette.View.extend({
1267   
1268     // used as the prefix for child view events
1269     // that are forwarded through the collectionview
1270     childViewEventPrefix: 'childview',
1271   
1272     // constructor
1273     // option to pass `{sort: false}` to prevent the `CollectionView` from
1274     // maintaining the sorted order of the collection.
1275     // This will fallback onto appending childView's to the end.
1276     constructor: function(options){
1277       var initOptions = options || {};
1278       this.sort = _.isUndefined(initOptions.sort) ? true : initOptions.sort;
1279   
1280       this._initChildViewStorage();
1281   
1282       Marionette.View.apply(this, arguments);
1283   
1284       this._initialEvents();
1285       this.initRenderBuffer();
1286     },
1287   
1288     // Instead of inserting elements one by one into the page,
1289     // it's much more performant to insert elements into a document
1290     // fragment and then insert that document fragment into the page
1291     initRenderBuffer: function() {
1292       this.elBuffer = document.createDocumentFragment();
1293       this._bufferedChildren = [];
1294     },
1295   
1296     startBuffering: function() {
1297       this.initRenderBuffer();
1298       this.isBuffering = true;
1299     },
1300   
1301     endBuffering: function() {
1302       this.isBuffering = false;
1303       this._triggerBeforeShowBufferedChildren();
1304       this.attachBuffer(this, this.elBuffer);
1305       this._triggerShowBufferedChildren();
1306       this.initRenderBuffer();
1307     },
1308   
1309     _triggerBeforeShowBufferedChildren: function() {
1310       if (this._isShown) {
1311         _.invoke(this._bufferedChildren, 'triggerMethod', 'before:show');
1312       }
1313     },
1314   
1315     _triggerShowBufferedChildren: function() {
1316       if (this._isShown) {
1317         _.each(this._bufferedChildren, function (child) {
1318           if (_.isFunction(child.triggerMethod)) {
1319             child.triggerMethod('show');
1320           } else {
1321             Marionette.triggerMethod.call(child, 'show');
1322           }
1323         });
1324         this._bufferedChildren = [];
1325       }
1326     },
1327   
1328     // Configured the initial events that the collection view
1329     // binds to.
1330     _initialEvents: function() {
1331       if (this.collection) {
1332         this.listenTo(this.collection, 'add', this._onCollectionAdd);
1333         this.listenTo(this.collection, 'remove', this._onCollectionRemove);
1334         this.listenTo(this.collection, 'reset', this.render);
1335   
1336         if (this.sort) {
1337           this.listenTo(this.collection, 'sort', this._sortViews);
1338         }
1339       }
1340     },
1341   
1342     // Handle a child added to the collection
1343     _onCollectionAdd: function(child, collection, options) {
1344       this.destroyEmptyView();
1345       var ChildView = this.getChildView(child);
1346       var index = this.collection.indexOf(child);
1347       this.addChild(child, ChildView, index);
1348     },
1349   
1350     // get the child view by model it holds, and remove it
1351     _onCollectionRemove: function(model) {
1352       var view = this.children.findByModel(model);
1353       this.removeChildView(view);
1354       this.checkEmpty();
1355     },
1356   
1357     // Override from `Marionette.View` to trigger show on child views
1358     onShowCalled: function(){
1359       this.children.each(function(child){
1360         if (_.isFunction(child.triggerMethod)) {
1361           child.triggerMethod('show');
1362         } else {
1363           Marionette.triggerMethod.call(child, 'show');
1364         }
1365       });
1366     },
1367   
1368     // Render children views. Override this method to
1369     // provide your own implementation of a render function for
1370     // the collection view.
1371     render: function() {
1372       this._ensureViewIsIntact();
1373       this.triggerMethod('before:render', this);
1374       this._renderChildren();
1375       this.triggerMethod('render', this);
1376       return this;
1377     },
1378   
1379     // Internal method. This checks for any changes in the order of the collection.
1380     // If the index of any view doesn't match, it will render.
1381     _sortViews: function(){
1382       // check for any changes in sort order of views
1383       var orderChanged = this.collection.find(function(item, index){
1384         var view = this.children.findByModel(item);
1385         return view && view._index !== index;
1386       }, this);
1387   
1388       if (orderChanged) {
1389         this.render();
1390       }
1391     },
1392   
1393     // Internal method. Separated so that CompositeView can have
1394     // more control over events being triggered, around the rendering
1395     // process
1396     _renderChildren: function() {
1397       this.startBuffering();
1398   
1399       this.destroyEmptyView();
1400       this.destroyChildren();
1401   
1402       if (!this.isEmpty(this.collection)) {
1403         this.triggerMethod('before:render:collection', this);
1404         this.showCollection();
1405         this.triggerMethod('render:collection', this);
1406       } else {
1407         this.showEmptyView();
1408       }
1409   
1410       this.endBuffering();
1411     },
1412   
1413     // Internal method to loop through collection and show each child view.
1414     showCollection: function() {
1415       var ChildView;
1416       this.collection.each(function(child, index) {
1417         ChildView = this.getChildView(child);
1418         this.addChild(child, ChildView, index);
1419       }, this);
1420     },
1421   
1422     // Internal method to show an empty view in place of
1423     // a collection of child views, when the collection is empty
1424     showEmptyView: function() {
1425       var EmptyView = this.getEmptyView();
1426   
1427       if (EmptyView && !this._showingEmptyView) {
1428         this.triggerMethod('before:render:empty');
1429   
1430         this._showingEmptyView = true;
1431         var model = new Backbone.Model();
1432         this.addEmptyView(model, EmptyView);
1433   
1434         this.triggerMethod('render:empty');
1435       }
1436     },
1437   
1438     // Internal method to destroy an existing emptyView instance
1439     // if one exists. Called when a collection view has been
1440     // rendered empty, and then a child is added to the collection.
1441     destroyEmptyView: function() {
1442       if (this._showingEmptyView) {
1443         this.destroyChildren();
1444         delete this._showingEmptyView;
1445       }
1446     },
1447   
1448     // Retrieve the empty view class
1449     getEmptyView: function() {
1450       return this.getOption('emptyView');
1451     },
1452   
1453     // Render and show the emptyView. Similar to addChild method
1454     // but "child:added" events are not fired, and the event from
1455     // emptyView are not forwarded
1456     addEmptyView: function(child, EmptyView){
1457   
1458       // get the emptyViewOptions, falling back to childViewOptions
1459       var emptyViewOptions = this.getOption('emptyViewOptions') ||
1460                             this.getOption('childViewOptions');
1461   
1462       if (_.isFunction(emptyViewOptions)){
1463         emptyViewOptions = emptyViewOptions.call(this);
1464       }
1465   
1466       // build the empty view
1467       var view = this.buildChildView(child, EmptyView, emptyViewOptions);
1468   
1469       // trigger the 'before:show' event on `view` if the collection view
1470       // has already been shown
1471       if (this._isShown){
1472         this.triggerMethod.call(view, 'before:show');
1473       }
1474   
1475       // Store the `emptyView` like a `childView` so we can properly
1476       // remove and/or close it later
1477       this.children.add(view);
1478   
1479       // Render it and show it
1480       this.renderChildView(view, -1);
1481   
1482       // call the 'show' method if the collection view
1483       // has already been shown
1484       if (this._isShown){
1485         this.triggerMethod.call(view, 'show');
1486       }
1487     },
1488   
1489     // Retrieve the childView class, either from `this.options.childView`
1490     // or from the `childView` in the object definition. The "options"
1491     // takes precedence.
1492     getChildView: function(child) {
1493       var childView = this.getOption('childView');
1494   
1495       if (!childView) {
1496         throwError('A "childView" must be specified', 'NoChildViewError');
1497       }
1498   
1499       return childView;
1500     },
1501   
1502     // Render the child's view and add it to the
1503     // HTML for the collection view at a given index.
1504     // This will also update the indices of later views in the collection
1505     // in order to keep the children in sync with the collection.
1506     addChild: function(child, ChildView, index) {
1507       var childViewOptions = this.getOption('childViewOptions');
1508       if (_.isFunction(childViewOptions)) {
1509         childViewOptions = childViewOptions.call(this, child, index);
1510       }
1511   
1512       var view = this.buildChildView(child, ChildView, childViewOptions);
1513   
1514       // increment indices of views after this one
1515       this._updateIndices(view, true, index);
1516   
1517       this._addChildView(view, index);
1518   
1519       return view;
1520     },
1521   
1522     // Internal method. This decrements or increments the indices of views after the
1523     // added/removed view to keep in sync with the collection.
1524     _updateIndices: function(view, increment, index) {
1525       if (!this.sort) {
1526         return;
1527       }
1528   
1529       if (increment) {
1530         // assign the index to the view
1531         view._index = index;
1532   
1533         // increment the index of views after this one
1534         this.children.each(function (laterView) {
1535           if (laterView._index >= view._index) {
1536             laterView._index++;
1537           }
1538         });
1539       }
1540       else {
1541         // decrement the index of views after this one
1542         this.children.each(function (laterView) {
1543           if (laterView._index >= view._index) {
1544             laterView._index--;
1545           }
1546         });
1547       }
1548     },
1549   
1550   
1551     // Internal Method. Add the view to children and render it at
1552     // the given index.
1553     _addChildView: function(view, index) {
1554       // set up the child view event forwarding
1555       this.proxyChildEvents(view);
1556   
1557       this.triggerMethod('before:add:child', view);
1558   
1559       // Store the child view itself so we can properly
1560       // remove and/or destroy it later
1561       this.children.add(view);
1562       this.renderChildView(view, index);
1563   
1564       if (this._isShown && !this.isBuffering){
1565         if (_.isFunction(view.triggerMethod)) {
1566           view.triggerMethod('show');
1567         } else {
1568           Marionette.triggerMethod.call(view, 'show');
1569         }
1570       }
1571   
1572       this.triggerMethod('add:child', view);
1573     },
1574   
1575     // render the child view
1576     renderChildView: function(view, index) {
1577       view.render();
1578       this.attachHtml(this, view, index);
1579     },
1580   
1581     // Build a `childView` for a model in the collection.
1582     buildChildView: function(child, ChildViewClass, childViewOptions) {
1583       var options = _.extend({model: child}, childViewOptions);
1584       return new ChildViewClass(options);
1585     },
1586   
1587     // Remove the child view and destroy it.
1588     // This function also updates the indices of
1589     // later views in the collection in order to keep
1590     // the children in sync with the collection.
1591     removeChildView: function(view) {
1592   
1593       if (view) {
1594         this.triggerMethod('before:remove:child', view);
1595         // call 'destroy' or 'remove', depending on which is found
1596         if (view.destroy) { view.destroy(); }
1597         else if (view.remove) { view.remove(); }
1598   
1599         this.stopListening(view);
1600         this.children.remove(view);
1601         this.triggerMethod('remove:child', view);
1602   
1603         // decrement the index of views after this one
1604         this._updateIndices(view, false);
1605       }
1606   
1607     },
1608   
1609     // check if the collection is empty
1610     isEmpty: function(collection) {
1611       return !this.collection || this.collection.length === 0;
1612     },
1613   
1614     // If empty, show the empty view
1615     checkEmpty: function() {
1616       if (this.isEmpty(this.collection)) {
1617         this.showEmptyView();
1618       }
1619     },
1620   
1621     // You might need to override this if you've overridden attachHtml
1622     attachBuffer: function(collectionView, buffer) {
1623       collectionView.$el.append(buffer);
1624     },
1625   
1626     // Append the HTML to the collection's `el`.
1627     // Override this method to do something other
1628     // than `.append`.
1629     attachHtml: function(collectionView, childView, index) {
1630       if (collectionView.isBuffering) {
1631         // buffering happens on reset events and initial renders
1632         // in order to reduce the number of inserts into the
1633         // document, which are expensive.
1634         collectionView.elBuffer.appendChild(childView.el);
1635         collectionView._bufferedChildren.push(childView);
1636       }
1637       else {
1638         // If we've already rendered the main collection, append
1639         // the new child into the correct order if we need to. Otherwise
1640         // append to the end.
1641         if (!collectionView._insertBefore(childView, index)){
1642           collectionView._insertAfter(childView);
1643         }
1644       }
1645     },
1646   
1647     // Internal method. Check whether we need to insert the view into
1648     // the correct position.
1649     _insertBefore: function(childView, index) {
1650       var currentView;
1651       var findPosition = this.sort && (index < this.children.length - 1);
1652       if (findPosition) {
1653         // Find the view after this one
1654         currentView = this.children.find(function (view) {
1655           return view._index === index + 1;
1656         });
1657       }
1658   
1659       if (currentView) {
1660         currentView.$el.before(childView.el);
1661         return true;
1662       }
1663   
1664       return false;
1665     },
1666   
1667     // Internal method. Append a view to the end of the $el
1668     _insertAfter: function(childView) {
1669       this.$el.append(childView.el);
1670     },
1671   
1672     // Internal method to set up the `children` object for
1673     // storing all of the child views
1674     _initChildViewStorage: function() {
1675       this.children = new Backbone.ChildViewContainer();
1676     },
1677   
1678     // Handle cleanup and other destroying needs for the collection of views
1679     destroy: function() {
1680       if (this.isDestroyed) { return; }
1681   
1682       this.triggerMethod('before:destroy:collection');
1683       this.destroyChildren();
1684       this.triggerMethod('destroy:collection');
1685   
1686       Marionette.View.prototype.destroy.apply(this, arguments);
1687     },
1688   
1689     // Destroy the child views that this collection view
1690     // is holding on to, if any
1691     destroyChildren: function() {
1692       this.children.each(this.removeChildView, this);
1693       this.checkEmpty();
1694     },
1695   
1696     // Set up the child view event forwarding. Uses a "childview:"
1697     // prefix in front of all forwarded events.
1698     proxyChildEvents: function(view) {
1699       var prefix = this.getOption('childViewEventPrefix');
1700   
1701       // Forward all child view events through the parent,
1702       // prepending "childview:" to the event name
1703       this.listenTo(view, 'all', function() {
1704         var args = Array.prototype.slice.call(arguments);
1705         var rootEvent = args[0];
1706         var childEvents = this.normalizeMethods(_.result(this, 'childEvents'));
1707   
1708         args[0] = prefix + ':' + rootEvent;
1709         args.splice(1, 0, view);
1710   
1711         // call collectionView childEvent if defined
1712         if (typeof childEvents !== 'undefined' && _.isFunction(childEvents[rootEvent])) {
1713           childEvents[rootEvent].apply(this, args.slice(1));
1714         }
1715   
1716         this.triggerMethod.apply(this, args);
1717       }, this);
1718     }
1719   });
1720   
1721   /* jshint maxstatements: 17, maxlen: 117 */
1722   
1723   // Composite View
1724   // --------------
1725   
1726   // Used for rendering a branch-leaf, hierarchical structure.
1727   // Extends directly from CollectionView and also renders an
1728   // a child view as `modelView`, for the top leaf
1729   Marionette.CompositeView = Marionette.CollectionView.extend({
1730   
1731     // Setting up the inheritance chain which allows changes to
1732     // Marionette.CollectionView.prototype.constructor which allows overriding
1733     // option to pass '{sort: false}' to prevent the CompositeView from
1734     // maintaining the sorted order of the collection.
1735     // This will fallback onto appending childView's to the end.
1736     constructor: function() {
1737       Marionette.CollectionView.apply(this, arguments);
1738     },
1739   
1740     // Configured the initial events that the composite view
1741     // binds to. Override this method to prevent the initial
1742     // events, or to add your own initial events.
1743     _initialEvents: function() {
1744   
1745       // Bind only after composite view is rendered to avoid adding child views
1746       // to nonexistent childViewContainer
1747       this.once('render', function() {
1748         if (this.collection) {
1749           this.listenTo(this.collection, 'add', this._onCollectionAdd);
1750           this.listenTo(this.collection, 'remove', this._onCollectionRemove);
1751           this.listenTo(this.collection, 'reset', this._renderChildren);
1752   
1753           if (this.sort) {
1754             this.listenTo(this.collection, 'sort', this._sortViews);
1755           }
1756         }
1757       });
1758   
1759     },
1760   
1761     // Retrieve the `childView` to be used when rendering each of
1762     // the items in the collection. The default is to return
1763     // `this.childView` or Marionette.CompositeView if no `childView`
1764     // has been defined
1765     getChildView: function(child) {
1766       var childView = this.getOption('childView') || this.constructor;
1767   
1768       if (!childView) {
1769         throwError('A "childView" must be specified', 'NoChildViewError');
1770       }
1771   
1772       return childView;
1773     },
1774   
1775     // Serialize the collection for the view.
1776     // You can override the `serializeData` method in your own view
1777     // definition, to provide custom serialization for your view's data.
1778     serializeData: function() {
1779       var data = {};
1780   
1781       if (this.model) {
1782         data = this.model.toJSON();
1783       }
1784   
1785       return data;
1786     },
1787   
1788     // Renders the model once, and the collection once. Calling
1789     // this again will tell the model's view to re-render itself
1790     // but the collection will not re-render.
1791     render: function() {
1792       this._ensureViewIsIntact();
1793       this.isRendered = true;
1794       this.resetChildViewContainer();
1795   
1796       this.triggerMethod('before:render', this);
1797   
1798       this._renderRoot();
1799       this._renderChildren();
1800   
1801       this.triggerMethod('render', this);
1802       return this;
1803     },
1804   
1805     _renderChildren: function() {
1806       if (this.isRendered) {
1807         Marionette.CollectionView.prototype._renderChildren.call(this);
1808       }
1809     },
1810   
1811     // Render the root template that the children
1812     // views are appended to
1813     _renderRoot: function() {
1814       var data = {};
1815       data = this.serializeData();
1816       data = this.mixinTemplateHelpers(data);
1817   
1818       this.triggerMethod('before:render:template');
1819   
1820       var template = this.getTemplate();
1821       var html = Marionette.Renderer.render(template, data);
1822       this.attachElContent(html);
1823   
1824       // the ui bindings is done here and not at the end of render since they
1825       // will not be available until after the model is rendered, but should be
1826       // available before the collection is rendered.
1827       this.bindUIElements();
1828       this.triggerMethod('render:template');
1829     },
1830   
1831     // Attaches the content of the root.
1832     // This method can be overriden to optimize rendering,
1833     // or to render in a non standard way.
1834     //
1835     // For example, using `innerHTML` instead of `$el.html`
1836     //
1837     // ```js
1838     // attachElContent: function(html) {
1839     //   this.el.innerHTML = html;
1840     //   return this;
1841     // }
1842     // ```
1843     attachElContent: function(html) {
1844       this.$el.html(html);
1845   
1846       return this;
1847     },
1848   
1849     // You might need to override this if you've overridden attachHtml
1850     attachBuffer: function(compositeView, buffer) {
1851       var $container = this.getChildViewContainer(compositeView);
1852       $container.append(buffer);
1853     },
1854   
1855     // Internal method. Append a view to the end of the $el.
1856     // Overidden from CollectionView to ensure view is appended to
1857     // childViewContainer
1858     _insertAfter: function (childView) {
1859       var $container = this.getChildViewContainer(this);
1860       $container.append(childView.el);
1861     },
1862   
1863     // Internal method to ensure an `$childViewContainer` exists, for the
1864     // `attachHtml` method to use.
1865     getChildViewContainer: function(containerView) {
1866       if ('$childViewContainer' in containerView) {
1867         return containerView.$childViewContainer;
1868       }
1869   
1870       var container;
1871       var childViewContainer = Marionette.getOption(containerView, 'childViewContainer');
1872       if (childViewContainer) {
1873   
1874         var selector = _.isFunction(childViewContainer) ? childViewContainer.call(containerView) : childViewContainer;
1875   
1876         if (selector.charAt(0) === '@' && containerView.ui) {
1877           container = containerView.ui[selector.substr(4)];
1878         } else {
1879           container = containerView.$(selector);
1880         }
1881   
1882         if (container.length <= 0) {
1883           throwError('The specified "childViewContainer" was not found: ' +
1884             containerView.childViewContainer, 'ChildViewContainerMissingError');
1885         }
1886   
1887       } else {
1888         container = containerView.$el;
1889       }
1890   
1891       containerView.$childViewContainer = container;
1892       return container;
1893     },
1894   
1895     // Internal method to reset the `$childViewContainer` on render
1896     resetChildViewContainer: function() {
1897       if (this.$childViewContainer) {
1898         delete this.$childViewContainer;
1899       }
1900     }
1901   });
1902   
1903   // LayoutView
1904   // ----------
1905   
1906   // Used for managing application layoutViews, nested layoutViews and
1907   // multiple regions within an application or sub-application.
1908   //
1909   // A specialized view class that renders an area of HTML and then
1910   // attaches `Region` instances to the specified `regions`.
1911   // Used for composite view management and sub-application areas.
1912   Marionette.LayoutView = Marionette.ItemView.extend({
1913     regionClass: Marionette.Region,
1914   
1915     // Ensure the regions are available when the `initialize` method
1916     // is called.
1917     constructor: function(options) {
1918       options = options || {};
1919   
1920       this._firstRender = true;
1921       this._initializeRegions(options);
1922   
1923       Marionette.ItemView.call(this, options);
1924     },
1925   
1926     // LayoutView's render will use the existing region objects the
1927     // first time it is called. Subsequent calls will destroy the
1928     // views that the regions are showing and then reset the `el`
1929     // for the regions to the newly rendered DOM elements.
1930     render: function() {
1931       this._ensureViewIsIntact();
1932   
1933       if (this._firstRender) {
1934         // if this is the first render, don't do anything to
1935         // reset the regions
1936         this._firstRender = false;
1937       } else {
1938         // If this is not the first render call, then we need to
1939         // re-initialize the `el` for each region
1940         this._reInitializeRegions();
1941       }
1942   
1943       return Marionette.ItemView.prototype.render.apply(this, arguments);
1944     },
1945   
1946     // Handle destroying regions, and then destroy the view itself.
1947     destroy: function() {
1948       if (this.isDestroyed) { return; }
1949   
1950       this.regionManager.destroy();
1951       Marionette.ItemView.prototype.destroy.apply(this, arguments);
1952     },
1953   
1954     // Add a single region, by name, to the layoutView
1955     addRegion: function(name, definition) {
1956       this.triggerMethod('before:region:add', name);
1957       var regions = {};
1958       regions[name] = definition;
1959       return this._buildRegions(regions)[name];
1960     },
1961   
1962     // Add multiple regions as a {name: definition, name2: def2} object literal
1963     addRegions: function(regions) {
1964       this.regions = _.extend({}, this.regions, regions);
1965       return this._buildRegions(regions);
1966     },
1967   
1968     // Remove a single region from the LayoutView, by name
1969     removeRegion: function(name) {
1970       this.triggerMethod('before:region:remove', name);
1971       delete this.regions[name];
1972       return this.regionManager.removeRegion(name);
1973     },
1974   
1975     // Provides alternative access to regions
1976     // Accepts the region name
1977     // getRegion('main')
1978     getRegion: function(region) {
1979       return this.regionManager.get(region);
1980     },
1981   
1982     // Get all regions
1983     getRegions: function(){
1984       return this.regionManager.getRegions();
1985     },
1986   
1987     // internal method to build regions
1988     _buildRegions: function(regions) {
1989       var that = this;
1990   
1991       var defaults = {
1992         regionClass: this.getOption('regionClass'),
1993         parentEl: function() { return that.$el; }
1994       };
1995   
1996       return this.regionManager.addRegions(regions, defaults);
1997     },
1998   
1999     // Internal method to initialize the regions that have been defined in a
2000     // `regions` attribute on this layoutView.
2001     _initializeRegions: function(options) {
2002       var regions;
2003       this._initRegionManager();
2004   
2005       if (_.isFunction(this.regions)) {
2006         regions = this.regions(options);
2007       } else {
2008         regions = this.regions || {};
2009       }
2010   
2011       // Enable users to define `regions` as instance options.
2012       var regionOptions = this.getOption.call(options, 'regions');
2013   
2014       // enable region options to be a function
2015       if (_.isFunction(regionOptions)) {
2016         regionOptions = regionOptions.call(this, options);
2017       }
2018   
2019       _.extend(regions, regionOptions);
2020   
2021       this.addRegions(regions);
2022     },
2023   
2024     // Internal method to re-initialize all of the regions by updating the `el` that
2025     // they point to
2026     _reInitializeRegions: function() {
2027       this.regionManager.emptyRegions();
2028       this.regionManager.each(function(region) {
2029         region.reset();
2030       });
2031     },
2032   
2033     // Enable easy overiding of the default `RegionManager`
2034     // for customized region interactions and buisness specific
2035     // view logic for better control over single regions.
2036     getRegionManager: function() {
2037       return new Marionette.RegionManager();
2038     },
2039   
2040     // Internal method to initialize the region manager
2041     // and all regions in it
2042     _initRegionManager: function() {
2043       this.regionManager = this.getRegionManager();
2044   
2045       this.listenTo(this.regionManager, 'before:add:region', function(name) {
2046         this.triggerMethod('before:add:region', name);
2047       });
2048   
2049       this.listenTo(this.regionManager, 'add:region', function(name, region) {
2050         this[name] = region;
2051         this.triggerMethod('add:region', name, region);
2052       });
2053   
2054       this.listenTo(this.regionManager, 'before:remove:region', function(name) {
2055         this.triggerMethod('before:remove:region', name);
2056       });
2057   
2058       this.listenTo(this.regionManager, 'remove:region', function(name, region) {
2059         delete this[name];
2060         this.triggerMethod('remove:region', name, region);
2061       });
2062     }
2063   });
2064   
2065
2066   // Behavior
2067   // -----------
2068   
2069   // A Behavior is an isolated set of DOM /
2070   // user interactions that can be mixed into any View.
2071   // Behaviors allow you to blackbox View specific interactions
2072   // into portable logical chunks, keeping your views simple and your code DRY.
2073   
2074   Marionette.Behavior = (function(_, Backbone) {
2075     function Behavior(options, view) {
2076       // Setup reference to the view.
2077       // this comes in handle when a behavior
2078       // wants to directly talk up the chain
2079       // to the view.
2080       this.view = view;
2081       this.defaults = _.result(this, 'defaults') || {};
2082       this.options  = _.extend({}, this.defaults, options);
2083   
2084       // proxy behavior $ method to the view
2085       // this is useful for doing jquery DOM lookups
2086       // scoped to behaviors view.
2087       this.$ = function() {
2088         return this.view.$.apply(this.view, arguments);
2089       };
2090   
2091       // Call the initialize method passing
2092       // the arguments from the instance constructor
2093       this.initialize.apply(this, arguments);
2094     }
2095   
2096     _.extend(Behavior.prototype, Backbone.Events, {
2097       initialize: function() {},
2098   
2099       // stopListening to behavior `onListen` events.
2100       destroy: function() {
2101         this.stopListening();
2102       },
2103   
2104       // import the `triggerMethod` to trigger events with corresponding
2105       // methods if the method exists
2106       triggerMethod: Marionette.triggerMethod,
2107   
2108       // Proxy `getOption` to enable getting options from this or this.options by name.
2109       getOption: Marionette.proxyGetOption,
2110   
2111       // Proxy `unbindEntityEvents` to enable binding view's events from another entity.
2112       bindEntityEvents: Marionette.proxyBindEntityEvents,
2113   
2114       // Proxy `unbindEntityEvents` to enable unbinding view's events from another entity.
2115       unbindEntityEvents: Marionette.proxyUnbindEntityEvents
2116     });
2117   
2118     // Borrow Backbones extend implementation
2119     // this allows us to setup a proper
2120     // inheritence pattern that follow in suite
2121     // with the rest of Marionette views.
2122     Behavior.extend = Marionette.extend;
2123   
2124     return Behavior;
2125   })(_, Backbone);
2126   
2127   /* jshint maxlen: 143, nonew: false */
2128   // Marionette.Behaviors
2129   // --------
2130   
2131   // Behaviors is a utility class that takes care of
2132   // glueing your behavior instances to their given View.
2133   // The most important part of this class is that you
2134   // **MUST** override the class level behaviorsLookup
2135   // method for things to work properly.
2136   
2137   Marionette.Behaviors = (function(Marionette, _) {
2138   
2139     function Behaviors(view, behaviors) {
2140       // Behaviors defined on a view can be a flat object literal
2141       // or it can be a function that returns an object.
2142       behaviors = Behaviors.parseBehaviors(view, behaviors || _.result(view, 'behaviors'));
2143   
2144       // Wraps several of the view's methods
2145       // calling the methods first on each behavior
2146       // and then eventually calling the method on the view.
2147       Behaviors.wrap(view, behaviors, [
2148         'bindUIElements', 'unbindUIElements',
2149         'delegateEvents', 'undelegateEvents',
2150         'behaviorEvents', 'triggerMethod',
2151         'setElement', 'destroy'
2152       ]);
2153     }
2154   
2155     var methods = {
2156       setElement: function(setElement, behaviors) {
2157         setElement.apply(this, _.tail(arguments, 2));
2158   
2159         // proxy behavior $el to the view's $el.
2160         // This is needed because a view's $el proxy
2161         // is not set until after setElement is called.
2162         _.each(behaviors, function(b) {
2163           b.$el = this.$el;
2164         }, this);
2165       },
2166   
2167       destroy: function(destroy, behaviors) {
2168         var args = _.tail(arguments, 2);
2169         destroy.apply(this, args);
2170   
2171         // Call destroy on each behavior after
2172         // destroying the view.
2173         // This unbinds event listeners
2174         // that behaviors have registerd for.
2175         _.invoke(behaviors, 'destroy', args);
2176       },
2177   
2178       bindUIElements: function(bindUIElements, behaviors) {
2179         bindUIElements.apply(this);
2180         _.invoke(behaviors, bindUIElements);
2181       },
2182   
2183       unbindUIElements: function(unbindUIElements, behaviors) {
2184         unbindUIElements.apply(this);
2185         _.invoke(behaviors, unbindUIElements);
2186       },
2187   
2188       triggerMethod: function(triggerMethod, behaviors) {
2189         var args = _.tail(arguments, 2);
2190         triggerMethod.apply(this, args);
2191   
2192         _.each(behaviors, function(b) {
2193           triggerMethod.apply(b, args);
2194         });
2195       },
2196   
2197       delegateEvents: function(delegateEvents, behaviors) {
2198         var args = _.tail(arguments, 2);
2199         delegateEvents.apply(this, args);
2200   
2201         _.each(behaviors, function(b) {
2202           Marionette.bindEntityEvents(b, this.model, Marionette.getOption(b, 'modelEvents'));
2203           Marionette.bindEntityEvents(b, this.collection, Marionette.getOption(b, 'collectionEvents'));
2204         }, this);
2205       },
2206   
2207       undelegateEvents: function(undelegateEvents, behaviors) {
2208         var args = _.tail(arguments, 2);
2209         undelegateEvents.apply(this, args);
2210   
2211         _.each(behaviors, function(b) {
2212           Marionette.unbindEntityEvents(b, this.model, Marionette.getOption(b, 'modelEvents'));
2213           Marionette.unbindEntityEvents(b, this.collection, Marionette.getOption(b, 'collectionEvents'));
2214         }, this);
2215       },
2216   
2217       behaviorEvents: function(behaviorEvents, behaviors) {
2218         var _behaviorsEvents = {};
2219         var viewUI = _.result(this, 'ui');
2220   
2221         _.each(behaviors, function(b, i) {
2222           var _events = {};
2223           var behaviorEvents = _.clone(_.result(b, 'events')) || {};
2224           var behaviorUI = _.result(b, 'ui');
2225   
2226           // Construct an internal UI hash first using
2227           // the views UI hash and then the behaviors UI hash.
2228           // This allows the user to use UI hash elements
2229           // defined in the parent view as well as those
2230           // defined in the given behavior.
2231           var ui = _.extend({}, viewUI, behaviorUI);
2232   
2233           // Normalize behavior events hash to allow
2234           // a user to use the @ui. syntax.
2235           behaviorEvents = Marionette.normalizeUIKeys(behaviorEvents, ui);
2236   
2237           _.each(_.keys(behaviorEvents), function(key) {
2238             // Append white-space at the end of each key to prevent behavior key collisions.
2239             // This is relying on the fact that backbone events considers "click .foo" the same as
2240             // "click .foo ".
2241   
2242             // +2 is used because new Array(1) or 0 is "" and not " "
2243             var whitespace = (new Array(i + 2)).join(' ');
2244             var eventKey   = key + whitespace;
2245             var handler    = _.isFunction(behaviorEvents[key]) ? behaviorEvents[key] : b[behaviorEvents[key]];
2246   
2247             _events[eventKey] = _.bind(handler, b);
2248           });
2249   
2250           _behaviorsEvents = _.extend(_behaviorsEvents, _events);
2251         });
2252   
2253         return _behaviorsEvents;
2254       }
2255     };
2256   
2257     _.extend(Behaviors, {
2258   
2259       // Placeholder method to be extended by the user.
2260       // The method should define the object that stores the behaviors.
2261       // i.e.
2262       //
2263       // ```js
2264       // Marionette.Behaviors.behaviorsLookup: function() {
2265       //   return App.Behaviors
2266       // }
2267       // ```
2268       behaviorsLookup: function() {
2269         throw new Error('You must define where your behaviors are stored.' +
2270           'See https://github.com/marionettejs/backbone.marionette' +
2271           '/blob/master/docs/marionette.behaviors.md#behaviorslookup');
2272       },
2273   
2274       // Takes care of getting the behavior class
2275       // given options and a key.
2276       // If a user passes in options.behaviorClass
2277       // default to using that. Otherwise delegate
2278       // the lookup to the users `behaviorsLookup` implementation.
2279       getBehaviorClass: function(options, key) {
2280         if (options.behaviorClass) {
2281           return options.behaviorClass;
2282         }
2283   
2284         // Get behavior class can be either a flat object or a method
2285         return _.isFunction(Behaviors.behaviorsLookup) ? Behaviors.behaviorsLookup.apply(this, arguments)[key] : Behaviors.behaviorsLookup[key];
2286       },
2287   
2288       // Iterate over the behaviors object, for each behavior
2289       // instantiate it and get its grouped behaviors.
2290       parseBehaviors: function(view, behaviors) {
2291         return _.chain(behaviors).map(function(options, key) {
2292           var BehaviorClass = Behaviors.getBehaviorClass(options, key);
2293   
2294           var behavior = new BehaviorClass(options, view);
2295           var nestedBehaviors = Behaviors.parseBehaviors(view, _.result(behavior, 'behaviors'));
2296   
2297           return [behavior].concat(nestedBehaviors);
2298         }).flatten().value();
2299       },
2300   
2301       // Wrap view internal methods so that they delegate to behaviors. For example,
2302       // `onDestroy` should trigger destroy on all of the behaviors and then destroy itself.
2303       // i.e.
2304       //
2305       // `view.delegateEvents = _.partial(methods.delegateEvents, view.delegateEvents, behaviors);`
2306       wrap: function(view, behaviors, methodNames) {
2307         _.each(methodNames, function(methodName) {
2308           view[methodName] = _.partial(methods[methodName], view[methodName], behaviors);
2309         });
2310       }
2311     });
2312   
2313     return Behaviors;
2314   
2315   })(Marionette, _);
2316   
2317
2318   // AppRouter
2319   // ---------
2320   
2321   // Reduce the boilerplate code of handling route events
2322   // and then calling a single method on another object.
2323   // Have your routers configured to call the method on
2324   // your object, directly.
2325   //
2326   // Configure an AppRouter with `appRoutes`.
2327   //
2328   // App routers can only take one `controller` object.
2329   // It is recommended that you divide your controller
2330   // objects in to smaller pieces of related functionality
2331   // and have multiple routers / controllers, instead of
2332   // just one giant router and controller.
2333   //
2334   // You can also add standard routes to an AppRouter.
2335   
2336   Marionette.AppRouter = Backbone.Router.extend({
2337   
2338     constructor: function(options) {
2339       Backbone.Router.apply(this, arguments);
2340   
2341       this.options = options || {};
2342   
2343       var appRoutes = this.getOption('appRoutes');
2344       var controller = this._getController();
2345       this.processAppRoutes(controller, appRoutes);
2346       this.on('route', this._processOnRoute, this);
2347     },
2348   
2349     // Similar to route method on a Backbone Router but
2350     // method is called on the controller
2351     appRoute: function(route, methodName) {
2352       var controller = this._getController();
2353       this._addAppRoute(controller, route, methodName);
2354     },
2355   
2356     // process the route event and trigger the onRoute
2357     // method call, if it exists
2358     _processOnRoute: function(routeName, routeArgs) {
2359       // find the path that matched
2360       var routePath = _.invert(this.appRoutes)[routeName];
2361   
2362       // make sure an onRoute is there, and call it
2363       if (_.isFunction(this.onRoute)) {
2364         this.onRoute(routeName, routePath, routeArgs);
2365       }
2366     },
2367   
2368     // Internal method to process the `appRoutes` for the
2369     // router, and turn them in to routes that trigger the
2370     // specified method on the specified `controller`.
2371     processAppRoutes: function(controller, appRoutes) {
2372       if (!appRoutes) { return; }
2373   
2374       var routeNames = _.keys(appRoutes).reverse(); // Backbone requires reverted order of routes
2375   
2376       _.each(routeNames, function(route) {
2377         this._addAppRoute(controller, route, appRoutes[route]);
2378       }, this);
2379     },
2380   
2381     _getController: function() {
2382       return this.getOption('controller');
2383     },
2384   
2385     _addAppRoute: function(controller, route, methodName) {
2386       var method = controller[methodName];
2387   
2388       if (!method) {
2389         throwError('Method "' + methodName + '" was not found on the controller');
2390       }
2391   
2392       this.route(route, methodName, _.bind(method, controller));
2393     },
2394   
2395     // Proxy `getOption` to enable getting options from this or this.options by name.
2396     getOption: Marionette.proxyGetOption
2397   });
2398   
2399   // Application
2400   // -----------
2401   
2402   // Contain and manage the composite application as a whole.
2403   // Stores and starts up `Region` objects, includes an
2404   // event aggregator as `app.vent`
2405   Marionette.Application = function(options) {
2406     this._initRegionManager();
2407     this._initCallbacks = new Marionette.Callbacks();
2408     var globalCh = Backbone.Wreqr.radio.channel('global');
2409     this.vent = globalCh.vent;
2410     this.commands = globalCh.commands;
2411     this.reqres = globalCh.reqres;
2412     this.submodules = {};
2413   
2414     _.extend(this, options);
2415   };
2416   
2417   _.extend(Marionette.Application.prototype, Backbone.Events, {
2418     // Command execution, facilitated by Backbone.Wreqr.Commands
2419     execute: function() {
2420       this.commands.execute.apply(this.commands, arguments);
2421     },
2422   
2423     // Request/response, facilitated by Backbone.Wreqr.RequestResponse
2424     request: function() {
2425       return this.reqres.request.apply(this.reqres, arguments);
2426     },
2427   
2428     // Add an initializer that is either run at when the `start`
2429     // method is called, or run immediately if added after `start`
2430     // has already been called.
2431     addInitializer: function(initializer) {
2432       this._initCallbacks.add(initializer);
2433     },
2434   
2435     // kick off all of the application's processes.
2436     // initializes all of the regions that have been added
2437     // to the app, and runs all of the initializer functions
2438     start: function(options) {
2439       this.triggerMethod('before:start', options);
2440       this._initCallbacks.run(options, this);
2441       this.triggerMethod('start', options);
2442     },
2443   
2444     // Add regions to your app.
2445     // Accepts a hash of named strings or Region objects
2446     // addRegions({something: "#someRegion"})
2447     // addRegions({something: Region.extend({el: "#someRegion"}) });
2448     addRegions: function(regions) {
2449       return this._regionManager.addRegions(regions);
2450     },
2451   
2452     // Empty all regions in the app, without removing them
2453     emptyRegions: function() {
2454       this._regionManager.emptyRegions();
2455     },
2456   
2457     // Removes a region from your app, by name
2458     // Accepts the regions name
2459     // removeRegion('myRegion')
2460     removeRegion: function(region) {
2461       this._regionManager.removeRegion(region);
2462     },
2463   
2464     // Provides alternative access to regions
2465     // Accepts the region name
2466     // getRegion('main')
2467     getRegion: function(region) {
2468       return this._regionManager.get(region);
2469     },
2470   
2471     // Get all the regions from the region manager
2472     getRegions: function(){
2473       return this._regionManager.getRegions();
2474     },
2475   
2476     // Create a module, attached to the application
2477     module: function(moduleNames, moduleDefinition) {
2478   
2479       // Overwrite the module class if the user specifies one
2480       var ModuleClass = Marionette.Module.getClass(moduleDefinition);
2481   
2482       // slice the args, and add this application object as the
2483       // first argument of the array
2484       var args = slice.call(arguments);
2485       args.unshift(this);
2486   
2487       // see the Marionette.Module object for more information
2488       return ModuleClass.create.apply(ModuleClass, args);
2489     },
2490   
2491     // Internal method to set up the region manager
2492     _initRegionManager: function() {
2493       this._regionManager = new Marionette.RegionManager();
2494   
2495       this.listenTo(this._regionManager, 'before:add:region', function(name) {
2496         this.triggerMethod('before:add:region', name);
2497       });
2498   
2499       this.listenTo(this._regionManager, 'add:region', function(name, region) {
2500         this[name] = region;
2501         this.triggerMethod('add:region', name, region);
2502       });
2503   
2504       this.listenTo(this._regionManager, 'before:remove:region', function(name) {
2505         this.triggerMethod('before:remove:region', name);
2506       });
2507   
2508       this.listenTo(this._regionManager, 'remove:region', function(name, region) {
2509         delete this[name];
2510         this.triggerMethod('remove:region', name, region);
2511       });
2512     },
2513   
2514     // import the `triggerMethod` to trigger events with corresponding
2515     // methods if the method exists
2516     triggerMethod: Marionette.triggerMethod
2517   });
2518   
2519   // Copy the `extend` function used by Backbone's classes
2520   Marionette.Application.extend = Marionette.extend;
2521   
2522   /* jshint maxparams: 9 */
2523   
2524   // Module
2525   // ------
2526   
2527   // A simple module system, used to create privacy and encapsulation in
2528   // Marionette applications
2529   Marionette.Module = function(moduleName, app, options) {
2530     this.moduleName = moduleName;
2531     this.options = _.extend({}, this.options, options);
2532     // Allow for a user to overide the initialize
2533     // for a given module instance.
2534     this.initialize = options.initialize || this.initialize;
2535   
2536     // Set up an internal store for sub-modules.
2537     this.submodules = {};
2538   
2539     this._setupInitializersAndFinalizers();
2540   
2541     // Set an internal reference to the app
2542     // within a module.
2543     this.app = app;
2544   
2545     // By default modules start with their parents.
2546     this.startWithParent = true;
2547   
2548     if (_.isFunction(this.initialize)) {
2549       this.initialize(moduleName, app, this.options);
2550     }
2551   };
2552   
2553   Marionette.Module.extend = Marionette.extend;
2554   
2555   // Extend the Module prototype with events / listenTo, so that the module
2556   // can be used as an event aggregator or pub/sub.
2557   _.extend(Marionette.Module.prototype, Backbone.Events, {
2558   
2559     // Initialize is an empty function by default. Override it with your own
2560     // initialization logic when extending Marionette.Module.
2561     initialize: function() {},
2562   
2563     // Initializer for a specific module. Initializers are run when the
2564     // module's `start` method is called.
2565     addInitializer: function(callback) {
2566       this._initializerCallbacks.add(callback);
2567     },
2568   
2569     // Finalizers are run when a module is stopped. They are used to teardown
2570     // and finalize any variables, references, events and other code that the
2571     // module had set up.
2572     addFinalizer: function(callback) {
2573       this._finalizerCallbacks.add(callback);
2574     },
2575   
2576     // Start the module, and run all of its initializers
2577     start: function(options) {
2578       // Prevent re-starting a module that is already started
2579       if (this._isInitialized) { return; }
2580   
2581       // start the sub-modules (depth-first hierarchy)
2582       _.each(this.submodules, function(mod) {
2583         // check to see if we should start the sub-module with this parent
2584         if (mod.startWithParent) {
2585           mod.start(options);
2586         }
2587       });
2588   
2589       // run the callbacks to "start" the current module
2590       this.triggerMethod('before:start', options);
2591   
2592       this._initializerCallbacks.run(options, this);
2593       this._isInitialized = true;
2594   
2595       this.triggerMethod('start', options);
2596     },
2597   
2598     // Stop this module by running its finalizers and then stop all of
2599     // the sub-modules for this module
2600     stop: function() {
2601       // if we are not initialized, don't bother finalizing
2602       if (!this._isInitialized) { return; }
2603       this._isInitialized = false;
2604   
2605       this.triggerMethod('before:stop');
2606   
2607       // stop the sub-modules; depth-first, to make sure the
2608       // sub-modules are stopped / finalized before parents
2609       _.each(this.submodules, function(mod) { mod.stop(); });
2610   
2611       // run the finalizers
2612       this._finalizerCallbacks.run(undefined, this);
2613   
2614       // reset the initializers and finalizers
2615       this._initializerCallbacks.reset();
2616       this._finalizerCallbacks.reset();
2617   
2618       this.triggerMethod('stop');
2619     },
2620   
2621     // Configure the module with a definition function and any custom args
2622     // that are to be passed in to the definition function
2623     addDefinition: function(moduleDefinition, customArgs) {
2624       this._runModuleDefinition(moduleDefinition, customArgs);
2625     },
2626   
2627     // Internal method: run the module definition function with the correct
2628     // arguments
2629     _runModuleDefinition: function(definition, customArgs) {
2630       // If there is no definition short circut the method.
2631       if (!definition) { return; }
2632   
2633       // build the correct list of arguments for the module definition
2634       var args = _.flatten([
2635         this,
2636         this.app,
2637         Backbone,
2638         Marionette,
2639         Backbone.$, _,
2640         customArgs
2641       ]);
2642   
2643       definition.apply(this, args);
2644     },
2645   
2646     // Internal method: set up new copies of initializers and finalizers.
2647     // Calling this method will wipe out all existing initializers and
2648     // finalizers.
2649     _setupInitializersAndFinalizers: function() {
2650       this._initializerCallbacks = new Marionette.Callbacks();
2651       this._finalizerCallbacks = new Marionette.Callbacks();
2652     },
2653   
2654     // import the `triggerMethod` to trigger events with corresponding
2655     // methods if the method exists
2656     triggerMethod: Marionette.triggerMethod
2657   });
2658   
2659   // Class methods to create modules
2660   _.extend(Marionette.Module, {
2661   
2662     // Create a module, hanging off the app parameter as the parent object.
2663     create: function(app, moduleNames, moduleDefinition) {
2664       var module = app;
2665   
2666       // get the custom args passed in after the module definition and
2667       // get rid of the module name and definition function
2668       var customArgs = slice.call(arguments);
2669       customArgs.splice(0, 3);
2670   
2671       // Split the module names and get the number of submodules.
2672       // i.e. an example module name of `Doge.Wow.Amaze` would
2673       // then have the potential for 3 module definitions.
2674       moduleNames = moduleNames.split('.');
2675       var length = moduleNames.length;
2676   
2677       // store the module definition for the last module in the chain
2678       var moduleDefinitions = [];
2679       moduleDefinitions[length - 1] = moduleDefinition;
2680   
2681       // Loop through all the parts of the module definition
2682       _.each(moduleNames, function(moduleName, i) {
2683         var parentModule = module;
2684         module = this._getModule(parentModule, moduleName, app, moduleDefinition);
2685         this._addModuleDefinition(parentModule, module, moduleDefinitions[i], customArgs);
2686       }, this);
2687   
2688       // Return the last module in the definition chain
2689       return module;
2690     },
2691   
2692     _getModule: function(parentModule, moduleName, app, def, args) {
2693       var options = _.extend({}, def);
2694       var ModuleClass = this.getClass(def);
2695   
2696       // Get an existing module of this name if we have one
2697       var module = parentModule[moduleName];
2698   
2699       if (!module) {
2700         // Create a new module if we don't have one
2701         module = new ModuleClass(moduleName, app, options);
2702         parentModule[moduleName] = module;
2703         // store the module on the parent
2704         parentModule.submodules[moduleName] = module;
2705       }
2706   
2707       return module;
2708     },
2709   
2710     // ## Module Classes
2711     //
2712     // Module classes can be used as an alternative to the define pattern.
2713     // The extend function of a Module is identical to the extend functions
2714     // on other Backbone and Marionette classes.
2715     // This allows module lifecyle events like `onStart` and `onStop` to be called directly.
2716     getClass: function(moduleDefinition) {
2717       var ModuleClass = Marionette.Module;
2718   
2719       if (!moduleDefinition) {
2720         return ModuleClass;
2721       }
2722   
2723       // If all of the module's functionality is defined inside its class,
2724       // then the class can be passed in directly. `MyApp.module("Foo", FooModule)`.
2725       if (moduleDefinition.prototype instanceof ModuleClass) {
2726         return moduleDefinition;
2727       }
2728   
2729       return moduleDefinition.moduleClass || ModuleClass;
2730     },
2731   
2732     // Add the module definition and add a startWithParent initializer function.
2733     // This is complicated because module definitions are heavily overloaded
2734     // and support an anonymous function, module class, or options object
2735     _addModuleDefinition: function(parentModule, module, def, args) {
2736       var fn = this._getDefine(def);
2737       var startWithParent = this._getStartWithParent(def, module);
2738   
2739       if (fn) {
2740         module.addDefinition(fn, args);
2741       }
2742   
2743       this._addStartWithParent(parentModule, module, startWithParent);
2744     },
2745   
2746     _getStartWithParent: function(def, module) {
2747       var swp;
2748   
2749       if (_.isFunction(def) && (def.prototype instanceof Marionette.Module)) {
2750         swp = module.constructor.prototype.startWithParent;
2751         return _.isUndefined(swp) ? true : swp;
2752       }
2753   
2754       if (_.isObject(def)) {
2755         swp = def.startWithParent;
2756         return _.isUndefined(swp) ? true : swp;
2757       }
2758   
2759       return true;
2760     },
2761   
2762     _getDefine: function(def) {
2763       if (_.isFunction(def) && !(def.prototype instanceof Marionette.Module)) {
2764         return def;
2765       }
2766   
2767       if (_.isObject(def)) {
2768         return def.define;
2769       }
2770   
2771       return null;
2772     },
2773   
2774     _addStartWithParent: function(parentModule, module, startWithParent) {
2775       module.startWithParent = module.startWithParent && startWithParent;
2776   
2777       if (!module.startWithParent || !!module.startWithParentIsConfigured) {
2778         return;
2779       }
2780   
2781       module.startWithParentIsConfigured = true;
2782   
2783       parentModule.addInitializer(function(options) {
2784         if (module.startWithParent) {
2785           module.start(options);
2786         }
2787       });
2788     }
2789   });
2790   
2791
2792   return Marionette;
2793 }));