marionette-rails 0.8.4 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- data/vendor/assets/javascripts/backbone.marionette.js +937 -885
- metadata +2 -2
@@ -1,1013 +1,1065 @@
|
|
1
|
-
// Backbone.Marionette v0.
|
1
|
+
// Backbone.Marionette v0.9.0
|
2
2
|
//
|
3
3
|
// Copyright (C)2012 Derick Bailey, Muted Solutions, LLC
|
4
4
|
// Distributed Under MIT License
|
5
5
|
//
|
6
6
|
// Documentation and Full License Available at:
|
7
7
|
// http://github.com/derickbailey/backbone.marionette
|
8
|
+
|
8
9
|
Backbone.Marionette = (function(Backbone, _, $){
|
9
10
|
var Marionette = {};
|
10
11
|
|
11
|
-
|
12
|
-
|
13
|
-
// Marionette.View
|
14
|
-
// ---------------
|
15
|
-
|
16
|
-
// The core view type that other Marionette views extend from.
|
17
|
-
Marionette.View = Backbone.View.extend({
|
18
|
-
// Get the template or template id/selector for this view
|
19
|
-
// instance. You can set a `template` attribute in the view
|
20
|
-
// definition or pass a `template: "whatever"` parameter in
|
21
|
-
// to the constructor options. The `template` can also be
|
22
|
-
// a function that returns a selector string.
|
23
|
-
getTemplateSelector: function(){
|
24
|
-
var template;
|
25
|
-
|
26
|
-
// Get the template from `this.options.template` or
|
27
|
-
// `this.template`. The `options` takes precedence.
|
28
|
-
if (this.options && this.options.template){
|
29
|
-
template = this.options.template;
|
30
|
-
} else {
|
31
|
-
template = this.template;
|
32
|
-
}
|
33
|
-
|
34
|
-
// check if it's a function and execute it, if it is
|
35
|
-
if (_.isFunction(template)){
|
36
|
-
template = template.call(this);
|
37
|
-
}
|
38
|
-
|
39
|
-
return template;
|
40
|
-
},
|
41
|
-
|
42
|
-
// Serialize the model or collection for the view. If a model is
|
43
|
-
// found, `.toJSON()` is called. If a collection is found, `.toJSON()`
|
44
|
-
// is also called, but is used to populate an `items` array in the
|
45
|
-
// resulting data. If both are found, defaults to the model.
|
46
|
-
// You can override the `serializeData` method in your own view
|
47
|
-
// definition, to provide custom serialization for your view's data.
|
48
|
-
serializeData: function(){
|
49
|
-
var data;
|
50
|
-
|
51
|
-
if (this.model) {
|
52
|
-
data = this.model.toJSON();
|
53
|
-
}
|
54
|
-
else if (this.collection) {
|
55
|
-
data = { items: this.collection.toJSON() };
|
56
|
-
}
|
12
|
+
// BindTo: Event Binding
|
13
|
+
// ---------------------
|
57
14
|
|
58
|
-
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
// object literal, or a function that returns an object
|
66
|
-
// literal. All methods and attributes from this object
|
67
|
-
// are copies to the object passed in.
|
68
|
-
mixinTemplateHelpers: function(target){
|
69
|
-
target = target || {};
|
70
|
-
var templateHelpers = this.templateHelpers;
|
71
|
-
if (_.isFunction(templateHelpers)){
|
72
|
-
templateHelpers = templateHelpers.call(this);
|
73
|
-
}
|
74
|
-
return _.extend(target, templateHelpers);
|
75
|
-
},
|
15
|
+
// BindTo facilitates the binding and unbinding of events
|
16
|
+
// from objects that extend `Backbone.Events`. It makes
|
17
|
+
// unbinding events, even with anonymous callback functions,
|
18
|
+
// easy.
|
19
|
+
//
|
20
|
+
// Thanks to Johnny Oshika for this code.
|
21
|
+
// http://stackoverflow.com/questions/7567404/backbone-js-repopulate-or-recreate-the-view/7607853#7607853
|
76
22
|
|
77
|
-
|
78
|
-
// events. `triggers: {"click .foo": "do:foo"}`
|
79
|
-
configureTriggers: function(){
|
80
|
-
if (!this.triggers) { return; }
|
23
|
+
Marionette.BindTo = {
|
81
24
|
|
82
|
-
|
83
|
-
|
84
|
-
|
25
|
+
// Store the event binding in array so it can be unbound
|
26
|
+
// easily, at a later point in time.
|
27
|
+
bindTo: function (obj, eventName, callback, context) {
|
28
|
+
context = context || this;
|
29
|
+
obj.on(eventName, callback, context);
|
85
30
|
|
86
|
-
|
87
|
-
if (_.isFunction(triggers)){ triggers = triggers.call(this); }
|
31
|
+
if (!this.bindings) { this.bindings = []; }
|
88
32
|
|
89
|
-
|
90
|
-
|
91
|
-
|
33
|
+
var binding = {
|
34
|
+
obj: obj,
|
35
|
+
eventName: eventName,
|
36
|
+
callback: callback,
|
37
|
+
context: context
|
38
|
+
}
|
92
39
|
|
93
|
-
|
94
|
-
if (e && e.preventDefault){ e.preventDefault(); }
|
95
|
-
if (e && e.stopPropagation){ e.stopPropagation(); }
|
96
|
-
that.trigger(value);
|
97
|
-
}
|
40
|
+
this.bindings.push(binding);
|
98
41
|
|
99
|
-
|
42
|
+
return binding;
|
43
|
+
},
|
100
44
|
|
101
|
-
|
102
|
-
|
45
|
+
// Unbind from a single binding object. Binding objects are
|
46
|
+
// returned from the `bindTo` method call.
|
47
|
+
unbindFrom: function(binding){
|
48
|
+
binding.obj.off(binding.eventName, binding.callback, binding.context);
|
49
|
+
this.bindings = _.reject(this.bindings, function(bind){return bind === binding});
|
50
|
+
},
|
103
51
|
|
104
|
-
|
105
|
-
|
106
|
-
|
52
|
+
// Unbind all of the events that we have stored.
|
53
|
+
unbindAll: function () {
|
54
|
+
var that = this;
|
107
55
|
|
108
|
-
|
109
|
-
|
110
|
-
|
56
|
+
// The `unbindFrom` call removes elements from the array
|
57
|
+
// while it is being iterated, so clone it first.
|
58
|
+
var bindings = _.map(this.bindings, _.identity);
|
59
|
+
_.each(bindings, function (binding, index) {
|
60
|
+
that.unbindFrom(binding);
|
61
|
+
});
|
62
|
+
}
|
63
|
+
};
|
64
|
+
|
65
|
+
|
66
|
+
// Marionette.View
|
67
|
+
// ---------------
|
68
|
+
|
69
|
+
// The core view type that other Marionette views extend from.
|
70
|
+
Marionette.View = Backbone.View.extend({
|
71
|
+
constructor: function(){
|
72
|
+
Backbone.View.prototype.constructor.apply(this, arguments);
|
73
|
+
this.bindTo(this, "show", this.onShowCalled, this);
|
74
|
+
},
|
75
|
+
|
76
|
+
// Get the template for this view
|
77
|
+
// instance. You can set a `template` attribute in the view
|
78
|
+
// definition or pass a `template: "whatever"` parameter in
|
79
|
+
// to the constructor options.
|
80
|
+
getTemplate: function(){
|
81
|
+
var template;
|
82
|
+
|
83
|
+
// Get the template from `this.options.template` or
|
84
|
+
// `this.template`. The `options` takes precedence.
|
85
|
+
if (this.options && this.options.template){
|
86
|
+
template = this.options.template;
|
87
|
+
} else {
|
88
|
+
template = this.template;
|
89
|
+
}
|
111
90
|
|
112
|
-
|
113
|
-
|
91
|
+
return template;
|
92
|
+
},
|
114
93
|
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
94
|
+
// Serialize the model or collection for the view. If a model is
|
95
|
+
// found, `.toJSON()` is called. If a collection is found, `.toJSON()`
|
96
|
+
// is also called, but is used to populate an `items` array in the
|
97
|
+
// resulting data. If both are found, defaults to the model.
|
98
|
+
// You can override the `serializeData` method in your own view
|
99
|
+
// definition, to provide custom serialization for your view's data.
|
100
|
+
serializeData: function(){
|
101
|
+
var data;
|
121
102
|
|
122
|
-
|
123
|
-
this.
|
103
|
+
if (this.model) {
|
104
|
+
data = this.model.toJSON();
|
105
|
+
}
|
106
|
+
else if (this.collection) {
|
107
|
+
data = { items: this.collection.toJSON() };
|
108
|
+
}
|
124
109
|
|
125
|
-
|
126
|
-
|
127
|
-
|
110
|
+
data = this.mixinTemplateHelpers(data);
|
111
|
+
|
112
|
+
return data;
|
113
|
+
},
|
114
|
+
|
115
|
+
// Mix in template helper methods. Looks for a
|
116
|
+
// `templateHelpers` attribute, which can either be an
|
117
|
+
// object literal, or a function that returns an object
|
118
|
+
// literal. All methods and attributes from this object
|
119
|
+
// are copies to the object passed in.
|
120
|
+
mixinTemplateHelpers: function(target){
|
121
|
+
target = target || {};
|
122
|
+
var templateHelpers = this.templateHelpers;
|
123
|
+
if (_.isFunction(templateHelpers)){
|
124
|
+
templateHelpers = templateHelpers.call(this);
|
128
125
|
}
|
129
|
-
|
126
|
+
return _.extend(target, templateHelpers);
|
127
|
+
},
|
130
128
|
|
131
|
-
//
|
132
|
-
//
|
133
|
-
|
134
|
-
|
135
|
-
|
136
|
-
|
137
|
-
|
138
|
-
|
139
|
-
var args = slice.call(arguments);
|
140
|
-
Marionette.View.prototype.constructor.apply(this, args);
|
129
|
+
// Configure `triggers` to forward DOM events to view
|
130
|
+
// events. `triggers: {"click .foo": "do:foo"}`
|
131
|
+
configureTriggers: function(){
|
132
|
+
if (!this.triggers) { return; }
|
133
|
+
|
134
|
+
var triggers = this.triggers;
|
135
|
+
var that = this;
|
136
|
+
var triggerEvents = {};
|
141
137
|
|
142
|
-
|
138
|
+
// Allow `triggers` to be configured as a function
|
139
|
+
if (_.isFunction(triggers)){ triggers = triggers.call(this); }
|
143
140
|
|
144
|
-
|
145
|
-
|
141
|
+
// Configure the triggers, prevent default
|
142
|
+
// action and stop propagation of DOM events
|
143
|
+
_.each(triggers, function(value, key){
|
146
144
|
|
147
|
-
|
148
|
-
|
149
|
-
|
150
|
-
|
151
|
-
if (this.collection){
|
152
|
-
this.bindTo(this.collection, "reset", this.render, this);
|
145
|
+
triggerEvents[key] = function(e){
|
146
|
+
if (e && e.preventDefault){ e.preventDefault(); }
|
147
|
+
if (e && e.stopPropagation){ e.stopPropagation(); }
|
148
|
+
that.trigger(value);
|
153
149
|
}
|
154
|
-
},
|
155
150
|
|
156
|
-
|
157
|
-
// You can override this in your view definition.
|
158
|
-
render: function(){
|
159
|
-
var that = this;
|
151
|
+
});
|
160
152
|
|
161
|
-
|
153
|
+
return triggerEvents;
|
154
|
+
},
|
162
155
|
|
163
|
-
|
164
|
-
|
165
|
-
|
156
|
+
// Overriding Backbone.View's delegateEvents specifically
|
157
|
+
// to handle the `triggers` configuration
|
158
|
+
delegateEvents: function(events){
|
159
|
+
events = events || this.events;
|
160
|
+
if (_.isFunction(events)){ events = events.call(this)}
|
166
161
|
|
167
|
-
|
168
|
-
|
169
|
-
|
162
|
+
var combinedEvents = {};
|
163
|
+
var triggers = this.configureTriggers();
|
164
|
+
_.extend(combinedEvents, events, triggers);
|
170
165
|
|
171
|
-
|
172
|
-
|
173
|
-
$.when(asyncRender).then(templateRendered);
|
174
|
-
}
|
166
|
+
Backbone.View.prototype.delegateEvents.call(this, combinedEvents);
|
167
|
+
},
|
175
168
|
|
176
|
-
|
177
|
-
|
178
|
-
callDeferredMethod(that.onRender, onRenderDone, that);
|
179
|
-
}
|
169
|
+
// Internal method, handles the `show` event.
|
170
|
+
onShowCalled: function(){},
|
180
171
|
|
181
|
-
|
182
|
-
|
183
|
-
|
172
|
+
// Default `close` implementation, for removing a view from the
|
173
|
+
// DOM and unbinding it. Regions will call this method
|
174
|
+
// for you. You can specify an `onClose` method in your view to
|
175
|
+
// add custom code that is called after the view is closed.
|
176
|
+
close: function(){
|
177
|
+
if (this.beforeClose) { this.beforeClose(); }
|
184
178
|
|
185
|
-
|
186
|
-
}
|
179
|
+
this.remove();
|
187
180
|
|
188
|
-
|
189
|
-
|
190
|
-
|
191
|
-
|
192
|
-
|
193
|
-
|
194
|
-
|
195
|
-
|
196
|
-
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
|
202
|
-
|
203
|
-
|
204
|
-
|
205
|
-
|
206
|
-
|
181
|
+
if (this.onClose) { this.onClose(); }
|
182
|
+
this.trigger('close');
|
183
|
+
this.unbindAll();
|
184
|
+
this.unbind();
|
185
|
+
}
|
186
|
+
});
|
187
|
+
|
188
|
+
// Copy the features of `BindTo`
|
189
|
+
_.extend(Marionette.View.prototype, Marionette.BindTo);
|
190
|
+
|
191
|
+
// Item View
|
192
|
+
// ---------
|
193
|
+
|
194
|
+
// A single item view implementation that contains code for rendering
|
195
|
+
// with underscore.js templates, serializing the view's model or collection,
|
196
|
+
// and calling several methods on extended views, such as `onRender`.
|
197
|
+
Marionette.ItemView = Marionette.View.extend({
|
198
|
+
constructor: function(){
|
199
|
+
Marionette.View.prototype.constructor.apply(this, arguments);
|
200
|
+
this.initialEvents();
|
201
|
+
},
|
202
|
+
|
203
|
+
// Configured the initial events that the item view
|
204
|
+
// binds to. Override this method to prevent the initial
|
205
|
+
// events, or to add your own initial events.
|
206
|
+
initialEvents: function(){
|
207
|
+
if (this.collection){
|
208
|
+
this.bindTo(this.collection, "reset", this.render, this);
|
207
209
|
}
|
208
|
-
}
|
209
|
-
|
210
|
-
//
|
211
|
-
//
|
212
|
-
|
213
|
-
//
|
214
|
-
//
|
215
|
-
|
216
|
-
|
217
|
-
|
218
|
-
|
219
|
-
|
220
|
-
|
221
|
-
|
222
|
-
|
223
|
-
|
224
|
-
|
225
|
-
|
226
|
-
|
227
|
-
|
228
|
-
|
229
|
-
|
230
|
-
|
231
|
-
|
232
|
-
|
233
|
-
|
234
|
-
|
235
|
-
|
236
|
-
|
237
|
-
|
238
|
-
|
239
|
-
|
240
|
-
|
241
|
-
|
242
|
-
|
243
|
-
|
244
|
-
|
245
|
-
|
246
|
-
|
247
|
-
|
248
|
-
|
249
|
-
|
250
|
-
|
251
|
-
|
252
|
-
|
253
|
-
|
254
|
-
|
255
|
-
|
256
|
-
|
210
|
+
},
|
211
|
+
|
212
|
+
// Render the view, defaulting to underscore.js templates.
|
213
|
+
// You can override this in your view definition to provide
|
214
|
+
// a very specific rendering for your view. In general, though,
|
215
|
+
// you should override the `Marionette.Renderer` object to
|
216
|
+
// change how Marionette renders views.
|
217
|
+
render: function(){
|
218
|
+
if (this.beforeRender){ this.beforeRender(); }
|
219
|
+
this.trigger("before:render", this);
|
220
|
+
this.trigger("item:before:render", this);
|
221
|
+
|
222
|
+
var data = this.serializeData();
|
223
|
+
var template = this.getTemplate();
|
224
|
+
var html = Marionette.Renderer.render(template, data);
|
225
|
+
this.$el.html(html);
|
226
|
+
|
227
|
+
if (this.onRender){ this.onRender(); }
|
228
|
+
this.trigger("render", this);
|
229
|
+
this.trigger("item:rendered", this);
|
230
|
+
},
|
231
|
+
|
232
|
+
// Override the default close event to add a few
|
233
|
+
// more events that are triggered.
|
234
|
+
close: function(){
|
235
|
+
this.trigger('item:before:close');
|
236
|
+
Marionette.View.prototype.close.apply(this, arguments);
|
237
|
+
this.trigger('item:closed');
|
238
|
+
}
|
239
|
+
});
|
240
|
+
|
241
|
+
// Collection View
|
242
|
+
// ---------------
|
243
|
+
|
244
|
+
// A view that iterates over a Backbone.Collection
|
245
|
+
// and renders an individual ItemView for each model.
|
246
|
+
Marionette.CollectionView = Marionette.View.extend({
|
247
|
+
constructor: function(){
|
248
|
+
Marionette.View.prototype.constructor.apply(this, arguments);
|
249
|
+
this.initChildViewStorage();
|
250
|
+
this.initialEvents();
|
251
|
+
this.onShowCallbacks = new Marionette.Callbacks();
|
252
|
+
},
|
253
|
+
|
254
|
+
// Configured the initial events that the collection view
|
255
|
+
// binds to. Override this method to prevent the initial
|
256
|
+
// events, or to add your own initial events.
|
257
|
+
initialEvents: function(){
|
258
|
+
if (this.collection){
|
259
|
+
this.bindTo(this.collection, "add", this.addChildView, this);
|
260
|
+
this.bindTo(this.collection, "remove", this.removeItemView, this);
|
261
|
+
this.bindTo(this.collection, "reset", this.render, this);
|
262
|
+
}
|
263
|
+
},
|
264
|
+
|
265
|
+
// Handle a child item added to the collection
|
266
|
+
addChildView: function(item, collection, options){
|
267
|
+
var ItemView = this.getItemView();
|
268
|
+
return this.addItemView(item, ItemView, options.index);
|
269
|
+
},
|
270
|
+
|
271
|
+
// Override from `Marionette.View` to guarantee the `onShow` method
|
272
|
+
// of child views is called.
|
273
|
+
onShowCalled: function(){
|
274
|
+
this.onShowCallbacks.run();
|
275
|
+
},
|
276
|
+
|
277
|
+
// Loop through all of the items and render
|
278
|
+
// each of them with the specified `itemView`.
|
279
|
+
render: function(){
|
280
|
+
var that = this;
|
281
|
+
var ItemView = this.getItemView();
|
282
|
+
var EmptyView = this.options.emptyView || this.emptyView;
|
283
|
+
|
284
|
+
if (this.beforeRender) { this.beforeRender(); }
|
285
|
+
this.trigger("collection:before:render", this);
|
286
|
+
|
287
|
+
this.closeChildren();
|
288
|
+
|
289
|
+
if (this.collection) {
|
290
|
+
if (this.collection.length === 0 && EmptyView) {
|
291
|
+
this.addItemView(new Backbone.Model(), EmptyView, 0);
|
292
|
+
} else {
|
293
|
+
this.collection.each(function(item, index){
|
294
|
+
that.addItemView(item, ItemView, index);
|
257
295
|
});
|
258
296
|
}
|
297
|
+
}
|
259
298
|
|
260
|
-
|
261
|
-
|
262
|
-
|
263
|
-
});
|
299
|
+
if (this.onRender) { this.onRender(); }
|
300
|
+
this.trigger("collection:rendered", this);
|
301
|
+
},
|
264
302
|
|
265
|
-
|
266
|
-
|
267
|
-
|
303
|
+
// Retrieve the itemView type, either from `this.options.itemView`
|
304
|
+
// or from the `itemView` in the object definition. The "options"
|
305
|
+
// takes precedence.
|
306
|
+
getItemView: function(){
|
307
|
+
var itemView = this.options.itemView || this.itemView;
|
268
308
|
|
269
|
-
|
270
|
-
|
309
|
+
if (!itemView){
|
310
|
+
var err = new Error("An `itemView` must be specified");
|
311
|
+
err.name = "NoItemViewError";
|
312
|
+
throw err;
|
313
|
+
}
|
271
314
|
|
272
|
-
|
273
|
-
|
274
|
-
// takes precedence.
|
275
|
-
getItemView: function(){
|
276
|
-
var itemView = this.options.itemView || this.itemView;
|
315
|
+
return itemView;
|
316
|
+
},
|
277
317
|
|
278
|
-
|
279
|
-
|
280
|
-
|
281
|
-
|
282
|
-
}
|
318
|
+
// Render the child item's view and add it to the
|
319
|
+
// HTML for the collection view.
|
320
|
+
addItemView: function(item, ItemView, index){
|
321
|
+
var that = this;
|
283
322
|
|
284
|
-
|
285
|
-
},
|
323
|
+
var view = this.buildItemView(item, ItemView);
|
286
324
|
|
287
|
-
//
|
288
|
-
//
|
289
|
-
|
290
|
-
|
325
|
+
// Store the child view itself so we can properly
|
326
|
+
// remove and/or close it later
|
327
|
+
this.storeChild(view);
|
328
|
+
if (this.onItemAdded){ this.onItemAdded(view); }
|
329
|
+
this.trigger("item:added", view);
|
291
330
|
|
292
|
-
|
293
|
-
|
331
|
+
// Render it and show it
|
332
|
+
var renderResult = this.renderItemView(view, index);
|
294
333
|
|
295
|
-
|
296
|
-
|
297
|
-
|
298
|
-
|
299
|
-
args[0] = "itemview:" + args[0];
|
300
|
-
args.splice(1, 0, view);
|
334
|
+
// call onShow for child item views
|
335
|
+
if (view.onShow){
|
336
|
+
this.onShowCallbacks.add(view.onShow, view);
|
337
|
+
}
|
301
338
|
|
302
|
-
|
303
|
-
|
339
|
+
// Forward all child item view events through the parent,
|
340
|
+
// prepending "itemview:" to the event name
|
341
|
+
var childBinding = this.bindTo(view, "all", function(){
|
342
|
+
var args = slice.call(arguments);
|
343
|
+
args[0] = "itemview:" + args[0];
|
344
|
+
args.splice(1, 0, view);
|
304
345
|
|
305
|
-
|
306
|
-
|
346
|
+
that.trigger.apply(that, args);
|
347
|
+
});
|
307
348
|
|
308
|
-
|
309
|
-
|
310
|
-
|
311
|
-
|
312
|
-
|
313
|
-
return viewRendered;
|
314
|
-
},
|
315
|
-
|
316
|
-
// Build an `itemView` for every model in the collection.
|
317
|
-
buildItemView: function(item, ItemView){
|
318
|
-
var view = new ItemView({
|
319
|
-
model: item
|
320
|
-
});
|
321
|
-
return view;
|
322
|
-
},
|
323
|
-
|
324
|
-
// Remove the child view and close it
|
325
|
-
removeItemView: function(item){
|
326
|
-
var view = this.children[item.cid];
|
327
|
-
if (view){
|
328
|
-
view.close();
|
329
|
-
delete this.children[item.cid];
|
330
|
-
}
|
331
|
-
this.trigger("item:removed", view);
|
332
|
-
},
|
333
|
-
|
334
|
-
// Append the HTML to the collection's `el`.
|
335
|
-
// Override this method to do something other
|
336
|
-
// then `.append`.
|
337
|
-
appendHtml: function(collectionView, itemView){
|
338
|
-
collectionView.$el.append(itemView.el);
|
339
|
-
},
|
340
|
-
|
341
|
-
// Store references to all of the child `itemView`
|
342
|
-
// instances so they can be managed and cleaned up, later.
|
343
|
-
storeChild: function(view){
|
344
|
-
if (!this.children){
|
345
|
-
this.children = {};
|
346
|
-
}
|
347
|
-
this.children[view.model.cid] = view;
|
348
|
-
},
|
349
|
+
// Store all child event bindings so we can unbind
|
350
|
+
// them when removing / closing the child view
|
351
|
+
this.childBindings = this.childBindings || {};
|
352
|
+
this.childBindings[view.cid] = childBinding;
|
349
353
|
|
350
|
-
|
351
|
-
|
352
|
-
|
353
|
-
|
354
|
-
|
355
|
-
|
356
|
-
|
357
|
-
|
358
|
-
|
359
|
-
|
360
|
-
|
361
|
-
|
362
|
-
|
363
|
-
|
354
|
+
return renderResult;
|
355
|
+
},
|
356
|
+
|
357
|
+
// render the item view
|
358
|
+
renderItemView: function(view, index) {
|
359
|
+
view.render();
|
360
|
+
this.appendHtml(this, view, index);
|
361
|
+
},
|
362
|
+
|
363
|
+
// Build an `itemView` for every model in the collection.
|
364
|
+
buildItemView: function(item, ItemView){
|
365
|
+
var itemViewOptions = _.result(this, "itemViewOptions");
|
366
|
+
var options = _.extend({model: item}, itemViewOptions);
|
367
|
+
var view = new ItemView(options);
|
368
|
+
return view;
|
369
|
+
},
|
370
|
+
|
371
|
+
// Remove the child view and close it
|
372
|
+
removeItemView: function(item){
|
373
|
+
var view = this.children[item.cid];
|
374
|
+
if (view){
|
375
|
+
var childBinding = this.childBindings[view.cid];
|
376
|
+
if (childBinding) {
|
377
|
+
this.unbindFrom(childBinding);
|
378
|
+
delete this.childBindings[view.cid];
|
364
379
|
}
|
380
|
+
view.close();
|
381
|
+
delete this.children[item.cid];
|
365
382
|
}
|
366
|
-
|
367
|
-
|
368
|
-
|
369
|
-
//
|
370
|
-
|
371
|
-
//
|
372
|
-
|
373
|
-
|
374
|
-
|
375
|
-
|
376
|
-
|
377
|
-
|
378
|
-
|
379
|
-
|
380
|
-
|
381
|
-
|
382
|
-
|
383
|
-
|
384
|
-
|
385
|
-
|
386
|
-
|
387
|
-
|
388
|
-
|
389
|
-
|
390
|
-
|
391
|
-
|
392
|
-
|
393
|
-
|
394
|
-
|
395
|
-
|
396
|
-
|
397
|
-
|
398
|
-
|
399
|
-
|
400
|
-
|
401
|
-
|
402
|
-
|
403
|
-
|
404
|
-
});
|
383
|
+
this.trigger("item:removed", view);
|
384
|
+
},
|
385
|
+
|
386
|
+
// Append the HTML to the collection's `el`.
|
387
|
+
// Override this method to do something other
|
388
|
+
// then `.append`.
|
389
|
+
appendHtml: function(collectionView, itemView, index){
|
390
|
+
collectionView.$el.append(itemView.el);
|
391
|
+
},
|
392
|
+
|
393
|
+
// Store references to all of the child `itemView`
|
394
|
+
// instances so they can be managed and cleaned up, later.
|
395
|
+
storeChild: function(view){
|
396
|
+
this.children[view.model.cid] = view;
|
397
|
+
},
|
398
|
+
|
399
|
+
// Internal method to set up the `children` object for
|
400
|
+
// storing all of the child views
|
401
|
+
initChildViewStorage: function(){
|
402
|
+
this.children = {};
|
403
|
+
},
|
404
|
+
|
405
|
+
// Handle cleanup and other closing needs for
|
406
|
+
// the collection of views.
|
407
|
+
close: function(){
|
408
|
+
this.trigger("collection:before:close");
|
409
|
+
this.closeChildren();
|
410
|
+
Marionette.View.prototype.close.apply(this, arguments);
|
411
|
+
this.trigger("collection:closed");
|
412
|
+
},
|
413
|
+
|
414
|
+
// Close the child views that this collection view
|
415
|
+
// is holding on to, if any
|
416
|
+
closeChildren: function(){
|
417
|
+
var that = this;
|
418
|
+
if (this.children){
|
419
|
+
_.each(_.clone(this.children), function(childView){
|
420
|
+
that.removeItemView(childView.model);
|
405
421
|
});
|
422
|
+
}
|
423
|
+
}
|
424
|
+
});
|
425
|
+
|
426
|
+
|
427
|
+
// Composite View
|
428
|
+
// --------------
|
429
|
+
|
430
|
+
// Used for rendering a branch-leaf, hierarchical structure.
|
431
|
+
// Extends directly from CollectionView and also renders an
|
432
|
+
// an item view as `modelView`, for the top leaf
|
433
|
+
Marionette.CompositeView = Marionette.CollectionView.extend({
|
434
|
+
constructor: function(options){
|
435
|
+
Marionette.CollectionView.apply(this, arguments);
|
436
|
+
this.itemView = this.getItemView();
|
437
|
+
},
|
438
|
+
|
439
|
+
// Configured the initial events that the composite view
|
440
|
+
// binds to. Override this method to prevent the initial
|
441
|
+
// events, or to add your own initial events.
|
442
|
+
initialEvents: function(){
|
443
|
+
if (this.collection){
|
444
|
+
this.bindTo(this.collection, "add", this.addChildView, this);
|
445
|
+
this.bindTo(this.collection, "remove", this.removeItemView, this);
|
446
|
+
this.bindTo(this.collection, "reset", this.renderCollection, this);
|
447
|
+
}
|
448
|
+
},
|
449
|
+
|
450
|
+
// Retrieve the `itemView` to be used when rendering each of
|
451
|
+
// the items in the collection. The default is to return
|
452
|
+
// `this.itemView` or Marionette.CompositeView if no `itemView`
|
453
|
+
// has been defined
|
454
|
+
getItemView: function(){
|
455
|
+
return this.itemView || this.constructor;
|
456
|
+
},
|
457
|
+
|
458
|
+
// Renders the model once, and the collection once. Calling
|
459
|
+
// this again will tell the model's view to re-render itself
|
460
|
+
// but the collection will not re-render.
|
461
|
+
render: function(){
|
462
|
+
var that = this;
|
463
|
+
|
464
|
+
var html = this.renderModel();
|
465
|
+
this.$el.html(html);
|
466
|
+
this.trigger("composite:model:rendered");
|
467
|
+
this.trigger("render");
|
468
|
+
|
469
|
+
this.renderCollection();
|
470
|
+
this.trigger("composite:rendered");
|
471
|
+
},
|
472
|
+
|
473
|
+
// Render the collection for the composite view
|
474
|
+
renderCollection: function(){
|
475
|
+
Marionette.CollectionView.prototype.render.apply(this, arguments);
|
476
|
+
this.trigger("composite:collection:rendered");
|
477
|
+
},
|
478
|
+
|
479
|
+
// Render an individual model, if we have one, as
|
480
|
+
// part of a composite view (branch / leaf). For example:
|
481
|
+
// a treeview.
|
482
|
+
renderModel: function(){
|
483
|
+
var data = {};
|
484
|
+
data = this.serializeData();
|
485
|
+
|
486
|
+
var template = this.getTemplate();
|
487
|
+
return Marionette.Renderer.render(template, data);
|
488
|
+
}
|
489
|
+
});
|
406
490
|
|
407
|
-
compositeRendered.done(function(){
|
408
|
-
that.trigger("composite:rendered");
|
409
|
-
});
|
410
491
|
|
411
|
-
|
412
|
-
|
492
|
+
// Region
|
493
|
+
// ------
|
413
494
|
|
414
|
-
|
415
|
-
|
416
|
-
|
417
|
-
|
418
|
-
this.trigger("composite:collection:rendered");
|
419
|
-
});
|
420
|
-
return collectionDeferred.promise();
|
421
|
-
},
|
422
|
-
|
423
|
-
// Render an individual model, if we have one, as
|
424
|
-
// part of a composite view (branch / leaf). For example:
|
425
|
-
// a treeview.
|
426
|
-
renderModel: function(){
|
427
|
-
var data = {};
|
428
|
-
data = this.serializeData();
|
429
|
-
|
430
|
-
var template = this.getTemplateSelector();
|
431
|
-
return Marionette.Renderer.render(template, data);
|
432
|
-
}
|
433
|
-
});
|
495
|
+
// Manage the visual regions of your composite application. See
|
496
|
+
// http://lostechies.com/derickbailey/2011/12/12/composite-js-apps-regions-and-region-managers/
|
497
|
+
Marionette.Region = function(options){
|
498
|
+
this.options = options || {};
|
434
499
|
|
435
|
-
|
436
|
-
// ------
|
500
|
+
_.extend(this, options);
|
437
501
|
|
438
|
-
|
439
|
-
|
440
|
-
|
441
|
-
|
502
|
+
if (!this.el){
|
503
|
+
var err = new Error("An 'el' must be specified");
|
504
|
+
err.name = "NoElError";
|
505
|
+
throw err;
|
506
|
+
}
|
442
507
|
|
443
|
-
|
508
|
+
if (this.initialize){
|
509
|
+
this.initialize.apply(this, arguments);
|
510
|
+
}
|
511
|
+
};
|
444
512
|
|
445
|
-
|
446
|
-
var err = new Error("An 'el' must be specified");
|
447
|
-
err.name = "NoElError";
|
448
|
-
throw err;
|
449
|
-
}
|
513
|
+
_.extend(Marionette.Region.prototype, Backbone.Events, {
|
450
514
|
|
451
|
-
|
452
|
-
|
453
|
-
|
454
|
-
|
515
|
+
// Displays a backbone view instance inside of the region.
|
516
|
+
// Handles calling the `render` method for you. Reads content
|
517
|
+
// directly from the `el` attribute. Also calls an optional
|
518
|
+
// `onShow` and `close` method on your view, just after showing
|
519
|
+
// or just before closing the view, respectively.
|
520
|
+
show: function(view){
|
521
|
+
var that = this;
|
455
522
|
|
456
|
-
|
523
|
+
this.ensureEl();
|
524
|
+
this.close();
|
457
525
|
|
458
|
-
|
459
|
-
|
460
|
-
// directly from the `el` attribute. Also calls an optional
|
461
|
-
// `onShow` and `close` method on your view, just after showing
|
462
|
-
// or just before closing the view, respectively.
|
463
|
-
show: function(view, appendMethod){
|
464
|
-
this.ensureEl();
|
526
|
+
view.render();
|
527
|
+
this.open(view);
|
465
528
|
|
466
|
-
|
467
|
-
|
529
|
+
if (view.onShow) { view.onShow(); }
|
530
|
+
view.trigger("show");
|
468
531
|
|
469
|
-
|
470
|
-
|
532
|
+
if (this.onShow) { this.onShow(view); }
|
533
|
+
this.trigger("view:show", view);
|
471
534
|
|
472
|
-
|
473
|
-
|
474
|
-
|
475
|
-
|
476
|
-
|
477
|
-
|
478
|
-
// Override this method to change how the region finds the
|
479
|
-
// DOM element that it manages. Return a jQuery selector object.
|
480
|
-
getEl: function(selector){
|
481
|
-
return $(selector);
|
482
|
-
},
|
483
|
-
|
484
|
-
// Internal method to render and display a view. Not meant
|
485
|
-
// to be called from any external code.
|
486
|
-
open: function(view, appendMethod){
|
487
|
-
var that = this;
|
488
|
-
appendMethod = appendMethod || "html";
|
489
|
-
|
490
|
-
$.when(view.render()).then(function () {
|
491
|
-
that.$el[appendMethod](view.el);
|
492
|
-
if (view.onShow) { view.onShow(); }
|
493
|
-
if (that.onShow) { that.onShow(view); }
|
494
|
-
view.trigger("show");
|
495
|
-
that.trigger("view:show", view);
|
496
|
-
});
|
497
|
-
},
|
498
|
-
|
499
|
-
// Close the current view, if there is one. If there is no
|
500
|
-
// current view, it does nothing and returns immediately.
|
501
|
-
close: function(){
|
502
|
-
var view = this.currentView;
|
503
|
-
if (!view){ return; }
|
504
|
-
|
505
|
-
if (view.close) { view.close(); }
|
506
|
-
this.trigger("view:closed", view);
|
507
|
-
|
508
|
-
delete this.currentView;
|
509
|
-
},
|
510
|
-
|
511
|
-
// Attach an existing view to the region. This
|
512
|
-
// will not call `render` or `onShow` for the new view,
|
513
|
-
// and will not replace the current HTML for the `el`
|
514
|
-
// of the region.
|
515
|
-
attachView: function(view){
|
516
|
-
this.currentView = view;
|
535
|
+
this.currentView = view;
|
536
|
+
},
|
537
|
+
|
538
|
+
ensureEl: function(){
|
539
|
+
if (!this.$el || this.$el.length === 0){
|
540
|
+
this.$el = this.getEl(this.el);
|
517
541
|
}
|
518
|
-
}
|
542
|
+
},
|
543
|
+
|
544
|
+
// Override this method to change how the region finds the
|
545
|
+
// DOM element that it manages. Return a jQuery selector object.
|
546
|
+
getEl: function(selector){
|
547
|
+
return $(selector);
|
548
|
+
},
|
549
|
+
|
550
|
+
// Override this method to change how the new view is
|
551
|
+
// appended to the `$el` that the region is managing
|
552
|
+
open: function(view){
|
553
|
+
this.$el.html(view.el);
|
554
|
+
},
|
555
|
+
|
556
|
+
// Close the current view, if there is one. If there is no
|
557
|
+
// current view, it does nothing and returns immediately.
|
558
|
+
close: function(){
|
559
|
+
var view = this.currentView;
|
560
|
+
if (!view){ return; }
|
561
|
+
|
562
|
+
if (view.close) { view.close(); }
|
563
|
+
this.trigger("view:closed", view);
|
564
|
+
|
565
|
+
delete this.currentView;
|
566
|
+
},
|
567
|
+
|
568
|
+
// Attach an existing view to the region. This
|
569
|
+
// will not call `render` or `onShow` for the new view,
|
570
|
+
// and will not replace the current HTML for the `el`
|
571
|
+
// of the region.
|
572
|
+
attachView: function(view){
|
573
|
+
this.currentView = view;
|
574
|
+
}
|
575
|
+
});
|
519
576
|
|
520
|
-
|
521
|
-
|
577
|
+
// Copy the `extend` function used by Backbone's classes
|
578
|
+
Marionette.Region.extend = Backbone.View.extend;
|
522
579
|
|
523
|
-
|
524
|
-
|
525
|
-
// Used for managing application layouts, nested layouts and
|
526
|
-
// multiple regions within an application or sub-application.
|
527
|
-
//
|
528
|
-
// A specialized view type that renders an area of HTML and then
|
529
|
-
// attaches `Region` instances to the specified `regions`.
|
530
|
-
// Used for composite view management and sub-application areas.
|
531
|
-
Marionette.Layout = Marionette.ItemView.extend({
|
532
|
-
constructor: function () {
|
533
|
-
this.vent = new Backbone.Marionette.EventAggregator();
|
534
|
-
Backbone.Marionette.ItemView.apply(this, arguments);
|
535
|
-
this.regionManagers = {};
|
536
|
-
},
|
580
|
+
// Copy the features of `BindTo`
|
581
|
+
_.extend(Marionette.Region.prototype, Marionette.BindTo);
|
537
582
|
|
538
|
-
|
539
|
-
|
540
|
-
return Backbone.Marionette.ItemView.prototype.render.call(this, arguments);
|
541
|
-
},
|
583
|
+
// Layout
|
584
|
+
// ------
|
542
585
|
|
543
|
-
|
586
|
+
// Used for managing application layouts, nested layouts and
|
587
|
+
// multiple regions within an application or sub-application.
|
588
|
+
//
|
589
|
+
// A specialized view type that renders an area of HTML and then
|
590
|
+
// attaches `Region` instances to the specified `regions`.
|
591
|
+
// Used for composite view management and sub-application areas.
|
592
|
+
Marionette.Layout = Marionette.ItemView.extend({
|
593
|
+
constructor: function () {
|
594
|
+
Backbone.Marionette.ItemView.apply(this, arguments);
|
595
|
+
this.initializeRegions();
|
596
|
+
},
|
597
|
+
|
598
|
+
// Layout's render will use the existing region objects the
|
599
|
+
// first time it is called. Subsequent calls will close the
|
600
|
+
// views that the regions are showing and then reset the `el`
|
601
|
+
// for the regions to the newly rendered DOM elements.
|
602
|
+
render: function(){
|
603
|
+
var result = Marionette.ItemView.prototype.render.apply(this, arguments);
|
604
|
+
|
605
|
+
// Rewrite this function to handle re-rendering and
|
606
|
+
// re-initializing the `el` for each region
|
607
|
+
this.render = function(){
|
544
608
|
this.closeRegions();
|
545
|
-
|
546
|
-
|
547
|
-
|
548
|
-
|
549
|
-
// `regions` attribute on this layout. The key of the
|
550
|
-
// hash becomes an attribute on the layout object directly.
|
551
|
-
// For example: `regions: { menu: ".menu-container" }`
|
552
|
-
// will product a `layout.menu` object which is a region
|
553
|
-
// that controls the `.menu-container` DOM element.
|
554
|
-
initializeRegions: function () {
|
555
|
-
var that = this;
|
556
|
-
_.each(this.regions, function (selector, name) {
|
557
|
-
var regionManager = new Backbone.Marionette.Region({
|
558
|
-
el: selector,
|
559
|
-
|
560
|
-
getEl: function(selector){
|
561
|
-
return that.$(selector);
|
562
|
-
}
|
563
|
-
});
|
564
|
-
that.regionManagers[name] = regionManager;
|
565
|
-
that[name] = regionManager;
|
566
|
-
});
|
567
|
-
},
|
568
|
-
|
569
|
-
// Close all of the regions that have been opened by
|
570
|
-
// this layout. This method is called when the layout
|
571
|
-
// itself is closed.
|
572
|
-
closeRegions: function () {
|
573
|
-
var that = this;
|
574
|
-
_.each(this.regionManagers, function (manager, name) {
|
575
|
-
manager.close();
|
576
|
-
delete that[name];
|
577
|
-
});
|
578
|
-
this.regionManagers = {};
|
609
|
+
this.reInitializeRegions();
|
610
|
+
|
611
|
+
var result = Marionette.ItemView.prototype.render.apply(this, arguments);
|
612
|
+
return result;
|
579
613
|
}
|
580
|
-
});
|
581
614
|
|
582
|
-
|
583
|
-
|
615
|
+
return result;
|
616
|
+
},
|
617
|
+
|
618
|
+
// Handle closing regions, and then close the view itself.
|
619
|
+
close: function () {
|
620
|
+
this.closeRegions();
|
621
|
+
this.destroyRegions();
|
622
|
+
Backbone.Marionette.ItemView.prototype.close.call(this, arguments);
|
623
|
+
},
|
624
|
+
|
625
|
+
// Initialize the regions that have been defined in a
|
626
|
+
// `regions` attribute on this layout. The key of the
|
627
|
+
// hash becomes an attribute on the layout object directly.
|
628
|
+
// For example: `regions: { menu: ".menu-container" }`
|
629
|
+
// will product a `layout.menu` object which is a region
|
630
|
+
// that controls the `.menu-container` DOM element.
|
631
|
+
initializeRegions: function () {
|
632
|
+
if (!this.regionManagers){
|
633
|
+
this.regionManagers = {};
|
634
|
+
}
|
584
635
|
|
585
|
-
|
586
|
-
|
587
|
-
// Have your routers configured to call the method on
|
588
|
-
// your object, directly.
|
589
|
-
//
|
590
|
-
// Configure an AppRouter with `appRoutes`.
|
591
|
-
//
|
592
|
-
// App routers can only take one `controller` object.
|
593
|
-
// It is recommended that you divide your controller
|
594
|
-
// objects in to smaller peices of related functionality
|
595
|
-
// and have multiple routers / controllers, instead of
|
596
|
-
// just one giant router and controller.
|
597
|
-
//
|
598
|
-
// You can also add standard routes to an AppRouter.
|
599
|
-
|
600
|
-
Marionette.AppRouter = Backbone.Router.extend({
|
636
|
+
var that = this;
|
637
|
+
_.each(this.regions, function (selector, name) {
|
601
638
|
|
602
|
-
|
603
|
-
|
639
|
+
var regionManager = new Backbone.Marionette.Region({
|
640
|
+
el: selector,
|
641
|
+
getEl: function(selector){
|
642
|
+
return that.$(selector);
|
643
|
+
}
|
644
|
+
});
|
604
645
|
|
605
|
-
|
606
|
-
|
607
|
-
|
608
|
-
|
646
|
+
that.regionManagers[name] = regionManager;
|
647
|
+
that[name] = regionManager;
|
648
|
+
});
|
649
|
+
|
650
|
+
},
|
651
|
+
|
652
|
+
// Re-initialize all of the regions by updating the `el` that
|
653
|
+
// they point to
|
654
|
+
reInitializeRegions: function(){
|
655
|
+
_.each(this.regionManagers, function(region){
|
656
|
+
delete region.$el;
|
657
|
+
});
|
658
|
+
},
|
659
|
+
|
660
|
+
// Close all of the regions that have been opened by
|
661
|
+
// this layout. This method is called when the layout
|
662
|
+
// itself is closed.
|
663
|
+
closeRegions: function () {
|
664
|
+
var that = this;
|
665
|
+
_.each(this.regionManagers, function (manager, name) {
|
666
|
+
manager.close();
|
667
|
+
});
|
668
|
+
},
|
669
|
+
|
670
|
+
// Destroys all of the regions by removing references
|
671
|
+
// from the Layout
|
672
|
+
destroyRegions: function(){
|
673
|
+
var that = this;
|
674
|
+
_.each(this.regionManagers, function (manager, name) {
|
675
|
+
delete that[name];
|
676
|
+
});
|
677
|
+
this.regionManagers = {};
|
678
|
+
}
|
679
|
+
});
|
680
|
+
|
681
|
+
|
682
|
+
// Application
|
683
|
+
// -----------
|
684
|
+
|
685
|
+
// Contain and manage the composite application as a whole.
|
686
|
+
// Stores and starts up `Region` objects, includes an
|
687
|
+
// event aggregator as `app.vent`
|
688
|
+
Marionette.Application = function(options){
|
689
|
+
this.initCallbacks = new Marionette.Callbacks();
|
690
|
+
this.vent = new Marionette.EventAggregator();
|
691
|
+
_.extend(this, options);
|
692
|
+
};
|
693
|
+
|
694
|
+
_.extend(Marionette.Application.prototype, Backbone.Events, {
|
695
|
+
// Add an initializer that is either run at when the `start`
|
696
|
+
// method is called, or run immediately if added after `start`
|
697
|
+
// has already been called.
|
698
|
+
addInitializer: function(initializer){
|
699
|
+
this.initCallbacks.add(initializer);
|
700
|
+
},
|
701
|
+
|
702
|
+
// kick off all of the application's processes.
|
703
|
+
// initializes all of the regions that have been added
|
704
|
+
// to the app, and runs all of the initializer functions
|
705
|
+
start: function(options){
|
706
|
+
this.trigger("initialize:before", options);
|
707
|
+
this.initCallbacks.run(options, this);
|
708
|
+
this.trigger("initialize:after", options);
|
709
|
+
|
710
|
+
this.trigger("start", options);
|
711
|
+
},
|
712
|
+
|
713
|
+
// Add regions to your app.
|
714
|
+
// Accepts a hash of named strings or Region objects
|
715
|
+
// addRegions({something: "#someRegion"})
|
716
|
+
// addRegions{{something: Region.extend({el: "#someRegion"}) });
|
717
|
+
addRegions: function(regions){
|
718
|
+
var regionValue, regionObj, region;
|
719
|
+
|
720
|
+
for(region in regions){
|
721
|
+
if (regions.hasOwnProperty(region)){
|
722
|
+
regionValue = regions[region];
|
723
|
+
|
724
|
+
if (typeof regionValue === "string"){
|
725
|
+
regionObj = new Marionette.Region({
|
726
|
+
el: regionValue
|
727
|
+
});
|
728
|
+
} else {
|
729
|
+
regionObj = new regionValue();
|
609
730
|
}
|
610
|
-
|
731
|
+
|
732
|
+
this[region] = regionObj;
|
611
733
|
}
|
612
|
-
}
|
734
|
+
}
|
735
|
+
},
|
613
736
|
|
614
|
-
|
615
|
-
|
616
|
-
|
617
|
-
|
618
|
-
|
737
|
+
// Create a module, attached to the application
|
738
|
+
module: function(){
|
739
|
+
// see the Marionette.Module object for more information
|
740
|
+
return Marionette.Module.create.apply(this, arguments);
|
741
|
+
}
|
742
|
+
});
|
619
743
|
|
620
|
-
|
621
|
-
|
622
|
-
routes.unshift([route, appRoutes[route]]);
|
623
|
-
}
|
624
|
-
}
|
744
|
+
// Copy the `extend` function used by Backbone's classes
|
745
|
+
Marionette.Application.extend = Backbone.View.extend;
|
625
746
|
|
626
|
-
|
627
|
-
|
628
|
-
|
629
|
-
|
630
|
-
|
631
|
-
|
632
|
-
|
633
|
-
|
634
|
-
|
635
|
-
|
636
|
-
|
637
|
-
|
747
|
+
// Copy the features of `BindTo`
|
748
|
+
_.extend(Marionette.Application.prototype, Marionette.BindTo);
|
749
|
+
|
750
|
+
// AppRouter
|
751
|
+
// ---------
|
752
|
+
|
753
|
+
// Reduce the boilerplate code of handling route events
|
754
|
+
// and then calling a single method on another object.
|
755
|
+
// Have your routers configured to call the method on
|
756
|
+
// your object, directly.
|
757
|
+
//
|
758
|
+
// Configure an AppRouter with `appRoutes`.
|
759
|
+
//
|
760
|
+
// App routers can only take one `controller` object.
|
761
|
+
// It is recommended that you divide your controller
|
762
|
+
// objects in to smaller peices of related functionality
|
763
|
+
// and have multiple routers / controllers, instead of
|
764
|
+
// just one giant router and controller.
|
765
|
+
//
|
766
|
+
// You can also add standard routes to an AppRouter.
|
767
|
+
|
768
|
+
Marionette.AppRouter = Backbone.Router.extend({
|
638
769
|
|
639
|
-
|
640
|
-
|
770
|
+
constructor: function(options){
|
771
|
+
Backbone.Router.prototype.constructor.call(this, options);
|
772
|
+
|
773
|
+
if (this.appRoutes){
|
774
|
+
var controller = this.controller;
|
775
|
+
if (options && options.controller) {
|
776
|
+
controller = options.controller;
|
641
777
|
}
|
778
|
+
this.processAppRoutes(controller, this.appRoutes);
|
642
779
|
}
|
643
|
-
}
|
644
|
-
|
645
|
-
//
|
646
|
-
//
|
647
|
-
|
648
|
-
|
649
|
-
|
650
|
-
|
651
|
-
|
652
|
-
|
653
|
-
|
654
|
-
|
655
|
-
|
656
|
-
|
657
|
-
_.extend(Marionette.Application.prototype, Backbone.Events, {
|
658
|
-
// Add an initializer that is either run at when the `start`
|
659
|
-
// method is called, or run immediately if added after `start`
|
660
|
-
// has already been called.
|
661
|
-
addInitializer: function(initializer){
|
662
|
-
this.initCallbacks.add(initializer);
|
663
|
-
},
|
664
|
-
|
665
|
-
// kick off all of the application's processes.
|
666
|
-
// initializes all of the regions that have been added
|
667
|
-
// to the app, and runs all of the initializer functions
|
668
|
-
start: function(options){
|
669
|
-
this.trigger("initialize:before", options);
|
670
|
-
this.initCallbacks.run(this, options);
|
671
|
-
this.trigger("initialize:after", options);
|
672
|
-
|
673
|
-
this.trigger("start", options);
|
674
|
-
},
|
675
|
-
|
676
|
-
// Add regions to your app.
|
677
|
-
// Accepts a hash of named strings or Region objects
|
678
|
-
// addRegions({something: "#someRegion"})
|
679
|
-
// addRegions{{something: Region.extend({el: "#someRegion"}) });
|
680
|
-
addRegions: function(regions){
|
681
|
-
var regionValue, regionObj, region;
|
682
|
-
|
683
|
-
for(region in regions){
|
684
|
-
if (regions.hasOwnProperty(region)){
|
685
|
-
regionValue = regions[region];
|
686
|
-
|
687
|
-
if (typeof regionValue === "string"){
|
688
|
-
regionObj = new Marionette.Region({
|
689
|
-
el: regionValue
|
690
|
-
});
|
691
|
-
} else {
|
692
|
-
regionObj = new regionValue();
|
693
|
-
}
|
694
|
-
|
695
|
-
this[region] = regionObj;
|
696
|
-
}
|
780
|
+
},
|
781
|
+
|
782
|
+
// Internal method to process the `appRoutes` for the
|
783
|
+
// router, and turn them in to routes that trigger the
|
784
|
+
// specified method on the specified `controller`.
|
785
|
+
processAppRoutes: function(controller, appRoutes){
|
786
|
+
var method, methodName;
|
787
|
+
var route, routesLength, i;
|
788
|
+
var routes = [];
|
789
|
+
var router = this;
|
790
|
+
|
791
|
+
for(route in appRoutes){
|
792
|
+
if (appRoutes.hasOwnProperty(route)){
|
793
|
+
routes.unshift([route, appRoutes[route]]);
|
697
794
|
}
|
698
795
|
}
|
699
|
-
});
|
700
796
|
|
701
|
-
|
702
|
-
|
703
|
-
|
704
|
-
|
705
|
-
|
706
|
-
|
707
|
-
|
708
|
-
|
709
|
-
|
710
|
-
|
711
|
-
|
712
|
-
// Store the event binding in array so it can be unbound
|
713
|
-
// easily, at a later point in time.
|
714
|
-
bindTo: function (obj, eventName, callback, context) {
|
715
|
-
context = context || this;
|
716
|
-
obj.on(eventName, callback, context);
|
717
|
-
|
718
|
-
if (!this.bindings) { this.bindings = []; }
|
719
|
-
|
720
|
-
var binding = {
|
721
|
-
obj: obj,
|
722
|
-
eventName: eventName,
|
723
|
-
callback: callback,
|
724
|
-
context: context
|
797
|
+
routesLength = routes.length;
|
798
|
+
for (i = 0; i < routesLength; i++){
|
799
|
+
route = routes[i][0];
|
800
|
+
methodName = routes[i][1];
|
801
|
+
method = controller[methodName];
|
802
|
+
|
803
|
+
if (!method){
|
804
|
+
var msg = "Method '" + methodName + "' was not found on the controller";
|
805
|
+
var err = new Error(msg);
|
806
|
+
err.name = "NoMethodError";
|
807
|
+
throw err;
|
725
808
|
}
|
726
809
|
|
727
|
-
|
810
|
+
method = _.bind(method, controller);
|
811
|
+
router.route(route, methodName, method);
|
812
|
+
}
|
813
|
+
}
|
814
|
+
});
|
728
815
|
|
729
|
-
return binding;
|
730
|
-
},
|
731
816
|
|
732
|
-
|
733
|
-
|
734
|
-
unbindFrom: function(binding){
|
735
|
-
binding.obj.off(binding.eventName, binding.callback);
|
736
|
-
this.bindings = _.reject(this.bindings, function(bind){return bind === binding});
|
737
|
-
},
|
817
|
+
// Module
|
818
|
+
// ------
|
738
819
|
|
739
|
-
|
740
|
-
|
741
|
-
|
820
|
+
// A simple module system, used to create privacy and encapsulation in
|
821
|
+
// Marionette applications
|
822
|
+
Marionette.Module = function(){};
|
742
823
|
|
743
|
-
|
744
|
-
|
745
|
-
|
746
|
-
_.each(bindings, function (binding, index) {
|
747
|
-
that.unbindFrom(binding);
|
748
|
-
});
|
749
|
-
}
|
750
|
-
};
|
824
|
+
// Extend the Module prototype with events / bindTo, so that the module
|
825
|
+
// can be used as an event aggregator or pub/sub.
|
826
|
+
_.extend(Marionette.Module.prototype, Backbone.Events, Marionette.BindTo);
|
751
827
|
|
752
|
-
|
753
|
-
|
828
|
+
// Function level methods to create modules
|
829
|
+
_.extend(Marionette.Module, {
|
754
830
|
|
755
|
-
//
|
756
|
-
//
|
757
|
-
|
758
|
-
|
759
|
-
|
760
|
-
|
761
|
-
|
831
|
+
// Create a module, hanging off 'this' as the parent object. This
|
832
|
+
// method must be called with .apply or .create
|
833
|
+
create: function(moduleNames, moduleDefinition){
|
834
|
+
var moduleName, module, moduleOverride;
|
835
|
+
var parentObject = this;
|
836
|
+
var parentModule = this;
|
837
|
+
var moduleNames = moduleNames.split(".");
|
762
838
|
|
763
|
-
|
764
|
-
|
765
|
-
|
766
|
-
|
767
|
-
// `run` method is called.
|
768
|
-
add: function(callback){
|
769
|
-
this.promise.done(function(context, options){
|
770
|
-
callback.call(context, options);
|
771
|
-
});
|
772
|
-
},
|
839
|
+
// Loop through all the parts of the module definition
|
840
|
+
var length = moduleNames.length;
|
841
|
+
for(var i = 0; i < length; i++){
|
842
|
+
var isLastModuleInChain = (i === length-1);
|
773
843
|
|
774
|
-
|
775
|
-
|
776
|
-
|
777
|
-
|
778
|
-
this.deferred.resolve(context, options);
|
779
|
-
}
|
780
|
-
});
|
781
|
-
|
782
|
-
// Event Aggregator
|
783
|
-
// ----------------
|
784
|
-
|
785
|
-
// A pub-sub object that can be used to decouple various parts
|
786
|
-
// of an application through event-driven architecture.
|
787
|
-
Marionette.EventAggregator = function(options){
|
788
|
-
_.extend(this, options);
|
789
|
-
};
|
790
|
-
|
791
|
-
_.extend(Marionette.EventAggregator.prototype, Backbone.Events, Marionette.BindTo, {
|
792
|
-
// Assumes the event aggregator itself is the
|
793
|
-
// object being bound to.
|
794
|
-
bindTo: function(eventName, callback, context){
|
795
|
-
return Marionette.BindTo.bindTo.call(this, this, eventName, callback, context);
|
796
|
-
}
|
797
|
-
});
|
844
|
+
// Get the module name, and check if it exists on
|
845
|
+
// the current parent already
|
846
|
+
moduleName = moduleNames[i];
|
847
|
+
module = parentModule[moduleName];
|
798
848
|
|
799
|
-
|
800
|
-
|
801
|
-
|
802
|
-
|
803
|
-
// caching them for faster access.
|
804
|
-
Marionette.TemplateCache = {
|
805
|
-
templates: {},
|
806
|
-
loaders: {},
|
807
|
-
|
808
|
-
// Get the specified template by id. Either
|
809
|
-
// retrieves the cached version, or loads it
|
810
|
-
// from the DOM.
|
811
|
-
get: function(templateId){
|
812
|
-
var that = this;
|
813
|
-
var templateRetrieval = $.Deferred();
|
814
|
-
var cachedTemplate = this.templates[templateId];
|
815
|
-
|
816
|
-
if (cachedTemplate){
|
817
|
-
templateRetrieval.resolve(cachedTemplate);
|
818
|
-
} else {
|
819
|
-
var loader = this.loaders[templateId];
|
820
|
-
if(loader) {
|
821
|
-
templateRetrieval = loader;
|
822
|
-
} else {
|
823
|
-
this.loaders[templateId] = templateRetrieval;
|
849
|
+
// Create a new module if we don't have one already
|
850
|
+
if (!module){
|
851
|
+
module = new Marionette.Module();
|
852
|
+
}
|
824
853
|
|
825
|
-
|
826
|
-
|
827
|
-
|
828
|
-
|
829
|
-
|
830
|
-
|
854
|
+
// Check to see if we need to run the definition
|
855
|
+
// for the module. Only run the definition if one
|
856
|
+
// is supplied, and if we're at the last segment
|
857
|
+
// of the "Module.Name" chain.
|
858
|
+
if (isLastModuleInChain && moduleDefinition){
|
859
|
+
// get the custom args passed in after the module definition and
|
860
|
+
// get rid of the module name and definition function
|
861
|
+
var customArgs = slice.apply(arguments);
|
862
|
+
customArgs.shift();
|
863
|
+
customArgs.shift();
|
864
|
+
|
865
|
+
// final arguments list for the module definition
|
866
|
+
var argsArray = [module, parentObject, Backbone, Marionette, jQuery, _, customArgs];
|
867
|
+
|
868
|
+
// flatten the nested array
|
869
|
+
var args = _.flatten(argsArray);
|
870
|
+
|
871
|
+
// ensure the module definition's `this` is the module itself
|
872
|
+
moduleDefinition.apply(module, args);
|
873
|
+
}
|
831
874
|
|
875
|
+
// If the defined module is not what we are
|
876
|
+
// currently storing as the module, replace it
|
877
|
+
if (parentModule[moduleName] !== module){
|
878
|
+
parentModule[moduleName] = module;
|
832
879
|
}
|
833
880
|
|
834
|
-
|
835
|
-
|
881
|
+
// Reset the parent module so that the next child
|
882
|
+
// in the list will be added to the correct parent
|
883
|
+
parentModule = module;
|
884
|
+
}
|
836
885
|
|
837
|
-
//
|
838
|
-
|
839
|
-
|
840
|
-
|
841
|
-
|
886
|
+
// Return the last module in the definition chain
|
887
|
+
return module;
|
888
|
+
}
|
889
|
+
});
|
890
|
+
|
891
|
+
// Template Cache
|
892
|
+
// --------------
|
893
|
+
|
894
|
+
// Manage templates stored in `<script>` blocks,
|
895
|
+
// caching them for faster access.
|
896
|
+
Marionette.TemplateCache = function(templateId){
|
897
|
+
this.templateId = templateId;
|
898
|
+
};
|
899
|
+
|
900
|
+
// TemplateCache object-level methods. Manage the template
|
901
|
+
// caches from these method calls instead of creating
|
902
|
+
// your own TemplateCache instances
|
903
|
+
_.extend(Marionette.TemplateCache, {
|
904
|
+
templateCaches: {},
|
905
|
+
|
906
|
+
// Get the specified template by id. Either
|
907
|
+
// retrieves the cached version, or loads it
|
908
|
+
// from the DOM.
|
909
|
+
get: function(templateId){
|
910
|
+
var that = this;
|
911
|
+
var cachedTemplate = this.templateCaches[templateId];
|
912
|
+
|
913
|
+
if (!cachedTemplate){
|
914
|
+
cachedTemplate = new Marionette.TemplateCache(templateId);
|
915
|
+
this.templateCaches[templateId] = cachedTemplate;
|
916
|
+
}
|
842
917
|
|
843
|
-
|
844
|
-
|
845
|
-
var msg = "Could not find template: '" + templateId + "'";
|
846
|
-
var err = new Error(msg);
|
847
|
-
err.name = "NoTemplateError";
|
848
|
-
throw err;
|
849
|
-
}
|
918
|
+
return cachedTemplate.load();
|
919
|
+
},
|
850
920
|
|
851
|
-
|
852
|
-
|
853
|
-
|
854
|
-
|
855
|
-
|
856
|
-
|
857
|
-
|
858
|
-
|
859
|
-
|
860
|
-
|
861
|
-
|
862
|
-
|
863
|
-
|
864
|
-
|
865
|
-
// are specified, clears all templates:
|
866
|
-
// `clear()`
|
867
|
-
//
|
868
|
-
// If arguments are specified, clears each of the
|
869
|
-
// specified templates from the cache:
|
870
|
-
// `clear("#t1", "#t2", "...")`
|
871
|
-
clear: function(){
|
872
|
-
var i;
|
873
|
-
var length = arguments.length;
|
874
|
-
|
875
|
-
if (length > 0){
|
876
|
-
for(i=0; i<length; i++){
|
877
|
-
delete this.templates[arguments[i]];
|
878
|
-
}
|
879
|
-
} else {
|
880
|
-
this.templates = {};
|
921
|
+
// Clear templates from the cache. If no arguments
|
922
|
+
// are specified, clears all templates:
|
923
|
+
// `clear()`
|
924
|
+
//
|
925
|
+
// If arguments are specified, clears each of the
|
926
|
+
// specified templates from the cache:
|
927
|
+
// `clear("#t1", "#t2", "...")`
|
928
|
+
clear: function(){
|
929
|
+
var i;
|
930
|
+
var length = arguments.length;
|
931
|
+
|
932
|
+
if (length > 0){
|
933
|
+
for(i=0; i<length; i++){
|
934
|
+
delete this.templateCaches[arguments[i]];
|
881
935
|
}
|
936
|
+
} else {
|
937
|
+
this.templateCaches = {};
|
882
938
|
}
|
883
|
-
}
|
939
|
+
}
|
940
|
+
});
|
884
941
|
|
885
|
-
|
886
|
-
|
887
|
-
|
888
|
-
|
889
|
-
// selector and the data to render.
|
890
|
-
Marionette.Renderer = {
|
891
|
-
|
892
|
-
// Render a template with data. The `template` parameter is
|
893
|
-
// passed to the `TemplateCache` object to retrieve the
|
894
|
-
// actual template. Override this method to provide your own
|
895
|
-
// custom rendering and template handling for all of Marionette.
|
896
|
-
render: function(template, data){
|
897
|
-
var that = this;
|
898
|
-
var asyncRender = $.Deferred();
|
899
|
-
|
900
|
-
var templateRetrieval = Marionette.TemplateCache.get(template);
|
901
|
-
|
902
|
-
$.when(templateRetrieval).then(function(template){
|
903
|
-
var html = that.renderTemplate(template, data);
|
904
|
-
asyncRender.resolve(html);
|
905
|
-
});
|
942
|
+
// TemplateCache instance methods, allowing each
|
943
|
+
// template cache object to manage it's own state
|
944
|
+
// and know whether or not it has been loaded
|
945
|
+
_.extend(Marionette.TemplateCache.prototype, {
|
906
946
|
|
907
|
-
|
908
|
-
|
947
|
+
// Internal method to load the template asynchronously.
|
948
|
+
load: function(){
|
949
|
+
var that = this;
|
909
950
|
|
910
|
-
//
|
911
|
-
|
912
|
-
|
913
|
-
var html = template(data);
|
914
|
-
return html;
|
951
|
+
// Guard clause to prevent loading this template more than once
|
952
|
+
if (this.compiledTemplate){
|
953
|
+
return this.compiledTemplate;
|
915
954
|
}
|
916
955
|
|
917
|
-
|
918
|
-
|
919
|
-
|
920
|
-
// -------
|
921
|
-
|
922
|
-
// The "Modules" object builds modules on an
|
923
|
-
// object that it is attached to. It should not be
|
924
|
-
// used on it's own, but should be attached to
|
925
|
-
// another object that will define modules.
|
926
|
-
Marionette.Modules = {
|
927
|
-
|
928
|
-
// Add modules to the application, providing direct
|
929
|
-
// access to your applicaiton object, Backbone,
|
930
|
-
// Marionette, jQuery and Underscore as parameters
|
931
|
-
// to a callback function.
|
932
|
-
module: function(moduleNames, moduleDefinition){
|
933
|
-
var moduleName, module, moduleOverride;
|
934
|
-
var parentModule = this;
|
935
|
-
var parentApp = this;
|
936
|
-
var moduleNames = moduleNames.split(".");
|
937
|
-
|
938
|
-
// Loop through all the parts of the module definition
|
939
|
-
var length = moduleNames.length;
|
940
|
-
for(var i = 0; i < length; i++){
|
941
|
-
var isLastModuleInChain = (i === length-1);
|
942
|
-
|
943
|
-
// Get the module name, and check if it exists on
|
944
|
-
// the current parent already
|
945
|
-
moduleName = moduleNames[i];
|
946
|
-
module = parentModule[moduleName];
|
947
|
-
|
948
|
-
// Create a new module if we don't have one already
|
949
|
-
if (!module){
|
950
|
-
module = new Marionette.Application();
|
951
|
-
}
|
952
|
-
|
953
|
-
// Check to see if we need to run the definition
|
954
|
-
// for the module. Only run the definition if one
|
955
|
-
// is supplied, and if we're at the last segment
|
956
|
-
// of the "Module.Name" chain.
|
957
|
-
if (isLastModuleInChain && moduleDefinition){
|
958
|
-
moduleOverride = moduleDefinition(module, parentApp, Backbone, Marionette, jQuery, _);
|
959
|
-
// If we have a module override, use it instead.
|
960
|
-
if (moduleOverride){
|
961
|
-
module = moduleOverride;
|
962
|
-
}
|
963
|
-
}
|
956
|
+
// Load the template and compile it
|
957
|
+
var template = this.loadTemplate(this.templateId);
|
958
|
+
this.compiledTemplate = this.compileTemplate(template);
|
964
959
|
|
965
|
-
|
966
|
-
|
967
|
-
if (parentModule[moduleName] !== module){
|
968
|
-
parentModule[moduleName] = module;
|
969
|
-
}
|
960
|
+
return this.compiledTemplate;
|
961
|
+
},
|
970
962
|
|
971
|
-
|
972
|
-
|
973
|
-
|
974
|
-
|
963
|
+
// Load a template from the DOM, by default. Override
|
964
|
+
// this method to provide your own template retrieval,
|
965
|
+
// such as asynchronous loading from a server.
|
966
|
+
loadTemplate: function(templateId){
|
967
|
+
var template = $(templateId).html();
|
975
968
|
|
976
|
-
|
977
|
-
|
969
|
+
if (!template || template.length === 0){
|
970
|
+
var msg = "Could not find template: '" + templateId + "'";
|
971
|
+
var err = new Error(msg);
|
972
|
+
err.name = "NoTemplateError";
|
973
|
+
throw err;
|
978
974
|
}
|
979
|
-
};
|
980
975
|
|
981
|
-
|
982
|
-
|
976
|
+
return template;
|
977
|
+
},
|
983
978
|
|
984
|
-
//
|
985
|
-
|
986
|
-
|
987
|
-
//
|
988
|
-
|
989
|
-
|
990
|
-
Marionette.Application.extend = extend;
|
991
|
-
|
992
|
-
// Copy the `modules` feature on to the `Application` object
|
993
|
-
Marionette.Application.prototype.module = Marionette.Modules.module;
|
994
|
-
|
995
|
-
// Copy the features of `BindTo` on to these objects
|
996
|
-
_.extend(Marionette.View.prototype, Marionette.BindTo);
|
997
|
-
_.extend(Marionette.Application.prototype, Marionette.BindTo);
|
998
|
-
_.extend(Marionette.Region.prototype, Marionette.BindTo);
|
999
|
-
|
1000
|
-
// A simple wrapper method for deferring a callback until
|
1001
|
-
// after another method has been called, passing the
|
1002
|
-
// results of the first method to the second. Uses jQuery's
|
1003
|
-
// deferred / promise objects, and $.when/then to make it
|
1004
|
-
// work.
|
1005
|
-
var callDeferredMethod = function(fn, callback, context){
|
1006
|
-
var promise;
|
1007
|
-
if (fn) { promise = fn.call(context); }
|
1008
|
-
$.when(promise).then(callback);
|
979
|
+
// Pre-compile the template before caching it. Override
|
980
|
+
// this method if you do not need to pre-compile a template
|
981
|
+
// (JST / RequireJS for example) or if you want to change
|
982
|
+
// the template engine used (Handebars, etc).
|
983
|
+
compileTemplate: function(rawTemplate){
|
984
|
+
return _.template(rawTemplate);
|
1009
985
|
}
|
986
|
+
});
|
987
|
+
|
988
|
+
|
989
|
+
// Renderer
|
990
|
+
// --------
|
991
|
+
|
992
|
+
// Render a template with data by passing in the template
|
993
|
+
// selector and the data to render.
|
994
|
+
Marionette.Renderer = {
|
995
|
+
|
996
|
+
// Render a template with data. The `template` parameter is
|
997
|
+
// passed to the `TemplateCache` object to retrieve the
|
998
|
+
// template function. Override this method to provide your own
|
999
|
+
// custom rendering and template handling for all of Marionette.
|
1000
|
+
render: function(template, data){
|
1001
|
+
var templateFunc = Marionette.TemplateCache.get(template);
|
1002
|
+
var html = templateFunc(data);
|
1003
|
+
return html;
|
1004
|
+
}
|
1005
|
+
};
|
1006
|
+
|
1007
|
+
|
1008
|
+
// Callbacks
|
1009
|
+
// ---------
|
1010
|
+
|
1011
|
+
// A simple way of managing a collection of callbacks
|
1012
|
+
// and executing them at a later point in time, using jQuery's
|
1013
|
+
// `Deferred` object.
|
1014
|
+
Marionette.Callbacks = function(){
|
1015
|
+
this.deferred = $.Deferred();
|
1016
|
+
this.promise = this.deferred.promise();
|
1017
|
+
};
|
1018
|
+
|
1019
|
+
_.extend(Marionette.Callbacks.prototype, {
|
1020
|
+
|
1021
|
+
// Add a callback to be executed. Callbacks added here are
|
1022
|
+
// guaranteed to execute, even if they are added after the
|
1023
|
+
// `run` method is called.
|
1024
|
+
add: function(callback, contextOverride){
|
1025
|
+
this.promise.done(function(context, options){
|
1026
|
+
if (contextOverride){ context = contextOverride; }
|
1027
|
+
callback.call(context, options);
|
1028
|
+
});
|
1029
|
+
},
|
1030
|
+
|
1031
|
+
// Run all registered callbacks with the context specified.
|
1032
|
+
// Additional callbacks can be added after this has been run
|
1033
|
+
// and they will still be executed.
|
1034
|
+
run: function(options, context){
|
1035
|
+
this.deferred.resolve(context, options);
|
1036
|
+
}
|
1037
|
+
});
|
1038
|
+
|
1039
|
+
|
1040
|
+
// Event Aggregator
|
1041
|
+
// ----------------
|
1042
|
+
|
1043
|
+
// A pub-sub object that can be used to decouple various parts
|
1044
|
+
// of an application through event-driven architecture.
|
1045
|
+
Marionette.EventAggregator = function(options){
|
1046
|
+
_.extend(this, options);
|
1047
|
+
};
|
1048
|
+
|
1049
|
+
_.extend(Marionette.EventAggregator.prototype, Backbone.Events, Marionette.BindTo, {
|
1050
|
+
// Assumes the event aggregator itself is the
|
1051
|
+
// object being bound to.
|
1052
|
+
bindTo: function(eventName, callback, context){
|
1053
|
+
return Marionette.BindTo.bindTo.call(this, this, eventName, callback, context);
|
1054
|
+
}
|
1055
|
+
});
|
1056
|
+
|
1057
|
+
|
1058
|
+
// Helpers
|
1059
|
+
// -------
|
1010
1060
|
|
1061
|
+
// For slicing `arguments` in functions
|
1062
|
+
var slice = Array.prototype.slice;
|
1011
1063
|
|
1012
1064
|
return Marionette;
|
1013
1065
|
})(Backbone, _, window.jQuery || window.Zepto || window.ender);
|