js_stack 0.5.1 → 0.5.2

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