Fix for #1231
[jquery.git] / build / js / base2.js
1 // timestamp: Tue, 01 May 2007 19:13:00\r
2 /*\r
3         base2.js - copyright 2007, Dean Edwards\r
4         http://www.opensource.org/licenses/mit-license\r
5 */\r
6 \r
7 var base2 = {};\r
8 \r
9 // You know, writing a javascript library is awfully time consuming.\r
10 \r
11 new function(_) { ////////////////////  BEGIN: CLOSURE  ////////////////////\r
12 \r
13 // =========================================================================\r
14 // base2/Base.js\r
15 // =========================================================================\r
16 \r
17 // version 1.1\r
18 \r
19 var Base = function() {\r
20         // call this method from any other method to invoke that method's ancestor\r
21 };\r
22 \r
23 Base.prototype = {      \r
24         extend: function(source) {\r
25                 if (arguments.length > 1) { // extending with a name/value pair\r
26                         var ancestor = this[source];\r
27                         var value = arguments[1];\r
28                         if (typeof value == "function" && ancestor && /\bbase\b/.test(value)) {\r
29                                 var method = value;                             \r
30                                 value = function() { // override\r
31                                         var previous = this.base;\r
32                                         this.base = ancestor;\r
33                                         var returnValue = method.apply(this, arguments);\r
34                                         this.base = previous;\r
35                                         return returnValue;\r
36                                 };\r
37                                 value.method = method;\r
38                                 value.ancestor = ancestor;\r
39                         }\r
40                         this[source] = value;\r
41                 } else if (source) { // extending with an object literal\r
42                         var extend = Base.prototype.extend;\r
43                         if (Base._prototyping) {\r
44                                 var key, i = 0, members = ["constructor", "toString", "valueOf"];\r
45                                 while (key = members[i++]) if (source[key] != Object.prototype[key]) {\r
46                                         extend.call(this, key, source[key]);\r
47                                 }\r
48                         } else if (typeof this != "function") {\r
49                                 // if the object has a customised extend() method then use it\r
50                                 extend = this.extend || extend;\r
51                         }                       \r
52                         // copy each of the source object's properties to this object\r
53                         for (key in source) if (!Object.prototype[key]) {\r
54                                 extend.call(this, key, source[key]);\r
55                         }\r
56                 }\r
57                 return this;\r
58         },\r
59 \r
60         base: Base\r
61 };\r
62 \r
63 Base.extend = function(_instance, _static) { // subclass\r
64         var extend = Base.prototype.extend;\r
65         \r
66         // build the prototype\r
67         Base._prototyping = true;\r
68         var proto = new this;\r
69         extend.call(proto, _instance);\r
70         delete Base._prototyping;\r
71         \r
72         // create the wrapper for the constructor function\r
73         var constructor = proto.constructor;\r
74         var klass = proto.constructor = function() {\r
75                 if (!Base._prototyping) {\r
76                         if (this._constructing || this.constructor == klass) { // instantiation\r
77                                 this._constructing = true;\r
78                                 constructor.apply(this, arguments);\r
79                                 delete this._constructing;\r
80                         } else { // casting\r
81                                 var object = arguments[0];\r
82                                 if (object != null) {\r
83                                         (object.extend || extend).call(object, proto);\r
84                                 }\r
85                                 return object;\r
86                         }\r
87                 }\r
88         };\r
89         \r
90         // build the class interface\r
91         for (var i in Base) klass[i] = this[i];\r
92         klass.ancestor = this;\r
93         klass.base = Base.base;\r
94         klass.prototype = proto;\r
95         klass.toString = this.toString;\r
96         extend.call(klass, _static);\r
97         // class initialisation\r
98         if (typeof klass.init == "function") klass.init();\r
99         return klass;\r
100 };\r
101 \r
102 // initialise\r
103 Base = Base.extend({\r
104         constructor: function() {\r
105                 this.extend(arguments[0]);\r
106         }\r
107 }, {\r
108         ancestor: Object,\r
109         base: Base,\r
110         \r
111         implement: function(_interface) {\r
112                 if (typeof _interface == "function") {\r
113                         // if it's a function, call it\r
114                         _interface(this.prototype);\r
115                 } else {\r
116                         // add the interface using the extend() method\r
117                         this.prototype.extend(_interface);\r
118                 }\r
119                 return this;\r
120         }\r
121 });\r
122 \r
123 // =========================================================================\r
124 // lang/main.js\r
125 // =========================================================================\r
126 \r
127 var Legacy = typeof $Legacy == "undefined" ? {} : $Legacy;\r
128 \r
129 var K = function(k) {return k};\r
130 \r
131 var assert = function(condition, message, Err) {\r
132         if (!condition) {\r
133                 throw new (Err || Error)(message || "Assertion failed.");\r
134         }\r
135 };\r
136 \r
137 var assertType = function(object, type, message) {\r
138         if (type) {\r
139                 var condition = typeof type == "function" ? instanceOf(object, type) : typeof object == type;\r
140                 assert(condition, message || "Invalid type.", TypeError);\r
141         }\r
142 };\r
143 \r
144 var copy = function(object) {\r
145         var fn = new Function;\r
146         fn.prototype = object;\r
147         return new fn;\r
148 };\r
149 \r
150 var format = function(string) {\r
151         // replace %n with arguments[n]\r
152         // e.g. format("%1 %2%3 %2a %1%3", "she", "se", "lls");\r
153         // ==> "she sells sea shells"\r
154         // only supports nine replacements: %1 - %9\r
155         var args = arguments;\r
156         return String(string).replace(/%([1-9])/g, function(match, index) {\r
157                 return index < args.length ? args[index] : match;\r
158         });\r
159 };\r
160 \r
161 var $instanceOf = Legacy.instanceOf || new Function("o,k", "return o instanceof k");\r
162 var instanceOf = function(object, klass) {\r
163         assertType(klass, "function", "Invalid 'instanceOf' operand.");\r
164         if ($instanceOf(object, klass)) return true;\r
165         // handle exceptions where the target object originates from another frame\r
166         //  this is handy for JSON parsing (amongst other things)\r
167         if (object != null) switch (klass) {\r
168                 case Object:\r
169                         return true;\r
170                 case Number:\r
171                 case Boolean:\r
172                 case Function:\r
173                 case String:\r
174                         return typeof object == typeof klass.prototype.valueOf();\r
175                 case Array:\r
176                         // this is the only troublesome one\r
177                         return !!(object.join && object.splice && !arguments.callee(object, Function));\r
178                 case Date:\r
179                         return !!object.getTimezoneOffset;\r
180                 case RegExp:\r
181                         return String(object.constructor.prototype) == String(new RegExp);\r
182         }\r
183         return false;\r
184 };\r
185         \r
186 var match = function(string, expression) {\r
187         // same as String.match() except that this function will return an empty \r
188         // array if there is no match\r
189         return String(string).match(expression) || [];\r
190 };\r
191 \r
192 var RESCAPE = /([\/()[\]{}|*+-.,^$?\\])/g;\r
193 var rescape = function(string) {\r
194         // make a string safe for creating a RegExp\r
195         return String(string).replace(RESCAPE, "\\$1");\r
196 };\r
197 \r
198 var $slice = Array.prototype.slice;\r
199 var slice = function(object) {\r
200         // slice an array-like object\r
201         return $slice.apply(object, $slice.call(arguments, 1));\r
202 };\r
203 \r
204 var TRIM = /^\s+|\s+$/g;\r
205 var trim = function(string) {\r
206         return String(string).replace(TRIM, "");        \r
207 };\r
208 \r
209 // =========================================================================\r
210 // lang/extend.js\r
211 // =========================================================================\r
212 \r
213 var base = function(object, args) {\r
214         // invoke the base method with all supplied arguments\r
215         return object.base.apply(object, args);\r
216 };\r
217 \r
218 var extend = function(object) {\r
219         assert(object != Object.prototype, "Object.prototype is verboten!");\r
220         return Base.prototype.extend.apply(object, slice(arguments, 1));\r
221 };\r
222 \r
223 // =========================================================================\r
224 // lang/assignID.js\r
225 // =========================================================================\r
226 \r
227 var $ID = 1;\r
228 var assignID = function(object) {\r
229         // assign a unique id\r
230         if (!object.base2ID) object.base2ID = "b2_" + $ID++;\r
231         return object.base2ID;\r
232 };\r
233 \r
234 // =========================================================================\r
235 // lang/forEach.js\r
236 // =========================================================================\r
237 \r
238 if (typeof StopIteration == "undefined") {\r
239         StopIteration = new Error("StopIteration");\r
240 }\r
241 \r
242 var forEach = function(object, block, context) {\r
243         if (object == null) return;\r
244         if (typeof object == "function") {\r
245                 // functions are a special case\r
246                 var fn = Function;\r
247         } else if (typeof object.forEach == "function" && object.forEach != arguments.callee) {\r
248                 // the object implements a custom forEach method\r
249                 object.forEach(block, context);\r
250                 return;\r
251         } else if (typeof object.length == "number") {\r
252                 // the object is array-like\r
253                 forEach.Array(object, block, context);\r
254                 return;\r
255         }\r
256         forEach.Function(fn || Object, object, block, context);\r
257 };\r
258 \r
259 // these are the two core enumeration methods. all other forEach methods\r
260 //  eventually call one of these two.\r
261 \r
262 forEach.Array = function(array, block, context) {\r
263         var i, length = array.length; // preserve\r
264         if (typeof array == "string") {\r
265                 for (i = 0; i < length; i++) {\r
266                         block.call(context, array.charAt(i), i, array);\r
267                 }\r
268         } else {\r
269                 for (i = 0; i < length; i++) {\r
270                         block.call(context, array[i], i, array);\r
271                 }\r
272         }\r
273 };\r
274 \r
275 forEach.Function = Legacy.forEach || function(fn, object, block, context) {\r
276         // enumerate an object and compare its keys with fn's prototype\r
277         for (var key in object) {\r
278                 if (fn.prototype[key] === undefined) {\r
279                         block.call(context, object[key], key, object);\r
280                 }\r
281         }\r
282 };\r
283 \r
284 // =========================================================================\r
285 // base2/Base/forEach.js\r
286 // =========================================================================\r
287 \r
288 Base.forEach = function(object, block, context) {\r
289         forEach.Function(this, object, block, context);\r
290 };\r
291 \r
292 // =========================================================================\r
293 // base2/../Function.js\r
294 // =========================================================================\r
295 \r
296 // some browsers don't define this\r
297 \r
298 Function.prototype.prototype = {};\r
299 \r
300 \r
301 // =========================================================================\r
302 // base2/../String.js\r
303 // =========================================================================\r
304 \r
305 // fix String.replace (Safari/IE5.0)\r
306 \r
307 if ("".replace(/^/, String)) {\r
308         extend(String.prototype, "replace", function(expression, replacement) {\r
309                 if (typeof replacement == "function") { // Safari doesn't like functions\r
310                         if (instanceOf(expression, RegExp)) {\r
311                                 var regexp = expression;\r
312                                 var global = regexp.global;\r
313                                 if (global == null) global = /(g|gi)$/.test(regexp);\r
314                                 // we have to convert global RexpExps for exec() to work consistently\r
315                                 if (global) regexp = new RegExp(regexp.source); // non-global\r
316                         } else {\r
317                                 regexp = new RegExp(rescape(expression));\r
318                         }\r
319                         var match, string = this, result = "";\r
320                         while (string && (match = regexp.exec(string))) {\r
321                                 result += string.slice(0, match.index) + replacement.apply(this, match);\r
322                                 string = string.slice(match.index + match[0].length);\r
323                                 if (!global) break;\r
324                         }\r
325                         return result + string;\r
326                 } else {\r
327                         return base(this, arguments);\r
328                 }\r
329         });\r
330 }\r
331 \r
332 // =========================================================================\r
333 // base2/Abstract.js\r
334 // =========================================================================\r
335 \r
336 var Abstract = Base.extend({\r
337         constructor: function() {\r
338                 throw new TypeError("Class cannot be instantiated.");\r
339         }\r
340 });\r
341 \r
342 // =========================================================================\r
343 // base2/Module.js\r
344 // =========================================================================\r
345 \r
346 // based on ruby's Module class and Mozilla's Array generics:\r
347 //   http://www.ruby-doc.org/core/classes/Module.html\r
348 //   http://developer.mozilla.org/en/docs/New_in_JavaScript_1.6#Array_and_String_generics\r
349 \r
350 // A Module is used as the basis for creating interfaces that can be\r
351 // applied to other classes. *All* properties and methods are static.\r
352 // When a module is used as a mixin, methods defined on what would normally be\r
353 // the instance interface become instance methods of the target object.\r
354 \r
355 // Modules cannot be instantiated. Static properties and methods are inherited.\r
356 \r
357 var Module = Abstract.extend(null, {\r
358         extend: function(_interface, _static) {\r
359                 // extend a module to create a new module\r
360                 var module = this.base();\r
361                 // inherit static methods\r
362                 forEach (this, function(property, name) {\r
363                         if (!Module[name] && name != "init") {\r
364                                 extend(module, name, property);\r
365                         }\r
366                 });\r
367                 // implement module (instance AND static) methods\r
368                 module.implement(_interface);\r
369                 // implement static properties and methods\r
370                 extend(module, _static);\r
371                 // Make the submarine noises Larry!\r
372                 if (typeof module.init == "function") module.init();\r
373                 return module;\r
374         },\r
375         \r
376         implement: function(_interface) {\r
377                 // implement an interface on BOTH the instance and static interfaces\r
378                 var module = this;\r
379                 if (typeof _interface == "function") {\r
380                         module.base(_interface);\r
381                         forEach (_interface, function(property, name) {\r
382                                 if (!Module[name] && name != "init") {\r
383                                         extend(module, name, property);\r
384                                 }\r
385                         });\r
386                 } else {\r
387                         // create the instance interface\r
388                         Base.forEach (extend({}, _interface), function(property, name) {\r
389                                 // instance methods call the equivalent static method\r
390                                 if (typeof property == "function") {\r
391                                         property = function() {\r
392                                                 base; // force inheritance\r
393                                                 return module[name].apply(module, [this].concat(slice(arguments)));\r
394                                         };\r
395                                 }\r
396                                 if (!Module[name]) extend(this, name, property);\r
397                         }, module.prototype);\r
398                         // add the static interface\r
399                         extend(module, _interface);\r
400                 }\r
401                 return module;\r
402         }\r
403 });\r
404 \r
405 \r
406 // =========================================================================\r
407 // base2/Enumerable.js\r
408 // =========================================================================\r
409 \r
410 var Enumerable = Module.extend({\r
411         every: function(object, test, context) {\r
412                 var result = true;\r
413                 try {\r
414                         this.forEach (object, function(value, key) {\r
415                                 result = test.call(context, value, key, object);\r
416                                 if (!result) throw StopIteration;\r
417                         });\r
418                 } catch (error) {\r
419                         if (error != StopIteration) throw error;\r
420                 }\r
421                 return !!result; // cast to boolean\r
422         },\r
423         \r
424         filter: function(object, test, context) {\r
425                 return this.reduce(object, function(result, value, key) {\r
426                         if (test.call(context, value, key, object)) {\r
427                                 result[result.length] = value;\r
428                         }\r
429                         return result;\r
430                 }, new Array2);\r
431         },\r
432 \r
433         invoke: function(object, method) {\r
434                 // apply a method to each item in the enumerated object\r
435                 var args = slice(arguments, 2);\r
436                 return this.map(object, (typeof method == "function") ? function(item) {\r
437                         if (item != null) return method.apply(item, args);\r
438                 } : function(item) {\r
439                         if (item != null) return item[method].apply(item, args);\r
440                 });\r
441         },\r
442         \r
443         map: function(object, block, context) {\r
444                 var result = new Array2;\r
445                 this.forEach (object, function(value, key) {\r
446                         result[result.length] = block.call(context, value, key, object);\r
447                 });\r
448                 return result;\r
449         },\r
450         \r
451         pluck: function(object, key) {\r
452                 return this.map(object, function(item) {\r
453                         if (item != null) return item[key];\r
454                 });\r
455         },\r
456         \r
457         reduce: function(object, block, result, context) {\r
458                 this.forEach (object, function(value, key) {\r
459                         result = block.call(context, result, value, key, object);\r
460                 });\r
461                 return result;\r
462         },\r
463         \r
464         some: function(object, test, context) {\r
465                 return !this.every(object, function(value, key) {\r
466                         return !test.call(context, value, key, object);\r
467                 });\r
468         }\r
469 }, {\r
470         forEach: forEach\r
471 });\r
472 \r
473 // =========================================================================\r
474 // base2/Array2.js\r
475 // =========================================================================\r
476 \r
477 // The IArray module implements all Array methods.\r
478 // This module is not public but its methods are accessible through the Array2 object (below). \r
479 \r
480 var IArray = Module.extend({\r
481         combine: function(keys, values) {\r
482                 // combine two arrays to make a hash\r
483                 if (!values) values = keys;\r
484                 return this.reduce(keys, function(object, key, index) {\r
485                         object[key] = values[index];\r
486                         return object;\r
487                 }, {});\r
488         },\r
489         \r
490         copy: function(array) {\r
491                 return this.concat(array);\r
492         },\r
493         \r
494         contains: function(array, item) {\r
495                 return this.indexOf(array, item) != -1;\r
496         },\r
497         \r
498         forEach: forEach.Array,\r
499         \r
500         indexOf: function(array, item, fromIndex) {\r
501                 var length = array.length;\r
502                 if (fromIndex == null) {\r
503                         fromIndex = 0;\r
504                 } else if (fromIndex < 0) {\r
505                         fromIndex = Math.max(0, length + fromIndex);\r
506                 }\r
507                 for (var i = fromIndex; i < length; i++) {\r
508                         if (array[i] === item) return i;\r
509                 }\r
510                 return -1;\r
511         },\r
512         \r
513         insertAt: function(array, item, index) {\r
514                 this.splice(array, index, 0, item);\r
515                 return item;\r
516         },\r
517         \r
518         insertBefore: function(array, item, before) {\r
519                 var index = this.indexOf(array, before);\r
520                 if (index == -1) this.push(array, item);\r
521                 else this.splice(array, index, 0, item);\r
522                 return item;\r
523         },\r
524         \r
525         lastIndexOf: function(array, item, fromIndex) {\r
526                 var length = array.length;\r
527                 if (fromIndex == null) {\r
528                         fromIndex = length - 1;\r
529                 } else if (from < 0) {\r
530                         fromIndex = Math.max(0, length + fromIndex);\r
531                 }\r
532                 for (var i = fromIndex; i >= 0; i--) {\r
533                         if (array[i] === item) return i;\r
534                 }\r
535                 return -1;\r
536         },\r
537         \r
538         remove: function(array, item) {\r
539                 var index = this.indexOf(array, item);\r
540                 if (index != -1) this.removeAt(array, index);\r
541                 return item;\r
542         },\r
543         \r
544         removeAt: function(array, index) {\r
545                 var item = array[index];\r
546                 this.splice(array, index, 1);\r
547                 return item;\r
548         }\r
549 });\r
550 \r
551 IArray.prototype.forEach = function(block, context) {\r
552         forEach.Array(this, block, context);\r
553 };\r
554 \r
555 IArray.implement(Enumerable);\r
556 \r
557 forEach ("concat,join,pop,push,reverse,shift,slice,sort,splice,unshift".split(","), function(name) {\r
558         IArray[name] = function(array) {\r
559                 return Array.prototype[name].apply(array, slice(arguments, 1));\r
560         };\r
561 });\r
562 \r
563 // create a faux constructor that augments the built-in Array object\r
564 var Array2 = function() {\r
565         return IArray(this.constructor == IArray ? Array.apply(null, arguments) : arguments[0]);\r
566 };\r
567 // expose IArray.prototype so that it can be extended\r
568 Array2.prototype = IArray.prototype;\r
569 \r
570 forEach (IArray, function(method, name, proto) {\r
571         if (Array[name]) {\r
572                 IArray[name] = Array[name];\r
573                 delete IArray.prototype[name];\r
574         }\r
575         Array2[name] = IArray[name];\r
576 });\r
577 \r
578 // =========================================================================\r
579 // base2/Hash.js\r
580 // =========================================================================\r
581 \r
582 var HASH = "#" + Number(new Date);\r
583 var KEYS = HASH + "keys";\r
584 var VALUES = HASH + "values";\r
585 \r
586 var Hash = Base.extend({\r
587         constructor: function(values) {\r
588                 this[KEYS] = new Array2;\r
589                 this[VALUES] = {};\r
590                 this.merge(values);\r
591         },\r
592 \r
593         copy: function() {\r
594                 var copy = new this.constructor(this);\r
595                 Base.forEach (this, function(property, name) {\r
596                         if (typeof property != "function" && name.charAt(0) != "#") {\r
597                                 copy[name] = property;\r
598                         }\r
599                 });\r
600                 return copy;\r
601         },\r
602 \r
603         // ancient browsers throw an error when we use "in" as an operator \r
604         //  so we must create the function dynamically\r
605         exists: Legacy.exists || new Function("k", format("return('%1'+k)in this['%2']", HASH, VALUES)),\r
606 \r
607         fetch: function(key) {\r
608                 return this[VALUES][HASH + key];\r
609         },\r
610 \r
611         forEach: function(block, context) {\r
612                 forEach (this[KEYS], function(key) {\r
613                         block.call(context, this.fetch(key), key, this);\r
614                 }, this);\r
615         },\r
616 \r
617         keys: function(index, length) {\r
618                 var keys = this[KEYS] || new Array2;\r
619                 switch (arguments.length) {\r
620                         case 0: return keys.copy();\r
621                         case 1: return keys[index];\r
622                         default: return keys.slice(index, length);\r
623                 }\r
624         },\r
625 \r
626         merge: function(values) {\r
627                 forEach (arguments, function(values) {\r
628                         forEach (values, function(value, key) {\r
629                                 this.store(key, value);\r
630                         }, this);\r
631                 }, this);\r
632                 return this;\r
633         },\r
634 \r
635         remove: function(key) {\r
636                 var value = this.fetch(key);\r
637                 this[KEYS].remove(String(key));\r
638                 delete this[VALUES][HASH + key];\r
639                 return value;\r
640         },\r
641 \r
642         store: function(key, value) {\r
643                 if (arguments.length == 1) value = key;\r
644                 // only store the key for a new entry\r
645                 if (!this.exists(key)) {\r
646                         this[KEYS].push(String(key));\r
647                 }\r
648                 // create the new entry (or overwrite the old entry)\r
649                 this[VALUES][HASH + key] = value;\r
650                 return value;\r
651         },\r
652 \r
653         toString: function() {\r
654                 return String(this[KEYS]);\r
655         },\r
656 \r
657         union: function(values) {\r
658                 return this.merge.apply(this.copy(), arguments);\r
659         },\r
660 \r
661         values: function(index, length) {\r
662                 var values = this.map(K);\r
663                 switch (arguments.length) {\r
664                         case 0: return values;\r
665                         case 1: return values[index];\r
666                         default: return values.slice(index, length);\r
667                 }\r
668         }\r
669 });\r
670 \r
671 Hash.implement(Enumerable);\r
672 \r
673 // =========================================================================\r
674 // base2/Collection.js\r
675 // =========================================================================\r
676 \r
677 // A Hash that is more array-like (accessible by index).\r
678 \r
679 // Collection classes have a special (optional) property: Item\r
680 // The Item property points to a constructor function.\r
681 // Members of the collection must be an instance of Item.\r
682 // e.g.\r
683 //     var Dates = Collection.extend();                 // create a collection class\r
684 //     Dates.Item = Date;                               // only JavaScript Date objects allowed as members\r
685 //     var appointments = new Dates();                  // instantiate the class\r
686 //     appointments.add(appointmentId, new Date);       // add a date\r
687 //     appointments.add(appointmentId, "tomorrow");     // ERROR!\r
688 \r
689 // The static create() method is responsible for all construction of collection items.\r
690 // Instance methods that add new items (add, store, insertAt, replaceAt) pass *all* of their arguments\r
691 // to the static create() method. If you want to modify the way collection items are \r
692 // created then you only need to override this method for custom collections.\r
693 \r
694 var Collection = Hash.extend({\r
695         add: function(key, item) {\r
696                 // Duplicates not allowed using add().\r
697                 //  - but you can still overwrite entries using store()\r
698                 assert(!this.exists(key), "Duplicate key.");\r
699                 return this.store.apply(this, arguments);\r
700         },\r
701 \r
702         count: function() {\r
703                 return this[KEYS].length;\r
704         },\r
705 \r
706         indexOf: function(key) {\r
707                 return this[KEYS].indexOf(String(key));\r
708         },\r
709 \r
710         insertAt: function(index, key, item) {\r
711                 assert(!this.exists(key), "Duplicate key.");\r
712                 this[KEYS].insertAt(index, String(key));\r
713                 return this.store.apply(this, slice(arguments, 1));\r
714         },\r
715 \r
716         item: function(index) {\r
717                 return this.fetch(this[KEYS][index]);\r
718         },\r
719 \r
720         removeAt: function(index) {\r
721                 return this.remove(this[KEYS][index]);\r
722         },\r
723 \r
724         reverse: function() {\r
725                 this[KEYS].reverse();\r
726                 return this;\r
727         },\r
728 \r
729         sort: function(compare) {\r
730                 if (compare) {\r
731                         var self = this;\r
732                         this[KEYS].sort(function(key1, key2) {\r
733                                 return compare(self.fetch(key1), self.fetch(key2), key1, key2);\r
734                         });\r
735                 } else this[KEYS].sort();\r
736                 return this;\r
737         },\r
738 \r
739         store: function(key, item) {\r
740                 if (arguments.length == 1) item = key;\r
741                 item = this.constructor.create.apply(this.constructor, arguments);\r
742                 return this.base(key, item);\r
743         },\r
744 \r
745         storeAt: function(index, item) {\r
746                 //-dean: get rid of this?\r
747                 assert(index < this.count(), "Index out of bounds.");\r
748                 arguments[0] = this[KEYS][index];\r
749                 return this.store.apply(this, arguments);\r
750         }\r
751 }, {\r
752         Item: null, // if specified, all members of the Collection must be instances of Item\r
753         \r
754         create: function(key, item) {\r
755                 if (this.Item && !instanceOf(item, this.Item)) {\r
756                         item = new this.Item(key, item);\r
757                 }\r
758                 return item;\r
759         },\r
760         \r
761         extend: function(_instance, _static) {\r
762                 var klass = this.base(_instance);\r
763                 klass.create = this.create;\r
764                 extend(klass, _static);\r
765                 if (!klass.Item) {\r
766                         klass.Item = this.Item;\r
767                 } else if (typeof klass.Item != "function") {\r
768                         klass.Item = (this.Item || Base).extend(klass.Item);\r
769                 }\r
770                 if (typeof klass.init == "function") klass.init();\r
771                 return klass;\r
772         }\r
773 });\r
774 \r
775 // =========================================================================\r
776 // base2/RegGrp.js\r
777 // =========================================================================\r
778 \r
779 var RegGrp = Collection.extend({\r
780         constructor: function(values, flags) {\r
781                 this.base(values);\r
782                 if (typeof flags == "string") {\r
783                         this.global = /g/.test(flags);\r
784                         this.ignoreCase = /i/.test(flags);\r
785                 }\r
786         },\r
787 \r
788         global: true, // global is the default setting\r
789         ignoreCase: false,\r
790 \r
791         exec: function(string, replacement) {\r
792                 if (arguments.length == 1) {\r
793                         var keys = this[KEYS];\r
794                         var values = this[VALUES];\r
795                         replacement = function(match) {\r
796                                 if (!match) return "";\r
797                                 var offset = 1, i = 0;\r
798                                 // loop through the values\r
799                                 while (match = values[HASH + keys[i++]]) {\r
800                                         // do we have a result?\r
801                                         if (arguments[offset]) {\r
802                                                 var replacement = match.replacement;\r
803                                                 switch (typeof replacement) {\r
804                                                         case "function":\r
805                                                                 return replacement.apply(null, slice(arguments, offset));\r
806                                                         case "number":\r
807                                                                 return arguments[offset + replacement];\r
808                                                         default:\r
809                                                                 return replacement;\r
810                                                 }\r
811                                         // no? then skip over references to sub-expressions\r
812                                         } else offset += match.length + 1;\r
813                                 }\r
814                         };\r
815                 }\r
816                 var flags = (this.global ? "g" : "") + (this.ignoreCase ? "i" : "");\r
817                 return String(string).replace(new RegExp(this, flags), replacement);\r
818         },\r
819 \r
820         test: function(string) {\r
821                 return this.exec(string) != string;\r
822         },\r
823         \r
824         toString: function() {\r
825                 var length = 0;\r
826                 return "(" + this.map(function(item) {\r
827                         // fix back references\r
828                         var expression = String(item).replace(/\\(\d+)/g, function($, index) {\r
829                                 return "\\" + (1 + Number(index) + length);\r
830                         });\r
831                         length += item.length + 1;\r
832                         return expression;\r
833                 }).join(")|(") + ")";\r
834         }\r
835 }, {\r
836         IGNORE: "$0",\r
837         \r
838         init: function() {\r
839                 forEach ("add,exists,fetch,remove,store".split(","), function(name) {\r
840                         extend(this, name, function(expression) {\r
841                                 if (instanceOf(expression, RegExp)) {\r
842                                         expression = expression.source;\r
843                                 }\r
844                                 return base(this, arguments);\r
845                         });\r
846                 }, this.prototype);\r
847         }\r
848 });\r
849 \r
850 // =========================================================================\r
851 // base2/RegGrp/Item.js\r
852 // =========================================================================\r
853 \r
854 RegGrp.Item = Base.extend({\r
855         constructor: function(expression, replacement) {\r
856                 var ESCAPE = /\\./g;\r
857                 var STRING = /(['"])\1\+(.*)\+\1\1$/;\r
858         \r
859                 expression = instanceOf(expression, RegExp) ? expression.source : String(expression);\r
860                 \r
861                 if (typeof replacement == "number") replacement = String(replacement);\r
862                 else if (replacement == null) replacement = "";\r
863                 \r
864                 // count the number of sub-expressions\r
865                 //  - add one because each pattern is itself a sub-expression\r
866                 this.length = match(expression.replace(ESCAPE, "").replace(/\[[^\]]+\]/g, ""), /\(/g).length;\r
867                 \r
868                 // does the pattern use sub-expressions?\r
869                 if (typeof replacement == "string" && /\$(\d+)/.test(replacement)) {\r
870                         // a simple lookup? (e.g. "$2")\r
871                         if (/^\$\d+$/.test(replacement)) {\r
872                                 // store the index (used for fast retrieval of matched strings)\r
873                                 replacement = parseInt(replacement.slice(1));\r
874                         } else { // a complicated lookup (e.g. "Hello $2 $1")\r
875                                 // build a function to do the lookup\r
876                                 var i = this.length + 1;\r
877                                 var Q = /'/.test(replacement.replace(ESCAPE, "")) ? '"' : "'";\r
878                                 replacement = replacement.replace(/\n/g, "\\n").replace(/\r/g, "\\r").replace(/\$(\d+)/g, Q +\r
879                                         "+(arguments[$1]||" + Q+Q + ")+" + Q);\r
880                                 replacement = new Function("return " + Q + replacement.replace(STRING, "$1") + Q);\r
881                         }\r
882                 }\r
883                 this.replacement = replacement;\r
884                 this.toString = function() {\r
885                         return expression || "";\r
886                 };\r
887         },\r
888         \r
889         length: 0,\r
890         replacement: ""\r
891 });\r
892 \r
893 // =========================================================================\r
894 // base2/Namespace.js\r
895 // =========================================================================\r
896 \r
897 var Namespace = Base.extend({\r
898         constructor: function(_private, _public) {\r
899                 this.extend(_public);\r
900                 this.toString = function() {\r
901                         return format("[base2.%1]", this.name);\r
902                 };\r
903                 \r
904                 // initialise\r
905                 if (typeof this.init == "function") this.init();\r
906                 \r
907                 if (this.name != "base2") {\r
908                         this.namespace = format("var %1=base2.%1;", this.name);\r
909                 }\r
910                 \r
911                 var namespace = "var base=" + base + ";";\r
912                 var imports = ("base2,lang," + this.imports).split(",");\r
913                 _private.imports = Enumerable.reduce(imports, function(namespace, name) {\r
914                         if (base2[name]) namespace += base2[name].namespace;\r
915                         return namespace;\r
916                 }, namespace);\r
917                 \r
918                 var namespace = format("base2.%1=%1;", this.name);\r
919                 var exports = this.exports.split(",");\r
920                 _private.exports = Enumerable.reduce(exports, function(namespace, name) {\r
921                         if (name) {\r
922                                 this.namespace += format("var %2=%1.%2;", this.name, name);\r
923                                 namespace += format("if(!%1.%2)%1.%2=%2;base2.%2=%1.%2;", this.name, name);\r
924                         }\r
925                         return namespace;\r
926                 }, namespace, this);\r
927                 \r
928                 if (this.name != "base2") {\r
929                         base2.namespace += format("var %1=base2.%1;", this.name);\r
930                 }\r
931         },\r
932 \r
933         exports: "",\r
934         imports: "",\r
935         namespace: "",\r
936         name: ""\r
937 });\r
938 \r
939 base2 = new Namespace(this, {\r
940         name:    "base2",\r
941         version: "0.8 (alpha)",\r
942         exports: "Base,Abstract,Module,Enumerable,Array2,Hash,Collection,RegGrp,Namespace"\r
943 });\r
944 \r
945 base2.toString = function() {\r
946         return "[base2]";\r
947 };\r
948 \r
949 eval(this.exports);\r
950 \r
951 // =========================================================================\r
952 // base2/lang/namespace.js\r
953 // =========================================================================\r
954 \r
955 var lang = new Namespace(this, {\r
956         name:    "lang",\r
957         version: base2.version,\r
958         exports: "K,assert,assertType,assignID,copy,instanceOf,extend,format,forEach,match,rescape,slice,trim",\r
959         \r
960         init: function() {\r
961                 this.extend = extend;\r
962                 // add the Enumerable methods to the lang object\r
963                 forEach (Enumerable.prototype, function(method, name) {\r
964                         if (!Module[name]) {\r
965                                 this[name] = function() {\r
966                                         return Enumerable[name].apply(Enumerable, arguments);\r
967                                 };\r
968                                 this.exports += "," + name;\r
969                         }\r
970                 }, this);\r
971         }\r
972 });\r
973 \r
974 eval(this.exports);\r
975 \r
976 base2.namespace += lang.namespace;\r
977 \r
978 }; ////////////////////  END: CLOSURE  /////////////////////////////////////\r