1 /** @fileOverview An easy-to-use database for persisting objects between requests.<br />
  2  * <p>Importing the "storage" module gives you access to a root
  3  * <code>storage</code> object. Any property you assign to this object
  4  * in one request will be avialable in any subsequent requests.
  5  * <p>The typical way to define a top-level property of
  6  * <code>storage</code> is to check if it's defined and – if not
  7  * – set it to some default value, as in the example below.</p>
  8  * <p>Performance Note: storage can be quite fast, depending on how it's used, but performance
  9  * tuning is beyond the scope of this reference.  We suggest that for starters
 10  * you just do whatever is simplest for your app, and for help performance-tuning,
 11  * ask for help in <a href="http:/forum.appjet.com/">The AppJet Forums</a>.</p>
 12  * @example
 13 import("storage");
 14 
 15 if (! storage.counter) {
 16     storage.counter = 0;
 17 }
 18 storage.counter++;
 19 
 20 printp("Hits to this page: ", storage.counter);
 21  */
 22 
 23 var _a = _appjetnative_;
 24 
 25 function _objectToArray(obj) {
 26   var arr = [];
 27   eachProperty(obj,
 28     function(k,v) {
 29       arr.push(k, (v instanceof Array ? v : [v]).map(function(poss) {
 30         var sh = _coerceToHolder(poss);
 31         if (! sh)
 32 	  throw new Error("Invalid property type on matching object: "+v);
 33         return sh; 
 34       }));
 35     });
 36   return arr;
 37 }
 38 
 39 function _Storage() {
 40   this.create = function(id) {
 41     var ret = _a.storage_create(id);
 42     if (ret.status != 0)
 43       throw new Error(ret.message);
 44     return _getObjectForId(ret.value);
 45   };
 46   this.get = function(id, key) {
 47     var ret = _a.storage_get(id, key);
 48     if (ret.status == 0 || ret.status == -2) {
 49       switch (ret.type) {
 50       case 'object':
 51 	// _print("...got object back: "+ret.value);
 52 	return _getObjectForId(ret.value);
 53       case 'date':
 54 	return new Date(ret.value);
 55       default:
 56 	return ret.value;
 57       }
 58     } else {
 59       throw new Error(ret.message);
 60     }
 61   };
 62   this.getIds = function(id) {
 63     var ret = _a.storage_getIds(id);
 64     if (ret.status != 0) {
 65       throw new Error(ret.message);
 66     }
 67     return ret.value;
 68   };
 69   this.getById = function(id) {
 70     var ret = _a.storage_getById(id);
 71     if (ret.status != 0)
 72       throw new Error(ret.message);
 73     return _proxy(ret.value); // better be a new object!
 74   };
 75   this.put = function(id, key, tv) {
 76     var ret = _a.storage_put(id, key, tv.type, tv.value);
 77     if (ret.status != 0)
 78       throw new Error(ret.message);
 79   };
 80   this.erase = function(id, key) {
 81     var ret = _a.storage_delete(id, key);
 82     if (ret.status != 0)
 83       throw new Error(ret.message);
 84   };
 85 }
 86 
 87 function _Collections() {
 88   this.create = function(id) {
 89     var ret = _a.storage_coll_create(id);
 90     if (ret.status != 0)
 91       throw new Error(ret.message);
 92     return _getObjectForId(ret.value);
 93   };
 94   this.add = function(id, obj) {
 95     var ret = _a.storage_coll_add(id, obj.id);
 96     if (ret.status != 0)
 97       throw new Error(ret.message);
 98   };
 99   this.remove = function(id, obj) {
100     var ret;
101     if ((obj instanceof StorableObject) || (obj instanceof _Iterable)) {
102       ret = _a.storage_coll_remove(id, obj.id);
103     } else {
104       ret = _a.storage_coll_remove(id, _objectToArray(obj));
105     }
106     if (ret.status != 0)
107       throw new Error(ret.message);
108   };
109   this.iterator = function(id, newId) {
110     var ret = _a.storage_coll_iterator(id, newId);
111     if (ret.status != 0)
112       throw new Error(ret.message);
113     return _getObjectForId(newId);
114   };
115 }
116 
117 function _Iterators() {
118   this.next = function(id) {
119     var ret = _a.storage_itr_next(id);
120     if (ret.status != 0) {
121       if (ret.status == -2) // empty iterator!
122 	return undefined;
123       throw new Error(ret.message);
124     }
125     return _getObjectForId(ret.value);
126   };
127   this.hasNext = function(id) {
128     var ret = _a.storage_itr_hasNext(id);
129     if (ret.status != 0)
130       throw new Error(ret.message);
131     return ret.value;
132   };
133   this.filter = function(id, newId, match) {
134     var ret = _a.storage_itr_filter(id, newId, _objectToArray(match));
135     if (ret.status != 0)
136       throw new Error(ret.message);
137     return _getObjectForId(newId);
138   };
139   this.skip = function(id, newId, n) {
140     var ret = _a.storage_itr_skip(id, newId, n);
141     if (ret.status != 0)
142       throw new Error(ret.message);
143     return _getObjectForId(newId);
144   };
145   this.limit = function(id, newId, n) {
146     var ret = _a.storage_itr_limit(id, newId, n);
147     if (ret.status != 0)
148       throw new Error(ret.message);
149     return _getObjectForId(newId);
150   };
151   this.sort = function(id, newId, f) {
152     var comparator;
153     if (typeof(f) == 'function')
154       comparator = function(id1, id2) {
155 	return f(_getObjectForId(id1), _getObjectForId(id2));
156       };
157     var ret = _a.storage_itr_sort(id, newId, comparator);
158     if (ret.status != 0)
159       throw new Error(ret.message);
160     return _getObjectForId(newId);
161   };
162   this.sortBy = function(id, newId, propertyNames) {
163     var ret = _a.storage_itr_sort(id, newId, propertyNames);
164     if (ret.status != 0)
165       throw new Error(ret.message);
166     return _getObjectForId(newId);
167   };
168   this.reverse = function(id, newId) {
169     var ret = _a.storage_itr_reverse(id, newId);
170     if (ret.status != 0)
171       throw new Error(ret.message);
172     return _getObjectForId(ret.value);
173   };
174   this.first = function(id) {
175     var ret = _a.storage_itr_first(id);
176     if (ret.status != 0) {
177       if (ret.status == -2)
178 	return undefined;
179       throw new Error(ret.message);
180     }
181     return _getObjectForId(ret.value);
182   };
183   this.size = function(id) {
184     var ret = _a.storage_itr_size(id);
185     if (ret.status != 0)
186       throw new Error(ret.message);
187     return ret.value;
188   };
189   this.name = function(id) {
190     var ret = _a.storage_viewName(id);
191     if (ret.status != 0)
192       throw new Error(ret.message);
193     return ret.value;
194   }
195 }
196 
197 var _s = new _Storage();
198 var _c = new _Collections();
199 var _i = new _Iterators();
200 // var debugstorage = _s;
201 
202 _radixChars = "0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz";
203 _radix = _radixChars.length;
204 
205 function _longToString(n) {
206   if (n < _radix) return _radixChars[n];
207   else return _longToString(Math.floor(n/_radix)) + _radixChars[n%_radix];
208 }
209 
210 function _newUniqueId(prefix) {
211   return prefix + _longToString(appjet._native.storage_time()) +  _longToString(Math.floor(Math.random()*_radix*_radix));
212 }
213 
214 function _proxy(id) {
215   if (id.slice(0, 5) == "coll-") {
216     return _a.createProxyObject(_coll_getHandler(id), _coll_setHandler(id), _coll_deleteHandler(id),
217 				_coll_getIdsHandler(id), StorableCollection.prototype);
218   } else if (id.slice(0, 4) == "obj-") {
219     return _a.createProxyObject(_getHandler(id), _setHandler(id), _deleteHandler(id),
220 				_getIdsHandler(id), StorableObject.prototype);
221   } else if (id.slice(0, 4) == "itr-") {
222     return _a.createProxyObject(_itr_getHandler(id), _itr_setHandler(id), _itr_deleteHandler(id),
223 				_itr_getIdsHandler(id), _Iterable.prototype);
224   } else {
225     throw new Error("Unknown type for object id");
226   }
227 }
228 
229 /**
230  * Gets a StorableObject or StorableCollection by its id.
231  * <p>Each StorableObject or StorableCollection has an id
232  * that never changes, and this id can be used
233  * to refer to the object in a URL or form parameter.
234  * @param {string} id The id of the object to be gotten.
235  * @example
236 import("quickforms");
237 import("storage");
238 
239 if (! storage.things) {
240     storage.things = new StorableCollection();
241     storage.things.add({message: "Hello world!"});
242 }
243 
244 if (! request.param("idToDisplay")) {
245     var firstId = storage.things.first().id;
246     // show a button that creates a request with idToDisplay = firstId
247     printp(new QuickButton("Show first thing.", {}, {idToDisplay: firstId}));
248 }
249 else {
250     // display the "foo" property of whatever object has an id of idToDisplay
251     printp(getStorable(request.param("idToDisplay")).message);
252 }
253  * @return {StorableObject} The StorableObject whose id is <code>id</code>,
254  * or <code>undefined</code> if no such object exists.
255  */
256 function getStorable(id) {
257   if (id)
258     return _getObjectForId(id);
259 }
260 
261 function _coerceToHolder(obj) {
262   if (typeof(obj) == 'function')
263     return false;
264   if (typeof(obj) != 'object')
265     return {type: typeof(obj), value: obj};
266   if (obj instanceof String)
267     return {type: 'string', value: obj.valueOf()};
268   if (obj instanceof Date)
269     return {type: 'date', value: obj.getTime()};
270   if (obj instanceof Number)
271     return {type: 'number', value: obj.valueOf()};
272   if (obj instanceof Boolean)
273     return {type: 'boolean', value: obj.valueOf()};
274   if (obj instanceof StorableObject)
275     return {type: 'object', value: obj.id};
276   return false;
277 }
278 
279 function _coerceToStorableHolder(obj) {
280   // _print("Coercing to storableHolder: "+obj);
281   var test = _coerceToHolder(obj);
282   if (test) {
283     // _print("...coerced to plain holder: "+test);
284     return test;
285   }
286   // _print("...failed to coerce to holder, trying object");
287   return {type: 'object', value: _coerceToStorableObject(obj).id};
288 }
289 
290 function _coerceToStorableObject(obj) {
291    // _print("Coercing to storableObject: "+obj);
292 
293   if (typeof(obj) != 'object')
294     throw new TypeError(""+obj+" could not be made storable!");
295 
296   if (obj instanceof StorableObject)
297     return obj;
298 
299   if (obj instanceof Array)
300     throw new TypeError("Can't store an array; use a StorableCollection instead.");
301 
302   eachProperty(obj, function(k, v) {
303       // _print("...testing each property; now: "+k+", value: "+v);
304       if (k == 'id' || k == 'toString' || k == 'hasOwnProperty' || (! _coerceToStorableHolder(v)))
305 	throw new TypeError("Couldn't make object storable; property "+k+" failed to convert ("+v+")");
306     });
307 
308   var ret = new StorableObject();
309   eachProperty(obj, function(k, v) {
310       // bypass the usual setting mechanism, since we have a holder here.
311       _setHandler(ret.id)(k, v);
312     });
313 
314   return ret;
315 }
316 
317 // function _print(msg) {
318 //   var _debug = false;
319 //   if (_debug)
320 //     print(msg, BR());
321 // }
322 
323 var _idMap = {};
324 function _getObjectForId(id) {
325   if (id == null || id == undefined)
326     throw new Error("Bad ID: "+id);
327   if (!(id in _idMap)) {
328     // _print("...wasn't available, making new");
329     var o = _s.getById(id);
330     // _print("...o is: "+o.id);
331     _idMap[id] = o;
332     // _print("...id is in _idMap? "+(id in _idMap));
333     var v = _idMap[id];
334     // _print("...v is: "+typeof(v));
335   }
336   // _print("...and returning: "+_idMap[id].id);
337   return _idMap[id];
338 }
339 
340 function _iteratingToString(obj) {
341   var visited = {}; visited[obj.id] = true;
342   var helper = function(obj) {
343     var s = "S{ ";
344     var props = [ "id: "+obj.id ];
345     for (var i in obj) {
346       if (obj[i] instanceof StorableObject) {
347 	if (visited[obj[i].id] == true) {
348 	  props.push(i+": [seen "+obj[i].id+"]");
349 	} else {
350 	  visited[obj[i].id] = true;
351 	  props.push(i+": "+(obj[i] instanceof StorableCollection ? obj[i] : helper(obj[i])));
352 	}
353       } else {
354 	props.push(i+": "+obj[i]);
355       }
356     }
357     s += props.join(", ") + " }";
358     return s;
359   };
360   return helper(obj);
361 }
362 
363 function _getHandler(id) {
364   return function(name) {
365     // _print("on object id: "+id+", request for property: "+name);
366     if (name == "id")
367       return id;
368     if (name == "toString") {
369       return function() { return _iteratingToString(this) };
370     }
371     if (name == "toHTML") {
372       return function() {
373 	var t = TABLE({border: 1, cellpadding: 2, cellspacing: 0});
374 	t.push(TR(TD("id"), TD(id)));
375 	eachProperty(this, function(name, value) {
376 	  t.push(TR(TD(String(name)), TD(String(value))));
377 	});
378 	return toHTML(t);
379       };
380     }
381     if (name == "hasOwnProperty") {
382       return function(name) {
383 	// XXX: This should be in ".has"
384 	return (_a.storage_get(id, name).status == 0);
385       };
386     }
387     return _s.get(id, name);
388   };
389 }
390 
391 function _setHandler(id, raw) {
392   return function(name, value) {
393     if (name == "id")
394       throw new Error("Can't set id property.");
395     if (name == "toString")
396       throw new Error("Can't set toString property.");
397     if (name == "hasOwnProperty")
398       throw new Error("Can't set hasOwnProperty property.");
399     // _print("on object id: "+id+", setting property: "+name+" to "+value);
400 
401     _s.put(id, name, (raw ? value : _coerceToStorableHolder(value)));
402   };
403 }
404 
405 function _deleteHandler(id) {
406   return function(name) {
407     _s.erase(id, name);
408   };
409 }
410 
411 function _getIdsHandler(id) {
412   return function() {
413     return _s.getIds(id);
414   };
415 }
416 
417 
418 /**
419  * <p>A StorableObject is an object that can be persisted between requests.</p>
420  * <p>To create a StorableObject, call <code>new StorableObject()</code>.</p>
421  * <p>You can optionally pass any JavaScript object to the constructor, and the
422  * properties and values of the object will become properties and values
423  * of the newly-created StorableObject, as in 
424  * <code>new StorableObject({username: "aiba", password: "sex"})</code>.</p>
425  * <p>StorableObjects are very similar to normal JavaScript objects, except that
426  * StorableObjects can't have properties that are functions.  Properties that
427  * are objects must be StorableObjects, and assigning a normal JavaScript object
428  * will cause a StorableObject to be created (in other words, <code>storage.foo
429  * = {a: 1}</code> behaves like <code>storage.foo = new StorableObject({a: 1})</code>).
430  * <p>Once a StorableObject is constructed, it may be persisted in two different 
431  * ways: You can assign it as the property of another StorableObject, such as
432  * the root StorableObject called <code>storage</code>.  Or you can add it
433  * to a <a href="#StorableCollection">StorableCollection</a>.</p>
434  * 
435  * @constructor
436  * @class StorableObject is an object that can be persisted between requests.
437  * @param {object} [obj] An optional object
438  * whose properties will be copied into the returned
439  * StorableObject. <code>obj</code> cannot have any functions, and any
440  * properties of <code>obj</code>'s prototype are ignored. Any objects
441  * that <code>obj</code> references will also be copied. (Note that
442  * because properties are <i>copied</i>, any subsequent modifications 
443  * to <code>obj</code> will not be reflected in the returned
444  * StorableObject).
445  * 
446  * @example
447 // Storing a string
448 
449 var obj = new StorableObject();
450 obj.message = "Hi there!";
451 storage.foo = obj;
452  * @example
453 // Alternative code to do the same thing
454 
455 storage.foo = new StorableObject({message: "Hi there!"});
456  * @example
457 // Adding StorableObjects to a StorableCollection
458 
459 storage.people = new StorableCollection();
460 
461 var person1 = new StorableObject({name: "Aaron", age: 25});
462 var person2 = new StorableObject({name: "David", age: 24});
463 var person3 = new StorableObject({name: "JD", age: 25});
464 
465 storage.people.add(person1);
466 storage.people.add(person2);
467 storage.people.add(person3);
468  */
469 function StorableObject(obj) {
470   if (typeof(obj) == 'object')
471     return _coerceToStorableObject(obj);
472 
473   var id = _newUniqueId("obj-");
474   // _print("making object for id: "+id);
475   var o = _s.create(id);
476   // _print("...and it is: "+o.id);
477   return o;
478 }
479 StorableObject.prototype = {};
480 
481 /**
482  * The id of a StorableObject is auto-generated and can't be changed. You can get the
483  * object associated with a given id using <code>getStorable</code>.
484  * @name id
485  * @memberOf StorableObject
486  * @type string
487  */
488 
489 var storage = _getObjectForId("obj-root");
490 
491 /**
492  * <p>A StorableCollection is a way of grouping together many StorableObjects.</p>
493  * <p>StorableObjects in a StorableCollection are not stored in any particular order,
494  * but you can use the <code><a
495  * href="#StorableCollection.sort">sort</a></code> or <code><a
496  * href="#StorableCollection.sortBy">sortBy</a></code> methods to access the objects in order.
497  * A collection usually contains objects of the same "type" in that they have a similar
498  * set of properties, but this is not strictly required.</p>
499  * 
500  * <p>If you are familiar with SQL databases, a StorableCollection provides
501  * much of the same functionality that a <em>table</em> provides in SQL,
502  * and you can think of each StorableObject in the collection as analogous to a
503  * <em>row</em> in a SQL table.  Unlike SQL tables, StorableCollections do not
504  * have pre-defined columns.  You can add StorableObjects with
505  * any properties you want.</p>
506  * 
507  * <p>After a StorableCollection is created, you add StorableObjects
508  * to it by calling <code>StorableCollection.add()</code>, as documented below.</p>
509  * 
510  * <p>StorableCollections are also StorableObjects.  Therefore, each
511  * StorableCollection has an "id" property, and may be retreived with a call to <a href="#getStorable">getStorable()</a>.
512  * Usually, however, StorableCollections are assigned to properties of
513  * the global <code>storage</code> object, as in the examples below.</p>
514  *
515  * @constructor
516  * @class StorableCollection is a way of grouping together many StorableObjects.
517  * @extends StorableObject
518  * 
519  * @inherits {string} StorableObject.id
520  * @example
521 import("storage");
522 
523 // A collection of books
524 storage.books = new StorableCollection();
525 
526 // Here we add a new StorableObject to the collection
527 storage.books.add(new StorableObject({title: "Shantaram", author: "Gregory David Roberts" }));
528 
529 // If you call add with an object that is not a StorableObject, it
530 // gets converted to a StorableObject.  So this is a more compact way
531 // of doing the same thing.
532 storage.books.add({title: "Musashi", author: "Eiji Yoshikawa"});
533 storage.books.add({title: "Hackers & Painters", author: "Paul Graham"});
534 
535 function printBook(book) {
536   printp(html("<i>", book.title, "</i> (by ", book.author, ")"));
537 }
538 
539 // Print books sorted by their StorableObject creation date.
540 storage.books.sort().forEach(printBook);
541 print(html("<hr>"));
542 
543 // Print books sorted alphabetically by title.
544 storage.books.sortBy("title").forEach(printBook);
545 print(html("<hr>"));
546 
547 // Print books sorted reverse-alphabetically by author.
548 storage.books.sortBy("-author").forEach(printBook);
549  */
550 function StorableCollection() {
551   var id = _newUniqueId("coll-");
552   // _print("making collection for id: "+id);
553   var o = _c.create(id);
554   return o;
555 }
556 StorableCollection.prototype = _a.createProxyObject(function() { }, function() { }, function() { }, function() { return []; },
557  						    StorableObject.prototype);
558 
559 /**
560  * Adds a StorableObject to this collection.
561  * @name add
562  * @function
563  * @memberOf StorableCollection
564  * @param {object} obj The object to add to this collection. If
565  * <code>obj</code> is not a StorableObject, a new StorableObject is
566  * created from <code>obj</code> by passing it to the StorableObject
567  * constructor, copying its properties.
568  * @example
569 var c = new StorableCollection();
570 c.add({name: "John"});
571  * @return {StorableObject} The added object.
572  */
573 function _coll_add(id, o) {
574   if (typeof(o) != 'object')
575     throw new TypeError("Not a storable object!");
576 
577   // Option to do an "addAll"; an iterable can't be part of a collection.
578   if (o instanceof _Iterable) {
579     // _print("doing an 'add-all'");
580     _c.add(id, o);
581     return true;
582   }
583 
584   var obj = _coerceToStorableObject(o);
585   // _print("coercing argument to storable obejct, got: "+obj);
586   _c.add(id, obj);
587 
588   return obj;
589 }
590 
591 /**
592  * Removes StorableObjects from this collection.
593  * @name remove
594  * @function
595  * @memberOf StorableCollection
596  * @param {object} obj The object to remove from this collection. If
597  * <code>obj</code> is a StorableObject, <code>obj</code> itself is
598  * removed from this collection. If <code>obj</code> is an collection
599  * or a view on a collection (such as one created by <a
600  * href="#StorableCollection.filter">filter</a>), then all objects
601  * provided in that collection are removed. Finally, if
602  * <code>obj</code> is just a plain object, then all members of this
603  * collection that have the same properties and values as
604  * <code>obj</code> are removed. Note: passing an <code>{}</code>
605  * removes <em>all</em> objects form this collection.
606  * @example
607 var c = storage.users; // a StorableCollection
608 c.remove({name: "John"});
609  * @see #StorableCollection.filter
610  */
611 function _coll_remove(id, obj) {
612   if (typeof(obj) != 'object')
613     throw new TypeError("Not a storable object!");
614 
615   _c.remove(id, obj);
616 }
617 
618 function _coll_iterator(id) {
619   var newId = _newUniqueId("itr-");
620   return _c.iterator(id, newId);
621 }
622 
623 function _Iterable(id) {
624 }
625 _Iterable.prototype = {};
626 
627 /**
628  * <p>Filters this collection based on a matching object.</p>
629  * <p>Like other operations on StorableCollection, filter() returns a <i>view</i>
630  * of the collection.  Views are temporary subsets of a collection which
631  * can be iterated over or chained with other StorableCollection operations.</p>
632  * <p><code>filter</code> is a "chainable" operation, along with sort(), sortBy(),
633  * and limit().  For example, it is common to perform calls such as
634  * <code>storage.mycollection.filter(...).sort(...).forEach(...)</code>.</p>
635  * @name filter
636  * @scope StorableCollection
637  * @function
638  * @memberOf StorableCollection
639  * @param {object} match The object to base a filter on. The returned
640  * view contains only those members of this StorableCollection whose
641  * properties have the same value as the ones in
642  * <code>match</code>. (Properties in the collection's members not
643  * present in <code>match</code> are ignored.) 
644  * @return A filtered view of this collection.
645  * @example
646 storage.people = new StorableCollection();
647 storage.people.add({firstName: "Bob", lastName: "Smith", age: 30});
648 storage.people.add({firstName: "John", lastName: "Smith", age: 29});
649 storage.people.add({firstName: "John", lastName: "Sanders", age: 29});
650 storage.people.add({firstName: "John", lastName: "Jacobs", age: 29});
651 
652 function printPerson(p) {
653   printp(p.firstName, " ", p.lastName, " (age: ", p.age, ")");
654 }
655 
656 printp("Everyone with firstName John:");
657 storage.people.filter({firstName: "John"}).sortBy("lastName").forEach(printPerson);
658 
659 printp("Everyone with lastName Smith:");
660 storage.people.filter({lastName: "Smith"}).sortBy("firstName").forEach(printPerson);
661 */
662 function _itr_filter(id, match) {
663   var newId = _newUniqueId("itr-");
664   return _i.filter(id, newId, match);
665 }
666 
667 /**
668  * <p>Returns a sorted view of this collection based
669  * on a sorting function, or by object creation date
670  * if no sorting function is given.</p>
671  * 
672  * <p><code>sort()</code> does not modify the StorableCollection is is called on.
673  * Instead, it returns a view of the StorableCollection, which can be further
674  * sorted, limited, or iterated over.</p>
675  * 
676  * <p><code>sort()</code> is a "chainable" operation:
677  * it can be applied to other filtered, sorted, or limited views, as in
678  * the example below.</p>
679  * @name sort
680  * @function
681  * @memberOf StorableCollection
682  * @param {function} [compare] <p>As with the function argument to
683  * Array.sort, <code>compare</code> should take two arguments
684  * <em>a</em> and <em>b</em>, and return a negative value, 0, or a
685  * positive value, if <em>a</em> < <em>b</em>, <em>a</em> =
686  * <em>b</em>, or <em>a</em> > <em>b</em>, respectively.</p>
687  * <p>If no sort function is passed in, the returned view is sorted by 
688  * object creation time, oldest objects first.</p>
689  * @return A sorted view of this collection.
690  * @example
691 function ageCompare(p1, p2) { return p2.age - p1.age; }
692 function printPerson(p) { printp(p.name); }
693 
694 storage.people.sort(ageCompare).forEach(printPerson);
695  */
696 function _itr_sort(id, compare) {
697   var newId = _newUniqueId("itr-");
698   return _i.sort(id, newId, compare);
699 }
700 
701 /**
702  * <p>Returns a sorted view of this collection based on a property name.
703  * This is sometimes more convienent than calling the more general <code>sort()</code> method
704  * and writing your own comparator function.</p>
705  * 
706  * <p><code>sortBy</code> is a "chainable" operation: it can be applied to
707  * other filtered, sorted, or limited views.</p>
708  * 
709  * @name sortBy
710  * @function
711  * @memberOf StorableCollection
712  * @param {string} propertyName1 Which property to use to compare the
713  * collected objects on. Optionally prepend the property name with a "-"
714  * to reverse the sort order.
715  * @param {string} propertyName2 A secondary property to further sort the
716  * objects.
717  * @param {string} etc etc.
718  * @return A sorted view of this collection.
719  * @example
720 // sorts by "lastName" property, descending, and then 
721 // secondarily by "firstName" property, ascending.
722 
723 storage.people.sortBy("-lastName", "firstName").forEach(printp);
724 
725  */
726 function _itr_sortBy(id, propertyNames) {
727   var newId = _newUniqueId("itr-");
728   return _i.sortBy(id, newId, propertyNames);
729 }
730 
731 /**
732  * <p>Executes a function once on each member of this collection.</p>
733  * 
734  * @name forEach
735  * @function
736  * @memberOf StorableCollection
737  * @param {function} f The function to call on each member of this
738  * collection. Returning <code>false</code> will cause forEach to
739  * abort.
740  * @example
741 // prints most recent 10 items of a collection.
742 storage.mycollection.sort().reverse().forEach(function(o) {
743   printp("recent object: ", o);
744 });
745  */
746 
747 /**
748  * Returns the number of elements in a collection. Can also be applied
749  * to filtered and sorted views of a collection. (This number may be
750  * approximate if your collection is very large or if many requests are
751  * modifying it simultaneously.)
752  * @name size
753  * @function
754  * @memberOf StorableCollection
755  * @example
756 var size = storage.mycollection.filter({status: 3}).size();
757  * @return {number} The size of this collection.
758  */
759 function _itr_size(id) {
760   return _i.size(id);
761 }
762 
763 
764 /**
765  * Gets the first object in this StorableCollection.
766  * @name first
767  * @function
768  * @memberOf StorableCollection
769  * @return {number} The first object in this collection.
770  * @example
771 print(storage.mycollection.filter({status: 3}).first());
772  */
773 function _itr_first(id) {
774   return _i.first(id);
775 }
776 
777 /**
778  * Returns a view consisting of the first <code>n</code> elements of
779  * this collection or view. Useful in combination with <a
780  * href="#StorableCollection.sort">sort</a> to get, for example, the
781  * 10 most recent items in a collection.</p>
782  * <p><code>limit()</code> is a
783  * "chainable" operation: it can be applied to other filtered, sorted, or
784  * limited views.</p>
785  * @name limit
786  * @function
787  * @memberOf StorableCollection
788  * @param {number} n How many elements to limit the new view to.
789  * @return A limited view of this collection.
790  * @example
791 storage.mycollection.filter({user: "bob"}).limit(10).forEach(printp);
792  */
793 function _itr_limit(id, n) {
794   var newId = _newUniqueId("itr-");
795   return _i.limit(id, newId, n);
796 }
797 
798 /**
799  * <p>Returns a view that skips the first <code>n</code> elements of this
800  * collection or view.</p>
801  * 
802  * <p><code>skip()</code> is a "chainable" operation: it
803  * can be applied to other filtered, sorted, or limited views.</p>
804  *
805  * <p>This is particularly useful for paginating elements in a storable 
806  * collection. If there are <i>n</i> elements per page, and you want to
807  * display page <i>p</i>, then you can get a view of this page's elements
808  * with <code>collection.skip(n*(p-1)).limit(n)</code>, as in the example 
809  * below.
810  *
811  * @name skip
812  * @function
813  * @memberOf StorableCollection
814  * @param {number} n How many elements to skip.
815  * @return A new view of this collection with the first <code>n</code> elements skipped.
816  * 
817  * @example
818 // rendering part of a paginated collection
819 var p = request.param("pageNum");
820 var n = 10; // items per page
821 var view = mycollection.sort().skip(n*(p-1)).limit(n);
822 view.forEach(printp);
823  */
824 function _itr_skip(id, n) {
825   var newId = _newUniqueId("itr-");
826   return _i.skip(id, newId, n);
827 }
828 
829 /**
830  * <p>Returns a view with the order of elements reversed.</p> 
831  * 
832  * <p><code>reverse()</code> is a "chainable" operation:
833  * it can be applied to other filtered, sorted, or limited views.</p>
834  * 
835  * @name reverse
836  * @function
837  * @memberOf StorableCollection
838  * @return A new view of this collection
839  */
840 function _itr_reverse(id) {
841   var newId = _newUniqueId("itr-");
842   return _i.reverse(id, newId);
843 }
844 
845 function _coll_toHTML(obj, id) {
846   return appjet._internal.likeObjectsToHTML(
847     function(f) {
848       obj.forEach(function(obj) {
849 		    f({id: obj.id}, obj, {});
850    });}, TH({colspan: 100}, _i.name(id)));
851 }
852 
853 function _coll_getHandler(id) {
854   return function(name) {
855     switch (name) {
856     case 'id':
857       return id;
858     case 'toString':
859       return function() {
860 	return "Collection id #"+id;
861       };
862     case 'add':
863       return function(obj) { return _coll_add(id, obj); };
864     case 'remove':
865       return function(obj) { return _coll_remove(id, obj); };
866     case 'iterator':
867       return function() { return _coll_iterator(id); };
868     case 'forEach':
869       return function(f) { return _coll_iterator(id).forEach(f); };
870     case 'size':
871       return function() { return _coll_iterator(id).size(); };
872     case 'hasOwnProperty':
873       return function() { return false; }; // XXX: This should be in ".has"
874     case 'filter':
875       return function(obj) { return _itr_filter(id, obj); };
876     case 'sort':
877       return function(f) { return _itr_sort(id, f); };
878     case 'sortBy':
879       return function(f1, f2, etc) { 
880 	return _itr_sortBy(id, Array.prototype.slice.call(arguments));
881       };
882     case 'first':
883       return function() { return _itr_first(id); };
884     case 'limit':
885       return function(n) { return _itr_limit(id, n); };
886     case 'reverse':
887       return function() { return _itr_reverse(id); };
888     case 'skip':
889       return function(n) { return _itr_skip(id, n); };
890     case 'toHTML':
891       return function() { return _coll_toHTML(this, id); };
892     default:
893       return undefined;
894     }
895   };
896 }
897 function _coll_setHandler(id) {
898   return function() { };
899 }
900 function _coll_getIdsHandler(id) {
901   return function() {
902     return ['id', 'toString', 'add', 'remove', 'iterator', 'forEach', 
903             'size', 'skip', 'sort', 'sortBy', 'filter', 'first', 'limit', 'reverse'];
904   };
905 }
906 function _coll_deleteHandler(id) {
907   return function() { };
908 }
909 
910 function _itr_getHandler(id) {
911   return function(name) {
912     switch (name) {
913     case 'id':
914       return id;
915     case 'next':
916       return function() {
917 	return _i.next(id);
918       };
919     case 'hasNext':
920       return function() {
921 	return _i.hasNext(id);
922       };
923     case 'forEach':
924       return function(f) {
925 	var o;
926 	while (_i.hasNext(id)) {
927 	  if ((o = f(_i.next(id))) === false)
928 	    break;
929 	}
930       };
931     case 'filter':
932       return function(obj) { return _itr_filter(id, obj); };
933     case 'sort':
934       return function(f) { return _itr_sort(id, f); };
935     case 'sortBy':
936       return function(f1, f2, etc) { 
937 	return _itr_sortBy(id, Array.prototype.slice.call(arguments));
938       };
939     case 'size':
940       return function() { return _itr_size(id); };
941     case 'first':
942       return function() { return _itr_first(id); };
943     case 'limit':
944       return function(n) { return _itr_limit(id, n); };
945     case 'reverse':
946       return function() { return _itr_reverse(id); };
947     case 'skip':
948       return function(n) { return _itr_skip(id, n); };
949     case 'toHTML':
950       return function() { return _coll_toHTML(this, id) }
951     default:
952       return undefined;
953     }
954   };
955 }
956 function _itr_setHandler(id) {
957   return function() { };
958 }
959 function _itr_getIdsHandler(id) {
960   return function() {
961     return ['id', 'toString', 'add', 'remove', 'forEach', 'next', 'hasNext',
962 	    'size', 'skip', 'sort', 'sortBy', 'filter', 'first', 'limit', 'reverse'];
963   };
964 }
965 function _itr_deleteHandler(id) {
966   return function() { };
967 }
968