kojac 0.9.0

Sign up to get free protection for your applications and to get access to all the features.
Files changed (101) hide show
  1. checksums.yaml +15 -0
  2. data/.gitignore +7 -0
  3. data/Gemfile +17 -0
  4. data/Gemfile.lock +158 -0
  5. data/MIT-LICENSE +20 -0
  6. data/README.md +69 -0
  7. data/Rakefile +27 -0
  8. data/app/assets/javascripts/can_extensions.js +45 -0
  9. data/app/assets/javascripts/kojac.js +1230 -0
  10. data/app/assets/javascripts/kojac_canjs.js +191 -0
  11. data/app/assets/javascripts/kojac_ember.js +463 -0
  12. data/app/controllers/kojac_controller.rb +70 -0
  13. data/app/serializers/default_kojac_serializer.rb +10 -0
  14. data/diagram.odg +0 -0
  15. data/kojac.gemspec +36 -0
  16. data/lib/kojac/app_serialize.rb +29 -0
  17. data/lib/kojac/kojac_rails.rb +432 -0
  18. data/lib/kojac/ring_strong_parameters.rb +195 -0
  19. data/lib/kojac/version.rb +3 -0
  20. data/lib/kojac.rb +8 -0
  21. data/lib/tasks/kojac_tasks.rake +4 -0
  22. data/notes.txt +48 -0
  23. data/spec/.DS_Store +0 -0
  24. data/spec/can_cache_spec.js +87 -0
  25. data/spec/can_factory_spec.js +144 -0
  26. data/spec/can_model_spec.js +127 -0
  27. data/spec/demo/README.rdoc +261 -0
  28. data/spec/demo/Rakefile +7 -0
  29. data/spec/demo/app/assets/javascripts/application.js +15 -0
  30. data/spec/demo/app/assets/stylesheets/application.css +13 -0
  31. data/spec/demo/app/controllers/application_controller.rb +3 -0
  32. data/spec/demo/app/helpers/application_helper.rb +2 -0
  33. data/spec/demo/app/mailers/.gitkeep +0 -0
  34. data/spec/demo/app/models/.gitkeep +0 -0
  35. data/spec/demo/app/views/layouts/application.html.erb +14 -0
  36. data/spec/demo/config/application.rb +65 -0
  37. data/spec/demo/config/boot.rb +10 -0
  38. data/spec/demo/config/database.yml +25 -0
  39. data/spec/demo/config/environment.rb +5 -0
  40. data/spec/demo/config/environments/development.rb +37 -0
  41. data/spec/demo/config/environments/production.rb +67 -0
  42. data/spec/demo/config/environments/test.rb +37 -0
  43. data/spec/demo/config/initializers/backtrace_silencers.rb +7 -0
  44. data/spec/demo/config/initializers/inflections.rb +15 -0
  45. data/spec/demo/config/initializers/mime_types.rb +5 -0
  46. data/spec/demo/config/initializers/secret_token.rb +7 -0
  47. data/spec/demo/config/initializers/session_store.rb +8 -0
  48. data/spec/demo/config/initializers/wrap_parameters.rb +14 -0
  49. data/spec/demo/config/locales/en.yml +5 -0
  50. data/spec/demo/config/routes.rb +58 -0
  51. data/spec/demo/config.ru +4 -0
  52. data/spec/demo/lib/assets/.gitkeep +0 -0
  53. data/spec/demo/log/.gitkeep +0 -0
  54. data/spec/demo/public/404.html +26 -0
  55. data/spec/demo/public/422.html +26 -0
  56. data/spec/demo/public/500.html +25 -0
  57. data/spec/demo/public/favicon.ico +0 -0
  58. data/spec/demo/script/rails +6 -0
  59. data/spec/ember_factory_spec.js +157 -0
  60. data/spec/ember_model_spec.js +179 -0
  61. data/spec/external/.DS_Store +0 -0
  62. data/spec/external/ember/.DS_Store +0 -0
  63. data/spec/external/ember/ember-1.0.0-rc.6.js +30970 -0
  64. data/spec/external/ember/handlebars-1.0.0-rc.4.js +2239 -0
  65. data/spec/external/jasmine/MIT.LICENSE +20 -0
  66. data/spec/external/jasmine/jasmine-html.js +616 -0
  67. data/spec/external/jasmine/jasmine.css +81 -0
  68. data/spec/external/jasmine/jasmine.js +2529 -0
  69. data/spec/external/jasmine.async.js +51 -0
  70. data/spec/external/jquery/jquery-1.7.2.js +9404 -0
  71. data/spec/external/jquery/jquery-1.7.2.min.js +4 -0
  72. data/spec/external/jquery/jquery-1.9.1.js +9597 -0
  73. data/spec/external/json2.js +480 -0
  74. data/spec/external/steal/steal-121115.js +2747 -0
  75. data/spec/external/steal/steal-3.2.3.js +2098 -0
  76. data/spec/external/steal/steal-3.2.3.min.js +27 -0
  77. data/spec/external/steal/steal.js +2466 -0
  78. data/spec/external/steal/steal.min.js +32 -0
  79. data/spec/external/steal/stealconfig.js +19 -0
  80. data/spec/external/underscore.js +1223 -0
  81. data/spec/external/underscore_plus.js +261 -0
  82. data/spec/external.zip +0 -0
  83. data/spec/handler_stack_spec.js +143 -0
  84. data/spec/helpers/SpecHelper.js +10 -0
  85. data/spec/kojac_caching_spec.js +105 -0
  86. data/spec/kojac_mock_spec.js +230 -0
  87. data/spec/kojac_model_spec.js +126 -0
  88. data/spec/kojac_object_spec.js +171 -0
  89. data/spec/kojac_operations_spec.js +41 -0
  90. data/spec/mockjson/order_item.js +37 -0
  91. data/spec/mockjson/order_item__49.js +15 -0
  92. data/spec/mockjson/order_item__50.js +15 -0
  93. data/spec/mockjson/order_item__51.js +15 -0
  94. data/spec/mockjson/product.js +82 -0
  95. data/spec/mockjson/product__3.js +22 -0
  96. data/spec/model_ring_spec.rb +52 -0
  97. data/spec/operation_include_spec.js +77 -0
  98. data/spec/run.html +81 -0
  99. data/spec/spec.js +2 -0
  100. data/spec/support/jasmine.yml +86 -0
  101. metadata +380 -0
