js_stack 0.2.0 → 0.3.0

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