js_stack 0.0.1

Sign up to get free protection for your applications and to get access to all the features.
@@ -0,0 +1,438 @@
1
+ /*jshint expr:true eqnull:true */
2
+ /**
3
+ *
4
+ * Backbone.DeepModel v0.10.4
5
+ *
6
+ * Copyright (c) 2013 Charles Davison, Pow Media Ltd
7
+ *
8
+ * https://github.com/powmedia/backbone-deep-model
9
+ * Licensed under the MIT License
10
+ */
11
+
12
+ /**
13
+ * Underscore mixins for deep objects
14
+ *
15
+ * Based on https://gist.github.com/echong/3861963
16
+ */
17
+ (function() {
18
+ var arrays, basicObjects, deepClone, deepExtend, deepExtendCouple, isBasicObject,
19
+ __slice = [].slice;
20
+
21
+ deepClone = function(obj) {
22
+ var func, isArr;
23
+ if (!_.isObject(obj) || _.isFunction(obj)) {
24
+ return obj;
25
+ }
26
+ if (obj instanceof Backbone.Collection || obj instanceof Backbone.Model) {
27
+ return obj;
28
+ }
29
+ if (_.isDate(obj)) {
30
+ return new Date(obj.getTime());
31
+ }
32
+ if (_.isRegExp(obj)) {
33
+ return new RegExp(obj.source, obj.toString().replace(/.*\//, ""));
34
+ }
35
+ isArr = _.isArray(obj || _.isArguments(obj));
36
+ func = function(memo, value, key) {
37
+ if (isArr) {
38
+ memo.push(deepClone(value));
39
+ } else {
40
+ memo[key] = deepClone(value);
41
+ }
42
+ return memo;
43
+ };
44
+ return _.reduce(obj, func, isArr ? [] : {});
45
+ };
46
+
47
+ isBasicObject = function(object) {
48
+ if (object == null) return false;
49
+ return (object.prototype === {}.prototype || object.prototype === Object.prototype) && _.isObject(object) && !_.isArray(object) && !_.isFunction(object) && !_.isDate(object) && !_.isRegExp(object) && !_.isArguments(object);
50
+ };
51
+
52
+ basicObjects = function(object) {
53
+ return _.filter(_.keys(object), function(key) {
54
+ return isBasicObject(object[key]);
55
+ });
56
+ };
57
+
58
+ arrays = function(object) {
59
+ return _.filter(_.keys(object), function(key) {
60
+ return _.isArray(object[key]);
61
+ });
62
+ };
63
+
64
+ deepExtendCouple = function(destination, source, maxDepth) {
65
+ var combine, recurse, sharedArrayKey, sharedArrayKeys, sharedObjectKey, sharedObjectKeys, _i, _j, _len, _len1;
66
+ if (maxDepth == null) {
67
+ maxDepth = 20;
68
+ }
69
+ if (maxDepth <= 0) {
70
+ console.warn('_.deepExtend(): Maximum depth of recursion hit.');
71
+ return _.extend(destination, source);
72
+ }
73
+ sharedObjectKeys = _.intersection(basicObjects(destination), basicObjects(source));
74
+ recurse = function(key) {
75
+ return source[key] = deepExtendCouple(destination[key], source[key], maxDepth - 1);
76
+ };
77
+ for (_i = 0, _len = sharedObjectKeys.length; _i < _len; _i++) {
78
+ sharedObjectKey = sharedObjectKeys[_i];
79
+ recurse(sharedObjectKey);
80
+ }
81
+ sharedArrayKeys = _.intersection(arrays(destination), arrays(source));
82
+ combine = function(key) {
83
+ return source[key] = _.union(destination[key], source[key]);
84
+ };
85
+ for (_j = 0, _len1 = sharedArrayKeys.length; _j < _len1; _j++) {
86
+ sharedArrayKey = sharedArrayKeys[_j];
87
+ combine(sharedArrayKey);
88
+ }
89
+ return _.extend(destination, source);
90
+ };
91
+
92
+ deepExtend = function() {
93
+ var finalObj, maxDepth, objects, _i;
94
+ objects = 2 <= arguments.length ? __slice.call(arguments, 0, _i = arguments.length - 1) : (_i = 0, []), maxDepth = arguments[_i++];
95
+ if (!_.isNumber(maxDepth)) {
96
+ objects.push(maxDepth);
97
+ maxDepth = 20;
98
+ }
99
+ if (objects.length <= 1) {
100
+ return objects[0];
101
+ }
102
+ if (maxDepth <= 0) {
103
+ return _.extend.apply(this, objects);
104
+ }
105
+ finalObj = objects.shift();
106
+ while (objects.length > 0) {
107
+ finalObj = deepExtendCouple(finalObj, deepClone(objects.shift()), maxDepth);
108
+ }
109
+ return finalObj;
110
+ };
111
+
112
+ _.mixin({
113
+ deepClone: deepClone,
114
+ isBasicObject: isBasicObject,
115
+ basicObjects: basicObjects,
116
+ arrays: arrays,
117
+ deepExtend: deepExtend
118
+ });
119
+
120
+ }).call(this);
121
+
122
+ /**
123
+ * Main source
124
+ */
125
+
126
+ ;(function(factory) {
127
+ if (typeof define === 'function' && define.amd) {
128
+ // AMD
129
+ define(['underscore', 'backbone'], factory);
130
+ } else {
131
+ // globals
132
+ factory(_, Backbone);
133
+ }
134
+ }(function(_, Backbone) {
135
+
136
+ /**
137
+ * Takes a nested object and returns a shallow object keyed with the path names
138
+ * e.g. { "level1.level2": "value" }
139
+ *
140
+ * @param {Object} Nested object e.g. { level1: { level2: 'value' } }
141
+ * @return {Object} Shallow object with path names e.g. { 'level1.level2': 'value' }
142
+ */
143
+ function objToPaths(obj) {
144
+ var ret = {},
145
+ separator = DeepModel.keyPathSeparator;
146
+
147
+ for (var key in obj) {
148
+ var val = obj[key];
149
+
150
+ if (val && val.constructor === Object && !_.isEmpty(val)) {
151
+ //Recursion for embedded objects
152
+ var obj2 = objToPaths(val);
153
+
154
+ for (var key2 in obj2) {
155
+ var val2 = obj2[key2];
156
+
157
+ ret[key + separator + key2] = val2;
158
+ }
159
+ } else {
160
+ ret[key] = val;
161
+ }
162
+ }
163
+
164
+ return ret;
165
+ }
166
+
167
+ /**
168
+ * @param {Object} Object to fetch attribute from
169
+ * @param {String} Object path e.g. 'user.name'
170
+ * @return {Mixed}
171
+ */
172
+ function getNested(obj, path, return_exists) {
173
+ var separator = DeepModel.keyPathSeparator;
174
+
175
+ var fields = path.split(separator);
176
+ var result = obj;
177
+ return_exists || (return_exists === false);
178
+ for (var i = 0, n = fields.length; i < n; i++) {
179
+ if (return_exists && !_.has(result, fields[i])) {
180
+ return false;
181
+ }
182
+ result = result[fields[i]];
183
+
184
+ if (result == null && i < n - 1) {
185
+ result = {};
186
+ }
187
+
188
+ if (typeof result === 'undefined') {
189
+ if (return_exists)
190
+ {
191
+ return true;
192
+ }
193
+ return result;
194
+ }
195
+ }
196
+ if (return_exists)
197
+ {
198
+ return true;
199
+ }
200
+ return result;
201
+ }
202
+
203
+ /**
204
+ * @param {Object} obj Object to fetch attribute from
205
+ * @param {String} path Object path e.g. 'user.name'
206
+ * @param {Object} [options] Options
207
+ * @param {Boolean} [options.unset] Whether to delete the value
208
+ * @param {Mixed} Value to set
209
+ */
210
+ function setNested(obj, path, val, options) {
211
+ options = options || {};
212
+
213
+ var separator = DeepModel.keyPathSeparator;
214
+
215
+ var fields = path.split(separator);
216
+ var result = obj;
217
+ for (var i = 0, n = fields.length; i < n && result !== undefined ; i++) {
218
+ var field = fields[i];
219
+
220
+ //If the last in the path, set the value
221
+ if (i === n - 1) {
222
+ options.unset ? delete result[field] : result[field] = val;
223
+ } else {
224
+ //Create the child object if it doesn't exist, or isn't an object
225
+ if (typeof result[field] === 'undefined' || ! _.isObject(result[field])) {
226
+ result[field] = {};
227
+ }
228
+
229
+ //Move onto the next part of the path
230
+ result = result[field];
231
+ }
232
+ }
233
+ }
234
+
235
+ function deleteNested(obj, path) {
236
+ setNested(obj, path, null, { unset: true });
237
+ }
238
+
239
+ var DeepModel = Backbone.Model.extend({
240
+
241
+ // Override constructor
242
+ // Support having nested defaults by using _.deepExtend instead of _.extend
243
+ constructor: function(attributes, options) {
244
+ var defaults;
245
+ var attrs = attributes || {};
246
+ this.cid = _.uniqueId('c');
247
+ this.attributes = {};
248
+ if (options && options.collection) this.collection = options.collection;
249
+ if (options && options.parse) attrs = this.parse(attrs, options) || {};
250
+ if (defaults = _.result(this, 'defaults')) {
251
+ //<custom code>
252
+ // Replaced the call to _.defaults with _.deepExtend.
253
+ attrs = _.deepExtend({}, defaults, attrs);
254
+ //</custom code>
255
+ }
256
+ this.set(attrs, options);
257
+ this.changed = {};
258
+ this.initialize.apply(this, arguments);
259
+ },
260
+
261
+ // Return a copy of the model's `attributes` object.
262
+ toJSON: function(options) {
263
+ return _.deepClone(this.attributes);
264
+ },
265
+
266
+ // Override get
267
+ // Supports nested attributes via the syntax 'obj.attr' e.g. 'author.user.name'
268
+ get: function(attr) {
269
+ return getNested(this.attributes, attr);
270
+ },
271
+
272
+ // Override set
273
+ // Supports nested attributes via the syntax 'obj.attr' e.g. 'author.user.name'
274
+ set: function(key, val, options) {
275
+ var attr, attrs, unset, changes, silent, changing, prev, current;
276
+ if (key == null) return this;
277
+
278
+ // Handle both `"key", value` and `{key: value}` -style arguments.
279
+ if (typeof key === 'object') {
280
+ attrs = key;
281
+ options = val || {};
282
+ } else {
283
+ (attrs = {})[key] = val;
284
+ }
285
+
286
+ options || (options = {});
287
+
288
+ // Run validation.
289
+ if (!this._validate(attrs, options)) return false;
290
+
291
+ // Extract attributes and options.
292
+ unset = options.unset;
293
+ silent = options.silent;
294
+ changes = [];
295
+ changing = this._changing;
296
+ this._changing = true;
297
+
298
+ if (!changing) {
299
+ this._previousAttributes = _.deepClone(this.attributes); //<custom>: Replaced _.clone with _.deepClone
300
+ this.changed = {};
301
+ }
302
+ current = this.attributes, prev = this._previousAttributes;
303
+
304
+ // Check for changes of `id`.
305
+ if (this.idAttribute in attrs) this.id = attrs[this.idAttribute];
306
+
307
+ //<custom code>
308
+ attrs = objToPaths(attrs);
309
+ //</custom code>
310
+
311
+ // For each `set` attribute, update or delete the current value.
312
+ for (attr in attrs) {
313
+ val = attrs[attr];
314
+
315
+ //<custom code>: Using getNested, setNested and deleteNested
316
+ if (!_.isEqual(getNested(current, attr), val)) changes.push(attr);
317
+ if (!_.isEqual(getNested(prev, attr), val)) {
318
+ setNested(this.changed, attr, val);
319
+ } else {
320
+ deleteNested(this.changed, attr);
321
+ }
322
+ unset ? deleteNested(current, attr) : setNested(current, attr, val);
323
+ //</custom code>
324
+ }
325
+
326
+ // Trigger all relevant attribute changes.
327
+ if (!silent) {
328
+ if (changes.length) this._pending = true;
329
+
330
+ //<custom code>
331
+ var separator = DeepModel.keyPathSeparator;
332
+
333
+ for (var i = 0, l = changes.length; i < l; i++) {
334
+ var key = changes[i];
335
+
336
+ this.trigger('change:' + key, this, getNested(current, key), options);
337
+
338
+ var fields = key.split(separator);
339
+
340
+ //Trigger change events for parent keys with wildcard (*) notation
341
+ for(var n = fields.length - 1; n > 0; n--) {
342
+ var parentKey = _.first(fields, n).join(separator),
343
+ wildcardKey = parentKey + separator + '*';
344
+
345
+ this.trigger('change:' + wildcardKey, this, getNested(current, parentKey), options);
346
+ }
347
+ //</custom code>
348
+ }
349
+ }
350
+
351
+ if (changing) return this;
352
+ if (!silent) {
353
+ while (this._pending) {
354
+ this._pending = false;
355
+ this.trigger('change', this, options);
356
+ }
357
+ }
358
+ this._pending = false;
359
+ this._changing = false;
360
+ return this;
361
+ },
362
+
363
+ // Clear all attributes on the model, firing `"change"` unless you choose
364
+ // to silence it.
365
+ clear: function(options) {
366
+ var attrs = {};
367
+ var shallowAttributes = objToPaths(this.attributes);
368
+ for (var key in shallowAttributes) attrs[key] = void 0;
369
+ return this.set(attrs, _.extend({}, options, {unset: true}));
370
+ },
371
+
372
+ // Determine if the model has changed since the last `"change"` event.
373
+ // If you specify an attribute name, determine if that attribute has changed.
374
+ hasChanged: function(attr) {
375
+ if (attr == null) return !_.isEmpty(this.changed);
376
+ return getNested(this.changed, attr) !== undefined;
377
+ },
378
+
379
+ // Return an object containing all the attributes that have changed, or
380
+ // false if there are no changed attributes. Useful for determining what
381
+ // parts of a view need to be updated and/or what attributes need to be
382
+ // persisted to the server. Unset attributes will be set to undefined.
383
+ // You can also pass an attributes object to diff against the model,
384
+ // determining if there *would be* a change.
385
+ changedAttributes: function(diff) {
386
+ //<custom code>: objToPaths
387
+ if (!diff) return this.hasChanged() ? objToPaths(this.changed) : false;
388
+ //</custom code>
389
+
390
+ var old = this._changing ? this._previousAttributes : this.attributes;
391
+
392
+ //<custom code>
393
+ diff = objToPaths(diff);
394
+ old = objToPaths(old);
395
+ //</custom code>
396
+
397
+ var val, changed = false;
398
+ for (var attr in diff) {
399
+ if (_.isEqual(old[attr], (val = diff[attr]))) continue;
400
+ (changed || (changed = {}))[attr] = val;
401
+ }
402
+ return changed;
403
+ },
404
+
405
+ // Get the previous value of an attribute, recorded at the time the last
406
+ // `"change"` event was fired.
407
+ previous: function(attr) {
408
+ if (attr == null || !this._previousAttributes) return null;
409
+
410
+ //<custom code>
411
+ return getNested(this._previousAttributes, attr);
412
+ //</custom code>
413
+ },
414
+
415
+ // Get all of the attributes of the model at the time of the previous
416
+ // `"change"` event.
417
+ previousAttributes: function() {
418
+ //<custom code>
419
+ return _.deepClone(this._previousAttributes);
420
+ //</custom code>
421
+ }
422
+ });
423
+
424
+
425
+ //Config; override in your app to customise
426
+ DeepModel.keyPathSeparator = '.';
427
+
428
+
429
+ //Exports
430
+ Backbone.DeepModel = DeepModel;
431
+
432
+ //For use in NodeJS
433
+ if (typeof module != 'undefined') module.exports = DeepModel;
434
+
435
+ return Backbone;
436
+
437
+ }));
438
+
@@ -0,0 +1 @@
1
+ //= require js_stack/backbone.deepmodel/backbone.deepmodel-0.10.4
@@ -0,0 +1 @@
1
+ //= require backbone-rails