yano-backbone-rails 2.1.0 → 2.2.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA1:
3
- metadata.gz: f2c9e4bd868b64b3e7bb4052b2ff03762433f488
4
- data.tar.gz: c582f60a3fe10a85aca465a2c0f762644dedf52a
3
+ metadata.gz: 4b5b4e2f77a8eef9485ebf79905b9d762350c0c7
4
+ data.tar.gz: 7db2da55a2cc7d57eaaae4329d1565be86de5db5
5
5
  SHA512:
6
- metadata.gz: a46b31d7d15dd356b14236341751827f703e5b99391cb01c521875bad20d51e87361b95c4199b8e9708bc83d7e4d5929a52de2c03c9e05db94bd29fbb643953f
7
- data.tar.gz: 2ed93efbfd50ce200da1949ce904e8550f1da243e01d4e38900dd1341f4f36561aaed7a375f1346d17498d41718986dee392148ec20cca5ce433b7ec9255840e
6
+ metadata.gz: cacd87ed66e1b401bdb4dba136fb48fb1ed95d1036248e1c9a099d9084d7402924dccd567c932c24e7089ff679a750e564df38389bed0763b4c80cab5b43abe4
7
+ data.tar.gz: fe776a18567ec8805968d14329f5703ee4e0968a1930fe79ff09a020a9ad9232a5ea8a9a6c823a8ec199cec5a94d642728e1b4bb213e85cdd0ca1399f05fd5b1
data/Rakefile CHANGED
@@ -94,7 +94,7 @@ namespace :update do
94
94
  end
95
95
 
96
96
  desc "Update Backbone marionette assets and dependencies"
97
- task :marionette => %w(marionette:clear marionette:babysitter marionette:radio marionette:adapters) do
97
+ task :marionette => %w(marionette:clear marionette:radio marionette:adapters) do
98
98
  Dir.chdir ASSETS_PATH do
99
99
  version = Yano::Backbone::Rails::MARIONETTE_VERSION
100
100
 
@@ -131,43 +131,17 @@ namespace :update do
131
131
 
132
132
  namespace :marionette do
133
133
  task :clear do
134
- puts "Cleaning temp folder"
135
- ["rm -rf ", "mkdir -p "].each do |cmd|
136
- puts "#{cmd} ./javascripts/marionette"
137
- puts `#{cmd} ./javascripts/marionette`
138
- end
139
- end
140
-
141
- task :babysitter do
142
134
  Dir.chdir ASSETS_PATH do
143
- version = Yano::Backbone::Rails::BABYSITTER_VERSION
144
-
145
- puts "Downloading babysitter.js"
146
- puts "curl -o ./javascripts/marionette/backbone.babysitter.js https://raw.githubusercontent.com/marionettejs/backbone.babysitter/v#{version}/lib/backbone.babysitter.js"
147
- puts `curl -o ./javascripts/marionette/backbone.babysitter.js https://raw.githubusercontent.com/marionettejs/backbone.babysitter/v#{version}/lib/backbone.babysitter.js`
148
-
149
- puts "Downloading babysitter.min.js"
150
- puts "curl -o ./javascripts/marionette/backbone.babysitter.min.js https://raw.githubusercontent.com/marionettejs/backbone.babysitter/v#{version}/lib/backbone.babysitteri.min.js"
151
- puts `curl -o ./javascripts/marionette/backbone.babysitter.min.js https://raw.githubusercontent.com/marionettejs/backbone.babysitter/v#{version}/lib/backbone.babysitter.min.js`
152
- File.open('./javascripts/marionette/backbone.babysitter.min.js', 'r') do |file|
153
- File.open("./javascripts/marionette/backbone.babysitter.min.js.erb", 'w') do |new_file|
154
- while (line = file.gets)
155
- if line =~ /sourceMappingURL/
156
- line = line.gsub("backbone.babysitter.min.js.map", "<%= asset_path 'backbone.babysitter.min.js.map' %>")
157
- end
158
-
159
- new_file.puts line
160
- end
161
- end
135
+ puts "Cleaning temp folder"
136
+ ["rm -rf ", "mkdir -p "].each do |cmd|
137
+ puts "#{cmd} ./javascripts/marionette"
138
+ puts `#{cmd} ./javascripts/marionette`
162
139
  end
163
- puts "rm -f ./javascripts/marionette/backbone.babysitter.min.js"
164
- puts `rm -f ./javascripts/marionette/backbone.babysitter.min.js`
165
-
166
- puts "Downloading babysitter.min.map"
167
- puts "mkdir -p ./source_maps"
168
- puts `mkdir -p ./source_maps`
169
- puts "curl -o ./source_maps/backbone.babysitter.min.js.map https://raw.githubusercontent.com/marionettejs/backbone.babysitter/v#{version}/lib/backbone.babysitter.min.js.map"
170
- puts `curl -o ./source_maps/backbone.babysitter.min.js.map https://raw.githubusercontent.com/marionettejs/backbone.babysitter/v#{version}/lib/backbone.babysitter.min.js.map`
140
+
141
+ puts "touch javascripts/marionette/.keep"
142
+ puts `touch javascripts/marionette/.keep`
143
+ puts "touch source_maps/.keep"
144
+ puts `touch source_maps/.keep`
171
145
  end
172
146
  end
173
147
 
@@ -207,10 +181,6 @@ namespace :update do
207
181
 
208
182
  task :adapters do
209
183
  Dir.chdir ASSETS_PATH do
210
- puts "Downloading backbone.radio.adapter.js"
211
- puts "curl -o ./javascripts/marionette/backbone.radio.adapter.js https://gist.githubusercontent.com/jmeas/7992474cdb1c5672d88b/raw/92b9be3a72571bd5761d88179efb0e9b1e40a245/radio.shim.js"
212
- puts `curl -o ./javascripts/marionette/backbone.radio.adapter.js https://gist.githubusercontent.com/jmeas/7992474cdb1c5672d88b/raw/92b9be3a72571bd5761d88179efb0e9b1e40a245/radio.shim.js`
213
-
214
184
  puts "Downloading backbone.handlebars.adapter.js"
215
185
  puts "curl -o ./javascripts/marionette/backbone.handlebars.adapter.js https://gist.githubusercontent.com/jonathanccalixto/a3223950577e9de3d3e00acc1f3ee5f2/raw/ea88cccbe3bcbb8c3a0bdb17a6683e4d58a353c4/backbone.handlebars.adapter.js"
216
186
  puts `curl -o ./javascripts/marionette/backbone.handlebars.adapter.js https://gist.githubusercontent.com/jonathanccalixto/a3223950577e9de3d3e00acc1f3ee5f2/raw/ea88cccbe3bcbb8c3a0bdb17a6683e4d58a353c4/backbone.handlebars.adapter.js`
@@ -1,12 +1,11 @@
1
1
  module Yano
2
2
  module Backbone
3
3
  module Rails
4
- VERSION = "2.1.0"
4
+ VERSION = "2.2.0"
5
5
  UNDERSCORE_VERSION = "1.8.3"
6
6
  BACKBONE_VERSION = "1.3.3"
7
- MARIONETTE_VERSION = "2.4.7"
8
- BABYSITTER_VERSION = "0.1.12"
9
- RADIO_VERSION = "1.0.5"
7
+ MARIONETTE_VERSION = "3.0.0"
8
+ RADIO_VERSION = "2.0.0"
10
9
  end
11
10
  end
12
11
  end