@@ -0,0 +1,1230 @@
1
+ /*--------------------------------------------------------------------------
2
+ *
3
+ * Key Oriented JSON Application Cache (KOJAC)
4
+ * (c) 2011-12 Buzzware Solutions
5
+ * https://github.com/buzzware/KOJAC
6
+ *
7
+ * KOJAC is freely distributable under the terms of an MIT-style license.
8
+ *
9
+ *--------------------------------------------------------------------------*/
10
+
11
+ Kojac = {};
12
+
13
+ /**
14
+ * @class Kojac.Object
15
+ *
16
+ * Based on :
17
+ * Simple JavaScript Inheritance
18
+ * By John Resig http://ejohn.org/blog/simple-javascript-inheritance/
19
+ * MIT Licensed.
20
+ *
21
+ * Inspired by base2 and Prototype
22
+ *
23
+ * added setup method support inspired by CanJs as used in Kojac.Model
24
+ *
25
+ */
26
+ (function(){
27
+ var initializing = false, fnTest = /xyz/.test(function(){xyz;}) ? /\b_super\b/ : /.*/;
28
+ // The base JrClass implementation (does nothing)
29
+ this.JrClass = function(aProperties){
30
+ if (initializing) { // making prototype
31
+ if (aProperties) {
32
+ _.extend(this,aProperties);
33
+ _.cloneComplexValues(this);
34
+ }
35
+ } else { // making instance
36
+ _.cloneComplexValues(this);
37
+ if (this.init)
38
+ this.init.call(this,aProperties);
39
+ }
40
+ };
41
+ this.JrClass.prototype.init = function(aProperties) {
42
+ _.extend(this,aProperties);
43
+ };
44
+ this.JrClass.prototype.toJSON = function() { // this adds an instance method used by JSON2 that returns an object containing all immediate and background properties (ie from the prototype)
45
+ return _.clone(this);
46
+ };
47
+
48
+ // Create a new JrClass that inherits from this class
49
+ this.JrClass.extend = function(prop) {
50
+ var _super = this.prototype;
51
+
52
+ // Instantiate a base class (but only create the instance,
53
+ // don't run the init constructor)
54
+ initializing = true;
55
+ var prototype = new this();
56
+ initializing = false;
57
+
58
+ // The dummy class constructor
59
+ function JrClass(aProperties) {
60
+ if (initializing) { // making prototype
61
+ if (aProperties) {
62
+ _.extend(this,aProperties);
63
+ _.cloneComplexValues(this);
64
+ }
65
+ } else { // making instance
66
+ _.cloneComplexValues(this);
67
+ if (this.init)
68
+ this.init.call(this,aProperties);
69
+ }
70
+ }
71
+
72
+ JrClass._superClass = this;
73
+
74
+ if (_super.setup)
75
+ prop = _super.setup.call(JrClass,prop);
76
+
77
+ // Copy the properties over onto the new prototype
78
+ for (var name in prop) {
79
+ // Check if we're overwriting an existing function
80
+ var t = typeof prop[name];//_.typeOf(prop[name]);
81
+ if (t == "function" && typeof _super[name] == "function" && fnTest.test(prop[name])) {
82
+ prototype[name] =
83
+ (function(name, fn){
84
+ return function() {
85
+ var tmp = this._super;
86
+
87
+ // Add a new ._super() method that is the same method
88
+ // but on the super-class
89
+ this._super = _super[name];
90
+
91
+ // The method only need to be bound temporarily, so we
92
+ // remove it when we're done executing
93
+ var ret = fn.apply(this, arguments);
94
+ this._super = tmp;
95
+
96
+ return ret;
97
+ };
98
+ })(name, prop[name]);
99
+ } else if (t==='array' || t==='object') {
100
+ prototype[name] = _.clone(prop[name]);
101
+ } else {
102
+ prototype[name] = prop[name];
103
+ }
104
+ }
105
+
106
+ // Populate our constructed prototype object
107
+ JrClass.prototype = prototype;
108
+
109
+ // Enforce the constructor to be what we expect
110
+ JrClass.prototype.constructor = JrClass;
111
+
112
+ // And make this class extendable
113
+ JrClass.extend = arguments.callee;
114
+
115
+ return JrClass;
116
+ };
117
+ Kojac.Object = this.JrClass;
118
+ })();
119
+
120
+
121
+ /*
122
+ * @class Kojac.Utils
123
+ *
124
+ * Provides static functions used by Kojac
125
+ */
126
+ Kojac.Utils = {
127
+
128
+ /**
129
+ * Converts one or more keys, given in multiple possible ways, to a standard array of strings
130
+ * @param aKeys one or more keys eg. as array of strings, or single comma-separated list in a single string
131
+ * @return {Array} array of single-key strings
132
+ */
133
+ interpretKeys: function(aKeys) {
134
+ if (_.isArray(aKeys))
135
+ return aKeys;
136
+ if (_.isString(aKeys))
137
+ return aKeys.split(',');
138
+ return [];
139
+ },
140
+
141
+ /**
142
+ * Convert object or array to [key1, value, key2, value]
143
+ * @param aKeyValues array or object of keys with values
144
+ * @return {Array} [key1, value, key2, value]
145
+ */
146
+ toKeyValueArray: function(aKeyValues) {
147
+ if (_.isArray(aKeyValues)) {
148
+ var first = aKeyValues[0];
149
+ if (_.isArray(first)) // this style : [[key,value],[key,value]]
150
+ return _.map(aKeyValues,function(o){ return _.flatten(o,true) });
151
+ else if (_.isObject(first)) { // this style : [{key: value},{key: value}]
152
+ var result = [];
153
+ for (var i=0; i<aKeyValues.length; i++)
154
+ result.push(_.pairs(aKeyValues[i]));
155
+ return _.flatten(result);
156
+ } else
157
+ return aKeyValues; // assume already [key1, value, key2, value]
158
+ } else if (_.isObject(aKeyValues)) {
159
+ return _.flatten(_.pairs(aKeyValues),true); // this style : {key1: value, key2: value}
160
+ } else
161
+ return null; // unrecognised input
162
+ }
163
+
164
+ //public static function getTrailingId(aKey: String): int {
165
+ // if (!aKey)
166
+ // return 0;
167
+ // var parts: Array = aKey.split('__')
168
+ // if (!parts.length)
169
+ // return 0;
170
+ // return StringUtils.toInt(parts[parts.length-1])
171
+ //}
172
+ };
173
+
174
+ /*
175
+ * Function used to determine the data type class of the given value
176
+ * @param {*} aValue
177
+ * @return {Class} eg. see Kojac.FieldTypes
178
+ */
179
+ Kojac.getPropertyValueType = function(aValue) {
180
+ var t = _.typeOf(aValue);
181
+ var result;
182
+ switch(t) {
183
+ case 'number': // determine number or int
184
+ result = (Math.floor(aValue) === aValue) ? Int : Number;
185
+ break;
186
+ default:
187
+ case 'undefined':
188
+ case 'null':
189
+ result = Null;
190
+ break;
191
+ case 'string':
192
+ result = String;
193
+ break;
194
+ case 'boolean':
195
+ result = Boolean;
196
+ break;
197
+ case 'array':
198
+ result = Array;
199
+ break;
200
+ case 'object':
201
+ result = Object;
202
+ break;
203
+ case 'function':
204
+ case 'class':
205
+ case 'instance':
206
+ case 'error':
207
+ result = null;
208
+ break;
209
+ }
210
+ return result;
211
+ };
212
+
213
+
214
+ /*
215
+ * Function used to interpret aValue as the given aDestType which is one of the supported data type classes
216
+ * @param {*} aValue any value
217
+ * @param {Class} aDestType Class used to interpret aValue
218
+ * @return {*} aValue interpreted as destination type
219
+ */
220
+ Kojac.interpretValueAsType = function(aValue, aDestType) {
221
+ var sourceType = Kojac.getPropertyValueType(aValue);
222
+ if (aDestType===sourceType)
223
+ return aValue;
224
+ switch (aDestType) {
225
+ case Null:
226
+ return aValue;
227
+ break;
228
+ case String:
229
+
230
+ switch(sourceType) {
231
+ case Int:
232
+ case Number:
233
+ case Boolean:
234
+ return aValue.toString();
235
+ break;
236
+ default:
237
+ case Null:
238
+ return null;
239
+ break;
240
+ }
241
+
242
+ break;
243
+ case Boolean:
244
+
245
+ switch(sourceType) {
246
+ case Null:
247
+ default:
248
+ return null;
249
+ break;
250
+ case Int:
251
+ case Number:
252
+ if (isNaN(aValue))
253
+ return null;
254
+ else
255
+ return !!aValue;
256
+ break;
257
+ }
258
+
259
+ break;
260
+
261
+ case Number:
262
+
263
+ switch(sourceType) {
264
+ case Null:
265
+ default:
266
+ return null;
267
+ break;
268
+ case Boolean:
269
+ return aValue ? 1 : 0;
270
+ break;
271
+ case Int:
272
+ return aValue;
273
+ break;
274
+ case String:
275
+ return Number(aValue);
276
+ break;
277
+ }
278
+ break;
279
+
280
+ case Int:
281
+
282
+ switch(sourceType) {
283
+ case Null:
284
+ default:
285
+ return null;
286
+ break;
287
+ case Boolean:
288
+ return aValue ? 1 : 0;
289
+ break;
290
+ case Number:
291
+ if (isNaN(aValue))
292
+ return null;
293
+ else
294
+ return Math.round(aValue);
295
+ break;
296
+ case String:
297
+ return Math.round(Number(aValue));
298
+ break;
299
+ }
300
+
301
+ break;
302
+ case Object:
303
+ return null;
304
+ break;
305
+ case Array:
306
+ return null;
307
+ break;
308
+ }
309
+ return null;
310
+ };
311
+
312
+ /*
313
+ * Function used to read values from a given source object into the given destination object, using the given aDefinition
314
+ * @param {Object} aDestination
315
+ * @param {Object} aSource
316
+ * @param {Object} aDefinition
317
+ * @return {Object} aDestination object
318
+ */
319
+ Kojac.readTypedProperties = function(aDestination, aSource, aDefinition) {
320
+ for (p in aSource) {
321
+ if (p in aDefinition) {
322
+ var value = aSource[p];
323
+ var destType = aDefinition[p];
324
+ if (destType===undefined)
325
+ throw Error('no definition for '+p);
326
+ aDestination[p] = Kojac.interpretValueAsType(value,destType);
327
+ } else if (aDefinition.__options===undefined || aDefinition.__options.allowDynamic===undefined || aDefinition.__options.allowDynamic==true) {
328
+ aDestination[p] = aSource[p];
329
+ }
330
+ };
331
+ return aDestination;
332
+ };
333
+
334
+ /*
335
+ * Returns an array of objects from the cache, based on a prefix and an array of ids
336
+ * @param {String} aPrefix
337
+ * @param {Array} aIds
338
+ * @param {Object} aCache
339
+ * @return {Array} of values from cache
340
+ */
341
+ Kojac.collectIds = function(aPrefix,aIds,aCache,aFilterFn) {
342
+ var result = [];
343
+ var item;
344
+ for (var i=0;i<aIds.length;i++) {
345
+ item = aCache[aPrefix+'__'+aIds[i]];
346
+ if (!aFilterFn || aFilterFn(item))
347
+ result.push(item);
348
+ }
349
+ return result;
350
+ };
351
+
352
+
353
+ /*
354
+ * Global static function that combines a given array of values (any number of arguments) into a cache key string, joined by double-underscores
355
+ * @return {String} cache key
356
+ */
357
+ keyJoin = function() {
358
+ var result = null;
359
+ for (var i=0;i<arguments.length;i++) {
360
+ var v = arguments[i];
361
+ if (!v)
362
+ return null;
363
+ if (!result)
364
+ result = v.toString();
365
+ else
366
+ result += '__' + v.toString();
367
+ }
368
+ return result;
369
+ }
370
+
371
+ keySplit = function(aKey) {
372
+ var r,ia,id,a;
373
+ var parts = aKey.split('__');
374
+ if (parts.length>=1) // resource
375
+ r = parts[0];
376
+ else
377
+ return [];
378
+ var result = [r];
379
+ if (parts.length<2)
380
+ return result;
381
+ ia = parts[1];
382
+ parts = ia.split('.');
383
+ if (parts.length>=1) { // id
384
+ id = parts[0];
385
+ var id_as_i = Number(id);
386
+ if (_.isFinite(id_as_i))
387
+ id = id_as_i;
388
+ result.push(id);
389
+ }
390
+ if (parts.length>=2) { // association
391
+ result.push(parts[1]);
392
+ }
393
+ return result;
394
+ }
395
+
396
+ keyResource = function(aKey) {
397
+ var parts = aKey.split('__');
398
+ return parts[0];
399
+ }
400
+
401
+ keyId = function(aKey) {
402
+ var parts = aKey.split('__');
403
+ return parts[1];
404
+ }
405
+
406
+ Int = {name: 'Int', toString: function() {return 'Int';}}; // represents a virtual integer type
407
+ Null = {name: 'Null', toString: function() {return 'Null';}}; // represents a virtual Null type
408
+ Kojac.FieldTypes = [Null,Int,Number,String,Boolean,Date,Array,Object]; // all possible types for fields in Kojac.Model
409
+ Kojac.FieldTypeStrings = ['Null','Int','Number','String','Boolean','Date','Array','Object']; // String names for FieldTypes
410
+ Kojac.SimpleTypes = [Null,Int,Number,String,Boolean,Date]; // simple field types in Kojac.Model ie. Object and Array are considered complex
411
+
412
+ /**
413
+ * Extends Kojac.Object to support typed attributes
414
+ * @class Kojac.Model
415
+ * @extends Kojac.Object
416
+ **/
417
+ Kojac.Model = Kojac.Object.extend({
418
+ /**
419
+ * This method is called when inheriting a new model from Kojac.Model, and allows attributes to be defined as
420
+ * name: Class (default value is null)
421
+ * or
422
+ * name: default value (class is inferred)
423
+ * or
424
+ * name: [Class,default value]
425
+ * @param prop Hash of attributes defined as above
426
+ * @return Hash of attributes in expected name:value format
427
+ */
428
+ setup: function(prop) {
429
+ this.__attributes = (this._superClass && this._superClass.__attributes && _.clone(this._superClass.__attributes)) || {};
430
+ //this.__defaults = (constructor.__defaults && _.clone(constructor.__defaults)) || {};
431
+ for (var p in prop) {
432
+ if (['__defaults','__attributes'].indexOf(p)>=0)
433
+ continue;
434
+ var propValue = prop[p];
435
+ if (_.isArray(propValue) && propValue.length===2 && Kojac.FieldTypes.indexOf(propValue[0])>=0) { // in form property: [Type, Default Value]
436
+ this.__attributes[p] = propValue[0];
437
+ prop[p] = propValue[1];
438
+ } else if (Kojac.FieldTypes.indexOf(propValue) >= 0) { // field type
439
+ prop[p] = null;
440
+ this.__attributes[p] = propValue;
441
+ //this.__defaults[p] = null;
442
+ } else if (_.isFunction(propValue)) {
443
+ continue;
444
+ } else { // default value
445
+ var i = Kojac.FieldTypes.indexOf(Kojac.getPropertyValueType(propValue));
446
+ if (i >= 0) {
447
+ this.__attributes[p] = Kojac.FieldTypes[i];
448
+ } else {
449
+ this.__attributes[p] = null;
450
+ }
451
+ //this.__defaults[p] = v;
452
+ }
453
+ }
454
+ return prop;
455
+ },
456
+ /**
457
+ * The base constructor for Kojac.Model. When creating an instance of a model, an optional hash aValues provides attribute values that override the default values
458
+ * @param aValues
459
+ * @constructor
460
+ */
461
+ init: function(aValues){
462
+ // we don't use base init here
463
+ if (!aValues)
464
+ return;
465
+ for (var p in aValues) {
466
+ if (this.isAttribute(p)) {
467
+ this.attr(p,aValues[p]);
468
+ } else {
469
+ this[p] = aValues[p];
470
+ }
471
+ }
472
+ },
473
+
474
+ /**
475
+ * Determines whether the given name is defined as an attribute in the model definition. Attributes are properties with an additional class and default value
476
+ * @param aName
477
+ * @return {Boolean}
478
+ */
479
+ isAttribute: function(aName) {
480
+ return this.constructor.__attributes && (aName in this.constructor.__attributes);
481
+ },
482
+
483
+ /**
484
+ * Used various ways to access the attributes of a model instance.
485
+ * 1. attr() returns an object of all attributes and their values
486
+ * 2. attr(<name>) returns the value of a given attribute
487
+ * 3. attr(<name>,<value>) sets an attribute to the given value after converting it to the attribute's class
488
+ * 4. attr({Object}) sets each of the given attributes to the given value after converting to the attribute's class
489
+ * @param aName
490
+ * @param aValue
491
+ * @return {*}
492
+ */
493
+ attr: function(aName,aValue) {
494
+ if (aName===undefined) { // read all attributes
495
+ return _.pick(this, _.keys(this.constructor.__attributes));
496
+ } else if (aValue===undefined) {
497
+ if (_.isObject(aName)) { // write all given attributes
498
+ aValue = aName;
499
+ aName = undefined;
500
+ if (!this.constructor.__attributes)
501
+ return {};
502
+ _.extend(this,_.pick(aValue,_.keys(this.constructor.__attributes)))
503
+ } else { // read single attribute
504
+ return (_.has(this.constructor.__attributes,aName) && this[aName]) || undefined;
505
+ }
506
+ } else { // write single attribute
507
+ var t = this.constructor.__attributes[aName];
508
+ if (t)
509
+ aValue = Kojac.interpretValueAsType(aValue,t);
510
+ return (this[aName]=aValue);
511
+ }
512
+ }
513
+ });
514
+
515
+ /**
516
+ * Provides a dynamic asynchronous execution model. Handlers are added in queue or stack style, then executed in order, passing a given context object to each handler.
517
+ * HandlerStack is a Javascript conversion of HandlerStack in the ActionScript Kojac library.
518
+ * @class HandlerStack
519
+ * @extends Kojac.Object
520
+ */
521
+ HandlerStack = Kojac.Object.extend({
522
+ handlers: null,
523
+ parameters: null,
524
+ thises: null,
525
+ parameter: null,
526
+ context: null,
527
+ error: null,
528
+ deferred: null,
529
+ nextHandlerIndex: -1,
530
+ waitForCallNext: false,
531
+
532
+ /**
533
+ * @constructor
534
+ */
535
+ init: function() {
536
+ this._super.apply(this,arguments);
537
+ this.clear();
538
+ },
539
+
540
+ // clears out all handlers and state
541
+ clear: function() {
542
+ this.handlers = [];
543
+ this.parameters = [];
544
+ this.thises = [];
545
+ this.reset();
546
+ },
547
+
548
+ // clears execution state but keeps handlers and parameters for a potential re-call()
549
+ reset: function() {
550
+ this.parameter = null;
551
+ this.context = null;
552
+ this.error = null;
553
+ this.deferred = null;
554
+ this.nextHandlerIndex = -1;
555
+ this.waitForCallNext = false;
556
+ },
557
+
558
+ push: function (aFunction, aParameter, aThis) {
559
+ this.handlers.unshift(aFunction);
560
+ this.parameters.unshift(aParameter);
561
+ this.thises.unshift(aThis);
562
+ },
563
+
564
+ // push in function and parameters to execute next
565
+ pushNext: function(aFunction, aParameter,aThis) {
566
+ if (this.nextHandlerIndex<0)
567
+ return this.push(aFunction,aParameter,aThis);
568
+ this.handlers.splice(this.nextHandlerIndex,0,aFunction);
569
+ this.parameters.splice(this.nextHandlerIndex,0,aParameter);
570
+ this.thises.splice(this.nextHandlerIndex,0,aThis);
571
+ },
572
+
573
+ add: function(aFunction, aParameter, aThis) {
574
+ this.handlers.push(aFunction);
575
+ this.parameters.push(aParameter);
576
+ this.thises.push(aThis);
577
+ },
578
+
579
+ callNext: function() {
580
+ if (this.context.error) {
581
+ if (!this.context.isRejected())
582
+ this.deferred.reject(this.context);
583
+ return;
584
+ }
585
+ if ((this.handlers.length===0) || (this.nextHandlerIndex>=this.handlers.length)) {
586
+ this.deferred.resolve(this.context);
587
+ return;
588
+ }
589
+ var fn = this.handlers[this.nextHandlerIndex];
590
+ var d = this.parameters[this.nextHandlerIndex];
591
+ var th = this.thises[this.nextHandlerIndex];
592
+ this.nextHandlerIndex++;
593
+ var me = this;
594
+ setTimeout(function() {
595
+ me.executeHandler(fn, d, th);
596
+ }, 0);
597
+ },
598
+
599
+ handleError: function(aError) {
600
+ this.context.error = aError;
601
+ this.deferred.reject(this.context);
602
+ },
603
+
604
+ executeHandler: function(fn,d,th) {
605
+ this.waitForCallNext = false;
606
+ try {
607
+ this.parameter = d;
608
+ if (th)
609
+ fn.call(th,this.context);
610
+ else
611
+ fn(this.context);
612
+ } catch (e) {
613
+ this.handleError(e);
614
+ }
615
+ if (!(this.waitForCallNext)) {
616
+ this.callNext();
617
+ }
618
+ },
619
+
620
+ run: function(aContext) {
621
+ this.context = aContext;
622
+ this.deferred = jQuery.Deferred();
623
+ this.deferred.promise(this.context);
624
+ if (this.context.isResolved===undefined)
625
+ this.context.isResolved = _.bind(
626
+ function() {
627
+ return this.state()==='resolved'
628
+ },
629
+ this.context
630
+ );
631
+ if (this.context.isRejected===undefined)
632
+ this.context.isRejected = _.bind(
633
+ function() {
634
+ return this.state()==='rejected'
635
+ },
636
+ this.context
637
+ );
638
+ if (this.context.isPending===undefined)
639
+ this.context.isPending = _.bind(
640
+ function() {
641
+ return this.state()==='pending'
642
+ },
643
+ this.context
644
+ );
645
+ this.nextHandlerIndex = 0;
646
+ this.callNext();
647
+ return this.context;
648
+ }
649
+ });
650
+
651
+
652
+ /**
653
+ * Represents a single Kojac operation ie. READ, WRITE, UPDATE, DELETE or EXECUTE
654
+ * @class Kojac.Operation
655
+ * @extends Kojac.Object
656
+ */
657
+ Kojac.Operation = Kojac.Object.extend({
658
+ request: this,
659
+ verb: null,
660
+ key: null,
661
+ value: undefined,
662
+ results: {},
663
+ result_key: null,
664
+ result: undefined,
665
+ error: null, // set with some truthy error if this operation fails
666
+ performed: false,
667
+ fromCache: null, // null means not performed, true means got from cache, false means got from server. !!! Should split this into performed and fromCache
668
+ receiveResult:function (aResponseOp) {
669
+ if (!aResponseOp) {
670
+ this.error = "no result";
671
+ } else if (aResponseOp.error) {
672
+ this.error = aResponseOp.error;
673
+ } else {
674
+ var request_key = this.result_key || this.key;
675
+ var response_key = aResponseOp.result_key || this.key;
676
+ var final_result_key = this.result_key || response_key; // result_key should not be specified unless trying to override
677
+ var results = _.isObjectStrict(aResponseOp.results) ? aResponseOp.results : _.createObject(response_key,aResponseOp.results); // fix up server mistake
678
+ var result;
679
+ if (aResponseOp.verb==='DESTROY')
680
+ result = undefined;
681
+ else
682
+ result = results[response_key];
683
+
684
+ results = _.omit(results,response_key); // results now excludes primary result
685
+ _.extend(this.results,results); // store other results
686
+ this.result_key = final_result_key;
687
+ this.results[final_result_key] = result; // store primary result
688
+ }
689
+ }
690
+ });
691
+
692
+ /**
693
+ * Represents a single Kojac request, analogous to a HTTP request. It may contain 1 or more operations
694
+ * @class Kojac.Request
695
+ * @extends Kojac.Object
696
+ */
697
+ Kojac.Request = Kojac.Object.extend({
698
+ kojac: null,
699
+ options: {},
700
+ ops: [],
701
+ handlers: null,
702
+ op: null,
703
+ result: undefined,
704
+ results: {},
705
+ error: null, // set with some truthy value if this whole request or any operation fails (will contain first error if multiple)
706
+ newOperation: function() {
707
+ var obj = new Kojac.Operation({request: this});
708
+ if (this.ops.length===0)
709
+ this.op = obj;
710
+ this.ops.push(obj);
711
+ return obj;
712
+ },
713
+
714
+ init: function(aProperties) {
715
+ this._super.apply(this,arguments);
716
+ this.handlers = new HandlerStack();
717
+ },
718
+
719
+ // {key: value} or [{key1: value},{key2: value}] or {key1: value, key2: value}
720
+ // Can give existing keys with id, and will create a clone in database with a new id
721
+ create: function(aKeyValues,aOptions) {
722
+
723
+ var result_key = aOptions && _.removeKey(aOptions,'result_key');
724
+ var params = aOptions && _.removeKey(aOptions,'params'); // extract specific params
725
+ var options = aOptions ? _.extend({cacheResults: true},aOptions) : {}; // extract known options
726
+
727
+ var kvArray = Kojac.Utils.toKeyValueArray(aKeyValues);
728
+ for (var i=0;i<kvArray.length-1;i+=2) {
729
+ var k = kvArray[i];
730
+ var v = kvArray[i+1];
731
+ var op = this.newOperation();
732
+ op.verb = 'CREATE';
733
+ op.options = _.clone(options);
734
+ op.params = params && _.clone(params);
735
+ var parts = keySplit(k);
736
+ if (parts.length >= 3)
737
+ op.key = k;
738
+ else
739
+ op.key = keyResource(k);
740
+ if ((i===0) && result_key)
741
+ op.result_key = result_key;
742
+ op.value = v;
743
+ }
744
+ return this;
745
+ },
746
+
747
+ // !!! if aKeys is String, split on ',' into an array
748
+ // known options will be moved from aOptions to op.options; remaining keys will be put into params
749
+ read: function(aKeys,aOptions) {
750
+ var keys = Kojac.Utils.interpretKeys(aKeys);
751
+ var result_key = aOptions && _.removeKey(aOptions,'result_key'); // extract result_key
752
+ var params = aOptions && _.removeKey(aOptions,'params'); // extract specific params
753
+ var options = aOptions ? _.extend({cacheResults: true},aOptions) : {}; // extract known options
754
+ var me = this;
755
+ jQuery.each(keys,function(i,k) {
756
+ var op = me.newOperation();
757
+ op.options = _.clone(options);
758
+ op.params = params && _.clone(params);
759
+ op.verb = 'READ';
760
+ op.key = k;
761
+ if (i===0)
762
+ op.result_key = result_key || k;
763
+ else
764
+ op.result_key = k;
765
+ });
766
+ return this;
767
+ },
768
+
769
+ cacheRead: function(aKeys,aOptions) {
770
+ aOptions = _.extend({},aOptions,{preferCache: true});
771
+ return this.read(aKeys,aOptions);
772
+ },
773
+
774
+ update: function(aKeyValues,aOptions) {
775
+ var result_key = aOptions && _.removeKey(aOptions,'result_key');
776
+ var options = aOptions ? _.extend({cacheResults: true},aOptions) : {}; // extract known options
777
+ var params = aOptions && _.removeKey(aOptions,'params'); // extract specific params
778
+ var first=true;
779
+ var kvArray = Kojac.Utils.toKeyValueArray(aKeyValues);
780
+ for (var i=0;i<kvArray.length-1;i+=2) {
781
+ var k = kvArray[i];
782
+ var v = kvArray[i+1];
783
+ var op = this.newOperation();
784
+ op.verb = 'UPDATE';
785
+ op.options = _.clone(options);
786
+ op.params = params && _.clone(params);
787
+ op.key = k;
788
+ if (first) {
789
+ op.result_key = result_key || k;
790
+ first = false;
791
+ } else
792
+ op.result_key = k;
793
+ op.value = v;
794
+ };
795
+ return this;
796
+ },
797
+
798
+ destroy: function(aKeys,aOptions) {
799
+ var keys = Kojac.Utils.interpretKeys(aKeys);
800
+ var result_key = aOptions && _.removeKey(aOptions,'result_key');
801
+ var options = aOptions ? _.extend({cacheResults: true},aOptions) : {}; // extract known options
802
+ var params = aOptions && _.removeKey(aOptions,'params'); // extract specific params
803
+ var me = this;
804
+ jQuery.each(keys,function(i,k) {
805
+ var op = me.newOperation();
806
+ op.options = _.clone(options);
807
+ op.params = params && _.clone(params);
808
+ op.verb = 'DESTROY';
809
+ op.key = k;
810
+ if (i===0)
811
+ op.result_key = result_key || k;
812
+ else
813
+ op.result_key = k;
814
+ });
815
+ return this;
816
+ },
817
+
818
+ execute: function(aKey,aValue,aOptions) {
819
+ var op = this.newOperation();
820
+ op.verb = 'EXECUTE';
821
+
822
+ var params = aOptions && _.removeKey(aOptions,'params'); // extract specific params
823
+ op.result_key = aOptions && _.removeKey(aOptions,'result_key') || aKey;
824
+ op.options = aOptions ? _.extend({cacheResults: false},aOptions) : {}; // extract known options
825
+ op.params = params && _.clone(params);
826
+ op.key = aKey;
827
+ op.value = aValue;
828
+ return this;
829
+ },
830
+
831
+ request: function() {
832
+ return this.kojac.performRequest(this);
833
+ }
834
+ });
835
+
836
+ /**
837
+ * The Kojac core object
838
+ * @class Kojac.Core
839
+ * @extends Kojac.Object
840
+ */
841
+ Kojac.Core = Kojac.Object.extend({
842
+
843
+ remoteProvider: null,
844
+ objectFactory: null,
845
+ cache: null,
846
+ dependentKeys: {},
847
+
848
+ newRequest: function() {
849
+ return new Kojac.Request({kojac: this});
850
+ },
851
+
852
+ cacheResults: function(aRequest) {
853
+ if (this.cache.beginPropertyChanges)
854
+ this.cache.beginPropertyChanges();
855
+ for (var i=0;i<aRequest.ops.length;i++) {
856
+ var op = aRequest.ops[i];
857
+ if (op.error)
858
+ break;
859
+ if (op.options.cacheResults===false)
860
+ continue;
861
+ for (p in op.results) {
862
+ if (p==op.result_key)
863
+ continue;
864
+ if (op.results[p]===undefined)
865
+ delete this.cache[p];
866
+ else
867
+ this.cache[p] = op.results[p];
868
+ }
869
+ if (op.results[op.result_key]===undefined)
870
+ delete this.cache[op.result_key];
871
+ else
872
+ this.cache[op.result_key] = op.results[op.result_key];
873
+ }
874
+ if (this.cache.endPropertyChanges)
875
+ this.cache.endPropertyChanges();
876
+ },
877
+
878
+ cacheHasKeys: function(aKeysArray) {
879
+ var me = this;
880
+ return _.all(aKeysArray,function(k){
881
+ return k in me.cache;
882
+ })
883
+ },
884
+
885
+ cacheValues: function(aKeysArray) {
886
+ var me = this;
887
+ return _.map(aKeysArray,function(k){
888
+ return me.cache[k];
889
+ })
890
+ },
891
+
892
+ finaliseRequest: function(aRequest) {
893
+ // set convenience properties
894
+ var results = {};
895
+ for (var i=0;i<aRequest.ops.length;i++) {
896
+ var op = aRequest.ops[i];
897
+ if (op.error && !aRequest.error)
898
+ aRequest.error = op.error;
899
+ _.extend(results,op.results);
900
+ op.result = !op.error && op.results && (op.result_key || op.key) ? op.results[op.result_key || op.key] : null;
901
+ if (i===0) {
902
+ aRequest.op = op;
903
+ }
904
+ if ((op.performed===true) && (op.fromCache===false) && (op.options.cacheResults!==false)) {
905
+ var ex_key = (op.result_key || op.key);
906
+ var dep_keys = [];
907
+ for (var p in op.results) {
908
+ if (p===ex_key)
909
+ continue;
910
+ dep_keys.push(p);
911
+ }
912
+ if (!dep_keys.length) {
913
+ if (op.key in aRequest.kojac.dependentKeys)
914
+ delete aRequest.kojac.dependentKeys[op.key];
915
+ } else {
916
+ aRequest.kojac.dependentKeys[op.key] = dep_keys
917
+ }
918
+ }
919
+ }
920
+ aRequest.results = results;
921
+ aRequest.result = aRequest.op && aRequest.op.result;
922
+ },
923
+
924
+ performRequest: function(aRequest) {
925
+ for (var i=0;i<aRequest.ops.length;i++) {
926
+ var op = aRequest.ops[i];
927
+ var k = (op.result_key && (op.result_key !== op.key)) ? op.result_key : op.key;
928
+ if (op.verb=='READ' && op.options.preferCache && (k in aRequest.kojac.cache)) { // resolve from cache
929
+ op.results[k] = aRequest.kojac.cache[k];
930
+ var dep_keys = aRequest.kojac.dependentKeys[op.key];
931
+ if (dep_keys) {
932
+ for (var i=0;i<dep_keys.length;i++) {
933
+ var dk = dep_keys[i];
934
+ // what if not in cache? perhaps dump siblings in dependentKeys and index key to cause full refresh? or refuse to remove from cache if in dependentKeys
935
+ op.results[dk] = aRequest.kojac.cache[dk];
936
+ }
937
+ }
938
+ op.result_key = k;
939
+ op.fromCache = true;
940
+ op.performed = true;
941
+ }
942
+ }
943
+ aRequest.handlers.add(this.remoteProvider.handleAjaxRequest,null,this.remoteProvider);
944
+ if (this.objectFactory && this.objectFactory.transformResultsToValueObjects)
945
+ aRequest.handlers.add(this.objectFactory.transformResultsToValueObjects,null,this.objectFactory);
946
+ if (this.cache.cacheResults)
947
+ aRequest.handlers.add(this.cache.cacheResults,null,this.cache);
948
+ else
949
+ aRequest.handlers.add(this.cacheResults,null,this);
950
+ aRequest.handlers.run(aRequest).then(this.finaliseRequest);
951
+ return aRequest;
952
+ },
953
+
954
+ // BEGIN User Functions
955
+
956
+ // These functions enable the user to build and trigger requests to the server/remote provider
957
+ // eg. kojac.read('cur_super').read('cur_super_products').request()
958
+ // or kojac.readRequest('cur_super','cur_super_products')
959
+
960
+ create: function(aKeyValues,aOptions) {
961
+ var req = this.newRequest();
962
+ return req.create(aKeyValues,aOptions);
963
+ },
964
+ createRequest: function(aKeyValues,aOptions) {
965
+ return this.create(aKeyValues,aOptions).request();
966
+ },
967
+
968
+ read: function(aKeys,aOptions) {
969
+ var req = this.newRequest();
970
+ return req.read(aKeys,aOptions);
971
+ },
972
+ readRequest: function(aKeys,aOptions) {
973
+ return this.read(aKeys,aOptions).request();
974
+ },
975
+ cacheReadRequest: function(aKeys,aOptions) {
976
+ aOptions = _.extend({},aOptions,{preferCache: true});
977
+ return this.read(aKeys,aOptions).request();
978
+ },
979
+ cacheRead: function(aKeys,aOptions) {
980
+ aOptions = _.extend({},aOptions,{preferCache: true});
981
+ return this.read(aKeys,aOptions);
982
+ },
983
+
984
+ update: function(aKeyValues,aOptions) {
985
+ var req = this.newRequest();
986
+ return req.update(aKeyValues,aOptions);
987
+ },
988
+ updateRequest: function(aKeyValues,aOptions) {
989
+ return this.update(aKeyValues,aOptions).request();
990
+ },
991
+
992
+ destroy: function(aKeys,aOptions) {
993
+ var req = this.newRequest();
994
+ return req.destroy(aKeys,aOptions);
995
+ },
996
+ destroyRequest: function(aKeys,aOptions) {
997
+ return this.destroy(aKeys,aOptions).request();
998
+ },
999
+
1000
+ execute: function(aKey,aValue,aOptions) {
1001
+ var req = this.newRequest();
1002
+ return req.execute(aKey,aValue,aOptions);
1003
+ },
1004
+ executeRequest: function(aKey,aValue,aOptions) {
1005
+ return this.execute(aKey,aValue,aOptions).request();
1006
+ }
1007
+ // END Convenience Functions
1008
+ });
1009
+
1010
+ /**
1011
+ * A default RemoteProvider implementation. Your own implementation, or a subclass of this may be used instead.
1012
+ * @class Kojac.RemoteProvider
1013
+ * @extends Kojac.Object
1014
+ */
1015
+ Kojac.RemoteProvider = Kojac.Object.extend({
1016
+
1017
+ useMockFileValues: false,
1018
+ mockFilePath: null,
1019
+ mockReadOperationHandler: null,
1020
+ serverPath: null,
1021
+
1022
+ mockWriteOperationHandler: null,//function(aOp) {
1023
+ // Ember.Logger.log(JSON.stringify(CanUtils.copyProperties({},aOp,null,['request'])));
1024
+ // },
1025
+
1026
+ operationsToJson: function(aOps) {
1027
+ var result = [];
1028
+ for (var i=0;i<aOps.length;i++) {
1029
+ var op = aOps[i];
1030
+ var jsonOp = {
1031
+ verb: op.verb,
1032
+ key: op.key
1033
+ };
1034
+ if ((op.verb==='CREATE') || (op.verb==='UPDATE') || (op.verb==='EXECUTE')) {
1035
+ if (op.value && ("toObject" in op.value))
1036
+ jsonOp.value = op.value.toObject(op.options);
1037
+ else
1038
+ jsonOp.value = op.value;
1039
+ }
1040
+ var options = op.options && _.omit(op.options,['cacheResults','preferCache']);
1041
+ if (options && !_.isEmpty(options))
1042
+ jsonOp.options = options; // omit local keys
1043
+ jsonOp.params = op.params;
1044
+ result.push(jsonOp);
1045
+ }
1046
+ return result
1047
+ },
1048
+
1049
+ handleAjaxRequest: function(aRequest) {
1050
+ var result;
1051
+ var op;
1052
+ for (var i=0;i<aRequest.ops.length;i++) {
1053
+ op = aRequest.ops[i];
1054
+ if (op.performed)
1055
+ continue;
1056
+ if (op.verb==='READ' || op.verb==='EXECUTE') {
1057
+ if (this.mockReadOperationHandler) {
1058
+ result = this.mockReadOperationHandler(op);
1059
+ op.performed = true;
1060
+ if (op.fromCache===null)
1061
+ op.fromCache = false;
1062
+ return result;
1063
+ }
1064
+ } else {
1065
+ if (this.mockWriteOperationHandler) {
1066
+ result = this.mockWriteOperationHandler(op);
1067
+ op.performed = true;
1068
+ if (op.fromCache===null)
1069
+ op.fromCache = false;
1070
+ return result;
1071
+ }
1072
+ }
1073
+ }
1074
+ var server_ops = _.filterByCriteria(aRequest.ops,{performed: false});
1075
+ if (!server_ops.length)
1076
+ return;
1077
+ if (this.useMockFileValues) {
1078
+ aRequest.handlers.waitForCallNext = true;
1079
+ var me = this;
1080
+ var getMockFile = function(aOp) {
1081
+ var fp = me.mockFilePath+aOp.key+'.js';
1082
+ var data = null;
1083
+ return jQuery.ajax({url: fp, dataType: 'json', cache: false, data: data}).done(
1084
+ function( aData ) {
1085
+ for (p in aData) {
1086
+ if (p==='results') {
1087
+ for (k in aData.results) {
1088
+ if (k===aOp.key && (aOp.result_key!=aOp.key))
1089
+ aOp.results[aOp.result_key] = aData.results[k];
1090
+ else
1091
+ aOp.results[k] = aData.results[k];
1092
+ }
1093
+ } else
1094
+ aOp[p] = aData[p];
1095
+ }
1096
+ aOp.receiveResult(aOp);
1097
+ this.fromCache = false;
1098
+ this.performed = true;
1099
+ }
1100
+ ).fail(
1101
+ function(jqXHR, textStatus) {
1102
+ aRequest.handlers.handleError(textStatus);
1103
+ }
1104
+ );
1105
+ };
1106
+ var reqs = [];
1107
+ for (var i=0;i<aRequest.ops.length;i++) {
1108
+ reqs.push(getMockFile(aRequest.ops[i]));
1109
+ }
1110
+ jQuery.when.apply(jQuery,reqs).then(function(){
1111
+ aRequest.handlers.callNext();
1112
+ });
1113
+ } else {
1114
+ var opsJson = this.operationsToJson(server_ops);
1115
+ var dataToSend = {
1116
+ kojac: {
1117
+ version: 'KOJAC-1.0',
1118
+ ops: opsJson
1119
+ }
1120
+ };
1121
+ aRequest.handlers.waitForCallNext = true;
1122
+ var ajaxpars = {
1123
+ type: 'POST',
1124
+ data: JSON.stringify(dataToSend),
1125
+ contentType: "application/json; charset=utf-8",
1126
+ dataType: "json"
1127
+ };
1128
+ var result = jQuery.ajax(this.serverPath,ajaxpars).done(function(aResult,aStatus,aXhr){
1129
+ // poke results into request ops using request_op_index
1130
+ aRequest.xhr = aXhr;
1131
+ for (var i=0;i<server_ops.length;i++) {
1132
+ var opRequest = server_ops[i]; //aRequest.ops[request_op_index[i]];
1133
+ var opResult = (_.isArray(aResult.ops) && (i<aResult.ops.length) && aResult.ops[i]);
1134
+ opRequest.receiveResult(opResult);
1135
+ opRequest.fromCache = false;
1136
+ opRequest.performed = true;
1137
+ }
1138
+ aRequest.handlers.callNext();
1139
+ }).fail(function(aXhr,aStatus,aError){
1140
+ for (var i=0;i<server_ops.length;i++) {
1141
+ var opRequest = server_ops[i]; //aRequest.ops[request_op_index[i]];
1142
+ opRequest.fromCache = false;
1143
+ opRequest.performed = true;
1144
+ }
1145
+ aRequest.error = aXhr;
1146
+ aRequest.handlers.handleError(aXhr);
1147
+ aRequest.handlers.callNext();
1148
+ });
1149
+ }
1150
+ }
1151
+ });
1152
+
1153
+ /**
1154
+ * A default ObjectFactory implementation. Your own implementation, or a subclass of this may be used instead.
1155
+ * @class Kojac.ObjectFactory
1156
+ * @extends Kojac.Object
1157
+ */
1158
+ Kojac.ObjectFactory = Kojac.Object.extend({
1159
+
1160
+ matchers: null,
1161
+ defaultClass: Object,
1162
+
1163
+ register: function(aPairs) {
1164
+ if (!aPairs)
1165
+ return;
1166
+ if (this.matchers===null)
1167
+ this.matchers = [];
1168
+ for (var i = 0; i < aPairs.length; i++)
1169
+ this.matchers.push(aPairs[i]);
1170
+ },
1171
+
1172
+ classFromKey: function(aKey) {
1173
+ var pair;
1174
+ var re;
1175
+ var newClass;
1176
+ for (var i = 0; i < this.matchers.length; i++) {
1177
+ pair = this.matchers[i];
1178
+ re = pair[0];
1179
+ if (!re.test(aKey))
1180
+ continue;
1181
+ newClass = pair[1];
1182
+ break;
1183
+ }
1184
+ if (newClass===undefined)
1185
+ newClass = this.defaultClass;
1186
+ return newClass;
1187
+ },
1188
+
1189
+ createInstance: function(aClass,aProperties) {
1190
+ aProperties = aProperties || {};
1191
+ return new aClass(aProperties);
1192
+ },
1193
+
1194
+ copyProperties: function(aDest,aSource) {
1195
+ return _.extend(aDest,aSource);
1196
+ },
1197
+
1198
+ manufacture: function(aObject,aKey) {
1199
+ var newClass = this.classFromKey(aKey);
1200
+ var result;
1201
+ var me = this;
1202
+ if (_.isArray(aObject)) {
1203
+ result = [];
1204
+ for (var i=0; i<aObject.length; i++) {
1205
+ var newv = me.createInstance(newClass,aObject[i]);
1206
+ result.push(newv);
1207
+ }
1208
+ } else {
1209
+ var newClass = this.classFromKey(aKey);
1210
+ result = me.createInstance(newClass,aObject);
1211
+ }
1212
+ return result;
1213
+ },
1214
+
1215
+ transformResultsToValueObjects: function(aRequest) {
1216
+ for (var i=0;i<aRequest.ops.length;i++) {
1217
+ var op = aRequest.ops[i];
1218
+ if (op.error)
1219
+ break;
1220
+ for (var k in op.results) {
1221
+ var v = op.results[k];
1222
+ if (!jQuery.isPlainObject(v))
1223
+ continue;
1224
+ op.results[k] = this.manufacture(v,k);
1225
+ }
1226
+ }
1227
+ }
1228
+
1229
+ });
1230
+