kojac 0.9.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.
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
+