File without changes
@@ -1,3512 +1 @@
1
- // MarionetteJS (Backbone.Marionette)
2
- // ----------------------------------
3
- // v2.4.7
4
- //
5
- // Copyright (c)2016 Derick Bailey, Muted Solutions, LLC.
6
- // Distributed under MIT license
7
- //
8
- // http://marionettejs.com
9
-
10
- (function(root, factory) {
11
-
12
- if (typeof define === 'function' && define.amd) {
13
- define(['backbone', 'underscore', 'backbone.wreqr', 'backbone.babysitter'], function(Backbone, _) {
14
- return (root.Marionette = root.Mn = factory(root, Backbone, _));
15
- });
16
- } else if (typeof exports !== 'undefined') {
17
- var Backbone = require('backbone');
18
- var _ = require('underscore');
19
- var Wreqr = require('backbone.wreqr');
20
- var BabySitter = require('backbone.babysitter');
21
- module.exports = factory(root, Backbone, _);
22
- } else {
23
- root.Marionette = root.Mn = factory(root, root.Backbone, root._);
24
- }
25
-
26
- }(this, function(root, Backbone, _) {
27
- 'use strict';
28
-
29
- var previousMarionette = root.Marionette;
30
- var previousMn = root.Mn;
31
-
32
- var Marionette = Backbone.Marionette = {};
33
-
34
- Marionette.VERSION = '2.4.7';
35
-
36
- Marionette.noConflict = function() {
37
- root.Marionette = previousMarionette;
38
- root.Mn = previousMn;
39
- return this;
40
- };
41
-
42
- // Get the Deferred creator for later use
43
- Marionette.Deferred = Backbone.$.Deferred;
44
-
45
- Marionette.FEATURES = {
46
- };
47
-
48
- Marionette.isEnabled = function(name) {
49
- return !!Marionette.FEATURES[name];
50
- };
51
-
52
- /* jshint unused: false *//* global console */
53
-
54
- // Helpers
55
- // -------
56
-
57
- // Marionette.extend
58
- // -----------------
59
-
60
- // Borrow the Backbone `extend` method so we can use it as needed
61
- Marionette.extend = Backbone.Model.extend;
62
-
63
- // Marionette.isNodeAttached
64
- // -------------------------
65
-
66
- // Determine if `el` is a child of the document
67
- Marionette.isNodeAttached = function(el) {
68
- return Backbone.$.contains(document.documentElement, el);
69
- };
70
-
71
- // Merge `keys` from `options` onto `this`
72
- Marionette.mergeOptions = function(options, keys) {
73
- if (!options) { return; }
74
- _.extend(this, _.pick(options, keys));
75
- };
76
-
77
- // Marionette.getOption
78
- // --------------------
79
-
80
- // Retrieve an object, function or other value from a target
81
- // object or its `options`, with `options` taking precedence.
82
- Marionette.getOption = function(target, optionName) {
83
- if (!target || !optionName) { return; }
84
- if (target.options && (target.options[optionName] !== undefined)) {
85
- return target.options[optionName];
86
- } else {
87
- return target[optionName];
88
- }
89
- };
90
-
91
- // Proxy `Marionette.getOption`
92
- Marionette.proxyGetOption = function(optionName) {
93
- return Marionette.getOption(this, optionName);
94
- };
95
-
96
- // Similar to `_.result`, this is a simple helper
97
- // If a function is provided we call it with context
98
- // otherwise just return the value. If the value is
99
- // undefined return a default value
100
- Marionette._getValue = function(value, context, params) {
101
- if (_.isFunction(value)) {
102
- value = params ? value.apply(context, params) : value.call(context);
103
- }
104
- return value;
105
- };
106
-
107
- // Marionette.normalizeMethods
108
- // ----------------------
109
-
110
- // Pass in a mapping of events => functions or function names
111
- // and return a mapping of events => functions
112
- Marionette.normalizeMethods = function(hash) {
113
- return _.reduce(hash, function(normalizedHash, method, name) {
114
- if (!_.isFunction(method)) {
115
- method = this[method];
116
- }
117
- if (method) {
118
- normalizedHash[name] = method;
119
- }
120
- return normalizedHash;
121
- }, {}, this);
122
- };
123
-
124
- // utility method for parsing @ui. syntax strings
125
- // into associated selector
126
- Marionette.normalizeUIString = function(uiString, ui) {
127
- return uiString.replace(/@ui\.[a-zA-Z-_$0-9]*/g, function(r) {
128
- return ui[r.slice(4)];
129
- });
130
- };
131
-
132
- // allows for the use of the @ui. syntax within
133
- // a given key for triggers and events
134
- // swaps the @ui with the associated selector.
135
- // Returns a new, non-mutated, parsed events hash.
136
- Marionette.normalizeUIKeys = function(hash, ui) {
137
- return _.reduce(hash, function(memo, val, key) {
138
- var normalizedKey = Marionette.normalizeUIString(key, ui);
139
- memo[normalizedKey] = val;
140
- return memo;
141
- }, {});
142
- };
143
-
144
- // allows for the use of the @ui. syntax within
145
- // a given value for regions
146
- // swaps the @ui with the associated selector
147
- Marionette.normalizeUIValues = function(hash, ui, properties) {
148
- _.each(hash, function(val, key) {
149
- if (_.isString(val)) {
150
- hash[key] = Marionette.normalizeUIString(val, ui);
151
- } else if (_.isObject(val) && _.isArray(properties)) {
152
- _.extend(val, Marionette.normalizeUIValues(_.pick(val, properties), ui));
153
- /* Value is an object, and we got an array of embedded property names to normalize. */
154
- _.each(properties, function(property) {
155
- var propertyVal = val[property];
156
- if (_.isString(propertyVal)) {
157
- val[property] = Marionette.normalizeUIString(propertyVal, ui);
158
- }
159
- });
160
- }
161
- });
162
- return hash;
163
- };
164
-
165
- // Mix in methods from Underscore, for iteration, and other
166
- // collection related features.
167
- // Borrowing this code from Backbone.Collection:
168
- // http://backbonejs.org/docs/backbone.html#section-121
169
- Marionette.actAsCollection = function(object, listProperty) {
170
- var methods = ['forEach', 'each', 'map', 'find', 'detect', 'filter',
171
- 'select', 'reject', 'every', 'all', 'some', 'any', 'include',
172
- 'contains', 'invoke', 'toArray', 'first', 'initial', 'rest',
173
- 'last', 'without', 'isEmpty', 'pluck'];
174
-
175
- _.each(methods, function(method) {
176
- object[method] = function() {
177
- var list = _.values(_.result(this, listProperty));
178
- var args = [list].concat(_.toArray(arguments));
179
- return _[method].apply(_, args);
180
- };
181
- });
182
- };
183
-
184
- var deprecate = Marionette.deprecate = function(message, test) {
185
- if (_.isObject(message)) {
186
- message = (
187
- message.prev + ' is going to be removed in the future. ' +
188
- 'Please use ' + message.next + ' instead.' +
189
- (message.url ? ' See: ' + message.url : '')
190
- );
191
- }
192
-
193
- if ((test === undefined || !test) && !deprecate._cache[message]) {
194
- deprecate._warn('Deprecation warning: ' + message);
195
- deprecate._cache[message] = true;
196
- }
197
- };
198
-
199
- deprecate._console = typeof console !== 'undefined' ? console : {};
200
- deprecate._warn = function() {
201
- var warn = deprecate._console.warn || deprecate._console.log || function() {};
202
- return warn.apply(deprecate._console, arguments);
203
- };
204
- deprecate._cache = {};
205
-
206
- /* jshint maxstatements: 14, maxcomplexity: 7 */
207
-
208
- // Trigger Method
209
- // --------------
210
-
211
- Marionette._triggerMethod = (function() {
212
- // split the event name on the ":"
213
- var splitter = /(^|:)(\w)/gi;
214
-
215
- // take the event section ("section1:section2:section3")
216
- // and turn it in to uppercase name
217
- function getEventName(match, prefix, eventName) {
218
- return eventName.toUpperCase();
219
- }
220
-
221
- return function(context, event, args) {
222
- var noEventArg = arguments.length < 3;
223
- if (noEventArg) {
224
- args = event;
225
- event = args[0];
226
- }
227
-
228
- // get the method name from the event name
229
- var methodName = 'on' + event.replace(splitter, getEventName);
230
- var method = context[methodName];
231
- var result;
232
-
233
- // call the onMethodName if it exists
234
- if (_.isFunction(method)) {
235
- // pass all args, except the event name
236
- result = method.apply(context, noEventArg ? _.rest(args) : args);
237
- }
238
-
239
- // trigger the event, if a trigger method exists
240
- if (_.isFunction(context.trigger)) {
241
- if (noEventArg + args.length > 1) {
242
- context.trigger.apply(context, noEventArg ? args : [event].concat(_.drop(args, 0)));
243
- } else {
244
- context.trigger(event);
245
- }
246
- }
247
-
248
- return result;
249
- };
250
- })();
251
-
252
- // Trigger an event and/or a corresponding method name. Examples:
253
- //
254
- // `this.triggerMethod("foo")` will trigger the "foo" event and
255
- // call the "onFoo" method.
256
- //
257
- // `this.triggerMethod("foo:bar")` will trigger the "foo:bar" event and
258
- // call the "onFooBar" method.
259
- Marionette.triggerMethod = function(event) {
260
- return Marionette._triggerMethod(this, arguments);
261
- };
262
-
263
- // triggerMethodOn invokes triggerMethod on a specific context
264
- //
265
- // e.g. `Marionette.triggerMethodOn(view, 'show')`
266
- // will trigger a "show" event or invoke onShow the view.
267
- Marionette.triggerMethodOn = function(context) {
268
- var fnc = _.isFunction(context.triggerMethod) ?
269
- context.triggerMethod :
270
- Marionette.triggerMethod;
271
-
272
- return fnc.apply(context, _.rest(arguments));
273
- };
274
-
275
- // DOM Refresh
276
- // -----------
277
-
278
- // Monitor a view's state, and after it has been rendered and shown
279
- // in the DOM, trigger a "dom:refresh" event every time it is
280
- // re-rendered.
281
-
282
- Marionette.MonitorDOMRefresh = function(view) {
283
- if (view._isDomRefreshMonitored) { return; }
284
- view._isDomRefreshMonitored = true;
285
-
286
- // track when the view has been shown in the DOM,
287
- // using a Marionette.Region (or by other means of triggering "show")
288
- function handleShow() {
289
- view._isShown = true;
290
- triggerDOMRefresh();
291
- }
292
-
293
- // track when the view has been rendered
294
- function handleRender() {
295
- view._isRendered = true;
296
- triggerDOMRefresh();
297
- }
298
-
299
- // Trigger the "dom:refresh" event and corresponding "onDomRefresh" method
300
- function triggerDOMRefresh() {
301
- if (view._isShown && view._isRendered && Marionette.isNodeAttached(view.el)) {
302
- Marionette.triggerMethodOn(view, 'dom:refresh', view);
303
- }
304
- }
305
-
306
- view.on({
307
- show: handleShow,
308
- render: handleRender
309
- });
310
- };
311
-
312
- /* jshint maxparams: 5 */
313
-
314
- // Bind Entity Events & Unbind Entity Events
315
- // -----------------------------------------
316
- //
317
- // These methods are used to bind/unbind a backbone "entity" (e.g. collection/model)
318
- // to methods on a target object.
319
- //
320
- // The first parameter, `target`, must have the Backbone.Events module mixed in.
321
- //
322
- // The second parameter is the `entity` (Backbone.Model, Backbone.Collection or
323
- // any object that has Backbone.Events mixed in) to bind the events from.
324
- //
325
- // The third parameter is a hash of { "event:name": "eventHandler" }
326
- // configuration. Multiple handlers can be separated by a space. A
327
- // function can be supplied instead of a string handler name.
328
-
329
- (function(Marionette) {
330
- 'use strict';
331
-
332
- // Bind the event to handlers specified as a string of
333
- // handler names on the target object
334
- function bindFromStrings(target, entity, evt, methods) {
335
- var methodNames = methods.split(/\s+/);
336
-
337
- _.each(methodNames, function(methodName) {
338
-
339
- var method = target[methodName];
340
- if (!method) {
341
- throw new Marionette.Error('Method "' + methodName +
342
- '" was configured as an event handler, but does not exist.');
343
- }
344
-
345
- target.listenTo(entity, evt, method);
346
- });
347
- }
348
-
349
- // Bind the event to a supplied callback function
350
- function bindToFunction(target, entity, evt, method) {
351
- target.listenTo(entity, evt, method);
352
- }
353
-
354
- // Bind the event to handlers specified as a string of
355
- // handler names on the target object
356
- function unbindFromStrings(target, entity, evt, methods) {
357
- var methodNames = methods.split(/\s+/);
358
-
359
- _.each(methodNames, function(methodName) {
360
- var method = target[methodName];
361
- target.stopListening(entity, evt, method);
362
- });
363
- }
364
-
365
- // Bind the event to a supplied callback function
366
- function unbindToFunction(target, entity, evt, method) {
367
- target.stopListening(entity, evt, method);
368
- }
369
-
370
- // generic looping function
371
- function iterateEvents(target, entity, bindings, functionCallback, stringCallback) {
372
- if (!entity || !bindings) { return; }
373
-
374
- // type-check bindings
375
- if (!_.isObject(bindings)) {
376
- throw new Marionette.Error({
377
- message: 'Bindings must be an object or function.',
378
- url: 'marionette.functions.html#marionettebindentityevents'
379
- });
380
- }
381
-
382
- // allow the bindings to be a function
383
- bindings = Marionette._getValue(bindings, target);
384
-
385
- // iterate the bindings and bind them
386
- _.each(bindings, function(methods, evt) {
387
-
388
- // allow for a function as the handler,
389
- // or a list of event names as a string
390
- if (_.isFunction(methods)) {
391
- functionCallback(target, entity, evt, methods);
392
- } else {
393
- stringCallback(target, entity, evt, methods);
394
- }
395
-
396
- });
397
- }
398
-
399
- // Export Public API
400
- Marionette.bindEntityEvents = function(target, entity, bindings) {
401
- iterateEvents(target, entity, bindings, bindToFunction, bindFromStrings);
402
- };
403
-
404
- Marionette.unbindEntityEvents = function(target, entity, bindings) {
405
- iterateEvents(target, entity, bindings, unbindToFunction, unbindFromStrings);
406
- };
407
-
408
- // Proxy `bindEntityEvents`
409
- Marionette.proxyBindEntityEvents = function(entity, bindings) {
410
- return Marionette.bindEntityEvents(this, entity, bindings);
411
- };
412
-
413
- // Proxy `unbindEntityEvents`
414
- Marionette.proxyUnbindEntityEvents = function(entity, bindings) {
415
- return Marionette.unbindEntityEvents(this, entity, bindings);
416
- };
417
- })(Marionette);
418
-
419
-
420
- // Error
421
- // -----
422
-
423
- var errorProps = ['description', 'fileName', 'lineNumber', 'name', 'message', 'number'];
424
-
425
- Marionette.Error = Marionette.extend.call(Error, {
426
- urlRoot: 'http://marionettejs.com/docs/v' + Marionette.VERSION + '/',
427
-
428
- constructor: function(message, options) {
429
- if (_.isObject(message)) {
430
- options = message;
431
- message = options.message;
432
- } else if (!options) {
433
- options = {};
434
- }
435
-
436
- var error = Error.call(this, message);
437
- _.extend(this, _.pick(error, errorProps), _.pick(options, errorProps));
438
-
439
- this.captureStackTrace();
440
-
441
- if (options.url) {
442
- this.url = this.urlRoot + options.url;
443
- }
444
- },
445
-
446
- captureStackTrace: function() {
447
- if (Error.captureStackTrace) {
448
- Error.captureStackTrace(this, Marionette.Error);
449
- }
450
- },
451
-
452
- toString: function() {
453
- return this.name + ': ' + this.message + (this.url ? ' See: ' + this.url : '');
454
- }
455
- });
456
-
457
- Marionette.Error.extend = Marionette.extend;
458
-
459
- // Callbacks
460
- // ---------
461
-
462
- // A simple way of managing a collection of callbacks
463
- // and executing them at a later point in time, using jQuery's
464
- // `Deferred` object.
465
- Marionette.Callbacks = function() {
466
- this._deferred = Marionette.Deferred();
467
- this._callbacks = [];
468
- };
469
-
470
- _.extend(Marionette.Callbacks.prototype, {
471
-
472
- // Add a callback to be executed. Callbacks added here are
473
- // guaranteed to execute, even if they are added after the
474
- // `run` method is called.
475
- add: function(callback, contextOverride) {
476
- var promise = _.result(this._deferred, 'promise');
477
-
478
- this._callbacks.push({cb: callback, ctx: contextOverride});
479
-
480
- promise.then(function(args) {
481
- if (contextOverride) { args.context = contextOverride; }
482
- callback.call(args.context, args.options);
483
- });
484
- },
485
-
486
- // Run all registered callbacks with the context specified.
487
- // Additional callbacks can be added after this has been run
488
- // and they will still be executed.
489
- run: function(options, context) {
490
- this._deferred.resolve({
491
- options: options,
492
- context: context
493
- });
494
- },
495
-
496
- // Resets the list of callbacks to be run, allowing the same list
497
- // to be run multiple times - whenever the `run` method is called.
498
- reset: function() {
499
- var callbacks = this._callbacks;
500
- this._deferred = Marionette.Deferred();
501
- this._callbacks = [];
502
-
503
- _.each(callbacks, function(cb) {
504
- this.add(cb.cb, cb.ctx);
505
- }, this);
506
- }
507
- });
508
-
509
- // Controller
510
- // ----------
511
-
512
- // A multi-purpose object to use as a controller for
513
- // modules and routers, and as a mediator for workflow
514
- // and coordination of other objects, views, and more.
515
- Marionette.Controller = function(options) {
516
- this.options = options || {};
517
-
518
- if (_.isFunction(this.initialize)) {
519
- this.initialize(this.options);
520
- }
521
- };
522
-
523
- Marionette.Controller.extend = Marionette.extend;
524
-
525
- // Controller Methods
526
- // --------------
527
-
528
- // Ensure it can trigger events with Backbone.Events
529
- _.extend(Marionette.Controller.prototype, Backbone.Events, {
530
- destroy: function() {
531
- Marionette._triggerMethod(this, 'before:destroy', arguments);
532
- Marionette._triggerMethod(this, 'destroy', arguments);
533
-
534
- this.stopListening();
535
- this.off();
536
- return this;
537
- },
538
-
539
- // import the `triggerMethod` to trigger events with corresponding
540
- // methods if the method exists
541
- triggerMethod: Marionette.triggerMethod,
542
-
543
- // A handy way to merge options onto the instance
544
- mergeOptions: Marionette.mergeOptions,
545
-
546
- // Proxy `getOption` to enable getting options from this or this.options by name.
547
- getOption: Marionette.proxyGetOption
548
-
549
- });
550
-
551
- // Object
552
- // ------
553
-
554
- // A Base Class that other Classes should descend from.
555
- // Object borrows many conventions and utilities from Backbone.
556
- Marionette.Object = function(options) {
557
- this.options = _.extend({}, _.result(this, 'options'), options);
558
-
559
- this.initialize.apply(this, arguments);
560
- };
561
-
562
- Marionette.Object.extend = Marionette.extend;
563
-
564
- // Object Methods
565
- // --------------
566
-
567
- // Ensure it can trigger events with Backbone.Events
568
- _.extend(Marionette.Object.prototype, Backbone.Events, {
569
-
570
- //this is a noop method intended to be overridden by classes that extend from this base
571
- initialize: function() {},
572
-
573
- destroy: function(options) {
574
- options = options || {};
575
-
576
- this.triggerMethod('before:destroy', options);
577
- this.triggerMethod('destroy', options);
578
- this.stopListening();
579
-
580
- return this;
581
- },
582
-
583
- // Import the `triggerMethod` to trigger events with corresponding
584
- // methods if the method exists
585
- triggerMethod: Marionette.triggerMethod,
586
-
587
- // A handy way to merge options onto the instance
588
- mergeOptions: Marionette.mergeOptions,
589
-
590
- // Proxy `getOption` to enable getting options from this or this.options by name.
591
- getOption: Marionette.proxyGetOption,
592
-
593
- // Proxy `bindEntityEvents` to enable binding view's events from another entity.
594
- bindEntityEvents: Marionette.proxyBindEntityEvents,
595
-
596
- // Proxy `unbindEntityEvents` to enable unbinding view's events from another entity.
597
- unbindEntityEvents: Marionette.proxyUnbindEntityEvents
598
- });
599
-
600
- /* jshint maxcomplexity: 16, maxstatements: 45, maxlen: 120 */
601
-
602
- // Region
603
- // ------
604
-
605
- // Manage the visual regions of your composite application. See
606
- // http://lostechies.com/derickbailey/2011/12/12/composite-js-apps-regions-and-region-managers/
607
-
608
- Marionette.Region = Marionette.Object.extend({
609
- constructor: function(options) {
610
-
611
- // set options temporarily so that we can get `el`.
612
- // options will be overriden by Object.constructor
613
- this.options = options || {};
614
- this.el = this.getOption('el');
615
-
616
- // Handle when this.el is passed in as a $ wrapped element.
617
- this.el = this.el instanceof Backbone.$ ? this.el[0] : this.el;
618
-
619
- if (!this.el) {
620
- throw new Marionette.Error({
621
- name: 'NoElError',
622
- message: 'An "el" must be specified for a region.'
623
- });
624
- }
625
-
626
- this.$el = this.getEl(this.el);
627
- Marionette.Object.call(this, options);
628
- },
629
-
630
- // Displays a backbone view instance inside of the region.
631
- // Handles calling the `render` method for you. Reads content
632
- // directly from the `el` attribute. Also calls an optional
633
- // `onShow` and `onDestroy` method on your view, just after showing
634
- // or just before destroying the view, respectively.
635
- // The `preventDestroy` option can be used to prevent a view from
636
- // the old view being destroyed on show.
637
- // The `forceShow` option can be used to force a view to be
638
- // re-rendered if it's already shown in the region.
639
- show: function(view, options) {
640
- if (!this._ensureElement()) {
641
- return;
642
- }
643
-
644
- this._ensureViewIsIntact(view);
645
- Marionette.MonitorDOMRefresh(view);
646
-
647
- var showOptions = options || {};
648
- var isDifferentView = view !== this.currentView;
649
- var preventDestroy = !!showOptions.preventDestroy;
650
- var forceShow = !!showOptions.forceShow;
651
-
652
- // We are only changing the view if there is a current view to change to begin with
653
- var isChangingView = !!this.currentView;
654
-
655
- // Only destroy the current view if we don't want to `preventDestroy` and if
656
- // the view given in the first argument is different than `currentView`
657
- var _shouldDestroyView = isDifferentView && !preventDestroy;
658
-
659
- // Only show the view given in the first argument if it is different than
660
- // the current view or if we want to re-show the view. Note that if
661
- // `_shouldDestroyView` is true, then `_shouldShowView` is also necessarily true.
662
- var _shouldShowView = isDifferentView || forceShow;
663
-
664
- if (isChangingView) {
665
- this.triggerMethod('before:swapOut', this.currentView, this, options);
666
- }
667
-
668
- if (this.currentView && isDifferentView) {
669
- delete this.currentView._parent;
670
- }
671
-
672
- if (_shouldDestroyView) {
673
- this.empty();
674
-
675
- // A `destroy` event is attached to the clean up manually removed views.
676
- // We need to detach this event when a new view is going to be shown as it
677
- // is no longer relevant.
678
- } else if (isChangingView && _shouldShowView) {
679
- this.currentView.off('destroy', this.empty, this);
680
- }
681
-
682
- if (_shouldShowView) {
683
-
684
- // We need to listen for if a view is destroyed
685
- // in a way other than through the region.
686
- // If this happens we need to remove the reference
687
- // to the currentView since once a view has been destroyed
688
- // we can not reuse it.
689
- view.once('destroy', this.empty, this);
690
-
691
- // make this region the view's parent,
692
- // It's important that this parent binding happens before rendering
693
- // so that any events the child may trigger during render can also be
694
- // triggered on the child's ancestor views
695
- view._parent = this;
696
- this._renderView(view);
697
-
698
- if (isChangingView) {
699
- this.triggerMethod('before:swap', view, this, options);
700
- }
701
-
702
- this.triggerMethod('before:show', view, this, options);
703
- Marionette.triggerMethodOn(view, 'before:show', view, this, options);
704
-
705
- if (isChangingView) {
706
- this.triggerMethod('swapOut', this.currentView, this, options);
707
- }
708
-
709
- // An array of views that we're about to display
710
- var attachedRegion = Marionette.isNodeAttached(this.el);
711
-
712
- // The views that we're about to attach to the document
713
- // It's important that we prevent _getNestedViews from being executed unnecessarily
714
- // as it's a potentially-slow method
715
- var displayedViews = [];
716
-
717
- var attachOptions = _.extend({
718
- triggerBeforeAttach: this.triggerBeforeAttach,
719
- triggerAttach: this.triggerAttach
720
- }, showOptions);
721
-
722
- if (attachedRegion && attachOptions.triggerBeforeAttach) {
723
- displayedViews = this._displayedViews(view);
724
- this._triggerAttach(displayedViews, 'before:');
725
- }
726
-
727
- this.attachHtml(view);
728
- this.currentView = view;
729
-
730
- if (attachedRegion && attachOptions.triggerAttach) {
731
- displayedViews = this._displayedViews(view);
732
- this._triggerAttach(displayedViews);
733
- }
734
-
735
- if (isChangingView) {
736
- this.triggerMethod('swap', view, this, options);
737
- }
738
-
739
- this.triggerMethod('show', view, this, options);
740
- Marionette.triggerMethodOn(view, 'show', view, this, options);
741
-
742
- return this;
743
- }
744
-
745
- return this;
746
- },
747
-
748
- triggerBeforeAttach: true,
749
- triggerAttach: true,
750
-
751
- _triggerAttach: function(views, prefix) {
752
- var eventName = (prefix || '') + 'attach';
753
- _.each(views, function(view) {
754
- Marionette.triggerMethodOn(view, eventName, view, this);
755
- }, this);
756
- },
757
-
758
- _displayedViews: function(view) {
759
- return _.union([view], _.result(view, '_getNestedViews') || []);
760
- },
761
-
762
- _renderView: function(view) {
763
- if (!view.supportsRenderLifecycle) {
764
- Marionette.triggerMethodOn(view, 'before:render', view);
765
- }
766
- view.render();
767
- if (!view.supportsRenderLifecycle) {
768
- Marionette.triggerMethodOn(view, 'render', view);
769
- }
770
- },
771
-
772
- _ensureElement: function() {
773
- if (!_.isObject(this.el)) {
774
- this.$el = this.getEl(this.el);
775
- this.el = this.$el[0];
776
- }
777
-
778
- if (!this.$el || this.$el.length === 0) {
779
- if (this.getOption('allowMissingEl')) {
780
- return false;
781
- } else {
782
- throw new Marionette.Error('An "el" ' + this.$el.selector + ' must exist in DOM');
783
- }
784
- }
785
- return true;
786
- },
787
-
788
- _ensureViewIsIntact: function(view) {
789
- if (!view) {
790
- throw new Marionette.Error({
791
- name: 'ViewNotValid',
792
- message: 'The view passed is undefined and therefore invalid. You must pass a view instance to show.'
793
- });
794
- }
795
-
796
- if (view.isDestroyed) {
797
- throw new Marionette.Error({
798
- name: 'ViewDestroyedError',
799
- message: 'View (cid: "' + view.cid + '") has already been destroyed and cannot be used.'
800
- });
801
- }
802
- },
803
-
804
- // Override this method to change how the region finds the DOM
805
- // element that it manages. Return a jQuery selector object scoped
806
- // to a provided parent el or the document if none exists.
807
- getEl: function(el) {
808
- return Backbone.$(el, Marionette._getValue(this.options.parentEl, this));
809
- },
810
-
811
- // Override this method to change how the new view is
812
- // appended to the `$el` that the region is managing
813
- attachHtml: function(view) {
814
- this.$el.contents().detach();
815
-
816
- this.el.appendChild(view.el);
817
- },
818
-
819
- // Destroy the current view, if there is one. If there is no
820
- // current view, it does nothing and returns immediately.
821
- empty: function(options) {
822
- var view = this.currentView;
823
-
824
- var emptyOptions = options || {};
825
- var preventDestroy = !!emptyOptions.preventDestroy;
826
- // If there is no view in the region
827
- // we should not remove anything
828
- if (!view) { return this; }
829
-
830
- view.off('destroy', this.empty, this);
831
- this.triggerMethod('before:empty', view);
832
- if (!preventDestroy) {
833
- this._destroyView();
834
- }
835
- this.triggerMethod('empty', view);
836
-
837
- // Remove region pointer to the currentView
838
- delete this.currentView;
839
-
840
- if (preventDestroy) {
841
- this.$el.contents().detach();
842
- }
843
-
844
- return this;
845
- },
846
-
847
- // call 'destroy' or 'remove', depending on which is found
848
- // on the view (if showing a raw Backbone view or a Marionette View)
849
- _destroyView: function() {
850
- var view = this.currentView;
851
- if (view.isDestroyed) { return; }
852
-
853
- if (!view.supportsDestroyLifecycle) {
854
- Marionette.triggerMethodOn(view, 'before:destroy', view);
855
- }
856
- if (view.destroy) {
857
- view.destroy();
858
- } else {
859
- view.remove();
860
-
861
- // appending isDestroyed to raw Backbone View allows regions
862
- // to throw a ViewDestroyedError for this view
863
- view.isDestroyed = true;
864
- }
865
- if (!view.supportsDestroyLifecycle) {
866
- Marionette.triggerMethodOn(view, 'destroy', view);
867
- }
868
- },
869
-
870
- // Attach an existing view to the region. This
871
- // will not call `render` or `onShow` for the new view,
872
- // and will not replace the current HTML for the `el`
873
- // of the region.
874
- attachView: function(view) {
875
- if (this.currentView) {
876
- delete this.currentView._parent;
877
- }
878
- view._parent = this;
879
- this.currentView = view;
880
- return this;
881
- },
882
-
883
- // Checks whether a view is currently present within
884
- // the region. Returns `true` if there is and `false` if
885
- // no view is present.
886
- hasView: function() {
887
- return !!this.currentView;
888
- },
889
-
890
- // Reset the region by destroying any existing view and
891
- // clearing out the cached `$el`. The next time a view
892
- // is shown via this region, the region will re-query the
893
- // DOM for the region's `el`.
894
- reset: function() {
895
- this.empty();
896
-
897
- if (this.$el) {
898
- this.el = this.$el.selector;
899
- }
900
-
901
- delete this.$el;
902
- return this;
903
- }
904
-
905
- },
906
-
907
- // Static Methods
908
- {
909
-
910
- // Build an instance of a region by passing in a configuration object
911
- // and a default region class to use if none is specified in the config.
912
- //
913
- // The config object should either be a string as a jQuery DOM selector,
914
- // a Region class directly, or an object literal that specifies a selector,
915
- // a custom regionClass, and any options to be supplied to the region:
916
- //
917
- // ```js
918
- // {
919
- // selector: "#foo",
920
- // regionClass: MyCustomRegion,
921
- // allowMissingEl: false
922
- // }
923
- // ```
924
- //
925
- buildRegion: function(regionConfig, DefaultRegionClass) {
926
- if (_.isString(regionConfig)) {
927
- return this._buildRegionFromSelector(regionConfig, DefaultRegionClass);
928
- }
929
-
930
- if (regionConfig.selector || regionConfig.el || regionConfig.regionClass) {
931
- return this._buildRegionFromObject(regionConfig, DefaultRegionClass);
932
- }
933
-
934
- if (_.isFunction(regionConfig)) {
935
- return this._buildRegionFromRegionClass(regionConfig);
936
- }
937
-
938
- throw new Marionette.Error({
939
- message: 'Improper region configuration type.',
940
- url: 'marionette.region.html#region-configuration-types'
941
- });
942
- },
943
-
944
- // Build the region from a string selector like '#foo-region'
945
- _buildRegionFromSelector: function(selector, DefaultRegionClass) {
946
- return new DefaultRegionClass({el: selector});
947
- },
948
-
949
- // Build the region from a configuration object
950
- // ```js
951
- // { selector: '#foo', regionClass: FooRegion, allowMissingEl: false }
952
- // ```
953
- _buildRegionFromObject: function(regionConfig, DefaultRegionClass) {
954
- var RegionClass = regionConfig.regionClass || DefaultRegionClass;
955
- var options = _.omit(regionConfig, 'selector', 'regionClass');
956
-
957
- if (regionConfig.selector && !options.el) {
958
- options.el = regionConfig.selector;
959
- }
960
-
961
- return new RegionClass(options);
962
- },
963
-
964
- // Build the region directly from a given `RegionClass`
965
- _buildRegionFromRegionClass: function(RegionClass) {
966
- return new RegionClass();
967
- }
968
- });
969
-
970
- // Region Manager
971
- // --------------
972
-
973
- // Manage one or more related `Marionette.Region` objects.
974
- Marionette.RegionManager = Marionette.Controller.extend({
975
- constructor: function(options) {
976
- this._regions = {};
977
- this.length = 0;
978
-
979
- Marionette.Controller.call(this, options);
980
-
981
- this.addRegions(this.getOption('regions'));
982
- },
983
-
984
- // Add multiple regions using an object literal or a
985
- // function that returns an object literal, where
986
- // each key becomes the region name, and each value is
987
- // the region definition.
988
- addRegions: function(regionDefinitions, defaults) {
989
- regionDefinitions = Marionette._getValue(regionDefinitions, this, arguments);
990
-
991
- return _.reduce(regionDefinitions, function(regions, definition, name) {
992
- if (_.isString(definition)) {
993
- definition = {selector: definition};
994
- }
995
- if (definition.selector) {
996
- definition = _.defaults({}, definition, defaults);
997
- }
998
-
999
- regions[name] = this.addRegion(name, definition);
1000
- return regions;
1001
- }, {}, this);
1002
- },
1003
-
1004
- // Add an individual region to the region manager,
1005
- // and return the region instance
1006
- addRegion: function(name, definition) {
1007
- var region;
1008
-
1009
- if (definition instanceof Marionette.Region) {
1010
- region = definition;
1011
- } else {
1012
- region = Marionette.Region.buildRegion(definition, Marionette.Region);
1013
- }
1014
-
1015
- this.triggerMethod('before:add:region', name, region);
1016
-
1017
- region._parent = this;
1018
- this._store(name, region);
1019
-
1020
- this.triggerMethod('add:region', name, region);
1021
- return region;
1022
- },
1023
-
1024
- // Get a region by name
1025
- get: function(name) {
1026
- return this._regions[name];
1027
- },
1028
-
1029
- // Gets all the regions contained within
1030
- // the `regionManager` instance.
1031
- getRegions: function() {
1032
- return _.clone(this._regions);
1033
- },
1034
-
1035
- // Remove a region by name
1036
- removeRegion: function(name) {
1037
- var region = this._regions[name];
1038
- this._remove(name, region);
1039
-
1040
- return region;
1041
- },
1042
-
1043
- // Empty all regions in the region manager, and
1044
- // remove them
1045
- removeRegions: function() {
1046
- var regions = this.getRegions();
1047
- _.each(this._regions, function(region, name) {
1048
- this._remove(name, region);
1049
- }, this);
1050
-
1051
- return regions;
1052
- },
1053
-
1054
- // Empty all regions in the region manager, but
1055
- // leave them attached
1056
- emptyRegions: function() {
1057
- var regions = this.getRegions();
1058
- _.invoke(regions, 'empty');
1059
- return regions;
1060
- },
1061
-
1062
- // Destroy all regions and shut down the region
1063
- // manager entirely
1064
- destroy: function() {
1065
- this.removeRegions();
1066
- return Marionette.Controller.prototype.destroy.apply(this, arguments);
1067
- },
1068
-
1069
- // internal method to store regions
1070
- _store: function(name, region) {
1071
- if (!this._regions[name]) {
1072
- this.length++;
1073
- }
1074
-
1075
- this._regions[name] = region;
1076
- },
1077
-
1078
- // internal method to remove a region
1079
- _remove: function(name, region) {
1080
- this.triggerMethod('before:remove:region', name, region);
1081
- region.empty();
1082
- region.stopListening();
1083
-
1084
- delete region._parent;
1085
- delete this._regions[name];
1086
- this.length--;
1087
- this.triggerMethod('remove:region', name, region);
1088
- }
1089
- });
1090
-
1091
- Marionette.actAsCollection(Marionette.RegionManager.prototype, '_regions');
1092
-
1093
-
1094
- // Template Cache
1095
- // --------------
1096
-
1097
- // Manage templates stored in `<script>` blocks,
1098
- // caching them for faster access.
1099
- Marionette.TemplateCache = function(templateId) {
1100
- this.templateId = templateId;
1101
- };
1102
-
1103
- // TemplateCache object-level methods. Manage the template
1104
- // caches from these method calls instead of creating
1105
- // your own TemplateCache instances
1106
- _.extend(Marionette.TemplateCache, {
1107
- templateCaches: {},
1108
-
1109
- // Get the specified template by id. Either
1110
- // retrieves the cached version, or loads it
1111
- // from the DOM.
1112
- get: function(templateId, options) {
1113
- var cachedTemplate = this.templateCaches[templateId];
1114
-
1115
- if (!cachedTemplate) {
1116
- cachedTemplate = new Marionette.TemplateCache(templateId);
1117
- this.templateCaches[templateId] = cachedTemplate;
1118
- }
1119
-
1120
- return cachedTemplate.load(options);
1121
- },
1122
-
1123
- // Clear templates from the cache. If no arguments
1124
- // are specified, clears all templates:
1125
- // `clear()`
1126
- //
1127
- // If arguments are specified, clears each of the
1128
- // specified templates from the cache:
1129
- // `clear("#t1", "#t2", "...")`
1130
- clear: function() {
1131
- var i;
1132
- var args = _.toArray(arguments);
1133
- var length = args.length;
1134
-
1135
- if (length > 0) {
1136
- for (i = 0; i < length; i++) {
1137
- delete this.templateCaches[args[i]];
1138
- }
1139
- } else {
1140
- this.templateCaches = {};
1141
- }
1142
- }
1143
- });
1144
-
1145
- // TemplateCache instance methods, allowing each
1146
- // template cache object to manage its own state
1147
- // and know whether or not it has been loaded
1148
- _.extend(Marionette.TemplateCache.prototype, {
1149
-
1150
- // Internal method to load the template
1151
- load: function(options) {
1152
- // Guard clause to prevent loading this template more than once
1153
- if (this.compiledTemplate) {
1154
- return this.compiledTemplate;
1155
- }
1156
-
1157
- // Load the template and compile it
1158
- var template = this.loadTemplate(this.templateId, options);
1159
- this.compiledTemplate = this.compileTemplate(template, options);
1160
-
1161
- return this.compiledTemplate;
1162
- },
1163
-
1164
- // Load a template from the DOM, by default. Override
1165
- // this method to provide your own template retrieval
1166
- // For asynchronous loading with AMD/RequireJS, consider
1167
- // using a template-loader plugin as described here:
1168
- // https://github.com/marionettejs/backbone.marionette/wiki/Using-marionette-with-requirejs
1169
- loadTemplate: function(templateId, options) {
1170
- var $template = Backbone.$(templateId);
1171
-
1172
- if (!$template.length) {
1173
- throw new Marionette.Error({
1174
- name: 'NoTemplateError',
1175
- message: 'Could not find template: "' + templateId + '"'
1176
- });
1177
- }
1178
- return $template.html();
1179
- },
1180
-
1181
- // Pre-compile the template before caching it. Override
1182
- // this method if you do not need to pre-compile a template
1183
- // (JST / RequireJS for example) or if you want to change
1184
- // the template engine used (Handebars, etc).
1185
- compileTemplate: function(rawTemplate, options) {
1186
- return _.template(rawTemplate, options);
1187
- }
1188
- });
1189
-
1190
- // Renderer
1191
- // --------
1192
-
1193
- // Render a template with data by passing in the template
1194
- // selector and the data to render.
1195
- Marionette.Renderer = {
1196
-
1197
- // Render a template with data. The `template` parameter is
1198
- // passed to the `TemplateCache` object to retrieve the
1199
- // template function. Override this method to provide your own
1200
- // custom rendering and template handling for all of Marionette.
1201
- render: function(template, data) {
1202
- if (!template) {
1203
- throw new Marionette.Error({
1204
- name: 'TemplateNotFoundError',
1205
- message: 'Cannot render the template since its false, null or undefined.'
1206
- });
1207
- }
1208
-
1209
- var templateFunc = _.isFunction(template) ? template : Marionette.TemplateCache.get(template);
1210
-
1211
- return templateFunc(data);
1212
- }
1213
- };
1214
-
1215
-
1216
- /* jshint maxlen: 114, nonew: false */
1217
- // View
1218
- // ----
1219
-
1220
- // The core view class that other Marionette views extend from.
1221
- Marionette.View = Backbone.View.extend({
1222
- isDestroyed: false,
1223
- supportsRenderLifecycle: true,
1224
- supportsDestroyLifecycle: true,
1225
-
1226
- constructor: function(options) {
1227
- this.render = _.bind(this.render, this);
1228
-
1229
- options = Marionette._getValue(options, this);
1230
-
1231
- // this exposes view options to the view initializer
1232
- // this is a backfill since backbone removed the assignment
1233
- // of this.options
1234
- // at some point however this may be removed
1235
- this.options = _.extend({}, _.result(this, 'options'), options);
1236
-
1237
- this._behaviors = Marionette.Behaviors(this);
1238
-
1239
- Backbone.View.call(this, this.options);
1240
-
1241
- Marionette.MonitorDOMRefresh(this);
1242
- },
1243
-
1244
- // Get the template for this view
1245
- // instance. You can set a `template` attribute in the view
1246
- // definition or pass a `template: "whatever"` parameter in
1247
- // to the constructor options.
1248
- getTemplate: function() {
1249
- return this.getOption('template');
1250
- },
1251
-
1252
- // Serialize a model by returning its attributes. Clones
1253
- // the attributes to allow modification.
1254
- serializeModel: function(model) {
1255
- return model.toJSON.apply(model, _.rest(arguments));
1256
- },
1257
-
1258
- // Mix in template helper methods. Looks for a
1259
- // `templateHelpers` attribute, which can either be an
1260
- // object literal, or a function that returns an object
1261
- // literal. All methods and attributes from this object
1262
- // are copies to the object passed in.
1263
- mixinTemplateHelpers: function(target) {
1264
- target = target || {};
1265
- var templateHelpers = this.getOption('templateHelpers');
1266
- templateHelpers = Marionette._getValue(templateHelpers, this);
1267
- return _.extend(target, templateHelpers);
1268
- },
1269
-
1270
- // normalize the keys of passed hash with the views `ui` selectors.
1271
- // `{"@ui.foo": "bar"}`
1272
- normalizeUIKeys: function(hash) {
1273
- var uiBindings = _.result(this, '_uiBindings');
1274
- return Marionette.normalizeUIKeys(hash, uiBindings || _.result(this, 'ui'));
1275
- },
1276
-
1277
- // normalize the values of passed hash with the views `ui` selectors.
1278
- // `{foo: "@ui.bar"}`
1279
- normalizeUIValues: function(hash, properties) {
1280
- var ui = _.result(this, 'ui');
1281
- var uiBindings = _.result(this, '_uiBindings');
1282
- return Marionette.normalizeUIValues(hash, uiBindings || ui, properties);
1283
- },
1284
-
1285
- // Configure `triggers` to forward DOM events to view
1286
- // events. `triggers: {"click .foo": "do:foo"}`
1287
- configureTriggers: function() {
1288
- if (!this.triggers) { return; }
1289
-
1290
- // Allow `triggers` to be configured as a function
1291
- var triggers = this.normalizeUIKeys(_.result(this, 'triggers'));
1292
-
1293
- // Configure the triggers, prevent default
1294
- // action and stop propagation of DOM events
1295
- return _.reduce(triggers, function(events, value, key) {
1296
- events[key] = this._buildViewTrigger(value);
1297
- return events;
1298
- }, {}, this);
1299
- },
1300
-
1301
- // Overriding Backbone.View's delegateEvents to handle
1302
- // the `triggers`, `modelEvents`, and `collectionEvents` configuration
1303
- delegateEvents: function(events) {
1304
- this._delegateDOMEvents(events);
1305
- this.bindEntityEvents(this.model, this.getOption('modelEvents'));
1306
- this.bindEntityEvents(this.collection, this.getOption('collectionEvents'));
1307
-
1308
- _.each(this._behaviors, function(behavior) {
1309
- behavior.bindEntityEvents(this.model, behavior.getOption('modelEvents'));
1310
- behavior.bindEntityEvents(this.collection, behavior.getOption('collectionEvents'));
1311
- }, this);
1312
-
1313
- return this;
1314
- },
1315
-
1316
- // internal method to delegate DOM events and triggers
1317
- _delegateDOMEvents: function(eventsArg) {
1318
- var events = Marionette._getValue(eventsArg || this.events, this);
1319
-
1320
- // normalize ui keys
1321
- events = this.normalizeUIKeys(events);
1322
- if (_.isUndefined(eventsArg)) {this.events = events;}
1323
-
1324
- var combinedEvents = {};
1325
-
1326
- // look up if this view has behavior events
1327
- var behaviorEvents = _.result(this, 'behaviorEvents') || {};
1328
- var triggers = this.configureTriggers();
1329
- var behaviorTriggers = _.result(this, 'behaviorTriggers') || {};
1330
-
1331
- // behavior events will be overriden by view events and or triggers
1332
- _.extend(combinedEvents, behaviorEvents, events, triggers, behaviorTriggers);
1333
-
1334
- Backbone.View.prototype.delegateEvents.call(this, combinedEvents);
1335
- },
1336
-
1337
- // Overriding Backbone.View's undelegateEvents to handle unbinding
1338
- // the `triggers`, `modelEvents`, and `collectionEvents` config
1339
- undelegateEvents: function() {
1340
- Backbone.View.prototype.undelegateEvents.apply(this, arguments);
1341
-
1342
- this.unbindEntityEvents(this.model, this.getOption('modelEvents'));
1343
- this.unbindEntityEvents(this.collection, this.getOption('collectionEvents'));
1344
-
1345
- _.each(this._behaviors, function(behavior) {
1346
- behavior.unbindEntityEvents(this.model, behavior.getOption('modelEvents'));
1347
- behavior.unbindEntityEvents(this.collection, behavior.getOption('collectionEvents'));
1348
- }, this);
1349
-
1350
- return this;
1351
- },
1352
-
1353
- // Internal helper method to verify whether the view hasn't been destroyed
1354
- _ensureViewIsIntact: function() {
1355
- if (this.isDestroyed) {
1356
- throw new Marionette.Error({
1357
- name: 'ViewDestroyedError',
1358
- message: 'View (cid: "' + this.cid + '") has already been destroyed and cannot be used.'
1359
- });
1360
- }
1361
- },
1362
-
1363
- // Default `destroy` implementation, for removing a view from the
1364
- // DOM and unbinding it. Regions will call this method
1365
- // for you. You can specify an `onDestroy` method in your view to
1366
- // add custom code that is called after the view is destroyed.
1367
- destroy: function() {
1368
- if (this.isDestroyed) { return this; }
1369
-
1370
- var args = _.toArray(arguments);
1371
-
1372
- this.triggerMethod.apply(this, ['before:destroy'].concat(args));
1373
-
1374
- // mark as destroyed before doing the actual destroy, to
1375
- // prevent infinite loops within "destroy" event handlers
1376
- // that are trying to destroy other views
1377
- this.isDestroyed = true;
1378
- this.triggerMethod.apply(this, ['destroy'].concat(args));
1379
-
1380
- // unbind UI elements
1381
- this.unbindUIElements();
1382
-
1383
- this.isRendered = false;
1384
-
1385
- // remove the view from the DOM
1386
- this.remove();
1387
-
1388
- // Call destroy on each behavior after
1389
- // destroying the view.
1390
- // This unbinds event listeners
1391
- // that behaviors have registered for.
1392
- _.invoke(this._behaviors, 'destroy', args);
1393
-
1394
- return this;
1395
- },
1396
-
1397
- bindUIElements: function() {
1398
- this._bindUIElements();
1399
- _.invoke(this._behaviors, this._bindUIElements);
1400
- },
1401
-
1402
- // This method binds the elements specified in the "ui" hash inside the view's code with
1403
- // the associated jQuery selectors.
1404
- _bindUIElements: function() {
1405
- if (!this.ui) { return; }
1406
-
1407
- // store the ui hash in _uiBindings so they can be reset later
1408
- // and so re-rendering the view will be able to find the bindings
1409
- if (!this._uiBindings) {
1410
- this._uiBindings = this.ui;
1411
- }
1412
-
1413
- // get the bindings result, as a function or otherwise
1414
- var bindings = _.result(this, '_uiBindings');
1415
-
1416
- // empty the ui so we don't have anything to start with
1417
- this.ui = {};
1418
-
1419
- // bind each of the selectors
1420
- _.each(bindings, function(selector, key) {
1421
- this.ui[key] = this.$(selector);
1422
- }, this);
1423
- },
1424
-
1425
- // This method unbinds the elements specified in the "ui" hash
1426
- unbindUIElements: function() {
1427
- this._unbindUIElements();
1428
- _.invoke(this._behaviors, this._unbindUIElements);
1429
- },
1430
-
1431
- _unbindUIElements: function() {
1432
- if (!this.ui || !this._uiBindings) { return; }
1433
-
1434
- // delete all of the existing ui bindings
1435
- _.each(this.ui, function($el, name) {
1436
- delete this.ui[name];
1437
- }, this);
1438
-
1439
- // reset the ui element to the original bindings configuration
1440
- this.ui = this._uiBindings;
1441
- delete this._uiBindings;
1442
- },
1443
-
1444
- // Internal method to create an event handler for a given `triggerDef` like
1445
- // 'click:foo'
1446
- _buildViewTrigger: function(triggerDef) {
1447
-
1448
- var options = _.defaults({}, triggerDef, {
1449
- preventDefault: true,
1450
- stopPropagation: true
1451
- });
1452
-
1453
- var eventName = _.isObject(triggerDef) ? options.event : triggerDef;
1454
-
1455
- return function(e) {
1456
- if (e) {
1457
- if (e.preventDefault && options.preventDefault) {
1458
- e.preventDefault();
1459
- }
1460
-
1461
- if (e.stopPropagation && options.stopPropagation) {
1462
- e.stopPropagation();
1463
- }
1464
- }
1465
-
1466
- var args = {
1467
- view: this,
1468
- model: this.model,
1469
- collection: this.collection
1470
- };
1471
-
1472
- this.triggerMethod(eventName, args);
1473
- };
1474
- },
1475
-
1476
- setElement: function() {
1477
- var ret = Backbone.View.prototype.setElement.apply(this, arguments);
1478
-
1479
- // proxy behavior $el to the view's $el.
1480
- // This is needed because a view's $el proxy
1481
- // is not set until after setElement is called.
1482
- _.invoke(this._behaviors, 'proxyViewProperties', this);
1483
-
1484
- return ret;
1485
- },
1486
-
1487
- // import the `triggerMethod` to trigger events with corresponding
1488
- // methods if the method exists
1489
- triggerMethod: function() {
1490
- var ret = Marionette._triggerMethod(this, arguments);
1491
-
1492
- this._triggerEventOnBehaviors(arguments);
1493
- this._triggerEventOnParentLayout(arguments[0], _.rest(arguments));
1494
-
1495
- return ret;
1496
- },
1497
-
1498
- _triggerEventOnBehaviors: function(args) {
1499
- var triggerMethod = Marionette._triggerMethod;
1500
- var behaviors = this._behaviors;
1501
- // Use good ol' for as this is a very hot function
1502
- for (var i = 0, length = behaviors && behaviors.length; i < length; i++) {
1503
- triggerMethod(behaviors[i], args);
1504
- }
1505
- },
1506
-
1507
- _triggerEventOnParentLayout: function(eventName, args) {
1508
- var layoutView = this._parentLayoutView();
1509
- if (!layoutView) {
1510
- return;
1511
- }
1512
-
1513
- // invoke triggerMethod on parent view
1514
- var eventPrefix = Marionette.getOption(layoutView, 'childViewEventPrefix');
1515
- var prefixedEventName = eventPrefix + ':' + eventName;
1516
- var callArgs = [this].concat(args);
1517
-
1518
- Marionette._triggerMethod(layoutView, prefixedEventName, callArgs);
1519
-
1520
- // call the parent view's childEvents handler
1521
- var childEvents = Marionette.getOption(layoutView, 'childEvents');
1522
-
1523
- // since childEvents can be an object or a function use Marionette._getValue
1524
- // to handle the abstaction for us.
1525
- childEvents = Marionette._getValue(childEvents, layoutView);
1526
- var normalizedChildEvents = layoutView.normalizeMethods(childEvents);
1527
-
1528
- if (normalizedChildEvents && _.isFunction(normalizedChildEvents[eventName])) {
1529
- normalizedChildEvents[eventName].apply(layoutView, callArgs);
1530
- }
1531
- },
1532
-
1533
- // This method returns any views that are immediate
1534
- // children of this view
1535
- _getImmediateChildren: function() {
1536
- return [];
1537
- },
1538
-
1539
- // Returns an array of every nested view within this view
1540
- _getNestedViews: function() {
1541
- var children = this._getImmediateChildren();
1542
-
1543
- if (!children.length) { return children; }
1544
-
1545
- return _.reduce(children, function(memo, view) {
1546
- if (!view._getNestedViews) { return memo; }
1547
- return memo.concat(view._getNestedViews());
1548
- }, children);
1549
- },
1550
-
1551
- // Walk the _parent tree until we find a layout view (if one exists).
1552
- // Returns the parent layout view hierarchically closest to this view.
1553
- _parentLayoutView: function() {
1554
- var parent = this._parent;
1555
-
1556
- while (parent) {
1557
- if (parent instanceof Marionette.LayoutView) {
1558
- return parent;
1559
- }
1560
- parent = parent._parent;
1561
- }
1562
- },
1563
-
1564
- // Imports the "normalizeMethods" to transform hashes of
1565
- // events=>function references/names to a hash of events=>function references
1566
- normalizeMethods: Marionette.normalizeMethods,
1567
-
1568
- // A handy way to merge passed-in options onto the instance
1569
- mergeOptions: Marionette.mergeOptions,
1570
-
1571
- // Proxy `getOption` to enable getting options from this or this.options by name.
1572
- getOption: Marionette.proxyGetOption,
1573
-
1574
- // Proxy `bindEntityEvents` to enable binding view's events from another entity.
1575
- bindEntityEvents: Marionette.proxyBindEntityEvents,
1576
-
1577
- // Proxy `unbindEntityEvents` to enable unbinding view's events from another entity.
1578
- unbindEntityEvents: Marionette.proxyUnbindEntityEvents
1579
- });
1580
-
1581
- // Item View
1582
- // ---------
1583
-
1584
- // A single item view implementation that contains code for rendering
1585
- // with underscore.js templates, serializing the view's model or collection,
1586
- // and calling several methods on extended views, such as `onRender`.
1587
- Marionette.ItemView = Marionette.View.extend({
1588
-
1589
- // Setting up the inheritance chain which allows changes to
1590
- // Marionette.View.prototype.constructor which allows overriding
1591
- constructor: function() {
1592
- Marionette.View.apply(this, arguments);
1593
- },
1594
-
1595
- // Serialize the model or collection for the view. If a model is
1596
- // found, the view's `serializeModel` is called. If a collection is found,
1597
- // each model in the collection is serialized by calling
1598
- // the view's `serializeCollection` and put into an `items` array in
1599
- // the resulting data. If both are found, defaults to the model.
1600
- // You can override the `serializeData` method in your own view definition,
1601
- // to provide custom serialization for your view's data.
1602
- serializeData: function() {
1603
- if (!this.model && !this.collection) {
1604
- return {};
1605
- }
1606
-
1607
- var args = [this.model || this.collection];
1608
- if (arguments.length) {
1609
- args.push.apply(args, arguments);
1610
- }
1611
-
1612
- if (this.model) {
1613
- return this.serializeModel.apply(this, args);
1614
- } else {
1615
- return {
1616
- items: this.serializeCollection.apply(this, args)
1617
- };
1618
- }
1619
- },
1620
-
1621
- // Serialize a collection by serializing each of its models.
1622
- serializeCollection: function(collection) {
1623
- return collection.toJSON.apply(collection, _.rest(arguments));
1624
- },
1625
-
1626
- // Render the view, defaulting to underscore.js templates.
1627
- // You can override this in your view definition to provide
1628
- // a very specific rendering for your view. In general, though,
1629
- // you should override the `Marionette.Renderer` object to
1630
- // change how Marionette renders views.
1631
- render: function() {
1632
- this._ensureViewIsIntact();
1633
-
1634
- this.triggerMethod('before:render', this);
1635
-
1636
- this._renderTemplate();
1637
- this.isRendered = true;
1638
- this.bindUIElements();
1639
-
1640
- this.triggerMethod('render', this);
1641
-
1642
- return this;
1643
- },
1644
-
1645
- // Internal method to render the template with the serialized data
1646
- // and template helpers via the `Marionette.Renderer` object.
1647
- // Throws an `UndefinedTemplateError` error if the template is
1648
- // any falsely value but literal `false`.
1649
- _renderTemplate: function() {
1650
- var template = this.getTemplate();
1651
-
1652
- // Allow template-less item views
1653
- if (template === false) {
1654
- return;
1655
- }
1656
-
1657
- if (!template) {
1658
- throw new Marionette.Error({
1659
- name: 'UndefinedTemplateError',
1660
- message: 'Cannot render the template since it is null or undefined.'
1661
- });
1662
- }
1663
-
1664
- // Add in entity data and template helpers
1665
- var data = this.mixinTemplateHelpers(this.serializeData());
1666
-
1667
- // Render and add to el
1668
- var html = Marionette.Renderer.render(template, data, this);
1669
- this.attachElContent(html);
1670
-
1671
- return this;
1672
- },
1673
-
1674
- // Attaches the content of a given view.
1675
- // This method can be overridden to optimize rendering,
1676
- // or to render in a non standard way.
1677
- //
1678
- // For example, using `innerHTML` instead of `$el.html`
1679
- //
1680
- // ```js
1681
- // attachElContent: function(html) {
1682
- // this.el.innerHTML = html;
1683
- // return this;
1684
- // }
1685
- // ```
1686
- attachElContent: function(html) {
1687
- this.$el.html(html);
1688
-
1689
- return this;
1690
- }
1691
- });
1692
-
1693
- /* jshint maxstatements: 20, maxcomplexity: 7 */
1694
-
1695
- // Collection View
1696
- // ---------------
1697
-
1698
- // A view that iterates over a Backbone.Collection
1699
- // and renders an individual child view for each model.
1700
- Marionette.CollectionView = Marionette.View.extend({
1701
-
1702
- // used as the prefix for child view events
1703
- // that are forwarded through the collectionview
1704
- childViewEventPrefix: 'childview',
1705
-
1706
- // flag for maintaining the sorted order of the collection
1707
- sort: true,
1708
-
1709
- // constructor
1710
- // option to pass `{sort: false}` to prevent the `CollectionView` from
1711
- // maintaining the sorted order of the collection.
1712
- // This will fallback onto appending childView's to the end.
1713
- //
1714
- // option to pass `{comparator: compFunction()}` to allow the `CollectionView`
1715
- // to use a custom sort order for the collection.
1716
- constructor: function(options) {
1717
- this.once('render', this._initialEvents);
1718
- this._initChildViewStorage();
1719
-
1720
- Marionette.View.apply(this, arguments);
1721
-
1722
- this.on({
1723
- 'before:show': this._onBeforeShowCalled,
1724
- 'show': this._onShowCalled,
1725
- 'before:attach': this._onBeforeAttachCalled,
1726
- 'attach': this._onAttachCalled
1727
- });
1728
- this.initRenderBuffer();
1729
- },
1730
-
1731
- // Instead of inserting elements one by one into the page,
1732
- // it's much more performant to insert elements into a document
1733
- // fragment and then insert that document fragment into the page
1734
- initRenderBuffer: function() {
1735
- this._bufferedChildren = [];
1736
- },
1737
-
1738
- startBuffering: function() {
1739
- this.initRenderBuffer();
1740
- this.isBuffering = true;
1741
- },
1742
-
1743
- endBuffering: function() {
1744
- // Only trigger attach if already shown and attached, otherwise Region#show() handles this.
1745
- var canTriggerAttach = this._isShown && Marionette.isNodeAttached(this.el);
1746
- var nestedViews;
1747
-
1748
- this.isBuffering = false;
1749
-
1750
- if (this._isShown) {
1751
- this._triggerMethodMany(this._bufferedChildren, this, 'before:show');
1752
- }
1753
- if (canTriggerAttach && this._triggerBeforeAttach) {
1754
- nestedViews = this._getNestedViews();
1755
- this._triggerMethodMany(nestedViews, this, 'before:attach');
1756
- }
1757
-
1758
- this.attachBuffer(this, this._createBuffer());
1759
-
1760
- if (canTriggerAttach && this._triggerAttach) {
1761
- nestedViews = this._getNestedViews();
1762
- this._triggerMethodMany(nestedViews, this, 'attach');
1763
- }
1764
- if (this._isShown) {
1765
- this._triggerMethodMany(this._bufferedChildren, this, 'show');
1766
- }
1767
- this.initRenderBuffer();
1768
- },
1769
-
1770
- _triggerMethodMany: function(targets, source, eventName) {
1771
- var args = _.drop(arguments, 3);
1772
-
1773
- _.each(targets, function(target) {
1774
- Marionette.triggerMethodOn.apply(target, [target, eventName, target, source].concat(args));
1775
- });
1776
- },
1777
-
1778
- // Configured the initial events that the collection view
1779
- // binds to.
1780
- _initialEvents: function() {
1781
- if (this.collection) {
1782
- this.listenTo(this.collection, 'add', this._onCollectionAdd);
1783
- this.listenTo(this.collection, 'remove', this._onCollectionRemove);
1784
- this.listenTo(this.collection, 'reset', this.render);
1785
-
1786
- if (this.getOption('sort')) {
1787
- this.listenTo(this.collection, 'sort', this._sortViews);
1788
- }
1789
- }
1790
- },
1791
-
1792
- // Handle a child added to the collection
1793
- _onCollectionAdd: function(child, collection, opts) {
1794
- // `index` is present when adding with `at` since BB 1.2; indexOf fallback for < 1.2
1795
- var index = opts.at !== undefined && (opts.index || collection.indexOf(child));
1796
-
1797
- // When filtered or when there is no initial index, calculate index.
1798
- if (this.getOption('filter') || index === false) {
1799
- index = _.indexOf(this._filteredSortedModels(index), child);
1800
- }
1801
-
1802
- if (this._shouldAddChild(child, index)) {
1803
- this.destroyEmptyView();
1804
- var ChildView = this.getChildView(child);
1805
- this.addChild(child, ChildView, index);
1806
- }
1807
- },
1808
-
1809
- // get the child view by model it holds, and remove it
1810
- _onCollectionRemove: function(model) {
1811
- var view = this.children.findByModel(model);
1812
- this.removeChildView(view);
1813
- this.checkEmpty();
1814
- },
1815
-
1816
- _onBeforeShowCalled: function() {
1817
- // Reset attach event flags at the top of the Region#show() event lifecycle; if the Region's
1818
- // show() options permit onBeforeAttach/onAttach events, these flags will be set true again.
1819
- this._triggerBeforeAttach = this._triggerAttach = false;
1820
- this.children.each(function(childView) {
1821
- Marionette.triggerMethodOn(childView, 'before:show', childView);
1822
- });
1823
- },
1824
-
1825
- _onShowCalled: function() {
1826
- this.children.each(function(childView) {
1827
- Marionette.triggerMethodOn(childView, 'show', childView);
1828
- });
1829
- },
1830
-
1831
- // If during Region#show() onBeforeAttach was fired, continue firing it for child views
1832
- _onBeforeAttachCalled: function() {
1833
- this._triggerBeforeAttach = true;
1834
- },
1835
-
1836
- // If during Region#show() onAttach was fired, continue firing it for child views
1837
- _onAttachCalled: function() {
1838
- this._triggerAttach = true;
1839
- },
1840
-
1841
- // Render children views. Override this method to
1842
- // provide your own implementation of a render function for
1843
- // the collection view.
1844
- render: function() {
1845
- this._ensureViewIsIntact();
1846
- this.triggerMethod('before:render', this);
1847
- this._renderChildren();
1848
- this.isRendered = true;
1849
- this.triggerMethod('render', this);
1850
- return this;
1851
- },
1852
-
1853
- // Reorder DOM after sorting. When your element's rendering
1854
- // do not use their index, you can pass reorderOnSort: true
1855
- // to only reorder the DOM after a sort instead of rendering
1856
- // all the collectionView
1857
- reorder: function() {
1858
- var children = this.children;
1859
- var models = this._filteredSortedModels();
1860
-
1861
- if (!models.length && this._showingEmptyView) { return this; }
1862
-
1863
- var anyModelsAdded = _.some(models, function(model) {
1864
- return !children.findByModel(model);
1865
- });
1866
-
1867
- // If there are any new models added due to filtering
1868
- // We need to add child views
1869
- // So render as normal
1870
- if (anyModelsAdded) {
1871
- this.render();
1872
- } else {
1873
- // get the DOM nodes in the same order as the models
1874
- var elsToReorder = _.map(models, function(model, index) {
1875
- var view = children.findByModel(model);
1876
- view._index = index;
1877
- return view.el;
1878
- });
1879
-
1880
- // find the views that were children before but arent in this new ordering
1881
- var filteredOutViews = children.filter(function(view) {
1882
- return !_.contains(elsToReorder, view.el);
1883
- });
1884
-
1885
- this.triggerMethod('before:reorder');
1886
-
1887
- // since append moves elements that are already in the DOM,
1888
- // appending the elements will effectively reorder them
1889
- this._appendReorderedChildren(elsToReorder);
1890
-
1891
- // remove any views that have been filtered out
1892
- _.each(filteredOutViews, this.removeChildView, this);
1893
- this.checkEmpty();
1894
-
1895
- this.triggerMethod('reorder');
1896
- }
1897
- },
1898
-
1899
- // Render view after sorting. Override this method to
1900
- // change how the view renders after a `sort` on the collection.
1901
- // An example of this would be to only `renderChildren` in a `CompositeView`
1902
- // rather than the full view.
1903
- resortView: function() {
1904
- if (Marionette.getOption(this, 'reorderOnSort')) {
1905
- this.reorder();
1906
- } else {
1907
- this.render();
1908
- }
1909
- },
1910
-
1911
- // Internal method. This checks for any changes in the order of the collection.
1912
- // If the index of any view doesn't match, it will render.
1913
- _sortViews: function() {
1914
- var models = this._filteredSortedModels();
1915
-
1916
- // check for any changes in sort order of views
1917
- var orderChanged = _.find(models, function(item, index) {
1918
- var view = this.children.findByModel(item);
1919
- return !view || view._index !== index;
1920
- }, this);
1921
-
1922
- if (orderChanged) {
1923
- this.resortView();
1924
- }
1925
- },
1926
-
1927
- // Internal reference to what index a `emptyView` is.
1928
- _emptyViewIndex: -1,
1929
-
1930
- // Internal method. Separated so that CompositeView can append to the childViewContainer
1931
- // if necessary
1932
- _appendReorderedChildren: function(children) {
1933
- this.$el.append(children);
1934
- },
1935
-
1936
- // Internal method. Separated so that CompositeView can have
1937
- // more control over events being triggered, around the rendering
1938
- // process
1939
- _renderChildren: function() {
1940
- this.destroyEmptyView();
1941
- this.destroyChildren({checkEmpty: false});
1942
-
1943
- if (this.isEmpty(this.collection)) {
1944
- this.showEmptyView();
1945
- } else {
1946
- this.triggerMethod('before:render:collection', this);
1947
- this.startBuffering();
1948
- this.showCollection();
1949
- this.endBuffering();
1950
- this.triggerMethod('render:collection', this);
1951
-
1952
- // If we have shown children and none have passed the filter, show the empty view
1953
- if (this.children.isEmpty() && this.getOption('filter')) {
1954
- this.showEmptyView();
1955
- }
1956
- }
1957
- },
1958
-
1959
- // Internal method to loop through collection and show each child view.
1960
- showCollection: function() {
1961
- var ChildView;
1962
-
1963
- var models = this._filteredSortedModels();
1964
-
1965
- _.each(models, function(child, index) {
1966
- ChildView = this.getChildView(child);
1967
- this.addChild(child, ChildView, index);
1968
- }, this);
1969
- },
1970
-
1971
- // Allow the collection to be sorted by a custom view comparator
1972
- _filteredSortedModels: function(addedAt) {
1973
- var viewComparator = this.getViewComparator();
1974
- var models = this.collection.models;
1975
- addedAt = Math.min(Math.max(addedAt, 0), models.length - 1);
1976
-
1977
- if (viewComparator) {
1978
- var addedModel;
1979
- // Preserve `at` location, even for a sorted view
1980
- if (addedAt) {
1981
- addedModel = models[addedAt];
1982
- models = models.slice(0, addedAt).concat(models.slice(addedAt + 1));
1983
- }
1984
- models = this._sortModelsBy(models, viewComparator);
1985
- if (addedModel) {
1986
- models.splice(addedAt, 0, addedModel);
1987
- }
1988
- }
1989
-
1990
- // Filter after sorting in case the filter uses the index
1991
- if (this.getOption('filter')) {
1992
- models = _.filter(models, function(model, index) {
1993
- return this._shouldAddChild(model, index);
1994
- }, this);
1995
- }
1996
-
1997
- return models;
1998
- },
1999
-
2000
- _sortModelsBy: function(models, comparator) {
2001
- if (typeof comparator === 'string') {
2002
- return _.sortBy(models, function(model) {
2003
- return model.get(comparator);
2004
- }, this);
2005
- } else if (comparator.length === 1) {
2006
- return _.sortBy(models, comparator, this);
2007
- } else {
2008
- return models.sort(_.bind(comparator, this));
2009
- }
2010
- },
2011
-
2012
- // Internal method to show an empty view in place of
2013
- // a collection of child views, when the collection is empty
2014
- showEmptyView: function() {
2015
- var EmptyView = this.getEmptyView();
2016
-
2017
- if (EmptyView && !this._showingEmptyView) {
2018
- this.triggerMethod('before:render:empty');
2019
-
2020
- this._showingEmptyView = true;
2021
- var model = new Backbone.Model();
2022
- this.addEmptyView(model, EmptyView);
2023
-
2024
- this.triggerMethod('render:empty');
2025
- }
2026
- },
2027
-
2028
- // Internal method to destroy an existing emptyView instance
2029
- // if one exists. Called when a collection view has been
2030
- // rendered empty, and then a child is added to the collection.
2031
- destroyEmptyView: function() {
2032
- if (this._showingEmptyView) {
2033
- this.triggerMethod('before:remove:empty');
2034
-
2035
- this.destroyChildren();
2036
- delete this._showingEmptyView;
2037
-
2038
- this.triggerMethod('remove:empty');
2039
- }
2040
- },
2041
-
2042
- // Retrieve the empty view class
2043
- getEmptyView: function() {
2044
- return this.getOption('emptyView');
2045
- },
2046
-
2047
- // Render and show the emptyView. Similar to addChild method
2048
- // but "add:child" events are not fired, and the event from
2049
- // emptyView are not forwarded
2050
- addEmptyView: function(child, EmptyView) {
2051
- // Only trigger attach if already shown, attached, and not buffering, otherwise endBuffer() or
2052
- // Region#show() handles this.
2053
- var canTriggerAttach = this._isShown && !this.isBuffering && Marionette.isNodeAttached(this.el);
2054
- var nestedViews;
2055
-
2056
- // get the emptyViewOptions, falling back to childViewOptions
2057
- var emptyViewOptions = this.getOption('emptyViewOptions') ||
2058
- this.getOption('childViewOptions');
2059
-
2060
- if (_.isFunction(emptyViewOptions)) {
2061
- emptyViewOptions = emptyViewOptions.call(this, child, this._emptyViewIndex);
2062
- }
2063
-
2064
- // build the empty view
2065
- var view = this.buildChildView(child, EmptyView, emptyViewOptions);
2066
-
2067
- view._parent = this;
2068
-
2069
- // Proxy emptyView events
2070
- this.proxyChildEvents(view);
2071
-
2072
- view.once('render', function() {
2073
- // trigger the 'before:show' event on `view` if the collection view has already been shown
2074
- if (this._isShown) {
2075
- Marionette.triggerMethodOn(view, 'before:show', view);
2076
- }
2077
-
2078
- // Trigger `before:attach` following `render` to avoid adding logic and event triggers
2079
- // to public method `renderChildView()`.
2080
- if (canTriggerAttach && this._triggerBeforeAttach) {
2081
- nestedViews = this._getViewAndNested(view);
2082
- this._triggerMethodMany(nestedViews, this, 'before:attach');
2083
- }
2084
- }, this);
2085
-
2086
- // Store the `emptyView` like a `childView` so we can properly remove and/or close it later
2087
- this.children.add(view);
2088
- this.renderChildView(view, this._emptyViewIndex);
2089
-
2090
- // Trigger `attach`
2091
- if (canTriggerAttach && this._triggerAttach) {
2092
- nestedViews = this._getViewAndNested(view);
2093
- this._triggerMethodMany(nestedViews, this, 'attach');
2094
- }
2095
- // call the 'show' method if the collection view has already been shown
2096
- if (this._isShown) {
2097
- Marionette.triggerMethodOn(view, 'show', view);
2098
- }
2099
- },
2100
-
2101
- // Retrieve the `childView` class, either from `this.options.childView`
2102
- // or from the `childView` in the object definition. The "options"
2103
- // takes precedence.
2104
- // This method receives the model that will be passed to the instance
2105
- // created from this `childView`. Overriding methods may use the child
2106
- // to determine what `childView` class to return.
2107
- getChildView: function(child) {
2108
- var childView = this.getOption('childView');
2109
-
2110
- if (!childView) {
2111
- throw new Marionette.Error({
2112
- name: 'NoChildViewError',
2113
- message: 'A "childView" must be specified'
2114
- });
2115
- }
2116
-
2117
- return childView;
2118
- },
2119
-
2120
- // Render the child's view and add it to the
2121
- // HTML for the collection view at a given index.
2122
- // This will also update the indices of later views in the collection
2123
- // in order to keep the children in sync with the collection.
2124
- addChild: function(child, ChildView, index) {
2125
- var childViewOptions = this.getOption('childViewOptions');
2126
- childViewOptions = Marionette._getValue(childViewOptions, this, [child, index]);
2127
-
2128
- var view = this.buildChildView(child, ChildView, childViewOptions);
2129
-
2130
- // increment indices of views after this one
2131
- this._updateIndices(view, true, index);
2132
-
2133
- this.triggerMethod('before:add:child', view);
2134
- this._addChildView(view, index);
2135
- this.triggerMethod('add:child', view);
2136
-
2137
- view._parent = this;
2138
-
2139
- return view;
2140
- },
2141
-
2142
- // Internal method. This decrements or increments the indices of views after the
2143
- // added/removed view to keep in sync with the collection.
2144
- _updateIndices: function(view, increment, index) {
2145
- if (!this.getOption('sort')) {
2146
- return;
2147
- }
2148
-
2149
- if (increment) {
2150
- // assign the index to the view
2151
- view._index = index;
2152
- }
2153
-
2154
- // update the indexes of views after this one
2155
- this.children.each(function(laterView) {
2156
- if (laterView._index >= view._index) {
2157
- laterView._index += increment ? 1 : -1;
2158
- }
2159
- });
2160
- },
2161
-
2162
- // Internal Method. Add the view to children and render it at
2163
- // the given index.
2164
- _addChildView: function(view, index) {
2165
- // Only trigger attach if already shown, attached, and not buffering, otherwise endBuffer() or
2166
- // Region#show() handles this.
2167
- var canTriggerAttach = this._isShown && !this.isBuffering && Marionette.isNodeAttached(this.el);
2168
- var nestedViews;
2169
-
2170
- // set up the child view event forwarding
2171
- this.proxyChildEvents(view);
2172
-
2173
- view.once('render', function() {
2174
- // trigger the 'before:show' event on `view` if the collection view has already been shown
2175
- if (this._isShown && !this.isBuffering) {
2176
- Marionette.triggerMethodOn(view, 'before:show', view);
2177
- }
2178
-
2179
- // Trigger `before:attach` following `render` to avoid adding logic and event triggers
2180
- // to public method `renderChildView()`.
2181
- if (canTriggerAttach && this._triggerBeforeAttach) {
2182
- nestedViews = this._getViewAndNested(view);
2183
- this._triggerMethodMany(nestedViews, this, 'before:attach');
2184
- }
2185
- }, this);
2186
-
2187
- // Store the child view itself so we can properly remove and/or destroy it later
2188
- this.children.add(view);
2189
- this.renderChildView(view, index);
2190
-
2191
- // Trigger `attach`
2192
- if (canTriggerAttach && this._triggerAttach) {
2193
- nestedViews = this._getViewAndNested(view);
2194
- this._triggerMethodMany(nestedViews, this, 'attach');
2195
- }
2196
- // Trigger `show`
2197
- if (this._isShown && !this.isBuffering) {
2198
- Marionette.triggerMethodOn(view, 'show', view);
2199
- }
2200
- },
2201
-
2202
- // render the child view
2203
- renderChildView: function(view, index) {
2204
- if (!view.supportsRenderLifecycle) {
2205
- Marionette.triggerMethodOn(view, 'before:render', view);
2206
- }
2207
- view.render();
2208
- if (!view.supportsRenderLifecycle) {
2209
- Marionette.triggerMethodOn(view, 'render', view);
2210
- }
2211
- this.attachHtml(this, view, index);
2212
- return view;
2213
- },
2214
-
2215
- // Build a `childView` for a model in the collection.
2216
- buildChildView: function(child, ChildViewClass, childViewOptions) {
2217
- var options = _.extend({model: child}, childViewOptions);
2218
- var childView = new ChildViewClass(options);
2219
- Marionette.MonitorDOMRefresh(childView);
2220
- return childView;
2221
- },
2222
-
2223
- // Remove the child view and destroy it.
2224
- // This function also updates the indices of
2225
- // later views in the collection in order to keep
2226
- // the children in sync with the collection.
2227
- removeChildView: function(view) {
2228
- if (!view) { return view; }
2229
-
2230
- this.triggerMethod('before:remove:child', view);
2231
-
2232
- if (!view.supportsDestroyLifecycle) {
2233
- Marionette.triggerMethodOn(view, 'before:destroy', view);
2234
- }
2235
- // call 'destroy' or 'remove', depending on which is found
2236
- if (view.destroy) {
2237
- view.destroy();
2238
- } else {
2239
- view.remove();
2240
- }
2241
- if (!view.supportsDestroyLifecycle) {
2242
- Marionette.triggerMethodOn(view, 'destroy', view);
2243
- }
2244
-
2245
- delete view._parent;
2246
- this.stopListening(view);
2247
- this.children.remove(view);
2248
- this.triggerMethod('remove:child', view);
2249
-
2250
- // decrement the index of views after this one
2251
- this._updateIndices(view, false);
2252
-
2253
- return view;
2254
- },
2255
-
2256
- // check if the collection is empty
2257
- isEmpty: function() {
2258
- return !this.collection || this.collection.length === 0;
2259
- },
2260
-
2261
- // If empty, show the empty view
2262
- checkEmpty: function() {
2263
- if (this.isEmpty(this.collection)) {
2264
- this.showEmptyView();
2265
- }
2266
- },
2267
-
2268
- // You might need to override this if you've overridden attachHtml
2269
- attachBuffer: function(collectionView, buffer) {
2270
- collectionView.$el.append(buffer);
2271
- },
2272
-
2273
- // Create a fragment buffer from the currently buffered children
2274
- _createBuffer: function() {
2275
- var elBuffer = document.createDocumentFragment();
2276
- _.each(this._bufferedChildren, function(b) {
2277
- elBuffer.appendChild(b.el);
2278
- });
2279
- return elBuffer;
2280
- },
2281
-
2282
- // Append the HTML to the collection's `el`.
2283
- // Override this method to do something other
2284
- // than `.append`.
2285
- attachHtml: function(collectionView, childView, index) {
2286
- if (collectionView.isBuffering) {
2287
- // buffering happens on reset events and initial renders
2288
- // in order to reduce the number of inserts into the
2289
- // document, which are expensive.
2290
- collectionView._bufferedChildren.splice(index, 0, childView);
2291
- } else {
2292
- // If we've already rendered the main collection, append
2293
- // the new child into the correct order if we need to. Otherwise
2294
- // append to the end.
2295
- if (!collectionView._insertBefore(childView, index)) {
2296
- collectionView._insertAfter(childView);
2297
- }
2298
- }
2299
- },
2300
-
2301
- // Internal method. Check whether we need to insert the view into
2302
- // the correct position.
2303
- _insertBefore: function(childView, index) {
2304
- var currentView;
2305
- var findPosition = this.getOption('sort') && (index < this.children.length - 1);
2306
- if (findPosition) {
2307
- // Find the view after this one
2308
- currentView = this.children.find(function(view) {
2309
- return view._index === index + 1;
2310
- });
2311
- }
2312
-
2313
- if (currentView) {
2314
- currentView.$el.before(childView.el);
2315
- return true;
2316
- }
2317
-
2318
- return false;
2319
- },
2320
-
2321
- // Internal method. Append a view to the end of the $el
2322
- _insertAfter: function(childView) {
2323
- this.$el.append(childView.el);
2324
- },
2325
-
2326
- // Internal method to set up the `children` object for
2327
- // storing all of the child views
2328
- _initChildViewStorage: function() {
2329
- this.children = new Backbone.ChildViewContainer();
2330
- },
2331
-
2332
- // Handle cleanup and other destroying needs for the collection of views
2333
- destroy: function() {
2334
- if (this.isDestroyed) { return this; }
2335
-
2336
- this.triggerMethod('before:destroy:collection');
2337
- this.destroyChildren({checkEmpty: false});
2338
- this.triggerMethod('destroy:collection');
2339
-
2340
- return Marionette.View.prototype.destroy.apply(this, arguments);
2341
- },
2342
-
2343
- // Destroy the child views that this collection view
2344
- // is holding on to, if any
2345
- destroyChildren: function(options) {
2346
- var destroyOptions = options || {};
2347
- var shouldCheckEmpty = true;
2348
- var childViews = this.children.map(_.identity);
2349
-
2350
- if (!_.isUndefined(destroyOptions.checkEmpty)) {
2351
- shouldCheckEmpty = destroyOptions.checkEmpty;
2352
- }
2353
-
2354
- this.children.each(this.removeChildView, this);
2355
-
2356
- if (shouldCheckEmpty) {
2357
- this.checkEmpty();
2358
- }
2359
- return childViews;
2360
- },
2361
-
2362
- // Return true if the given child should be shown
2363
- // Return false otherwise
2364
- // The filter will be passed (child, index, collection)
2365
- // Where
2366
- // 'child' is the given model
2367
- // 'index' is the index of that model in the collection
2368
- // 'collection' is the collection referenced by this CollectionView
2369
- _shouldAddChild: function(child, index) {
2370
- var filter = this.getOption('filter');
2371
- return !_.isFunction(filter) || filter.call(this, child, index, this.collection);
2372
- },
2373
-
2374
- // Set up the child view event forwarding. Uses a "childview:"
2375
- // prefix in front of all forwarded events.
2376
- proxyChildEvents: function(view) {
2377
- var prefix = this.getOption('childViewEventPrefix');
2378
-
2379
- // Forward all child view events through the parent,
2380
- // prepending "childview:" to the event name
2381
- this.listenTo(view, 'all', function() {
2382
- var args = _.toArray(arguments);
2383
- var rootEvent = args[0];
2384
- var childEvents = this.normalizeMethods(_.result(this, 'childEvents'));
2385
-
2386
- args[0] = prefix + ':' + rootEvent;
2387
- args.splice(1, 0, view);
2388
-
2389
- // call collectionView childEvent if defined
2390
- if (typeof childEvents !== 'undefined' && _.isFunction(childEvents[rootEvent])) {
2391
- childEvents[rootEvent].apply(this, args.slice(1));
2392
- }
2393
-
2394
- this.triggerMethod.apply(this, args);
2395
- });
2396
- },
2397
-
2398
- _getImmediateChildren: function() {
2399
- return _.values(this.children._views);
2400
- },
2401
-
2402
- _getViewAndNested: function(view) {
2403
- // This will not fail on Backbone.View which does not have #_getNestedViews.
2404
- return [view].concat(_.result(view, '_getNestedViews') || []);
2405
- },
2406
-
2407
- getViewComparator: function() {
2408
- return this.getOption('viewComparator');
2409
- }
2410
- });
2411
-
2412
- /* jshint maxstatements: 17, maxlen: 117 */
2413
-
2414
- // Composite View
2415
- // --------------
2416
-
2417
- // Used for rendering a branch-leaf, hierarchical structure.
2418
- // Extends directly from CollectionView and also renders an
2419
- // a child view as `modelView`, for the top leaf
2420
- Marionette.CompositeView = Marionette.CollectionView.extend({
2421
-
2422
- // Setting up the inheritance chain which allows changes to
2423
- // Marionette.CollectionView.prototype.constructor which allows overriding
2424
- // option to pass '{sort: false}' to prevent the CompositeView from
2425
- // maintaining the sorted order of the collection.
2426
- // This will fallback onto appending childView's to the end.
2427
- constructor: function() {
2428
- Marionette.CollectionView.apply(this, arguments);
2429
- },
2430
-
2431
- // Configured the initial events that the composite view
2432
- // binds to. Override this method to prevent the initial
2433
- // events, or to add your own initial events.
2434
- _initialEvents: function() {
2435
-
2436
- // Bind only after composite view is rendered to avoid adding child views
2437
- // to nonexistent childViewContainer
2438
-
2439
- if (this.collection) {
2440
- this.listenTo(this.collection, 'add', this._onCollectionAdd);
2441
- this.listenTo(this.collection, 'remove', this._onCollectionRemove);
2442
- this.listenTo(this.collection, 'reset', this._renderChildren);
2443
-
2444
- if (this.getOption('sort')) {
2445
- this.listenTo(this.collection, 'sort', this._sortViews);
2446
- }
2447
- }
2448
- },
2449
-
2450
- // Retrieve the `childView` to be used when rendering each of
2451
- // the items in the collection. The default is to return
2452
- // `this.childView` or Marionette.CompositeView if no `childView`
2453
- // has been defined
2454
- getChildView: function(child) {
2455
- var childView = this.getOption('childView') || this.constructor;
2456
-
2457
- return childView;
2458
- },
2459
-
2460
- // Serialize the model for the view.
2461
- // You can override the `serializeData` method in your own view
2462
- // definition, to provide custom serialization for your view's data.
2463
- serializeData: function() {
2464
- var data = {};
2465
-
2466
- if (this.model) {
2467
- data = _.partial(this.serializeModel, this.model).apply(this, arguments);
2468
- }
2469
-
2470
- return data;
2471
- },
2472
-
2473
- // Renders the model and the collection.
2474
- render: function() {
2475
- this._ensureViewIsIntact();
2476
- this._isRendering = true;
2477
- this.resetChildViewContainer();
2478
-
2479
- this.triggerMethod('before:render', this);
2480
-
2481
- this._renderTemplate();
2482
- this._renderChildren();
2483
-
2484
- this._isRendering = false;
2485
- this.isRendered = true;
2486
- this.triggerMethod('render', this);
2487
- return this;
2488
- },
2489
-
2490
- _renderChildren: function() {
2491
- if (this.isRendered || this._isRendering) {
2492
- Marionette.CollectionView.prototype._renderChildren.call(this);
2493
- }
2494
- },
2495
-
2496
- // Render the root template that the children
2497
- // views are appended to
2498
- _renderTemplate: function() {
2499
- var data = {};
2500
- data = this.serializeData();
2501
- data = this.mixinTemplateHelpers(data);
2502
-
2503
- this.triggerMethod('before:render:template');
2504
-
2505
- var template = this.getTemplate();
2506
- var html = Marionette.Renderer.render(template, data, this);
2507
- this.attachElContent(html);
2508
-
2509
- // the ui bindings is done here and not at the end of render since they
2510
- // will not be available until after the model is rendered, but should be
2511
- // available before the collection is rendered.
2512
- this.bindUIElements();
2513
- this.triggerMethod('render:template');
2514
- },
2515
-
2516
- // Attaches the content of the root.
2517
- // This method can be overridden to optimize rendering,
2518
- // or to render in a non standard way.
2519
- //
2520
- // For example, using `innerHTML` instead of `$el.html`
2521
- //
2522
- // ```js
2523
- // attachElContent: function(html) {
2524
- // this.el.innerHTML = html;
2525
- // return this;
2526
- // }
2527
- // ```
2528
- attachElContent: function(html) {
2529
- this.$el.html(html);
2530
-
2531
- return this;
2532
- },
2533
-
2534
- // You might need to override this if you've overridden attachHtml
2535
- attachBuffer: function(compositeView, buffer) {
2536
- var $container = this.getChildViewContainer(compositeView);
2537
- $container.append(buffer);
2538
- },
2539
-
2540
- // Internal method. Append a view to the end of the $el.
2541
- // Overidden from CollectionView to ensure view is appended to
2542
- // childViewContainer
2543
- _insertAfter: function(childView) {
2544
- var $container = this.getChildViewContainer(this, childView);
2545
- $container.append(childView.el);
2546
- },
2547
-
2548
- // Internal method. Append reordered childView'.
2549
- // Overidden from CollectionView to ensure reordered views
2550
- // are appended to childViewContainer
2551
- _appendReorderedChildren: function(children) {
2552
- var $container = this.getChildViewContainer(this);
2553
- $container.append(children);
2554
- },
2555
-
2556
- // Internal method to ensure an `$childViewContainer` exists, for the
2557
- // `attachHtml` method to use.
2558
- getChildViewContainer: function(containerView, childView) {
2559
- if (!!containerView.$childViewContainer) {
2560
- return containerView.$childViewContainer;
2561
- }
2562
-
2563
- var container;
2564
- var childViewContainer = Marionette.getOption(containerView, 'childViewContainer');
2565
- if (childViewContainer) {
2566
-
2567
- var selector = Marionette._getValue(childViewContainer, containerView);
2568
-
2569
- if (selector.charAt(0) === '@' && containerView.ui) {
2570
- container = containerView.ui[selector.substr(4)];
2571
- } else {
2572
- container = containerView.$(selector);
2573
- }
2574
-
2575
- if (container.length <= 0) {
2576
- throw new Marionette.Error({
2577
- name: 'ChildViewContainerMissingError',
2578
- message: 'The specified "childViewContainer" was not found: ' + containerView.childViewContainer
2579
- });
2580
- }
2581
-
2582
- } else {
2583
- container = containerView.$el;
2584
- }
2585
-
2586
- containerView.$childViewContainer = container;
2587
- return container;
2588
- },
2589
-
2590
- // Internal method to reset the `$childViewContainer` on render
2591
- resetChildViewContainer: function() {
2592
- if (this.$childViewContainer) {
2593
- this.$childViewContainer = undefined;
2594
- }
2595
- }
2596
- });
2597
-
2598
- // Layout View
2599
- // -----------
2600
-
2601
- // Used for managing application layoutViews, nested layoutViews and
2602
- // multiple regions within an application or sub-application.
2603
- //
2604
- // A specialized view class that renders an area of HTML and then
2605
- // attaches `Region` instances to the specified `regions`.
2606
- // Used for composite view management and sub-application areas.
2607
- Marionette.LayoutView = Marionette.ItemView.extend({
2608
- regionClass: Marionette.Region,
2609
-
2610
- options: {
2611
- destroyImmediate: false
2612
- },
2613
-
2614
- // used as the prefix for child view events
2615
- // that are forwarded through the layoutview
2616
- childViewEventPrefix: 'childview',
2617
-
2618
- // Ensure the regions are available when the `initialize` method
2619
- // is called.
2620
- constructor: function(options) {
2621
- options = options || {};
2622
-
2623
- this._firstRender = true;
2624
- this._initializeRegions(options);
2625
-
2626
- Marionette.ItemView.call(this, options);
2627
- },
2628
-
2629
- // LayoutView's render will use the existing region objects the
2630
- // first time it is called. Subsequent calls will destroy the
2631
- // views that the regions are showing and then reset the `el`
2632
- // for the regions to the newly rendered DOM elements.
2633
- render: function() {
2634
- this._ensureViewIsIntact();
2635
-
2636
- if (this._firstRender) {
2637
- // if this is the first render, don't do anything to
2638
- // reset the regions
2639
- this._firstRender = false;
2640
- } else {
2641
- // If this is not the first render call, then we need to
2642
- // re-initialize the `el` for each region
2643
- this._reInitializeRegions();
2644
- }
2645
-
2646
- return Marionette.ItemView.prototype.render.apply(this, arguments);
2647
- },
2648
-
2649
- // Handle destroying regions, and then destroy the view itself.
2650
- destroy: function() {
2651
- if (this.isDestroyed) { return this; }
2652
- // #2134: remove parent element before destroying the child views, so
2653
- // removing the child views doesn't retrigger repaints
2654
- if (this.getOption('destroyImmediate') === true) {
2655
- this.$el.remove();
2656
- }
2657
- this.regionManager.destroy();
2658
- return Marionette.ItemView.prototype.destroy.apply(this, arguments);
2659
- },
2660
-
2661
- showChildView: function(regionName, view, options) {
2662
- var region = this.getRegion(regionName);
2663
- return region.show.apply(region, _.rest(arguments));
2664
- },
2665
-
2666
- getChildView: function(regionName) {
2667
- return this.getRegion(regionName).currentView;
2668
- },
2669
-
2670
- // Add a single region, by name, to the layoutView
2671
- addRegion: function(name, definition) {
2672
- var regions = {};
2673
- regions[name] = definition;
2674
- return this._buildRegions(regions)[name];
2675
- },
2676
-
2677
- // Add multiple regions as a {name: definition, name2: def2} object literal
2678
- addRegions: function(regions) {
2679
- this.regions = _.extend({}, this.regions, regions);
2680
- return this._buildRegions(regions);
2681
- },
2682
-
2683
- // Remove a single region from the LayoutView, by name
2684
- removeRegion: function(name) {
2685
- delete this.regions[name];
2686
- return this.regionManager.removeRegion(name);
2687
- },
2688
-
2689
- // Provides alternative access to regions
2690
- // Accepts the region name
2691
- // getRegion('main')
2692
- getRegion: function(region) {
2693
- return this.regionManager.get(region);
2694
- },
2695
-
2696
- // Get all regions
2697
- getRegions: function() {
2698
- return this.regionManager.getRegions();
2699
- },
2700
-
2701
- // internal method to build regions
2702
- _buildRegions: function(regions) {
2703
- var defaults = {
2704
- regionClass: this.getOption('regionClass'),
2705
- parentEl: _.partial(_.result, this, 'el')
2706
- };
2707
-
2708
- return this.regionManager.addRegions(regions, defaults);
2709
- },
2710
-
2711
- // Internal method to initialize the regions that have been defined in a
2712
- // `regions` attribute on this layoutView.
2713
- _initializeRegions: function(options) {
2714
- var regions;
2715
- this._initRegionManager();
2716
-
2717
- regions = Marionette._getValue(this.regions, this, [options]) || {};
2718
-
2719
- // Enable users to define `regions` as instance options.
2720
- var regionOptions = this.getOption.call(options, 'regions');
2721
-
2722
- // enable region options to be a function
2723
- regionOptions = Marionette._getValue(regionOptions, this, [options]);
2724
-
2725
- _.extend(regions, regionOptions);
2726
-
2727
- // Normalize region selectors hash to allow
2728
- // a user to use the @ui. syntax.
2729
- regions = this.normalizeUIValues(regions, ['selector', 'el']);
2730
-
2731
- this.addRegions(regions);
2732
- },
2733
-
2734
- // Internal method to re-initialize all of the regions by updating the `el` that
2735
- // they point to
2736
- _reInitializeRegions: function() {
2737
- this.regionManager.invoke('reset');
2738
- },
2739
-
2740
- // Enable easy overriding of the default `RegionManager`
2741
- // for customized region interactions and business specific
2742
- // view logic for better control over single regions.
2743
- getRegionManager: function() {
2744
- return new Marionette.RegionManager();
2745
- },
2746
-
2747
- // Internal method to initialize the region manager
2748
- // and all regions in it
2749
- _initRegionManager: function() {
2750
- this.regionManager = this.getRegionManager();
2751
- this.regionManager._parent = this;
2752
-
2753
- this.listenTo(this.regionManager, 'before:add:region', function(name) {
2754
- this.triggerMethod('before:add:region', name);
2755
- });
2756
-
2757
- this.listenTo(this.regionManager, 'add:region', function(name, region) {
2758
- this[name] = region;
2759
- this.triggerMethod('add:region', name, region);
2760
- });
2761
-
2762
- this.listenTo(this.regionManager, 'before:remove:region', function(name) {
2763
- this.triggerMethod('before:remove:region', name);
2764
- });
2765
-
2766
- this.listenTo(this.regionManager, 'remove:region', function(name, region) {
2767
- delete this[name];
2768
- this.triggerMethod('remove:region', name, region);
2769
- });
2770
- },
2771
-
2772
- _getImmediateChildren: function() {
2773
- return _.chain(this.regionManager.getRegions())
2774
- .pluck('currentView')
2775
- .compact()
2776
- .value();
2777
- }
2778
- });
2779
-
2780
-
2781
- // Behavior
2782
- // --------
2783
-
2784
- // A Behavior is an isolated set of DOM /
2785
- // user interactions that can be mixed into any View.
2786
- // Behaviors allow you to blackbox View specific interactions
2787
- // into portable logical chunks, keeping your views simple and your code DRY.
2788
-
2789
- Marionette.Behavior = Marionette.Object.extend({
2790
- constructor: function(options, view) {
2791
- // Setup reference to the view.
2792
- // this comes in handle when a behavior
2793
- // wants to directly talk up the chain
2794
- // to the view.
2795
- this.view = view;
2796
- this.defaults = _.result(this, 'defaults') || {};
2797
- this.options = _.extend({}, this.defaults, options);
2798
- // Construct an internal UI hash using
2799
- // the views UI hash and then the behaviors UI hash.
2800
- // This allows the user to use UI hash elements
2801
- // defined in the parent view as well as those
2802
- // defined in the given behavior.
2803
- this.ui = _.extend({}, _.result(view, 'ui'), _.result(this, 'ui'));
2804
-
2805
- Marionette.Object.apply(this, arguments);
2806
- },
2807
-
2808
- // proxy behavior $ method to the view
2809
- // this is useful for doing jquery DOM lookups
2810
- // scoped to behaviors view.
2811
- $: function() {
2812
- return this.view.$.apply(this.view, arguments);
2813
- },
2814
-
2815
- // Stops the behavior from listening to events.
2816
- // Overrides Object#destroy to prevent additional events from being triggered.
2817
- destroy: function() {
2818
- this.stopListening();
2819
-
2820
- return this;
2821
- },
2822
-
2823
- proxyViewProperties: function(view) {
2824
- this.$el = view.$el;
2825
- this.el = view.el;
2826
- }
2827
- });
2828
-
2829
- /* jshint maxlen: 143 */
2830
- // Behaviors
2831
- // ---------
2832
-
2833
- // Behaviors is a utility class that takes care of
2834
- // gluing your behavior instances to their given View.
2835
- // The most important part of this class is that you
2836
- // **MUST** override the class level behaviorsLookup
2837
- // method for things to work properly.
2838
-
2839
- Marionette.Behaviors = (function(Marionette, _) {
2840
- // Borrow event splitter from Backbone
2841
- var delegateEventSplitter = /^(\S+)\s*(.*)$/;
2842
-
2843
- function Behaviors(view, behaviors) {
2844
-
2845
- if (!_.isObject(view.behaviors)) {
2846
- return {};
2847
- }
2848
-
2849
- // Behaviors defined on a view can be a flat object literal
2850
- // or it can be a function that returns an object.
2851
- behaviors = Behaviors.parseBehaviors(view, behaviors || _.result(view, 'behaviors'));
2852
-
2853
- // Wraps several of the view's methods
2854
- // calling the methods first on each behavior
2855
- // and then eventually calling the method on the view.
2856
- Behaviors.wrap(view, behaviors, _.keys(methods));
2857
- return behaviors;
2858
- }
2859
-
2860
- var methods = {
2861
- behaviorTriggers: function(behaviorTriggers, behaviors) {
2862
- var triggerBuilder = new BehaviorTriggersBuilder(this, behaviors);
2863
- return triggerBuilder.buildBehaviorTriggers();
2864
- },
2865
-
2866
- behaviorEvents: function(behaviorEvents, behaviors) {
2867
- var _behaviorsEvents = {};
2868
-
2869
- _.each(behaviors, function(b, i) {
2870
- var _events = {};
2871
- var behaviorEvents = _.clone(_.result(b, 'events')) || {};
2872
-
2873
- // Normalize behavior events hash to allow
2874
- // a user to use the @ui. syntax.
2875
- behaviorEvents = Marionette.normalizeUIKeys(behaviorEvents, getBehaviorsUI(b));
2876
-
2877
- var j = 0;
2878
- _.each(behaviorEvents, function(behaviour, key) {
2879
- var match = key.match(delegateEventSplitter);
2880
-
2881
- // Set event name to be namespaced using the view cid,
2882
- // the behavior index, and the behavior event index
2883
- // to generate a non colliding event namespace
2884
- // http://api.jquery.com/event.namespace/
2885
- var eventName = match[1] + '.' + [this.cid, i, j++, ' '].join('');
2886
- var selector = match[2];
2887
-
2888
- var eventKey = eventName + selector;
2889
- var handler = _.isFunction(behaviour) ? behaviour : b[behaviour];
2890
- if (!handler) { return; }
2891
- _events[eventKey] = _.bind(handler, b);
2892
- }, this);
2893
-
2894
- _behaviorsEvents = _.extend(_behaviorsEvents, _events);
2895
- }, this);
2896
-
2897
- return _behaviorsEvents;
2898
- }
2899
- };
2900
-
2901
- _.extend(Behaviors, {
2902
-
2903
- // Placeholder method to be extended by the user.
2904
- // The method should define the object that stores the behaviors.
2905
- // i.e.
2906
- //
2907
- // ```js
2908
- // Marionette.Behaviors.behaviorsLookup: function() {
2909
- // return App.Behaviors
2910
- // }
2911
- // ```
2912
- behaviorsLookup: function() {
2913
- throw new Marionette.Error({
2914
- message: 'You must define where your behaviors are stored.',
2915
- url: 'marionette.behaviors.html#behaviorslookup'
2916
- });
2917
- },
2918
-
2919
- // Takes care of getting the behavior class
2920
- // given options and a key.
2921
- // If a user passes in options.behaviorClass
2922
- // default to using that. Otherwise delegate
2923
- // the lookup to the users `behaviorsLookup` implementation.
2924
- getBehaviorClass: function(options, key) {
2925
- if (options.behaviorClass) {
2926
- return options.behaviorClass;
2927
- }
2928
-
2929
- // Get behavior class can be either a flat object or a method
2930
- return Marionette._getValue(Behaviors.behaviorsLookup, this, [options, key])[key];
2931
- },
2932
-
2933
- // Iterate over the behaviors object, for each behavior
2934
- // instantiate it and get its grouped behaviors.
2935
- parseBehaviors: function(view, behaviors) {
2936
- return _.chain(behaviors).map(function(options, key) {
2937
- var BehaviorClass = Behaviors.getBehaviorClass(options, key);
2938
-
2939
- var behavior = new BehaviorClass(options, view);
2940
- var nestedBehaviors = Behaviors.parseBehaviors(view, _.result(behavior, 'behaviors'));
2941
-
2942
- return [behavior].concat(nestedBehaviors);
2943
- }).flatten().value();
2944
- },
2945
-
2946
- // Wrap view internal methods so that they delegate to behaviors. For example,
2947
- // `onDestroy` should trigger destroy on all of the behaviors and then destroy itself.
2948
- // i.e.
2949
- //
2950
- // `view.delegateEvents = _.partial(methods.delegateEvents, view.delegateEvents, behaviors);`
2951
- wrap: function(view, behaviors, methodNames) {
2952
- _.each(methodNames, function(methodName) {
2953
- view[methodName] = _.partial(methods[methodName], view[methodName], behaviors);
2954
- });
2955
- }
2956
- });
2957
-
2958
- // Class to build handlers for `triggers` on behaviors
2959
- // for views
2960
- function BehaviorTriggersBuilder(view, behaviors) {
2961
- this._view = view;
2962
- this._behaviors = behaviors;
2963
- this._triggers = {};
2964
- }
2965
-
2966
- _.extend(BehaviorTriggersBuilder.prototype, {
2967
- // Main method to build the triggers hash with event keys and handlers
2968
- buildBehaviorTriggers: function() {
2969
- _.each(this._behaviors, this._buildTriggerHandlersForBehavior, this);
2970
- return this._triggers;
2971
- },
2972
-
2973
- // Internal method to build all trigger handlers for a given behavior
2974
- _buildTriggerHandlersForBehavior: function(behavior, i) {
2975
- var triggersHash = _.clone(_.result(behavior, 'triggers')) || {};
2976
-
2977
- triggersHash = Marionette.normalizeUIKeys(triggersHash, getBehaviorsUI(behavior));
2978
-
2979
- _.each(triggersHash, _.bind(this._setHandlerForBehavior, this, behavior, i));
2980
- },
2981
-
2982
- // Internal method to create and assign the trigger handler for a given
2983
- // behavior
2984
- _setHandlerForBehavior: function(behavior, i, eventName, trigger) {
2985
- // Unique identifier for the `this._triggers` hash
2986
- var triggerKey = trigger.replace(/^\S+/, function(triggerName) {
2987
- return triggerName + '.' + 'behaviortriggers' + i;
2988
- });
2989
-
2990
- this._triggers[triggerKey] = this._view._buildViewTrigger(eventName);
2991
- }
2992
- });
2993
-
2994
- function getBehaviorsUI(behavior) {
2995
- return behavior._uiBindings || behavior.ui;
2996
- }
2997
-
2998
- return Behaviors;
2999
-
3000
- })(Marionette, _);
3001
-
3002
-
3003
- // App Router
3004
- // ----------
3005
-
3006
- // Reduce the boilerplate code of handling route events
3007
- // and then calling a single method on another object.
3008
- // Have your routers configured to call the method on
3009
- // your object, directly.
3010
- //
3011
- // Configure an AppRouter with `appRoutes`.
3012
- //
3013
- // App routers can only take one `controller` object.
3014
- // It is recommended that you divide your controller
3015
- // objects in to smaller pieces of related functionality
3016
- // and have multiple routers / controllers, instead of
3017
- // just one giant router and controller.
3018
- //
3019
- // You can also add standard routes to an AppRouter.
3020
-
3021
- Marionette.AppRouter = Backbone.Router.extend({
3022
-
3023
- constructor: function(options) {
3024
- this.options = options || {};
3025
-
3026
- Backbone.Router.apply(this, arguments);
3027
-
3028
- var appRoutes = this.getOption('appRoutes');
3029
- var controller = this._getController();
3030
- this.processAppRoutes(controller, appRoutes);
3031
- this.on('route', this._processOnRoute, this);
3032
- },
3033
-
3034
- // Similar to route method on a Backbone Router but
3035
- // method is called on the controller
3036
- appRoute: function(route, methodName) {
3037
- var controller = this._getController();
3038
- this._addAppRoute(controller, route, methodName);
3039
- },
3040
-
3041
- // process the route event and trigger the onRoute
3042
- // method call, if it exists
3043
- _processOnRoute: function(routeName, routeArgs) {
3044
- // make sure an onRoute before trying to call it
3045
- if (_.isFunction(this.onRoute)) {
3046
- // find the path that matches the current route
3047
- var routePath = _.invert(this.getOption('appRoutes'))[routeName];
3048
- this.onRoute(routeName, routePath, routeArgs);
3049
- }
3050
- },
3051
-
3052
- // Internal method to process the `appRoutes` for the
3053
- // router, and turn them in to routes that trigger the
3054
- // specified method on the specified `controller`.
3055
- processAppRoutes: function(controller, appRoutes) {
3056
- if (!appRoutes) { return; }
3057
-
3058
- var routeNames = _.keys(appRoutes).reverse(); // Backbone requires reverted order of routes
3059
-
3060
- _.each(routeNames, function(route) {
3061
- this._addAppRoute(controller, route, appRoutes[route]);
3062
- }, this);
3063
- },
3064
-
3065
- _getController: function() {
3066
- return this.getOption('controller');
3067
- },
3068
-
3069
- _addAppRoute: function(controller, route, methodName) {
3070
- var method = controller[methodName];
3071
-
3072
- if (!method) {
3073
- throw new Marionette.Error('Method "' + methodName + '" was not found on the controller');
3074
- }
3075
-
3076
- this.route(route, methodName, _.bind(method, controller));
3077
- },
3078
-
3079
- mergeOptions: Marionette.mergeOptions,
3080
-
3081
- // Proxy `getOption` to enable getting options from this or this.options by name.
3082
- getOption: Marionette.proxyGetOption,
3083
-
3084
- triggerMethod: Marionette.triggerMethod,
3085
-
3086
- bindEntityEvents: Marionette.proxyBindEntityEvents,
3087
-
3088
- unbindEntityEvents: Marionette.proxyUnbindEntityEvents
3089
- });
3090
-
3091
- // Application
3092
- // -----------
3093
-
3094
- // Contain and manage the composite application as a whole.
3095
- // Stores and starts up `Region` objects, includes an
3096
- // event aggregator as `app.vent`
3097
- Marionette.Application = Marionette.Object.extend({
3098
- constructor: function(options) {
3099
- this._initializeRegions(options);
3100
- this._initCallbacks = new Marionette.Callbacks();
3101
- this.submodules = {};
3102
- _.extend(this, options);
3103
- this._initChannel();
3104
- Marionette.Object.apply(this, arguments);
3105
- },
3106
-
3107
- // Command execution, facilitated by Backbone.Wreqr.Commands
3108
- execute: function() {
3109
- this.commands.execute.apply(this.commands, arguments);
3110
- },
3111
-
3112
- // Request/response, facilitated by Backbone.Wreqr.RequestResponse
3113
- request: function() {
3114
- return this.reqres.request.apply(this.reqres, arguments);
3115
- },
3116
-
3117
- // Add an initializer that is either run at when the `start`
3118
- // method is called, or run immediately if added after `start`
3119
- // has already been called.
3120
- addInitializer: function(initializer) {
3121
- this._initCallbacks.add(initializer);
3122
- },
3123
-
3124
- // kick off all of the application's processes.
3125
- // initializes all of the regions that have been added
3126
- // to the app, and runs all of the initializer functions
3127
- start: function(options) {
3128
- this.triggerMethod('before:start', options);
3129
- this._initCallbacks.run(options, this);
3130
- this.triggerMethod('start', options);
3131
- },
3132
-
3133
- // Add regions to your app.
3134
- // Accepts a hash of named strings or Region objects
3135
- // addRegions({something: "#someRegion"})
3136
- // addRegions({something: Region.extend({el: "#someRegion"}) });
3137
- addRegions: function(regions) {
3138
- return this._regionManager.addRegions(regions);
3139
- },
3140
-
3141
- // Empty all regions in the app, without removing them
3142
- emptyRegions: function() {
3143
- return this._regionManager.emptyRegions();
3144
- },
3145
-
3146
- // Removes a region from your app, by name
3147
- // Accepts the regions name
3148
- // removeRegion('myRegion')
3149
- removeRegion: function(region) {
3150
- return this._regionManager.removeRegion(region);
3151
- },
3152
-
3153
- // Provides alternative access to regions
3154
- // Accepts the region name
3155
- // getRegion('main')
3156
- getRegion: function(region) {
3157
- return this._regionManager.get(region);
3158
- },
3159
-
3160
- // Get all the regions from the region manager
3161
- getRegions: function() {
3162
- return this._regionManager.getRegions();
3163
- },
3164
-
3165
- // Create a module, attached to the application
3166
- module: function(moduleNames, moduleDefinition) {
3167
-
3168
- // Overwrite the module class if the user specifies one
3169
- var ModuleClass = Marionette.Module.getClass(moduleDefinition);
3170
-
3171
- var args = _.toArray(arguments);
3172
- args.unshift(this);
3173
-
3174
- // see the Marionette.Module object for more information
3175
- return ModuleClass.create.apply(ModuleClass, args);
3176
- },
3177
-
3178
- // Enable easy overriding of the default `RegionManager`
3179
- // for customized region interactions and business-specific
3180
- // view logic for better control over single regions.
3181
- getRegionManager: function() {
3182
- return new Marionette.RegionManager();
3183
- },
3184
-
3185
- // Internal method to initialize the regions that have been defined in a
3186
- // `regions` attribute on the application instance
3187
- _initializeRegions: function(options) {
3188
- var regions = _.isFunction(this.regions) ? this.regions(options) : this.regions || {};
3189
-
3190
- this._initRegionManager();
3191
-
3192
- // Enable users to define `regions` in instance options.
3193
- var optionRegions = Marionette.getOption(options, 'regions');
3194
-
3195
- // Enable region options to be a function
3196
- if (_.isFunction(optionRegions)) {
3197
- optionRegions = optionRegions.call(this, options);
3198
- }
3199
-
3200
- // Overwrite current regions with those passed in options
3201
- _.extend(regions, optionRegions);
3202
-
3203
- this.addRegions(regions);
3204
-
3205
- return this;
3206
- },
3207
-
3208
- // Internal method to set up the region manager
3209
- _initRegionManager: function() {
3210
- this._regionManager = this.getRegionManager();
3211
- this._regionManager._parent = this;
3212
-
3213
- this.listenTo(this._regionManager, 'before:add:region', function() {
3214
- Marionette._triggerMethod(this, 'before:add:region', arguments);
3215
- });
3216
-
3217
- this.listenTo(this._regionManager, 'add:region', function(name, region) {
3218
- this[name] = region;
3219
- Marionette._triggerMethod(this, 'add:region', arguments);
3220
- });
3221
-
3222
- this.listenTo(this._regionManager, 'before:remove:region', function() {
3223
- Marionette._triggerMethod(this, 'before:remove:region', arguments);
3224
- });
3225
-
3226
- this.listenTo(this._regionManager, 'remove:region', function(name) {
3227
- delete this[name];
3228
- Marionette._triggerMethod(this, 'remove:region', arguments);
3229
- });
3230
- },
3231
-
3232
- // Internal method to setup the Wreqr.radio channel
3233
- _initChannel: function() {
3234
- this.channelName = _.result(this, 'channelName') || 'global';
3235
- this.channel = _.result(this, 'channel') || Backbone.Wreqr.radio.channel(this.channelName);
3236
- this.vent = _.result(this, 'vent') || this.channel.vent;
3237
- this.commands = _.result(this, 'commands') || this.channel.commands;
3238
- this.reqres = _.result(this, 'reqres') || this.channel.reqres;
3239
- }
3240
- });
3241
-
3242
- /* jshint maxparams: 9 */
3243
-
3244
- // Module
3245
- // ------
3246
-
3247
- // A simple module system, used to create privacy and encapsulation in
3248
- // Marionette applications
3249
- Marionette.Module = function(moduleName, app, options) {
3250
- this.moduleName = moduleName;
3251
- this.options = _.extend({}, this.options, options);
3252
- // Allow for a user to overide the initialize
3253
- // for a given module instance.
3254
- this.initialize = options.initialize || this.initialize;
3255
-
3256
- // Set up an internal store for sub-modules.
3257
- this.submodules = {};
3258
-
3259
- this._setupInitializersAndFinalizers();
3260
-
3261
- // Set an internal reference to the app
3262
- // within a module.
3263
- this.app = app;
3264
-
3265
- if (_.isFunction(this.initialize)) {
3266
- this.initialize(moduleName, app, this.options);
3267
- }
3268
- };
3269
-
3270
- Marionette.Module.extend = Marionette.extend;
3271
-
3272
- // Extend the Module prototype with events / listenTo, so that the module
3273
- // can be used as an event aggregator or pub/sub.
3274
- _.extend(Marionette.Module.prototype, Backbone.Events, {
3275
-
3276
- // By default modules start with their parents.
3277
- startWithParent: true,
3278
-
3279
- // Initialize is an empty function by default. Override it with your own
3280
- // initialization logic when extending Marionette.Module.
3281
- initialize: function() {},
3282
-
3283
- // Initializer for a specific module. Initializers are run when the
3284
- // module's `start` method is called.
3285
- addInitializer: function(callback) {
3286
- this._initializerCallbacks.add(callback);
3287
- },
3288
-
3289
- // Finalizers are run when a module is stopped. They are used to teardown
3290
- // and finalize any variables, references, events and other code that the
3291
- // module had set up.
3292
- addFinalizer: function(callback) {
3293
- this._finalizerCallbacks.add(callback);
3294
- },
3295
-
3296
- // Start the module, and run all of its initializers
3297
- start: function(options) {
3298
- // Prevent re-starting a module that is already started
3299
- if (this._isInitialized) { return; }
3300
-
3301
- // start the sub-modules (depth-first hierarchy)
3302
- _.each(this.submodules, function(mod) {
3303
- // check to see if we should start the sub-module with this parent
3304
- if (mod.startWithParent) {
3305
- mod.start(options);
3306
- }
3307
- });
3308
-
3309
- // run the callbacks to "start" the current module
3310
- this.triggerMethod('before:start', options);
3311
-
3312
- this._initializerCallbacks.run(options, this);
3313
- this._isInitialized = true;
3314
-
3315
- this.triggerMethod('start', options);
3316
- },
3317
-
3318
- // Stop this module by running its finalizers and then stop all of
3319
- // the sub-modules for this module
3320
- stop: function() {
3321
- // if we are not initialized, don't bother finalizing
3322
- if (!this._isInitialized) { return; }
3323
- this._isInitialized = false;
3324
-
3325
- this.triggerMethod('before:stop');
3326
-
3327
- // stop the sub-modules; depth-first, to make sure the
3328
- // sub-modules are stopped / finalized before parents
3329
- _.invoke(this.submodules, 'stop');
3330
-
3331
- // run the finalizers
3332
- this._finalizerCallbacks.run(undefined, this);
3333
-
3334
- // reset the initializers and finalizers
3335
- this._initializerCallbacks.reset();
3336
- this._finalizerCallbacks.reset();
3337
-
3338
- this.triggerMethod('stop');
3339
- },
3340
-
3341
- // Configure the module with a definition function and any custom args
3342
- // that are to be passed in to the definition function
3343
- addDefinition: function(moduleDefinition, customArgs) {
3344
- this._runModuleDefinition(moduleDefinition, customArgs);
3345
- },
3346
-
3347
- // Internal method: run the module definition function with the correct
3348
- // arguments
3349
- _runModuleDefinition: function(definition, customArgs) {
3350
- // If there is no definition short circut the method.
3351
- if (!definition) { return; }
3352
-
3353
- // build the correct list of arguments for the module definition
3354
- var args = _.flatten([
3355
- this,
3356
- this.app,
3357
- Backbone,
3358
- Marionette,
3359
- Backbone.$, _,
3360
- customArgs
3361
- ]);
3362
-
3363
- definition.apply(this, args);
3364
- },
3365
-
3366
- // Internal method: set up new copies of initializers and finalizers.
3367
- // Calling this method will wipe out all existing initializers and
3368
- // finalizers.
3369
- _setupInitializersAndFinalizers: function() {
3370
- this._initializerCallbacks = new Marionette.Callbacks();
3371
- this._finalizerCallbacks = new Marionette.Callbacks();
3372
- },
3373
-
3374
- // import the `triggerMethod` to trigger events with corresponding
3375
- // methods if the method exists
3376
- triggerMethod: Marionette.triggerMethod
3377
- });
3378
-
3379
- // Class methods to create modules
3380
- _.extend(Marionette.Module, {
3381
-
3382
- // Create a module, hanging off the app parameter as the parent object.
3383
- create: function(app, moduleNames, moduleDefinition) {
3384
- var module = app;
3385
-
3386
- // get the custom args passed in after the module definition and
3387
- // get rid of the module name and definition function
3388
- var customArgs = _.drop(arguments, 3);
3389
-
3390
- // Split the module names and get the number of submodules.
3391
- // i.e. an example module name of `Doge.Wow.Amaze` would
3392
- // then have the potential for 3 module definitions.
3393
- moduleNames = moduleNames.split('.');
3394
- var length = moduleNames.length;
3395
-
3396
- // store the module definition for the last module in the chain
3397
- var moduleDefinitions = [];
3398
- moduleDefinitions[length - 1] = moduleDefinition;
3399
-
3400
- // Loop through all the parts of the module definition
3401
- _.each(moduleNames, function(moduleName, i) {
3402
- var parentModule = module;
3403
- module = this._getModule(parentModule, moduleName, app, moduleDefinition);
3404
- this._addModuleDefinition(parentModule, module, moduleDefinitions[i], customArgs);
3405
- }, this);
3406
-
3407
- // Return the last module in the definition chain
3408
- return module;
3409
- },
3410
-
3411
- _getModule: function(parentModule, moduleName, app, def, args) {
3412
- var options = _.extend({}, def);
3413
- var ModuleClass = this.getClass(def);
3414
-
3415
- // Get an existing module of this name if we have one
3416
- var module = parentModule[moduleName];
3417
-
3418
- if (!module) {
3419
- // Create a new module if we don't have one
3420
- module = new ModuleClass(moduleName, app, options);
3421
- parentModule[moduleName] = module;
3422
- // store the module on the parent
3423
- parentModule.submodules[moduleName] = module;
3424
- }
3425
-
3426
- return module;
3427
- },
3428
-
3429
- // ## Module Classes
3430
- //
3431
- // Module classes can be used as an alternative to the define pattern.
3432
- // The extend function of a Module is identical to the extend functions
3433
- // on other Backbone and Marionette classes.
3434
- // This allows module lifecyle events like `onStart` and `onStop` to be called directly.
3435
- getClass: function(moduleDefinition) {
3436
- var ModuleClass = Marionette.Module;
3437
-
3438
- if (!moduleDefinition) {
3439
- return ModuleClass;
3440
- }
3441
-
3442
- // If all of the module's functionality is defined inside its class,
3443
- // then the class can be passed in directly. `MyApp.module("Foo", FooModule)`.
3444
- if (moduleDefinition.prototype instanceof ModuleClass) {
3445
- return moduleDefinition;
3446
- }
3447
-
3448
- return moduleDefinition.moduleClass || ModuleClass;
3449
- },
3450
-
3451
- // Add the module definition and add a startWithParent initializer function.
3452
- // This is complicated because module definitions are heavily overloaded
3453
- // and support an anonymous function, module class, or options object
3454
- _addModuleDefinition: function(parentModule, module, def, args) {
3455
- var fn = this._getDefine(def);
3456
- var startWithParent = this._getStartWithParent(def, module);
3457
-
3458
- if (fn) {
3459
- module.addDefinition(fn, args);
3460
- }
3461
-
3462
- this._addStartWithParent(parentModule, module, startWithParent);
3463
- },
3464
-
3465
- _getStartWithParent: function(def, module) {
3466
- var swp;
3467
-
3468
- if (_.isFunction(def) && (def.prototype instanceof Marionette.Module)) {
3469
- swp = module.constructor.prototype.startWithParent;
3470
- return _.isUndefined(swp) ? true : swp;
3471
- }
3472
-
3473
- if (_.isObject(def)) {
3474
- swp = def.startWithParent;
3475
- return _.isUndefined(swp) ? true : swp;
3476
- }
3477
-
3478
- return true;
3479
- },
3480
-
3481
- _getDefine: function(def) {
3482
- if (_.isFunction(def) && !(def.prototype instanceof Marionette.Module)) {
3483
- return def;
3484
- }
3485
-
3486
- if (_.isObject(def)) {
3487
- return def.define;
3488
- }
3489
-
3490
- return null;
3491
- },
3492
-
3493
- _addStartWithParent: function(parentModule, module, startWithParent) {
3494
- module.startWithParent = module.startWithParent && startWithParent;
3495
-
3496
- if (!module.startWithParent || !!module.startWithParentIsConfigured) {
3497
- return;
3498
- }
3499
-
3500
- module.startWithParentIsConfigured = true;
3501
-
3502
- parentModule.addInitializer(function(options) {
3503
- if (module.startWithParent) {
3504
- module.start(options);
3505
- }
3506
- });
3507
- }
3508
- });
3509
-
3510
-
3511
- return Marionette;
3512
- }));
1
+ 404: Not Found