js_stack 1.1.0 → 1.2.0

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