js_stack 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,2466 @@
1
+ // MarionetteJS (Backbone.Marionette)
2
+ // ----------------------------------
3
+ // v1.4.0
4
+ //
5
+ // Copyright (c)2013 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 name
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(){
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){
557
+ if (_.isFunction(view.triggerMethod)){
558
+ view.triggerMethod("dom:refresh");
559
+ }
560
+ }
561
+ }
562
+
563
+ // Export public API
564
+ return function(view){
565
+ view.listenTo(view, "show", function(){
566
+ handleShow(view);
567
+ });
568
+
569
+ view.listenTo(view, "render", function(){
570
+ handleRender(view);
571
+ });
572
+ };
573
+ })();
574
+
575
+
576
+ // Marionette.bindEntityEvents & unbindEntityEvents
577
+ // ---------------------------
578
+ //
579
+ // These methods are used to bind/unbind a backbone "entity" (collection/model)
580
+ // to methods on a target object.
581
+ //
582
+ // The first parameter, `target`, must have a `listenTo` method from the
583
+ // EventBinder object.
584
+ //
585
+ // The second parameter is the entity (Backbone.Model or Backbone.Collection)
586
+ // to bind the events from.
587
+ //
588
+ // The third parameter is a hash of { "event:name": "eventHandler" }
589
+ // configuration. Multiple handlers can be separated by a space. A
590
+ // function can be supplied instead of a string handler name.
591
+
592
+ (function(Marionette){
593
+ "use strict";
594
+
595
+ // Bind the event to handlers specified as a string of
596
+ // handler names on the target object
597
+ function bindFromStrings(target, entity, evt, methods){
598
+ var methodNames = methods.split(/\s+/);
599
+
600
+ _.each(methodNames,function(methodName) {
601
+
602
+ var method = target[methodName];
603
+ if(!method) {
604
+ throwError("Method '"+ methodName +"' was configured as an event handler, but does not exist.");
605
+ }
606
+
607
+ target.listenTo(entity, evt, method, target);
608
+ });
609
+ }
610
+
611
+ // Bind the event to a supplied callback function
612
+ function bindToFunction(target, entity, evt, method){
613
+ target.listenTo(entity, evt, method, target);
614
+ }
615
+
616
+ // Bind the event to handlers specified as a string of
617
+ // handler names on the target object
618
+ function unbindFromStrings(target, entity, evt, methods){
619
+ var methodNames = methods.split(/\s+/);
620
+
621
+ _.each(methodNames,function(methodName) {
622
+ var method = target[methodName];
623
+ target.stopListening(entity, evt, method, target);
624
+ });
625
+ }
626
+
627
+ // Bind the event to a supplied callback function
628
+ function unbindToFunction(target, entity, evt, method){
629
+ target.stopListening(entity, evt, method, target);
630
+ }
631
+
632
+
633
+ // generic looping function
634
+ function iterateEvents(target, entity, bindings, functionCallback, stringCallback){
635
+ if (!entity || !bindings) { return; }
636
+
637
+ // allow the bindings to be a function
638
+ if (_.isFunction(bindings)){
639
+ bindings = bindings.call(target);
640
+ }
641
+
642
+ // iterate the bindings and bind them
643
+ _.each(bindings, function(methods, evt){
644
+
645
+ // allow for a function as the handler,
646
+ // or a list of event names as a string
647
+ if (_.isFunction(methods)){
648
+ functionCallback(target, entity, evt, methods);
649
+ } else {
650
+ stringCallback(target, entity, evt, methods);
651
+ }
652
+
653
+ });
654
+ }
655
+
656
+ // Export Public API
657
+ Marionette.bindEntityEvents = function(target, entity, bindings){
658
+ iterateEvents(target, entity, bindings, bindToFunction, bindFromStrings);
659
+ };
660
+
661
+ Marionette.unbindEntityEvents = function(target, entity, bindings){
662
+ iterateEvents(target, entity, bindings, unbindToFunction, unbindFromStrings);
663
+ };
664
+
665
+ })(Marionette);
666
+
667
+
668
+ // Callbacks
669
+ // ---------
670
+
671
+ // A simple way of managing a collection of callbacks
672
+ // and executing them at a later point in time, using jQuery's
673
+ // `Deferred` object.
674
+ Marionette.Callbacks = function(){
675
+ this._deferred = Marionette.$.Deferred();
676
+ this._callbacks = [];
677
+ };
678
+
679
+ _.extend(Marionette.Callbacks.prototype, {
680
+
681
+ // Add a callback to be executed. Callbacks added here are
682
+ // guaranteed to execute, even if they are added after the
683
+ // `run` method is called.
684
+ add: function(callback, contextOverride){
685
+ this._callbacks.push({cb: callback, ctx: contextOverride});
686
+
687
+ this._deferred.done(function(context, options){
688
+ if (contextOverride){ context = contextOverride; }
689
+ callback.call(context, options);
690
+ });
691
+ },
692
+
693
+ // Run all registered callbacks with the context specified.
694
+ // Additional callbacks can be added after this has been run
695
+ // and they will still be executed.
696
+ run: function(options, context){
697
+ this._deferred.resolve(context, options);
698
+ },
699
+
700
+ // Resets the list of callbacks to be run, allowing the same list
701
+ // to be run multiple times - whenever the `run` method is called.
702
+ reset: function(){
703
+ var callbacks = this._callbacks;
704
+ this._deferred = Marionette.$.Deferred();
705
+ this._callbacks = [];
706
+
707
+ _.each(callbacks, function(cb){
708
+ this.add(cb.cb, cb.ctx);
709
+ }, this);
710
+ }
711
+ });
712
+
713
+
714
+ // Marionette Controller
715
+ // ---------------------
716
+ //
717
+ // A multi-purpose object to use as a controller for
718
+ // modules and routers, and as a mediator for workflow
719
+ // and coordination of other objects, views, and more.
720
+ Marionette.Controller = function(options){
721
+ this.triggerMethod = Marionette.triggerMethod;
722
+ this.options = options || {};
723
+
724
+ if (_.isFunction(this.initialize)){
725
+ this.initialize(this.options);
726
+ }
727
+ };
728
+
729
+ Marionette.Controller.extend = Marionette.extend;
730
+
731
+ // Controller Methods
732
+ // --------------
733
+
734
+ // Ensure it can trigger events with Backbone.Events
735
+ _.extend(Marionette.Controller.prototype, Backbone.Events, {
736
+ close: function(){
737
+ this.stopListening();
738
+ this.triggerMethod("close");
739
+ this.unbind();
740
+ }
741
+ });
742
+
743
+ // Region
744
+ // ------
745
+ //
746
+ // Manage the visual regions of your composite application. See
747
+ // http://lostechies.com/derickbailey/2011/12/12/composite-js-apps-regions-and-region-managers/
748
+
749
+ Marionette.Region = function(options){
750
+ this.options = options || {};
751
+
752
+ this.el = Marionette.getOption(this, "el");
753
+
754
+ if (!this.el){
755
+ var err = new Error("An 'el' must be specified for a region.");
756
+ err.name = "NoElError";
757
+ throw err;
758
+ }
759
+
760
+ if (this.initialize){
761
+ var args = Array.prototype.slice.apply(arguments);
762
+ this.initialize.apply(this, args);
763
+ }
764
+ };
765
+
766
+
767
+ // Region Type methods
768
+ // -------------------
769
+
770
+ _.extend(Marionette.Region, {
771
+
772
+ // Build an instance of a region by passing in a configuration object
773
+ // and a default region type to use if none is specified in the config.
774
+ //
775
+ // The config object should either be a string as a jQuery DOM selector,
776
+ // a Region type directly, or an object literal that specifies both
777
+ // a selector and regionType:
778
+ //
779
+ // ```js
780
+ // {
781
+ // selector: "#foo",
782
+ // regionType: MyCustomRegion
783
+ // }
784
+ // ```
785
+ //
786
+ buildRegion: function(regionConfig, defaultRegionType){
787
+
788
+ var regionIsString = (typeof regionConfig === "string");
789
+ var regionSelectorIsString = (typeof regionConfig.selector === "string");
790
+ var regionTypeIsUndefined = (typeof regionConfig.regionType === "undefined");
791
+ var regionIsType = (typeof regionConfig === "function");
792
+
793
+ if (!regionIsType && !regionIsString && !regionSelectorIsString) {
794
+ throw new Error("Region must be specified as a Region type, a selector string or an object with selector property");
795
+ }
796
+
797
+ var selector, RegionType;
798
+
799
+ // get the selector for the region
800
+
801
+ if (regionIsString) {
802
+ selector = regionConfig;
803
+ }
804
+
805
+ if (regionConfig.selector) {
806
+ selector = regionConfig.selector;
807
+ }
808
+
809
+ // get the type for the region
810
+
811
+ if (regionIsType){
812
+ RegionType = regionConfig;
813
+ }
814
+
815
+ if (!regionIsType && regionTypeIsUndefined) {
816
+ RegionType = defaultRegionType;
817
+ }
818
+
819
+ if (regionConfig.regionType) {
820
+ RegionType = regionConfig.regionType;
821
+ }
822
+
823
+ // build the region instance
824
+ var region = new RegionType({
825
+ el: selector
826
+ });
827
+
828
+ // override the `getEl` function if we have a parentEl
829
+ // this must be overridden to ensure the selector is found
830
+ // on the first use of the region. if we try to assign the
831
+ // region's `el` to `parentEl.find(selector)` in the object
832
+ // literal to build the region, the element will not be
833
+ // guaranteed to be in the DOM already, and will cause problems
834
+ if (regionConfig.parentEl){
835
+
836
+ region.getEl = function(selector) {
837
+ var parentEl = regionConfig.parentEl;
838
+ if (_.isFunction(parentEl)){
839
+ parentEl = parentEl();
840
+ }
841
+ return parentEl.find(selector);
842
+ };
843
+ }
844
+
845
+ return region;
846
+ }
847
+
848
+ });
849
+
850
+ // Region Instance Methods
851
+ // -----------------------
852
+
853
+ _.extend(Marionette.Region.prototype, Backbone.Events, {
854
+
855
+ // Displays a backbone view instance inside of the region.
856
+ // Handles calling the `render` method for you. Reads content
857
+ // directly from the `el` attribute. Also calls an optional
858
+ // `onShow` and `close` method on your view, just after showing
859
+ // or just before closing the view, respectively.
860
+ show: function(view){
861
+
862
+ this.ensureEl();
863
+
864
+ var isViewClosed = view.isClosed || _.isUndefined(view.$el);
865
+
866
+ var isDifferentView = view !== this.currentView;
867
+
868
+ if (isDifferentView) {
869
+ this.close();
870
+ }
871
+
872
+ view.render();
873
+
874
+ if (isDifferentView || isViewClosed) {
875
+ this.open(view);
876
+ }
877
+
878
+ this.currentView = view;
879
+
880
+ Marionette.triggerMethod.call(this, "show", view);
881
+ Marionette.triggerMethod.call(view, "show");
882
+ },
883
+
884
+ ensureEl: function(){
885
+ if (!this.$el || this.$el.length === 0){
886
+ this.$el = this.getEl(this.el);
887
+ }
888
+ },
889
+
890
+ // Override this method to change how the region finds the
891
+ // DOM element that it manages. Return a jQuery selector object.
892
+ getEl: function(selector){
893
+ return Marionette.$(selector);
894
+ },
895
+
896
+ // Override this method to change how the new view is
897
+ // appended to the `$el` that the region is managing
898
+ open: function(view){
899
+ this.$el.empty().append(view.el);
900
+ },
901
+
902
+ // Close the current view, if there is one. If there is no
903
+ // current view, it does nothing and returns immediately.
904
+ close: function(){
905
+ var view = this.currentView;
906
+ if (!view || view.isClosed){ return; }
907
+
908
+ // call 'close' or 'remove', depending on which is found
909
+ if (view.close) { view.close(); }
910
+ else if (view.remove) { view.remove(); }
911
+
912
+ Marionette.triggerMethod.call(this, "close");
913
+
914
+ delete this.currentView;
915
+ },
916
+
917
+ // Attach an existing view to the region. This
918
+ // will not call `render` or `onShow` for the new view,
919
+ // and will not replace the current HTML for the `el`
920
+ // of the region.
921
+ attachView: function(view){
922
+ this.currentView = view;
923
+ },
924
+
925
+ // Reset the region by closing any existing view and
926
+ // clearing out the cached `$el`. The next time a view
927
+ // is shown via this region, the region will re-query the
928
+ // DOM for the region's `el`.
929
+ reset: function(){
930
+ this.close();
931
+ delete this.$el;
932
+ }
933
+ });
934
+
935
+ // Copy the `extend` function used by Backbone's classes
936
+ Marionette.Region.extend = Marionette.extend;
937
+
938
+ // Marionette.RegionManager
939
+ // ------------------------
940
+ //
941
+ // Manage one or more related `Marionette.Region` objects.
942
+ Marionette.RegionManager = (function(Marionette){
943
+
944
+ var RegionManager = Marionette.Controller.extend({
945
+ constructor: function(options){
946
+ this._regions = {};
947
+ Marionette.Controller.prototype.constructor.call(this, options);
948
+ },
949
+
950
+ // Add multiple regions using an object literal, where
951
+ // each key becomes the region name, and each value is
952
+ // the region definition.
953
+ addRegions: function(regionDefinitions, defaults){
954
+ var regions = {};
955
+
956
+ _.each(regionDefinitions, function(definition, name){
957
+ if (typeof definition === "string"){
958
+ definition = { selector: definition };
959
+ }
960
+
961
+ if (definition.selector){
962
+ definition = _.defaults({}, definition, defaults);
963
+ }
964
+
965
+ var region = this.addRegion(name, definition);
966
+ regions[name] = region;
967
+ }, this);
968
+
969
+ return regions;
970
+ },
971
+
972
+ // Add an individual region to the region manager,
973
+ // and return the region instance
974
+ addRegion: function(name, definition){
975
+ var region;
976
+
977
+ var isObject = _.isObject(definition);
978
+ var isString = _.isString(definition);
979
+ var hasSelector = !!definition.selector;
980
+
981
+ if (isString || (isObject && hasSelector)){
982
+ region = Marionette.Region.buildRegion(definition, Marionette.Region);
983
+ } else if (_.isFunction(definition)){
984
+ region = Marionette.Region.buildRegion(definition, Marionette.Region);
985
+ } else {
986
+ region = definition;
987
+ }
988
+
989
+ this._store(name, region);
990
+ this.triggerMethod("region:add", name, region);
991
+ return region;
992
+ },
993
+
994
+ // Get a region by name
995
+ get: function(name){
996
+ return this._regions[name];
997
+ },
998
+
999
+ // Remove a region by name
1000
+ removeRegion: function(name){
1001
+ var region = this._regions[name];
1002
+ this._remove(name, region);
1003
+ },
1004
+
1005
+ // Close all regions in the region manager, and
1006
+ // remove them
1007
+ removeRegions: function(){
1008
+ _.each(this._regions, function(region, name){
1009
+ this._remove(name, region);
1010
+ }, this);
1011
+ },
1012
+
1013
+ // Close all regions in the region manager, but
1014
+ // leave them attached
1015
+ closeRegions: function(){
1016
+ _.each(this._regions, function(region, name){
1017
+ region.close();
1018
+ }, this);
1019
+ },
1020
+
1021
+ // Close all regions and shut down the region
1022
+ // manager entirely
1023
+ close: function(){
1024
+ this.removeRegions();
1025
+ var args = Array.prototype.slice.call(arguments);
1026
+ Marionette.Controller.prototype.close.apply(this, args);
1027
+ },
1028
+
1029
+ // internal method to store regions
1030
+ _store: function(name, region){
1031
+ this._regions[name] = region;
1032
+ this._setLength();
1033
+ },
1034
+
1035
+ // internal method to remove a region
1036
+ _remove: function(name, region){
1037
+ region.close();
1038
+ delete this._regions[name];
1039
+ this._setLength();
1040
+ this.triggerMethod("region:remove", name, region);
1041
+ },
1042
+
1043
+ // set the number of regions current held
1044
+ _setLength: function(){
1045
+ this.length = _.size(this._regions);
1046
+ }
1047
+
1048
+ });
1049
+
1050
+ // Borrowing this code from Backbone.Collection:
1051
+ // http://backbonejs.org/docs/backbone.html#section-106
1052
+ //
1053
+ // Mix in methods from Underscore, for iteration, and other
1054
+ // collection related features.
1055
+ var methods = ['forEach', 'each', 'map', 'find', 'detect', 'filter',
1056
+ 'select', 'reject', 'every', 'all', 'some', 'any', 'include',
1057
+ 'contains', 'invoke', 'toArray', 'first', 'initial', 'rest',
1058
+ 'last', 'without', 'isEmpty', 'pluck'];
1059
+
1060
+ _.each(methods, function(method) {
1061
+ RegionManager.prototype[method] = function() {
1062
+ var regions = _.values(this._regions);
1063
+ var args = [regions].concat(_.toArray(arguments));
1064
+ return _[method].apply(_, args);
1065
+ };
1066
+ });
1067
+
1068
+ return RegionManager;
1069
+ })(Marionette);
1070
+
1071
+
1072
+ // Template Cache
1073
+ // --------------
1074
+
1075
+ // Manage templates stored in `<script>` blocks,
1076
+ // caching them for faster access.
1077
+ Marionette.TemplateCache = function(templateId){
1078
+ this.templateId = templateId;
1079
+ };
1080
+
1081
+ // TemplateCache object-level methods. Manage the template
1082
+ // caches from these method calls instead of creating
1083
+ // your own TemplateCache instances
1084
+ _.extend(Marionette.TemplateCache, {
1085
+ templateCaches: {},
1086
+
1087
+ // Get the specified template by id. Either
1088
+ // retrieves the cached version, or loads it
1089
+ // from the DOM.
1090
+ get: function(templateId){
1091
+ var cachedTemplate = this.templateCaches[templateId];
1092
+
1093
+ if (!cachedTemplate){
1094
+ cachedTemplate = new Marionette.TemplateCache(templateId);
1095
+ this.templateCaches[templateId] = cachedTemplate;
1096
+ }
1097
+
1098
+ return cachedTemplate.load();
1099
+ },
1100
+
1101
+ // Clear templates from the cache. If no arguments
1102
+ // are specified, clears all templates:
1103
+ // `clear()`
1104
+ //
1105
+ // If arguments are specified, clears each of the
1106
+ // specified templates from the cache:
1107
+ // `clear("#t1", "#t2", "...")`
1108
+ clear: function(){
1109
+ var i;
1110
+ var args = slice(arguments);
1111
+ var length = args.length;
1112
+
1113
+ if (length > 0){
1114
+ for(i=0; i<length; i++){
1115
+ delete this.templateCaches[args[i]];
1116
+ }
1117
+ } else {
1118
+ this.templateCaches = {};
1119
+ }
1120
+ }
1121
+ });
1122
+
1123
+ // TemplateCache instance methods, allowing each
1124
+ // template cache object to manage its own state
1125
+ // and know whether or not it has been loaded
1126
+ _.extend(Marionette.TemplateCache.prototype, {
1127
+
1128
+ // Internal method to load the template
1129
+ load: function(){
1130
+ // Guard clause to prevent loading this template more than once
1131
+ if (this.compiledTemplate){
1132
+ return this.compiledTemplate;
1133
+ }
1134
+
1135
+ // Load the template and compile it
1136
+ var template = this.loadTemplate(this.templateId);
1137
+ this.compiledTemplate = this.compileTemplate(template);
1138
+
1139
+ return this.compiledTemplate;
1140
+ },
1141
+
1142
+ // Load a template from the DOM, by default. Override
1143
+ // this method to provide your own template retrieval
1144
+ // For asynchronous loading with AMD/RequireJS, consider
1145
+ // using a template-loader plugin as described here:
1146
+ // https://github.com/marionettejs/backbone.marionette/wiki/Using-marionette-with-requirejs
1147
+ loadTemplate: function(templateId){
1148
+ var template = Marionette.$(templateId).html();
1149
+
1150
+ if (!template || template.length === 0){
1151
+ throwError("Could not find template: '" + templateId + "'", "NoTemplateError");
1152
+ }
1153
+
1154
+ return template;
1155
+ },
1156
+
1157
+ // Pre-compile the template before caching it. Override
1158
+ // this method if you do not need to pre-compile a template
1159
+ // (JST / RequireJS for example) or if you want to change
1160
+ // the template engine used (Handebars, etc).
1161
+ compileTemplate: function(rawTemplate){
1162
+ return _.template(rawTemplate);
1163
+ }
1164
+ });
1165
+
1166
+
1167
+ // Renderer
1168
+ // --------
1169
+
1170
+ // Render a template with data by passing in the template
1171
+ // selector and the data to render.
1172
+ Marionette.Renderer = {
1173
+
1174
+ // Render a template with data. The `template` parameter is
1175
+ // passed to the `TemplateCache` object to retrieve the
1176
+ // template function. Override this method to provide your own
1177
+ // custom rendering and template handling for all of Marionette.
1178
+ render: function(template, data){
1179
+
1180
+ if (!template) {
1181
+ var error = new Error("Cannot render the template since it's false, null or undefined.");
1182
+ error.name = "TemplateNotFoundError";
1183
+ throw error;
1184
+ }
1185
+
1186
+ var templateFunc;
1187
+ if (typeof template === "function"){
1188
+ templateFunc = template;
1189
+ } else {
1190
+ templateFunc = Marionette.TemplateCache.get(template);
1191
+ }
1192
+
1193
+ return templateFunc(data);
1194
+ }
1195
+ };
1196
+
1197
+
1198
+
1199
+ // Marionette.View
1200
+ // ---------------
1201
+
1202
+ // The core view type that other Marionette views extend from.
1203
+ Marionette.View = Backbone.View.extend({
1204
+
1205
+ constructor: function(options){
1206
+ _.bindAll(this, "render");
1207
+
1208
+ var args = Array.prototype.slice.apply(arguments);
1209
+
1210
+ // this exposes view options to the view initializer
1211
+ // this is a backfill since backbone removed the assignment
1212
+ // of this.options
1213
+ // at some point however this may be removed
1214
+ this.options = options || {};
1215
+
1216
+ // parses out the @ui DSL for events
1217
+ this.events = this.normalizeUIKeys(_.result(this, 'events'));
1218
+ Backbone.View.prototype.constructor.apply(this, args);
1219
+
1220
+ Marionette.MonitorDOMRefresh(this);
1221
+ this.listenTo(this, "show", this.onShowCalled, this);
1222
+ },
1223
+
1224
+ // import the "triggerMethod" to trigger events with corresponding
1225
+ // methods if the method exists
1226
+ triggerMethod: Marionette.triggerMethod,
1227
+
1228
+ // Get the template for this view
1229
+ // instance. You can set a `template` attribute in the view
1230
+ // definition or pass a `template: "whatever"` parameter in
1231
+ // to the constructor options.
1232
+ getTemplate: function(){
1233
+ return Marionette.getOption(this, "template");
1234
+ },
1235
+
1236
+ // Mix in template helper methods. Looks for a
1237
+ // `templateHelpers` attribute, which can either be an
1238
+ // object literal, or a function that returns an object
1239
+ // literal. All methods and attributes from this object
1240
+ // are copies to the object passed in.
1241
+ mixinTemplateHelpers: function(target){
1242
+ target = target || {};
1243
+ var templateHelpers = Marionette.getOption(this, "templateHelpers");
1244
+ if (_.isFunction(templateHelpers)){
1245
+ templateHelpers = templateHelpers.call(this);
1246
+ }
1247
+ return _.extend(target, templateHelpers);
1248
+ },
1249
+
1250
+ // allows for the use of the @ui. syntax within
1251
+ // a given key for triggers and events
1252
+ // swaps the @ui with the associated selector
1253
+ normalizeUIKeys: function(hash) {
1254
+ if (typeof(hash) === "undefined") {
1255
+ return;
1256
+ }
1257
+
1258
+ _.each(_.keys(hash), function(v) {
1259
+ var split = v.split("@ui.");
1260
+ if (split.length === 2) {
1261
+ hash[split[0]+this.ui[split[1]]] = hash[v];
1262
+ delete hash[v];
1263
+ }
1264
+ }, this);
1265
+
1266
+ return hash;
1267
+ },
1268
+
1269
+ // Configure `triggers` to forward DOM events to view
1270
+ // events. `triggers: {"click .foo": "do:foo"}`
1271
+ configureTriggers: function(){
1272
+ if (!this.triggers) { return; }
1273
+
1274
+ var triggerEvents = {};
1275
+
1276
+ // Allow `triggers` to be configured as a function
1277
+ var triggers = this.normalizeUIKeys(_.result(this, "triggers"));
1278
+
1279
+ // Configure the triggers, prevent default
1280
+ // action and stop propagation of DOM events
1281
+ _.each(triggers, function(value, key){
1282
+
1283
+ var hasOptions = _.isObject(value);
1284
+ var eventName = hasOptions ? value.event : value;
1285
+
1286
+ // build the event handler function for the DOM event
1287
+ triggerEvents[key] = function(e){
1288
+
1289
+ // stop the event in its tracks
1290
+ if (e) {
1291
+ var prevent = e.preventDefault;
1292
+ var stop = e.stopPropagation;
1293
+
1294
+ var shouldPrevent = hasOptions ? value.preventDefault : prevent;
1295
+ var shouldStop = hasOptions ? value.stopPropagation : stop;
1296
+
1297
+ if (shouldPrevent && prevent) { prevent.apply(e); }
1298
+ if (shouldStop && stop) { stop.apply(e); }
1299
+ }
1300
+
1301
+ // build the args for the event
1302
+ var args = {
1303
+ view: this,
1304
+ model: this.model,
1305
+ collection: this.collection
1306
+ };
1307
+
1308
+ // trigger the event
1309
+ this.triggerMethod(eventName, args);
1310
+ };
1311
+
1312
+ }, this);
1313
+
1314
+ return triggerEvents;
1315
+ },
1316
+
1317
+ // Overriding Backbone.View's delegateEvents to handle
1318
+ // the `triggers`, `modelEvents`, and `collectionEvents` configuration
1319
+ delegateEvents: function(events){
1320
+ this._delegateDOMEvents(events);
1321
+ Marionette.bindEntityEvents(this, this.model, Marionette.getOption(this, "modelEvents"));
1322
+ Marionette.bindEntityEvents(this, this.collection, Marionette.getOption(this, "collectionEvents"));
1323
+ },
1324
+
1325
+ // internal method to delegate DOM events and triggers
1326
+ _delegateDOMEvents: function(events){
1327
+ events = events || this.events;
1328
+ if (_.isFunction(events)){ events = events.call(this); }
1329
+
1330
+ var combinedEvents = {};
1331
+ var triggers = this.configureTriggers();
1332
+ _.extend(combinedEvents, events, triggers);
1333
+
1334
+ Backbone.View.prototype.delegateEvents.call(this, combinedEvents);
1335
+ },
1336
+
1337
+ // Overriding Backbone.View's undelegateEvents to handle unbinding
1338
+ // the `triggers`, `modelEvents`, and `collectionEvents` config
1339
+ undelegateEvents: function(){
1340
+ var args = Array.prototype.slice.call(arguments);
1341
+ Backbone.View.prototype.undelegateEvents.apply(this, args);
1342
+
1343
+ Marionette.unbindEntityEvents(this, this.model, Marionette.getOption(this, "modelEvents"));
1344
+ Marionette.unbindEntityEvents(this, this.collection, Marionette.getOption(this, "collectionEvents"));
1345
+ },
1346
+
1347
+ // Internal method, handles the `show` event.
1348
+ onShowCalled: function(){},
1349
+
1350
+ // Default `close` implementation, for removing a view from the
1351
+ // DOM and unbinding it. Regions will call this method
1352
+ // for you. You can specify an `onClose` method in your view to
1353
+ // add custom code that is called after the view is closed.
1354
+ close: function(){
1355
+ if (this.isClosed) { return; }
1356
+
1357
+ // allow the close to be stopped by returning `false`
1358
+ // from the `onBeforeClose` method
1359
+ var shouldClose = this.triggerMethod("before:close");
1360
+ if (shouldClose === false){
1361
+ return;
1362
+ }
1363
+
1364
+ // mark as closed before doing the actual close, to
1365
+ // prevent infinite loops within "close" event handlers
1366
+ // that are trying to close other views
1367
+ this.isClosed = true;
1368
+ this.triggerMethod("close");
1369
+
1370
+ // unbind UI elements
1371
+ this.unbindUIElements();
1372
+
1373
+ // remove the view from the DOM
1374
+ this.remove();
1375
+ },
1376
+
1377
+ // This method binds the elements specified in the "ui" hash inside the view's code with
1378
+ // the associated jQuery selectors.
1379
+ bindUIElements: function(){
1380
+ if (!this.ui) { return; }
1381
+
1382
+ // store the ui hash in _uiBindings so they can be reset later
1383
+ // and so re-rendering the view will be able to find the bindings
1384
+ if (!this._uiBindings){
1385
+ this._uiBindings = this.ui;
1386
+ }
1387
+
1388
+ // get the bindings result, as a function or otherwise
1389
+ var bindings = _.result(this, "_uiBindings");
1390
+
1391
+ // empty the ui so we don't have anything to start with
1392
+ this.ui = {};
1393
+
1394
+ // bind each of the selectors
1395
+ _.each(_.keys(bindings), function(key) {
1396
+ var selector = bindings[key];
1397
+ this.ui[key] = this.$(selector);
1398
+ }, this);
1399
+ },
1400
+
1401
+ // This method unbinds the elements specified in the "ui" hash
1402
+ unbindUIElements: function(){
1403
+ if (!this.ui || !this._uiBindings){ return; }
1404
+
1405
+ // delete all of the existing ui bindings
1406
+ _.each(this.ui, function($el, name){
1407
+ delete this.ui[name];
1408
+ }, this);
1409
+
1410
+ // reset the ui element to the original bindings configuration
1411
+ this.ui = this._uiBindings;
1412
+ delete this._uiBindings;
1413
+ }
1414
+ });
1415
+
1416
+ // Item View
1417
+ // ---------
1418
+
1419
+ // A single item view implementation that contains code for rendering
1420
+ // with underscore.js templates, serializing the view's model or collection,
1421
+ // and calling several methods on extended views, such as `onRender`.
1422
+ Marionette.ItemView = Marionette.View.extend({
1423
+
1424
+ // Setting up the inheritance chain which allows changes to
1425
+ // Marionette.View.prototype.constructor which allows overriding
1426
+ constructor: function(){
1427
+ Marionette.View.prototype.constructor.apply(this, slice(arguments));
1428
+ },
1429
+
1430
+ // Serialize the model or collection for the view. If a model is
1431
+ // found, `.toJSON()` is called. If a collection is found, `.toJSON()`
1432
+ // is also called, but is used to populate an `items` array in the
1433
+ // resulting data. If both are found, defaults to the model.
1434
+ // You can override the `serializeData` method in your own view
1435
+ // definition, to provide custom serialization for your view's data.
1436
+ serializeData: function(){
1437
+ var data = {};
1438
+
1439
+ if (this.model) {
1440
+ data = this.model.toJSON();
1441
+ }
1442
+ else if (this.collection) {
1443
+ data = { items: this.collection.toJSON() };
1444
+ }
1445
+
1446
+ return data;
1447
+ },
1448
+
1449
+ // Render the view, defaulting to underscore.js templates.
1450
+ // You can override this in your view definition to provide
1451
+ // a very specific rendering for your view. In general, though,
1452
+ // you should override the `Marionette.Renderer` object to
1453
+ // change how Marionette renders views.
1454
+ render: function(){
1455
+ this.isClosed = false;
1456
+
1457
+ this.triggerMethod("before:render", this);
1458
+ this.triggerMethod("item:before:render", this);
1459
+
1460
+ var data = this.serializeData();
1461
+ data = this.mixinTemplateHelpers(data);
1462
+
1463
+ var template = this.getTemplate();
1464
+ var html = Marionette.Renderer.render(template, data);
1465
+
1466
+ this.$el.html(html);
1467
+ this.bindUIElements();
1468
+
1469
+ this.triggerMethod("render", this);
1470
+ this.triggerMethod("item:rendered", this);
1471
+
1472
+ return this;
1473
+ },
1474
+
1475
+ // Override the default close event to add a few
1476
+ // more events that are triggered.
1477
+ close: function(){
1478
+ if (this.isClosed){ return; }
1479
+
1480
+ this.triggerMethod('item:before:close');
1481
+
1482
+ Marionette.View.prototype.close.apply(this, slice(arguments));
1483
+
1484
+ this.triggerMethod('item:closed');
1485
+ }
1486
+ });
1487
+
1488
+ // Collection View
1489
+ // ---------------
1490
+
1491
+ // A view that iterates over a Backbone.Collection
1492
+ // and renders an individual ItemView for each model.
1493
+ Marionette.CollectionView = Marionette.View.extend({
1494
+ // used as the prefix for item view events
1495
+ // that are forwarded through the collectionview
1496
+ itemViewEventPrefix: "itemview",
1497
+
1498
+ // constructor
1499
+ constructor: function(options){
1500
+ this._initChildViewStorage();
1501
+
1502
+ Marionette.View.prototype.constructor.apply(this, slice(arguments));
1503
+
1504
+ this._initialEvents();
1505
+ this.initRenderBuffer();
1506
+ },
1507
+
1508
+ // Instead of inserting elements one by one into the page,
1509
+ // it's much more performant to insert elements into a document
1510
+ // fragment and then insert that document fragment into the page
1511
+ initRenderBuffer: function() {
1512
+ this.elBuffer = document.createDocumentFragment();
1513
+ },
1514
+
1515
+ startBuffering: function() {
1516
+ this.initRenderBuffer();
1517
+ this.isBuffering = true;
1518
+ },
1519
+
1520
+ endBuffering: function() {
1521
+ this.appendBuffer(this, this.elBuffer);
1522
+ this.initRenderBuffer();
1523
+ this.isBuffering = false;
1524
+ },
1525
+
1526
+ // Configured the initial events that the collection view
1527
+ // binds to. Override this method to prevent the initial
1528
+ // events, or to add your own initial events.
1529
+ _initialEvents: function(){
1530
+ if (this.collection){
1531
+ this.listenTo(this.collection, "add", this.addChildView, this);
1532
+ this.listenTo(this.collection, "remove", this.removeItemView, this);
1533
+ this.listenTo(this.collection, "reset", this.render, this);
1534
+ }
1535
+ },
1536
+
1537
+ // Handle a child item added to the collection
1538
+ addChildView: function(item, collection, options){
1539
+ this.closeEmptyView();
1540
+ var ItemView = this.getItemView(item);
1541
+ var index = this.collection.indexOf(item);
1542
+ this.addItemView(item, ItemView, index);
1543
+ },
1544
+
1545
+ // Override from `Marionette.View` to guarantee the `onShow` method
1546
+ // of child views is called.
1547
+ onShowCalled: function(){
1548
+ this.children.each(function(child){
1549
+ Marionette.triggerMethod.call(child, "show");
1550
+ });
1551
+ },
1552
+
1553
+ // Internal method to trigger the before render callbacks
1554
+ // and events
1555
+ triggerBeforeRender: function(){
1556
+ this.triggerMethod("before:render", this);
1557
+ this.triggerMethod("collection:before:render", this);
1558
+ },
1559
+
1560
+ // Internal method to trigger the rendered callbacks and
1561
+ // events
1562
+ triggerRendered: function(){
1563
+ this.triggerMethod("render", this);
1564
+ this.triggerMethod("collection:rendered", this);
1565
+ },
1566
+
1567
+ // Render the collection of items. Override this method to
1568
+ // provide your own implementation of a render function for
1569
+ // the collection view.
1570
+ render: function(){
1571
+ this.isClosed = false;
1572
+ this.triggerBeforeRender();
1573
+ this._renderChildren();
1574
+ this.triggerRendered();
1575
+ return this;
1576
+ },
1577
+
1578
+ // Internal method. Separated so that CompositeView can have
1579
+ // more control over events being triggered, around the rendering
1580
+ // process
1581
+ _renderChildren: function(){
1582
+ this.startBuffering();
1583
+
1584
+ this.closeEmptyView();
1585
+ this.closeChildren();
1586
+
1587
+ if (this.collection && this.collection.length > 0) {
1588
+ this.showCollection();
1589
+ } else {
1590
+ this.showEmptyView();
1591
+ }
1592
+
1593
+ this.endBuffering();
1594
+ },
1595
+
1596
+ // Internal method to loop through each item in the
1597
+ // collection view and show it
1598
+ showCollection: function(){
1599
+ var ItemView;
1600
+ this.collection.each(function(item, index){
1601
+ ItemView = this.getItemView(item);
1602
+ this.addItemView(item, ItemView, index);
1603
+ }, this);
1604
+ },
1605
+
1606
+ // Internal method to show an empty view in place of
1607
+ // a collection of item views, when the collection is
1608
+ // empty
1609
+ showEmptyView: function(){
1610
+ var EmptyView = this.getEmptyView();
1611
+
1612
+ if (EmptyView && !this._showingEmptyView){
1613
+ this._showingEmptyView = true;
1614
+ var model = new Backbone.Model();
1615
+ this.addItemView(model, EmptyView, 0);
1616
+ }
1617
+ },
1618
+
1619
+ // Internal method to close an existing emptyView instance
1620
+ // if one exists. Called when a collection view has been
1621
+ // rendered empty, and then an item is added to the collection.
1622
+ closeEmptyView: function(){
1623
+ if (this._showingEmptyView){
1624
+ this.closeChildren();
1625
+ delete this._showingEmptyView;
1626
+ }
1627
+ },
1628
+
1629
+ // Retrieve the empty view type
1630
+ getEmptyView: function(){
1631
+ return Marionette.getOption(this, "emptyView");
1632
+ },
1633
+
1634
+ // Retrieve the itemView type, either from `this.options.itemView`
1635
+ // or from the `itemView` in the object definition. The "options"
1636
+ // takes precedence.
1637
+ getItemView: function(item){
1638
+ var itemView = Marionette.getOption(this, "itemView");
1639
+
1640
+ if (!itemView){
1641
+ throwError("An `itemView` must be specified", "NoItemViewError");
1642
+ }
1643
+
1644
+ return itemView;
1645
+ },
1646
+
1647
+ // Render the child item's view and add it to the
1648
+ // HTML for the collection view.
1649
+ addItemView: function(item, ItemView, index){
1650
+ // get the itemViewOptions if any were specified
1651
+ var itemViewOptions = Marionette.getOption(this, "itemViewOptions");
1652
+ if (_.isFunction(itemViewOptions)){
1653
+ itemViewOptions = itemViewOptions.call(this, item, index);
1654
+ }
1655
+
1656
+ // build the view
1657
+ var view = this.buildItemView(item, ItemView, itemViewOptions);
1658
+
1659
+ // set up the child view event forwarding
1660
+ this.addChildViewEventForwarding(view);
1661
+
1662
+ // this view is about to be added
1663
+ this.triggerMethod("before:item:added", view);
1664
+
1665
+ // Store the child view itself so we can properly
1666
+ // remove and/or close it later
1667
+ this.children.add(view);
1668
+
1669
+ // Render it and show it
1670
+ this.renderItemView(view, index);
1671
+
1672
+ // call the "show" method if the collection view
1673
+ // has already been shown
1674
+ if (this._isShown){
1675
+ Marionette.triggerMethod.call(view, "show");
1676
+ }
1677
+
1678
+ // this view was added
1679
+ this.triggerMethod("after:item:added", view);
1680
+ },
1681
+
1682
+ // Set up the child view event forwarding. Uses an "itemview:"
1683
+ // prefix in front of all forwarded events.
1684
+ addChildViewEventForwarding: function(view){
1685
+ var prefix = Marionette.getOption(this, "itemViewEventPrefix");
1686
+
1687
+ // Forward all child item view events through the parent,
1688
+ // prepending "itemview:" to the event name
1689
+ this.listenTo(view, "all", function(){
1690
+ var args = slice(arguments);
1691
+ args[0] = prefix + ":" + args[0];
1692
+ args.splice(1, 0, view);
1693
+
1694
+ Marionette.triggerMethod.apply(this, args);
1695
+ }, this);
1696
+ },
1697
+
1698
+ // render the item view
1699
+ renderItemView: function(view, index) {
1700
+ view.render();
1701
+ this.appendHtml(this, view, index);
1702
+ },
1703
+
1704
+ // Build an `itemView` for every model in the collection.
1705
+ buildItemView: function(item, ItemViewType, itemViewOptions){
1706
+ var options = _.extend({model: item}, itemViewOptions);
1707
+ return new ItemViewType(options);
1708
+ },
1709
+
1710
+ // get the child view by item it holds, and remove it
1711
+ removeItemView: function(item){
1712
+ var view = this.children.findByModel(item);
1713
+ this.removeChildView(view);
1714
+ this.checkEmpty();
1715
+ },
1716
+
1717
+ // Remove the child view and close it
1718
+ removeChildView: function(view){
1719
+
1720
+ // shut down the child view properly,
1721
+ // including events that the collection has from it
1722
+ if (view){
1723
+ this.stopListening(view);
1724
+
1725
+ // call 'close' or 'remove', depending on which is found
1726
+ if (view.close) { view.close(); }
1727
+ else if (view.remove) { view.remove(); }
1728
+
1729
+ this.children.remove(view);
1730
+ }
1731
+
1732
+ this.triggerMethod("item:removed", view);
1733
+ },
1734
+
1735
+ // helper to show the empty view if the collection is empty
1736
+ checkEmpty: function() {
1737
+ // check if we're empty now, and if we are, show the
1738
+ // empty view
1739
+ if (!this.collection || this.collection.length === 0){
1740
+ this.showEmptyView();
1741
+ }
1742
+ },
1743
+
1744
+ // You might need to override this if you've overridden appendHtml
1745
+ appendBuffer: function(collectionView, buffer) {
1746
+ collectionView.$el.append(buffer);
1747
+ },
1748
+
1749
+ // Append the HTML to the collection's `el`.
1750
+ // Override this method to do something other
1751
+ // then `.append`.
1752
+ appendHtml: function(collectionView, itemView, index){
1753
+ if (collectionView.isBuffering) {
1754
+ // buffering happens on reset events and initial renders
1755
+ // in order to reduce the number of inserts into the
1756
+ // document, which are expensive.
1757
+ collectionView.elBuffer.appendChild(itemView.el);
1758
+ }
1759
+ else {
1760
+ // If we've already rendered the main collection, just
1761
+ // append the new items directly into the element.
1762
+ collectionView.$el.append(itemView.el);
1763
+ }
1764
+ },
1765
+
1766
+ // Internal method to set up the `children` object for
1767
+ // storing all of the child views
1768
+ _initChildViewStorage: function(){
1769
+ this.children = new Backbone.ChildViewContainer();
1770
+ },
1771
+
1772
+ // Handle cleanup and other closing needs for
1773
+ // the collection of views.
1774
+ close: function(){
1775
+ if (this.isClosed){ return; }
1776
+
1777
+ this.triggerMethod("collection:before:close");
1778
+ this.closeChildren();
1779
+ this.triggerMethod("collection:closed");
1780
+
1781
+ Marionette.View.prototype.close.apply(this, slice(arguments));
1782
+ },
1783
+
1784
+ // Close the child views that this collection view
1785
+ // is holding on to, if any
1786
+ closeChildren: function(){
1787
+ this.children.each(function(child){
1788
+ this.removeChildView(child);
1789
+ }, this);
1790
+ this.checkEmpty();
1791
+ }
1792
+ });
1793
+
1794
+
1795
+ // Composite View
1796
+ // --------------
1797
+
1798
+ // Used for rendering a branch-leaf, hierarchical structure.
1799
+ // Extends directly from CollectionView and also renders an
1800
+ // an item view as `modelView`, for the top leaf
1801
+ Marionette.CompositeView = Marionette.CollectionView.extend({
1802
+
1803
+ // Setting up the inheritance chain which allows changes to
1804
+ // Marionette.CollectionView.prototype.constructor which allows overriding
1805
+ constructor: function(){
1806
+ Marionette.CollectionView.prototype.constructor.apply(this, slice(arguments));
1807
+ },
1808
+
1809
+ // Configured the initial events that the composite view
1810
+ // binds to. Override this method to prevent the initial
1811
+ // events, or to add your own initial events.
1812
+ _initialEvents: function(){
1813
+
1814
+ // Bind only after composite view in rendered to avoid adding child views
1815
+ // to unexisting itemViewContainer
1816
+ this.once('render', function () {
1817
+ if (this.collection){
1818
+ this.listenTo(this.collection, "add", this.addChildView, this);
1819
+ this.listenTo(this.collection, "remove", this.removeItemView, this);
1820
+ this.listenTo(this.collection, "reset", this._renderChildren, this);
1821
+ }
1822
+ });
1823
+
1824
+ },
1825
+
1826
+ // Retrieve the `itemView` to be used when rendering each of
1827
+ // the items in the collection. The default is to return
1828
+ // `this.itemView` or Marionette.CompositeView if no `itemView`
1829
+ // has been defined
1830
+ getItemView: function(item){
1831
+ var itemView = Marionette.getOption(this, "itemView") || this.constructor;
1832
+
1833
+ if (!itemView){
1834
+ throwError("An `itemView` must be specified", "NoItemViewError");
1835
+ }
1836
+
1837
+ return itemView;
1838
+ },
1839
+
1840
+ // Serialize the collection for the view.
1841
+ // You can override the `serializeData` method in your own view
1842
+ // definition, to provide custom serialization for your view's data.
1843
+ serializeData: function(){
1844
+ var data = {};
1845
+
1846
+ if (this.model){
1847
+ data = this.model.toJSON();
1848
+ }
1849
+
1850
+ return data;
1851
+ },
1852
+
1853
+ // Renders the model once, and the collection once. Calling
1854
+ // this again will tell the model's view to re-render itself
1855
+ // but the collection will not re-render.
1856
+ render: function(){
1857
+ this.isRendered = true;
1858
+ this.isClosed = false;
1859
+ this.resetItemViewContainer();
1860
+
1861
+ this.triggerBeforeRender();
1862
+ var html = this.renderModel();
1863
+ this.$el.html(html);
1864
+ // the ui bindings is done here and not at the end of render since they
1865
+ // will not be available until after the model is rendered, but should be
1866
+ // available before the collection is rendered.
1867
+ this.bindUIElements();
1868
+ this.triggerMethod("composite:model:rendered");
1869
+
1870
+ this._renderChildren();
1871
+
1872
+ this.triggerMethod("composite:rendered");
1873
+ this.triggerRendered();
1874
+ return this;
1875
+ },
1876
+
1877
+ _renderChildren: function(){
1878
+ if (this.isRendered){
1879
+ Marionette.CollectionView.prototype._renderChildren.call(this);
1880
+ this.triggerMethod("composite:collection:rendered");
1881
+ }
1882
+ },
1883
+
1884
+ // Render an individual model, if we have one, as
1885
+ // part of a composite view (branch / leaf). For example:
1886
+ // a treeview.
1887
+ renderModel: function(){
1888
+ var data = {};
1889
+ data = this.serializeData();
1890
+ data = this.mixinTemplateHelpers(data);
1891
+
1892
+ var template = this.getTemplate();
1893
+ return Marionette.Renderer.render(template, data);
1894
+ },
1895
+
1896
+
1897
+ // You might need to override this if you've overridden appendHtml
1898
+ appendBuffer: function(compositeView, buffer) {
1899
+ var $container = this.getItemViewContainer(compositeView);
1900
+ $container.append(buffer);
1901
+ },
1902
+
1903
+ // Appends the `el` of itemView instances to the specified
1904
+ // `itemViewContainer` (a jQuery selector). Override this method to
1905
+ // provide custom logic of how the child item view instances have their
1906
+ // HTML appended to the composite view instance.
1907
+ appendHtml: function(compositeView, itemView, index){
1908
+ if (compositeView.isBuffering) {
1909
+ compositeView.elBuffer.appendChild(itemView.el);
1910
+ }
1911
+ else {
1912
+ // If we've already rendered the main collection, just
1913
+ // append the new items directly into the element.
1914
+ var $container = this.getItemViewContainer(compositeView);
1915
+ $container.append(itemView.el);
1916
+ }
1917
+ },
1918
+
1919
+
1920
+ // Internal method to ensure an `$itemViewContainer` exists, for the
1921
+ // `appendHtml` method to use.
1922
+ getItemViewContainer: function(containerView){
1923
+ if ("$itemViewContainer" in containerView){
1924
+ return containerView.$itemViewContainer;
1925
+ }
1926
+
1927
+ var container;
1928
+ var itemViewContainer = Marionette.getOption(containerView, "itemViewContainer");
1929
+ if (itemViewContainer){
1930
+
1931
+ var selector = _.isFunction(itemViewContainer) ? itemViewContainer() : itemViewContainer;
1932
+ container = containerView.$(selector);
1933
+ if (container.length <= 0) {
1934
+ throwError("The specified `itemViewContainer` was not found: " + containerView.itemViewContainer, "ItemViewContainerMissingError");
1935
+ }
1936
+
1937
+ } else {
1938
+ container = containerView.$el;
1939
+ }
1940
+
1941
+ containerView.$itemViewContainer = container;
1942
+ return container;
1943
+ },
1944
+
1945
+ // Internal method to reset the `$itemViewContainer` on render
1946
+ resetItemViewContainer: function(){
1947
+ if (this.$itemViewContainer){
1948
+ delete this.$itemViewContainer;
1949
+ }
1950
+ }
1951
+ });
1952
+
1953
+
1954
+ // Layout
1955
+ // ------
1956
+
1957
+ // Used for managing application layouts, nested layouts and
1958
+ // multiple regions within an application or sub-application.
1959
+ //
1960
+ // A specialized view type that renders an area of HTML and then
1961
+ // attaches `Region` instances to the specified `regions`.
1962
+ // Used for composite view management and sub-application areas.
1963
+ Marionette.Layout = Marionette.ItemView.extend({
1964
+ regionType: Marionette.Region,
1965
+
1966
+ // Ensure the regions are available when the `initialize` method
1967
+ // is called.
1968
+ constructor: function (options) {
1969
+ options = options || {};
1970
+
1971
+ this._firstRender = true;
1972
+ this._initializeRegions(options);
1973
+
1974
+ Marionette.ItemView.prototype.constructor.call(this, options);
1975
+ },
1976
+
1977
+ // Layout's render will use the existing region objects the
1978
+ // first time it is called. Subsequent calls will close the
1979
+ // views that the regions are showing and then reset the `el`
1980
+ // for the regions to the newly rendered DOM elements.
1981
+ render: function(){
1982
+
1983
+ if (this.isClosed){
1984
+ // a previously closed layout means we need to
1985
+ // completely re-initialize the regions
1986
+ this._initializeRegions();
1987
+ }
1988
+ if (this._firstRender) {
1989
+ // if this is the first render, don't do anything to
1990
+ // reset the regions
1991
+ this._firstRender = false;
1992
+ } else if (!this.isClosed){
1993
+ // If this is not the first render call, then we need to
1994
+ // re-initializing the `el` for each region
1995
+ this._reInitializeRegions();
1996
+ }
1997
+
1998
+ var args = Array.prototype.slice.apply(arguments);
1999
+ var result = Marionette.ItemView.prototype.render.apply(this, args);
2000
+
2001
+ return result;
2002
+ },
2003
+
2004
+ // Handle closing regions, and then close the view itself.
2005
+ close: function () {
2006
+ if (this.isClosed){ return; }
2007
+ this.regionManager.close();
2008
+ var args = Array.prototype.slice.apply(arguments);
2009
+ Marionette.ItemView.prototype.close.apply(this, args);
2010
+ },
2011
+
2012
+ // Add a single region, by name, to the layout
2013
+ addRegion: function(name, definition){
2014
+ var regions = {};
2015
+ regions[name] = definition;
2016
+ return this._buildRegions(regions)[name];
2017
+ },
2018
+
2019
+ // Add multiple regions as a {name: definition, name2: def2} object literal
2020
+ addRegions: function(regions){
2021
+ this.regions = _.extend({}, this.regions, regions);
2022
+ return this._buildRegions(regions);
2023
+ },
2024
+
2025
+ // Remove a single region from the Layout, by name
2026
+ removeRegion: function(name){
2027
+ delete this.regions[name];
2028
+ return this.regionManager.removeRegion(name);
2029
+ },
2030
+
2031
+ // internal method to build regions
2032
+ _buildRegions: function(regions){
2033
+ var that = this;
2034
+
2035
+ var defaults = {
2036
+ regionType: Marionette.getOption(this, "regionType"),
2037
+ parentEl: function(){ return that.$el; }
2038
+ };
2039
+
2040
+ return this.regionManager.addRegions(regions, defaults);
2041
+ },
2042
+
2043
+ // Internal method to initialize the regions that have been defined in a
2044
+ // `regions` attribute on this layout.
2045
+ _initializeRegions: function (options) {
2046
+ var regions;
2047
+ this._initRegionManager();
2048
+
2049
+ if (_.isFunction(this.regions)) {
2050
+ regions = this.regions(options);
2051
+ } else {
2052
+ regions = this.regions || {};
2053
+ }
2054
+
2055
+ this.addRegions(regions);
2056
+ },
2057
+
2058
+ // Internal method to re-initialize all of the regions by updating the `el` that
2059
+ // they point to
2060
+ _reInitializeRegions: function(){
2061
+ this.regionManager.closeRegions();
2062
+ this.regionManager.each(function(region){
2063
+ region.reset();
2064
+ });
2065
+ },
2066
+
2067
+ // Internal method to initialize the region manager
2068
+ // and all regions in it
2069
+ _initRegionManager: function(){
2070
+ this.regionManager = new Marionette.RegionManager();
2071
+
2072
+ this.listenTo(this.regionManager, "region:add", function(name, region){
2073
+ this[name] = region;
2074
+ this.trigger("region:add", name, region);
2075
+ });
2076
+
2077
+ this.listenTo(this.regionManager, "region:remove", function(name, region){
2078
+ delete this[name];
2079
+ this.trigger("region:remove", name, region);
2080
+ });
2081
+ }
2082
+ });
2083
+
2084
+
2085
+ // AppRouter
2086
+ // ---------
2087
+
2088
+ // Reduce the boilerplate code of handling route events
2089
+ // and then calling a single method on another object.
2090
+ // Have your routers configured to call the method on
2091
+ // your object, directly.
2092
+ //
2093
+ // Configure an AppRouter with `appRoutes`.
2094
+ //
2095
+ // App routers can only take one `controller` object.
2096
+ // It is recommended that you divide your controller
2097
+ // objects in to smaller pieces of related functionality
2098
+ // and have multiple routers / controllers, instead of
2099
+ // just one giant router and controller.
2100
+ //
2101
+ // You can also add standard routes to an AppRouter.
2102
+
2103
+ Marionette.AppRouter = Backbone.Router.extend({
2104
+
2105
+ constructor: function(options){
2106
+ Backbone.Router.prototype.constructor.apply(this, slice(arguments));
2107
+
2108
+ this.options = options || {};
2109
+
2110
+ var appRoutes = Marionette.getOption(this, "appRoutes");
2111
+ var controller = this._getController();
2112
+ this.processAppRoutes(controller, appRoutes);
2113
+ },
2114
+
2115
+ // Similar to route method on a Backbone Router but
2116
+ // method is called on the controller
2117
+ appRoute: function(route, methodName) {
2118
+ var controller = this._getController();
2119
+ this._addAppRoute(controller, route, methodName);
2120
+ },
2121
+
2122
+ // Internal method to process the `appRoutes` for the
2123
+ // router, and turn them in to routes that trigger the
2124
+ // specified method on the specified `controller`.
2125
+ processAppRoutes: function(controller, appRoutes) {
2126
+ if (!appRoutes){ return; }
2127
+
2128
+ var routeNames = _.keys(appRoutes).reverse(); // Backbone requires reverted order of routes
2129
+
2130
+ _.each(routeNames, function(route) {
2131
+ this._addAppRoute(controller, route, appRoutes[route]);
2132
+ }, this);
2133
+ },
2134
+
2135
+ _getController: function(){
2136
+ return Marionette.getOption(this, "controller");
2137
+ },
2138
+
2139
+ _addAppRoute: function(controller, route, methodName){
2140
+ var method = controller[methodName];
2141
+
2142
+ if (!method) {
2143
+ throw new Error("Method '" + methodName + "' was not found on the controller");
2144
+ }
2145
+
2146
+ this.route(route, methodName, _.bind(method, controller));
2147
+ }
2148
+ });
2149
+
2150
+
2151
+ // Application
2152
+ // -----------
2153
+
2154
+ // Contain and manage the composite application as a whole.
2155
+ // Stores and starts up `Region` objects, includes an
2156
+ // event aggregator as `app.vent`
2157
+ Marionette.Application = function(options){
2158
+ this._initRegionManager();
2159
+ this._initCallbacks = new Marionette.Callbacks();
2160
+ this.vent = new Backbone.Wreqr.EventAggregator();
2161
+ this.commands = new Backbone.Wreqr.Commands();
2162
+ this.reqres = new Backbone.Wreqr.RequestResponse();
2163
+ this.submodules = {};
2164
+
2165
+ _.extend(this, options);
2166
+
2167
+ this.triggerMethod = Marionette.triggerMethod;
2168
+ };
2169
+
2170
+ _.extend(Marionette.Application.prototype, Backbone.Events, {
2171
+ // Command execution, facilitated by Backbone.Wreqr.Commands
2172
+ execute: function(){
2173
+ var args = Array.prototype.slice.apply(arguments);
2174
+ this.commands.execute.apply(this.commands, args);
2175
+ },
2176
+
2177
+ // Request/response, facilitated by Backbone.Wreqr.RequestResponse
2178
+ request: function(){
2179
+ var args = Array.prototype.slice.apply(arguments);
2180
+ return this.reqres.request.apply(this.reqres, args);
2181
+ },
2182
+
2183
+ // Add an initializer that is either run at when the `start`
2184
+ // method is called, or run immediately if added after `start`
2185
+ // has already been called.
2186
+ addInitializer: function(initializer){
2187
+ this._initCallbacks.add(initializer);
2188
+ },
2189
+
2190
+ // kick off all of the application's processes.
2191
+ // initializes all of the regions that have been added
2192
+ // to the app, and runs all of the initializer functions
2193
+ start: function(options){
2194
+ this.triggerMethod("initialize:before", options);
2195
+ this._initCallbacks.run(options, this);
2196
+ this.triggerMethod("initialize:after", options);
2197
+
2198
+ this.triggerMethod("start", options);
2199
+ },
2200
+
2201
+ // Add regions to your app.
2202
+ // Accepts a hash of named strings or Region objects
2203
+ // addRegions({something: "#someRegion"})
2204
+ // addRegions({something: Region.extend({el: "#someRegion"}) });
2205
+ addRegions: function(regions){
2206
+ return this._regionManager.addRegions(regions);
2207
+ },
2208
+
2209
+ // Close all regions in the app, without removing them
2210
+ closeRegions: function(){
2211
+ this._regionManager.closeRegions();
2212
+ },
2213
+
2214
+ // Removes a region from your app, by name
2215
+ // Accepts the regions name
2216
+ // removeRegion('myRegion')
2217
+ removeRegion: function(region) {
2218
+ this._regionManager.removeRegion(region);
2219
+ },
2220
+
2221
+ // Provides alternative access to regions
2222
+ // Accepts the region name
2223
+ // getRegion('main')
2224
+ getRegion: function(region) {
2225
+ return this._regionManager.get(region);
2226
+ },
2227
+
2228
+ // Create a module, attached to the application
2229
+ module: function(moduleNames, moduleDefinition){
2230
+ // slice the args, and add this application object as the
2231
+ // first argument of the array
2232
+ var args = slice(arguments);
2233
+ args.unshift(this);
2234
+
2235
+ // see the Marionette.Module object for more information
2236
+ return Marionette.Module.create.apply(Marionette.Module, args);
2237
+ },
2238
+
2239
+ // Internal method to set up the region manager
2240
+ _initRegionManager: function(){
2241
+ this._regionManager = new Marionette.RegionManager();
2242
+
2243
+ this.listenTo(this._regionManager, "region:add", function(name, region){
2244
+ this[name] = region;
2245
+ });
2246
+
2247
+ this.listenTo(this._regionManager, "region:remove", function(name, region){
2248
+ delete this[name];
2249
+ });
2250
+ }
2251
+ });
2252
+
2253
+ // Copy the `extend` function used by Backbone's classes
2254
+ Marionette.Application.extend = Marionette.extend;
2255
+
2256
+ // Module
2257
+ // ------
2258
+
2259
+ // A simple module system, used to create privacy and encapsulation in
2260
+ // Marionette applications
2261
+ Marionette.Module = function(moduleName, app){
2262
+ this.moduleName = moduleName;
2263
+
2264
+ // store sub-modules
2265
+ this.submodules = {};
2266
+
2267
+ this._setupInitializersAndFinalizers();
2268
+
2269
+ // store the configuration for this module
2270
+ this.app = app;
2271
+ this.startWithParent = true;
2272
+
2273
+ this.triggerMethod = Marionette.triggerMethod;
2274
+ };
2275
+
2276
+ // Extend the Module prototype with events / listenTo, so that the module
2277
+ // can be used as an event aggregator or pub/sub.
2278
+ _.extend(Marionette.Module.prototype, Backbone.Events, {
2279
+
2280
+ // Initializer for a specific module. Initializers are run when the
2281
+ // module's `start` method is called.
2282
+ addInitializer: function(callback){
2283
+ this._initializerCallbacks.add(callback);
2284
+ },
2285
+
2286
+ // Finalizers are run when a module is stopped. They are used to teardown
2287
+ // and finalize any variables, references, events and other code that the
2288
+ // module had set up.
2289
+ addFinalizer: function(callback){
2290
+ this._finalizerCallbacks.add(callback);
2291
+ },
2292
+
2293
+ // Start the module, and run all of its initializers
2294
+ start: function(options){
2295
+ // Prevent re-starting a module that is already started
2296
+ if (this._isInitialized){ return; }
2297
+
2298
+ // start the sub-modules (depth-first hierarchy)
2299
+ _.each(this.submodules, function(mod){
2300
+ // check to see if we should start the sub-module with this parent
2301
+ if (mod.startWithParent){
2302
+ mod.start(options);
2303
+ }
2304
+ });
2305
+
2306
+ // run the callbacks to "start" the current module
2307
+ this.triggerMethod("before:start", options);
2308
+
2309
+ this._initializerCallbacks.run(options, this);
2310
+ this._isInitialized = true;
2311
+
2312
+ this.triggerMethod("start", options);
2313
+ },
2314
+
2315
+ // Stop this module by running its finalizers and then stop all of
2316
+ // the sub-modules for this module
2317
+ stop: function(){
2318
+ // if we are not initialized, don't bother finalizing
2319
+ if (!this._isInitialized){ return; }
2320
+ this._isInitialized = false;
2321
+
2322
+ Marionette.triggerMethod.call(this, "before:stop");
2323
+
2324
+ // stop the sub-modules; depth-first, to make sure the
2325
+ // sub-modules are stopped / finalized before parents
2326
+ _.each(this.submodules, function(mod){ mod.stop(); });
2327
+
2328
+ // run the finalizers
2329
+ this._finalizerCallbacks.run(undefined,this);
2330
+
2331
+ // reset the initializers and finalizers
2332
+ this._initializerCallbacks.reset();
2333
+ this._finalizerCallbacks.reset();
2334
+
2335
+ Marionette.triggerMethod.call(this, "stop");
2336
+ },
2337
+
2338
+ // Configure the module with a definition function and any custom args
2339
+ // that are to be passed in to the definition function
2340
+ addDefinition: function(moduleDefinition, customArgs){
2341
+ this._runModuleDefinition(moduleDefinition, customArgs);
2342
+ },
2343
+
2344
+ // Internal method: run the module definition function with the correct
2345
+ // arguments
2346
+ _runModuleDefinition: function(definition, customArgs){
2347
+ if (!definition){ return; }
2348
+
2349
+ // build the correct list of arguments for the module definition
2350
+ var args = _.flatten([
2351
+ this,
2352
+ this.app,
2353
+ Backbone,
2354
+ Marionette,
2355
+ Marionette.$, _,
2356
+ customArgs
2357
+ ]);
2358
+
2359
+ definition.apply(this, args);
2360
+ },
2361
+
2362
+ // Internal method: set up new copies of initializers and finalizers.
2363
+ // Calling this method will wipe out all existing initializers and
2364
+ // finalizers.
2365
+ _setupInitializersAndFinalizers: function(){
2366
+ this._initializerCallbacks = new Marionette.Callbacks();
2367
+ this._finalizerCallbacks = new Marionette.Callbacks();
2368
+ }
2369
+ });
2370
+
2371
+ // Type methods to create modules
2372
+ _.extend(Marionette.Module, {
2373
+
2374
+ // Create a module, hanging off the app parameter as the parent object.
2375
+ create: function(app, moduleNames, moduleDefinition){
2376
+ var module = app;
2377
+
2378
+ // get the custom args passed in after the module definition and
2379
+ // get rid of the module name and definition function
2380
+ var customArgs = slice(arguments);
2381
+ customArgs.splice(0, 3);
2382
+
2383
+ // split the module names and get the length
2384
+ moduleNames = moduleNames.split(".");
2385
+ var length = moduleNames.length;
2386
+
2387
+ // store the module definition for the last module in the chain
2388
+ var moduleDefinitions = [];
2389
+ moduleDefinitions[length-1] = moduleDefinition;
2390
+
2391
+ // Loop through all the parts of the module definition
2392
+ _.each(moduleNames, function(moduleName, i){
2393
+ var parentModule = module;
2394
+ module = this._getModule(parentModule, moduleName, app);
2395
+ this._addModuleDefinition(parentModule, module, moduleDefinitions[i], customArgs);
2396
+ }, this);
2397
+
2398
+ // Return the last module in the definition chain
2399
+ return module;
2400
+ },
2401
+
2402
+ _getModule: function(parentModule, moduleName, app, def, args){
2403
+ // Get an existing module of this name if we have one
2404
+ var module = parentModule[moduleName];
2405
+
2406
+ if (!module){
2407
+ // Create a new module if we don't have one
2408
+ module = new Marionette.Module(moduleName, app);
2409
+ parentModule[moduleName] = module;
2410
+ // store the module on the parent
2411
+ parentModule.submodules[moduleName] = module;
2412
+ }
2413
+
2414
+ return module;
2415
+ },
2416
+
2417
+ _addModuleDefinition: function(parentModule, module, def, args){
2418
+ var fn;
2419
+ var startWithParent;
2420
+
2421
+ if (_.isFunction(def)){
2422
+ // if a function is supplied for the module definition
2423
+ fn = def;
2424
+ startWithParent = true;
2425
+
2426
+ } else if (_.isObject(def)){
2427
+ // if an object is supplied
2428
+ fn = def.define;
2429
+ startWithParent = def.startWithParent;
2430
+
2431
+ } else {
2432
+ // if nothing is supplied
2433
+ startWithParent = true;
2434
+ }
2435
+
2436
+ // add module definition if needed
2437
+ if (fn){
2438
+ module.addDefinition(fn, args);
2439
+ }
2440
+
2441
+ // `and` the two together, ensuring a single `false` will prevent it
2442
+ // from starting with the parent
2443
+ module.startWithParent = module.startWithParent && startWithParent;
2444
+
2445
+ // setup auto-start if needed
2446
+ if (module.startWithParent && !module.startWithParentIsConfigured){
2447
+
2448
+ // only configure this once
2449
+ module.startWithParentIsConfigured = true;
2450
+
2451
+ // add the module initializer config
2452
+ parentModule.addInitializer(function(options){
2453
+ if (module.startWithParent){
2454
+ module.start(options);
2455
+ }
2456
+ });
2457
+
2458
+ }
2459
+
2460
+ }
2461
+ });
2462
+
2463
+
2464
+
2465
+ return Marionette;
2466
+ })(this, Backbone, _);