js_stack 0.0.4 → 0.1.0

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,574 @@
1
+ //
2
+ // Backbone-associations.js 0.5.4
3
+ //
4
+ // (c) 2013 Dhruva Ray, Jaynti Kanani, Persistent Systems Ltd.
5
+ // Backbone-associations may be freely distributed under the MIT license.
6
+ // For all details and documentation:
7
+ // https://github.com/dhruvaray/backbone-associations/
8
+ //
9
+
10
+ // Initial Setup
11
+ // --------------
12
+ (function () {
13
+ "use strict";
14
+
15
+ // Save a reference to the global object (`window` in the browser, `exports`
16
+ // on the server).
17
+ var root = this;
18
+
19
+ // The top-level namespace. All public Backbone classes and modules will be attached to this.
20
+ // Exported for the browser and CommonJS.
21
+ var _, Backbone, BackboneModel, BackboneCollection, ModelProto,
22
+ CollectionProto, defaultEvents, AssociatedModel, pathChecker,
23
+ collectionEvents, delimiters, pathSeparator;
24
+
25
+ if (typeof exports !== 'undefined') {
26
+ _ = require('underscore');
27
+ Backbone = require('backbone');
28
+ if (typeof module !== 'undefined' && module.exports) {
29
+ module.exports = Backbone;
30
+ }
31
+ exports = Backbone;
32
+ } else {
33
+ _ = root._;
34
+ Backbone = root.Backbone;
35
+ }
36
+ // Create local reference `Model` prototype.
37
+ BackboneModel = Backbone.Model;
38
+ BackboneCollection = Backbone.Collection;
39
+ ModelProto = BackboneModel.prototype;
40
+ CollectionProto = BackboneCollection.prototype;
41
+
42
+ // Built-in Backbone `events`.
43
+ defaultEvents = ["change", "add", "remove", "reset", "sort", "destroy"];
44
+ collectionEvents = ["reset", "sort"];
45
+
46
+ Backbone.Associations = {
47
+ VERSION: "0.5.4"
48
+ };
49
+
50
+ // Define `getter` and `setter` for `separator`
51
+ var getSeparator = function() {
52
+ return pathSeparator;
53
+ };
54
+ // Define `setSeperator`
55
+ var setSeparator = function(value) {
56
+ if (!_.isString(value) || _.size(value) < 1) {
57
+ value = ".";
58
+ }
59
+ // set private properties
60
+ pathSeparator = value;
61
+ pathChecker = new RegExp("[\\" + pathSeparator + "\\[\\]]+", "g");
62
+ delimiters = new RegExp("[^\\" + pathSeparator + "\\[\\]]+", "g");
63
+ };
64
+
65
+ try {
66
+ // Define `SEPERATOR` property to Backbone.Associations
67
+ Object.defineProperty(Backbone.Associations, 'SEPARATOR', {
68
+ enumerable: true,
69
+ get: getSeparator,
70
+ set: setSeparator
71
+ });
72
+ } catch (e) {}
73
+
74
+ // Backbone.AssociatedModel
75
+ // --------------
76
+
77
+ //Add `Many` and `One` relations to Backbone Object.
78
+ Backbone.Associations.Many = Backbone.Many = "Many";
79
+ Backbone.Associations.One = Backbone.One = "One";
80
+ Backbone.Associations.Self = Backbone.Self = "Self";
81
+ // Set default separator
82
+ Backbone.Associations.SEPARATOR = ".";
83
+ Backbone.Associations.getSeparator = getSeparator;
84
+ Backbone.Associations.setSeparator = setSeparator;
85
+ setSeparator();
86
+ // Define `AssociatedModel` (Extends Backbone.Model).
87
+ AssociatedModel = Backbone.AssociatedModel = Backbone.Associations.AssociatedModel = BackboneModel.extend({
88
+ // Define relations with Associated Model.
89
+ relations:undefined,
90
+ // Define `Model` property which can keep track of already fired `events`,
91
+ // and prevent redundant event to be triggered in case of cyclic model graphs.
92
+ _proxyCalls:undefined,
93
+
94
+ // Get the value of an attribute.
95
+ get:function (attr) {
96
+ var obj = ModelProto.get.call(this, attr);
97
+ return obj ? obj : this._getAttr.apply(this, arguments);
98
+ },
99
+
100
+ // Set a hash of model attributes on the Backbone Model.
101
+ set:function (key, value, options) {
102
+ var attributes, result;
103
+ // Duplicate backbone's behavior to allow separate key/value parameters,
104
+ // instead of a single 'attributes' object.
105
+ if (_.isObject(key) || key == null) {
106
+ attributes = key;
107
+ options = value;
108
+ } else {
109
+ attributes = {};
110
+ attributes[key] = value;
111
+ }
112
+ result = this._set(attributes, options);
113
+ // Trigger events which have been blocked until the entire object graph is updated.
114
+ this._processPendingEvents();
115
+ return result;
116
+
117
+ },
118
+
119
+ // Works with an attribute hash and options + fully qualified paths
120
+ _set:function (attributes, options) {
121
+ var attr, modelMap, modelId, obj, result = this;
122
+ if (!attributes) return this;
123
+ for (attr in attributes) {
124
+ //Create a map for each unique object whose attributes we want to set
125
+ modelMap || (modelMap = {});
126
+ if (attr.match(pathChecker)) {
127
+ var pathTokens = getPathArray(attr), initials = _.initial(pathTokens),
128
+ last = pathTokens[pathTokens.length - 1],
129
+ parentModel = this.get(initials);
130
+ if (parentModel instanceof AssociatedModel) {
131
+ obj = modelMap[parentModel.cid] || (modelMap[parentModel.cid] = {'model':parentModel, 'data':{}});
132
+ obj.data[last] = attributes[attr];
133
+ }
134
+ } else {
135
+ obj = modelMap[this.cid] || (modelMap[this.cid] = {'model':this, 'data':{}});
136
+ obj.data[attr] = attributes[attr];
137
+ }
138
+ }
139
+
140
+ if (modelMap) {
141
+ for (modelId in modelMap) {
142
+ obj = modelMap[modelId];
143
+ this._setAttr.call(obj.model, obj.data, options) || (result = false);
144
+
145
+ }
146
+ } else {
147
+ result = this._setAttr.call(this, attributes, options);
148
+ }
149
+ return result;
150
+
151
+ },
152
+
153
+ // Set a hash of model attributes on the object,
154
+ // fire Backbone `event` with options.
155
+ // It maintains relations between models during the set operation.
156
+ // It also bubbles up child events to the parent.
157
+ _setAttr:function (attributes, options) {
158
+ var attr;
159
+ // Extract attributes and options.
160
+ options || (options = {});
161
+ if (options.unset) for (attr in attributes) attributes[attr] = void 0;
162
+ this.parents = this.parents || [];
163
+
164
+ if (this.relations) {
165
+ // Iterate over `this.relations` and `set` model and collection values
166
+ // if `relations` are available.
167
+ _.each(this.relations, function (relation) {
168
+ var relationKey = relation.key,
169
+ relatedModel = relation.relatedModel,
170
+ collectionType = relation.collectionType,
171
+ map = relation.map,
172
+ currVal = this.attributes[relationKey],
173
+ idKey = currVal && currVal.idAttribute,
174
+ val, relationOptions, data, relationValue, newCtx = false;
175
+
176
+ // Call function if relatedModel is implemented as a function
177
+ if (relatedModel && !(relatedModel.prototype instanceof BackboneModel))
178
+ relatedModel = _.isFunction(relatedModel) ?
179
+ relatedModel.call(this, relation, attributes) :
180
+ relatedModel;
181
+
182
+ // Get class if relation and map is stored as a string.
183
+ if (relatedModel && _.isString(relatedModel)) {
184
+ relatedModel = (relatedModel === Backbone.Self) ? this.constructor : map2Scope(relatedModel);
185
+ }
186
+ collectionType && _.isString(collectionType) && (collectionType = map2Scope(collectionType));
187
+ map && _.isString(map) && (map = map2Scope(map));
188
+ // Merge in `options` specific to this relation.
189
+ relationOptions = relation.options ? _.extend({}, relation.options, options) : options;
190
+
191
+ if ((!relatedModel) && (!collectionType))
192
+ throw new Error('specify either a relatedModel or collectionType');
193
+
194
+ if (attributes[relationKey]) {
195
+ // Get value of attribute with relation key in `val`.
196
+ val = _.result(attributes, relationKey);
197
+ // Map `val` if a transformation function is provided.
198
+ val = map ? map.call(this, val, collectionType ? collectionType : relatedModel) : val;
199
+
200
+ // If `relation.type` is `Backbone.Many`,
201
+ // Create `Backbone.Collection` with passed data and perform Backbone `set`.
202
+ if (relation.type === Backbone.Many) {
203
+ // `collectionType` of defined `relation` should be instance of `Backbone.Collection`.
204
+ if (collectionType && !collectionType.prototype instanceof BackboneCollection) {
205
+ throw new Error('collectionType must inherit from Backbone.Collection');
206
+ }
207
+
208
+ if (currVal) {
209
+ // Setting this flag will prevent events from firing immediately. That way clients
210
+ // will not get events until the entire object graph is updated.
211
+ currVal._deferEvents = true;
212
+
213
+ // Use Backbone.Collection's `reset` or smart `set` method
214
+ currVal[relationOptions.reset ? 'reset' : 'set'](
215
+ val instanceof BackboneCollection ? val.models : val, relationOptions);
216
+
217
+ data = currVal;
218
+
219
+ } else {
220
+ newCtx = true;
221
+
222
+ if (val instanceof BackboneCollection) {
223
+ data = val;
224
+ } else {
225
+ data = collectionType ? new collectionType() : this._createCollection(relatedModel);
226
+ data[relationOptions.reset ? 'reset' : 'set'](val, relationOptions);
227
+ }
228
+ }
229
+
230
+ } else if (relation.type === Backbone.One) {
231
+
232
+ if (!relatedModel)
233
+ throw new Error('specify a relatedModel for Backbone.One type');
234
+
235
+ if (!(relatedModel.prototype instanceof Backbone.AssociatedModel))
236
+ throw new Error('specify an AssociatedModel for Backbone.One type');
237
+
238
+ data = val instanceof AssociatedModel ? val : new relatedModel(val, relationOptions);
239
+ //Is the passed in data for the same key?
240
+ if (currVal && data.attributes[idKey] &&
241
+ currVal.attributes[idKey] === data.attributes[idKey]) {
242
+ // Setting this flag will prevent events from firing immediately. That way clients
243
+ // will not get events until the entire object graph is updated.
244
+ currVal._deferEvents = true;
245
+ // Perform the traditional `set` operation
246
+ currVal._set(val instanceof AssociatedModel ? val.attributes : val, relationOptions);
247
+ data = currVal;
248
+ } else {
249
+ newCtx = true;
250
+ }
251
+
252
+ } else {
253
+ throw new Error('type attribute must be specified and have the values Backbone.One or Backbone.Many');
254
+ }
255
+
256
+
257
+ attributes[relationKey] = data;
258
+ relationValue = data;
259
+
260
+ // Add proxy events to respective parents.
261
+ // Only add callback if not defined or new Ctx has been identified.
262
+ if (newCtx || (relationValue && !relationValue._proxyCallback)) {
263
+ relationValue._proxyCallback = function () {
264
+ return this._bubbleEvent.call(this, relationKey, relationValue, arguments);
265
+ };
266
+ relationValue.on("all", relationValue._proxyCallback, this);
267
+ }
268
+
269
+ }
270
+ //Distinguish between the value of undefined versus a set no-op
271
+ if (attributes.hasOwnProperty(relationKey)) {
272
+ //Maintain reverse pointers - a.k.a parents
273
+ var updated = attributes[relationKey];
274
+ var original = this.attributes[relationKey];
275
+ if (updated) {
276
+ updated.parents = updated.parents || [];
277
+ (_.indexOf(updated.parents, this) == -1) && updated.parents.push(this);
278
+ } else if (original && original.parents.length > 0) { // New value is undefined
279
+ original.parents = _.difference(original.parents, [this]);
280
+ // Don't bubble to this parent anymore
281
+ original._proxyCallback && original.off("all", original._proxyCallback, this);
282
+ }
283
+ }
284
+ }, this);
285
+ }
286
+ // Return results for `BackboneModel.set`.
287
+ return ModelProto.set.call(this, attributes, options);
288
+ },
289
+ // Bubble-up event to `parent` Model
290
+ _bubbleEvent:function (relationKey, relationValue, eventArguments) {
291
+ var args = eventArguments,
292
+ opt = args[0].split(":"),
293
+ eventType = opt[0],
294
+ catch_all = args[0] == "nested-change",
295
+ eventObject = args[1],
296
+ colObject = args[2],
297
+ indexEventObject = -1,
298
+ _proxyCalls = relationValue._proxyCalls,
299
+ cargs,
300
+ eventPath,
301
+ basecolEventPath,
302
+ isDefaultEvent = _.indexOf(defaultEvents, eventType) !== -1;
303
+
304
+ //Short circuit the listen in to the nested-graph event
305
+ if (catch_all) return;
306
+
307
+ // Change the event name to a fully qualified path.
308
+ _.size(opt) > 1 && (eventPath = opt[1]);
309
+
310
+ if (_.indexOf(collectionEvents, eventType) !== -1) {
311
+ colObject = eventObject;
312
+ }
313
+
314
+ // Find the specific object in the collection which has changed.
315
+ if (relationValue instanceof BackboneCollection && isDefaultEvent && eventObject) {
316
+ var pathTokens = getPathArray(eventPath),
317
+ initialTokens = _.initial(pathTokens), colModel;
318
+
319
+ colModel = relationValue.find(function (model) {
320
+ if (eventObject === model) return true;
321
+ if (!model) return false;
322
+ var changedModel = model.get(initialTokens);
323
+
324
+ if ((changedModel instanceof AssociatedModel || changedModel instanceof BackboneCollection)
325
+ && eventObject === changedModel)
326
+ return true;
327
+
328
+ changedModel = model.get(pathTokens);
329
+
330
+ if ((changedModel instanceof AssociatedModel || changedModel instanceof BackboneCollection)
331
+ && eventObject === changedModel)
332
+ return true;
333
+
334
+ if (changedModel instanceof BackboneCollection && colObject
335
+ && colObject === changedModel)
336
+ return true;
337
+ });
338
+ colModel && (indexEventObject = relationValue.indexOf(colModel));
339
+ }
340
+
341
+ // Manipulate `eventPath`.
342
+ eventPath = relationKey + ((indexEventObject !== -1 && (eventType === "change" || eventPath)) ?
343
+ "[" + indexEventObject + "]" : "") + (eventPath ? pathSeparator + eventPath : "");
344
+
345
+ // Short circuit collection * events
346
+ if (/\[\*\]/g.test(eventPath)) return this;
347
+ basecolEventPath = eventPath.replace(/\[\d+\]/g, '[*]');
348
+
349
+ cargs = [];
350
+ cargs.push.apply(cargs, args);
351
+ cargs[0] = eventType + ":" + eventPath;
352
+
353
+ // If event has been already triggered as result of same source `eventPath`,
354
+ // no need to re-trigger event to prevent cycle.
355
+ _proxyCalls = relationValue._proxyCalls = (_proxyCalls || {});
356
+ if (this._isEventAvailable.call(this, _proxyCalls, eventPath)) return this;
357
+
358
+ // Add `eventPath` in `_proxyCalls` to keep track of already triggered `event`.
359
+ _proxyCalls[eventPath] = true;
360
+
361
+ // Set up previous attributes correctly.
362
+ if ("change" === eventType) {
363
+ this._previousAttributes[relationKey] = relationValue._previousAttributes;
364
+ this.changed[relationKey] = relationValue;
365
+ }
366
+
367
+ // Bubble up event to parent `model` with new changed arguments.
368
+ this.trigger.apply(this, cargs);
369
+
370
+ //Only fire for change. Not change:attribute
371
+ if ("change" === eventType && this.get(eventPath) != args[2]) {
372
+ var ncargs = ["nested-change", eventPath, args[1]];
373
+ args[2] && ncargs.push(args[2]); //args[2] will be options if present
374
+ this.trigger.apply(this, ncargs);
375
+ }
376
+
377
+ // Remove `eventPath` from `_proxyCalls`,
378
+ // if `eventPath` and `_proxyCalls` are available,
379
+ // which allow event to be triggered on for next operation of `set`.
380
+ if (_proxyCalls && eventPath) delete _proxyCalls[eventPath];
381
+
382
+ // Create a collection modified event with wild-card
383
+ if (eventPath !== basecolEventPath) {
384
+ cargs[0] = eventType + ":" + basecolEventPath;
385
+ this.trigger.apply(this, cargs);
386
+ }
387
+
388
+ return this;
389
+ },
390
+
391
+ // Has event been fired from this source. Used to prevent event recursion in cyclic graphs
392
+ _isEventAvailable:function (_proxyCalls, path) {
393
+ return _.find(_proxyCalls, function (value, eventKey) {
394
+ return path.indexOf(eventKey, path.length - eventKey.length) !== -1;
395
+ });
396
+ },
397
+
398
+ // Returns New `collection` of type `relation.relatedModel`.
399
+ _createCollection:function (type) {
400
+ var collection, relatedModel = type;
401
+ _.isString(relatedModel) && (relatedModel = map2Scope(relatedModel));
402
+ // Creates new `Backbone.Collection` and defines model class.
403
+ if (relatedModel && (relatedModel.prototype instanceof AssociatedModel) || _.isFunction(relatedModel)) {
404
+ collection = new BackboneCollection();
405
+ collection.model = relatedModel;
406
+ } else {
407
+ throw new Error('type must inherit from Backbone.AssociatedModel');
408
+ }
409
+ return collection;
410
+ },
411
+
412
+ // Process all pending events after the entire object graph has been updated
413
+ _processPendingEvents:function () {
414
+ if (!this._processedEvents) {
415
+ this._processedEvents = true;
416
+
417
+ this._deferEvents = false;
418
+
419
+ // Trigger all pending events
420
+ _.each(this._pendingEvents, function (e) {
421
+ e.c.trigger.apply(e.c, e.a);
422
+ });
423
+
424
+ this._pendingEvents = [];
425
+
426
+ // Traverse down the object graph and call process pending events on sub-trees
427
+ _.each(this.relations, function (relation) {
428
+ var val = this.attributes[relation.key];
429
+ val && val._processPendingEvents();
430
+ }, this);
431
+
432
+ delete this._processedEvents;
433
+ }
434
+ },
435
+
436
+ // Override trigger to defer events in the object graph.
437
+ trigger:function (name) {
438
+ // Defer event processing
439
+ if (this._deferEvents) {
440
+ this._pendingEvents = this._pendingEvents || [];
441
+ // Maintain a queue of pending events to trigger after the entire object graph is updated.
442
+ this._pendingEvents.push({c:this, a:arguments});
443
+ } else {
444
+ ModelProto.trigger.apply(this, arguments);
445
+ }
446
+ },
447
+
448
+ // The JSON representation of the model.
449
+ toJSON:function (options) {
450
+ var json = {}, aJson;
451
+ json[this.idAttribute] = this.id;
452
+ if (!this.visited) {
453
+ this.visited = true;
454
+ // Get json representation from `BackboneModel.toJSON`.
455
+ json = ModelProto.toJSON.apply(this, arguments);
456
+ // If `this.relations` is defined, iterate through each `relation`
457
+ // and added it's json representation to parents' json representation.
458
+ if (this.relations) {
459
+ _.each(this.relations, function (relation) {
460
+ var attr = this.attributes[relation.key];
461
+ if (attr) {
462
+ aJson = attr.toJSON ? attr.toJSON(options) : attr;
463
+ json[relation.key] = _.isArray(aJson) ? _.compact(aJson) : aJson;
464
+ }
465
+ }, this);
466
+ }
467
+ delete this.visited;
468
+ }
469
+ return json;
470
+ },
471
+
472
+ // Create a new model with identical attributes to this one.
473
+ clone:function () {
474
+ return new this.constructor(this.toJSON());
475
+ },
476
+
477
+ // Call this if you want to set an `AssociatedModel` to a falsy value like undefined/null directly.
478
+ // Not calling this will leak memory and have wrong parents.
479
+ // See test case "parent relations"
480
+ cleanup:function () {
481
+ _.each(this.relations, function (relation) {
482
+ var val = this.attributes[relation.key];
483
+ val && (val.parents = _.difference(val.parents, [this]));
484
+ }, this);
485
+ this.off();
486
+ },
487
+
488
+ // Navigate the path to the leaf object in the path to query for the attribute value
489
+ _getAttr:function (path) {
490
+
491
+ var result = this,
492
+ //Tokenize the path
493
+ attrs = getPathArray(path),
494
+ key,
495
+ i;
496
+ if (_.size(attrs) < 1) return;
497
+ for (i = 0; i < attrs.length; i++) {
498
+ key = attrs[i];
499
+ if (!result) break;
500
+ //Navigate the path to get to the result
501
+ result = result instanceof BackboneCollection
502
+ ? (isNaN(key) ? undefined : result.at(key))
503
+ : result.attributes[key];
504
+ }
505
+ return result;
506
+ }
507
+ });
508
+
509
+ // Tokenize the fully qualified event path
510
+ var getPathArray = function (path) {
511
+ if (path === '') return [''];
512
+ return _.isString(path) ? (path.match(delimiters)) : path || [];
513
+ };
514
+
515
+ var map2Scope = function (path) {
516
+ return _.reduce(path.split(pathSeparator), function (memo, elem) {
517
+ return memo[elem];
518
+ }, root);
519
+ };
520
+
521
+ //Infer the relation from the collection's parents and find the appropriate map for the passed in `models`
522
+ var map2models = function (parents, target, models) {
523
+ var relation, surrogate;
524
+ //Iterate over collection's parents
525
+ _.find(parents, function (parent) {
526
+ //Iterate over relations
527
+ relation = _.find(parent.relations, function (rel) {
528
+ return parent.get(rel.key) === target;
529
+ }, this);
530
+ if (relation) {
531
+ surrogate = parent;//surrogate for transformation
532
+ return true;//break;
533
+ }
534
+ }, this);
535
+
536
+ //If we found a relation and it has a mapping function
537
+ if (relation && relation.map) {
538
+ return relation.map.call(surrogate, models, target);
539
+ }
540
+ return models;
541
+ };
542
+
543
+ var proxies = {};
544
+ // Proxy Backbone collection methods
545
+ _.each(['set', 'remove', 'reset'], function (method) {
546
+ proxies[method] = BackboneCollection.prototype[method];
547
+
548
+ CollectionProto[method] = function (models, options) {
549
+ //Short-circuit if this collection doesn't hold `AssociatedModels`
550
+ if (this.model.prototype instanceof AssociatedModel && this.parents) {
551
+ //Find a map function if available and perform a transformation
552
+ arguments[0] = map2models(this.parents, this, models);
553
+ }
554
+ return proxies[method].apply(this, arguments);
555
+ }
556
+ });
557
+
558
+ // Override trigger to defer events in the object graph.
559
+ proxies['trigger'] = CollectionProto['trigger'];
560
+ CollectionProto['trigger'] = function (name) {
561
+ if (this._deferEvents) {
562
+ this._pendingEvents = this._pendingEvents || [];
563
+ // Maintain a queue of pending events to trigger after the entire object graph is updated.
564
+ this._pendingEvents.push({c:this, a:arguments});
565
+ } else {
566
+ proxies['trigger'].apply(this, arguments);
567
+ }
568
+ };
569
+
570
+ // Attach process pending event functionality on collections as well. Re-use from `AssociatedModel`
571
+ CollectionProto._processPendingEvents = AssociatedModel.prototype._processPendingEvents;
572
+
573
+
574
+ }).call(this);
@@ -1 +1 @@
1
- //= require js_stack/backbone.associations/backbone.associations-0.5.1
1
+ //= require js_stack/backbone.associations/backbone.associations-0.5.4
@@ -1 +1 @@
1
- //= require backbone-rails
1
+ //= require js_stack/backbone/backbone-1.1.0