js_stack 0.5.7 → 0.6.0

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