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.
- checksums.yaml +15 -0
- data/.gitignore +7 -0
- data/Gemfile +17 -0
- data/Gemfile.lock +158 -0
- data/MIT-LICENSE +20 -0
- data/README.md +69 -0
- data/Rakefile +27 -0
- data/app/assets/javascripts/can_extensions.js +45 -0
- data/app/assets/javascripts/kojac.js +1230 -0
- data/app/assets/javascripts/kojac_canjs.js +191 -0
- data/app/assets/javascripts/kojac_ember.js +463 -0
- data/app/controllers/kojac_controller.rb +70 -0
- data/app/serializers/default_kojac_serializer.rb +10 -0
- data/diagram.odg +0 -0
- data/kojac.gemspec +36 -0
- data/lib/kojac/app_serialize.rb +29 -0
- data/lib/kojac/kojac_rails.rb +432 -0
- data/lib/kojac/ring_strong_parameters.rb +195 -0
- data/lib/kojac/version.rb +3 -0
- data/lib/kojac.rb +8 -0
- data/lib/tasks/kojac_tasks.rake +4 -0
- data/notes.txt +48 -0
- data/spec/.DS_Store +0 -0
- data/spec/can_cache_spec.js +87 -0
- data/spec/can_factory_spec.js +144 -0
- data/spec/can_model_spec.js +127 -0
- data/spec/demo/README.rdoc +261 -0
- data/spec/demo/Rakefile +7 -0
- data/spec/demo/app/assets/javascripts/application.js +15 -0
- data/spec/demo/app/assets/stylesheets/application.css +13 -0
- data/spec/demo/app/controllers/application_controller.rb +3 -0
- data/spec/demo/app/helpers/application_helper.rb +2 -0
- data/spec/demo/app/mailers/.gitkeep +0 -0
- data/spec/demo/app/models/.gitkeep +0 -0
- data/spec/demo/app/views/layouts/application.html.erb +14 -0
- data/spec/demo/config/application.rb +65 -0
- data/spec/demo/config/boot.rb +10 -0
- data/spec/demo/config/database.yml +25 -0
- data/spec/demo/config/environment.rb +5 -0
- data/spec/demo/config/environments/development.rb +37 -0
- data/spec/demo/config/environments/production.rb +67 -0
- data/spec/demo/config/environments/test.rb +37 -0
- data/spec/demo/config/initializers/backtrace_silencers.rb +7 -0
- data/spec/demo/config/initializers/inflections.rb +15 -0
- data/spec/demo/config/initializers/mime_types.rb +5 -0
- data/spec/demo/config/initializers/secret_token.rb +7 -0
- data/spec/demo/config/initializers/session_store.rb +8 -0
- data/spec/demo/config/initializers/wrap_parameters.rb +14 -0
- data/spec/demo/config/locales/en.yml +5 -0
- data/spec/demo/config/routes.rb +58 -0
- data/spec/demo/config.ru +4 -0
- data/spec/demo/lib/assets/.gitkeep +0 -0
- data/spec/demo/log/.gitkeep +0 -0
- data/spec/demo/public/404.html +26 -0
- data/spec/demo/public/422.html +26 -0
- data/spec/demo/public/500.html +25 -0
- data/spec/demo/public/favicon.ico +0 -0
- data/spec/demo/script/rails +6 -0
- data/spec/ember_factory_spec.js +157 -0
- data/spec/ember_model_spec.js +179 -0
- data/spec/external/.DS_Store +0 -0
- data/spec/external/ember/.DS_Store +0 -0
- data/spec/external/ember/ember-1.0.0-rc.6.js +30970 -0
- data/spec/external/ember/handlebars-1.0.0-rc.4.js +2239 -0
- data/spec/external/jasmine/MIT.LICENSE +20 -0
- data/spec/external/jasmine/jasmine-html.js +616 -0
- data/spec/external/jasmine/jasmine.css +81 -0
- data/spec/external/jasmine/jasmine.js +2529 -0
- data/spec/external/jasmine.async.js +51 -0
- data/spec/external/jquery/jquery-1.7.2.js +9404 -0
- data/spec/external/jquery/jquery-1.7.2.min.js +4 -0
- data/spec/external/jquery/jquery-1.9.1.js +9597 -0
- data/spec/external/json2.js +480 -0
- data/spec/external/steal/steal-121115.js +2747 -0
- data/spec/external/steal/steal-3.2.3.js +2098 -0
- data/spec/external/steal/steal-3.2.3.min.js +27 -0
- data/spec/external/steal/steal.js +2466 -0
- data/spec/external/steal/steal.min.js +32 -0
- data/spec/external/steal/stealconfig.js +19 -0
- data/spec/external/underscore.js +1223 -0
- data/spec/external/underscore_plus.js +261 -0
- data/spec/external.zip +0 -0
- data/spec/handler_stack_spec.js +143 -0
- data/spec/helpers/SpecHelper.js +10 -0
- data/spec/kojac_caching_spec.js +105 -0
- data/spec/kojac_mock_spec.js +230 -0
- data/spec/kojac_model_spec.js +126 -0
- data/spec/kojac_object_spec.js +171 -0
- data/spec/kojac_operations_spec.js +41 -0
- data/spec/mockjson/order_item.js +37 -0
- data/spec/mockjson/order_item__49.js +15 -0
- data/spec/mockjson/order_item__50.js +15 -0
- data/spec/mockjson/order_item__51.js +15 -0
- data/spec/mockjson/product.js +82 -0
- data/spec/mockjson/product__3.js +22 -0
- data/spec/model_ring_spec.rb +52 -0
- data/spec/operation_include_spec.js +77 -0
- data/spec/run.html +81 -0
- data/spec/spec.js +2 -0
- data/spec/support/jasmine.yml +86 -0
- 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
|
+
|