1 
  2 /** @fileOverview Easy way to persist data between requests.<br />
  3  * <p>Importing the "storage" module gives you access to a root
  4  * <code>storage</code> object. Any property you assign to this object
  5  * in one request will be avialable in any subsequent requests.
  6  * <p>The typical way to add a top-level property of
  7  * <code>storage</code> is to check if it's defined and – if not
  8  * – set it to some default value, as in the example below.
  9  * @example
 10 import("storage");
 11 
 12 if (! storage.counter) {
 13     storage.counter = 0;
 14 }
 15  */
 16 
 17 var _a = _appjetnative_;
 18 
 19 function _Storage() {
 20   this.get = function(key) { 
 21     return _a.storage_get(key);
 22   }
 23   this.put = function(key, value) {
 24     _a.storage_put(key, value)
 25   }
 26   this.erase = function(key) {
 27     _a.storage_delete(key);
 28   }
 29 }
 30 
 31 var _s = new _Storage();
 32 // var debugstorage = _s;
 33 
 34 _radixChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz"
 35 _radix = _radixChars.length
 36 
 37 function _longToString(n) {
 38   if (n < _radix) return _radixChars[n];
 39   else return _longToString(Math.floor(n/_radix)) + _radixChars[n%_radix];
 40 }
 41 
 42 function _newUniqueId(prefix) {
 43   return prefix + _longToString(appjet._native.storage_time()) +  _longToString(Math.floor(Math.random()*_radix*_radix));
 44 }
 45 
 46 function _proxy(id) {
 47   if (id.slice(0, 5) == "coll-") {
 48     return _a.createProxyObject(_coll_getHandler(id), _coll_setHandler(id), _coll_deleteHandler(id),
 49 				_coll_getIdsHandler(id), StorableCollection.prototype);
 50   } else if (id.slice(0, 4) == "obj-") {
 51     return _a.createProxyObject(_getHandler(id), _setHandler(id), _deleteHandler(id), 
 52 				_getIdsHandler(id), StorableObject.prototype);
 53   } else {
 54     throw new Error("Unknown type for object id");
 55   }
 56 }
 57 
 58 /**
 59  * Gets a StorableObject or StorableCollection by id. <!-- This
 60  * function is useful for passing "objects" between user requests; for
 61  * example, to modify an object using a form, just pass the id of the
 62  * object to the posted page in a hidden input element, then retrieve
 63  * it on the posted page using this function.  See the example
 64  * below. -->
 65  * @param {string} id The id of the object to be gotten.
 66  * @example
 67 function get_editor() {
 68   // Objects passed between requests must be reachable from storage.
 69   var obj = storage.users.iterator().next();
 70 
 71   // In your edit page; "obj" is the object to edit.
 72   print(FORM({action: "/doedit", method: "post"},
 73              INPUT({type: "text", name: "title"}),
 74              INPUT({type: "hidden", name: "id", value: obj.id}),
 75              INPUT({type: "submit", name: "edit"})));
 76 }
 77 
 78 function post_doedit() {
 79   // In your form's action page.
 80 
 81   getStorable(request.param("id")).title = request.param("title");
 82 }
 83  * @return {StorableObject} The StorableObject whose id is <code>id</code>, 
 84  * or <code>undefined</code> if no such object exists.
 85  */ 
 86 function getStorable(id) {
 87   if (_s.get(id+":") != undefined)
 88     return _getObjectForId(id);
 89 }
 90 
 91 function _coerceToHolder(obj) {
 92   if (typeof(obj) == 'function')
 93     return false;
 94   if (typeof(obj) != 'object')
 95     return {t: typeof(obj), v: ""+obj}
 96   if (obj instanceof String)
 97     return {t: 'string', v: ""+obj};
 98   if (obj instanceof Date)
 99     return {t: 'date', v: ""+obj.getTime()}
100   if (obj instanceof Number)
101     return {t: 'number', v: ""+obj}
102   if (obj instanceof Boolean)
103     return {t: 'boolean', v: ""+obj}
104   if (obj instanceof StorableObject)
105     return {t: 'object', v: obj.id}
106   return false
107 }
108 
109 function _coerceToStorableHolder(obj) {
110   //  _print("Coercing to storableHolder: "+obj);
111   var test = _coerceToHolder(obj);
112   if (test) {
113     //    _print("...coerced to plain holder: "+test)
114     return test;
115   }
116   //  _print("...failed to coerce to holder, trying object")
117   return {t: 'object', v: _coerceToStorableObject(obj).id}
118 } 
119 
120 function _coerceToStorableObject(obj) {
121   //  _print("Coercing to storableObject: "+obj)
122 
123   if (typeof(obj) != 'object')
124     throw new TypeError(""+obj+" could not be made storable!");
125 
126   if (obj instanceof StorableObject)
127     return obj;
128 
129   if (obj instanceof Array)
130     throw new TypeError("Can't store an array; use a StorableCollection instead.")
131 
132   eachProperty(obj, function(k, v) {
133       //      _print("...testing each property; now: "+k+", value: "+v);
134       if (k == 'id' || k == 'toString' || k == 'hasOwnProperty' || (! _coerceToStorableHolder(v)))
135 	throw new TypeError("Couldn't make object storable; property "+k+" failed to convert ("+v+")");
136     });
137 
138   var ret = new StorableObject();
139   eachProperty(obj, function(k, v) {
140       // bypass the usual setting mechanism, since we have a holder here.
141       _setHandler(ret.id, true)(k, _coerceToStorableHolder(v));
142     });
143 
144   return ret;
145 }
146 
147 function _print(msg) {
148   var _debug = false;
149   if (_debug)
150     print(msg);
151 }
152 
153 var _idMap = {};
154 function _getObjectForId(id) {
155   if (id == null || id == undefined)
156     throw new Error("Bad ID: "+id)
157   if (!(id in _idMap)) {
158     //    _print("...wasn't available, making new");
159     var o = _proxy(id);
160     //    _print("...o is: "+o.id);
161     _idMap[id] = o;
162     //    _print("...id is in _idMap? "+(id in _idMap));
163     var v = _idMap[id];
164     //    _print("...v is: "+typeof(v));
165   }
166   //  _print("...and returning: "+_idMap[id].id);
167   return _idMap[id];
168 }
169 
170 function _iteratingToString(obj) {
171   var visited = {}; visited[obj.id] = true;
172   var helper = function(obj) {
173     var s = "S{ ";
174     var props = [ "id: "+obj.id ];
175     for (var i in obj) {
176       if (obj[i] instanceof StorableObject) {
177 	if (visited[obj[i].id] == true) {
178 	  props.push(i+": [seen "+obj[i].id+"]");
179 	} else {
180 	  visited[obj[i].id] = true;
181 	  props.push(i+": "+(obj[i] instanceof StorableCollection ? obj[i] : helper(obj[i])));
182 	}
183       } else {
184 	props.push(i+": "+obj[i]);
185       }
186     }
187     s += props.join(", ") + " }";
188     return s;
189   }
190   return helper(obj);
191 }      
192 
193 function _getHandler(id) {
194   return function(name) {
195     //    _print("on object id: "+id+", request for property: "+name);
196     if (name == "id") 
197       return id;
198     if (name == "toString") {
199       return function() { return _iteratingToString(this) };
200     }
201     if (name == "toHTML") {
202       return function() {
203 	var t = TABLE({border: 1, cellpadding: 2, cellspacing: 0});
204 	t.push(TR(TD("id"), TD(id)));
205 	eachProperty(this, function(name, value) {
206 	  t.push(TR(TD(String(name)), TD(String(value))));
207 	});
208 	return toHTML(t);
209       }
210     }
211     if (name == "hasOwnProperty") {
212       return function(name) {
213 	// XXX: This should be in ".has"
214 	return (! (_s.get(id+"/"+name) === undefined));
215       }
216     }
217     eval("var o = "+_s.get(id+"/"+name));
218     if (o == undefined)
219       return undefined;
220     switch (o.t) {
221     case 'number':
222       return Number(o.v);
223     case 'boolean':
224       return (o.v == "true" ? true : false);
225     case 'undefined':
226       return undefined;
227     case 'date':
228       return new Date(Number(o.v));
229     case 'object':
230       return _getObjectForId(o.v);
231     default:
232       //      _print("for id "+id+" getting property "+name+" returning "+o.v);
233       return o.v;
234     }
235   }
236 }
237 
238 function _setHandler(id, raw) {
239   return function(name, value) {
240     if (name == "id")
241       throw new Error("Can't set id property.");
242     if (name == "toString")
243       throw new Error("Can't set toString property.");
244     if (name == "hasOwnProperty")
245       throw new Error("Can't set hasOwnProperty property.");
246     var old = _getIdsHandler(id)();
247 
248     if (old == undefined)
249       old = []
250     var exists = false;
251     for (var i = 0; i < old.length; ++i) {
252       if (old[i] == name) {
253         exists = true;
254         break;
255       }
256     }
257 
258     var newValue = (raw ? value : _coerceToStorableHolder(value))
259 
260     var oldValue
261     if (! exists) {
262       old.push(name);
263       _s.put(id+":", ""+old);
264     } else {
265       eval("oldValue = "+_s.get(id+"/"+name));
266     }
267 
268     _s.put(id+"/"+name, ""+newValue);
269   }
270 }
271 
272 function _deleteHandler(id) {
273   return function(name) {
274     eval("var oldValue = "+_s.get(id+"/"+name));
275     _s.erase(id+"/"+name);
276     eval("var old = "+_s.get(id+":"));
277     old = _removeItem(old, name);
278     _s.put(id+":", ""+old);
279   }
280 }
281 
282 function _getIdsHandler(id) {
283   return function() {
284     eval("var old = "+_s.get(id+":"));
285     if (old == undefined)
286       old = [];
287     //    _print("for id "+id+", returning ids: "+old);
288     return old;
289   }
290 }    
291 
292 
293 /**
294  * An object that can be stored directly, and is backed by our
295  * database.  StorableObjects can be added to StorableCollections.
296  * @constructor @param {object} [obj] An optional object argument
297  * whose properties will be copied into the newly-created
298  * StorableObject. <code>obj</code> cannot have any functions, and any
299  * properties of <code>obj</code>'s prototype are ignored. Any objects
300  * that <code>obj</code> references will also be copied. (Note that
301  * any subsequent modifications to <code>obj</code> will not be
302  * reflected in the newly-created StorableObject.)
303  * @example
304 var s = new StorableObject();
305 s.message = "Hi there!";
306 
307 storage.foo = s;
308  * @example
309 var s = new StorableObject({message: "Hi there!"});
310 
311 var c = new StorableCollection();
312 c.add(s)
313  */
314 function StorableObject(obj) {
315   if (typeof(obj) == 'object')
316     return _coerceToStorableObject(obj)
317 
318   var id = _newUniqueId("obj-");
319   //  _print("making object for id: "+id);
320   _s.put(id+":", ""+[]);
321 
322   var o = _getObjectForId(id);
323   //  _print("...and it is: "+o.id);
324   return o;
325 }
326 StorableObject.prototype = {}
327 
328 /**
329  * The id of a StorableObject is auto-generated. You can get the
330  * object associated with a given id using <code>getStorable</code>.
331  * @name id 
332  * @memberOf StorableObject
333  * @type string
334  */
335 
336 var storage = _getObjectForId("obj-root");
337 
338 function _Iterable() {
339   this.i = 0;
340   this.reset = function() { this.i = 0; }
341 
342   this.next = function() {
343     if (this.hasNext()) {
344       this.i += 1;
345       return _getObjectForId(this.base[this.i-1])
346     }
347   }
348   this.hasNext = function() {
349     return ((this.base instanceof Array) && (this.i < this.base.length))
350   }
351 
352   this.forEach = function(cb) {
353     for (var i = 0; i < this.base.length; ++i) {
354       if (cb(_getObjectForId(this.base[i])) === false) {
355 	break;
356       }
357     }
358   }
359   this.size = function() { return this.base.length; }
360   this.filter = function(filter_obj) {
361     return new _FilterIterator(this, filter_obj);
362   }
363   this.sort = function(sort_fn) {
364     return new _SortIterator(this, sort_fn);
365   }
366   this.toHTML = function() { return _coll_toHTML(this, this.type); }
367 }
368 var _iterable = new _Iterable();
369 
370 function _Iterator(base, id) {
371   this.type = id;
372   this.base = base;
373 }
374 _Iterator.prototype = _iterable;
375 
376 function _FilterIterator(base, filter_obj) {
377   this.type = base.type+".filter("+filter_obj+")";
378   this.base = []
379   while (base.hasNext()) {
380     var n = base.next();
381     if (_match(filter_obj, n)) {
382       this.base.push(n.id);
383     }
384   }
385 }
386 _FilterIterator.prototype = _iterable;
387 
388 function _SortIterator(base, sort_fn) {
389   this.type = base.type+".sort("+sort_fn+")";
390   this.base = [].concat(base.base);
391   this.base.sort(function(ida, idb) {
392     return sort_fn(_getObjectForId(ida), _getObjectForId(idb));
393   });
394 }
395 _SortIterator.prototype = _iterable;
396 
397 // function _dll_add(id, obj) {
398 //   if (!isStorableObject(obj)) throw new TypeError("Not a storable object!");
399 //   eval("var base = "+_s.get(id+"/"));
400 //   if (base == undefined)
401 //     base = {'start': obj.id, 'end': ""}
402 //   var newObj = {prev: base.end, next: ""}
403 //   base.end = obj.id
404 //   _s.put(id+"/"+obj.id, ""+newObj);
405 //   _s.put(id+"/", ""+base);
406 // }
407 
408 // function _dll_remove(id, obj) {
409 //   if (!_isStorableObject(obj)) throw new TypeError("Not a storable object!");
410 //   eval("var base = "+_s.get(id+"/"));
411 //   if (base == undefined)
412 //     return;
413 //   eval("var ptr = "+_s.get(id+"/"+base.start));
414 //   while (base.end != "") {
415 //     if (ptr.id == obj.id) {
416 //       if (ptr.prev != "") {
417 // 	eval("var prev = "+_s.get(id+"/"+ptr.prev));
418 // 	prev.next = ptr.next;
419 // 	_s.put(id+"/"+ptr.prev, ""+prev)
420 //       } else {
421 // 	base.start = ptr.next;
422 //       }
423 //       if (ptr.next != "") {
424 // 	eval("var next = "+_s.get(id+"/"+ptr.next));
425 // 	next.prev = ptr.prev;
426 // 	_s.put(id+"/"+ptr.next, ""+next)
427 //       } else {
428 // 	base.end = ptr.prev;
429 //       }
430 //       _s.put(id+"/", ""+base);
431 //       break
432 //     }
433 //   }
434 // }
435 
436 // function _dll_getIds(id, obj) {
437 //   return function() { return ['add', 'remove', 'iterator', 'filter', 'sort']; }
438 // }  
439 
440 /**
441  * A StorableCollection persistently stores other StorableObjects.
442  * @constructor
443  * @extends StorableObject
444  * @inherits {string} StorableObject.id
445  * @example
446 storage.foo = new StorableCollection();
447 storage.foo.add({name: "John"});
448 storage.foo.add({name: "Mary"});
449 
450 storage.foo.forEach(printp);
451  */
452 function StorableCollection() {
453   var id = _newUniqueId("coll-");
454   _s.put(id+":", "[]");
455   return _getObjectForId(id);
456 }
457 StorableCollection.prototype = _a.createProxyObject(function() { }, function() { }, function() { }, function() { },
458 						    StorableObject.prototype)
459 
460 function _match(base, query) {
461   for (var i in base) {
462     if (base[i] != query[i])
463       return false;
464   }
465   return true;
466 }
467 
468 function _removeItem(arr, obj) {
469   var newArr = []
470   for (var i = 0; i < arr.length; ++i) {
471     if (obj != arr[i])
472       newArr.push(arr[i]);
473   }
474   return newArr;
475 }
476 
477 /**
478  * Adds a StorableObject to this collection.
479  * @name add
480  * @function
481  * @memberOf StorableCollection
482  * @param {object} obj The object to add to this collection. If
483  * <code>obj</code> is not a StorableObject, a new StorableObject is
484  * created from <code>obj</code> by passing it to the StorableObject
485  * constructor, copying its properties.
486  * @example
487 var c = new StorableCollection();
488 c.add({name: "John"});
489  */
490 function _coll_add(id, o) { 
491   if (typeof(o) != 'object')
492     throw new TypeError("Not a storable object!");
493   
494   // Option to do an "addAll"; an iterable can't be part of a collection.
495   if (o instanceof _Iterable) {
496     o.forEach(function(obj) { _coll_add(id, obj) });
497     return true;
498   }
499 
500   var obj = _coerceToStorableObject(o)
501 
502   // update self
503   eval("var base = "+_s.get(id+":"));
504   if (base == undefined)
505     base = [];
506   base.push(obj.id);
507   _s.put(id+":", ""+base);
508   return obj;
509 }
510 
511 /**
512  * Removes StorableObjects from this collection.
513  * @name remove
514  * @function
515  * @memberOf StorableCollection
516  * @param {object} obj The object to remove from this collection. If
517  * <code>obj</code> is a StorableObject, <code>obj</code> itself is
518  * removed from this collection. If <code>obj</code> is an collection
519  * or a view on a collection (such as one created by <a
520  * href="#StorableCollection.filter">filter</a>), then all objects
521  * provided in that collection are removed. Finally, if
522  * <code>obj</code> is just a plain object, then all members of this
523  * collection that have the same properties and values as
524  * <code>obj</code> are removed. Note: passing an <code>{}</code>
525  * removes <em>all</em> objects form this collection.
526  * @example
527 var c = storage.users; // a StorableCollection
528 c.remove({name: "John"});
529  * @see #StorableCollection.filter
530  */
531 function _coll_remove(id, obj) { 
532   if (typeof(obj) != 'object')
533     throw new TypeError("Not a storable object!");
534 
535   // A "removeAll"; an iterable can't be part of a collection.
536   if (obj instanceof _Iterable) {
537     obj.forEach(function(o) { _coll_remove(id, o) });
538     return;
539   }
540   if (! (obj instanceof StorableObject)) {
541     _coll_remove(id, _coll_filter(id, obj));
542     return;
543   }
544 
545   eval("var base = "+_s.get(id+":"));
546   if (base == undefined)
547     base = [];
548   var  newBase = [];
549   for (var i = 0; i < base.length; ++i) {
550     if (base[i] != obj.id)
551       newBase.push(base[i]);
552   }
553   _s.put(id+":", ""+newBase);
554 }
555 
556 function _coll_iterator(id) { 
557   eval("var base = "+_s.get(id+":"));
558   if (base == undefined)
559     base = [];
560   var i = 0;
561   return new _Iterator(base, id)
562 }
563 
564 /**
565  * Filters this collection based on a matching object.
566  * @name filter
567  * @scope StorableCollection
568  * @function
569  * @memberOf StorableCollection
570  * @param {object} match The object to base a filter on. The returned
571  * view contains only those members of this StorableCollection whose
572  * properties have the same value as the ones in
573  * <code>match</code>. (Properties in the collection's members not
574  * present in <code>match</code> are ignored.) <code>filter</code> and
575  * <code>sort</code> are "chainable" operations; they can be applied
576  * to filtered and sorted views.
577  * @example
578 var c = new StorableCollection();
579 c.add(new StorableObject({first_name: "Bob", last_name: "Smith"}));
580 c.add(new StorableObject({first_name: "John", last_name: "Smith"}));
581 
582 var i = c.filter({first_name: "John"});
583 i.forEach(printp)      // prints John Smith's entry above.
584  * @example
585 // Filter and sort are chainable, as in this example:
586 var c = storage.users;  // a StorableCollection
587 
588 var view = 
589   c.filter({first_name: "John"}).sort(function(a, b) { return a.age - b.age })
590 view.forEach(printp)   // prints users named John, sorted from youngest to oldest.
591  * @return A filtered view of this collection.
592  */
593 function _coll_filter(id, match) { return new _FilterIterator(_coll_iterator(id), match) }
594 
595 /**
596  * Sorts this collection based on a sorting function.
597  * @name sort
598  * @function
599  * @memberOf StorableCollection
600  * @param {function} compare As with the function argument to
601  * Array.sort, <code>compare</code> should take two arguments
602  * <em>a</em> and <em>b</em>, and return a negative value, 0, or a
603  * positive value, if <em>a</em> < <em>b</em>, <em>a</em> =
604  * <em>b</em>, or <em>a</em> > <em>b</em>,
605  * respectively. <code>filter</code> and <code>sort</code> are
606  * "chainable" operations; they can be applied to filtered and sorted
607  * views.
608  * @example
609 c.sort(function(a, b) { return a.date - b.date })
610  * @return A sorted view of this collection.
611  */
612 function _coll_sort(id, compare) { return new _SortIterator(_coll_iterator(id), compare) }
613 
614 /**
615  * Executes a function once on each member of this collection. Note
616  * that forEach can only be called once on a view (though it can be
617  * called multiple times on a StorableCollection).
618  * @name forEach
619  * @function
620  * @memberOf StorableCollection
621  * @param {function} f The function to call on each member of this
622  * collection. Returning <code>false</code> will cause forEach to abort.
623  */
624  
625 /**
626  * Returns the number of elements in a collection. Can also be applied
627  * to filtered and sorted views of a collection. (This number may be
628  * approximate if your collection is very large.)
629  * @name size
630  * @function
631  * @m