backbone-associations-rails 0.2.0 → 0.3.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/README.md +1 -1
- data/lib/backbone-associations-rails/version.rb +1 -1
- data/vendor/assets/javascripts/backbone-associations.js +307 -173
- metadata +1 -1
data/README.md
CHANGED
@@ -4,7 +4,7 @@ This gem vendors the [Backbone-associations](https://github.com/dhruvaray/backbo
|
|
4
4
|
|
5
5
|
## Dependencies
|
6
6
|
|
7
|
-
```backbone-associations``` requires Backbone.js >= 0.9.2 and Underscore.js >= 1.3.
|
7
|
+
```backbone-associations``` requires Backbone.js >= 0.9.2 and Underscore.js >= 1.3.3
|
8
8
|
|
9
9
|
## Installation
|
10
10
|
|
@@ -1,222 +1,356 @@
|
|
1
1
|
//
|
2
|
-
// Backbone-associations.js 0.
|
2
|
+
// Backbone-associations.js 0.3.0
|
3
3
|
//
|
4
4
|
// (c) 2012 Dhruva Ray, Jaynti Kanani
|
5
|
-
//
|
6
|
-
//
|
7
|
-
//
|
8
|
-
//
|
9
|
-
//
|
5
|
+
// Backbone-associations may be freely distributed under the MIT license;
|
6
|
+
// see the accompanying LICENSE.txt.
|
7
|
+
// Depends on [Backbone](https://github.com/documentcloud/backbone)
|
8
|
+
// and [Underscore](https://github.com/documentcloud/underscore/) as well.
|
10
9
|
// A complete [Test & Benchmark Suite](../test/test-suite.html) is included for your perusal.
|
11
10
|
|
12
11
|
// Initial Setup
|
13
12
|
// --------------
|
14
|
-
(function() {
|
15
|
-
|
16
|
-
|
17
|
-
|
18
|
-
|
19
|
-
|
20
|
-
|
21
|
-
|
22
|
-
|
23
|
-
|
13
|
+
(function () {
|
14
|
+
"use strict";
|
15
|
+
|
16
|
+
// The top-level namespace. All public Backbone classes and modules will be attached to this.
|
17
|
+
// Exported for the browser and CommonJS.
|
18
|
+
var _, Backbone, BackboneModel, defaultEvents, AssociatedModel;
|
19
|
+
if (typeof require !== 'undefined') {
|
20
|
+
_ = require('underscore');
|
21
|
+
Backbone = require('backbone');
|
22
|
+
exports = module.exports = Backbone;
|
23
|
+
} else {
|
24
24
|
_ = window._;
|
25
|
-
Backbone = window.Backbone;
|
26
|
-
|
27
|
-
|
25
|
+
Backbone = window.Backbone;
|
26
|
+
}
|
27
|
+
// Create local reference `Model` prototype.
|
28
|
+
BackboneModel = Backbone.Model.prototype;
|
29
|
+
// Built-in Backbone `events`.
|
30
|
+
defaultEvents = ["change", "add", "remove", "reset", "destroy", "sync", "error", "sort", "request"];
|
31
|
+
|
28
32
|
// Backbone.AssociatedModel
|
29
33
|
// --------------
|
30
34
|
|
31
|
-
//Add `Many` and `One` relations to Backbone Object
|
32
|
-
Backbone.Many
|
33
|
-
Backbone.One
|
34
|
-
//Define `AssociatedModel` (Extends Backbone.Model)
|
35
|
-
Backbone.AssociatedModel = Backbone.Model.extend({
|
36
|
-
//Define relations with Associated Model
|
37
|
-
relations:
|
38
|
-
//
|
39
|
-
//
|
40
|
-
|
41
|
-
|
42
|
-
|
43
|
-
|
44
|
-
|
35
|
+
//Add `Many` and `One` relations to Backbone Object.
|
36
|
+
Backbone.Many = "Many";
|
37
|
+
Backbone.One = "One";
|
38
|
+
// Define `AssociatedModel` (Extends Backbone.Model).
|
39
|
+
AssociatedModel = Backbone.AssociatedModel = Backbone.Model.extend({
|
40
|
+
// Define relations with Associated Model.
|
41
|
+
relations:undefined,
|
42
|
+
// Define `Model` property which can keep track of already fired `events`,
|
43
|
+
// and prevent redundant event to be triggered in case of circular model graph.
|
44
|
+
_proxyCalls:undefined,
|
45
|
+
// Set a hash of model attributes on the object,
|
46
|
+
// fire Backbone `event` with options.
|
47
|
+
// It maintains relations between models during the set operation.
|
48
|
+
// It also bubbles up child events to the parent.
|
49
|
+
set:function (key, value, options) {
|
50
|
+
var attributes, processedRelations, tbp, attr;
|
51
|
+
// Duplicate backbone's behavior to allow separate key/value parameters,
|
52
|
+
// instead of a single 'attributes' object.
|
53
|
+
if (_.isObject(key) || key == null) {
|
45
54
|
attributes = key;
|
46
55
|
options = value;
|
47
|
-
}
|
48
|
-
else {
|
56
|
+
} else {
|
49
57
|
attributes = {};
|
50
|
-
attributes[
|
51
|
-
}
|
58
|
+
attributes[key] = value;
|
59
|
+
}
|
52
60
|
// Extract attributes and options.
|
53
61
|
options || (options = {});
|
54
|
-
if(!attributes) return;
|
55
|
-
if (options.unset) for (attr in attributes) attributes[attr] = void 0;
|
56
|
-
|
57
|
-
if(this.relations){
|
58
|
-
//Iterate over `this.relations` and `set` model and collection values
|
59
|
-
|
60
|
-
|
61
|
-
|
62
|
-
|
63
|
-
|
64
|
-
|
65
|
-
|
66
|
-
|
67
|
-
|
68
|
-
|
69
|
-
|
70
|
-
|
71
|
-
|
72
|
-
|
73
|
-
|
74
|
-
|
75
|
-
|
76
|
-
|
77
|
-
|
62
|
+
if (!attributes) return this;
|
63
|
+
if (options.unset) for (attr in attributes) attributes[attr] = void 0;
|
64
|
+
|
65
|
+
if (this.relations) {
|
66
|
+
// Iterate over `this.relations` and `set` model and collection values
|
67
|
+
// if `relations` are available.
|
68
|
+
_.each(this.relations, function (relation) {
|
69
|
+
var relationKey = relation.key, relatedModel = relation.relatedModel,
|
70
|
+
collectionType = relation.collectionType,
|
71
|
+
val, relationOptions, data;
|
72
|
+
if (attributes[relationKey]) {
|
73
|
+
//Get value of attribute with relation key in `val`.
|
74
|
+
val = _.result(attributes, relationKey);
|
75
|
+
// Get class if relation is stored as a string.
|
76
|
+
relatedModel && _.isString(relatedModel) && (relatedModel = eval(relatedModel));
|
77
|
+
collectionType && _.isString(collectionType) && (collectionType = eval(collectionType));
|
78
|
+
// Merge in `options` specific to this relation.
|
79
|
+
relationOptions = relation.options ? _.extend({}, relation.options, options) : options;
|
80
|
+
// If `relation.type` is `Backbone.Many`,
|
81
|
+
// create `Backbone.Collection` with passed data and perform Backbone `set`.
|
82
|
+
if (relation.type === Backbone.Many) {
|
83
|
+
// `collectionType` of defined `relation` should be instance of `Backbone.Collection`.
|
84
|
+
if (collectionType && !collectionType.prototype instanceof Backbone.Collection) {
|
85
|
+
throw new Error('collectionType must inherit from Backbone.Collection');
|
78
86
|
}
|
79
|
-
|
80
|
-
if(val instanceof Backbone.Collection){
|
81
|
-
|
82
|
-
}
|
83
|
-
|
84
|
-
|
85
|
-
|
86
|
-
|
87
|
-
|
88
|
-
|
89
|
-
|
90
|
-
|
91
|
-
if(val instanceof Backbone.AssociatedModel) return;
|
92
|
-
this.attributes[relation.key] = new relation.relatedModel();
|
93
|
-
refChanged = true;
|
87
|
+
|
88
|
+
if (val instanceof Backbone.Collection) {
|
89
|
+
BackboneModel.set.call(this, relationKey, val, relationOptions);
|
90
|
+
} else if (!this.attributes[relationKey]) {
|
91
|
+
// If `attributes` has no property present,
|
92
|
+
// create `Collection` having `relation.collectionType` as type and
|
93
|
+
// `relation.Model` as model reference, and perform Backbone `set`.
|
94
|
+
data = collectionType ? new collectionType() : this._createCollection(relatedModel);
|
95
|
+
data.add(val, relationOptions);
|
96
|
+
BackboneModel.set.call(this, relationKey, data, relationOptions);
|
97
|
+
} else {
|
98
|
+
this.attributes[relationKey].reset(val, relationOptions);
|
94
99
|
}
|
95
|
-
|
96
|
-
|
97
|
-
|
98
|
-
|
99
|
-
|
100
|
-
|
101
|
-
});
|
102
|
-
this.attributes[relation.key].set(opt,{silent:true});
|
103
|
-
}
|
104
|
-
//Set `val` to model with options
|
105
|
-
this.attributes[relation.key].set(val,options);
|
100
|
+
|
101
|
+
} else if (relation.type === Backbone.One && relatedModel) {
|
102
|
+
// If passed data is not instance of `Backbone.AssociatedModel`,
|
103
|
+
// create `AssociatedModel` and perform backbone `set`.
|
104
|
+
data = val instanceof AssociatedModel ? val : new relatedModel(val);
|
105
|
+
BackboneModel.set.call(this, relationKey, data, relationOptions)
|
106
106
|
}
|
107
|
-
|
108
|
-
|
109
|
-
|
110
|
-
|
111
|
-
|
112
|
-
|
113
|
-
|
114
|
-
|
115
|
-
|
116
|
-
|
117
|
-
|
118
|
-
|
119
|
-
|
120
|
-
|
121
|
-
|
122
|
-
|
107
|
+
|
108
|
+
// Add proxy events to respective parents.
|
109
|
+
// Only add callback if not defined.
|
110
|
+
if (!this.attributes[relationKey]._proxyCallback) {
|
111
|
+
this.attributes[relationKey]._proxyCallback = function () {
|
112
|
+
var args = arguments,
|
113
|
+
opt = args[0].split(":"),
|
114
|
+
eventType = opt[0],
|
115
|
+
eventObject = args[1],
|
116
|
+
indexEventObject = -1,
|
117
|
+
_proxyCalls = this.attributes[relationKey]._proxyCalls,
|
118
|
+
eventPath,
|
119
|
+
eventAvailable;
|
120
|
+
|
121
|
+
// Change the event name to a fully qualified path.
|
122
|
+
if (_.contains(defaultEvents, eventType)) {
|
123
|
+
if (opt && _.size(opt) > 1) {
|
124
|
+
eventPath = opt[1];
|
125
|
+
}
|
126
|
+
// Find the specific object in the collection which has changed.
|
127
|
+
if (this.attributes[relationKey] instanceof Backbone.Collection && "change" === eventType) {
|
128
|
+
indexEventObject = _.indexOf(this.attributes[relationKey].models, eventObject);
|
129
|
+
}
|
130
|
+
// Manipulate `eventPath`.
|
131
|
+
eventPath = relationKey + (indexEventObject !== -1 ?
|
132
|
+
"[" + indexEventObject + "]" : "") + (eventPath ? "." + eventPath : "");
|
133
|
+
args[0] = eventType + ":" + eventPath;
|
134
|
+
|
135
|
+
if (_proxyCalls) {
|
136
|
+
// If event has been already triggered as result of same source `eventPath`,
|
137
|
+
// no need to re-trigger event to prevent cycle.
|
138
|
+
eventAvailable = _.find(_proxyCalls, function (value, eventKey) {
|
139
|
+
// `event` ends with eventKey.
|
140
|
+
var d = eventPath.length - eventKey.length;
|
141
|
+
return eventPath.indexOf(eventKey, d) !== -1;
|
142
|
+
});
|
143
|
+
if (eventAvailable) {
|
144
|
+
return this;
|
145
|
+
}
|
146
|
+
} else {
|
147
|
+
_proxyCalls = this.attributes[relationKey]._proxyCalls = {};
|
148
|
+
}
|
149
|
+
// Add `eventPath` in `_proxyCalls` to keep track of already triggered `event`.
|
150
|
+
_proxyCalls[eventPath] = true;
|
151
|
+
}
|
152
|
+
|
153
|
+
// Bubble up event to parent `model` with new changed arguments.
|
154
|
+
this.trigger.apply(this, args);
|
155
|
+
|
156
|
+
// Remove `eventPath` from `_proxyCalls`,
|
157
|
+
// if `eventPath` and `_proxCalls` are available,
|
158
|
+
// which allow event to be triggered on for next operation of `set`.
|
159
|
+
if (eventPath && _proxyCalls) {
|
160
|
+
delete _proxyCalls[eventPath];
|
161
|
+
}
|
162
|
+
return this;
|
163
|
+
};
|
164
|
+
this.attributes[relationKey].on("all", this.attributes[relationKey]._proxyCallback, this);
|
165
|
+
}
|
166
|
+
|
167
|
+
// Create a local `processedRelations` array to store the relation key which has been processed.
|
168
|
+
// We cannot use `this.relations` because if there is no value defined for `relationKey`,
|
169
|
+
// it will not get processed by either `BackboneModel` `set` or the `AssociatedModel` `set`.
|
170
|
+
!processedRelations && (processedRelations = []);
|
171
|
+
if (_.indexOf(processedRelations, relationKey) === -1) {
|
172
|
+
processedRelations.push(relationKey);
|
173
|
+
}
|
174
|
+
}
|
175
|
+
}, this);
|
176
|
+
}
|
177
|
+
if (processedRelations) {
|
178
|
+
// Find attributes yet to be processed - `tbp`.
|
123
179
|
tbp = {};
|
124
|
-
for(
|
125
|
-
if(_.indexOf(processedRelations,
|
126
|
-
tbp[
|
180
|
+
for (attr in attributes) {
|
181
|
+
if (_.indexOf(processedRelations, attr) === -1) {
|
182
|
+
tbp[attr] = attributes[attr];
|
127
183
|
}
|
128
184
|
}
|
129
|
-
}
|
130
|
-
|
131
|
-
else{
|
185
|
+
} else {
|
186
|
+
// Set all `attributes` to `tbp`.
|
132
187
|
tbp = attributes;
|
133
|
-
}
|
134
|
-
//
|
135
|
-
return
|
136
|
-
},
|
137
|
-
//Returns New `collection` of type `relation.relatedModel
|
138
|
-
_createCollection:
|
139
|
-
var collection;
|
140
|
-
|
141
|
-
|
188
|
+
}
|
189
|
+
// Return results for `BackboneModel.set`.
|
190
|
+
return BackboneModel.set.call(this, tbp, options);
|
191
|
+
},
|
192
|
+
// Returns New `collection` of type `relation.relatedModel`.
|
193
|
+
_createCollection:function (type) {
|
194
|
+
var collection, relatedModel = type;
|
195
|
+
_.isString(relatedModel) && (relatedModel = eval(relatedModel));
|
196
|
+
// Creates new `Backbone.Collection` and defines model class.
|
197
|
+
if (relatedModel && relatedModel.prototype instanceof AssociatedModel) {
|
142
198
|
collection = new Backbone.Collection();
|
143
|
-
collection.model =
|
144
|
-
}
|
145
|
-
|
146
|
-
throw new Error( 'type must inherit from Backbone.AssociatedModel' );
|
199
|
+
collection.model = relatedModel;
|
200
|
+
} else {
|
201
|
+
throw new Error('type must inherit from Backbone.AssociatedModel');
|
147
202
|
}
|
148
203
|
return collection;
|
149
204
|
},
|
150
|
-
//
|
151
|
-
|
152
|
-
|
153
|
-
if
|
154
|
-
|
205
|
+
// Has the model changed. Traverse the object hierarchy to compute dirtyness.
|
206
|
+
hasChanged:function (attr) {
|
207
|
+
var isDirty, relation, attrValue, i, dirtyObjects;
|
208
|
+
// To prevent cycles, check if this node is visited.
|
209
|
+
if (!this.visitedHC) {
|
210
|
+
this.visitedHC = true;
|
211
|
+
isDirty = BackboneModel.hasChanged.apply(this, arguments);
|
212
|
+
if (!isDirty && this.relations) {
|
213
|
+
// Go down the hierarchy to see if anything has `changed`.
|
214
|
+
for (i = 0; i < this.relations.length; ++i) {
|
215
|
+
relation = this.relations[i];
|
216
|
+
attrValue = this.attributes[relation.key];
|
217
|
+
if (attrValue) {
|
218
|
+
if (attrValue instanceof Backbone.Collection) {
|
219
|
+
dirtyObjects = attrValue.filter(function (m) {
|
220
|
+
return m.hasChanged() === true;
|
221
|
+
});
|
222
|
+
_.size(dirtyObjects) > 0 && (isDirty = true);
|
223
|
+
} else {
|
224
|
+
isDirty = attrValue.hasChanged && attrValue.hasChanged();
|
225
|
+
}
|
226
|
+
if (isDirty) {
|
227
|
+
break;
|
228
|
+
}
|
229
|
+
}
|
230
|
+
}
|
231
|
+
}
|
232
|
+
delete this.visitedHC;
|
233
|
+
}
|
234
|
+
return !!isDirty;
|
235
|
+
},
|
236
|
+
// Returns a hash of the changed attributes.
|
237
|
+
changedAttributes:function (diff) {
|
238
|
+
var delta, relation, attrValue, changedCollection, i;
|
239
|
+
// To prevent cycles, check if this node is visited.
|
240
|
+
if (!this.visited) {
|
241
|
+
this.visited = true;
|
242
|
+
delta = BackboneModel.changedAttributes.apply(this, arguments);
|
243
|
+
if (this.relations) {
|
244
|
+
for (i = 0; i < this.relations.length; ++i) {
|
245
|
+
relation = this.relations[i];
|
246
|
+
attrValue = this.attributes[relation.key];
|
247
|
+
if (attrValue) {
|
248
|
+
if (attrValue instanceof Backbone.Collection) {
|
249
|
+
changedCollection = _.filter(attrValue.map(function (m) {
|
250
|
+
return m.changedAttributes();
|
251
|
+
}), function (m) {
|
252
|
+
return !!m;
|
253
|
+
});
|
254
|
+
if (_.size(changedCollection) > 0) {
|
255
|
+
delta[relation.key] = changedCollection;
|
256
|
+
}
|
257
|
+
} else if (attrValue instanceof AssociatedModel && attrValue.hasChanged()) {
|
258
|
+
delta[relation.key] = attrValue.toJSON();
|
259
|
+
}
|
260
|
+
}
|
261
|
+
}
|
262
|
+
}
|
263
|
+
delete this.visited;
|
264
|
+
}
|
265
|
+
return !delta ? false : delta;
|
266
|
+
},
|
267
|
+
// Returns the hash of the previous attributes of the graph.
|
268
|
+
previousAttributes:function () {
|
269
|
+
var pa, attrValue, pattrValue, pattrJSON;
|
270
|
+
// To prevent cycles, check if this node is visited.
|
271
|
+
if (!this.visited) {
|
155
272
|
this.visited = true;
|
156
|
-
|
157
|
-
|
273
|
+
pa = BackboneModel.previousAttributes.apply(this, arguments);
|
274
|
+
if (this.relations) {
|
275
|
+
_.each(this.relations, function (relation) {
|
276
|
+
attrValue = this.attributes[relation.key];
|
277
|
+
pattrValue = pa[relation.key];
|
278
|
+
pattrJSON = pattrValue ? pattrValue.toJSON() : undefined;
|
279
|
+
if (pattrValue && pattrValue == attrValue) {
|
280
|
+
if (attrValue instanceof AssociatedModel) {
|
281
|
+
pa[relation.key] = attrValue.previousAttributes();
|
282
|
+
} else if (attrValue instanceof Backbone.Collection) {
|
283
|
+
pa[relation.key] = attrValue.map(function (m) {
|
284
|
+
return m.previousAttributes();
|
285
|
+
});
|
286
|
+
}
|
287
|
+
} else {
|
288
|
+
if (pattrValue)
|
289
|
+
pa[relation.key] = pattrJSON;
|
290
|
+
}
|
291
|
+
}, this);
|
292
|
+
}
|
158
293
|
delete this.visited;
|
159
294
|
}
|
160
|
-
return
|
295
|
+
return pa;
|
296
|
+
},
|
297
|
+
// Return the previous value of the passed in attribute.
|
298
|
+
previous:function (attr) {
|
299
|
+
return this.previousAttributes()[attr];
|
161
300
|
},
|
162
|
-
//The JSON representation of the model.
|
163
|
-
toJSON
|
164
|
-
var json;
|
165
|
-
if(!this.visited){
|
301
|
+
// The JSON representation of the model.
|
302
|
+
toJSON:function (options) {
|
303
|
+
var json, aJson;
|
304
|
+
if (!this.visited) {
|
166
305
|
this.visited = true;
|
167
|
-
//Get json representation from `
|
168
|
-
json =
|
169
|
-
//If `this.relations` is defined, iterate through each `relation`
|
170
|
-
|
171
|
-
|
306
|
+
// Get json representation from `BackboneModel.toJSON`.
|
307
|
+
json = BackboneModel.toJSON.apply(this, arguments);
|
308
|
+
// If `this.relations` is defined, iterate through each `relation`
|
309
|
+
// and added it's json representation to parents' json representation.
|
310
|
+
if (this.relations) {
|
311
|
+
_.each(this.relations, function (relation) {
|
172
312
|
var attr = this.attributes[relation.key];
|
173
|
-
if(attr){
|
174
|
-
aJson = attr.toJSON();
|
175
|
-
json[relation.key] = _.isArray(aJson)?_.compact(aJson):aJson;
|
313
|
+
if (attr) {
|
314
|
+
aJson = attr.toJSON(options);
|
315
|
+
json[relation.key] = _.isArray(aJson) ? _.compact(aJson) : aJson;
|
176
316
|
}
|
177
|
-
},this);
|
317
|
+
}, this);
|
178
318
|
}
|
179
319
|
delete this.visited;
|
180
320
|
}
|
181
321
|
return json;
|
182
322
|
},
|
183
|
-
//
|
184
|
-
clone
|
185
|
-
var cloneObj;
|
186
|
-
if(!this.visited){
|
323
|
+
// Deep `clone` the model.
|
324
|
+
clone:function () {
|
325
|
+
var cloneObj, newCollection;
|
326
|
+
if (!this.visited) {
|
187
327
|
this.visited = true;
|
188
|
-
//Get shallow clone from `
|
189
|
-
cloneObj =
|
190
|
-
//If `this.relations` is defined, iterate through each `relation` and `clone
|
191
|
-
if(this.relations){
|
192
|
-
_.each(this.relations
|
193
|
-
if(this.attributes[relation.key]){
|
328
|
+
// Get shallow clone from `BackboneModel.clone`.
|
329
|
+
cloneObj = BackboneModel.clone.apply(this, arguments);
|
330
|
+
// If `this.relations` is defined, iterate through each `relation` and `clone`.
|
331
|
+
if (this.relations) {
|
332
|
+
_.each(this.relations, function (relation) {
|
333
|
+
if (this.attributes[relation.key]) {
|
194
334
|
var sourceObj = cloneObj.attributes[relation.key];
|
195
|
-
if(sourceObj instanceof Backbone.Collection){
|
196
|
-
//Creates new `collection` using `relation
|
197
|
-
|
198
|
-
|
199
|
-
|
200
|
-
|
201
|
-
mClone
|
202
|
-
|
335
|
+
if (sourceObj instanceof Backbone.Collection) {
|
336
|
+
// Creates new `collection` using `relation`.
|
337
|
+
newCollection = relation.collectionType ? new relation.collectionType() :
|
338
|
+
this._createCollection(relation.relatedModel);
|
339
|
+
// Added each `clone` model to `newCollection`.
|
340
|
+
sourceObj.each(function (model) {
|
341
|
+
var mClone = model.clone();
|
342
|
+
mClone && newCollection.add(mClone);
|
343
|
+
});
|
203
344
|
cloneObj.attributes[relation.key] = newCollection;
|
204
|
-
}
|
205
|
-
else if(sourceObj instanceof Backbone.Model){
|
345
|
+
} else if (sourceObj instanceof Backbone.Model) {
|
206
346
|
cloneObj.attributes[relation.key] = sourceObj.clone();
|
207
347
|
}
|
208
348
|
}
|
209
|
-
},this);
|
349
|
+
}, this);
|
210
350
|
}
|
211
351
|
delete this.visited;
|
212
352
|
}
|
213
353
|
return cloneObj;
|
214
|
-
}
|
215
|
-
});
|
216
|
-
|
217
|
-
// or as a function.
|
218
|
-
var getValue = function(object, prop) {
|
219
|
-
if (!(object && object[prop])) return null;
|
220
|
-
return _.isFunction(object[prop]) ? object[prop]() : object[prop];
|
221
|
-
};
|
222
|
-
})();
|
354
|
+
}
|
355
|
+
});
|
356
|
+
})();
|