-
Notifications
You must be signed in to change notification settings - Fork 1.3k
/
marionette.behaviors.js
189 lines (154 loc) · 6.58 KB
/
marionette.behaviors.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
/* jshint maxlen: 143, nonew: false */
// Marionette.Behaviors
// --------
// Behaviors is a utility class that takes care of
// glueing your behavior instances to their given View.
// The most important part of this class is that you
// **MUST** override the class level behaviorsLookup
// method for things to work properly.
Marionette.Behaviors = (function(Marionette, _) {
function Behaviors(view, behaviors) {
// Behaviors defined on a view can be a flat object literal
// or it can be a function that returns an object.
behaviors = Behaviors.parseBehaviors(view, behaviors || _.result(view, 'behaviors'));
// Wraps several of the view's methods
// calling the methods first on each behavior
// and then eventually calling the method on the view.
Behaviors.wrap(view, behaviors, [
'bindUIElements', 'unbindUIElements',
'delegateEvents', 'undelegateEvents',
'behaviorEvents', 'triggerMethod',
'setElement', 'destroy'
]);
}
var methods = {
setElement: function(setElement, behaviors) {
setElement.apply(this, _.tail(arguments, 2));
// proxy behavior $el to the view's $el.
// This is needed because a view's $el proxy
// is not set until after setElement is called.
_.each(behaviors, function(b) {
b.$el = this.$el;
}, this);
},
destroy: function(destroy, behaviors) {
var args = _.tail(arguments, 2);
destroy.apply(this, args);
// Call destroy on each behavior after
// destroying the view.
// This unbinds event listeners
// that behaviors have registerd for.
_.invoke(behaviors, 'destroy', args);
},
bindUIElements: function(bindUIElements, behaviors) {
bindUIElements.apply(this);
_.invoke(behaviors, bindUIElements);
},
unbindUIElements: function(unbindUIElements, behaviors) {
unbindUIElements.apply(this);
_.invoke(behaviors, unbindUIElements);
},
triggerMethod: function(triggerMethod, behaviors) {
var args = _.tail(arguments, 2);
triggerMethod.apply(this, args);
_.each(behaviors, function(b) {
triggerMethod.apply(b, args);
});
},
delegateEvents: function(delegateEvents, behaviors) {
var args = _.tail(arguments, 2);
delegateEvents.apply(this, args);
_.each(behaviors, function(b) {
Marionette.bindEntityEvents(b, this.model, Marionette.getOption(b, 'modelEvents'));
Marionette.bindEntityEvents(b, this.collection, Marionette.getOption(b, 'collectionEvents'));
}, this);
},
undelegateEvents: function(undelegateEvents, behaviors) {
var args = _.tail(arguments, 2);
undelegateEvents.apply(this, args);
_.each(behaviors, function(b) {
Marionette.unbindEntityEvents(b, this.model, Marionette.getOption(b, 'modelEvents'));
Marionette.unbindEntityEvents(b, this.collection, Marionette.getOption(b, 'collectionEvents'));
}, this);
},
behaviorEvents: function(behaviorEvents, behaviors) {
var _behaviorsEvents = {};
var viewUI = _.result(this, 'ui');
_.each(behaviors, function(b, i) {
var _events = {};
var behaviorEvents = _.clone(_.result(b, 'events')) || {};
var behaviorUI = _.result(b, 'ui');
// Construct an internal UI hash first using
// the views UI hash and then the behaviors UI hash.
// This allows the user to use UI hash elements
// defined in the parent view as well as those
// defined in the given behavior.
var ui = _.extend({}, viewUI, behaviorUI);
// Normalize behavior events hash to allow
// a user to use the @ui. syntax.
behaviorEvents = Marionette.normalizeUIKeys(behaviorEvents, ui);
_.each(_.keys(behaviorEvents), function(key) {
// Append white-space at the end of each key to prevent behavior key collisions.
// This is relying on the fact that backbone events considers "click .foo" the same as
// "click .foo ".
// +2 is used because new Array(1) or 0 is "" and not " "
var whitespace = (new Array(i + 2)).join(' ');
var eventKey = key + whitespace;
var handler = _.isFunction(behaviorEvents[key]) ? behaviorEvents[key] : b[behaviorEvents[key]];
_events[eventKey] = _.bind(handler, b);
});
_behaviorsEvents = _.extend(_behaviorsEvents, _events);
});
return _behaviorsEvents;
}
};
_.extend(Behaviors, {
// Placeholder method to be extended by the user.
// The method should define the object that stores the behaviors.
// i.e.
//
// ```js
// Marionette.Behaviors.behaviorsLookup: function() {
// return App.Behaviors
// }
// ```
behaviorsLookup: function() {
throw new Error('You must define where your behaviors are stored.' +
'See https://github.com/marionettejs/backbone.marionette' +
'/blob/master/docs/marionette.behaviors.md#behaviorslookup');
},
// Takes care of getting the behavior class
// given options and a key.
// If a user passes in options.behaviorClass
// default to using that. Otherwise delegate
// the lookup to the users `behaviorsLookup` implementation.
getBehaviorClass: function(options, key) {
if (options.behaviorClass) {
return options.behaviorClass;
}
// Get behavior class can be either a flat object or a method
return _.isFunction(Behaviors.behaviorsLookup) ? Behaviors.behaviorsLookup.apply(this, arguments)[key] : Behaviors.behaviorsLookup[key];
},
// Iterate over the behaviors object, for each behavior
// instantiate it and get its grouped behaviors.
parseBehaviors: function(view, behaviors) {
return _.chain(behaviors).map(function(options, key) {
var BehaviorClass = Behaviors.getBehaviorClass(options, key);
var behavior = new BehaviorClass(options, view);
var nestedBehaviors = Behaviors.parseBehaviors(view, _.result(behavior, 'behaviors'));
return [behavior].concat(nestedBehaviors);
}).flatten().value();
},
// Wrap view internal methods so that they delegate to behaviors. For example,
// `onDestroy` should trigger destroy on all of the behaviors and then destroy itself.
// i.e.
//
// `view.delegateEvents = _.partial(methods.delegateEvents, view.delegateEvents, behaviors);`
wrap: function(view, behaviors, methodNames) {
_.each(methodNames, function(methodName) {
view[methodName] = _.partial(methods[methodName], view[methodName], behaviors);
});
}
});
return Behaviors;
})(Marionette, _);