pageflow 17.0.1 → 17.0.3

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2369 @@
1
+ const $ = require('jquery');
2
+ const Backbone = require('backbone');
3
+ const _ = require('underscore');
4
+
5
+ // MarionetteJS (Marionette)
6
+ // ----------------------------------
7
+ // v1.1.0
8
+ //
9
+ // Copyright (c)2013 Derick Bailey, Muted Solutions, LLC.
10
+ // Distributed under MIT license
11
+ //
12
+ // http://marionettejs.com
13
+
14
+
15
+
16
+ /*!
17
+ * Includes BabySitter
18
+ * https://github.com/marionettejs/backbone.babysitter/
19
+ *
20
+ * Includes Wreqr
21
+ * https://github.com/marionettejs/backbone.wreqr/
22
+ */
23
+
24
+ // Backbone.BabySitter
25
+ // -------------------
26
+ // v0.0.6
27
+ //
28
+ // Copyright (c)2013 Derick Bailey, Muted Solutions, LLC.
29
+ // Distributed under MIT license
30
+ //
31
+ // http://github.com/babysitterjs/backbone.babysitter
32
+
33
+ // ChildViewContainer
34
+ // ---------------------------
35
+ //
36
+ // Provide a container to store, retrieve and
37
+ // shut down child views.
38
+
39
+ var ChildViewContainer = (function(Backbone, _){
40
+
41
+ // Container Constructor
42
+ // ---------------------
43
+
44
+ var Container = function(views){
45
+ this._views = {};
46
+ this._indexByModel = {};
47
+ this._indexByCustom = {};
48
+ this._updateLength();
49
+
50
+ _.each(views, this.add, this);
51
+ };
52
+
53
+ // Container Methods
54
+ // -----------------
55
+
56
+ _.extend(Container.prototype, {
57
+
58
+ // Add a view to this container. Stores the view
59
+ // by `cid` and makes it searchable by the model
60
+ // cid (and model itself). Optionally specify
61
+ // a custom key to store an retrieve the view.
62
+ add: function(view, customIndex){
63
+ var viewCid = view.cid;
64
+
65
+ // store the view
66
+ this._views[viewCid] = view;
67
+
68
+ // index it by model
69
+ if (view.model){
70
+ this._indexByModel[view.model.cid] = viewCid;
71
+ }
72
+
73
+ // index by custom
74
+ if (customIndex){
75
+ this._indexByCustom[customIndex] = viewCid;
76
+ }
77
+
78
+ this._updateLength();
79
+ },
80
+
81
+ // Find a view by the model that was attached to
82
+ // it. Uses the model's `cid` to find it.
83
+ findByModel: function(model){
84
+ return this.findByModelCid(model.cid);
85
+ },
86
+
87
+ // Find a view by the `cid` of the model that was attached to
88
+ // it. Uses the model's `cid` to find the view `cid` and
89
+ // retrieve the view using it.
90
+ findByModelCid: function(modelCid){
91
+ var viewCid = this._indexByModel[modelCid];
92
+ return this.findByCid(viewCid);
93
+ },
94
+
95
+ // Find a view by a custom indexer.
96
+ findByCustom: function(index){
97
+ var viewCid = this._indexByCustom[index];
98
+ return this.findByCid(viewCid);
99
+ },
100
+
101
+ // Find by index. This is not guaranteed to be a
102
+ // stable index.
103
+ findByIndex: function(index){
104
+ return _.values(this._views)[index];
105
+ },
106
+
107
+ // retrieve a view by it's `cid` directly
108
+ findByCid: function(cid){
109
+ return this._views[cid];
110
+ },
111
+
112
+ // Remove a view
113
+ remove: function(view){
114
+ var viewCid = view.cid;
115
+
116
+ // delete model index
117
+ if (view.model){
118
+ delete this._indexByModel[view.model.cid];
119
+ }
120
+
121
+ // delete custom index
122
+ _.any(this._indexByCustom, function(cid, key) {
123
+ if (cid === viewCid) {
124
+ delete this._indexByCustom[key];
125
+ return true;
126
+ }
127
+ }, this);
128
+
129
+ // remove the view from the container
130
+ delete this._views[viewCid];
131
+
132
+ // update the length
133
+ this._updateLength();
134
+ },
135
+
136
+ // Call a method on every view in the container,
137
+ // passing parameters to the call method one at a
138
+ // time, like `function.call`.
139
+ call: function(method){
140
+ this.apply(method, _.tail(arguments));
141
+ },
142
+
143
+ // Apply a method on every view in the container,
144
+ // passing parameters to the call method one at a
145
+ // time, like `function.apply`.
146
+ apply: function(method, args){
147
+ _.each(this._views, function(view){
148
+ if (_.isFunction(view[method])){
149
+ view[method].apply(view, args || []);
150
+ }
151
+ });
152
+ },
153
+
154
+ // Update the `.length` attribute on this container
155
+ _updateLength: function(){
156
+ this.length = _.size(this._views);
157
+ }
158
+ });
159
+
160
+ // Borrowing this code from Backbone.Collection:
161
+ // http://backbonejs.org/docs/backbone.html#section-106
162
+ //
163
+ // Mix in methods from Underscore, for iteration, and other
164
+ // collection related features.
165
+ var methods = ['forEach', 'each', 'map', 'find', 'detect', 'filter',
166
+ 'select', 'reject', 'every', 'all', 'some', 'any', 'include',
167
+ 'contains', 'invoke', 'toArray', 'first', 'initial', 'rest',
168
+ 'last', 'without', 'isEmpty', 'pluck'];
169
+
170
+ _.each(methods, function(method) {
171
+ Container.prototype[method] = function() {
172
+ var views = _.values(this._views);
173
+ var args = [views].concat(_.toArray(arguments));
174
+ return _[method].apply(_, args);
175
+ };
176
+ });
177
+
178
+ // return the public API
179
+ return Container;
180
+ })(Backbone, _);
181
+
182
+ // Backbone.Wreqr (Marionette)
183
+ // ----------------------------------
184
+ // v0.2.0
185
+ //
186
+ // Copyright (c)2013 Derick Bailey, Muted Solutions, LLC.
187
+ // Distributed under MIT license
188
+ //
189
+ // http://github.com/marionettejs/backbone.wreqr
190
+
191
+
192
+ Backbone.Wreqr = (function(Backbone, Marionette, _){
193
+ "use strict";
194
+ var Wreqr = {};
195
+
196
+ // Handlers
197
+ // --------
198
+ // A registry of functions to call, given a name
199
+
200
+ Wreqr.Handlers = (function(Backbone, _){
201
+ "use strict";
202
+
203
+ // Constructor
204
+ // -----------
205
+
206
+ var Handlers = function(options){
207
+ this.options = options;
208
+ this._wreqrHandlers = {};
209
+
210
+ if (_.isFunction(this.initialize)){
211
+ this.initialize(options);
212
+ }
213
+ };
214
+
215
+ Handlers.extend = Backbone.Model.extend;
216
+
217
+ // Instance Members
218
+ // ----------------
219
+
220
+ _.extend(Handlers.prototype, Backbone.Events, {
221
+
222
+ // Add multiple handlers using an object literal configuration
223
+ setHandlers: function(handlers){
224
+ _.each(handlers, function(handler, name){
225
+ var context = null;
226
+
227
+ if (_.isObject(handler) && !_.isFunction(handler)){
228
+ context = handler.context;
229
+ handler = handler.callback;
230
+ }
231
+
232
+ this.setHandler(name, handler, context);
233
+ }, this);
234
+ },
235
+
236
+ // Add a handler for the given name, with an
237
+ // optional context to run the handler within
238
+ setHandler: function(name, handler, context){
239
+ var config = {
240
+ callback: handler,
241
+ context: context
242
+ };
243
+
244
+ this._wreqrHandlers[name] = config;
245
+
246
+ this.trigger("handler:add", name, handler, context);
247
+ },
248
+
249
+ // Determine whether or not a handler is registered
250
+ hasHandler: function(name){
251
+ return !! this._wreqrHandlers[name];
252
+ },
253
+
254
+ // Get the currently registered handler for
255
+ // the specified name. Throws an exception if
256
+ // no handler is found.
257
+ getHandler: function(name){
258
+ var config = this._wreqrHandlers[name];
259
+
260
+ if (!config){
261
+ throw new Error("Handler not found for '" + name + "'");
262
+ }
263
+
264
+ return function(){
265
+ var args = Array.prototype.slice.apply(arguments);
266
+ return config.callback.apply(config.context, args);
267
+ };
268
+ },
269
+
270
+ // Remove a handler for the specified name
271
+ removeHandler: function(name){
272
+ delete this._wreqrHandlers[name];
273
+ },
274
+
275
+ // Remove all handlers from this registry
276
+ removeAllHandlers: function(){
277
+ this._wreqrHandlers = {};
278
+ }
279
+ });
280
+
281
+ return Handlers;
282
+ })(Backbone, _);
283
+
284
+ // Wreqr.CommandStorage
285
+ // --------------------
286
+ //
287
+ // Store and retrieve commands for execution.
288
+ Wreqr.CommandStorage = (function(){
289
+ "use strict";
290
+
291
+ // Constructor function
292
+ var CommandStorage = function(options){
293
+ this.options = options;
294
+ this._commands = {};
295
+
296
+ if (_.isFunction(this.initialize)){
297
+ this.initialize(options);
298
+ }
299
+ };
300
+
301
+ // Instance methods
302
+ _.extend(CommandStorage.prototype, Backbone.Events, {
303
+
304
+ // Get an object literal by command name, that contains
305
+ // the `commandName` and the `instances` of all commands
306
+ // represented as an array of arguments to process
307
+ getCommands: function(commandName){
308
+ var commands = this._commands[commandName];
309
+
310
+ // we don't have it, so add it
311
+ if (!commands){
312
+
313
+ // build the configuration
314
+ commands = {
315
+ command: commandName,
316
+ instances: []
317
+ };
318
+
319
+ // store it
320
+ this._commands[commandName] = commands;
321
+ }
322
+
323
+ return commands;
324
+ },
325
+
326
+ // Add a command by name, to the storage and store the
327
+ // args for the command
328
+ addCommand: function(commandName, args){
329
+ var command = this.getCommands(commandName);
330
+ command.instances.push(args);
331
+ },
332
+
333
+ // Clear all commands for the given `commandName`
334
+ clearCommands: function(commandName){
335
+ var command = this.getCommands(commandName);
336
+ command.instances = [];
337
+ }
338
+ });
339
+
340
+ return CommandStorage;
341
+ })();
342
+
343
+ // Wreqr.Commands
344
+ // --------------
345
+ //
346
+ // A simple command pattern implementation. Register a command
347
+ // handler and execute it.
348
+ Wreqr.Commands = (function(Wreqr){
349
+ "use strict";
350
+
351
+ return Wreqr.Handlers.extend({
352
+ // default storage type
353
+ storageType: Wreqr.CommandStorage,
354
+
355
+ constructor: function(options){
356
+ this.options = options || {};
357
+
358
+ this._initializeStorage(this.options);
359
+ this.on("handler:add", this._executeCommands, this);
360
+
361
+ var args = Array.prototype.slice.call(arguments);
362
+ Wreqr.Handlers.prototype.constructor.apply(this, args);
363
+ },
364
+
365
+ // Execute a named command with the supplied args
366
+ execute: function(name, args){
367
+ name = arguments[0];
368
+ args = Array.prototype.slice.call(arguments, 1);
369
+
370
+ if (this.hasHandler(name)){
371
+ this.getHandler(name).apply(this, args);
372
+ } else {
373
+ this.storage.addCommand(name, args);
374
+ }
375
+
376
+ },
377
+
378
+ // Internal method to handle bulk execution of stored commands
379
+ _executeCommands: function(name, handler, context){
380
+ var command = this.storage.getCommands(name);
381
+
382
+ // loop through and execute all the stored command instances
383
+ _.each(command.instances, function(args){
384
+ handler.apply(context, args);
385
+ });
386
+
387
+ this.storage.clearCommands(name);
388
+ },
389
+
390
+ // Internal method to initialize storage either from the type's
391
+ // `storageType` or the instance `options.storageType`.
392
+ _initializeStorage: function(options){
393
+ var storage;
394
+
395
+ var StorageType = options.storageType || this.storageType;
396
+ if (_.isFunction(StorageType)){
397
+ storage = new StorageType();
398
+ } else {
399
+ storage = StorageType;
400
+ }
401
+
402
+ this.storage = storage;
403
+ }
404
+ });
405
+
406
+ })(Wreqr);
407
+
408
+ // Wreqr.RequestResponse
409
+ // ---------------------
410
+ //
411
+ // A simple request/response implementation. Register a
412
+ // request handler, and return a response from it
413
+ Wreqr.RequestResponse = (function(Wreqr){
414
+ "use strict";
415
+
416
+ return Wreqr.Handlers.extend({
417
+ request: function(){
418
+ var name = arguments[0];
419
+ var args = Array.prototype.slice.call(arguments, 1);
420
+
421
+ return this.getHandler(name).apply(this, args);
422
+ }
423
+ });
424
+
425
+ })(Wreqr);
426
+
427
+ // Event Aggregator
428
+ // ----------------
429
+ // A pub-sub object that can be used to decouple various parts
430
+ // of an application through event-driven architecture.
431
+
432
+ Wreqr.EventAggregator = (function(Backbone, _){
433
+ "use strict";
434
+ var EA = function(){};
435
+
436
+ // Copy the `extend` function used by Backbone's classes
437
+ EA.extend = Backbone.Model.extend;
438
+
439
+ // Copy the basic Backbone.Events on to the event aggregator
440
+ _.extend(EA.prototype, Backbone.Events);
441
+
442
+ return EA;
443
+ })(Backbone, _);
444
+
445
+
446
+ return Wreqr;
447
+ })(Backbone, Marionette, _);
448
+
449
+ var Marionette = (function(global, Backbone, _){
450
+ "use strict";
451
+
452
+ // Define and export the Marionette namespace
453
+ var Marionette = {};
454
+ Marionette = Marionette;
455
+
456
+ // Get the DOM manipulator for later use
457
+ Marionette.$ = Backbone.$;
458
+
459
+ // Helpers
460
+ // -------
461
+
462
+ // For slicing `arguments` in functions
463
+ var protoSlice = Array.prototype.slice;
464
+ function slice(args) {
465
+ return protoSlice.call(args);
466
+ }
467
+
468
+ function throwError(message, name) {
469
+ var error = new Error(message);
470
+ error.name = name || 'Error';
471
+ throw error;
472
+ }
473
+
474
+ // Marionette.extend
475
+ // -----------------
476
+
477
+ // Borrow the Backbone `extend` method so we can use it as needed
478
+ Marionette.extend = Backbone.Model.extend;
479
+
480
+ // Marionette.getOption
481
+ // --------------------
482
+
483
+ // Retrieve an object, function or other value from a target
484
+ // object or its `options`, with `options` taking precedence.
485
+ Marionette.getOption = function(target, optionName){
486
+ if (!target || !optionName){ return; }
487
+ var value;
488
+
489
+ if (target.options && (optionName in target.options) && (target.options[optionName] !== undefined)){
490
+ value = target.options[optionName];
491
+ } else {
492
+ value = target[optionName];
493
+ }
494
+
495
+ return value;
496
+ };
497
+
498
+ // Trigger an event and/or a corresponding method name. Examples:
499
+ //
500
+ // `this.triggerMethod("foo")` will trigger the "foo" event and
501
+ // call the "onFoo" method.
502
+ //
503
+ // `this.triggerMethod("foo:bar") will trigger the "foo:bar" event and
504
+ // call the "onFooBar" method.
505
+ Marionette.triggerMethod = (function(){
506
+
507
+ // split the event name on the :
508
+ var splitter = /(^|:)(\w)/gi;
509
+
510
+ // take the event section ("section1:section2:section3")
511
+ // and turn it in to uppercase name
512
+ function getEventName(match, prefix, eventName) {
513
+ return eventName.toUpperCase();
514
+ }
515
+
516
+ // actual triggerMethod name
517
+ var triggerMethod = function(event) {
518
+ // get the method name from the event name
519
+ var methodName = 'on' + event.replace(splitter, getEventName);
520
+ var method = this[methodName];
521
+
522
+ // trigger the event, if a trigger method exists
523
+ if(_.isFunction(this.trigger)) {
524
+ this.trigger.apply(this, arguments);
525
+ }
526
+
527
+ // call the onMethodName if it exists
528
+ if (_.isFunction(method)) {
529
+ // pass all arguments, except the event name
530
+ return method.apply(this, _.tail(arguments));
531
+ }
532
+ };
533
+
534
+ return triggerMethod;
535
+ })();
536
+
537
+ // DOMRefresh
538
+ // ----------
539
+ //
540
+ // Monitor a view's state, and after it has been rendered and shown
541
+ // in the DOM, trigger a "dom:refresh" event every time it is
542
+ // re-rendered.
543
+
544
+ Marionette.MonitorDOMRefresh = (function(){
545
+ // track when the view has been shown in the DOM,
546
+ // using a Marionette.Region (or by other means of triggering "show")
547
+ function handleShow(view){
548
+ view._isShown = true;
549
+ triggerDOMRefresh(view);
550
+ }
551
+
552
+ // track when the view has been rendered
553
+ function handleRender(view){
554
+ view._isRendered = true;
555
+ triggerDOMRefresh(view);
556
+ }
557
+
558
+ // Trigger the "dom:refresh" event and corresponding "onDomRefresh" method
559
+ function triggerDOMRefresh(view){
560
+ if (view._isShown && view._isRendered){
561
+ if (_.isFunction(view.triggerMethod)){
562
+ view.triggerMethod("dom:refresh");
563
+ }
564
+ }
565
+ }
566
+
567
+ // Export public API
568
+ return function(view){
569
+ view.listenTo(view, "show", function(){
570
+ handleShow(view);
571
+ });
572
+
573
+ view.listenTo(view, "render", function(){
574
+ handleRender(view);
575
+ });
576
+ };
577
+ })();
578
+
579
+
580
+ // Marionette.bindEntityEvents & unbindEntityEvents
581
+ // ---------------------------
582
+ //
583
+ // These methods are used to bind/unbind a backbone "entity" (collection/model)
584
+ // to methods on a target object.
585
+ //
586
+ // The first parameter, `target`, must have a `listenTo` method from the
587
+ // EventBinder object.
588
+ //
589
+ // The second parameter is the entity (Backbone.Model or Backbone.Collection)
590
+ // to bind the events from.
591
+ //
592
+ // The third parameter is a hash of { "event:name": "eventHandler" }
593
+ // configuration. Multiple handlers can be separated by a space. A
594
+ // function can be supplied instead of a string handler name.
595
+
596
+ (function(Marionette){
597
+ "use strict";
598
+
599
+ // Bind the event to handlers specified as a string of
600
+ // handler names on the target object
601
+ function bindFromStrings(target, entity, evt, methods){
602
+ var methodNames = methods.split(/\s+/);
603
+
604
+ _.each(methodNames,function(methodName) {
605
+
606
+ var method = target[methodName];
607
+ if(!method) {
608
+ throwError("Method '"+ methodName +"' was configured as an event handler, but does not exist.");
609
+ }
610
+
611
+ target.listenTo(entity, evt, method, target);
612
+ });
613
+ }
614
+
615
+ // Bind the event to a supplied callback function
616
+ function bindToFunction(target, entity, evt, method){
617
+ target.listenTo(entity, evt, method, target);
618
+ }
619
+
620
+ // Bind the event to handlers specified as a string of
621
+ // handler names on the target object
622
+ function unbindFromStrings(target, entity, evt, methods){
623
+ var methodNames = methods.split(/\s+/);
624
+
625
+ _.each(methodNames,function(methodName) {
626
+ var method = target[methodName];
627
+ target.stopListening(entity, evt, method, target);
628
+ });
629
+ }
630
+
631
+ // Bind the event to a supplied callback function
632
+ function unbindToFunction(target, entity, evt, method){
633
+ target.stopListening(entity, evt, method, target);
634
+ }
635
+
636
+
637
+ // generic looping function
638
+ function iterateEvents(target, entity, bindings, functionCallback, stringCallback){
639
+ if (!entity || !bindings) { return; }
640
+
641
+ // allow the bindings to be a function
642
+ if (_.isFunction(bindings)){
643
+ bindings = bindings.call(target);
644
+ }
645
+
646
+ // iterate the bindings and bind them
647
+ _.each(bindings, function(methods, evt){
648
+
649
+ // allow for a function as the handler,
650
+ // or a list of event names as a string
651
+ if (_.isFunction(methods)){
652
+ functionCallback(target, entity, evt, methods);
653
+ } else {
654
+ stringCallback(target, entity, evt, methods);
655
+ }
656
+
657
+ });
658
+ }
659
+
660
+ // Export Public API
661
+ Marionette.bindEntityEvents = function(target, entity, bindings){
662
+ iterateEvents(target, entity, bindings, bindToFunction, bindFromStrings);
663
+ };
664
+
665
+ Marionette.unbindEntityEvents = function(target, entity, bindings){
666
+ iterateEvents(target, entity, bindings, unbindToFunction, unbindFromStrings);
667
+ };
668
+
669
+ })(Marionette);
670
+
671
+
672
+ // Callbacks
673
+ // ---------
674
+
675
+ // A simple way of managing a collection of callbacks
676
+ // and executing them at a later point in time, using jQuery's
677
+ // `Deferred` object.
678
+ Marionette.Callbacks = function(){
679
+ this._deferred = Marionette.$.Deferred();
680
+ this._callbacks = [];
681
+ };
682
+
683
+ _.extend(Marionette.Callbacks.prototype, {
684
+
685
+ // Add a callback to be executed. Callbacks added here are
686
+ // guaranteed to execute, even if they are added after the
687
+ // `run` method is called.
688
+ add: function(callback, contextOverride){
689
+ this._callbacks.push({cb: callback, ctx: contextOverride});
690
+
691
+ this._deferred.done(function(context, options){
692
+ if (contextOverride){ context = contextOverride; }
693
+ callback.call(context, options);
694
+ });
695
+ },
696
+
697
+ // Run all registered callbacks with the context specified.
698
+ // Additional callbacks can be added after this has been run
699
+ // and they will still be executed.
700
+ run: function(options, context){
701
+ this._deferred.resolve(context, options);
702
+ },
703
+
704
+ // Resets the list of callbacks to be run, allowing the same list
705
+ // to be run multiple times - whenever the `run` method is called.
706
+ reset: function(){
707
+ var callbacks = this._callbacks;
708
+ this._deferred = Marionette.$.Deferred();
709
+ this._callbacks = [];
710
+
711
+ _.each(callbacks, function(cb){
712
+ this.add(cb.cb, cb.ctx);
713
+ }, this);
714
+ }
715
+ });
716
+
717
+
718
+ // Marionette Controller
719
+ // ---------------------
720
+ //
721
+ // A multi-purpose object to use as a controller for
722
+ // modules and routers, and as a mediator for workflow
723
+ // and coordination of other objects, views, and more.
724
+ Marionette.Controller = function(options){
725
+ this.triggerMethod = Marionette.triggerMethod;
726
+ this.options = options || {};
727
+
728
+ if (_.isFunction(this.initialize)){
729
+ this.initialize(this.options);
730
+ }
731
+ };
732
+
733
+ Marionette.Controller.extend = Marionette.extend;
734
+
735
+ // Controller Methods
736
+ // --------------
737
+
738
+ // Ensure it can trigger events with Backbone.Events
739
+ _.extend(Marionette.Controller.prototype, Backbone.Events, {
740
+ close: function(){
741
+ this.stopListening();
742
+ this.triggerMethod("close");
743
+ this.unbind();
744
+ }
745
+ });
746
+
747
+ // Region
748
+ // ------
749
+ //
750
+ // Manage the visual regions of your composite application. See
751
+ // http://lostechies.com/derickbailey/2011/12/12/composite-js-apps-regions-and-region-managers/
752
+
753
+ Marionette.Region = function(options){
754
+ this.options = options || {};
755
+
756
+ this.el = Marionette.getOption(this, "el");
757
+
758
+ if (!this.el){
759
+ var err = new Error("An 'el' must be specified for a region.");
760
+ err.name = "NoElError";
761
+ throw err;
762
+ }
763
+
764
+ if (this.initialize){
765
+ var args = Array.prototype.slice.apply(arguments);
766
+ this.initialize.apply(this, args);
767
+ }
768
+ };
769
+
770
+
771
+ // Region Type methods
772
+ // -------------------
773
+
774
+ _.extend(Marionette.Region, {
775
+
776
+ // Build an instance of a region by passing in a configuration object
777
+ // and a default region type to use if none is specified in the config.
778
+ //
779
+ // The config object should either be a string as a jQuery DOM selector,
780
+ // a Region type directly, or an object literal that specifies both
781
+ // a selector and regionType:
782
+ //
783
+ // ```js
784
+ // {
785
+ // selector: "#foo",
786
+ // regionType: MyCustomRegion
787
+ // }
788
+ // ```
789
+ //
790
+ buildRegion: function(regionConfig, defaultRegionType){
791
+
792
+ var regionIsString = (typeof regionConfig === "string");
793
+ var regionSelectorIsString = (typeof regionConfig.selector === "string");
794
+ var regionTypeIsUndefined = (typeof regionConfig.regionType === "undefined");
795
+ var regionIsType = (typeof regionConfig === "function");
796
+
797
+ if (!regionIsType && !regionIsString && !regionSelectorIsString) {
798
+ throw new Error("Region must be specified as a Region type, a selector string or an object with selector property");
799
+ }
800
+
801
+ var selector, RegionType;
802
+
803
+ // get the selector for the region
804
+
805
+ if (regionIsString) {
806
+ selector = regionConfig;
807
+ }
808
+
809
+ if (regionConfig.selector) {
810
+ selector = regionConfig.selector;
811
+ }
812
+
813
+ // get the type for the region
814
+
815
+ if (regionIsType){
816
+ RegionType = regionConfig;
817
+ }
818
+
819
+ if (!regionIsType && regionTypeIsUndefined) {
820
+ RegionType = defaultRegionType;
821
+ }
822
+
823
+ if (regionConfig.regionType) {
824
+ RegionType = regionConfig.regionType;
825
+ }
826
+
827
+ // build the region instance
828
+ var region = new RegionType({
829
+ el: selector
830
+ });
831
+
832
+ // override the `getEl` function if we have a parentEl
833
+ // this must be overridden to ensure the selector is found
834
+ // on the first use of the region. if we try to assign the
835
+ // region's `el` to `parentEl.find(selector)` in the object
836
+ // literal to build the region, the element will not be
837
+ // guaranteed to be in the DOM already, and will cause problems
838
+ if (regionConfig.parentEl){
839
+
840
+ region.getEl = function(selector) {
841
+ var parentEl = regionConfig.parentEl;
842
+ if (_.isFunction(parentEl)){
843
+ parentEl = parentEl();
844
+ }
845
+ return parentEl.find(selector);
846
+ };
847
+ }
848
+
849
+ return region;
850
+ }
851
+
852
+ });
853
+
854
+ // Region Instance Methods
855
+ // -----------------------
856
+
857
+ _.extend(Marionette.Region.prototype, Backbone.Events, {
858
+
859
+ // Displays a backbone view instance inside of the region.
860
+ // Handles calling the `render` method for you. Reads content
861
+ // directly from the `el` attribute. Also calls an optional
862
+ // `onShow` and `close` method on your view, just after showing
863
+ // or just before closing the view, respectively.
864
+ show: function(view){
865
+
866
+ this.ensureEl();
867
+
868
+ var isViewClosed = view.isClosed || _.isUndefined(view.$el);
869
+
870
+ var isDifferentView = view !== this.currentView;
871
+
872
+ if (isDifferentView) {
873
+ this.close();
874
+ }
875
+
876
+ view.render();
877
+
878
+ if (isDifferentView || isViewClosed) {
879
+ this.open(view);
880
+ }
881
+
882
+ this.currentView = view;
883
+
884
+ Marionette.triggerMethod.call(this, "show", view);
885
+ Marionette.triggerMethod.call(view, "show");
886
+ },
887
+
888
+ ensureEl: function(){
889
+ if (!this.$el || this.$el.length === 0){
890
+ this.$el = this.getEl(this.el);
891
+ }
892
+ },
893
+
894
+ // Override this method to change how the region finds the
895
+ // DOM element that it manages. Return a jQuery selector object.
896
+ getEl: function(selector){
897
+ return Marionette.$(selector);
898
+ },
899
+
900
+ // Override this method to change how the new view is
901
+ // appended to the `$el` that the region is managing
902
+ open: function(view){
903
+ this.$el.empty().append(view.el);
904
+ },
905
+
906
+ // Close the current view, if there is one. If there is no
907
+ // current view, it does nothing and returns immediately.
908
+ close: function(){
909
+ var view = this.currentView;
910
+ if (!view || view.isClosed){ return; }
911
+
912
+ // call 'close' or 'remove', depending on which is found
913
+ if (view.close) { view.close(); }
914
+ else if (view.remove) { view.remove(); }
915
+
916
+ Marionette.triggerMethod.call(this, "close");
917
+
918
+ delete this.currentView;
919
+ },
920
+
921
+ // Attach an existing view to the region. This
922
+ // will not call `render` or `onShow` for the new view,
923
+ // and will not replace the current HTML for the `el`
924
+ // of the region.
925
+ attachView: function(view){
926
+ this.currentView = view;
927
+ },
928
+
929
+ // Reset the region by closing any existing view and
930
+ // clearing out the cached `$el`. The next time a view
931
+ // is shown via this region, the region will re-query the
932
+ // DOM for the region's `el`.
933
+ reset: function(){
934
+ this.close();
935
+ delete this.$el;
936
+ }
937
+ });
938
+
939
+ // Copy the `extend` function used by Backbone's classes
940
+ Marionette.Region.extend = Marionette.extend;
941
+
942
+ // Marionette.RegionManager
943
+ // ------------------------
944
+ //
945
+ // Manage one or more related `Marionette.Region` objects.
946
+ Marionette.RegionManager = (function(Marionette){
947
+
948
+ var RegionManager = Marionette.Controller.extend({
949
+ constructor: function(options){
950
+ this._regions = {};
951
+ Marionette.Controller.prototype.constructor.call(this, options);
952
+ },
953
+
954
+ // Add multiple regions using an object literal, where
955
+ // each key becomes the region name, and each value is
956
+ // the region definition.
957
+ addRegions: function(regionDefinitions, defaults){
958
+ var regions = {};
959
+
960
+ _.each(regionDefinitions, function(definition, name){
961
+ if (typeof definition === "string"){
962
+ definition = { selector: definition };
963
+ }
964
+
965
+ if (definition.selector){
966
+ definition = _.defaults({}, definition, defaults);
967
+ }
968
+
969
+ var region = this.addRegion(name, definition);
970
+ regions[name] = region;
971
+ }, this);
972
+
973
+ return regions;
974
+ },
975
+
976
+ // Add an individual region to the region manager,
977
+ // and return the region instance
978
+ addRegion: function(name, definition){
979
+ var region;
980
+
981
+ var isObject = _.isObject(definition);
982
+ var isString = _.isString(definition);
983
+ var hasSelector = !!definition.selector;
984
+
985
+ if (isString || (isObject && hasSelector)){
986
+ region = Marionette.Region.buildRegion(definition, Marionette.Region);
987
+ } else if (_.isFunction(definition)){
988
+ region = Marionette.Region.buildRegion(definition, Marionette.Region);
989
+ } else {
990
+ region = definition;
991
+ }
992
+
993
+ this._store(name, region);
994
+ this.triggerMethod("region:add", name, region);
995
+ return region;
996
+ },
997
+
998
+ // Get a region by name
999
+ get: function(name){
1000
+ return this._regions[name];
1001
+ },
1002
+
1003
+ // Remove a region by name
1004
+ removeRegion: function(name){
1005
+ var region = this._regions[name];
1006
+ this._remove(name, region);
1007
+ },
1008
+
1009
+ // Close all regions in the region manager, and
1010
+ // remove them
1011
+ removeRegions: function(){
1012
+ _.each(this._regions, function(region, name){
1013
+ this._remove(name, region);
1014
+ }, this);
1015
+ },
1016
+
1017
+ // Close all regions in the region manager, but
1018
+ // leave them attached
1019
+ closeRegions: function(){
1020
+ _.each(this._regions, function(region, name){
1021
+ region.close();
1022
+ }, this);
1023
+ },
1024
+
1025
+ // Close all regions and shut down the region
1026
+ // manager entirely
1027
+ close: function(){
1028
+ this.removeRegions();
1029
+ var args = Array.prototype.slice.call(arguments);
1030
+ Marionette.Controller.prototype.close.apply(this, args);
1031
+ },
1032
+
1033
+ // internal method to store regions
1034
+ _store: function(name, region){
1035
+ this._regions[name] = region;
1036
+ this._setLength();
1037
+ },
1038
+
1039
+ // internal method to remove a region
1040
+ _remove: function(name, region){
1041
+ region.close();
1042
+ delete this._regions[name];
1043
+ this._setLength();
1044
+ this.triggerMethod("region:remove", name, region);
1045
+ },
1046
+
1047
+ // set the number of regions current held
1048
+ _setLength: function(){
1049
+ this.length = _.size(this._regions);
1050
+ }
1051
+
1052
+ });
1053
+
1054
+ // Borrowing this code from Backbone.Collection:
1055
+ // http://backbonejs.org/docs/backbone.html#section-106
1056
+ //
1057
+ // Mix in methods from Underscore, for iteration, and other
1058
+ // collection related features.
1059
+ var methods = ['forEach', 'each', 'map', 'find', 'detect', 'filter',
1060
+ 'select', 'reject', 'every', 'all', 'some', 'any', 'include',
1061
+ 'contains', 'invoke', 'toArray', 'first', 'initial', 'rest',
1062
+ 'last', 'without', 'isEmpty', 'pluck'];
1063
+
1064
+ _.each(methods, function(method) {
1065
+ RegionManager.prototype[method] = function() {
1066
+ var regions = _.values(this._regions);
1067
+ var args = [regions].concat(_.toArray(arguments));
1068
+ return _[method].apply(_, args);
1069
+ };
1070
+ });
1071
+
1072
+ return RegionManager;
1073
+ })(Marionette);
1074
+
1075
+
1076
+ // Template Cache
1077
+ // --------------
1078
+
1079
+ // Manage templates stored in `<script>` blocks,
1080
+ // caching them for faster access.
1081
+ Marionette.TemplateCache = function(templateId){
1082
+ this.templateId = templateId;
1083
+ };
1084
+
1085
+ // TemplateCache object-level methods. Manage the template
1086
+ // caches from these method calls instead of creating
1087
+ // your own TemplateCache instances
1088
+ _.extend(Marionette.TemplateCache, {
1089
+ templateCaches: {},
1090
+
1091
+ // Get the specified template by id. Either
1092
+ // retrieves the cached version, or loads it
1093
+ // from the DOM.
1094
+ get: function(templateId){
1095
+ var cachedTemplate = this.templateCaches[templateId];
1096
+
1097
+ if (!cachedTemplate){
1098
+ cachedTemplate = new Marionette.TemplateCache(templateId);
1099
+ this.templateCaches[templateId] = cachedTemplate;
1100
+ }
1101
+
1102
+ return cachedTemplate.load();
1103
+ },
1104
+
1105
+ // Clear templates from the cache. If no arguments
1106
+ // are specified, clears all templates:
1107
+ // `clear()`
1108
+ //
1109
+ // If arguments are specified, clears each of the
1110
+ // specified templates from the cache:
1111
+ // `clear("#t1", "#t2", "...")`
1112
+ clear: function(){
1113
+ var i;
1114
+ var args = slice(arguments);
1115
+ var length = args.length;
1116
+
1117
+ if (length > 0){
1118
+ for(i=0; i<length; i++){
1119
+ delete this.templateCaches[args[i]];
1120
+ }
1121
+ } else {
1122
+ this.templateCaches = {};
1123
+ }
1124
+ }
1125
+ });
1126
+
1127
+ // TemplateCache instance methods, allowing each
1128
+ // template cache object to manage its own state
1129
+ // and know whether or not it has been loaded
1130
+ _.extend(Marionette.TemplateCache.prototype, {
1131
+
1132
+ // Internal method to load the template
1133
+ load: function(){
1134
+ // Guard clause to prevent loading this template more than once
1135
+ if (this.compiledTemplate){
1136
+ return this.compiledTemplate;
1137
+ }
1138
+
1139
+ // Load the template and compile it
1140
+ var template = this.loadTemplate(this.templateId);
1141
+ this.compiledTemplate = this.compileTemplate(template);
1142
+
1143
+ return this.compiledTemplate;
1144
+ },
1145
+
1146
+ // Load a template from the DOM, by default. Override
1147
+ // this method to provide your own template retrieval
1148
+ // For asynchronous loading with AMD/RequireJS, consider
1149
+ // using a template-loader plugin as described here:
1150
+ // https://github.com/marionettejs/backbone.marionette/wiki/Using-marionette-with-requirejs
1151
+ loadTemplate: function(templateId){
1152
+ var template = Marionette.$(templateId).html();
1153
+
1154
+ if (!template || template.length === 0){
1155
+ throwError("Could not find template: '" + templateId + "'", "NoTemplateError");
1156
+ }
1157
+
1158
+ return template;
1159
+ },
1160
+
1161
+ // Pre-compile the template before caching it. Override
1162
+ // this method if you do not need to pre-compile a template
1163
+ // (JST / RequireJS for example) or if you want to change
1164
+ // the template engine used (Handebars, etc).
1165
+ compileTemplate: function(rawTemplate){
1166
+ return _.template(rawTemplate);
1167
+ }
1168
+ });
1169
+
1170
+
1171
+ // Renderer
1172
+ // --------
1173
+
1174
+ // Render a template with data by passing in the template
1175
+ // selector and the data to render.
1176
+ Marionette.Renderer = {
1177
+
1178
+ // Render a template with data. The `template` parameter is
1179
+ // passed to the `TemplateCache` object to retrieve the
1180
+ // template function. Override this method to provide your own
1181
+ // custom rendering and template handling for all of Marionette.
1182
+ render: function(template, data){
1183
+
1184
+ if (!template) {
1185
+ var error = new Error("Cannot render the template since it's false, null or undefined.");
1186
+ error.name = "TemplateNotFoundError";
1187
+ throw error;
1188
+ }
1189
+
1190
+ var templateFunc;
1191
+ if (typeof template === "function"){
1192
+ templateFunc = template;
1193
+ } else {
1194
+ templateFunc = Marionette.TemplateCache.get(template);
1195
+ }
1196
+
1197
+ return templateFunc(data);
1198
+ }
1199
+ };
1200
+
1201
+
1202
+
1203
+ // Marionette.View
1204
+ // ---------------
1205
+
1206
+ // The core view type that other Marionette views extend from.
1207
+ Marionette.View = Backbone.View.extend({
1208
+
1209
+ constructor: function(){
1210
+ _.bindAll(this, "render");
1211
+
1212
+ var args = Array.prototype.slice.apply(arguments);
1213
+ Backbone.View.prototype.constructor.apply(this, args);
1214
+
1215
+ Marionette.MonitorDOMRefresh(this);
1216
+ this.listenTo(this, "show", this.onShowCalled, this);
1217
+ },
1218
+
1219
+ // import the "triggerMethod" to trigger events with corresponding
1220
+ // methods if the method exists
1221
+ triggerMethod: Marionette.triggerMethod,
1222
+
1223
+ // Get the template for this view
1224
+ // instance. You can set a `template` attribute in the view
1225
+ // definition or pass a `template: "whatever"` parameter in
1226
+ // to the constructor options.
1227
+ getTemplate: function(){
1228
+ return Marionette.getOption(this, "template");
1229
+ },
1230
+
1231
+ // Mix in template helper methods. Looks for a
1232
+ // `templateHelpers` attribute, which can either be an
1233
+ // object literal, or a function that returns an object
1234
+ // literal. All methods and attributes from this object
1235
+ // are copies to the object passed in.
1236
+ mixinTemplateHelpers: function(target){
1237
+ target = target || {};
1238
+ var templateHelpers = Marionette.getOption(this, "templateHelpers");
1239
+ if (_.isFunction(templateHelpers)){
1240
+ templateHelpers = templateHelpers.call(this);
1241
+ }
1242
+ return _.extend(target, templateHelpers);
1243
+ },
1244
+
1245
+ // Configure `triggers` to forward DOM events to view
1246
+ // events. `triggers: {"click .foo": "do:foo"}`
1247
+ configureTriggers: function(){
1248
+ if (!this.triggers) { return; }
1249
+
1250
+ var triggerEvents = {};
1251
+
1252
+ // Allow `triggers` to be configured as a function
1253
+ var triggers = _.result(this, "triggers");
1254
+
1255
+ // Configure the triggers, prevent default
1256
+ // action and stop propagation of DOM events
1257
+ _.each(triggers, function(value, key){
1258
+
1259
+ // build the event handler function for the DOM event
1260
+ triggerEvents[key] = function(e){
1261
+
1262
+ // stop the event in its tracks
1263
+ if (e && e.preventDefault){ e.preventDefault(); }
1264
+ if (e && e.stopPropagation){ e.stopPropagation(); }
1265
+
1266
+ // build the args for the event
1267
+ var args = {
1268
+ view: this,
1269
+ model: this.model,
1270
+ collection: this.collection
1271
+ };
1272
+
1273
+ // trigger the event
1274
+ this.triggerMethod(value, args);
1275
+ };
1276
+
1277
+ }, this);
1278
+
1279
+ return triggerEvents;
1280
+ },
1281
+
1282
+ // Overriding Backbone.View's delegateEvents to handle
1283
+ // the `triggers`, `modelEvents`, and `collectionEvents` configuration
1284
+ delegateEvents: function(events){
1285
+ this._delegateDOMEvents(events);
1286
+ Marionette.bindEntityEvents(this, this.model, Marionette.getOption(this, "modelEvents"));
1287
+ Marionette.bindEntityEvents(this, this.collection, Marionette.getOption(this, "collectionEvents"));
1288
+ },
1289
+
1290
+ // internal method to delegate DOM events and triggers
1291
+ _delegateDOMEvents: function(events){
1292
+ events = events || this.events;
1293
+ if (_.isFunction(events)){ events = events.call(this); }
1294
+
1295
+ var combinedEvents = {};
1296
+ var triggers = this.configureTriggers();
1297
+ _.extend(combinedEvents, events, triggers);
1298
+
1299
+ Backbone.View.prototype.delegateEvents.call(this, combinedEvents);
1300
+ },
1301
+
1302
+ // Overriding Backbone.View's undelegateEvents to handle unbinding
1303
+ // the `triggers`, `modelEvents`, and `collectionEvents` config
1304
+ undelegateEvents: function(){
1305
+ var args = Array.prototype.slice.call(arguments);
1306
+ Backbone.View.prototype.undelegateEvents.apply(this, args);
1307
+
1308
+ Marionette.unbindEntityEvents(this, this.model, Marionette.getOption(this, "modelEvents"));
1309
+ Marionette.unbindEntityEvents(this, this.collection, Marionette.getOption(this, "collectionEvents"));
1310
+ },
1311
+
1312
+ // Internal method, handles the `show` event.
1313
+ onShowCalled: function(){},
1314
+
1315
+ // Default `close` implementation, for removing a view from the
1316
+ // DOM and unbinding it. Regions will call this method
1317
+ // for you. You can specify an `onClose` method in your view to
1318
+ // add custom code that is called after the view is closed.
1319
+ close: function(){
1320
+ if (this.isClosed) { return; }
1321
+
1322
+ // allow the close to be stopped by returning `false`
1323
+ // from the `onBeforeClose` method
1324
+ var shouldClose = this.triggerMethod("before:close");
1325
+ if (shouldClose === false){
1326
+ return;
1327
+ }
1328
+
1329
+ // mark as closed before doing the actual close, to
1330
+ // prevent infinite loops within "close" event handlers
1331
+ // that are trying to close other views
1332
+ this.isClosed = true;
1333
+ this.triggerMethod("close");
1334
+
1335
+ // unbind UI elements
1336
+ this.unbindUIElements();
1337
+
1338
+ // remove the view from the DOM
1339
+ this.remove();
1340
+ },
1341
+
1342
+ // This method binds the elements specified in the "ui" hash inside the view's code with
1343
+ // the associated jQuery selectors.
1344
+ bindUIElements: function(){
1345
+ if (!this.ui) { return; }
1346
+
1347
+ // store the ui hash in _uiBindings so they can be reset later
1348
+ // and so re-rendering the view will be able to find the bindings
1349
+ if (!this._uiBindings){
1350
+ this._uiBindings = this.ui;
1351
+ }
1352
+
1353
+ // get the bindings result, as a function or otherwise
1354
+ var bindings = _.result(this, "_uiBindings");
1355
+
1356
+ // empty the ui so we don't have anything to start with
1357
+ this.ui = {};
1358
+
1359
+ // bind each of the selectors
1360
+ _.each(_.keys(bindings), function(key) {
1361
+ var selector = bindings[key];
1362
+ this.ui[key] = this.$(selector);
1363
+ }, this);
1364
+ },
1365
+
1366
+ // This method unbinds the elements specified in the "ui" hash
1367
+ unbindUIElements: function(){
1368
+ if (!this.ui || !this._uiBindings){ return; }
1369
+
1370
+ // delete all of the existing ui bindings
1371
+ _.each(this.ui, function($el, name){
1372
+ delete this.ui[name];
1373
+ }, this);
1374
+
1375
+ // reset the ui element to the original bindings configuration
1376
+ this.ui = this._uiBindings;
1377
+ delete this._uiBindings;
1378
+ }
1379
+ });
1380
+
1381
+ // Item View
1382
+ // ---------
1383
+
1384
+ // A single item view implementation that contains code for rendering
1385
+ // with underscore.js templates, serializing the view's model or collection,
1386
+ // and calling several methods on extended views, such as `onRender`.
1387
+ Marionette.ItemView = Marionette.View.extend({
1388
+
1389
+ // Setting up the inheritance chain which allows changes to
1390
+ // Marionette.View.prototype.constructor which allows overriding
1391
+ constructor: function(){
1392
+ Marionette.View.prototype.constructor.apply(this, slice(arguments));
1393
+ },
1394
+
1395
+ // Serialize the model or collection for the view. If a model is
1396
+ // found, `.toJSON()` is called. If a collection is found, `.toJSON()`
1397
+ // is also called, but is used to populate an `items` array in the
1398
+ // resulting data. If both are found, defaults to the model.
1399
+ // You can override the `serializeData` method in your own view
1400
+ // definition, to provide custom serialization for your view's data.
1401
+ serializeData: function(){
1402
+ var data = {};
1403
+
1404
+ if (this.model) {
1405
+ data = this.model.toJSON();
1406
+ }
1407
+ else if (this.collection) {
1408
+ data = { items: this.collection.toJSON() };
1409
+ }
1410
+
1411
+ return data;
1412
+ },
1413
+
1414
+ // Render the view, defaulting to underscore.js templates.
1415
+ // You can override this in your view definition to provide
1416
+ // a very specific rendering for your view. In general, though,
1417
+ // you should override the `Marionette.Renderer` object to
1418
+ // change how Marionette renders views.
1419
+ render: function(){
1420
+ this.isClosed = false;
1421
+
1422
+ this.triggerMethod("before:render", this);
1423
+ this.triggerMethod("item:before:render", this);
1424
+
1425
+ var data = this.serializeData();
1426
+ data = this.mixinTemplateHelpers(data);
1427
+
1428
+ var template = this.getTemplate();
1429
+ var html = Marionette.Renderer.render(template, data);
1430
+
1431
+ this.$el.html(html);
1432
+ this.bindUIElements();
1433
+
1434
+ this.triggerMethod("render", this);
1435
+ this.triggerMethod("item:rendered", this);
1436
+
1437
+ return this;
1438
+ },
1439
+
1440
+ // Override the default close event to add a few
1441
+ // more events that are triggered.
1442
+ close: function(){
1443
+ if (this.isClosed){ return; }
1444
+
1445
+ this.triggerMethod('item:before:close');
1446
+
1447
+ Marionette.View.prototype.close.apply(this, slice(arguments));
1448
+
1449
+ this.triggerMethod('item:closed');
1450
+ }
1451
+ });
1452
+
1453
+ // Collection View
1454
+ // ---------------
1455
+
1456
+ // A view that iterates over a Backbone.Collection
1457
+ // and renders an individual ItemView for each model.
1458
+ Marionette.CollectionView = Marionette.View.extend({
1459
+ // used as the prefix for item view events
1460
+ // that are forwarded through the collectionview
1461
+ itemViewEventPrefix: "itemview",
1462
+
1463
+ // constructor
1464
+ constructor: function(options){
1465
+ this._initChildViewStorage();
1466
+
1467
+ Marionette.View.prototype.constructor.apply(this, slice(arguments));
1468
+
1469
+ this._initialEvents();
1470
+ },
1471
+
1472
+ // Configured the initial events that the collection view
1473
+ // binds to. Override this method to prevent the initial
1474
+ // events, or to add your own initial events.
1475
+ _initialEvents: function(){
1476
+ if (this.collection){
1477
+ this.listenTo(this.collection, "add", this.addChildView, this);
1478
+ this.listenTo(this.collection, "remove", this.removeItemView, this);
1479
+ this.listenTo(this.collection, "reset", this.render, this);
1480
+ }
1481
+ },
1482
+
1483
+ // Handle a child item added to the collection
1484
+ addChildView: function(item, collection, options){
1485
+ this.closeEmptyView();
1486
+ var ItemView = this.getItemView(item);
1487
+ var index = this.collection.indexOf(item);
1488
+ this.addItemView(item, ItemView, index);
1489
+ },
1490
+
1491
+ // Override from `Marionette.View` to guarantee the `onShow` method
1492
+ // of child views is called.
1493
+ onShowCalled: function(){
1494
+ this.children.each(function(child){
1495
+ Marionette.triggerMethod.call(child, "show");
1496
+ });
1497
+ },
1498
+
1499
+ // Internal method to trigger the before render callbacks
1500
+ // and events
1501
+ triggerBeforeRender: function(){
1502
+ this.triggerMethod("before:render", this);
1503
+ this.triggerMethod("collection:before:render", this);
1504
+ },
1505
+
1506
+ // Internal method to trigger the rendered callbacks and
1507
+ // events
1508
+ triggerRendered: function(){
1509
+ this.triggerMethod("render", this);
1510
+ this.triggerMethod("collection:rendered", this);
1511
+ },
1512
+
1513
+ // Render the collection of items. Override this method to
1514
+ // provide your own implementation of a render function for
1515
+ // the collection view.
1516
+ render: function(){
1517
+ this.isClosed = false;
1518
+ this.triggerBeforeRender();
1519
+ this._renderChildren();
1520
+ this.triggerRendered();
1521
+ return this;
1522
+ },
1523
+
1524
+ // Internal method. Separated so that CompositeView can have
1525
+ // more control over events being triggered, around the rendering
1526
+ // process
1527
+ _renderChildren: function(){
1528
+ this.closeEmptyView();
1529
+ this.closeChildren();
1530
+
1531
+ if (this.collection && this.collection.length > 0) {
1532
+ this.showCollection();
1533
+ } else {
1534
+ this.showEmptyView();
1535
+ }
1536
+ },
1537
+
1538
+ // Internal method to loop through each item in the
1539
+ // collection view and show it
1540
+ showCollection: function(){
1541
+ var ItemView;
1542
+ this.collection.each(function(item, index){
1543
+ ItemView = this.getItemView(item);
1544
+ this.addItemView(item, ItemView, index);
1545
+ }, this);
1546
+ },
1547
+
1548
+ // Internal method to show an empty view in place of
1549
+ // a collection of item views, when the collection is
1550
+ // empty
1551
+ showEmptyView: function(){
1552
+ var EmptyView = Marionette.getOption(this, "emptyView");
1553
+
1554
+ if (EmptyView && !this._showingEmptyView){
1555
+ this._showingEmptyView = true;
1556
+ var model = new Backbone.Model();
1557
+ this.addItemView(model, EmptyView, 0);
1558
+ }
1559
+ },
1560
+
1561
+ // Internal method to close an existing emptyView instance
1562
+ // if one exists. Called when a collection view has been
1563
+ // rendered empty, and then an item is added to the collection.
1564
+ closeEmptyView: function(){
1565
+ if (this._showingEmptyView){
1566
+ this.closeChildren();
1567
+ delete this._showingEmptyView;
1568
+ }
1569
+ },
1570
+
1571
+ // Retrieve the itemView type, either from `this.options.itemView`
1572
+ // or from the `itemView` in the object definition. The "options"
1573
+ // takes precedence.
1574
+ getItemView: function(item){
1575
+ var itemView = Marionette.getOption(this, "itemView");
1576
+
1577
+ if (!itemView){
1578
+ throwError("An `itemView` must be specified", "NoItemViewError");
1579
+ }
1580
+
1581
+ return itemView;
1582
+ },
1583
+
1584
+ // Render the child item's view and add it to the
1585
+ // HTML for the collection view.
1586
+ addItemView: function(item, ItemView, index){
1587
+ // get the itemViewOptions if any were specified
1588
+ var itemViewOptions = Marionette.getOption(this, "itemViewOptions");
1589
+ if (_.isFunction(itemViewOptions)){
1590
+ itemViewOptions = itemViewOptions.call(this, item, index);
1591
+ }
1592
+
1593
+ // build the view
1594
+ var view = this.buildItemView(item, ItemView, itemViewOptions);
1595
+
1596
+ // set up the child view event forwarding
1597
+ this.addChildViewEventForwarding(view);
1598
+
1599
+ // this view is about to be added
1600
+ this.triggerMethod("before:item:added", view);
1601
+
1602
+ // Store the child view itself so we can properly
1603
+ // remove and/or close it later
1604
+ this.children.add(view);
1605
+
1606
+ // Render it and show it
1607
+ this.renderItemView(view, index);
1608
+
1609
+ // call the "show" method if the collection view
1610
+ // has already been shown
1611
+ if (this._isShown){
1612
+ Marionette.triggerMethod.call(view, "show");
1613
+ }
1614
+
1615
+ // this view was added
1616
+ this.triggerMethod("after:item:added", view);
1617
+ },
1618
+
1619
+ // Set up the child view event forwarding. Uses an "itemview:"
1620
+ // prefix in front of all forwarded events.
1621
+ addChildViewEventForwarding: function(view){
1622
+ var prefix = Marionette.getOption(this, "itemViewEventPrefix");
1623
+
1624
+ // Forward all child item view events through the parent,
1625
+ // prepending "itemview:" to the event name
1626
+ this.listenTo(view, "all", function(){
1627
+ var args = slice(arguments);
1628
+ args[0] = prefix + ":" + args[0];
1629
+ args.splice(1, 0, view);
1630
+
1631
+ Marionette.triggerMethod.apply(this, args);
1632
+ }, this);
1633
+ },
1634
+
1635
+ // render the item view
1636
+ renderItemView: function(view, index) {
1637
+ view.render();
1638
+ this.appendHtml(this, view, index);
1639
+ },
1640
+
1641
+ // Build an `itemView` for every model in the collection.
1642
+ buildItemView: function(item, ItemViewType, itemViewOptions){
1643
+ var options = _.extend({model: item}, itemViewOptions);
1644
+ return new ItemViewType(options);
1645
+ },
1646
+
1647
+ // get the child view by item it holds, and remove it
1648
+ removeItemView: function(item){
1649
+ var view = this.children.findByModel(item);
1650
+ this.removeChildView(view);
1651
+ this.checkEmpty();
1652
+ },
1653
+
1654
+ // Remove the child view and close it
1655
+ removeChildView: function(view){
1656
+
1657
+ // shut down the child view properly,
1658
+ // including events that the collection has from it
1659
+ if (view){
1660
+ this.stopListening(view);
1661
+
1662
+ // call 'close' or 'remove', depending on which is found
1663
+ if (view.close) { view.close(); }
1664
+ else if (view.remove) { view.remove(); }
1665
+
1666
+ this.children.remove(view);
1667
+ }
1668
+
1669
+ this.triggerMethod("item:removed", view);
1670
+ },
1671
+
1672
+ // helper to show the empty view if the collection is empty
1673
+ checkEmpty: function() {
1674
+ // check if we're empty now, and if we are, show the
1675
+ // empty view
1676
+ if (!this.collection || this.collection.length === 0){
1677
+ this.showEmptyView();
1678
+ }
1679
+ },
1680
+
1681
+ // Append the HTML to the collection's `el`.
1682
+ // Override this method to do something other
1683
+ // then `.append`.
1684
+ appendHtml: function(collectionView, itemView, index){
1685
+ collectionView.$el.append(itemView.el);
1686
+ },
1687
+
1688
+ // Internal method to set up the `children` object for
1689
+ // storing all of the child views
1690
+ _initChildViewStorage: function(){
1691
+ this.children = new ChildViewContainer();
1692
+ },
1693
+
1694
+ // Handle cleanup and other closing needs for
1695
+ // the collection of views.
1696
+ close: function(){
1697
+ if (this.isClosed){ return; }
1698
+
1699
+ this.triggerMethod("collection:before:close");
1700
+ this.closeChildren();
1701
+ this.triggerMethod("collection:closed");
1702
+
1703
+ Marionette.View.prototype.close.apply(this, slice(arguments));
1704
+ },
1705
+
1706
+ // Close the child views that this collection view
1707
+ // is holding on to, if any
1708
+ closeChildren: function(){
1709
+ this.children.each(function(child){
1710
+ this.removeChildView(child);
1711
+ }, this);
1712
+ this.checkEmpty();
1713
+ }
1714
+ });
1715
+
1716
+
1717
+ // Composite View
1718
+ // --------------
1719
+
1720
+ // Used for rendering a branch-leaf, hierarchical structure.
1721
+ // Extends directly from CollectionView and also renders an
1722
+ // an item view as `modelView`, for the top leaf
1723
+ Marionette.CompositeView = Marionette.CollectionView.extend({
1724
+
1725
+ // Setting up the inheritance chain which allows changes to
1726
+ // Marionette.CollectionView.prototype.constructor which allows overriding
1727
+ constructor: function(){
1728
+ Marionette.CollectionView.prototype.constructor.apply(this, slice(arguments));
1729
+ },
1730
+
1731
+ // Configured the initial events that the composite view
1732
+ // binds to. Override this method to prevent the initial
1733
+ // events, or to add your own initial events.
1734
+ _initialEvents: function(){
1735
+ if (this.collection){
1736
+ this.listenTo(this.collection, "add", this.addChildView, this);
1737
+ this.listenTo(this.collection, "remove", this.removeItemView, this);
1738
+ this.listenTo(this.collection, "reset", this._renderChildren, this);
1739
+ }
1740
+ },
1741
+
1742
+ // Retrieve the `itemView` to be used when rendering each of
1743
+ // the items in the collection. The default is to return
1744
+ // `this.itemView` or Marionette.CompositeView if no `itemView`
1745
+ // has been defined
1746
+ getItemView: function(item){
1747
+ var itemView = Marionette.getOption(this, "itemView") || this.constructor;
1748
+
1749
+ if (!itemView){
1750
+ throwError("An `itemView` must be specified", "NoItemViewError");
1751
+ }
1752
+
1753
+ return itemView;
1754
+ },
1755
+
1756
+ // Serialize the collection for the view.
1757
+ // You can override the `serializeData` method in your own view
1758
+ // definition, to provide custom serialization for your view's data.
1759
+ serializeData: function(){
1760
+ var data = {};
1761
+
1762
+ if (this.model){
1763
+ data = this.model.toJSON();
1764
+ }
1765
+
1766
+ return data;
1767
+ },
1768
+
1769
+ // Renders the model once, and the collection once. Calling
1770
+ // this again will tell the model's view to re-render itself
1771
+ // but the collection will not re-render.
1772
+ render: function(){
1773
+ this.isRendered = true;
1774
+ this.isClosed = false;
1775
+ this.resetItemViewContainer();
1776
+
1777
+ this.triggerBeforeRender();
1778
+ var html = this.renderModel();
1779
+ this.$el.html(html);
1780
+ // the ui bindings is done here and not at the end of render since they
1781
+ // will not be available until after the model is rendered, but should be
1782
+ // available before the collection is rendered.
1783
+ this.bindUIElements();
1784
+ this.triggerMethod("composite:model:rendered");
1785
+
1786
+ this._renderChildren();
1787
+
1788
+ this.triggerMethod("composite:rendered");
1789
+ this.triggerRendered();
1790
+ return this;
1791
+ },
1792
+
1793
+ _renderChildren: function(){
1794
+ if (this.isRendered){
1795
+ Marionette.CollectionView.prototype._renderChildren.call(this);
1796
+ this.triggerMethod("composite:collection:rendered");
1797
+ }
1798
+ },
1799
+
1800
+ // Render an individual model, if we have one, as
1801
+ // part of a composite view (branch / leaf). For example:
1802
+ // a treeview.
1803
+ renderModel: function(){
1804
+ var data = {};
1805
+ data = this.serializeData();
1806
+ data = this.mixinTemplateHelpers(data);
1807
+
1808
+ var template = this.getTemplate();
1809
+ return Marionette.Renderer.render(template, data);
1810
+ },
1811
+
1812
+ // Appends the `el` of itemView instances to the specified
1813
+ // `itemViewContainer` (a jQuery selector). Override this method to
1814
+ // provide custom logic of how the child item view instances have their
1815
+ // HTML appended to the composite view instance.
1816
+ appendHtml: function(cv, iv, index){
1817
+ var $container = this.getItemViewContainer(cv);
1818
+ $container.append(iv.el);
1819
+ },
1820
+
1821
+ // Internal method to ensure an `$itemViewContainer` exists, for the
1822
+ // `appendHtml` method to use.
1823
+ getItemViewContainer: function(containerView){
1824
+ if ("$itemViewContainer" in containerView){
1825
+ return containerView.$itemViewContainer;
1826
+ }
1827
+
1828
+ var container;
1829
+ var itemViewContainer = Marionette.getOption(containerView, "itemViewContainer");
1830
+ if (itemViewContainer){
1831
+
1832
+ var selector = _.isFunction(itemViewContainer) ? itemViewContainer() : itemViewContainer;
1833
+ container = containerView.$(selector);
1834
+ if (container.length <= 0) {
1835
+ throwError("The specified `itemViewContainer` was not found: " + containerView.itemViewContainer, "ItemViewContainerMissingError");
1836
+ }
1837
+
1838
+ } else {
1839
+ container = containerView.$el;
1840
+ }
1841
+
1842
+ containerView.$itemViewContainer = container;
1843
+ return container;
1844
+ },
1845
+
1846
+ // Internal method to reset the `$itemViewContainer` on render
1847
+ resetItemViewContainer: function(){
1848
+ if (this.$itemViewContainer){
1849
+ delete this.$itemViewContainer;
1850
+ }
1851
+ }
1852
+ });
1853
+
1854
+
1855
+ // Layout
1856
+ // ------
1857
+
1858
+ // Used for managing application layouts, nested layouts and
1859
+ // multiple regions within an application or sub-application.
1860
+ //
1861
+ // A specialized view type that renders an area of HTML and then
1862
+ // attaches `Region` instances to the specified `regions`.
1863
+ // Used for composite view management and sub-application areas.
1864
+ Marionette.Layout = Marionette.ItemView.extend({
1865
+ regionType: Marionette.Region,
1866
+
1867
+ // Ensure the regions are available when the `initialize` method
1868
+ // is called.
1869
+ constructor: function (options) {
1870
+ options = options || {};
1871
+
1872
+ this._firstRender = true;
1873
+ this._initializeRegions(options);
1874
+
1875
+ Marionette.ItemView.prototype.constructor.call(this, options);
1876
+ },
1877
+
1878
+ // Layout's render will use the existing region objects the
1879
+ // first time it is called. Subsequent calls will close the
1880
+ // views that the regions are showing and then reset the `el`
1881
+ // for the regions to the newly rendered DOM elements.
1882
+ render: function(){
1883
+
1884
+ if (this.isClosed){
1885
+ // a previously closed layout means we need to
1886
+ // completely re-initialize the regions
1887
+ this._initializeRegions();
1888
+ }
1889
+ if (this._firstRender) {
1890
+ // if this is the first render, don't do anything to
1891
+ // reset the regions
1892
+ this._firstRender = false;
1893
+ } else if (!this.isClosed){
1894
+ // If this is not the first render call, then we need to
1895
+ // re-initializing the `el` for each region
1896
+ this._reInitializeRegions();
1897
+ }
1898
+
1899
+ var args = Array.prototype.slice.apply(arguments);
1900
+ var result = Marionette.ItemView.prototype.render.apply(this, args);
1901
+
1902
+ return result;
1903
+ },
1904
+
1905
+ // Handle closing regions, and then close the view itself.
1906
+ close: function () {
1907
+ if (this.isClosed){ return; }
1908
+ this.regionManager.close();
1909
+ var args = Array.prototype.slice.apply(arguments);
1910
+ Marionette.ItemView.prototype.close.apply(this, args);
1911
+ },
1912
+
1913
+ // Add a single region, by name, to the layout
1914
+ addRegion: function(name, definition){
1915
+ var regions = {};
1916
+ regions[name] = definition;
1917
+ return this._buildRegions(regions)[name];
1918
+ },
1919
+
1920
+ // Add multiple regions as a {name: definition, name2: def2} object literal
1921
+ addRegions: function(regions){
1922
+ this.regions = _.extend({}, this.regions, regions);
1923
+ return this._buildRegions(regions);
1924
+ },
1925
+
1926
+ // Remove a single region from the Layout, by name
1927
+ removeRegion: function(name){
1928
+ delete this.regions[name];
1929
+ return this.regionManager.removeRegion(name);
1930
+ },
1931
+
1932
+ // internal method to build regions
1933
+ _buildRegions: function(regions){
1934
+ var that = this;
1935
+
1936
+ var defaults = {
1937
+ regionType: Marionette.getOption(this, "regionType"),
1938
+ parentEl: function(){ return that.$el; }
1939
+ };
1940
+
1941
+ return this.regionManager.addRegions(regions, defaults);
1942
+ },
1943
+
1944
+ // Internal method to initialize the regions that have been defined in a
1945
+ // `regions` attribute on this layout.
1946
+ _initializeRegions: function (options) {
1947
+ var regions;
1948
+ this._initRegionManager();
1949
+
1950
+ if (_.isFunction(this.regions)) {
1951
+ regions = this.regions(options);
1952
+ } else {
1953
+ regions = this.regions || {};
1954
+ }
1955
+
1956
+ this.addRegions(regions);
1957
+ },
1958
+
1959
+ // Internal method to re-initialize all of the regions by updating the `el` that
1960
+ // they point to
1961
+ _reInitializeRegions: function(){
1962
+ this.regionManager.closeRegions();
1963
+ this.regionManager.each(function(region){
1964
+ region.reset();
1965
+ });
1966
+ },
1967
+
1968
+ // Internal method to initialize the region manager
1969
+ // and all regions in it
1970
+ _initRegionManager: function(){
1971
+ this.regionManager = new Marionette.RegionManager();
1972
+
1973
+ this.listenTo(this.regionManager, "region:add", function(name, region){
1974
+ this[name] = region;
1975
+ this.trigger("region:add", name, region);
1976
+ });
1977
+
1978
+ this.listenTo(this.regionManager, "region:remove", function(name, region){
1979
+ delete this[name];
1980
+ this.trigger("region:remove", name, region);
1981
+ });
1982
+ }
1983
+ });
1984
+
1985
+
1986
+ // AppRouter
1987
+ // ---------
1988
+
1989
+ // Reduce the boilerplate code of handling route events
1990
+ // and then calling a single method on another object.
1991
+ // Have your routers configured to call the method on
1992
+ // your object, directly.
1993
+ //
1994
+ // Configure an AppRouter with `appRoutes`.
1995
+ //
1996
+ // App routers can only take one `controller` object.
1997
+ // It is recommended that you divide your controller
1998
+ // objects in to smaller pieces of related functionality
1999
+ // and have multiple routers / controllers, instead of
2000
+ // just one giant router and controller.
2001
+ //
2002
+ // You can also add standard routes to an AppRouter.
2003
+
2004
+ Marionette.AppRouter = Backbone.Router.extend({
2005
+
2006
+ constructor: function(options){
2007
+ Backbone.Router.prototype.constructor.apply(this, slice(arguments));
2008
+
2009
+ this.options = options || {};
2010
+
2011
+ var appRoutes = Marionette.getOption(this, "appRoutes");
2012
+ var controller = this._getController();
2013
+ this.processAppRoutes(controller, appRoutes);
2014
+ },
2015
+
2016
+ // Similar to route method on a Backbone Router but
2017
+ // method is called on the controller
2018
+ appRoute: function(route, methodName) {
2019
+ var controller = this._getController();
2020
+ this._addAppRoute(controller, route, methodName);
2021
+ },
2022
+
2023
+ // Internal method to process the `appRoutes` for the
2024
+ // router, and turn them in to routes that trigger the
2025
+ // specified method on the specified `controller`.
2026
+ processAppRoutes: function(controller, appRoutes) {
2027
+ if (!appRoutes){ return; }
2028
+
2029
+ var routeNames = _.keys(appRoutes).reverse(); // Backbone requires reverted order of routes
2030
+
2031
+ _.each(routeNames, function(route) {
2032
+ this._addAppRoute(controller, route, appRoutes[route]);
2033
+ }, this);
2034
+ },
2035
+
2036
+ _getController: function(){
2037
+ return Marionette.getOption(this, "controller");
2038
+ },
2039
+
2040
+ _addAppRoute: function(controller, route, methodName){
2041
+ var method = controller[methodName];
2042
+
2043
+ if (!method) {
2044
+ throw new Error("Method '" + methodName + "' was not found on the controller");
2045
+ }
2046
+
2047
+ this.route(route, methodName, _.bind(method, controller));
2048
+ }
2049
+ });
2050
+
2051
+
2052
+ // Application
2053
+ // -----------
2054
+
2055
+ // Contain and manage the composite application as a whole.
2056
+ // Stores and starts up `Region` objects, includes an
2057
+ // event aggregator as `app.vent`
2058
+ Marionette.Application = function(options){
2059
+ this._initRegionManager();
2060
+ this._initCallbacks = new Marionette.Callbacks();
2061
+ this.vent = new Backbone.Wreqr.EventAggregator();
2062
+ this.commands = new Backbone.Wreqr.Commands();
2063
+ this.reqres = new Backbone.Wreqr.RequestResponse();
2064
+ this.submodules = {};
2065
+
2066
+ _.extend(this, options);
2067
+
2068
+ this.triggerMethod = Marionette.triggerMethod;
2069
+ };
2070
+
2071
+ _.extend(Marionette.Application.prototype, Backbone.Events, {
2072
+ // Command execution, facilitated by Backbone.Wreqr.Commands
2073
+ execute: function(){
2074
+ var args = Array.prototype.slice.apply(arguments);
2075
+ this.commands.execute.apply(this.commands, args);
2076
+ },
2077
+
2078
+ // Request/response, facilitated by Backbone.Wreqr.RequestResponse
2079
+ request: function(){
2080
+ var args = Array.prototype.slice.apply(arguments);
2081
+ return this.reqres.request.apply(this.reqres, args);
2082
+ },
2083
+
2084
+ // Add an initializer that is either run at when the `start`
2085
+ // method is called, or run immediately if added after `start`
2086
+ // has already been called.
2087
+ addInitializer: function(initializer){
2088
+ this._initCallbacks.add(initializer);
2089
+ },
2090
+
2091
+ // kick off all of the application's processes.
2092
+ // initializes all of the regions that have been added
2093
+ // to the app, and runs all of the initializer functions
2094
+ start: function(options){
2095
+ this.triggerMethod("initialize:before", options);
2096
+ this._initCallbacks.run(options, this);
2097
+ this.triggerMethod("initialize:after", options);
2098
+
2099
+ this.triggerMethod("start", options);
2100
+ },
2101
+
2102
+ // Add regions to your app.
2103
+ // Accepts a hash of named strings or Region objects
2104
+ // addRegions({something: "#someRegion"})
2105
+ // addRegions({something: Region.extend({el: "#someRegion"}) });
2106
+ addRegions: function(regions){
2107
+ return this._regionManager.addRegions(regions);
2108
+ },
2109
+
2110
+ // Close all regions in the app, without removing them
2111
+ closeRegions: function(){
2112
+ this._regionManager.closeRegions();
2113
+ },
2114
+
2115
+ // Removes a region from your app, by name
2116
+ // Accepts the regions name
2117
+ // removeRegion('myRegion')
2118
+ removeRegion: function(region) {
2119
+ this._regionManager.removeRegion(region);
2120
+ },
2121
+
2122
+ // Provides alternative access to regions
2123
+ // Accepts the region name
2124
+ // getRegion('main')
2125
+ getRegion: function(region) {
2126
+ return this._regionManager.get(region);
2127
+ },
2128
+
2129
+ // Create a module, attached to the application
2130
+ module: function(moduleNames, moduleDefinition){
2131
+ // slice the args, and add this application object as the
2132
+ // first argument of the array
2133
+ var args = slice(arguments);
2134
+ args.unshift(this);
2135
+
2136
+ // see the Marionette.Module object for more information
2137
+ return Marionette.Module.create.apply(Marionette.Module, args);
2138
+ },
2139
+
2140
+ // Internal method to set up the region manager
2141
+ _initRegionManager: function(){
2142
+ this._regionManager = new Marionette.RegionManager();
2143
+
2144
+ this.listenTo(this._regionManager, "region:add", function(name, region){
2145
+ this[name] = region;
2146
+ });
2147
+
2148
+ this.listenTo(this._regionManager, "region:remove", function(name, region){
2149
+ delete this[name];
2150
+ });
2151
+ }
2152
+ });
2153
+
2154
+ // Copy the `extend` function used by Backbone's classes
2155
+ Marionette.Application.extend = Marionette.extend;
2156
+
2157
+ // Module
2158
+ // ------
2159
+
2160
+ // A simple module system, used to create privacy and encapsulation in
2161
+ // Marionette applications
2162
+ Marionette.Module = function(moduleName, app){
2163
+ this.moduleName = moduleName;
2164
+
2165
+ // store sub-modules
2166
+ this.submodules = {};
2167
+
2168
+ this._setupInitializersAndFinalizers();
2169
+
2170
+ // store the configuration for this module
2171
+ this.app = app;
2172
+ this.startWithParent = true;
2173
+
2174
+ this.triggerMethod = Marionette.triggerMethod;
2175
+ };
2176
+
2177
+ // Extend the Module prototype with events / listenTo, so that the module
2178
+ // can be used as an event aggregator or pub/sub.
2179
+ _.extend(Marionette.Module.prototype, Backbone.Events, {
2180
+
2181
+ // Initializer for a specific module. Initializers are run when the
2182
+ // module's `start` method is called.
2183
+ addInitializer: function(callback){
2184
+ this._initializerCallbacks.add(callback);
2185
+ },
2186
+
2187
+ // Finalizers are run when a module is stopped. They are used to teardown
2188
+ // and finalize any variables, references, events and other code that the
2189
+ // module had set up.
2190
+ addFinalizer: function(callback){
2191
+ this._finalizerCallbacks.add(callback);
2192
+ },
2193
+
2194
+ // Start the module, and run all of its initializers
2195
+ start: function(options){
2196
+ // Prevent re-starting a module that is already started
2197
+ if (this._isInitialized){ return; }
2198
+
2199
+ // start the sub-modules (depth-first hierarchy)
2200
+ _.each(this.submodules, function(mod){
2201
+ // check to see if we should start the sub-module with this parent
2202
+ if (mod.startWithParent){
2203
+ mod.start(options);
2204
+ }
2205
+ });
2206
+
2207
+ // run the callbacks to "start" the current module
2208
+ this.triggerMethod("before:start", options);
2209
+
2210
+ this._initializerCallbacks.run(options, this);
2211
+ this._isInitialized = true;
2212
+
2213
+ this.triggerMethod("start", options);
2214
+ },
2215
+
2216
+ // Stop this module by running its finalizers and then stop all of
2217
+ // the sub-modules for this module
2218
+ stop: function(){
2219
+ // if we are not initialized, don't bother finalizing
2220
+ if (!this._isInitialized){ return; }
2221
+ this._isInitialized = false;
2222
+
2223
+ Marionette.triggerMethod.call(this, "before:stop");
2224
+
2225
+ // stop the sub-modules; depth-first, to make sure the
2226
+ // sub-modules are stopped / finalized before parents
2227
+ _.each(this.submodules, function(mod){ mod.stop(); });
2228
+
2229
+ // run the finalizers
2230
+ this._finalizerCallbacks.run(undefined,this);
2231
+
2232
+ // reset the initializers and finalizers
2233
+ this._initializerCallbacks.reset();
2234
+ this._finalizerCallbacks.reset();
2235
+
2236
+ Marionette.triggerMethod.call(this, "stop");
2237
+ },
2238
+
2239
+ // Configure the module with a definition function and any custom args
2240
+ // that are to be passed in to the definition function
2241
+ addDefinition: function(moduleDefinition, customArgs){
2242
+ this._runModuleDefinition(moduleDefinition, customArgs);
2243
+ },
2244
+
2245
+ // Internal method: run the module definition function with the correct
2246
+ // arguments
2247
+ _runModuleDefinition: function(definition, customArgs){
2248
+ if (!definition){ return; }
2249
+
2250
+ // build the correct list of arguments for the module definition
2251
+ var args = _.flatten([
2252
+ this,
2253
+ this.app,
2254
+ Backbone,
2255
+ Marionette,
2256
+ Marionette.$, _,
2257
+ customArgs
2258
+ ]);
2259
+
2260
+ definition.apply(this, args);
2261
+ },
2262
+
2263
+ // Internal method: set up new copies of initializers and finalizers.
2264
+ // Calling this method will wipe out all existing initializers and
2265
+ // finalizers.
2266
+ _setupInitializersAndFinalizers: function(){
2267
+ this._initializerCallbacks = new Marionette.Callbacks();
2268
+ this._finalizerCallbacks = new Marionette.Callbacks();
2269
+ }
2270
+ });
2271
+
2272
+ // Type methods to create modules
2273
+ _.extend(Marionette.Module, {
2274
+
2275
+ // Create a module, hanging off the app parameter as the parent object.
2276
+ create: function(app, moduleNames, moduleDefinition){
2277
+ var module = app;
2278
+
2279
+ // get the custom args passed in after the module definition and
2280
+ // get rid of the module name and definition function
2281
+ var customArgs = slice(arguments);
2282
+ customArgs.splice(0, 3);
2283
+
2284
+ // split the module names and get the length
2285
+ moduleNames = moduleNames.split(".");
2286
+ var length = moduleNames.length;
2287
+
2288
+ // store the module definition for the last module in the chain
2289
+ var moduleDefinitions = [];
2290
+ moduleDefinitions[length-1] = moduleDefinition;
2291
+
2292
+ // Loop through all the parts of the module definition
2293
+ _.each(moduleNames, function(moduleName, i){
2294
+ var parentModule = module;
2295
+ module = this._getModule(parentModule, moduleName, app);
2296
+ this._addModuleDefinition(parentModule, module, moduleDefinitions[i], customArgs);
2297
+ }, this);
2298
+
2299
+ // Return the last module in the definition chain
2300
+ return module;
2301
+ },
2302
+
2303
+ _getModule: function(parentModule, moduleName, app, def, args){
2304
+ // Get an existing module of this name if we have one
2305
+ var module = parentModule[moduleName];
2306
+
2307
+ if (!module){
2308
+ // Create a new module if we don't have one
2309
+ module = new Marionette.Module(moduleName, app);
2310
+ parentModule[moduleName] = module;
2311
+ // store the module on the parent
2312
+ parentModule.submodules[moduleName] = module;
2313
+ }
2314
+
2315
+ return module;
2316
+ },
2317
+
2318
+ _addModuleDefinition: function(parentModule, module, def, args){
2319
+ var fn;
2320
+ var startWithParent;
2321
+
2322
+ if (_.isFunction(def)){
2323
+ // if a function is supplied for the module definition
2324
+ fn = def;
2325
+ startWithParent = true;
2326
+
2327
+ } else if (_.isObject(def)){
2328
+ // if an object is supplied
2329
+ fn = def.define;
2330
+ startWithParent = def.startWithParent;
2331
+
2332
+ } else {
2333
+ // if nothing is supplied
2334
+ startWithParent = true;
2335
+ }
2336
+
2337
+ // add module definition if needed
2338
+ if (fn){
2339
+ module.addDefinition(fn, args);
2340
+ }
2341
+
2342
+ // `and` the two together, ensuring a single `false` will prevent it
2343
+ // from starting with the parent
2344
+ module.startWithParent = module.startWithParent && startWithParent;
2345
+
2346
+ // setup auto-start if needed
2347
+ if (module.startWithParent && !module.startWithParentIsConfigured){
2348
+
2349
+ // only configure this once
2350
+ module.startWithParentIsConfigured = true;
2351
+
2352
+ // add the module initializer config
2353
+ parentModule.addInitializer(function(options){
2354
+ if (module.startWithParent){
2355
+ module.start(options);
2356
+ }
2357
+ });
2358
+
2359
+ }
2360
+
2361
+ }
2362
+ });
2363
+
2364
+
2365
+
2366
+ return Marionette;
2367
+ })(window, Backbone, _);
2368
+
2369
+ module.exports = Marionette;