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