Loading rico1 and rico3 files
[infodrom/rico3] / ricoClient / js / baselibs / glow.core.debug.js
1 /*      
2         Copyright 2009 British Broadcasting Corporation
3
4         Licensed under the Apache License, Version 2.0 (the "License");
5         you may not use this file except in compliance with the License.
6         You may obtain a copy of the License at
7
8            http://www.apache.org/licenses/LICENSE-2.0
9
10         Unless required by applicable law or agreed to in writing, software
11         distributed under the License is distributed on an "AS IS" BASIS,
12         WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13         See the License for the specific language governing permissions and
14         limitations under the License.
15 */
16 /**
17 @name glow
18 @namespace
19 @version 1.7.0 (2008-09-22)
20 @description The glow namespace and core library.
21
22     Includes common methods for running scripts onDomReady and user agent sniffing.
23 */
24 (function() {
25         /*
26         PrivateVar: moduleRegister
27                 Holds info on which modules are registered {name:true}
28         */
29         var moduleRegister = {glow: true},
30                 /*
31                 PrivateVar: regexEscape
32                         For escaping strings to go in regex
33                 */
34                 regexEscape = /([$^\\\/()|?+*\[\]{}.-])/g,
35                 /*
36                 PrivateVar: ua
37                         A lowercase representation of the user's useragent string
38                 */
39                 ua = navigator.userAgent.toLowerCase(),
40                 //glow version
41                 version = "1.7.0",
42                 // number of blockers blocking (when it's zero, we're ready)
43                 blockersActive = 0,
44                 /*
45                 PrivateMethod: domReadyQueue
46                         array of functions to call when dom is ready
47                 */
48                 domReadyQueue = [],
49                 domReadyQueueLen = 0,
50                 /*
51                 PrivateMethod: readyQueue
52                         array of functions to call when all ready blockers are unblocked
53                 */
54                 //we want to set isReady to true when this is first run
55                 readyQueue = [],
56                 readyQueueLen = 0,
57                 // stops two instances of 'runReadyQueue' fighting on the call stack
58                 processingReadyQueue = false,
59                 glow = {
60                         /**
61                         @name glow.VERSION
62                         @description Version of glow
63                                 This is in the format 1.2.3
64
65                         @type String
66
67                         @see <a href="/glow/docs/previous_versions.shtml#versionScheme">Glow's versioning scheme</a>
68                         */
69                         VERSION: version,
70
71                         /**
72                         @name glow.UID
73                         @description A unique ID for this instance of Glow
74
75                                 This will be used in glow-specific property names
76                                 that need to be unique to this instance of glow.
77
78                         @type String
79                         */
80                         UID: "glow" + Math.floor(Math.random() * (1<<30)),
81
82                         /**
83                         @name glow.isDomReady
84                         @description Is the DOM ready?
85
86                                 If glow is loaded after the page has loaded (by means other than Gloader)
87                                 this value should be set manually.
88
89                         @type Boolean
90                         */
91                         //check gloader to see if dom is already ready
92                         isDomReady: window.gloader && gloader.isReady,
93                         
94                         /**
95                         @name glow.isReady
96                         @description Is Glow ready?
97
98                                 Set to true when Glow is ready. This includes DOM ready,
99                                 a supported browser and any additional requirements. For example
100                                 Glow widgets will add the loading of their CSS file as a requirement.
101
102                         @type Boolean
103                         */
104                         //check gloader to see if dom is already ready
105                         isReady: window.gloader && gloader.isReady,
106
107                         /**
108                         @name glow.env
109                         @description Information about the browser / platform
110                         @type Object
111
112                         @example
113                                 if (glow.env.ie < 7) {
114                                         //this only runs in IE 6 and below
115                                 }
116                                 if (glow.env.gecko < 1.9) {
117                                         //this only runs in Gecko versions less than 1.9
118                                         //Wikipedia can be used to link engine versions to browser versions
119                                 }
120                         */
121                         /**
122                         @name glow.env.gecko
123                         @description Gecko version number to one decimal place (eg 1.9) or NaN
124                         @type Number
125                         */
126                         /**
127                         @name glow.env.ie
128                         @description IE version number or NaN
129                         @type Number
130                         */
131                         /**
132                         @name glow.env.opera
133                         @description Opera version (eg 8.02) or NaN
134                         @type Number
135                         */
136                         /**
137                         @name glow.env.webkit
138                         @description Webkit version number to one decimal place (eg 419.3) or NaN
139                         @type Number
140                         */
141                         /**
142                         @name glow.env.khtml
143                         @description KHTML version number to one decimal place or NaN
144                         @type Number
145                         */
146                         /**
147                         @name glow.env.standardsMode
148                         @description True if the browser reports itself to be in 'standards mode'
149                         @type Boolean
150                         */
151                         /**
152                         @name glow.env.version
153                         @description Browser version as a string. Includes non-numerical data, eg "1.8.1" or "7b"
154                         @type String
155                         */
156                         env: function(){
157                                 var nanArray = [0, NaN],
158                                         opera = (/opera[\s\/]([\w\.]+)/.exec(ua) || nanArray)[1],
159                                         ie = opera ? NaN : (/msie ([\w\.]+)/.exec(ua) || nanArray)[1],
160                                         gecko = (/rv:([\w\.]+).*gecko\//.exec(ua) || nanArray)[1],
161                                         webkit = (/applewebkit\/([\w\.]+)/.exec(ua) || nanArray)[1],
162                                         khtml = (/khtml\/([\w\.]+)/.exec(ua) || nanArray)[1],
163                                         toNum = parseFloat;
164
165                                 return {
166                                         gecko   : toNum(gecko),
167                                         ie      : toNum(ie),
168                                         opera   : toNum(opera),
169                                         webkit  : toNum(webkit),
170                                         khtml   : toNum(khtml),
171                                         version : ie || gecko || webkit || opera || khtml,
172                                         standardsMode : document.compatMode != "BackCompat" && (!ie || ie >= 6)
173                                 }
174                         }(),
175
176                         /**
177                         @name glow.module
178                         @private
179                         @function
180                         @description Registers a new module with the library, checking version numbers &amp; dependencies.
181
182                         @param {Object} meta
183                                 Object containing all of the following. This object is
184                                 compatible with gloader.module, hence some properties which seem
185                                 unnecessary here. This is all simplified by glow's module pattern.
186
187                         @param {String} meta.name Name of the module.
188                                 Eg. "glow.dom" or "glow.widgets.Panel"
189
190                         @param {String[]} meta.library Information about Glow.
191                                 This must be ["glow", "1.7.0"]. 1.7.0 should be
192                                 the version number of glow expected.
193
194                         @param {String[]} meta.depends The module's dependencies.
195                                 This must start ["glow", "1.7.0"], followed by modules
196                                 such as "glow.dom". 1.7.0 should be the version number
197                                 of glow expected.
198
199                         @param {Function} meta.builder The module's implementation.
200                                 A reference to glow will be passed in as the first parameter
201                                 to this function. Add to that object to create publicly
202                                 accessabile properties. Anything else in this function
203                                 will be private.
204
205                         @returns {Object} Glow
206
207                         @example
208                                 glow.module({
209                                         name: "glow.anim",
210                                         library: ["glow", "1.0.0"],
211                                         depends: ["glow", "1.0.0", "glow.dom"],
212                                         builder: function(glow) {
213                                                 glow.anim = {
214                                                         //...
215                                                 };
216                                         }
217                                 });
218                         */
219                         module: function(meta) {
220                                 var i = 2,
221                                         depends = meta.depends[0] || [],
222                                         dependsLen = depends.length,
223                                         name = meta.name,
224                                         objRef = window.glow; //holds the parent object for the new module
225
226                                 //check version number match core version
227                                 if (meta.library[1] != glow.VERSION) {
228                                         throw new Error("Cannot register " + name + ": Version mismatch");
229                                 }
230
231                                 //check dependencies loaded
232                                 if (depends[2]) {
233                                         for (; i < dependsLen; i++) {
234                                                 //check exists
235                                                 if (!moduleRegister[depends[i]]) {
236                                                         //check again ondomready to detect if modules are being included in wrong order
237                                                         throw new Error("Module " + depends[i] + " required before " + name);
238                                                 }
239                                         }
240                                 }
241
242                                 //create module
243                                 meta.builder(glow);
244                                 //register it as built
245                                 moduleRegister[name] = true;
246                                 return glow;
247                         },
248
249                         /**
250                         @name glow.ready
251                         @function
252                         @description Calls a function when the DOM had loaded and the browser is supported
253                         
254                                 "ready" also waits for glow's CSS file to load if it has been
255                                 requested.
256
257                         @param {Function} callback Function to call
258
259                         @returns {glow}
260
261                         @example
262                                 glow.ready(function() {
263                                         alert("DOM Ready!");
264                                 });
265                         */
266                         ready: function(f) {
267                                 //just run function if already ready
268                                 if (this.isReady) {
269                                         f();
270                                 } else {
271                                         readyQueue[readyQueueLen++] = f;
272                                 }
273                                 return this;
274                         },
275                         
276                         /**
277                         @name glow._readyBlockers
278                         @private
279                         @object
280                         @description A hash (by name) of blockers.
281                                 True if they are blocking, false if they've since unblocked
282                         */
283                         _readyBlockers: {},
284                         
285                         /**
286                         @name glow._addReadyBlock
287                         @private
288                         @function
289                         @description Adds a blocker to anything added via glow.ready.
290                                 Uncalled callbacks added via glow.ready will not fire until
291                                 this block is removed
292
293                         @param {String} name Name of blocker
294
295                         @returns {glow}
296
297                         @example
298                                 glow._addReadyBlock("widgetsCss");
299                                 
300                                 // when CSS is ready...
301                                 glow._removeReadyBlock("widgetsCss");
302                         */
303                         _addReadyBlock: function(name) {
304                                 if (name in glow._readyBlockers) {
305                                         throw new Error("Blocker '" + name +"' already exists");
306                                 }
307                                 glow._readyBlockers[name] = true;
308                                 glow.isReady = false;
309                                 blockersActive++;
310                                 return glow;
311                         },
312                         
313                         /**
314                         @name glow._removeReadyBlock
315                         @private
316                         @function
317                         @description Removes a ready blocker added via glow._addReadyBlock
318
319                         @param {String} name Name of blocker
320
321                         @returns {glow}
322
323                         @example
324                                 glow._addReadyBlock("widgetsCss");
325                                 
326                                 // when CSS is ready...
327                                 glow._removeReadyBlock("widgetsCss");
328                         */
329                         _removeReadyBlock: function(name) {
330                                 if (glow._readyBlockers[name]) {
331                                         glow._readyBlockers[name] = false;
332                                         blockersActive--;
333                                         // if we're out of blockers
334                                         if (!blockersActive) {
335                                                 // call our queue
336                                                 glow.isReady = true;
337                                                 runReadyQueue();
338                                         }
339                                 }
340                                 return glow;
341                         },
342
343                         /**
344                         @name glow.onDomReady
345                         @function
346                         @description Calls a function when / if the DOM is ready.
347
348                                 This function does not wait for glow's CSS to load, nor
349                                 does it block unsupported browsers. If you want these features,
350                                 use {@link glow.ready}
351
352                         @param {Function} callback Function to call
353
354                         @returns {glow}
355
356                         @exmaple
357                                 glow.onDomReady(function() {
358                                         alert("DOM Ready!");
359                                 });
360                         */
361                         onDomReady: function(f) {
362                                 //just run function if already ready
363                                 if (this.isDomReady) {
364                                         f();
365                                 } else {
366                                         domReadyQueue[domReadyQueueLen++] = f;
367                                 }
368                         },
369
370                         /**
371                         @name glow.lang
372                         @namespace
373                         @description Useful language functions.
374                         @see <a href="../furtherinfo/glow/glow.lang.shtml">Using glow.lang.clone</a>
375                         */
376                         lang: {
377                                 /**
378                                 @name glow.lang.trim
379                                 @function
380                                 @description Removes leading and trailing whitespace from a string
381
382                                 @param {String} str String to trim
383
384                                 @returns {String}
385
386                                         String without leading and trailing whitespace
387
388                                 @example
389                                         glow.lang.trim("  Hello World  "); // "Hello World"
390                                 */
391                                 trim: function(sStr) {
392                                         //this optimisation from http://blog.stevenlevithan.com/archives/faster-trim-javascript
393                                         return sStr.replace(/^\s*((?:[\S\s]*\S)?)\s*$/, '$1');
394                                 },
395
396                                 /**
397                                 @name glow.lang.toArray
398                                 @function
399                                 @description Converts an array-like object to a real array
400
401                                 @param {Object} arrayLike Any array-like object
402
403                                 @returns {Array}
404
405                                 @example
406                                         var a = glow.lang.toArray(glow.dom.get("a"));
407                                 */
408                                 toArray: function(aArrayLike) {
409                                         if (aArrayLike.constructor == Array) {
410                                                 return aArrayLike;
411                                         }
412                                         //use array.slice if not IE? Could be faster
413                                         var r = [], i=0, len = aArrayLike.length;
414                                         for (; i < len; i++) {
415                                                 r[i] = aArrayLike[i];
416                                         }
417                                         return r;
418                                 },
419
420                                 /**
421                                 @name glow.lang.apply
422                                 @function
423                                 @description Copies properties from one object to another
424
425                                 @param {Object} destination Destination object
426
427                                 @param {Object} source Properties of this object will be copied onto the destination
428
429                                 @returns {Object}
430
431                                 @example
432                                         var obj = glow.lang.apply({foo: "hello", bar: "world"}, {bar: "everyone"});
433                                         //results in {foo: "hello", bar: "everyone"}
434                                 */
435                                 apply: function(destination, source) {
436                                         for (var i in source) {
437                                                 destination[i] = source[i];
438                                         }
439                                         return destination;
440                                 },
441
442                                 /**
443                                 @name glow.lang.map
444                                 @function
445                                 @description Runs a function for each element of an array and returns an array of the results
446
447                                 @param {Array} array Array to loop over
448                                 @param {Function} callback The function to run on each element. This function is passed three params, the array item, its index and the source array.
449                                 @param {Object} [context] The context for the callback function (the array is used if not specified)
450
451                                 @returns {Array}
452
453                                         Array containing one element for each value returned from the callback
454
455                                 @example
456                                         var weekdays = ["Monday", "Tuesday", "Wednesday", "Thursday", "Friday"]
457                                         var weekdaysAbbr = glow.lang.map(weekdays, function (day) {
458                                                 return day.slice(0, 3).toLowerCase();
459                                         });
460                                         // returns ["mon", "tue", "wed", "thu", "fri"]
461                                 */
462                                 map: function (arr, callback, context) {
463                                         if (Array.prototype.map) { return Array.prototype.map.call(arr, callback, context || arr); }
464                                         if (! callback.call) { throw new TypeError(); }
465
466                                         var len = arr.length,
467                                                 res = [],
468                                                 thisp = context || arr,
469                                                 i = 0;
470
471                                         for (; i < len; i++) {
472                                                 if (i in arr) {
473                                                         res[i] = callback.call(thisp, arr[i], i, arr);
474                                                 }
475                                         }
476                                         return res;
477                                 },
478
479                                 /**
480                                 @name glow.lang.replace
481                                 @function
482                                 @description Makes a replacement in a string.
483
484                                         Has the same interface as the builtin
485                                         String.prototype.replace method, but takes the input
486                                         string as the first parameter. In general the native string
487                                         method should be used unless you need to pass a function as the
488                                         second parameter, as this method will work accross our
489                                         supported browsers.
490
491                                 @param {String} str Input string
492
493                                 @param {String | RegExp} pattern String or regular expression to match against
494
495                                 @param {String | Function} replacement String to make replacements with, or a function to generate the replacements
496
497                                 @returns {String}
498                                         A new string with the replacement(s) made
499
500                                 @example
501                                         var myDays = '1 3 6';
502                                         var dayNames = glow.lang.replace(myDays, /(\d)/, function (day) {
503                                                 return " MTWTFSS".charAt(day - 1);
504                                         });
505                                         // dayNames now contains "M W S"
506                                 */
507                                 replace: (function () {
508                                         var replaceBroken = "g".replace(/g/, function () { return 'l'; }) != 'l',
509                                                 def = String.prototype.replace;
510                                         return function (inputString, re, replaceWith) {
511                                                 var pos, match, last, buf;
512                                                 if (! replaceBroken || typeof(replaceWith) != 'function') {
513                                                         return def.call(inputString, re, replaceWith);
514                                                 }
515                                                 if (! (re instanceof RegExp)) {
516                                                         pos = inputString.indexOf(re);
517                                                         return pos == -1 ?
518                                                                 inputString :
519                                                                 def.call(inputString, re, replaceWith.call(null, re, pos, inputString));
520                                                 }
521                                                 buf = [];
522                                                 last = re.lastIndex = 0;
523                                                 while ((match = re.exec(inputString)) != null) {
524                                                         pos = match.index;
525                                                         buf[buf.length] = inputString.slice(last, pos);
526                                                         buf[buf.length] = replaceWith.apply(null, match);
527                                                         if (re.global) {
528                                                                 last = re.lastIndex;
529                                                         } else {
530                                                                 last = pos + match[0].length;
531                                                                 break;
532                                                         }
533                                                 }
534                                                 buf[buf.length] = inputString.slice(last);
535                                                 return buf.join("");
536                                         };
537                                 })(),
538
539                                 /**
540                                 @name glow.lang.interpolate
541                                 @function
542                                 @description Replaces placeholders in a string with data from an object
543
544                                 @param {String} template The string containing {placeholders}
545                                 @param {Object} data Object containing the data to be merged in to the template
546                                         <p>The object can contain nested data objects and arrays, with nested object properties and array elements are accessed using dot notation. eg foo.bar or foo.0.</p>
547                                         <p>The data labels in the object cannot contain characters used in the template delimiters, so if the data must be allowed to contain the default { and } delimiters, the delimters must be changed using the option below.</p>
548                                 @param {Object} opts Options object
549                                         @param {String} [opts.delimiter="{}"] Alternative label delimiter(s) for the template
550                                                 The first character supplied will be the opening delimiter, and the second the closing. If only one character is supplied, it will be used for both ends.
551                                         @param {Boolean} [opts.escapeHtml=false] Escape any special html characters found in the data object
552                                                 Use this to safely inject data from the user into an HTML template. The glow.dom module
553                                                 must be present for this feature to work (an error will be thrown otherwise).
554
555                                 @returns {String}
556
557                                 @example
558                                         var data = {
559                                                 name: "Domino",
560                                                 colours: ["black", "white"],
561                                                 family: {
562                                                         mum: "Spot",
563                                                         dad: "Patch",
564                                                         siblings: []
565                                                 }
566                                         };
567                                         var template = "My cat's name is {name}. His colours are {colours.0} & {colours.1}. His mum is {family.mum}, his dad is {family.dad} and he has {family.siblings.length} brothers or sisters.";
568                                         var result = glow.lang.interpolate(template, data);
569                                         // result == "My cat's name is Domino. His colours are black & white. His mum is Spot, his dad is Patch and he has 0 brothers or sisters."
570                                 
571                                 @example
572                                         var data = {
573                                                 name: 'Haxors!!1 <script src="hackhackhack.js"></script>'
574                                         }
575                                         var template = '<p>Hello, my name is {name}</p>';
576                                         var result = glow.lang.interpolate(template, data, {
577                                                 escapeHtml: true
578                                         });
579                                         // result == '<p>Hello, my name is Haxors!!1 &lt;script src="hackhackhack.js"&gt;&lt;/script&gt;</p>'
580                                 */
581                                 interpolate : function (template, data, opts) {
582                                         var placeHolderRx,
583                                                 leftDelimiter,
584                                                 rightDelimiter,
585                                                 // div used for html escaping
586                                                 div;
587
588                                         opts = opts || {};
589                                         
590                                         // make sure the dom module is around
591                                         if (opts.escapeHtml) {
592                                                 if (!glow.dom) { throw new Error('glow.lang.interpolate - glow.dom is needed for escapeHtml'); }
593                                                 div = glow.dom.create('<div></div>');
594                                         }
595
596                                         if (opts.delimiter == undefined) {
597                                                 placeHolderRx = /\{[^{}]+\}/g;
598                                         } else {
599                                                 leftDelimiter = opts.delimiter.substr(0, 1).replace(regexEscape, "\\$1");
600                                                 rightDelimiter = opts.delimiter.substr(1, 1).replace(regexEscape, "\\$1") || leftDelimiter;
601                                                 placeHolderRx = new RegExp(leftDelimiter + "[^" + leftDelimiter + rightDelimiter + "]+" + rightDelimiter, "g");
602                                         }
603
604                                         return template.replace(placeHolderRx, function (placeholder) {
605
606                                                 var key = placeholder.slice(1, -1),
607                                                         keyParts = key.split("."),
608                                                         val,
609                                                         i = 0,
610                                                         len = keyParts.length;
611                                                 
612                                                 if (key in data) {
613                                                         // need to be backwards compatible with "flattened" data.
614                                                         val = data[key]; 
615                                                 } else {
616                                                         // look up the chain
617                                                         val = data;
618                                                         for (; i < len; i++) {
619                                                                 if (keyParts[i] in val) {
620                                                                         val = val[ keyParts[i] ];
621                                                                 } else {
622                                                                         return placeholder;
623                                                                 }
624                                                         }
625                                                 }
626                                                 
627                                                 if (opts.escapeHtml) {
628                                                         val = div.text(val).html();
629                                                 }
630                                                 return val;
631                                         });
632                                 },
633                                 /**
634                                 @name glow.lang.hasOwnProperty
635                                 @function
636                                 @description Cross-browser implementation
637                                 @deprecated
638
639                                   Safari 1.3 doesn't support
640                                   <a href="http://developer.mozilla.org/en/docs/Core_JavaScript_1.5_Reference:Global_Objects:Object:hasOwnProperty">
641                                     Object.hasOwnProperty
642                                   </a>, use this method instead.
643
644                                 @param {Object} obj The object to check
645
646                                 @param {String} property Property name
647
648                                 @returns {Boolean}
649
650                                         Returns false if a property doesn't exist in an object, or it
651                                         was inherited from the object's prototype. Otherwise, returns
652                                         true
653                                 */
654                                 hasOwnProperty: {}.hasOwnProperty ? //not supported in Safari 1.3
655                                         function(obj, prop) {
656                                                 return obj.hasOwnProperty(prop);
657                                         } :
658                                         function(obj, prop) {
659                                                 var propVal = obj[prop], //value of the property
660                                                         objProto = obj.__proto__, //prototype of obj
661                                                         protoVal = objProto ? objProto[prop] : {}; //prototype val
662                                                 if (propVal !== protoVal) {
663                                                         return true;
664                                                 }
665                                                 //try changing prototype and see if obj reacts
666                                                 var restoreProtoVal = glow.lang.hasOwnProperty(objProto, prop),
667                                                         tempObjProtoVal = objProto[prop] = {},
668                                                         hasOwn = (obj[prop] !== tempObjProtoVal);
669
670                                                 delete objProto[prop];
671                                                 if (restoreProtoVal) {
672                                                         objProto[name] = tempObjProtoVal;
673                                                 }
674                                                 return hasOwn;
675                                         },
676
677                                 /**
678                                 @name glow.lang.extend
679                                 @function
680                                 @description Copies the prototype of one object to another.
681
682                                         The 'subclass' can also access the 'base class' via subclass.base
683
684                                 @param {Function} sub Class which inherits properties.
685
686                                 @param {Function} base Class to inherit from.
687
688                                 @param {Object} additionalProperties An object of properties and methods to add to the subclass.
689
690                                 @example
691                                         function MyClass(arg) {
692                                                 this.prop = arg;
693                                         }
694                                         MyClass.prototype = {
695                                                 showProp: function() { alert(this.prop); }
696                                         };
697                                         function MyOtherClass(arg) {
698                                                 //call the base class's constructor
699                                                 arguments.callee.base.apply(this, arguments);
700                                         }
701                                         glow.lang.extend(MyOtherClass, MyClass, {
702                                                 setProp: function(newProp) { this.prop = newProp; }
703                                         });
704
705                                         var test = new MyOtherClass("hello");
706                                         test.showProp(); // alerts "hello"
707                                         test.setProp("world");
708                                         test.showProp(); // alerts "world"
709                                  *
710                                  */
711                                 extend: function(sub, base, additionalProperties) {
712                                         var f = function () {}, p;
713                                         f.prototype = base.prototype;
714                                         p = new f();
715                                         sub.prototype = p;
716                                         p.constructor = sub;
717                                         sub.base = base;
718                                         if (additionalProperties) {
719                                                 glow.lang.apply(sub.prototype, additionalProperties);
720                                         }
721                                 },
722
723                                 /**
724                                 @name glow.lang.clone
725                                 @function
726                                 @description Deep clones an object / array
727
728                                 @param {Object} Data Object to clone
729
730                                 @returns {Object}
731
732                                 @example
733                                         var firstObj = { name: "Bob", secondNames: ["is","your","uncle"] };
734                                         var clonedObj = glow.lang.clone( firstObj );
735                                 */
736                                 clone: function( obj ) {
737                                         var index, _index, tmp;
738                                         obj = obj.valueOf();
739                                         if ( typeof obj !== 'object' ) {
740                                                 return obj;
741                                         } else {
742                                                 if ( obj[0] || obj.concat ) {
743                                                         tmp = [ ];
744                                                         index = obj.length;
745                                                         while(index--) {
746                                                                 tmp[index] = arguments.callee( obj[index] );
747                                                         }
748                                         } else {
749                                                         tmp = { };
750                                                         for ( index in obj ) {
751                                                                 tmp[index] = arguments.callee( obj[index] );
752                                                         }
753                                                 }
754                                         return tmp;
755                                         }
756
757                                 }
758                         }
759                 },
760                 env = glow.env,
761                 d = document;
762         
763         //dom ready stuff
764         //run queued ready functions when DOM is ready
765         
766         function runDomReadyQueue() {
767                 glow.isDomReady = true;
768                 // run all functions in the array
769                 for (var i = 0; i < domReadyQueueLen; i++) {
770                         domReadyQueue[i]();
771                 }
772         }
773         
774         function runReadyQueue() {
775                 // if we're already processing the queue, just exit, the other instance will take care of it
776                 if (processingReadyQueue) return;
777                 processingReadyQueue = true;
778                 for (var i = 0; i < readyQueueLen;) {
779                         readyQueue[i]();
780                         i++;
781                         // check if the previous function has created a blocker
782                         if (blockersActive) {
783                                 break;
784                         }
785                 }
786                 // take items off the ready queue that have processed
787                 readyQueue = readyQueue.slice(i);
788                 // update len
789                 readyQueueLen = readyQueueLen - i;
790                 processingReadyQueue = false;
791         }
792         
793         (function(){
794                 //don't do this stuff if the dom is already ready
795                 if (glow.isDomReady) { return; }
796                 
797                 glow._addReadyBlock("glow_domReady");
798                 if (env.ie) {
799                         if (typeof window.frameElement != 'undefined') {
800                                 // we can't use doScroll if we're in an iframe...
801                                 d.attachEvent("onreadystatechange", function(){
802                                         if (d.readyState == "complete") {
803                                                 d.detachEvent("onreadystatechange", arguments.callee);
804                                                 runDomReadyQueue();
805                                                 glow._removeReadyBlock("glow_domReady");
806                                         }
807                                 });
808                         } else {
809                                 // polling for no errors
810                                 (function () {
811                                         try {
812                                                 // throws errors until after ondocumentready
813                                                 d.documentElement.doScroll('left');
814                                         } catch (e) {
815                                                 setTimeout(arguments.callee, 0);
816                                                 return;
817                                         }
818                                         // no errors, fire
819                                         runDomReadyQueue();
820                                         glow._removeReadyBlock("glow_domReady");
821                                 })();
822                         }
823                 } else if (glow.env.webkit < 525.13 && typeof d.readyState != 'undefined') {
824                         var f = function(){
825                                 if ( /loaded|complete/.test(d.readyState) ) {
826                                         runDomReadyQueue();
827                                         glow._removeReadyBlock("glow_domReady");
828                                 } else {
829                                         setTimeout(f, 0);
830                                 }
831                         };
832                         f();
833                 } else {
834                         var callback = function () {
835                                 if (callback.fired) { return; }
836                                 callback.fired = true;
837                                 runDomReadyQueue();
838                                 glow._removeReadyBlock("glow_domReady");
839                         };
840                         if (d.addEventListener) {
841                                 d.addEventListener("DOMContentLoaded", callback, false);
842                         }
843                         var oldOnload = window.onload;
844                         window.onload = function () {
845                                 if (oldOnload) { oldOnload(); }
846                                 callback();
847                         };
848                 }
849         })();
850
851         /**
852         @name glow.isSupported
853         @description Set to true in supported user agents
854                 This will read false in 'level 2' browsers in BBC's Browser Support Guidelines
855         @type Boolean
856
857         @see <a href="http://www.bbc.co.uk/guidelines/newmedia/technical/browser_support.shtml">BBC's Browser Support Guidelines</a>
858         */
859         // TODO: for v2 we should switch this to 'notSupported' as it's a blacklist
860         glow.isSupported = !(
861                 //here are the browsers we don't support
862                 env.ie < 6 ||
863                 (env.gecko < 1.9 && !/^1\.8\.1/.test(env.version)) ||
864                 env.opera < 9 ||
865                 env.webkit < 412
866         );
867         // block 'ready' if browser isn't supported
868         if (!glow.isSupported) {
869                 glow._addReadyBlock("glow_browserSupport");
870         }
871
872         if (window.gloader) {
873                 gloader.library({
874                         name: "glow",
875                         version: "1.7.0",
876                         builder: function () {
877                                 return glow;
878                         }
879                 });
880         } else if (window.glow) {
881                 throw new Error("Glow global object already exists");
882         } else {
883                 window.glow = glow;
884         }
885
886         // this helps IE cache background images
887         if (glow.ie) {
888                 try {
889                         document.execCommand("BackgroundImageCache", false, true);
890                 } catch(e) {}
891         }
892 })();
893 /*@cc_on @*/
894 /*@if (@_jscript_version > 5.5)@*/
895 /**
896 @name glow.i18n
897 @namespace
898 @description Internationalisation Module.
899 @requires glow
900 @see <a href="../furtherinfo/i18n/index.shtml">Using glow.i18n</a>
901 */
902 (window.gloader || glow).module({
903         name: "glow.i18n",
904         library: ["glow", "1.7.0"],
905         depends: [["glow", "1.7.0"]],
906         builder: function(glow) {
907                 var self;
908                 
909                 // Regexes for judging subtag types in the 'L'anguage, 'S'cript, 'R'egion, 'V'ariant form
910                 // lv is a match for a subtag that can be either 'L'anguage or 'V'ariant
911                 // See full explanation of tags and subtags at the end of the file
912                 var subtagRegexes = {
913                                 l  : /^[a-z]$/,
914                                 lv : /^[a-z]{2,3}$/,
915                                 s  : /^[A-Z][a-z]{3}$/,
916                                 r  : /^[A-Z]{2}|[0-9]{3}$/,
917                                 v  : /^[a-z0-9]{4,}$/
918                         };
919
920                 // Bit masks for tag signature in the 'L'anguage, 'S'cript, 'R'egion, 'V'ariant form
921                 // See full explanation of tags and subtags at the end of the file
922                 var L    = 1,
923                         S    = 2,
924                         R    = 4,
925                         V    = 8,
926                         LSRV = L + S + R + V,
927                         LRV  = L     + R + V,
928                         LSV  = L + S     + V,
929                         LV   = L         + V,
930                         LSR  = L + S + R    ,
931                         LR   = L     + R    ,
932                         LS   = L + S;
933         
934                 // Base structures for tag parsing using 'L'anguage, 'S'cript, 'R'egion, 'V'ariant form
935                 // See full explanation of tags and subtags at the end of the file
936                 var masks   = {l: L, s: S, r: R, v: V}, // map subtag types to masks
937                         subtags = ['l', 's', 'r', 'v'],         // in order
938                         labels  = {l: 0, s: 1, r: 2, v: 3}; // reverse look up for subtags
939
940                 // holds the actual local pack data
941                 var localePacks = {};
942
943                 // holds the main index of the pack / module structure
944                 var moduleStructure = {};
945
946                 // the currently selected locale
947                 // comes from html lang if it exists and is valid, defaults to 'en'
948                 var currentLocale = parseTag(document.documentElement.lang || 'en') || parseTag('en');
949                 
950                 // Determine what, if any, type of subtag this is by regex matching
951                 function getSubtagPattern( subtag ) {
952                         for (var pattern in subtagRegexes) { // These regexes are mutually exclusive, so order is immaterial
953                                 if (subtagRegexes[pattern].test(subtag)) {
954                                         return pattern;
955                                 }
956                         }
957         
958                         return "";
959                 }
960
961                 // Parse the given tag and return a queryable data set using 'L'anguage, 'S'cript, 'R'egion, 'V'ariant form
962                 // See full explanation of tags and subtags at the end of the file
963                 // if the tag is valid, returns the internal representation of a parsed locale tag
964                 // canonical is the valid canonical form of the tag eg "en-GB", mask is a bitmask indicating which subtag types are present and subtags is an object containing the actual subtags
965                 function parseTag( tag ) {
966                         if (!tag.split) {
967                                 tag = "";
968                         }
969                 
970                         var parts = tag.split("-"), // get the subtags
971                                 len = parts.length,
972                                 canon = [],                             // will hold the subtags of the canonical tag
973                                 matchedSubtags = {l: "", s:"", r:"", v:""},
974                                 start = 0,
975                                 i = start,
976                                 mask = 0,
977                                 subtag,
978                                 label;
979                 
980                         for (var j = 0, jlen = subtags.length; j < jlen; j++) { // order of subtag match is important
981                                 i = start;
982                                 subtag = subtags[j];
983                                 label = labels[subtag];
984         
985                                 while ((getSubtagPattern(parts[i]).indexOf(subtag) == -1) && (i < len)) { // indexOf allows for partial match of 'lv' regex
986                                         i++; // if no match, move on
987                                 }
988                                 
989                                 if (i < len) { // if a match is found, store it and continue with the next subtag from this point
990                                         canon[label] = parts[i];
991                                         mask += masks[subtag];
992                                         matchedSubtags[subtag] = parts[i];
993                                         parts[i] = "*";
994                                         start = i;
995                                 }
996                         }
997                         
998                         // put it all back together as a string
999                         var canonical = canon.join("-").replace(/-+/g, "-");
1000                         
1001                         if ((canonical == "") || (canonical.substring(0, 1) == "-")) { // this means there is no language subtag, so we must fail the parse
1002                                 return false;
1003                         }
1004                         else {
1005                                 return {canonical: canonical, mask: mask, subtags: matchedSubtags};
1006                         }
1007                 }               
1008                 
1009                 // For a given tag, and mask for a subset of it, return the subset tag using 'L'anguage, 'S'cript, 'R'egion, 'V'ariant form
1010                 // eg for 'en-GB-scouse' and an LR mask, return 'en-GB'
1011                 // See full explanation of tags and subtags at the end of the file
1012                 // ** note the bitwise operations **
1013                 function getSubsetTag( parsed, test, mask ) {
1014                         var subset;
1015                         
1016                         // this test (bitwise because the operands are bitmasks) determines that mask has no bits set that parsed.mask does not
1017                         // this is to make sure that the tag being generated is genuinely a subset of the main tag
1018                         if ((mask & ~parsed.mask) == 0) {
1019                                 subset = parsed.subtags["l"]; // there must be a language subtag, because this tags passed the parse
1020                                 
1021                                 if (S & mask) {
1022                                         subset = subset + "-" + parsed.subtags["s"];
1023                                 }
1024                                 if (R & mask) {
1025                                         subset = subset + "-" + parsed.subtags["r"];
1026                                 }
1027                                 if (V & mask) {
1028                                         subset = subset + "-" + parsed.subtags["v"];
1029                                 }
1030         
1031                                 if (test(subset)) {
1032                                         return subset;
1033                                 }
1034                         }
1035                 
1036                         return false;
1037                 }
1038
1039                 // Performs the generic tag negotiation process
1040                 // The test is a function that performs an additional check to see if the subset tag currently being
1041                 // checked should be used, normally based on the presence of appropriate data in the locale packs
1042                 // parsed is the parsed locale tag, as returned by parseTag
1043                 // testFn is the function passed to getSubsetTag, used to determine if a given subste tag is a negotiated hit
1044                 // successFn, failFn are the functions to be run when the negotiation result is known
1045                 function negotiate( parsed, testFn, successFn, failFn ) {
1046                         var subset;
1047                                         
1048                         switch(parsed.mask) { // NOTE the breaks are conditional because it might be OK for all the cases to execute - need to move on to the next case if the "if" fails
1049                                 case LRV:
1050                                         if ((subset = getSubsetTag(parsed, testFn, LRV ))) {
1051                                                 break;
1052                                         }
1053                                 case LR:
1054                                         if ((subset = getSubsetTag(parsed, testFn, LR  ))) {
1055                                                 break;
1056                                         }
1057                                 case LSRV:
1058                                         if ((subset = getSubsetTag(parsed, testFn, LSRV))) {
1059                                                 break;
1060                                         }
1061                                 case LSR:
1062                                         if ((subset = getSubsetTag(parsed, testFn, LSR ))) {
1063                                                 break;
1064                                         }
1065                                 case LSV:
1066                                         if ((subset = getSubsetTag(parsed, testFn, LSV ))) {
1067                                                 break;
1068                                         }
1069                                 case LS:
1070                                         if ((subset = getSubsetTag(parsed, testFn, LS  ))) {
1071                                                 break;
1072                                         }
1073                                 case LV:
1074                                         if ((subset = getSubsetTag(parsed, testFn, LV  ))) {
1075                                                 break;
1076                                         }
1077                                 case L:
1078                                         if ((subset = getSubsetTag(parsed, testFn, L   ))) {
1079                                                 break;
1080                                         }
1081                                 default:
1082                                         if (testFn('en')) {
1083                                                 subset = 'en';
1084                                         }
1085                                         else {
1086                                                 subset = null;
1087                                         }
1088                         }
1089
1090                         if (subset == null) {
1091                                 failFn();
1092                         } 
1093                         else {
1094                                 successFn(subset);
1095                         }
1096                 }
1097         
1098                 /**
1099                 @name glow.i18n.setLocale
1100                 @function
1101                 @description Sets the locale to a new one, stacking up the old one for later retreival.
1102                 
1103                         Has no effect if the newLocaleTag is invalid.
1104                 
1105                 @param {String} newLocaleTag The new locale tag to be set.
1106
1107                 @returns this
1108                 
1109                 @example
1110                         // assume locale is "en-GB" first
1111                         glow.i18n.setLocale("cy-GB");
1112                         // locale is now "cy-GB" with "en-GB" stacked up
1113                 */
1114                 function setLocale( newLocaleTag ) {
1115                         var old = currentLocale,
1116                                 parsed = parseTag(newLocaleTag);
1117
1118                         if (parsed) {
1119                                 currentLocale = parsed;
1120                                 currentLocale.next = old;
1121                         }
1122                         
1123                         return self;
1124                 }
1125         
1126                 /**
1127                 @name glow.i18n.revertLocale
1128                 @function
1129                 @description Reverts the locale to the one used immediately prior to the current one.
1130                 
1131                         Has no effect if the current locale was the first set (ie if no new locales have been set,
1132                         or if the locale has been reverted all the way back to the beginning).
1133
1134                 @returns this
1135                 
1136                 @example
1137                         // assume locale is "en-GB" first
1138                         glow.i18n.setLocale("cy-GB");
1139                         // locale is now "cy-GB" with "en-GB" stacked up
1140                         glow.i18n.revertLocale();
1141                         // locale is now back to "en-GB"
1142                 */
1143                 function revertLocale() {
1144                         currentLocale = currentLocale.next || currentLocale;
1145                         
1146                         return self;            
1147                 }
1148                 
1149                 /**
1150                 @name glow.i18n.getLocale
1151                 @function
1152                 @description Returns the tag of the current locale in canonical form.
1153
1154                 @returns {String}
1155
1156                 @example
1157                         loc = glow.i18n.getLocale(); // returns the current locale eg "en-GB"
1158
1159                         glow.i18n.setLocale("cy-GB");
1160                         loc = glow.i18n.getLocale(); // now returns "cy
1161
1162                         glow.i18n.setLocale("en-ignoredsubtag-US");
1163                         loc = glow.i18n.getLocale(); // now returns "en-US", which is the canonical form
1164                 */
1165                 function getLocale() {
1166                         return currentLocale.canonical;
1167                 }
1168                 
1169                 /**
1170                 @name glow.i18n.addLocaleModule
1171                 @function
1172                 @description Stores the given data against the given module on the given locale.
1173                         Creates any local packs and / or moduels that dio not already exist.
1174                         Adds the given data to any that do.
1175
1176                 @param {String} moduleName The name of the module to be created/added to.
1177                 @param {String} localeTag The locale tag to add the module to.
1178                 @param {Object} data The data of the module in key : value form.
1179
1180                 @returns this
1181                 
1182                 @example
1183                         // assume locale is "en-GB" first
1184                         glow.i18n.setLocale("cy-nonsense-GB");
1185                         var newLocale = glow.i18n.getLocale(); // returns "cy-GB"
1186                 */
1187                 function addLocaleModule( moduleName, localeTag, data ) {
1188                         var tag = parseTag(localeTag),
1189                                 pack,
1190                                 module,
1191                                 structure;
1192                         
1193                         if (tag) {
1194                                 pack      = localePacks[tag.canonical]  = localePacks[tag.canonical]  || {};
1195                                 module    = pack[moduleName]            = pack[moduleName]            || {};
1196                                 structure = moduleStructure[moduleName] = moduleStructure[moduleName] || {};
1197                                 
1198                                 for (var key in data) { // not using glow.lang.apply to avoid two loops
1199                                         module[key] = data[key];
1200                                         structure[key] = 1;
1201                                 }
1202                         }
1203                         
1204                         return self;
1205                 }
1206         
1207                 /**
1208                 @name glow.i18n.getLocaleModule
1209                 @function
1210                 @description Retreives an object whose keys are every label that can have a value (via tag negotiation) associated with it, and whose values are the associated label values.
1211
1212                 @param {String} moduleName The name of the module retreived.
1213                 @param {Object} [opts] Options object
1214                         @param {String} [opts.locale] On override locale to use instaed of the system locale.
1215
1216                 @returns {Object}
1217                 */
1218                 function getLocaleModule( moduleName, opts ) {
1219                         var module = {},
1220                                 options = opts || {},
1221                                 structure = moduleStructure[moduleName] || {},
1222                                 localeTag = currentLocale,
1223                                 tag,
1224                                 label;
1225
1226                         // define the customisers for negotiate outside the loop
1227                         // oddly, the loop control variable *is* correclty accessed, becasue of var scoping and the synchronous function calls
1228                         function test( nTag ) {
1229                                 if (localePacks[nTag] && localePacks[nTag][moduleName] && localePacks[nTag][moduleName][label]) {
1230                                         return true;
1231                                 }
1232                                 else {
1233                                         return false;
1234                                 }
1235                         }
1236                         
1237                         function success( sTag ) {
1238                                 module[label] = localePacks[sTag][moduleName][label];
1239                         }
1240                         
1241                         function fail() {
1242                                 module[label] = "[Error! No " + moduleName + "." + label + " on " + localeTag.canonical + "]";
1243                         }
1244                         
1245                         if (options.locale != undefined) {
1246                                 tag = parseTag(options.locale);
1247                                 
1248                                 if (tag) {
1249                                         localeTag = tag;
1250                                 }
1251                         }
1252
1253                         for (label in structure) {
1254                                 negotiate(localeTag, test, success, fail);
1255                         }
1256
1257                         return module;
1258                 }
1259                 
1260                 /**
1261                 @name glow.i18n.addLocalePack
1262                 @function
1263                 @description Shortcut for creating many locale modules on one locale (ie a brand new entire locale pack)
1264
1265                 @param {String} localeTag The name of the module retreived.
1266                 @param {Object} data The data of the module in MODULE : key : value form.
1267
1268                 @returns this
1269                 */
1270                 function addLocalePack( localeTag, data ) {
1271                         for (var moduleName in data) {
1272                                 addLocaleModule(moduleName, localeTag, data[moduleName]);
1273                         }
1274                         
1275                         return self;
1276                 }
1277         
1278                 /**
1279                 @name glow.i18n.checkLocale
1280                 @function
1281                 @description Developer focused checker for getting the locale tag(s) returned by tag negotiation
1282                         if no options passed it returns a structured data object of the entire data set for the given locale
1283                         if just a module name is passed, the result set is limited to that module
1284                         if a module name and a label are passed it returns a string for just that label
1285         
1286                 @param {String} localeTag The name of the module retreived.
1287                 @param {Object} [opts] Options object
1288                         @param {String} [opts.module] If set, restricts the results to that module.
1289                         @param {String} [opts.label] If set alongside opts.module, restricts the results to that label on that module.
1290
1291                 @returns {Object}
1292                 */
1293                 function checkLocale( localeTag, opts ) {
1294                         var options = opts || {},
1295                                 parsed = parseTag(localeTag);
1296         
1297                         if (options.module) {
1298                                 if (options.label) {
1299                                         return checkLocaleByModuleAndLabel(parsed, options.module, options.label);
1300                                 }
1301                                 else {
1302                                         return checkLocaleByModule(parsed, options.module);
1303                                 }
1304                         }
1305                         else {
1306                                 return checkLocaleByEverything(parsed);
1307                         }
1308                         
1309                         return null;
1310                 }
1311
1312                 // helper for checkLocale
1313                 function checkLocaleByModuleAndLabel( parsed, module, label ) {
1314                         var result;
1315                         
1316                         // Define the customisers for negotiate outside the function call to be consistent with other checkLocaleBy* helpers
1317                         function test( nTag ) {
1318                                 if (localePacks[nTag] && localePacks[nTag][module] && localePacks[nTag][module][label]) {
1319                                         return true;
1320                                 }
1321                                 else {
1322                                         return false;
1323                                 }
1324                         }
1325                         
1326                         function success( sTag ) {
1327                                 result = sTag;
1328                         }
1329                         
1330                         function fail() {
1331                                 result = "**error** - no negotiated value exists";
1332                         }
1333
1334                         negotiate(parsed, test, success, fail);
1335                         
1336                         return result;
1337                 }
1338                 
1339                 // helper for checkLocale
1340                 function checkLocaleByModule( parsed, module ) {
1341                         var structure = moduleStructure[module] || {},
1342                                 results = {},
1343                                 label;
1344
1345                         // Define the customisers for negotiate outside the loop
1346                         // Oddly, the loop control variable *is* correclty accessed, becasue of var scoping and the synchronous function calls
1347                         function test( nTag ) {
1348                                 if (localePacks[nTag] && localePacks[nTag][module] && localePacks[nTag][module][label]) {
1349                                         return true;
1350                                 }
1351                                 else {
1352                                         return false;
1353                                 }
1354                         }
1355                         
1356                         function success( sTag ) {
1357                                 results[label] = sTag;
1358                         }
1359                         
1360                         function fail() {
1361                                 results[label] = "**error** - no negotiated value exists";
1362                         }
1363                         
1364                         for (label in structure) {
1365                                 negotiate(parsed, test, success, fail);
1366                         }
1367                         
1368                         return results;                 
1369                 }
1370                 
1371                 // helper for checkLocale
1372                 function checkLocaleByEverything( parsed ) {
1373                         var results = {},
1374                                 module,
1375                                 label;
1376                         
1377                         // Define the customisers for negotiate outside the loop
1378                         // Oddly, the loop control variable *is* correclty accessed, becasue of var scoping and the synchronous function calls
1379                         function test( nTag ) {
1380                                 if (localePacks[nTag] && localePacks[nTag][module] && localePacks[nTag][module][label]) {
1381                                         return true;
1382                                 }
1383                                 else {
1384                                         return false;
1385                                 }
1386                         }
1387                         
1388                         function success( sTag ) {
1389                                 results[module][label] = sTag;
1390                         }
1391                         
1392                         function fail() {
1393                                 results[module][label] = "**error** - no negotiated value exists";
1394                         }
1395
1396                         for (module in moduleStructure) {
1397                                 results[module] = {};
1398                                 
1399                                 for (label in moduleStructure[module]) {
1400                                         negotiate(parsed, test, success, fail);
1401                                 }
1402                         }
1403                         
1404                         return results;
1405                 }
1406
1407                 
1408                 // make the module
1409                 glow.i18n = self = {
1410                         setLocale : setLocale,
1411                         revertLocale : revertLocale,
1412                         getLocale : getLocale,
1413                         addLocaleModule : addLocaleModule,
1414                         getLocaleModule : getLocaleModule,
1415                         addLocalePack : addLocalePack,
1416                         checkLocale : checkLocale
1417                 }
1418                 
1419                 // define the basic 'en' locale pack
1420                 // just the properties - widgets and modules will add their own locale modules
1421                 addLocalePack("en", {
1422                         PROPERTIES : {
1423                                 LANGUAGE : "English",
1424                                 DIR : "ltr"
1425                         }
1426                 });
1427         }
1428 });
1429
1430 /* * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1431  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1432
1433         About Locale Tags and Tag Negotiation
1434         =====================================
1435         
1436         Locale Tag negotiation is the process Glow uses to find the most specific
1437         match for a given tag given the context.
1438         
1439         
1440         
1441         Locale Tag Structure
1442         ~~~~~~~~~~~~~~~~~~~~
1443         
1444         First, let's look at the anatomy of locale tags in Glow.
1445         
1446         According to the IANA spec, locale tags have this structure
1447         
1448                 language-script-region-variant-extension-privateuse
1449         
1450         There are also "grandfathered" and "redundant" tags that existed before
1451         this structure was implemented and continue for backwards compatibility.
1452         
1453         Glow only supports the following subset of that structure. Subtags are
1454         separated by "-" and must appear in the correct order.
1455         
1456                 language-script-region-variant
1457         
1458         These are the IANA formats of these subtags
1459         
1460         language : /[a-z]{2,3}/          - 2 or 3 lowercase letters.
1461         script   : /[A-Z][a-z]{3}/       - an uppercase letter followed by
1462                                                                            3 lowercase letters
1463         region   : /[A-Z]{2}|[0-9]{3}/   - 2 upper case letters or a 3 digit number
1464         variant  : /[a-z0-9]{4,}/        - lower case letters or numbers,
1465                                                                            4 or more characters
1466         
1467         eg (yes, these are all official IANA tags, even the last one...)
1468         
1469         en                        language                  generic English
1470         en-GB             language-region               British English
1471         en-Runr           language-script           English written in Runic script
1472         en-Runr-GB        language-script-region    British English written in Runic
1473         en-GB-scouse  language-region-variant   Scouse variant of British English
1474         
1475         While Glow does not support grandfathered or redundant tags or extension or
1476         private use subtags, it does not enforce compliance with the IANA registry.
1477         
1478         This means that if the subtag formats can be relaxed a little, variation
1479         from the standard could be used to fill these gaps with a tag that Glow can
1480         parse.
1481         
1482         As a result Glow applies theses formats to subtags
1483         
1484         language : /[a-z]{1,3}/        - 1, 2 or 3 lowercase letters
1485                                                                                 > allowing shorter subtag than IANA
1486         script   : /[A-Z][a-z]{3}/     - an uppercase letter
1487                                                                          followed by 3 lowercase letters
1488                                                                                 > same as IANA
1489         region   : /[A-Z]{2}|[0-9]{3}/ - 2 upper case letters or a 3 digit number
1490                                                                                 > same as IANA
1491         variant  : /[a-z0-9]{2,}/      - lower case letters or numbers,
1492                                                                          2 or more characters
1493                                                                                 > allowing shorter subtag than IANA
1494         
1495         This does mean that there is now potential conflict between the language
1496         and the variant subtags; a subtag that is 2 or 3 lowercase letters could be
1497         either. Glow's insistence on strict subtag order solves this. If a subtag
1498         that falls in this overlap zone is encountered, it is a language subtag
1499         first, a variant if a language has already be found, and ignored as a last
1500         resort.
1501         
1502         Now, even though according to IANA, en-GB-oed is grandfathered (the variant
1503         is too short), it can be directly supported by Glow.
1504         
1505         (If you're interested, en-GB-oed is British English, but with Oxford
1506         English Dictionary spellings. These basically -ize rather than -ise but all
1507         other spellings as en-GB, so the OED lists "colourize")
1508         
1509         
1510         
1511         Tag Negotiation Process
1512         ~~~~~~~~~~~~~~~~~~~~~~~
1513         
1514         When parsing a tag, Glow will look for each subtag in the expected order.
1515         If a subtag is encountered out of order, or when that item has already been
1516         parsed, it will be ignored. If a subtag's type cannot be identified, it
1517         will be ignored.
1518         
1519         GB-cy-1-en-US will be parsed as cy-US.
1520                 'GB' is out of order,
1521                 'en' is a duplicated language subtag and
1522                 '1' is not a recognised subtag type.
1523         
1524         Given all this, Locale negotiation occurs in the following order.
1525         
1526                                                                 script present?
1527                                                                         /      \
1528                                                          (yes) /        \ (no)
1529                                                                   /          \
1530                 language-script-region-variant    language-region-variant
1531                                 language-script-region    language-region
1532                            language-script-variant       /
1533                                            language-script      /
1534                                                                         \      /
1535                                                                 language-variant
1536                                                                         language
1537                                                                            en
1538         
1539         It starts at the specificity of the given tag, and skips steps which have a
1540         subtag that isn't present. eg
1541         
1542         xx-YY, which will be parsed as language-region, will negotiate as
1543                 language-region
1544                 language
1545                 en
1546         
1547         xx-Yyyy-ZZ, will be parsed as language-script-region, and negotiate as
1548                 language-script-region
1549                 language-script
1550                 language
1551                 en
1552         
1553         When Locale packs are created, the tag will be checked to see if it can
1554         be negotiated. A valid tag must have at least a language, so "en-GB" and
1555         "cy" are valid, but "GB" is not.
1556         
1557         
1558         How this Module handles all this
1559         ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
1560         
1561         The code has a number of variables, objects and arrays whose names or
1562         values make reference to some variant or combination of l, s, r or v. These
1563         match to the 'L'anguage, 'S'cript, 'R'egion or 'V'ariant subtag.
1564
1565         So, for instance, the variable LSRV is a bitmask for a tag structure with
1566         all four subtags.
1567         
1568         
1569         
1570         Useful URLs
1571         ~~~~~~~~~~~
1572         
1573         http://www.w3.org/International/articles/language-tags/
1574         http://www.w3.org/International/articles/bcp47/
1575         http://www.iana.org/assignments/language-subtag-registry
1576
1577
1578
1579  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * *
1580  * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * * */
1581 /**
1582 @name glow.dom
1583 @namespace
1584 @description Accessing and manipulating the DOM
1585 @see <a href="../furtherinfo/creatingnodelists/">Creating NodeLists</a>
1586 @see <a href="../furtherinfo/workingwithnodelists/">Working with NodeLists</a>
1587 @see <a href="../furtherinfo/xmlnodelists/">XML NodeLists</a>
1588 */
1589 (window.gloader || glow).module({
1590         name: "glow.dom",
1591         library: ["glow", "1.7.0"],
1592         depends: [],
1593         builder: function(glow) {
1594                 //private
1595                 var env = glow.env,
1596                         lang = glow.lang,
1597                 /*
1598                 PrivateVar: cssRegex
1599                         For matching CSS selectors
1600                 */
1601                         cssRegex = {
1602                                 tagName: /^(\w+|\*)/,
1603                                 combinator: /^\s*([>]?)\s*/,
1604                                 //safari 1.3 is a bit dim when it comes to unicode stuff, only dot matches them (not even \S), so some negative lookalheads are needed
1605                                 classNameOrId: (env.webkit < 417) ? new RegExp("^([\\.#])((?:(?![\\.#\\[:\\s\\\\]).|\\\\.)+)") : /^([\.#])((?:[^\.#\[:\\\s]+|\\.)+)/
1606                         },
1607                         //for escaping strings in regex
1608                         regexEscape = /([$^\\\/()|?+*\[\]{}.-])/g,
1609
1610                 /*
1611                 PrivateVar: cssCache
1612                         Cache of arrays representing an execution path for css selectors
1613                 */
1614                         cssCache = {},
1615
1616                 /*
1617                 PrivateVar: dom0PropertyMapping
1618                         Mapping of HTML attribute names to DOM0 property names.
1619                 */
1620                         dom0PropertyMapping = {
1621                                 checked    : "checked",
1622                                 "class"    : "className",
1623                                 "disabled" : "disabled",
1624                                 "for"      : "htmlFor",
1625                                 maxlength  : "maxLength"
1626                         },
1627
1628                 /*
1629                 PrivateVar: dom0BooleanAttribute
1630                         The HTML attributes names that should be converted from true/false
1631                         to ATTRIBUTENAME/undefined (i.e. boolean attributes like checked="checked").
1632                 */
1633                         dom0BooleanAttribute = {
1634                                 checked  : true,
1635                                 disabled : true
1636                         },
1637
1638                 /*
1639                 PrivateVar: dom0AttributeMappings
1640                         Functions that map dom0 values to sane ones.
1641                 */
1642                         dom0AttributeMappings = {
1643                                 maxlength : function (val) { return val.toString() == "2147483647" ? undefined : val; }
1644                         },
1645                 /*
1646                 PrivateVar: ucheck
1647                         Used by unique(), increased by 1 on each use
1648                 */
1649                         ucheck = 1,
1650                 /*
1651                 PrivateVar: ucheckPropName
1652                         This is the property name used by unique checks
1653                 */
1654                         ucheckPropName = "_unique" + glow.UID,
1655                         
1656                 /**
1657                         @name glow.dom-dataPropName
1658                         @private
1659                         @type String
1660                         @description The property name added to the DomElement by the NodeList#data method.
1661                 */
1662                         dataPropName = "_uniqueData" + glow.UID,
1663                 
1664                 /**
1665                         @name glow.dom-dataIndex
1666                         @private
1667                         @type String
1668                         @description The value of the dataPropName added by the NodeList#data method.
1669                 */
1670                         dataIndex = 1, // must be a truthy value
1671                         
1672                 /**
1673                         @name glow.dom-dataCache
1674                         @private
1675                         @type Object
1676                         @description Holds the data used by the NodeList#data method.
1677                         
1678                         The structure is like:
1679                         [
1680                                 {
1681                                         myKey: "my data"
1682                                 }
1683                         ]
1684                 */
1685                         dataCache = [],
1686                 
1687                 /*
1688                 PrivateVar: htmlColorNames
1689                         Mapping of colour names to hex values
1690                 */
1691                         htmlColorNames = {
1692                                 black: 0,
1693                                 silver: 0xc0c0c0,
1694                                 gray: 0x808080,
1695                                 white: 0xffffff,
1696                                 maroon: 0x800000,
1697                                 red: 0xff0000,
1698                                 purple: 0x800080,
1699                                 fuchsia: 0xff00ff,
1700                                 green: 0x8000,
1701                                 lime: 0xff00,
1702                                 olive: 0x808000,
1703                                 yellow: 0xffff00,
1704                                 navy: 128,
1705                                 blue: 255,
1706                                 teal: 0x8080,
1707                                 aqua: 0xffff,
1708                                 orange: 0xffa500
1709                         },
1710                 /*
1711                 PrivateVar: usesYAxis
1712                         regex for detecting which css properties need to be calculated relative to the y axis
1713                 */
1714                         usesYAxis = /height|top/,
1715                         colorRegex = /^rgb\(([\d\.]+)(%?),\s*([\d\.]+)(%?),\s*([\d\.]+)(%?)/i,
1716                         cssPropRegex = /^(?:(width|height)|(border-(top|bottom|left|right)-width))$/,
1717                         hasUnits = /width|height|top$|bottom$|left$|right$|spacing$|indent$|font-size/,
1718                         //append gets set to a function below
1719                         append,
1720                         //unique gets set to a function below
1721                         unique,
1722                         //we set this up at the end of the module
1723                         placeholderElm,
1724                         //getByTagName gets get to a function below
1725                         getByTagName,
1726                         win = window,
1727                         doc = document,
1728                         docBody,
1729                         docElm,
1730                         // true if properties of a dom node are cloned when the node is cloned (eg, true in IE)
1731                         nodePropertiesCloned,
1732                         // used to convert divs to strings
1733                         tmpDiv = doc.createElement("div"),
1734                         /*
1735                         PrivateVars: tableArray, elmFilter
1736                                 Used in private function stringToNodes to capture any 
1737                                 elements that cannot be a childNode of <div>.
1738                                         Each entry in JSON responds to an element.
1739                                         First array value is how deep the element will be created in a node tree.
1740                                         Second array value is the beginning of the node tree.
1741                                         Third array value is the end of the node tree.
1742                         */
1743                         tableArray = [1, '<table>', '</table>'],
1744                         emptyArray = [0, '', ''],
1745                         // webkit won't accept <link> elms to be the only child of an element,
1746                         // it steals them and hides them in the head for some reason. Using
1747                         // broken html fixes it for some reason
1748                         paddingElmArray = env.webkit < 526 ? [0, '', '</div>', true] : [1, 'b<div>', '</div>'],
1749                         trArray = [3, '<table><tbody><tr>', '</tr></tbody></table>'],
1750                         elmWraps = {
1751                                 caption: tableArray,
1752                                 thead: tableArray,
1753                                 th: trArray,
1754                                 colgroup: tableArray,
1755                                 tbody: tableArray,
1756                                 tr: [2, '<table><tbody>', '</tbody></table>'],
1757                                 td: trArray,
1758                                 tfoot: tableArray,
1759                                 option: [1, '<select>', '</select>'],
1760                                 legend: [1, '<fieldset>', '</fieldset>'],
1761                                 link: paddingElmArray,
1762                                 script: paddingElmArray,
1763                                 style: paddingElmArray
1764                         };
1765                 
1766                 // clean up IE's mess
1767                 if (env.ie) {
1768                         window.attachEvent("onunload", function() {
1769                                 tmpDiv = null;
1770                         });
1771                 }
1772                 
1773                 glow.ready(function() {
1774                         docBody = doc.body;
1775                         docElm = doc.documentElement;
1776                 });
1777                 
1778                 
1779                 // test for nodePropertiesCloned
1780                 (function() {
1781                         var div = doc.createElement("div");
1782                         div.a = 1;
1783                         nodePropertiesCloned = !!div.cloneNode(true).a;
1784                 })();
1785                 
1786                 /**
1787                  @name glow.dom-getFirstChildElm
1788                  @private
1789                  @description Returns the first leaf of a NodeList
1790                  @param {NodeList} 
1791                 */
1792                 function getFirstChildElm(parent) {                                     
1793                         for (var child = parent.firstChild; child; child = child.nextSibling) {
1794                                 if (child.nodeType == 1) {
1795                                         return child;
1796                                 }                       
1797                         }                       
1798                         return null;                    
1799                 }
1800
1801                 /*
1802                 PrivateMethod: removeClassRegex
1803                         Get a regex that can be used to remove a class from a space separated list of classes.
1804
1805                 Arguments:
1806                         name - (string) the name of the class.
1807
1808                 Returns:
1809                         The regex.
1810                 */
1811                 function removeClassRegex (name) {
1812                         return new RegExp(["(^|\\s)", name.replace(regexEscape, "\\$1"), "($|\\s)"].join(""), "g");
1813                 }
1814
1815
1816                 /*
1817                 PrivateMethod: stringToNodes
1818                         Creates an array of nodes from a string
1819                 */
1820                 function stringToNodes(str) {
1821                         var r = [],
1822                                 tagName = (/^\s*<([^\s>]+)/.exec(str) || [,'div'])[1],
1823                                 // This matches str content with potential elements that cannot
1824                                 // be a child of <div>.  elmFilter declared at top of page.
1825                                 elmWrap = elmWraps[tagName] || emptyArray, 
1826                                 nodeDepth,
1827                                 childElm,
1828                                 rLen = 0;
1829                         
1830                         // Create the new element using the node tree contents available in filteredElm.
1831                         tmpDiv.innerHTML = (elmWrap[1] + str + elmWrap[2]);
1832                         
1833                         childElm = tmpDiv;
1834                         
1835                         // Strip newElement down to just the required element and its parent
1836                         nodeDepth = elmWrap[0];
1837                         while(nodeDepth--) {
1838                                 childElm = childElm.lastChild;
1839                         }
1840
1841                         // pull nodes out of child
1842                         while (childElm.firstChild) {
1843                                 r[rLen++] = childElm.removeChild(childElm.firstChild);
1844                         }
1845                         
1846                         childElm = null;
1847                         
1848                         return r;
1849                 }
1850
1851                 /*
1852                 PrivateMethod: nodelistToArray
1853                         Converts a w3 NodeList to an array
1854                 */
1855                 function nodelistToArray(nodelist) {
1856                         var r = [], i = 0;
1857                         for (; nodelist[i]; i++) {
1858                                 r[i] = nodelist[i];
1859                         }
1860                         return r;
1861                 }
1862
1863                 /*
1864                 PrivateMethod: setAttribute
1865                         Sets the attribute in the nodelist using the supplied function.
1866
1867                 Arguments:
1868                         value - (String|Function) the value/value generator.
1869                         attributeSetter - (Function) a function that can be called to actually set the attribute.
1870
1871                 Returns:
1872                         The <NodeList> object.
1873                 */
1874                 // is marginal having this separated out as it is only used twice and call is almost as big
1875                 // leaving it separate for now for once and only once, as well as in case attr does some more mutating stuff
1876                 // could be merged back with attr later
1877                 function setAttribute (value, attributeSetter) {
1878                         for (var that = this, i = 0, length = that.length; i < length; i++) {
1879                                 attributeSetter.call(
1880                                         that[i],
1881                                         value.call ?
1882                                                 value.call(that[i], i) :
1883                                                 value
1884                                 );
1885                         }
1886                         return that;
1887                 }
1888
1889
1890                 /*
1891                 PrivateMethod: append
1892                         append the nodes in "b" to the array / list "a"
1893                 */
1894                 //return different function for IE & Opera to deal with their stupid bloody expandos. Pah.
1895                 if (document.all) {
1896                         append = function(a, b) {
1897                                 var i = 0,
1898                                         ai = a.length,
1899                                         length = b.length;
1900                                 if (typeof b.length == "number") {
1901                                         for (; i < length; i++) {
1902                                                 a[ai++] = b[i];
1903                                         }
1904                                 } else {
1905                                         for (; b[i]; i++) {
1906                                                 a[ai++] = b[i];
1907                                         }
1908                                 }
1909                         };
1910                 } else {
1911                         append = function(a, b) {
1912                                 var i = 0, ai = a.length;
1913                                 for (; b[i]; i++) {
1914                                         a[ai++] = b[i];
1915                                 }
1916                         };
1917                 }
1918
1919                 /*
1920                 PrivateMethod: isXml
1921                         Is this node an XML Document node or within an XML Document node
1922
1923                 Arguments:
1924                         node
1925
1926                 Returns:
1927                         Bool
1928                 */
1929                 function isXml(node) {
1930                         //test for nodes within xml element
1931                         return  (node.ownerDocument && !node.ownerDocument.body) ||
1932                                         //test for xml document elements
1933                                         (node.documentElement && !node.documentElement.body);
1934                 }
1935
1936                 /*
1937                 PrivateMethod: unique
1938                         Get an array of nodes without duplicate nodes from an array of nodes.
1939
1940                 Arguments:
1941                         aNodes - (Array|<NodeList>)
1942
1943                 Returns:
1944                         An array of nodes without duplicates.
1945                 */
1946                 //worth checking if it's an XML document?
1947                 if (env.ie) {
1948                         unique = function(aNodes) {
1949                                 if (aNodes.length == 1) { return aNodes; }
1950
1951                                 //remove duplicates
1952                                 var r = [],
1953                                         ri = 0,
1954                                         i = 0;
1955
1956                                 for (; aNodes[i]; i++) {
1957                                         if (aNodes[i].getAttribute(ucheckPropName) != ucheck && aNodes[i].nodeType == 1) {
1958                                                 r[ri++] = aNodes[i];
1959                                         }
1960                                         aNodes[i].setAttribute(ucheckPropName, ucheck);
1961                                 }
1962                                 for (i=0; aNodes[i]; i++) {
1963                                         aNodes[i].removeAttribute(ucheckPropName);
1964                                 }
1965                                 ucheck++;
1966                                 return r;
1967                         }
1968                 } else {
1969                         unique = function(aNodes) {
1970                                 if (aNodes.length == 1) { return aNodes; }
1971
1972                                 //remove duplicates
1973                                 var r = [],
1974                                         ri = 0,
1975                                         i = 0;
1976
1977                                 for (; aNodes[i]; i++) {
1978                                         if (aNodes[i][ucheckPropName] != ucheck && aNodes[i].nodeType == 1) {
1979                                                 r[ri++] = aNodes[i];
1980                                         }
1981                                         aNodes[i][ucheckPropName] = ucheck;
1982                                 }
1983                                 ucheck++;
1984                                 return r;
1985                         }
1986                 }
1987
1988                 /*
1989                 PrivateMethod: getElementsByTag
1990                         Get elements by a specified tag name from a set of context objects. If multiple
1991                         context objects are passed, then the resulting array may contain duplicates. See
1992                         <unique> to remove duplicate nodes.
1993
1994                 Arguments:
1995                         tag - (string) Tag name. "*" for all.
1996                         contexts - (array) DOM Documents and/or DOM Elements to search in.
1997
1998                 Returns:
1999                         An array(like) collection of elements with the specified tag name.
2000                 */
2001                 if (document.all) { //go the long way around for IE (and Opera)
2002                         getByTagName = function(tag, context) {
2003                                 var r = [], i = 0;
2004                                 for (; context[i]; i++) {
2005                                         //need to check .all incase data is XML
2006                                         //TODO: Drop IE5.5
2007                                         if (tag == "*" && context[i].all && !isXml(context[i])) { // IE 5.5 doesn't support getElementsByTagName("*")
2008                                                 append(r, context[i].all);
2009                                         } else {
2010                                                 append(r, context[i].getElementsByTagName(tag));
2011                                         }
2012                                 }
2013                                 return r;
2014                         };
2015                 } else {
2016                         getByTagName = function(tag, context) {
2017                                 var r = [], i = 0, len = context.length;
2018                                 for (; i < len; i++) {
2019                                         append(r, context[i].getElementsByTagName(tag));
2020                                 }
2021                                 return r;
2022                         };
2023                 }
2024                 
2025                 /*
2026                         Get the child elements for an html node
2027                 */
2028                 function getChildElms(node) {
2029                         var r = [],
2030                                 childNodes = node.childNodes,
2031                                 i = 0,
2032                                 ri = 0;
2033                         
2034                         for (; childNodes[i]; i++) {
2035                                 if (childNodes[i].nodeType == 1 && childNodes[i].nodeName != "!") {
2036                                         r[ri++] = childNodes[i];
2037                                 }
2038                         }
2039                         return r;
2040                 }
2041                 
2042                 
2043                 var horizontalBorderPadding = [
2044                                 'border-left-width',
2045                                 'border-right-width',
2046                                 'padding-left',
2047                                 'padding-right'
2048                         ],
2049                         verticalBorderPadding = [
2050                                 'border-top-width',
2051                                 'border-bottom-width',
2052                                 'padding-top',
2053                                 'padding-bottom'
2054                         ];
2055                 
2056                 /*
2057                 PrivateMethod: getElmDimension
2058                         Gets the size of an element as an integer, not including padding or border
2059                 */              
2060                 function getElmDimension(elm, cssProp /* (width|height) */) {
2061                         var r, // val to return
2062                                 docElmOrBody = env.standardsMode ? docElm : docBody,
2063                                 isWidth = (cssProp == "width"),
2064                                 cssPropCaps = isWidth ? "Width" : "Height",
2065                                 cssBorderPadding;
2066
2067                         if (elm.window) { // is window
2068                                 r = env.webkit < 522.11 ? (isWidth ? elm.innerWidth                             : elm.innerHeight) :
2069                                         env.webkit                      ? (isWidth ? docBody.clientWidth                : elm.innerHeight) :
2070                                         env.opera < 9.5         ? (isWidth ? docBody.clientWidth                : docBody.clientHeight) :
2071                                         /* else */                        (isWidth ? docElmOrBody.clientWidth   : docElmOrBody.clientHeight);
2072
2073                         }
2074                         else if (elm.getElementById) { // is document
2075                                 // we previously checked offsetWidth & clientWidth here
2076                                 // but they returned values too large in IE6 scrollWidth seems enough
2077                                 r = Math.max(
2078                                         docBody["scroll" + cssPropCaps],
2079                                         docElm["scroll" + cssPropCaps]
2080                                 )
2081                         }
2082                         else {
2083                                 // get an array of css borders & padding
2084                                 cssBorderPadding = isWidth ? horizontalBorderPadding : verticalBorderPadding;
2085                                 r = elm['offset' + cssPropCaps] - parseInt( getCssValue(elm, cssBorderPadding) );
2086                         }
2087                         return r;
2088                 }
2089
2090                 /*
2091                 PrivateMethod: getBodyElm
2092                         Gets the body elm for a given element. Gets around IE5.5 nonsense. do getBodyElm(elm).parentNode to get documentElement
2093                 */
2094                 function getBodyElm(elm) {
2095                         if (env.ie < 6) {
2096                                 return elm.document.body;
2097                         } else {
2098                                 return elm.ownerDocument.body;
2099                         }
2100                 }
2101
2102                 /*
2103                 PrivateMethod: setElmsSize
2104                         Set element's size
2105
2106                 Arguments:
2107                         elms - (<NodeList>) Elements
2108                         val - (Mixed) Set element height / width. In px unless stated
2109                         type - (String) "height" or "width"
2110
2111                 Returns:
2112                         Nowt.
2113                 */
2114                 function setElmsSize(elms, val, type) {
2115                         if (typeof val == "number" || /\d$/.test(val)) {
2116                                 val += "px";
2117                         }
2118                         for (var i = 0, len = elms.length; i < len; i++) {
2119                                 elms[i].style[type] = val;
2120                         }
2121                 }
2122
2123                 /*
2124                 PrivateMethod: toStyleProp
2125                         Converts a css property name into its javascript name, such as "background-color" to "backgroundColor".
2126
2127                 Arguments:
2128                         prop - (String) CSS Property name
2129
2130                 Returns:
2131                         String, javascript style property name
2132                 */
2133                 function toStyleProp(prop) {
2134                         if (prop == "float") {
2135                                 return env.ie ? "styleFloat" : "cssFloat";
2136                         }
2137                         return lang.replace(prop, /-(\w)/g, function(match, p1) {
2138                                 return p1.toUpperCase();
2139                         });
2140                 }
2141
2142                 /*
2143                 PrivateMethod: tempBlock
2144                         Gives an element display:block (but keeps it hidden) and runs a function, then sets the element back how it was
2145
2146                 Arguments:
2147                         elm - element
2148                         func - function to run
2149
2150                 Returns:
2151                         Return value of the function
2152                 */
2153                 function tempBlock(elm, func) {
2154                         //TODO: rather than recording individual style properties, just cache cssText? This was faster for getting the element size
2155                         var r,
2156                                 elmStyle = elm.style,
2157                                 oldDisp = elmStyle.display,
2158                                 oldVis = elmStyle.visibility,
2159                                 oldPos = elmStyle.position;
2160
2161                         elmStyle.visibility = "hidden";
2162                         elmStyle.position = "absolute";
2163                         elmStyle.display = "block";
2164                         if (!isVisible(elm)) {
2165                                 elmStyle.position = oldPos;
2166                                 r = tempBlock(elm.parentNode, func);
2167                                 elmStyle.display = oldDisp;
2168                                 elmStyle.visibility = oldVis;
2169                         } else {
2170                                 r = func();
2171                                 elmStyle.display = oldDisp;
2172                                 elmStyle.position = oldPos;
2173                                 elmStyle.visibility = oldVis;
2174                         }
2175                         return r;
2176                 }
2177
2178                 /*
2179                 PrivateMethod: isVisible
2180                         Is the element visible?
2181                 */
2182                 function isVisible(elm) {
2183                         //this is a bit of a guess, if there's a better way to do this I'm interested!
2184                         return elm.offsetWidth ||
2185                                 elm.offsetHeight;
2186                 }
2187
2188                 /*
2189                 PrivateMethod: getCssValue
2190                         Get a computed css property
2191
2192                 Arguments:
2193                         elm - element
2194                         prop - css property or array of properties to add together
2195
2196                 Returns:
2197                         String, value
2198                 */
2199                 function getCssValue(elm, prop) {
2200                         var r, //return value
2201                                 total = 0,
2202                                 i = 0,
2203                                 propLen = prop.length,
2204                                 compStyle = doc.defaultView && (doc.defaultView.getComputedStyle(elm, null) || doc.defaultView.getComputedStyle),
2205                                 elmCurrentStyle = elm.currentStyle,
2206                                 oldDisplay,
2207                                 match,
2208                                 propTest = prop.push || cssPropRegex.exec(prop) || [];
2209
2210
2211                         if (prop.push) { //multiple properties, add them up
2212                                 for (; i < propLen; i++) {
2213                                         total += parseInt( getCssValue(elm, prop[i]), 10 ) || 0;
2214                                 }
2215                                 return total + "px";
2216                         }
2217                         
2218                         if (propTest[1]) { // is width / height
2219                                 if (!isVisible(elm)) { //element may be display: none
2220                                         return tempBlock(elm, function() {
2221                                                 return getElmDimension(elm, propTest[1]) + "px";
2222                                         });
2223                                 }
2224                                 return getElmDimension(elm, propTest[1]) + "px";
2225                         }
2226                         else if (propTest[2] //is border-*-width
2227                                 && glow.env.ie
2228                                 && getCssValue(elm, "border-" + propTest[3] + "-style") == "none"
2229                         ) {
2230                                 return "0";
2231                         }
2232                         else if (compStyle) { //W3 Method
2233                                 //this returns computed values
2234                                 if (typeof compStyle == "function") {
2235                                         //safari returns null for compStyle when element is display:none
2236
2237                                         oldDisplay = elm.style.display;
2238                                         r = tempBlock(elm, function() {
2239                                                 if (prop == "display") { //get true value for display, since we've just fudged it
2240                                                         elm.style.display = oldDisplay;
2241                                                         if (!doc.defaultView.getComputedStyle(elm, null)) {
2242                                                                 return "none";
2243                                                         }
2244                                                         elm.style.display = "block";
2245                                                 }
2246                                                 return getCssValue(elm, prop);
2247                                         });
2248                                 } else {
2249                                         // assume equal horizontal margins in safari 3
2250                                         // http://bugs.webkit.org/show_bug.cgi?id=13343
2251                                         // The above bug doesn't appear to be closed, but it works fine in Safari 4
2252                                         if (env.webkit > 500 && env.webkit < 526 && prop == 'margin-right' && compStyle.getPropertyValue('position') != 'absolute') {
2253                                                 prop = 'margin-left';
2254                                         }
2255                                         r = compStyle.getPropertyValue(prop);
2256                                 }
2257                         } else if (elmCurrentStyle) { //IE method
2258                                 if (prop == "opacity") {
2259                                         match = /alpha\(opacity=([^\)]+)\)/.exec(elmCurrentStyle.filter);
2260                                         return match ? String(parseInt(match[1], 10) / 100) : "1";
2261                                 }
2262                                 //this returns cascaded values so needs fixing
2263                                 r = String(elmCurrentStyle[toStyleProp(prop)]);
2264                                 if (/^-?[\d\.]+(?!px)[%a-z]+$/i.test(r) && prop != "font-size") {
2265                                         r = getPixelValue(elm, r, usesYAxis.test(prop)) + "px";
2266                                 }
2267                         }
2268                         //some results need post processing
2269                         if (prop.indexOf("color") != -1) { //deal with colour values
2270                                 r = normaliseCssColor(r).toString();
2271                         } else if (r.indexOf("url") == 0) { //some browsers put quotes around the url, get rid
2272                                 r = r.replace(/\"/g,"");
2273                         }
2274                         return r;
2275                 }
2276
2277                 /*
2278                 PrivateMethod: getPixelValue
2279                         Converts a relative value into an absolute pixel value. Only works in IE with Dimension value (not stuff like relative font-size).
2280                         Based on some Dean Edwards' code
2281
2282                 Arguments:
2283                         element - element used to calculate relative values
2284                         value - (string) relative value
2285                         useYAxis - (string) calulate relative values to the y axis rather than x
2286
2287                 Returns:
2288                         Number
2289                 */
2290                 function getPixelValue(element, value, useYAxis) {
2291                         // Remember the original values
2292                         var axisPos = useYAxis ? "top" : "left",
2293                                 axisPosUpper = useYAxis ? "Top" : "Left",
2294                                 elmStyle = element.style,
2295                                 positionVal = elmStyle[axisPos],
2296                                 runtimePositionVal = element.runtimeStyle[axisPos],
2297                                 r;
2298                         
2299                         // copy to the runtime type to prevent changes to the display
2300                         element.runtimeStyle[axisPos] = element.currentStyle[axisPos];
2301                         // set value to left / top
2302                         elmStyle[axisPos] = value;
2303                         // get the pixel value
2304                         r = elmStyle["pixel" + axisPosUpper];
2305                         
2306                         // revert values
2307                         elmStyle[axisPos] = positionVal;
2308                         element.runtimeStyle[axisPos] = runtimePositionVal;
2309                         
2310                         return r;
2311                 }
2312
2313                 /*
2314                 PrivateMethod: normaliseCssColor
2315                         Converts a CSS colour into "rgb(255, 255, 255)" or "transparent" format
2316                 */
2317
2318                 function normaliseCssColor(val) {
2319                         if (/^(transparent|rgba\(0, ?0, ?0, ?0\))$/.test(val)) { return 'transparent'; }
2320                         var match, //tmp regex match holder
2321                                 r, g, b, //final colour vals
2322                                 hex, //tmp hex holder
2323                                 mathRound = Math.round,
2324                                 parseIntFunc = parseInt,
2325                                 parseFloatFunc = parseFloat;
2326
2327                         if (match = colorRegex.exec(val)) { //rgb() format, cater for percentages
2328                                 r = match[2] ? mathRound(((parseFloatFunc(match[1]) / 100) * 255)) : parseIntFunc(match[1]);
2329                                 g = match[4] ? mathRound(((parseFloatFunc(match[3]) / 100) * 255)) : parseIntFunc(match[3]);
2330                                 b = match[6] ? mathRound(((parseFloatFunc(match[5]) / 100) * 255)) : parseIntFunc(match[5]);
2331                         } else {
2332                                 if (typeof val == "number") {
2333                                         hex = val;
2334                                 } else if (val.charAt(0) == "#") {
2335                                         if (val.length == "4") { //deal with #fff shortcut
2336                                                 val = "#" + val.charAt(1) + val.charAt(1) + val.charAt(2) + val.charAt(2) + val.charAt(3) + val.charAt(3);
2337                                         }
2338                                         hex = parseIntFunc(val.slice(1), 16);
2339                                 } else {
2340                                         hex = htmlColorNames[val];
2341                                 }
2342
2343                                 r = (hex) >> 16;
2344                                 g = (hex & 0x00ff00) >> 8;
2345                                 b = (hex & 0x0000ff);
2346                         }
2347
2348                         val = new String("rgb(" + r + ", " + g + ", " + b + ")");
2349                         val.r = r;
2350                         val.g = g;
2351                         val.b = b;
2352                         return val;
2353                 }
2354
2355                 /*
2356                 PrivateMethod: getTextNodeConcat
2357                         Take an element and returns a string of its text nodes combined. This is useful when a browser (Safari 2) goes mad and doesn't return anything for innerText or textContent
2358                 */
2359                 function getTextNodeConcat(elm) {
2360                         var r = "",
2361                                 nodes = elm.childNodes,
2362                                 i = 0,
2363                                 len = nodes.length;
2364                         for (; i < len; i++) {
2365                                 if (nodes[i].nodeType == 3) { //text
2366                                         //this function is used in safari only, string concatination is faster
2367                                         r += nodes[i].nodeValue;
2368                                 } else if (nodes[i].nodeType == 1) { //element
2369                                         r += getTextNodeConcat(nodes[i]);
2370                                 }
2371                         }
2372                         return r;
2373                 }
2374
2375                 /*
2376                 PrivateMethod: getNextOrPrev
2377                         This gets the next / previous sibling element of each node in a nodeset
2378                         and returns the new nodeset.
2379                 */
2380                 function getNextOrPrev(nodelist, dir /* "next" or "previous" */) {
2381                         var ret = [],
2382                                 ri = 0,
2383                                 nextTmp,
2384                                 i = 0,
2385                                 length = nodelist.length;
2386
2387                         for (; i < length; i++) {
2388                                 nextTmp = nodelist[i];
2389                                 while (nextTmp = nextTmp[dir + "Sibling"]) {
2390                                         if (nextTmp.nodeType == 1 && nextTmp.nodeName != "!") {
2391                                                 ret[ri++] = nextTmp;
2392                                                 break;
2393                                         }
2394                                 }
2395                         }
2396                         return r.get(ret);
2397                 }
2398                 
2399                 /*
2400                  Get the 'real' positioned parent for an element, otherwise return null.
2401                 */
2402                 function getPositionedParent(elm) {
2403                         var offsetParent = elm.offsetParent;
2404                         
2405                         // get the real positioned parent
2406                         // IE places elements with hasLayout in the offsetParent chain even if they're position:static
2407                         // Also, <body> and <html> can appear in the offsetParent chain, but we don't want to return them if they're position:static
2408                         while (offsetParent && r.get(offsetParent).css("position") == "static") {       
2409                                 offsetParent = offsetParent.offsetParent;
2410                         }
2411                         
2412                         // sometimes the <html> element doesn't appear in the offsetParent chain, even if it has position:relative
2413                         if (!offsetParent && r.get(docElm).css("position") != "static") {
2414                                 offsetParent = docElm;
2415                         }
2416                         
2417                         return offsetParent || null;
2418                 }
2419                 
2420                 /**
2421                  @name glow.dom-getScrollOffset
2422                  @private
2423                  @description Get the scrollTop / scrollLeft of a particular element
2424                  @param {Element} elm Element (or window object) to get the scroll position of
2425                  @param {Boolean} isLeft True if we're dealing with left scrolling, otherwise top
2426                 */
2427                 function getScrollOffset(elm, isLeft) {
2428                         var r,                  
2429                                 scrollProp = 'scroll' + (isLeft ? "Left" : "Top");
2430                         
2431                         // are we dealing with the window object or the document object?
2432                         if (elm.window) {
2433                                 // get the scroll of the documentElement or the pageX/Yoffset
2434                                 // - some browsers use one but not the other
2435                                 r = elm.document.documentElement[scrollProp]
2436                                         || (isLeft ? elm.pageXOffset : elm.pageYOffset)
2437                                         || 0;
2438                         }
2439                         else {
2440                                 r = elm[scrollProp];
2441                         }
2442                         return r;
2443                 }
2444                 
2445                 /**
2446                  @name glow.dom-setScrollOffset
2447                  @private
2448                  @description Set the scrollTop / scrollLeft of a particular element
2449                  @param {Element} elm Element (or window object) to get the scroll position of
2450                  @param {Boolean} isLeft True if we're dealing with left scrolling, otherwise top
2451                  @param {Number} newVal New scroll value
2452                 */
2453                 function setScrollOffset(elm, isLeft, newVal) {
2454                         
2455                         // are we dealing with the window object or the document object?
2456                         if (elm.window) {
2457                                 // we need to get whichever value we're not setting
2458                                 elm.scrollTo(
2459                                         isLeft  ? newVal : getScrollOffset(elm, true),
2460                                         !isLeft ? newVal : getScrollOffset(elm, false)
2461                                 );
2462                         }
2463                         else {
2464                                 elm['scroll' + (isLeft ? "Left" : "Top")] = newVal;
2465                         }
2466                 }
2467                 
2468                 /**
2469                  @name glow.dom-scrollOffset
2470                  @private
2471                  @description Set/get the scrollTop / scrollLeft of a NodeList
2472                  @param {glow.dom.NodeList} nodeList Elements to get / set the position of
2473                  @param {Boolean} isLeft True if we're dealing with left scrolling, otherwise top
2474                  @param {Number} [val] Val to set (if not provided, we'll get the value)
2475                  
2476                  @returns NodeList for sets, Number for gets
2477                 */
2478                 function scrollOffset(nodeList, isLeft, val) {
2479                         var i = nodeList.length;
2480                         
2481                         if (val !== undefined) {
2482                                 while (i--) {
2483                                         setScrollOffset(nodeList[i], isLeft, val);
2484                                 }
2485                                 return nodeList;
2486                         } else {
2487                                 return getScrollOffset(nodeList[0], isLeft);
2488                         }
2489                 }
2490
2491                 //public
2492                 var r = {}; //object to be returned
2493
2494                 /**
2495                 @name glow.dom.get
2496                 @function
2497                 @description Returns a {@link glow.dom.NodeList NodeList} from CSS selectors and/or Elements.
2498
2499                 @param {String | String[] | Element | Element[] | glow.dom.NodeList} nodespec+ One or more CSS selector strings, Elements or {@link glow.dom.NodeList NodeLists}.
2500
2501                         Will also accept arrays of these types, or any combinations thereof.
2502
2503                         Supported CSS selectors:
2504
2505                         <ul>
2506                                 <li>Universal selector "*".</li>
2507                                 <li>Type selector "div"</li>
2508                                 <li>Class selector ".myClass"</li>
2509                                 <li>ID selector "#myDiv"</li>
2510                                 <li>Child selector "ul > li"</li>
2511                                 <li>Grouping "div, p"</li>
2512                         </ul>
2513
2514                 @returns {glow.dom.NodeList}
2515
2516                 @example
2517                         // Nodelist with all links in element with id "nav"
2518                         var myNodeList = glow.dom.get("#nav a");
2519
2520                 @example
2521                         // NodeList containing the nodes passed in
2522                         var myNodeList = glow.dom.get(someNode, anotherNode);
2523
2524                 @example
2525                         // NodeList containing elements in the first form
2526                         var myNodeList = glow.dom.get(document.forms[0].elements);
2527                 */
2528                 r.get = function() {
2529                         var r = new glow.dom.NodeList(),
2530                                 i = 0,
2531                                 args = arguments,
2532                                 argsLen = args.length;
2533
2534                         for (; i < argsLen; i++) {
2535                                 if (typeof args[i] == "string") {
2536                                         r.push(new glow.dom.NodeList().push(doc).get(args[i]));
2537                                 } else {
2538                                         r.push(args[i]);
2539                                 }
2540                         }
2541                         return r;
2542                 };
2543
2544                 /**
2545                 @name glow.dom.create
2546                 @function
2547                 @description Returns a {@link glow.dom.NodeList NodeList} from an HTML fragment.
2548
2549                 @param {String} html An HTML string.
2550
2551                         All top-level nodes must be elements (i.e. text content in the
2552                         HTML must be wrapped in HTML tags).
2553
2554                 @param {Object} [opts] An optional options object
2555                         @param {Object} [opts.interpolate] Data for a call to {@link glow.lang.interpolate}
2556                                 If this option is set, the String html parameter will be passed through glow.lang.interpolate with this as the data and no options
2557                                 If glow.lang.interpolates options are required, an explicit call must be made
2558                         @param {Boolean} [opts.escapeHtml] Escape HTML in the interpolate data object.
2559                                 See {@link glow.lang.interpolate}
2560
2561                 @returns {glow.dom.NodeList}
2562
2563                 @example
2564                         // NodeList of two elements
2565                         var myNodeList = glow.dom.create("<div>Hello</div><div>World</div>");
2566                         
2567                 @example
2568                         // Nodelist of one list item
2569                         var listItem = glow.dom.create('<li>{content}</li>', {
2570                                 interpolate: {content: textFromUser},
2571                                 escapeHtml: true
2572                         });
2573                         // if testFromUser contains HTML, it will be correctly escaped
2574                         // before being inserted into the li
2575                 */
2576                 r.create = function(sHtml, opts) {
2577                         var ret = [],
2578                                 i = 0,
2579                                 rLen = 0,
2580                                 toCheck;
2581                         
2582                         // set default options
2583                         opts = glow.lang.apply({
2584                                 interpolate: null,
2585                                 escapeHtml: false
2586                         }, opts || {});
2587                         
2588                         if (opts.interpolate) {
2589                                 sHtml = lang.interpolate(sHtml, opts.interpolate, {
2590                                         escapeHtml: opts.escapeHtml
2591                                 });
2592                         }
2593                         
2594                         toCheck = stringToNodes(sHtml);
2595                         
2596                         for (; toCheck[i]; i++) {
2597                                 if (toCheck[i].nodeType == 1 && toCheck[i].nodeName != "!") {
2598                                         ret[rLen++] = toCheck[i];
2599                                 } else if (toCheck[i].nodeType == 3 && lang.trim(toCheck[i].nodeValue) !== "") {
2600                                         throw new Error("glow.dom.create - Text must be wrapped in an element");
2601                                 }
2602                         }
2603                         return new r.NodeList().push(ret);
2604                 };
2605
2606                 /**
2607                 @name glow.dom.parseCssColor
2608                 @function
2609                 @description Returns an object representing a CSS colour string.
2610
2611                 @param {String} color A CSS colour.
2612
2613                         Examples of valid values are "red", "#f00", "#ff0000",
2614                         "rgb(255,0,0)", "rgb(100%, 0%, 0%)"
2615
2616                 @returns {Object}
2617
2618                         An object with properties named "r", "g" and "b", each will have
2619                         an integer value between 0 and 255.
2620
2621                 @example
2622                         glow.dom.parseCssColor("#ff0000");
2623                         // returns {r:255, g:0, b:0}
2624                 */
2625                 r.parseCssColor = function(cssColor) {
2626                         var normal = normaliseCssColor(cssColor);
2627                         return {r: normal.r, g: normal.g, b: normal.b};
2628                 }
2629
2630                 /**
2631                 @name glow.dom.NodeList
2632                 @class
2633                 @description An array-like collection of DOM Elements.
2634
2635                         It is recommended you create a NodeList using {@link glow.dom.get glow.dom.get},
2636                         or {@link glow.dom.create glow.dom.create}, but you can also use
2637                         the constructor to create an empty NodeList.
2638
2639                         Unless otherwise specified, all methods of NodeList return the
2640                         NodeList itself, allowing you to chain methods calls together.
2641
2642                 @example
2643                         // empty NodeList
2644                         var nodes = new glow.dom.NodeList();
2645
2646                 @example
2647                         // using get to return a NodeList then chaining methods
2648                         glow.dom.get("p").addClass("eg").append("<b>Hello!</b>");
2649
2650                 @see <a href="../furtherinfo/creatingnodelists/">Creating NodeLists</a>
2651                 @see <a href="../furtherinfo/workingwithnodelists/">Working with NodeLists</a>
2652                 @see <a href="../furtherinfo/xmlnodelists/">XML NodeLists</a>
2653                 */
2654                 r.NodeList = function() {
2655
2656                         /**
2657                         @name glow.dom.NodeList#length
2658                         @type Number
2659                         @description Number of nodes in the NodeList
2660                         @example
2661                                 // get the number of paragraphs on the page
2662                                 glow.dom.get("p").length;
2663                         */
2664                         this.length = 0; //number of elements in NodeList
2665                 };
2666
2667                 /*
2668                         Group: Methods
2669                 */
2670                 r.NodeList.prototype = {
2671
2672                         /**
2673                         @name glow.dom.NodeList#item
2674                         @function
2675                         @description Returns a node from the NodeList.
2676
2677                         @param {Number} index The numeric index of the node to return.
2678
2679                         @returns {Element}
2680
2681                         @example
2682                                 // get the fourth node
2683                                 var node = myNodeList.item(3);
2684
2685                         @example
2686                                 // another way to get the fourth node
2687                                 var node = myNodeList[3];
2688                         */
2689                         item: function (nIndex) {
2690                                 return this[nIndex];
2691                         },
2692
2693                         /**
2694                         @name glow.dom.NodeList#push
2695                         @function
2696                         @description Adds Elements to the NodeList.
2697
2698                         @param {Element | Element[] | glow.dom.NodeList} nodespec+ One or more Elements, Arrays of Elements or NodeLists.
2699
2700                         @returns {glow.dom.NodeList}
2701
2702                         @example
2703                                 var myNodeList = glow.dom.create("<div>Hello world</div>");
2704                                 myNodeList.push("<div>Foo</div>", glow.dom.get("#myList li"));
2705                         */
2706                         push: function() {
2707                                 var args = arguments,
2708                                         argsLen = args.length,
2709                                         i = 0,
2710                                         n,
2711                                         nNodeListLength,
2712                                         that = this,
2713                                         arrayPush = Array.prototype.push;
2714
2715                                 for (; i < argsLen; i++) {
2716                                         if (!args[i]) {
2717                                                 continue;
2718                                         } else if (args[i].nodeType == 1 || args[i].nodeType == 9 || args[i].document) { //is Node
2719                                                 arrayPush.call(that, args[i]);
2720                                         } else if (args[i][0]) { //is array or array like
2721                                                 for (n = 0, nNodeListLength = args[i].length; n < nNodeListLength; n++) {
2722                                                         arrayPush.call(that, args[i][n]);
2723                                                 }
2724                                         }
2725                                 }
2726                                 return that;
2727                         },
2728
2729                         /**
2730                         @name glow.dom.NodeList#each
2731                         @function
2732                         @description Calls a function for each node.
2733
2734                                 The supplied function will be called for each node in the NodeList.
2735                                 
2736                                 The index of the node will be provided as the first parameter, and
2737                                 'this' will refer to the node.
2738
2739                         @param {Function} callback The function to run for each node.
2740
2741                         @returns {glow.dom.NodeList}
2742
2743                         @example
2744                                 var myNodeList = glow.dom.get("a");
2745                                 myNodeList.each(function(i){
2746                                         // in this function: this == myNodeList[i]
2747                                 });
2748                         */
2749                         each: function (callback) {
2750                                 for (var i = 0, that = this, length = that.length; i < length; i++) {
2751                                         callback.call(that[i], i, that);
2752                                 }
2753                                 return that;
2754                         },
2755
2756                         /**
2757                         @name glow.dom.NodeList#eq
2758                         @function
2759                         @description Compares the NodeList to an element, array of elements or another NodeList
2760
2761                                         Returns true if the items are the same and are in the same
2762                                         order.
2763
2764                         @param {Element | Element[] | glow.dom.NodeList} nodespec The element, array or NodeList the NodeList is being compared to.
2765
2766                         @returns {Boolean}
2767
2768                         @example
2769                                 // the following returns true
2770                                 glow.dom.get('#blah').eq( document.getElementById('blah') );
2771                         */
2772                         eq: function (nodelist) {
2773                                 var that = this, i = 0, length = that.length;
2774
2775                                 if (! nodelist.push) { nodelist = [nodelist]; }
2776                                 if (nodelist.length != that.length) { return false; }
2777                                 for (; i < length; i++) {
2778                                         if (that[i] != nodelist[i]) { return false; }
2779                                 }
2780                                 return true;
2781                         },
2782
2783                         /**
2784                         @name glow.dom.NodeList#isWithin
2785                         @function
2786                         @description Tests if all the nodes are decendents of an element.
2787
2788                         @param {Element | glow.dom.NodeList} nodespec The element or NodeList that the NodeList is being tested against.
2789
2790                                 If nodespec is a NodeList, then the its first node will be used.
2791
2792                         @returns {glow.dom.NodeList}
2793
2794                         @example
2795                                 var myNodeList = glow.dom.get("input");
2796                                 if (myNodeList.isWithin(glow.dom.get("form")) {
2797                                         // do something
2798                                 }
2799                         */
2800                         isWithin: function (node) {
2801                                 if (node.push) { node = node[0]; }
2802                                 
2803                                 // missing some nodes? Return false
2804                                 if ( !node || !this.length ) { return false; }
2805                                 
2806                                 var that = this,
2807                                         i = 0,
2808                                         length = that.length,
2809                                         toTest; //node to test in manual method
2810
2811                                 if (node.contains && env.webkit >= 521) { //proprietary method, safari 2 has a wonky implementation of this, avoid, avoid, avoid
2812                                         //loop through
2813                                         for (; i < length; i++) {
2814                                                 // myNode.contains(myNode) returns true in most browsers
2815                                                 if (!(node.contains(that[i]) && that[i] != node)) { return false; }
2816                                         }
2817                                 } else if (that[0].compareDocumentPosition) { //w3 method
2818                                         //loop through
2819                                         for (; i < length; i++) {
2820                                                 //compare against bitmask
2821                                                 if (!(that[i].compareDocumentPosition(node) & 8)) { return false; }
2822                                         }
2823                                 } else { //manual method
2824                                         for (; i < length; i++) {
2825                                                 toTest = that[i];
2826                                                 while (toTest = toTest.parentNode) {
2827                                                         if (toTest == node) { break; }
2828                                                 }
2829                                                 if (!toTest) { return false; }
2830                                         }
2831                                 }
2832                                 return true;
2833                         },
2834
2835                         /**
2836                         @name glow.dom.NodeList#attr
2837                         @function
2838                         @description Gets or sets attributes
2839
2840                                 When getting an attribute, it is retrieved from the first
2841                                 node in the NodeList. Setting attributes applies the change
2842                                 to each element in the NodeList.
2843
2844                                 To set an attribute, pass in the name as the first
2845                                 parameter and the value as a second parameter.
2846
2847                                 To set multiple attributes in one call, pass in an object of
2848                                 name/value pairs as a single parameter.
2849
2850                                 For browsers that don't support manipulating attributes
2851                                 using the DOM, this method will try to do the right thing
2852                                 (i.e. don't expect the semantics of this method to be
2853                                 consistent across browsers as this is not possible with
2854                                 currently supported browsers).
2855
2856                         @param {String | Object} name The name of the attribute, or an object of name/value pairs
2857                         @param {String} [value] The value to set the attribute to.
2858
2859                         @returns {String | glow.dom.NodeList}
2860
2861                                 When setting attributes it returns the NodeList, otherwise
2862                                 returns the attribute value.
2863
2864                         @example
2865                                 var myNodeList = glow.dom.get(".myImgClass");
2866
2867                                 // get an attribute
2868                                 myNodeList.attr("class");
2869
2870                                 // set an attribute
2871                                 myNodeList.attr("class", "anotherImgClass");
2872
2873                                 // set multiple attributes
2874                                 myNodeList.attr({
2875                                   src: "a.png",
2876                                   alt: "Cat jumping through a field"
2877                                 });
2878                         */
2879                         attr: function (name /* , val */) {
2880                                 var that = this,
2881                                         args = arguments,
2882                                         argsLen = args.length,
2883                                         i,
2884                                         value;
2885
2886                                 if (that.length === 0) {
2887                                         return argsLen > 1 ? that : undefined;
2888                                 }
2889                                 if (typeof name == 'object') {
2890                                         for (i in name) {
2891                                                 if (lang.hasOwnProperty(name, i)) {
2892                                                         that.attr(i, name[i]);
2893                                                 }
2894                                         }
2895                                         return that;
2896                                 }
2897                                 if (env.ie && dom0PropertyMapping[name]) {
2898                                         if (argsLen > 1) {
2899                                                 setAttribute.call(
2900                                                         that,
2901                                                         args[1],
2902                                                         // in the callback this is the dom node
2903                                                         function (val) { this[dom0PropertyMapping[name]] = val; }
2904                                                 );
2905                                                 return that;
2906                                         }
2907                                         value = that[0][dom0PropertyMapping[name]];
2908                                         if (dom0BooleanAttribute[name]) {
2909                                                 return value ? name : undefined;
2910                                         }
2911                                         else if (dom0AttributeMappings[name]) {
2912                                                 return dom0AttributeMappings[name](value);
2913                                         }
2914                                         return value;
2915                                 }
2916                                 if (argsLen > 1) {
2917                                         setAttribute.call(
2918                                                 that,
2919                                                 args[1],
2920                                                 // in the callback this is the dom node
2921                                                 function (val) { this.setAttribute(name, val); }
2922                                         );
2923                                         return that;
2924                                 }
2925                                 //2nd parameter makes IE behave, but errors for XML nodes (and isn't needed for xml nodes)
2926                                 return isXml(that[0]) ? that[0].getAttribute(name) : that[0].getAttribute(name, 2);
2927                         },
2928
2929                         /**
2930                         @name glow.dom.NodeList#removeAttr
2931                         @function
2932                         @description Removes an attribute from each node.
2933
2934                         @param {String} name The name of the attribute to remove.
2935
2936                         @returns {glow.dom.NodeList}
2937
2938                         @example
2939                                 glow.dom.get("a").removeAttr("target");
2940                         */
2941                         removeAttr: function (name) {
2942                                 var mapping = env.ie && dom0PropertyMapping[name],
2943                                         that = this,
2944                                         i = 0,
2945                                         length = that.length;
2946
2947                                 for (; i < length; i++) {
2948                                         if (mapping) {
2949                                                 that[i][mapping] = "";
2950                                         } else {
2951                                                 that[i].removeAttribute(name);
2952                                         }
2953                                 }
2954                                 return that;
2955                         },
2956
2957                         /**
2958                         @name glow.dom.NodeList#hasAttr
2959                         @function
2960                         @description Does the node have a particular attribute?
2961                                 
2962                                 The first node in the NodeList is tested
2963                                 
2964                         @param {String} name The name of the attribute to test for.
2965
2966                         @returns {Boolean}
2967
2968                         @example
2969                                 if ( glow.dom.get("#myImg").hasAttr("alt") ){
2970                                         // ...
2971                                 }
2972                         */
2973                         hasAttr: function (name) {
2974                                 var firstNode = this[0],
2975                                         attributes = firstNode.attributes;
2976
2977                                 if (isXml(firstNode) && env.ie) { //getAttributeNode not supported for XML
2978                                         var attributes = firstNode.attributes,
2979                                                 i = 0,
2980                                                 len = attributes.length;
2981
2982                                         //named node map doesn't seem to work properly, so need to go by index
2983                                         for (; i < len; i++) {
2984                                                 if (attributes[i].nodeName == name) {
2985                                                         return attributes[i].specified;
2986                                                 }
2987                                         }
2988                                         return false;
2989                                 } else if (this[0].getAttributeNode) {
2990                                         var attr = this[0].getAttributeNode(name);
2991                                         return attr ? attr.specified : false;
2992                                 }
2993
2994                                 return typeof attributes[attr] != "undefined";
2995                         },
2996                         
2997                         /**
2998                         @name glow.dom.NodeList#prop
2999                         @function
3000                         @description Gets or sets node peropties
3001                         
3002                                 This function gets / sets node properties, to get attributes,
3003                                 see {@link glow.dom.NodeList#attr NodeList#attr}.
3004                                 
3005                                 When getting a property, it is retrieved from the first
3006                                 node in the NodeList. Setting properties to each element in
3007                                 the NodeList.
3008                                 
3009                                 To set multiple properties in one call, pass in an object of
3010                                 name/value pairs.
3011                                 
3012                         @param {String | Object} name The name of the property, or an object of name/value pairs
3013                         @param {String} [value] The value to set the property to.
3014
3015                         @returns {String | glow.dom.NodeList}
3016
3017                                 When setting properties it returns the NodeList, otherwise
3018                                 returns the property value.
3019
3020                         @example
3021                                 var myNodeList = glow.dom.get("#formElement");
3022
3023                                 // get the node name
3024                                 myNodeList.prop("nodeName");
3025
3026                                 // set a property
3027                                 myNodeList.prop("_secretValue", 10);
3028
3029                                 // set multiple properties
3030                                 myNodeList.prop({
3031                                         checked: true,
3032                                         _secretValue: 10
3033                                 });
3034                         */
3035                         prop: function(name, val) {
3036                                 
3037                                 // setting multiple
3038                                 if (name.constructor === Object) {
3039                                         var hash = name,
3040                                                 key;
3041                                         
3042                                         // loop through hash
3043                                         for (key in hash) {
3044                                                 this.prop(key, hash[key]);
3045                                         }
3046                                         return this;
3047                                 }
3048                                 
3049                                 // setting single (to all in the NodeList)
3050                                 if (val !== undefined) {
3051                                         var i = this.length;
3052                                         while (i--) {
3053                                                 this[i][name] = val;
3054                                         }
3055                                         return this;
3056                                 }
3057                                 
3058                                 // getting
3059                                 if (!this[0]) { return undefined; }
3060                                 return this[0][name];
3061                         },
3062                         
3063                         /**
3064                         @name glow.dom.NodeList#hasClass
3065                         @function
3066                         @description Tests if a node has a given class.
3067
3068                                 Will return true if any node in the NodeList has the supplied class
3069
3070                                 <p><em>This method is not applicable to XML NodeLists.</em></p>
3071
3072                         @param {String} name The name of the class to test for.
3073
3074                         @returns {Boolean}
3075
3076                         @example
3077                                 if (glow.dom.get("a").hasClass("termsLink")){
3078                                         // ...
3079                                 }
3080                         */
3081                         hasClass: function (name) {
3082                                 for (var i = 0, length = this.length; i < length; i++) {
3083                                         if ((" " + this[i].className + " ").indexOf(" " + name + " ") != -1) {
3084                                                 return true;
3085                                         }
3086                                 }
3087                                 return false;
3088                         },
3089
3090                         /**
3091                         @name glow.dom.NodeList#addClass
3092                         @function
3093                         @description Adds a class to each node.
3094
3095                                 <p><em>This method is not applicable to XML NodeLists.</em></p>
3096
3097                         @param {String} name The name of the class to add.
3098
3099                         @returns {glow.dom.NodeList}
3100
3101                         @example
3102                                 glow.dom.get("#login a").addClass("highlight");
3103                         */
3104                         addClass: function (name) {
3105                                 for (var i = 0, length = this.length; i < length; i++) {
3106                                         if ((" " + this[i].className + " ").indexOf(" " + name + " ") == -1) {
3107                                                 this[i].className += ((this[i].className)? " " : "") + name;
3108                                         }
3109                                 }
3110                                 return this;
3111                         },
3112
3113                         /**
3114                         @name glow.dom.NodeList#removeClass
3115                         @function
3116                         @description Removes a class from each node.
3117
3118                                 <p><em>This method is not applicable to XML NodeLists.</em></p>
3119
3120                         @param {String} name The name of the class to remove.
3121
3122                         @returns {glow.dom.NodeList}
3123
3124                         @example
3125                                 glow.dom.get("#footer #login a").removeClass("highlight");
3126                         */
3127                         removeClass: function (name) {
3128                                 var re = removeClassRegex(name),
3129                                         that = this,
3130                                         i = 0,
3131                                         length = that.length;
3132
3133                                 for (; i < length; i++) {
3134                                         that[i].className = that[i].className.replace(re, " ");
3135                                 }
3136                                 return that;
3137                         },
3138
3139                         /**
3140                         @name glow.dom.NodeList#toggleClass
3141                         @function
3142                         @description Toggles a class on each node.
3143
3144                                 <p><em>This method is not applicable to XML NodeLists.</em></p>
3145
3146                         @param {String} name The name of the class to toggle.
3147
3148                         @returns {glow.dom.NodeList}
3149
3150                         @example
3151                                 glow.dom.get(".onOffSwitch").toggleClass("on");
3152                          */
3153                         toggleClass: function (name) {
3154                                 var i = this.length,
3155                                         paddedClassName,
3156                                         paddedName = " " + name + " ";
3157                                         
3158                                 while (i--) {
3159                                         paddedClassName = " " + this[i].className + " ";
3160                                         
3161                                         if (paddedClassName.indexOf(paddedName) != -1) {
3162                                                 this[i].className = paddedClassName.replace(paddedName, " ");
3163                                         } else {
3164                                                 this[i].className += " " + name;
3165                                         }
3166                                 }
3167                                 return this;
3168                         },
3169
3170                         /**
3171                         @name glow.dom.NodeList#val
3172                         @function
3173                         @description Gets or sets form values for the first node.
3174
3175                                 <p><em>This method is not applicable to XML NodeLists.</em></p>
3176
3177                                 <p><em>Getting values from form elements</em></p>
3178
3179                                 The returned value depends on the type of element, see below:
3180
3181                                 <dl>
3182                                 <dt>Radio button or checkbox</dt>
3183                                 <dd>If checked, then the contents of the value attribute, otherwise an empty string.</dd>
3184                                 <dt>Select</dt>
3185                                 <dd>The contents of value attribute of the selected option</dd>
3186                                 <dt>Select (multiple)</dt>
3187                                 <dd>An array of selected option values.</dd>
3188                                 <dt>Other form element</dt>
3189                                 <dd>The value of the input.</dd>
3190                                 </dl>
3191
3192                                 <p><em>Getting values from a form</em></p>
3193
3194                                 If the first element in the NodeList is a form, then an
3195                                 object is returned containing the form data. Each item
3196                                 property of the object is a value as above, apart from when
3197                                 multiple elements of the same name exist, in which case the
3198                                 it will contain an array of values.
3199
3200                                 <p><em>Setting values for form elements</em></p>
3201
3202                                 If a value is passed and the first element of the NodeList
3203                                 is a form element, then the form element is given that value.
3204                                 For select elements, this means that the first option that
3205                                 matches the value will be selected. For selects that allow
3206                                 multiple selection, the options which have a value that
3207                                 exists in the array of values/match the value will be
3208                                 selected and others will be deselected.
3209
3210                                 Currently checkboxes and radio buttons are not checked or
3211                                 unchecked, just their value is changed. This does mean that
3212                                 this does not do exactly the reverse of getting the value
3213                                 from the element (see above) and as such may be subject to
3214                                 change
3215
3216                                 <p><em>Setting values for forms</em></p>
3217
3218                                 If the first element in the NodeList is a form and the
3219                                 value is an object, then each element of the form has its
3220                                 value set to the corresponding property of the object, using
3221                                 the method described above.
3222
3223                         @param {String | Object} [value] The value to set the form element/elements to.
3224
3225                         @returns {glow.dom.NodeList | String | Object}
3226
3227                                 When used to set a value it returns the NodeList, otherwise
3228                                 returns the value as described above.
3229
3230                         @example
3231                                 // get a value
3232                                 var username = glow.dom.get("input#username").val();
3233
3234                         @example
3235                                 // get values from a form
3236                                 var userDetails = glow.dom.get("form").val();
3237
3238                         @example
3239                                 // set a value
3240                                 glow.dom.get("input#username").val("example username");
3241
3242                         @example
3243                                 // set values in a form
3244                                 glow.dom.get("form").val({
3245                                         username : "another",
3246                                         name     : "A N Other"
3247                                 });
3248                         */
3249                         val: function () {
3250
3251                                 /*
3252                                 PrivateFunction: elementValue
3253                                         Get a value for a form element.
3254                                 */
3255                                 function elementValue (el) {
3256                                         var elType = el.type,
3257                                                 elChecked = el.checked,
3258                                                 elValue = el.value,
3259                                                 vals = [],
3260                                                 i = 0;
3261
3262                                         if (elType == "radio") {
3263                                                 return elChecked ? elValue : "";
3264                                         } else if (elType == "checkbox") {
3265                                                 return elChecked ? elValue : "";
3266
3267                                         } else if (elType == "select-one") {
3268                                                 return el.selectedIndex > -1 ?
3269                                                         el.options[el.selectedIndex].value : "";
3270
3271                                         } else if (elType == "select-multiple") {
3272                                                 for (var length = el.options.length; i < length; i++) {
3273                                                         if (el.options[i].selected) {
3274                                                                 vals[vals.length] = el.options[i].value;
3275                                                         }
3276                                                 }
3277                                                 return vals;
3278                                         } else {
3279                                                 return elValue;
3280                                         }
3281                                 }
3282
3283                                 /*
3284                                 PrivateMethod: formValues
3285                                         Get an object containing form data.
3286                                 */
3287                                 function formValues (form) {
3288                                         var vals = {},
3289                                                 radios = {},
3290                                                 formElements = form.elements,
3291                                                 i = 0,
3292                                                 length = formElements.length,
3293                                                 name,
3294                                                 formElement,
3295                                                 j,
3296                                                 radio,
3297                                                 nodeName;
3298
3299                                         for (; i < length; i++) {
3300                                                 formElement = formElements[i];
3301                                                 nodeName = formElement.nodeName.toLowerCase();
3302                                                 name = formElement.name;
3303                                                 
3304                                                 // fieldsets & objects come back as form elements, but we don't care about these
3305                                                 // we don't bother with fields that don't have a name
3306                                                 // switch to whitelist?
3307                                                 if (
3308                                                         nodeName == "fieldset" ||
3309                                                         nodeName == "object" ||
3310                                                         !name
3311                                                 ) { continue; }
3312
3313                                                 if (formElement.type == "checkbox" && ! formElement.checked) {
3314                                                         if (! name in vals) {
3315                                                                 vals[name] = undefined;
3316                                                         }
3317                                                 } else if (formElement.type == "radio") {
3318                                                         if (radios[name]) {
3319                                                                 radios[name][radios[name].length] = formElement;
3320                                                         } else {
3321                                                                 radios[name] = [formElement];
3322                                                         }
3323                                                 } else {
3324                                                         var value = elementValue(formElement);
3325                                                         if (name in vals) {
3326                                                                 if (vals[name].push) {
3327                                                                         vals[name][vals[name].length] = value;
3328                                                                 } else {
3329                                                                         vals[name] = [vals[name], value];
3330                                                                 }
3331                                                         } else {
3332                                                                 vals[name] = value;
3333                                                         }
3334                                                 }
3335                                         }
3336                                         for (i in radios) {
3337                                                 j = 0;
3338                                                 for (length = radios[i].length; j < length; j++) {
3339                                                         radio = radios[i][j];
3340                                                         name = radio.name;
3341                                                         if (radio.checked) {
3342                                                                 vals[radio.name] = radio.value;
3343                                                                 break;
3344                                                         }
3345                                                 }
3346                                                 if (! name in vals) { vals[name] = undefined; }
3347                                         }
3348                                         return vals;
3349                                 }
3350
3351                                 /*
3352                                 PrivateFunction: setFormValues
3353                                         Set values of a form to those in passed in object.
3354                                 */
3355                                 function setFormValues (form, vals) {
3356                                         var prop, currentField,
3357                                                 fields = {},
3358                                                 storeType, i = 0, n, len, foundOne, currentFieldType;
3359
3360                                         for (prop in vals) {
3361                                                 currentField = form[prop];
3362                                                 if (currentField && currentField[0] && !currentField.options) { // is array of fields
3363                                                         //normalise values to array of vals
3364                                                         vals[prop] = vals[prop] && vals[prop].push ? vals[prop] : [vals[prop]];
3365                                                         //order the fields by types that matter
3366                                                         fields.radios = [];
3367                                                         fields.checkboxesSelects = [];
3368                                                         fields.multiSelects = [];
3369                                                         fields.other = [];
3370
3371                                                         for (i = 0; currentField[i]; i++) {
3372                                                                 currentFieldType = currentField[i].type;
3373                                                                 if (currentFieldType == "radio") {
3374                                                                         storeType = "radios";
3375                                                                 } else if (currentFieldType == "select-one" || currentFieldType == "checkbox") {
3376                                                                         storeType = "checkboxesSelects";
3377                                                                 } else if (currentFieldType == "select-multiple") {
3378                                                                         storeType = "multiSelects";
3379                                                                 } else {
3380                                                                         storeType = "other";
3381                                                                 }
3382                                                                 //add it to the correct array
3383                                                                 fields[storeType][fields[storeType].length] = currentField[i];
3384                                                         }
3385
3386                                                         for (i = 0; fields.multiSelects[i]; i++) {
3387                                                                 vals[prop] = setValue(fields.multiSelects[i], vals[prop]);
3388                                                         }
3389                                                         for (i = 0; fields.checkboxesSelects[i]; i++) {
3390                                                                 setValue(fields.checkboxesSelects[i], "");
3391                                                                 for (n = 0, len = vals[prop].length; n < len; n++) {
3392                                                                         if (setValue(fields.checkboxesSelects[i], vals[prop][n])) {
3393                                                                                 vals[prop].slice(n, 1);
3394                                                                                 break;
3395                                                                         }
3396                                                                 }
3397                                                         }
3398                                                         for (i = 0; fields.radios[i]; i++) {
3399                                                                 fields.radios[i].checked = false;
3400                                                                 foundOne = false;
3401                                                                 for (n = 0, len = vals[prop].length; n < len; n++) {
3402                                                                         if (setValue(fields.radios[i], vals[prop][n])) {
3403                                                                                 vals[prop].slice(n, 1);
3404                                                                                 foundOne = true;
3405                                                                                 break;
3406                                                                         }
3407                                                                         if (foundOne) { break; }
3408                                                                 }
3409                                                         }
3410                                                         for (i = 0; fields.other[i] && vals[prop][i] !== undefined; i++) {
3411                                                                 setValue(fields.other[i], vals[prop][i]);
3412                                                         }
3413                                                 } else if (currentField && currentField.nodeName) { // is single field, easy
3414                                                         setValue(currentField, vals[prop]);
3415                                                 }
3416                                         }
3417                                 }
3418
3419                                 /*
3420                                 PrivateFunction: setValue
3421                                         Set the value of a form element.
3422
3423                                 Returns:
3424                                         values that weren't able to set if array of vals passed (for multi select). Otherwise true if val set, false if not
3425                                 */
3426                                 function setValue (el, val) {
3427                                         var i = 0,
3428                                                 length,
3429                                                 n = 0,
3430                                                 nlen,
3431                                                 elOption,
3432                                                 optionVal;
3433
3434                                         if (el.type == "select-one") {
3435                                                 for (length = el.options.length; i < length; i++) {
3436                                                         if (el.options[i].value == val) {
3437                                                                 el.selectedIndex = i;
3438                                                                 return true;
3439                                                         }
3440                                                 }
3441                                                 return false;
3442                                         } else if (el.type == "select-multiple") {
3443                                                 var isArray = !!val.push;
3444                                                 for (i = 0, length = el.options.length; i < length; i++) {
3445                                                         elOption = el.options[i];
3446                                                         optionVal = elOption.value;
3447                                                         if (isArray) {
3448                                                                 elOption.selected = false;
3449                                                                 for (nlen = val.length; n < nlen; n++) {
3450                                                                         if (optionVal == val[n]) {
3451                                                                                 elOption.selected = true;
3452                                                                                 val.splice(n, 1);
3453                                                                                 break;
3454                                                                         }
3455                                                                 }
3456                                                         } else {
3457                                                                 return elOption.selected = val == optionVal;
3458                                                         }
3459                                                 }
3460                                                 return false;
3461                                         } else if (el.type == "radio" || el.type == "checkbox") {
3462                                                 el.checked = val == el.value;
3463                                                 return val == el.value;
3464                                         } else {
3465                                                 el.value = val;
3466                                                 return true;
3467                                         }
3468                                 }
3469
3470                                 // toplevel implementation
3471                                 return function (/* [value] */) {
3472                                         var args = arguments,
3473                                                 val = args[0],
3474                                                 that = this,
3475                                                 i = 0,
3476                                                 length = that.length;
3477
3478                                         if (args.length === 0) {
3479                                                 return that[0].nodeName == 'FORM' ?
3480                                                         formValues(that[0]) :
3481                                                         elementValue(that[0]);
3482                                         }
3483                                         if (that[0].nodeName == 'FORM') {
3484                                                 if (! typeof val == 'object') {
3485                                                         throw 'value for FORM must be object';
3486                                                 }
3487                                                 setFormValues(that[0], val);
3488                                         } else {
3489                                                 for (; i < length; i++) {
3490                                                         setValue(that[i], val);
3491                                                 }
3492                                         }
3493                                         return that;
3494                                 };
3495                         }(),
3496
3497                         /**
3498                         @name glow.dom.NodeList#slice
3499                         @function
3500                         @description Extracts nodes from a NodeList and returns them as a new NodeList.
3501
3502                         @param {Number} start The NodeList index at which to begin extraction.
3503
3504                                 If negative, this param specifies a position measured from
3505                                 the end of the NodeList
3506
3507                         @param {Number} [end] The NodeList index immediately after the end of the extraction.
3508
3509                                 If not specified the extraction includes all nodes from the
3510                                 start to the end of the NodeList. A Negative end specifies
3511                                 an position measured from the end of the NodeList.
3512
3513                         @returns {glow.dom.NodeList}
3514
3515                                 Returns a new NodeList containing the extracted nodes
3516
3517                         @example
3518                                 var myNodeList = glow.dom.create("<div></div><div></div>");
3519                                 myNodeList = myNodeList.slice(1, 2); // just second div
3520                          */
3521                         slice: function (/* start, end */) {
3522                                 return new r.NodeList().push(Array.prototype.slice.apply(this, arguments));
3523                         },
3524
3525                         /**
3526                         @name glow.dom.NodeList#sort
3527                         @function
3528                         @description Returns a new NodeList containing the same nodes in order.
3529
3530                                 Sort order defaults to document order if no sort function is passed in.
3531
3532                         @param {Function} [func] Function to determine sort order
3533
3534                                 This function will be passed 2 nodes (a, b). The function
3535                                 should return a number less than 0 to sort a lower than b
3536                                 and greater than 0 to sort a higher than b.
3537
3538                         @returns {glow.dom.NodeList}
3539
3540                                 Returns a new NodeList containing the sorted nodes
3541
3542                         @example
3543                                 // heading elements in document order
3544                                 var headings = glow.dom.get("h1, h2, h3, h4, h5, h6").sort();
3545
3546                                 //get links in alphabetical (well, lexicographical) order
3547                                 var links = glow.dom.get("a").sort(function(a, b) {
3548                                         return ((a.textContent || a.innerText) < (b.textContent || b.innerText)) ? -1 : 1;
3549                                 })
3550                         */
3551                         sort: function(func) {
3552                                 var that = this, i=0, aNodes;
3553
3554                                 if (!that.length) { return that; }
3555                                 if (!func) {
3556                                         if (typeof that[0].sourceIndex == "number") {
3557                                                 // sourceIndex is IE proprietary (but Opera supports)
3558                                                 func = function(a, b) {
3559                                                         return a.sourceIndex - b.sourceIndex;
3560                                                 };
3561                                         } else if (that[0].compareDocumentPosition) {
3562                                                 // DOM3 method
3563                                                 func = function(a, b) {
3564                                                         return 3 - (a.compareDocumentPosition(b) & 6);
3565                                                 };
3566                                         } else {
3567                                                 // js emulation of sourceIndex
3568                                                 aNodes = getByTagName("*", [doc]);
3569                                                 for (; aNodes[i]; i++) {
3570                                                         aNodes[i]._sourceIndex = i;
3571                                                 }
3572                                                 func = function(a, b) {
3573                                                         return a._sourceIndex - b._sourceIndex;
3574                                                 };
3575                                         }
3576                                 }
3577
3578                                 return r.get([].sort.call(that, func));
3579                         },
3580
3581                         /**
3582                         @name glow.dom.NodeList#filter
3583                         @function
3584                         @description Filter the NodeList using a function
3585
3586                                 The supplied function will be called for each node in the NodeList.
3587                                 
3588                                 The index of the node will be provided as the first parameter, and
3589                                 'this' will refer to the node.
3590                                 
3591                                 Return true to keep the node, or false to remove it.
3592
3593                         @param {Function} func Function to test each node
3594
3595                         @returns {glow.dom.NodeList}
3596
3597                                 Returns a new NodeList containing the filtered nodes
3598
3599                         @example
3600                                 // return images with a width greater than 320
3601                                 glow.dom.get("img").filter(function (i) {
3602                                         return this.width > 320;
3603                                 });
3604                          */
3605                         filter: function(callback) {
3606                                 var ret = [], //result
3607                                         ri = 0,
3608                                         i = 0,
3609                                         length = this.length;
3610                                 for (; i < length; i++) {
3611                                         if (callback.apply(this[i], [i])) {
3612                                                 ret[ri++] = this[i];
3613                                         }
3614                                 }
3615                                 return r.get(ret);
3616                         },
3617
3618                         /**
3619                         @name glow.dom.NodeList#children
3620                         @function
3621                         @description Gets the child elements of each node as a new NodeList.
3622
3623                         @returns {glow.dom.NodeList}
3624
3625                                 Returns a new NodeList containing all the child nodes
3626
3627                         @example
3628                                 // get all list items
3629                                 var items = glow.dom.get("ul, ol").children();
3630                          */
3631                         children: function() {
3632                                 var ret = [],
3633                                         ri = 0,
3634                                         i = 0,
3635                                         n = 0,
3636                                         length = this.length,
3637                                         childTmp;
3638                                 
3639                                 for (; i < length; i++) {
3640                                         ret = ret.concat( getChildElms(this[i]) );
3641                                 }
3642                                 return r.get(ret);
3643                         },
3644
3645                         /**
3646                         @name glow.dom.NodeList#parent
3647                         @function
3648                         @description Gets the unique parent nodes of each node as a new NodeList.
3649                         The given nodelist will always be placed in the first element with no child elements.
3650
3651                         @returns {glow.dom.NodeList}
3652
3653                                 Returns a new NodeList containing the parent nodes, with
3654                                 duplicates removed
3655
3656                         @example
3657                                 // elements which contain links
3658                                 var parents = glow.dom.get("a").parent();
3659                         */
3660                         parent: function() {
3661                                 var ret = [],
3662                                         ri = 0,
3663                                         i = 0,
3664                                         length = this.length;
3665
3666                                 for (; i < length; i++) {
3667                                         ret[ri++] = this[i].parentNode;
3668                                 }
3669                                 
3670                                 return r.get(unique(ret));
3671                         },
3672                         
3673                         /**
3674                         @name glow.dom.NodeList#ancestors
3675                         @function
3676                         @description Gets the unique ancestor nodes of each node as a new NodeList.
3677
3678                         @returns {glow.dom.NodeList}
3679
3680                                 Returns NodeList
3681
3682                         @example
3683                                 // get ancestor elements for anchor elements 
3684                                 var ancestors = glow.dom.get("a").ancestors();
3685                         */
3686                         ancestors: function() {
3687                                 var ret = [],
3688                                         ri = 0,
3689                                         i = 0,
3690                                         length = this.length,
3691                                         elm;
3692                                         
3693                                 while (i < length) {
3694                                         elm = this[i].parentNode;
3695                                         
3696                                         while (elm && elm.nodeType == 1) {                                                      
3697                                                 ret[ri++] = elm;
3698                                                 elm = elm.parentNode;
3699                                         }                                                               
3700                                         i++;
3701                                 }
3702                                 
3703                                 return r.get(unique(ret));
3704                         },
3705                         
3706                         
3707                         /**
3708                         @name glow.dom.NodeList#wrap
3709                         @function
3710                         @description Wraps the given NodeList with the specified element(s).
3711                         
3712                                 The given NodeList items will always be placed in the first child node that contains no further element nodes.
3713                                 
3714                                 Each item in a given NodeList will be wrapped individually.
3715
3716                         @returns {glow.dom.NodeList}
3717
3718                                 Returns the NodeList with new wrapper parents
3719
3720                         @example
3721                                 // wrap the given element 
3722                                 glow.dom.get("p").wrap("<div></div>");
3723                                 // <div><p></p></div>
3724                         */
3725                         wrap: function(wrapper) {
3726                                 var length = this.length,
3727                                         childElm,
3728                                         parent,
3729                                         wrappingNodes;
3730                                         
3731                                 if (typeof wrapper == 'string') {
3732                                         wrappingNodes = r.create(wrapper);
3733                                 }
3734                                 else {
3735                                         wrappingNodes = r.get(wrapper);
3736                                 }
3737                                                 
3738                                 for (i=0; i < length; i++) {                                    
3739                                         parent = wrappingNodes[0];
3740                                         
3741                                         while (parent) {                                        
3742                                                 childElm = getFirstChildElm(parent);
3743                                                         
3744                                                 if (childElm) {                                 
3745                                                         parent = childElm;
3746                                                 }
3747                                                 else {
3748                                                         break;
3749                                                 }
3750                                         }                                                       
3751                                         
3752                                         if (this[i].parentNode) {                                               
3753                                                 wrappingNodes.insertBefore(this[i]);                                                                                                    
3754                                         }
3755                                         // If wrapping multiple nodes, we need to take a clean copy of the wrapping nodes
3756                                         if (i != length-1) {
3757                                                 wrappingNodes = wrappingNodes.clone();
3758                                         }
3759                                         
3760                                         parent.appendChild(this[i]);
3761
3762                                 }
3763                                 
3764                                 return this;
3765                         },
3766                         
3767                         /**
3768                         @name glow.dom.NodeList#unwrap
3769                         @function
3770                         @description Removes the first unique parent for each item of a supplied NodeList
3771
3772                         @returns {glow.dom.NodeList}
3773
3774                                 Returns the unwrapped NodeList
3775
3776                         @example
3777                                 // Before: <div><div><p></p></div></div>
3778                                 // unwrap the given element
3779                                 glow.dom.get("p").unwrap();
3780                                 // After: <div><p></p></div>
3781                         */
3782                         unwrap: function() {
3783                                 var toRemove,
3784                                         nodesToRemove = this.parent(),
3785                                         length = nodesToRemove.length;
3786                                 
3787                                 for (i=0; i < length; i++) {                            
3788                                         toRemove = nodesToRemove.slice(i, i+1);
3789                                         // if the item we're removing has no new parent (i.e. is not in document), then we just remove the child and destroy the old parent
3790                                         if (!toRemove[0].parentNode){
3791                                                 toRemove.children().remove();
3792                                                 toRemove.destroy();
3793                                         }
3794                                         else {
3795                                                 toRemove.children().insertBefore(toRemove);
3796                                                 toRemove.destroy();                                                     
3797                                         }                                               
3798
3799                                 }
3800                                 return this;
3801                         },
3802
3803                         /**
3804                         @name glow.dom.NodeList#next
3805                         @function
3806                         @description Gets the next sibling element for each node as a new NodeList.
3807
3808                         @returns {glow.dom.NodeList}
3809
3810                                 A new NodeList containing the next sibling elements.
3811
3812                         @example
3813                                 // gets the element following #myLink (if there is one)
3814                                 var next = glow.dom.get("#myLink").next();
3815                         */
3816                         next: function() {
3817                                 return getNextOrPrev(this, "next");
3818                         },
3819
3820                         /**
3821                         @name glow.dom.NodeList#prev
3822                         @function
3823                         @description Gets the previous sibling element for each node as a new NodeList.
3824
3825                         @returns {glow.dom.NodeList}
3826
3827                                 A new NodeList containing the previous sibling elements.
3828
3829                         @example
3830                                 // gets the elements before #myLink (if there is one)
3831                                 var previous = glow.dom.get("#myLink").previous();
3832                         */
3833                         prev: function() {
3834                                 return getNextOrPrev(this, "previous");
3835                         },
3836
3837                         /**
3838                         @name glow.dom.NodeList#is
3839                         @function
3840                         @description Tests if all the nodes match a CSS selector.
3841                         
3842                                 Jake: I'm deprecating this until we have time to make it faster (probably when we change our CSS selector engine)
3843                         
3844                         @deprecated
3845                         @param {String} selector A CSS selector string
3846
3847                         @returns {Boolean}
3848
3849                         @example
3850                                 var bigHeadings = glow.dom.get("h1, h2, h3");
3851
3852                                 // true
3853                                 if (bigHeadings.is("h1, h2, h3, h4, h5, h6")) ...
3854
3855                                 // false
3856                                 if (bigHeadings.is("a")) ...
3857                         */
3858                         is: function (selector) {
3859                                 // TODO - this implementation needs to be optimized
3860                                 var nodes = glow.dom.get(selector),
3861                                         i = 0,
3862                                         iLen = this.length,
3863                                         j,
3864                                         jLen;
3865
3866                                 node:
3867                                 for (; i < iLen; i++) {
3868                                         for (j = 0, jLen = nodes.length; j < jLen; j++) {
3869                                                 if (this[i] == nodes[j]) {
3870                                                         continue node;
3871                                                 }
3872                                         }
3873                                         return false;
3874                                 }
3875                                 return true;
3876                         },
3877
3878                         /**
3879                         @name glow.dom.NodeList#text
3880                         @function
3881                         @description Gets the inner text of the first node, or set the inner text of all matched nodes.
3882
3883                         @param {String} [text] String to set as inner text of nodes
3884
3885                         @returns {glow.dom.NodeList | String}
3886
3887                                 If the text argument is passed then the NodeList is
3888                                 returned, otherwise the text is returned.
3889
3890                         @example
3891                                 // set text
3892                                 var div = glow.dom.create("<div></div>").text("Hello World!");
3893
3894                                 // get text
3895                                 var greeting = div.text();
3896                         */
3897                         text: function (/* text */) {
3898                                 var args = arguments,
3899                                         i = 0,
3900                                         that = this,
3901                                         length = that.length;
3902
3903                                 if (args.length > 0) {
3904                                         for (; i < length; i++) {
3905                                                 that[i].innerHTML = "";
3906                                                 that[i].appendChild(doc.createTextNode(args[0]));
3907                                         }
3908                                         return that;
3909                                 }
3910                                 //innerText (empty) and textContent (undefined) don't work in safari 2 for hidden elements
3911                                 return that[0].innerText || that[0].textContent == undefined ? getTextNodeConcat(that[0]) : that[0].textContent;
3912                         },
3913
3914                         /**
3915                         @name glow.dom.NodeList#empty
3916                         @function
3917                         @description Removes the contents of all the nodes.
3918
3919                         @returns {glow.dom.NodeList}
3920
3921                         @example
3922                                 // remove the contents of all textareas
3923                                 glow.dom.get("textarea").empty();
3924                         */
3925                         empty: function () {
3926                                 /*
3927                                   NOTE: I changed this to destroy all nodes within the parent, but
3928                                   seemed backwards incompatible with our own timetable demo
3929                                   so we best hadn't do it until Glow 2
3930                                 */
3931                                 var i = 0,
3932                                         len = this.length;
3933                                         
3934                                 for (; i < len; i++) {
3935                                         while(this[i].firstChild) {
3936                                                 this[i].removeChild(this[i].firstChild);
3937                                         }
3938                                 }
3939                                 return this;
3940                         },
3941
3942                         /**
3943                         @name glow.dom.NodeList#remove
3944                         @function
3945                         @description Removes each node from its parent node.
3946                                 If you no longer need the nodes, consider using
3947                                 {@link glow.dom.NodeList#destroy destroy}
3948                                 
3949                         @returns {glow.dom.NodeList}
3950
3951                         @example
3952                                 // take all the links out of a document
3953                                 glow.dom.get("a").remove();
3954                         */
3955                         remove: function () {
3956                                 for (var that = this, i = 0, length = that.length, parentNode; i < length; i++) {
3957                                         if (parentNode = that[i].parentNode) {
3958                                                 parentNode.removeChild(that[i]);
3959                                         }
3960                                 }
3961                                 return that;
3962                         },
3963                         
3964                         /**
3965                         @name glow.dom.NodeList#destroy
3966                         @function
3967                         @description Removes each node from the DOM
3968                                 The node will actually be destroyed to free up memory
3969
3970                         @returns {glow.dom.NodeList} An empty NodeList
3971
3972                         @example
3973                                 // destroy all links in the document
3974                                 glow.dom.get("a").destroy();
3975                         */
3976                         destroy: function () {
3977                                 // remove any data attached to this NodeList
3978                                 this.get("*").push(this).removeData();
3979                                 
3980                                 this.appendTo(tmpDiv);
3981                                 // destroy nodes
3982                                 tmpDiv.innerHTML = "";
3983                                 // empty the nodelist
3984                                 
3985                                 Array.prototype.splice.call(this, 0, this.length);
3986                                 return this;
3987                         },
3988
3989                         /**
3990                         @name glow.dom.NodeList#clone
3991                         @function
3992                         @description Gets a new NodeList containing a clone of each node.
3993                         
3994                         @param {Boolean} [cloneListeners=false] Also clone any event listeners assigned using Glow
3995                         
3996                         @returns {glow.dom.NodeList}
3997
3998                                 Returns a new NodeList containing clones of all the nodes in
3999                                 the NodeList
4000
4001                         @example
4002                                 // get a copy of all heading elements
4003                                 var myClones = glow.dom.get("h1, h2, h3, h4, h5, h6").clone();
4004                                 
4005                         @example
4006                                 // get a copy of all anchor elements with 
4007                                 var myAnchors = glow.dom.get("a").clone(true);
4008                         */
4009                         clone: function (cloneListeners) {
4010                                 var ret = [],
4011                                         i = this.length,
4012                                         allCloneElms,
4013                                         allBaseElms
4014                                         eventIdProp = '__eventId' + glow.UID;
4015
4016                                 while (i--) {
4017                                         ret[i] = this[i].cloneNode(true);
4018                                 }
4019                                 
4020                                 // some browsers (ie) also clone node properties as attributes
4021                                 // we need to get rid of the eventId.
4022                                 allCloneElms = r.get( ret ).get("*").push( ret );
4023                                 if (nodePropertiesCloned && !isXml(ret[0])) {
4024                                         i = allCloneElms.length;
4025                                         while(i--) {
4026                                                 allCloneElms[i][eventIdProp] = null;
4027                                         }
4028                                 }
4029                                 
4030                                 // copy data from base elements to clone elements
4031                                 allBaseElms = this.get("*").push( this );
4032                                 i = allCloneElms.length;
4033                                 while (i--) {
4034                                         allCloneElms[i].removeAttribute(dataPropName);
4035                                         glow.dom.get(allCloneElms[i]).data(
4036                                                 glow.dom.get(allBaseElms[i]).data()
4037                                         );
4038                                 }
4039                                 
4040                                 // shall we clone events too?
4041                                 if (cloneListeners) {
4042                                         // check the stuff we need is hanging around, we don't want
4043                                         // glow.dom to be dependant on glow.events as it's a circular
4044                                         // dependency
4045                                         if ( !glow.events ) {
4046                                                 throw "glow.events required to clone event listeners";
4047                                         }
4048                                         
4049                                         glow.events._copyListeners(
4050                                                 this.get("*").push( this ),
4051                                                 allCloneElms || r.get( ret ).get("*").push( ret )
4052                                         );
4053                                 }
4054                                 
4055                                 return r.get(ret);
4056                         },
4057
4058                         /**
4059                         @name glow.dom.NodeList#html
4060                         @function
4061                         @description Gets the HTML content of the first node, or set the HTML content of all nodes.
4062
4063                                 <p><em>This method is not applicable to XML NodeLists.</em></p>
4064
4065                         @param {String} [html] String to set as inner HTML of nodes
4066
4067                         @returns {glow.dom.NodeList | String}
4068
4069                                 If the html argument is passed, then the NodeList is
4070                                 returned, otherwise the inner HTML of the first node is returned.
4071
4072                         @example
4073                                 // get the html in #footer
4074                                 var footerContents = glow.dom.get("#footer").html();
4075
4076                         @example
4077                                 // set a new footer
4078                                 glow.dom.get("#footer").html("<strong>Hello World!</strong>");
4079                         */
4080                         html: function (newHtml) {
4081                                 var i = 0,
4082                                         length = this.length;
4083
4084                                 if (newHtml !== undefined) {
4085                                         // not setting innerHTML, doesn't work in IE for elements like <table>
4086                                         return this.empty().append(newHtml);
4087                                 }
4088                                 return this[0] ? this[0].innerHTML : "";
4089                         },
4090
4091                         /**
4092                         @name glow.dom.NodeList#width
4093                         @function
4094                         @description Gets the width of the first node in pixels or sets the width of all nodes
4095
4096                                 <p><em>This method is not applicable to XML NodeLists.</em></p>
4097
4098                                 Return value does not include the padding or border of the
4099                                 element in browsers supporting the correct box model.
4100
4101                                 You can use this to easily get the width of the document or
4102                                 window, see example below.
4103
4104                         @param {Number} [width] New width in pixels.
4105
4106                         @returns {glow.dom.NodeList | Number}
4107
4108                                 Width of first element in pixels, or NodeList when setting widths
4109
4110                         @example
4111                                 // get the width of #myDiv
4112                                 glow.dom.get("#myDiv").width();
4113
4114                         @example
4115                                 // set the width of list items in #myDiv to 200 pixels
4116                                 glow.dom.get("#myDiv li").width(200);
4117
4118                         @example
4119                                 // get the height of the document
4120                                 glow.dom.get(document).width()
4121
4122                         @example
4123                                 // get the height of the window
4124                                 glow.dom.get(window).width()
4125                         */
4126                         width: function(width) {
4127                                 if (width == undefined) {
4128                                         return getElmDimension(this[0], "width");
4129                                 }
4130                                 setElmsSize(this, width, "width");
4131                                 return this;
4132                         },
4133
4134                         /**
4135                         @name glow.dom.NodeList#height
4136                         @function
4137                         @description Gets the height of the first element in pixels or sets the height of all nodes
4138
4139                                 <p><em>This method is not applicable to XML NodeLists.</em></p>
4140
4141                                  Return value does not include the padding or border of the element in
4142                                  browsers supporting the correct box model.
4143
4144                                  You can use this to easily get the height of the document or
4145                                  window, see example below.
4146
4147                         @param {Number} [height] New height in pixels.
4148
4149                         @returns {glow.dom.NodeList | Number}
4150
4151                                 Height of first element in pixels, or NodeList when setting heights.
4152
4153                         @example
4154                                 // get the height of #myDiv
4155                                 glow.dom.get("#myDiv").height();
4156
4157                         @example
4158                                 // set the height of list items in #myDiv to 200 pixels
4159                                 glow.dom.get("#myDiv li").height(200);
4160
4161                         @example
4162                                 // get the height of the document
4163                                 glow.dom.get(document).height()
4164
4165                         @example
4166                                 // get the height of the window
4167                                 glow.dom.get(window).height()
4168                         */
4169                         height: function(height) {
4170                                 if (height == undefined) {
4171                                         return getElmDimension(this[0], "height");
4172                                 }
4173                                 setElmsSize(this, height, "height");
4174                                 return this;
4175                         },
4176                         
4177                         /**
4178                         @name glow.dom.NodeList#scrollLeft
4179                         @function
4180                         @description Gets/sets the number of pixels the element has scrolled horizontally
4181                                 
4182                                 Get the value by calling without arguments, set by providing a new
4183                                 value.
4184                                 
4185                                 To get/set the scroll position of the window, use this method on
4186                                 a nodelist containing the window object.
4187
4188                         @param {Number} [val] New left scroll position
4189
4190                         @returns {glow.dom.NodeList | Number}
4191
4192                                 Current scrollLeft value, or NodeList when setting scroll position.
4193
4194                         @example
4195                                 // get the scroll left value of #myDiv
4196                                 var scrollPos = glow.dom.get("#myDiv").scrollLeft();
4197                                 // scrollPos is a number, eg: 45
4198
4199                         @example
4200                                 // set the scroll left value of #myDiv to 20
4201                                 glow.dom.get("#myDiv").scrollLeft(20);
4202
4203                         @example
4204                                 // get the scrollLeft of the window
4205                                 var scrollPos = glow.dom.get(window).scrollLeft();
4206                                 // scrollPos is a number, eg: 45
4207                         */
4208                         scrollLeft: function(val) {
4209                                 return scrollOffset(this, true, val);
4210                         },
4211                         
4212                         /**
4213                         @name glow.dom.NodeList#scrollTop
4214                         @function
4215                         @description Gets/sets the number of pixels the element has scrolled vertically
4216                                 
4217                                 Get the value by calling without arguments, set by providing a new
4218                                 value.
4219                                 
4220                                 To get/set the scroll position of the window, use this method on
4221                                 a nodelist containing the window object.
4222
4223                         @param {Number} [val] New top scroll position
4224
4225                         @returns {glow.dom.NodeList | Number}
4226
4227                                 Current scrollTop value, or NodeList when setting scroll position.
4228
4229                         @example
4230                                 // get the scroll top value of #myDiv
4231                                 var scrollPos = glow.dom.get("#myDiv").scrollTop();
4232                                 // scrollPos is a number, eg: 45
4233
4234                         @example
4235                                 // set the scroll top value of #myDiv to 20
4236                                 glow.dom.get("#myDiv").scrollTop(20);
4237
4238                         @example
4239                                 // get the scrollTop of the window
4240                                 var scrollPos = glow.dom.get(window).scrollTop();
4241                                 // scrollPos is a number, eg: 45
4242                         */
4243                         scrollTop: function(val) {
4244                                 return scrollOffset(this, false, val);
4245                         },
4246                         
4247                         /**
4248                         @name glow.dom.NodeList#show
4249                         @function
4250                         @description Shows all hidden items in the NodeList.
4251                         @returns {glow.dom.NodeList}
4252                         @example
4253                                 // Show element with ID myDiv
4254                                 glow.dom.get("#myDiv").show();
4255                         @example
4256                                 // Show all list items within #myDiv
4257                                 glow.dom.get("#myDiv li").show();
4258                         */
4259                         show: function() {
4260                                 var i = 0,
4261                                         len = this.length,
4262                                         currItem,
4263                                         itemStyle;
4264                                 for (; i < len; i++) {
4265                                         /* Create a NodeList for the current item */
4266                                         currItem = r.get(this[i]);
4267                                         itemStyle = currItem[0].style;
4268                                         if (currItem.css("display") == "none") {
4269                                                 itemStyle.display = "";
4270                                                 itemStyle.visibility = "visible";
4271                                                 /* If display is still none, set to block */
4272                                                 if (currItem.css("display") == "none") {
4273                                                         itemStyle.display = "block";
4274                                                 }
4275                                         }
4276                                 }
4277                                 return this;
4278                         },
4279
4280                         /**
4281                         @name glow.dom.NodeList#hide
4282                         @function
4283                         @description Hides all items in the NodeList.
4284                         @returns {glow.dom.NodeList}
4285                         @example
4286                                 // Hides all list items within #myDiv
4287                                 glow.dom.get("#myDiv li").hide();
4288                         */
4289                         hide: function() {
4290                                 return this.css("display", "none").css("visibility", "hidden");
4291                         },
4292
4293                         /**
4294                         @name glow.dom.NodeList#css
4295                         @function
4296                         @description Gets a CSS property for the first node or sets a CSS property on all nodes.
4297
4298                                 <p><em>This method is not applicable to XML NodeLists.</em></p>
4299
4300                                 If a single property name is passed, the corresponding value
4301                                 from the first node will be returned.
4302
4303                                 If a single property name is passed with a value, that value
4304                                 will be set on all nodes on the NodeList.
4305
4306                                 If an array of properties name is passed with no value, the return
4307                                 value is the sum of those values from the first node in the NodeList.
4308                                 This can be useful for getting the total of left and right padding,
4309                                 for example.
4310
4311                                 Return values are strings. For instance, "height" will return
4312                                 "25px" for an element 25 pixels high. You may want to use
4313                                 parseInt to convert these values.
4314
4315                                 Here are the compatible properties you can get, if you use one
4316                                 which isn't on this list the return value may differ between browsers.
4317
4318                                 <dl>
4319                                 <dt>width</dt><dd>Returns pixel width, eg "25px". Can be used even if width has not been set with CSS.</dd>
4320                                 <dt>height</dt><dd>Returns pixel height, eg "25px". Can be used even if height has not been set with CSS.</dd>
4321                                 <dt>top, right, bottom, left</dt><dd>Pixel position relative to positioned parent, or original location if position:relative, eg "10px". These can be used even if position:static.</dd>
4322                                 <dt>padding-top</dt><dd>Pixel size of top padding, eg "5px"</dd>
4323                                 <dt>padding-right</dt><dd>Pixel size of right padding, eg "5px"</dd>
4324                                 <dt>padding-bottom</dt><dd>Pixel size of bottom padding, eg "5px"</dd>
4325                                 <dt>padding-left</dt><dd>Pixel size of left padding, eg "5px"</dd>
4326                                 <dt>margin-top</dt><dd>Pixel size of top margin, eg "5px"</dd>
4327                                 <dt>margin-right</dt><dd>Pixel size of right margin, eg "5px"</dd>
4328                                 <dt>margin-bottom</dt><dd>Pixel size of bottom margin, eg "5px"</dd>
4329                                 <dt>margin-left</dt><dd>Pixel size of left margin, eg "5px"</dd>
4330                                 <dt>border-top-width</dt><dd>Pixel size of top border, eg "5px"</dd>
4331                                 <dt>border-right-width</dt><dd>Pixel size of right border, eg "5px"</dd>
4332                                 <dt>border-bottom-width</dt><dd>Pixel size of bottom border, eg "5px"</dd>
4333                                 <dt>border-left-width</dt><dd>Pixel size of left border, eg "5px"</dd>
4334                                 <dt>border-*-style</dt><dd>eg "dotted"</dd>
4335                                 <dt>border-*-color</dt><dd>returns colour in format "rgb(255, 255, 255)", return value also has properties r, g & b to get individual values as integers</dd>
4336                                 <dt>color</dt><dd>returns colour in format "rgb(255, 255, 255)", return value also has properties r, g & b to get individual values as integers</dd>
4337                                 <dt>list-style-position</dt><dd>eg "outside"</dd>
4338                                 <dt>list-style-type</dt><dd>eg "square"</dd>
4339                                 <dt>list-style-image</dt><dd>Returns full image path, eg "url(http://www.bbc.co.uk/lifestyle/images/bullets/1.gif)" or "none"</dd>
4340                                 <dt>background-image</dt><dd>Returns full image path, eg "url(http://www.bbc.co.uk/lifestyle/images/bgs/1.gif)" or "none"</dd>
4341                                 <dt>background-attachment</dt><dd>eg "scroll"</dd>
4342                                 <dt>background-repeat</dt><dd>eg "repeat-x"</dd>
4343                                 <dt>direction</dt><dd>eg "ltr"</dd>
4344                                 <dt>font-style</dt><dd>eg "italic"</dd>
4345                                 <dt>font-variant</dt><dd>eg "small-caps"</dd>
4346                                 <dt>line-height</dt><dd>Pixel height, eg "30px". Note, Opera may return value with 2 decimal places, eg "30.00px"</dd>
4347                                 <dt>letter-spacing</dt><dd>Pixel spacing, eg "10px"</dd>
4348                                 <dt>text-align</dt><dd>eg "right"</dd>
4349                                 <dt>text-decoration</dt><dd>eg "underline"</dd>
4350                                 <dt>text-indent</dt><dd>Pixel indent, eg "10px"</dd>
4351                                 <dt>white-space</dt><dd>eg "nowrap"</dd>
4352                                 <dt>word-spacing</dt><dd>Pixel spacing, eg "5px"</dd>
4353                                 <dt>float</dt><dd>eg "left"</dd>
4354                                 <dt>clear</dt><dd>eg "right"</dd>
4355                                 <dt>opacity</dt><dd>Value between 0 & 1. In IE, this value comes from the filter property (/100)</dd>
4356                                 <dt>position</dt><dd>eg "relative"</dd>
4357                                 <dt>z-index</dt><dd>eg "32"</dd>
4358                                 <dt>display</dt><dd>eg "block"</dd>
4359                                 <dt>text-transform</dt><dd>eg "uppercase"</dd>
4360                                 </dl>
4361
4362                                 The following return values that may be usable but have differences in browsers
4363
4364                                 <dl>
4365                                 <dt>font-family</dt><dd>Some browsers return the font used ("Verdana"), others return the full list found in the declaration ("madeup, verdana, sans-serif")</dd>
4366                                 <dt>font-size</dt><dd>Returns size as pixels except in IE, which will return the value in the same units it was set in ("0.9em")</dd>
4367                                 <dt>font-weight</dt><dd>Returns named values in some browsers ("bold"), returns computed weight in others ("700")</dd>
4368                                 </dl>
4369
4370                         @param {String | String[] | Object} property The CSS property name, array of names to sum, or object of key-value pairs
4371
4372                         @param {String} [value] The value to apply
4373
4374                         @returns {glow.dom.NodeList | String}
4375
4376                                 Returns the CSS value from the first node, or NodeList when setting values
4377
4378                         @example
4379                                 // get value from first node
4380                                 glow.dom.get("#myDiv").css("display");
4381
4382                         @example
4383                                 // set left padding to 10px on all nodes
4384                                 glow.dom.get("#myDiv").css("padding-left", "10px");
4385
4386                         @example
4387                                 // "30px", total of left & right padding
4388                                 glow.dom.get("#myDiv").css(["padding-left", "padding-right"]);
4389
4390                         @example
4391                                 // where appropriate, px is assumed when no unit is passed
4392                                 glow.dom.get("#myDiv").css("height", 300);
4393                 
4394                         @example
4395                                 // set multiple CSS values at once
4396                                 // NOTE: Property names containing a hyphen such as font-weight must be quoted
4397                                 glow.dom.get("#myDiv").css({
4398                                         "font-weight": "bold",
4399                                         padding: "10px",
4400                                         color: "#00cc99"
4401                                 })
4402                         */
4403                         css: function(prop, val) {
4404                                 var that = this,
4405                                         thisStyle,
4406                                         i = 0,
4407                                         len = that.length,
4408                                         originalProp = prop;
4409
4410                                 if (prop.constructor === Object) { // set multiple values
4411                                         for (style in prop) {
4412                                                 this.css(style, prop[style]);
4413                                         }
4414                                         return that;
4415                                 }
4416                                 else if (val != undefined) { //set one CSS value
4417                                         prop = toStyleProp(prop);
4418                                         for (; i < len; i++) {
4419                                                 thisStyle = that[i].style;
4420                                                 
4421                                                 if (typeof val == "number" && hasUnits.test(originalProp)) {
4422                                                         val = val.toString() + "px";
4423                                                 }
4424                                                 if (prop == "opacity" && env.ie) {
4425                                                         //in IE the element needs hasLayout for opacity to work
4426                                                         thisStyle.zoom = "1";
4427                                                         if (val === "") {
4428                                                                 thisStyle.filter = "";
4429                                                         } else {
4430                                                                 thisStyle.filter = "alpha(opacity=" + Math.round(Number(val, 10) * 100) + ")";
4431                                                         }
4432                                                 } else {
4433                                                         thisStyle[prop] = val;
4434                                                 }
4435                                         }
4436                                         return that;
4437                                 } else { //getting stuff
4438                                         if (!len) { return; }
4439                                         return getCssValue(that[0], prop);
4440                                 }
4441                         },
4442
4443                         /**
4444                         @name glow.dom.NodeList#offset
4445                         @function
4446                         @description Gets the offset from the top left of the document.
4447                         
4448                                 If the NodeList contains multiple items, the offset of the
4449                                 first item is returned.
4450
4451                         @returns {Object}
4452                                 Returns an object with "top" & "left" properties in pixels
4453
4454                         @example
4455                                 glow.dom.get("#myDiv").offset().top
4456                         */
4457                         offset: function () {
4458                                 // http://weblogs.asp.net/bleroy/archive/2008/01/29/getting-absolute-coordinates-from-a-dom-element.aspx - great bit of research, most bugfixes identified here (and also jquery trac)
4459                                 var elm = this[0],
4460                                         docScrollPos = {
4461                                                 x: getScrollOffset(window, true),
4462                                                 y: getScrollOffset(window, false)
4463                                         }
4464
4465                                 //this is simple(r) if we can use 'getBoundingClientRect'
4466                                 // Sorry but the sooper dooper simple(r) way is not accurate in Safari 4
4467                                 if (!glow.env.webkit && elm.getBoundingClientRect) {
4468                                         var rect = elm.getBoundingClientRect();
4469                                         return {
4470                                                 top: rect.top
4471                                                         /*
4472                                                          getBoundingClientRect is realive to top left of
4473                                                          the viewport, so we need to sort out scrolling offset
4474                                                         */
4475                                                         + docScrollPos.y
4476                                                         /*
4477                                                          IE adds the html element's border to the value. We can
4478                                                          deduct this value using client(Top|Left). However, if
4479                                                          the user has done html{border:0} clientTop will still
4480                                                          report a 2px border in IE quirksmode so offset will be off by 2.
4481                                                          Hopefully this is an edge case but we may have to revisit this
4482                                                          in future
4483                                                         */
4484                                                         - docElm.clientTop,
4485
4486                                                 left: rect.left //see above for docs on all this stuff
4487                                                         + docScrollPos.x
4488                                                         - docElm.clientLeft
4489                                         };
4490                                 } else { //damnit, let's go the long way around
4491                                         var top = elm.offsetTop,
4492                                                 left = elm.offsetLeft,
4493                                                 originalElm = elm,
4494                                                 nodeNameLower,
4495                                                 //does the parent chain contain a position:fixed element
4496                                                 involvesFixedElement = false,
4497                                                 offsetParentBeforeBody = elm;
4498
4499                                         //add up all the offset positions
4500                                         while (elm = elm.offsetParent) {
4501                                                 left += elm.offsetLeft;
4502                                                 top += elm.offsetTop;
4503
4504                                                 //if css position is fixed, we need to add in the scroll offset too, catch it here
4505                                                 if (getCssValue(elm, "position") == "fixed") {
4506                                                         involvesFixedElement = true;
4507                                                 }
4508
4509                                                 //gecko & webkit (safari 3) don't add on the border for positioned items
4510                                                 if (env.gecko || env.webkit > 500) {
4511                                                         left += parseInt(getCssValue(elm, "border-left-width")) || 0;
4512                                                         top  += parseInt(getCssValue(elm, "border-top-width"))  || 0;
4513                                                 }
4514
4515                                                 //we need the offset parent (before body) later
4516                                                 if (elm.nodeName.toLowerCase() != "body") {
4517                                                         offsetParentBeforeBody = elm;
4518                                                 }
4519                                         }
4520
4521                                         //deduct all the scroll offsets
4522                                         elm = originalElm;
4523                                         while ((elm = elm.parentNode) && (elm != docBody) && (elm != docElm)) {
4524                                                 left -= elm.scrollLeft;
4525                                                 top -= elm.scrollTop;
4526
4527                                                 //FIXES
4528                                                 //gecko doesn't add the border of contained elements to the offset (overflow!=visible)
4529                                                 if (env.gecko && getCssValue(elm, "overflow") != "visible") {
4530                                                         left += parseInt(getCssValue(elm, "border-left-width"));
4531                                                         top += parseInt(getCssValue(elm, "border-top-width"));
4532                                                 }
4533                                         }
4534
4535                                         //if we found a fixed position element we need to add the scroll offsets
4536                                         if (involvesFixedElement) {
4537                                                 left += docScrollPos.x;
4538                                                 top += docScrollPos.y;
4539                                         }
4540
4541                                         //FIXES
4542                                         // Webkit < 500 body's offset gets counted twice for absolutely-positioned elements (or if there's a fixed element)
4543                                         // Gecko - non-absolutely positioned elements that are direct children of body get the body offset counted twice
4544                                         if (
4545                                                 (env.webkit < 500 && (involvesFixedElement || getCssValue(offsetParentBeforeBody, "position") == "absolute")) ||
4546                                                 (env.gecko && getCssValue(offsetParentBeforeBody, "position") != "absolute")
4547                                         ) {
4548                                                 left -= docBody.offsetLeft;
4549                                                 top -= docBody.offsetTop;
4550                                         }
4551
4552                                         return {left:left, top:top};
4553                                 }
4554                         },
4555                         
4556                         /**
4557                         @name glow.dom.NodeList#position
4558                         @function
4559                         @description Get the top & left position of an element relative to its positioned parent
4560                                 
4561                                 This is useful if you want to make a position:static element position:absolute
4562                                 and retain the original position of the element
4563                                 
4564                         @returns {Object} An object with 'top' and 'left' number properties
4565
4566                         @example
4567                                 // get the top distance from the positioned parent
4568                                 glow.dom.get("#elm").position().top
4569                         */
4570                         position: function() {
4571                                 var positionedParent = r.get( getPositionedParent(this[0]) ),
4572                                         hasPositionedParent = !!positionedParent[0],
4573                                         
4574                                         // element margins to deduct
4575                                         marginLeft = parseInt( this.css("margin-left") ) || 0,
4576                                         marginTop  = parseInt( this.css("margin-top")  ) || 0,
4577                                         
4578                                         // offset parent borders to deduct, set to zero if there's no positioned parent
4579                                         positionedParentBorderLeft = ( hasPositionedParent && parseInt( positionedParent.css("border-left-width") ) ) || 0,
4580                                         positionedParentBorderTop  = ( hasPositionedParent && parseInt( positionedParent.css("border-top-width")  ) ) || 0,
4581                                         
4582                                         // element offsets
4583                                         elOffset = this.offset(),
4584                                         positionedParentOffset = hasPositionedParent ? positionedParent.offset() : {top: 0, left: 0};
4585                                 
4586                                 return {
4587                                         left: elOffset.left - positionedParentOffset.left - marginLeft - positionedParentBorderLeft,
4588                                         top:  elOffset.top  - positionedParentOffset.top  - marginTop  - positionedParentBorderTop
4589                                 }
4590                         },
4591                         
4592                         /**
4593                         @name glow.dom.NodeList#append
4594                         @function
4595                         @description Appends the given elements to each node.
4596
4597                                 If there is more than one node in the NodeList, then the given elements
4598                                 are appended to the first node and clones are appended to the other
4599                                 nodes.
4600
4601                                 <p><em>String nodespecs cannot be used with XML NodeLists</em></p>
4602
4603                         @param {String | Element | glow.dom.NodeList} nodespec HTML string, Element or NodeList to append to each node.
4604
4605                         @returns {glow.dom.NodeList}
4606
4607                         @example
4608                                 // ends every paragraph with '...'
4609                                 glow.dom.get('p').append(
4610                                         '<span>...</span>'
4611                                 );
4612                         */
4613                         append: function (nodeSpec) {
4614                                 var that = this,
4615                                         j = 0,
4616                                         i = 1,
4617                                         length = that.length,
4618                                         nodes;
4619
4620                                 if (length == 0) { return that; }
4621                                 nodes = typeof nodeSpec == "string" ? nodelistToArray(stringToNodes(nodeSpec)) :
4622                                                 nodeSpec.nodeType ? [nodeSpec] : nodelistToArray(nodeSpec);
4623
4624                                 for (; nodes[j]; j++) {
4625                                         that[0].appendChild(nodes[j]);
4626                                 }
4627                                 for (; i < length; i++) {
4628                                         for (j = 0; nodes[j]; j++) {
4629                                                 that[i].appendChild(nodes[j].cloneNode(true));
4630                                         }
4631                                 }
4632                                 return that;
4633                         },
4634
4635                         /**
4636                         @name glow.dom.NodeList#prepend
4637                         @function
4638                         @description Prepends the given elements to each node.
4639
4640                                 If there is more than one node in the NodeList, then the given elements
4641                                 are prepended to the first node and clones are prepended to the other
4642                                 nodes.
4643
4644                                 <p><em>String nodespecs cannot be used with XML NodeLists</em></p>
4645
4646                         @param {String | Element | glow.dom.NodeList} nodespec HTML string, Element or NodeList to prepend to each node.
4647
4648                         @returns {glow.dom.NodeList}
4649
4650                         @example
4651                                 // prepends every paragraph with 'Paragraph: '
4652                                 glow.dom.get('p').prepend(
4653                                         '<span>Paragraph: </span>'
4654                                 );
4655                         */
4656                         prepend: function (nodeSpec) {
4657                                 var that = this,
4658                                         j = 0,
4659                                         i = 1,
4660                                         length = that.length,
4661                                         nodes,
4662                                         first;
4663
4664                                 if (length == 0) { return that; }
4665
4666                                 nodes = typeof nodeSpec == "string" ? nodelistToArray(stringToNodes(nodeSpec)) :
4667                                                 nodeSpec.nodeType ? [nodeSpec] : nodelistToArray(nodeSpec);
4668
4669                                 first = that[0].firstChild;
4670
4671                                 for (; nodes[j]; j++) {
4672                                         that[0].insertBefore(nodes[j], first);
4673                                 }
4674
4675                                 for (; i < length; i++) {
4676                                         first = that[i].firstChild;
4677                                         for (j = 0; nodes[j]; j++) {
4678                                                 that[i].insertBefore(nodes[j].cloneNode(true), first);
4679                                         }
4680                                 }
4681                                 return that;
4682                         },
4683
4684                         /**
4685                         @name glow.dom.NodeList#appendTo
4686                         @function
4687                         @description Appends the NodeList to elements.
4688
4689                                 If more than one element is given (i.e. if the nodespec argument
4690                                 is an array of nodes or a NodeList) the NodeList will be
4691                                 appended to the first element and clones to each subsequent
4692                                 element.
4693
4694                         @param {String | Element | glow.dom.NodeList} nodespec CSS selector string, Element or NodeList to append the NodeList to.
4695
4696                         @returns {glow.dom.NodeList}
4697
4698                         @example
4699                                 // appends '...' to every paragraph
4700                                 glow.dom.create('<span>...</span>').appendTo('p');
4701                         */
4702                         appendTo: function (nodes) {
4703                                 if (! (nodes instanceof r.NodeList)) { nodes = r.get(nodes); }
4704                                 nodes.append(this);
4705                                 return this;
4706                         },
4707
4708                         /**
4709                         @name glow.dom.NodeList#prependTo
4710                         @function
4711                         @description Prepends the NodeList to elements.
4712
4713                                 If more than one element is given (i.e. if the nodespec argument
4714                                 is an array of nodes or a NodeList) the NodeList will be
4715                                 prepended to the first element and clones to each subsequent
4716                                 element.
4717
4718                         @param {String | Element | glow.dom.NodeList} nodespec CSS selector string, Element or NodeList to prepend the NodeList to.
4719
4720                         @returns {glow.dom.NodeList}
4721
4722                         @example
4723                                 // prepends 'Paragraph: ' to every paragraph
4724                                 glow.dom.create('<span>Paragraph: </span>').prependTo('p');
4725                         */
4726                         prependTo: function (nodes) {
4727                                 if (! (nodes instanceof r.NodeList)) { nodes = r.get(nodes); }
4728                                 nodes.prepend(this);
4729                                 return this;
4730                         },
4731
4732                         /**
4733                         @name glow.dom.NodeList#after
4734                         @function
4735                         @description Inserts elements after each node.
4736
4737                                 If there is more than one node in the NodeList, the elements
4738                                 will be inserted after the first node and clones inserted
4739                                 after each subsequent node.
4740
4741                         @param {String | Element | glow.dom.NodeList} nodespec HTML string, Element or NodeList to insert after each node
4742
4743                         @returns {glow.dom.NodeList}
4744
4745                         @example
4746                                 // adds a paragraph after each heading
4747                                 glow.dom.get('h1, h2, h3').after('<p>...</p>');
4748                         */
4749                         after: function (nodeSpec) {
4750                                 var that = this,
4751                                         length = that.length,
4752                                         nodes,
4753                                         nodesLen,
4754                                         j,
4755                                         i = 1,
4756                                         cloned;
4757
4758                                 if (length == 0) { return that; }
4759
4760                                 nodes = typeof nodeSpec == "string" ? r.create(nodeSpec) :
4761                                         nodeSpec instanceof r.NodeList ? nodeSpec :
4762                                         r.get(nodeSpec);
4763
4764                                 nodesLen = nodes.length;
4765
4766                                 for (j = nodesLen - 1; j >= 0; j--) {
4767                                         that[0].parentNode.insertBefore(nodes[j], that[0].nextSibling);
4768                                 }
4769                                 for (; i < length; i++) {
4770                                         cloned = nodes.clone();
4771
4772                                         for (j = nodesLen - 1; j >= 0; j--) {
4773                                                 that[i].parentNode.insertBefore(cloned[j], that[i].nextSibling);
4774                                         }
4775                                 }
4776                                 return that;
4777                         },
4778
4779                         /**
4780                         @name glow.dom.NodeList#before
4781                         @function
4782                         @description Inserts elements before each node.
4783
4784                                 If there is more than one node in the NodeList, the elements
4785                                 will be inserted before the first node and clones inserted
4786                                 before each subsequent node.
4787
4788                         @param {String | Element | glow.dom.NodeList} nodespec HTML string, Element or NodeList to insert before each node
4789
4790                         @returns {glow.dom.NodeList}
4791
4792                         @example
4793                                 // adds a heading before each
4794                                 glow.dom.get('p').before('<h1>Paragraph:</h1>');
4795                         */
4796                         before: function (nodeSpec) {
4797                                 var that = this,
4798                                         length = that.length,
4799                                         j = 0,
4800                                         i = 1,
4801                                         nodes,
4802                                         nodesLen,
4803                                         cloned;
4804
4805                                 if (length == 0) { return that; }
4806
4807                                 nodes = typeof nodeSpec == "string" ? r.create(nodeSpec) :
4808                                         nodeSpec instanceof r.NodeList ? nodeSpec :
4809                                         r.get(nodeSpec);
4810
4811                                 nodesLen = nodes.length;
4812
4813                                 for (; j < nodesLen; j++) {
4814                                         that[0].parentNode.insertBefore(nodes[j], that[0]);
4815                                 }
4816                                 for (; i < length; i++) {
4817                                         cloned = nodes.clone();
4818                                         for (j = 0; j < nodesLen; j++) {
4819                                                 that[i].parentNode.insertBefore(cloned[j], that[i]);
4820                                         }
4821                                 }
4822                                 return that;
4823                         },
4824
4825                         /**
4826                         @name glow.dom.NodeList#insertAfter
4827                         @function
4828                         @description Insert the NodeList after each of the given elements.
4829
4830                                 If more than one element is passed in, the NodeList will be
4831                                 inserted after the first element and clones inserted after each
4832                                 subsequent element.
4833
4834                         @param {String | Element | glow.dom.NodeList} nodespec CSS selector string, Element or NodeList to insert the NodeList after
4835
4836                         @returns {glow.dom.NodeList}
4837
4838                         @example
4839                                 // adds a paragraph after each heading
4840                                 glow.dom.create('<p>HAI!</p>').insertAfter('h1, h2, h3');
4841                         */
4842                         insertAfter: function (nodes) {
4843                                 if (! (nodes instanceof r.NodeList)) { nodes = r.get(nodes); }
4844                                 nodes.after(this);
4845                                 return this;
4846                         },
4847
4848                         /**
4849                         @name glow.dom.NodeList#insertBefore
4850                         @function
4851                         @description Insert the NodeList before each of the given elements.
4852
4853                                 If more than one element is passed in, the NodeList will be
4854                                 inserted before the first element and clones inserted before each
4855                                 subsequent element.
4856
4857                         @param {String | Element | glow.dom.NodeList} nodespec CSS selector string, Element or NodeList to insert the NodeList before
4858
4859                         @returns {glow.dom.NodeList}
4860
4861                         @example
4862                                 // adds a heading before each paragraph
4863                                 glow.dom.create('<h1>Paragraph:</h1>').insertBefore('p');
4864                         */
4865                         insertBefore: function (nodes) {
4866                                 if (! (nodes instanceof r.NodeList)) { nodes = r.get(nodes); }
4867                                 nodes.before(this);
4868                                 return this;
4869                         },
4870
4871
4872                         /**
4873                         @name glow.dom.NodeList#replaceWith
4874                         @function
4875                         @description Replace nodes on the page with given elements.
4876
4877                         @param {glow.dom.NodeList | String} nodespec Elements to insert into the document.
4878
4879                                 If more than one node is to be replaced then nodespec will be
4880                                 cloned for additional elements. If a string is provided it will
4881                                 be treated as HTML and converted into elements.
4882
4883                         @returns {glow.dom.NodeList}
4884                                 Returns a new NodeList containing the nodes which have been removed
4885                         */
4886                         replaceWith: function (nodeSpec) {
4887                                 /*
4888                                  we need to put a placeholder in first in case the new stuff
4889                                  has the same ids as stuff being replaced. This causes issues
4890                                  in safari 2, the new element can't be picked up with getElementById
4891                                 */
4892                                 if (env.webkit < 500) {
4893                                         this.after(placeholderElm).remove();
4894                                         r.get("u.glow-placeholder").after(nodeSpec).remove();
4895                                 } else {
4896                                         this.after(nodeSpec).remove()
4897                                 }
4898                                 return this;
4899                         },
4900                         
4901                         /**
4902                         @name glow.dom.NodeList#data
4903                         @function
4904                         @description Use this to safely attach arbitrary data to any DOM Element.
4905                         
4906                         This method is useful when you wish to avoid memory leaks that are possible when adding your own data directly to DOM Elements.
4907                         
4908                         When called with no arguments, will return glow's entire data store for the first item in the NodeList.
4909                         
4910                         Otherwise, when given a stringy key, will return the associated value from the first item in the NodeList.
4911                         
4912                         When given both a key and a value, will store that data on every item in the NodeList.
4913                         
4914                         Optionally you can pass in a single object composed of multiple key, value pairs.
4915                         
4916                         @param {String|Object} [key] The name of the value in glow's data store for the NodeList item.
4917                         @param {Object} [val] The the value you wish to associate with the given key.
4918                         @see glow.dom.NodeList#removeData
4919                         @example
4920                         
4921                         glow.dom.get("p").data("tea", "milky");
4922                         var colour = glow.dom.get("p").data("tea"); // milky
4923                         @returns {Object} When setting a value this method can be chained, as in that case it will return itself.
4924                         */
4925                         data: function (key, val) { /*debug*///console.log("data("+key+", "+val+")");
4926                                 if (typeof key === "object") { // setting many values
4927                                         for (var prop in key) { this.data(prop, key[prop]); }
4928                                         return this; // chainable with ({key: val}) signature
4929                                 }
4930                                 
4931                                 var index,
4932                                         elm;
4933                                         // uses private class-scoped variables: dataCache, dataPropName, dataIndex
4934                                 
4935                                 switch (arguments.length) {
4936                                         case 0: // getting entire cache from first node
4937                                                 if (this[0] === undefined) { return undefined; }
4938                                                 index = this[0][dataPropName] || dataIndex++;
4939                                                 return dataCache[index] || (dataCache[index] = {}); // create a new cache when reading entire cache
4940                                         case 1:  // getting val from first node
4941                                                 if (this[0] === undefined) { return undefined; }
4942                                                 index = this[0][dataPropName]; // don't create a new cache when reading just a specific val
4943                                                 return index? dataCache[index][key] : undefined;
4944                                         case 2: // setting key:val on every node
4945                                                 // TODO - need to defend against reserved words being used as keys?
4946                                                 for (var i = this.length; i--;) {
4947                                                         elm = this[i];
4948                                                         
4949                                                         if ( !(index = elm[dataPropName]) ) { // assumes index is always > 0
4950                                                                 index = dataIndex++;
4951                                                                 
4952                                                                 elm[dataPropName] = index;
4953                                                                 dataCache[index] = {};
4954                                                         }
4955                                                         dataCache[index][key] = val;
4956                                                 }
4957                                                 
4958                                                 return this; // chainable with (key, val) signature
4959                                         default:
4960                                                 throw new Error("glow.dom.NodeList#data expects 2 or less arguments, not "+arguments.length+".");
4961                                 }
4962                         },
4963                         
4964                         /**
4965                         @name glow.dom.NodeList#removeData
4966                         @function
4967                         @description Removes data previously added by {@link glow.dom.NodeList#data} from items in a NodeList.
4968                         
4969                         When called with no arguments, will delete glow's entire data store for every item in the NodeList.
4970                         
4971                         Otherwise, when given a key, will delete the associated value from every item in the NodeList.
4972                         
4973                         @param {String} [key] The name of the value in glow's data store for the NodeList item.
4974                         */
4975                         removeData: function (key) { /*debug*///console.log("removeData("+key+")");
4976                                 var elm,
4977                                         i = this.length,
4978                                         index;
4979                                         // uses private class-scoped variables: dataCache, dataPropName
4980                                 
4981                                 while (i--) {
4982                                         elm = this[i];
4983                                         index = elm[dataPropName];
4984                                         
4985                                         if (index !== undefined) {
4986                                                 switch (arguments.length) {
4987                                                         case 0:
4988                                                                 dataCache[index] = undefined;
4989                                                                 elm[dataPropName] = undefined;
4990                                                                 try {
4991                                                                         delete elm[dataPropName]; // IE 6 goes wobbly here
4992                                                                 }
4993                                                                 catch(e) { // remove expando from IE 6
4994                                                                         elm.removeAttribute && elm.removeAttribute(dataPropName);
4995                                                                 }
4996                                                                 break;
4997                                                         case 1:
4998                                                                 dataCache[index][key] = undefined;
4999                                                                 delete dataCache[index][key];
5000                                                                 break;
5001                                                         default:
5002                                                                 throw new Error("glow.dom.NodeList#removeData expects 1 or less arguments, not "+arguments.length+".");
5003                                                 }
5004                                         }
5005                                 }
5006                                 
5007                                 return this; // chainable
5008                         },
5009
5010                         /**
5011                         @name glow.dom.NodeList#get
5012                         @function
5013                         @description Gets decendents of nodes that match a CSS selector.
5014
5015                         @param {String} selector CSS selector
5016
5017                         @returns {glow.dom.NodeList}
5018                                 Returns a new NodeList containing matched elements
5019
5020                         @example
5021                                 // create a new NodeList
5022                                 var myNodeList = glow.dom.create("<div><a href='s.html'>Link</a></div>");
5023
5024                                 // get 'a' tags that are decendants of the NodeList nodes
5025                                 myNewNodeList = myNodeList.get("a");
5026                         */
5027                         get: function() {
5028
5029                                 /*
5030                                 PrivateFunction: compileSelector
5031                                         Compile a CSS selector to an AST.
5032
5033                                 Arguments:
5034                                         sSelector - A string containing the CSS selector (or comma separated group of selectors).
5035
5036                                 Returns:
5037                                         An array containing an AST for each selector in the group.
5038                                 */
5039                                 function compileSelector(sSelector) {
5040                                         //return from cache if possible
5041                                         if (cssCache[sSelector]) {
5042                                                 return cssCache[sSelector];
5043                                         }
5044
5045                                         var r = [], //array of result objects
5046                                                 ri = 0, //results index
5047                                                 comb, //current combinator
5048                                                 tagTmp,
5049                                                 idTmp,
5050                                                 aRx, //temp regex result
5051                                                 matchedCondition, //have we matched a condition?
5052                                                 sLastSelector, //holds last copy of selector to prevent infinite loop
5053                                                 firstLoop = true,
5054                                                 originalSelector = sSelector;
5055
5056                                         while (sSelector && sSelector != sLastSelector) {
5057                                                 tagTmp = "";
5058                                                 idTmp = "";
5059                                                 //protect us from infinite loop
5060                                                 sLastSelector = sSelector;
5061
5062                                                 //start by getting the scope (combinator)
5063                                                 if (aRx = cssRegex.combinator.exec(sSelector)) {
5064                                                         comb = aRx[1];
5065                                                         sSelector = sSelector.slice(aRx[0].length);
5066                                                 }
5067                                                 //look for optimal id & tag searching
5068                                                 if (aRx = cssRegex.tagName.exec(sSelector)) {
5069                                                         tagTmp = aRx[1];
5070                                                         sSelector = sSelector.slice(aRx[0].length);
5071                                                 }
5072                                                 if (aRx = cssRegex.classNameOrId.exec(sSelector)) {
5073                                                         if (aRx[1] == "#") {
5074                                                                 idTmp = aRx[2];
5075                                                                 sSelector = sSelector.slice(aRx[0].length);
5076                                                         }
5077                                                 }
5078                                                 if (!comb) { //use native stuff
5079                                                         if (idTmp && firstLoop) {
5080                                                                 r[ri++] = [getByIdQuick, [idTmp.replace(/\\/g, ""), tagTmp || "*", null]];
5081                                                         } else {
5082                                                                 r[ri++] = [getByTagName, [tagTmp || "*", null]];
5083                                                                 if (idTmp) {
5084                                                                         r[ri++] = [hasId, [idTmp.replace(/\\/g, ""), null]];
5085                                                                 }
5086                                                         }
5087                                                 } else if (comb == ">") {
5088                                                         r[ri++] = [getChildren, [null]];
5089                                                         if (idTmp) {
5090                                                                 r[ri++] = [hasId, [idTmp.replace(/\\/g, ""), null]];
5091                                                         }
5092                                                         if (tagTmp && tagTmp != "*") { //uses tag
5093                                                                 r[ri++] = [isTag, [tagTmp, null]];
5094                                                         }
5095                                                 }
5096
5097                                                 //other conditions can appear in any order, so here we go:
5098                                                 matchedCondition = true;
5099                                                 while (matchedCondition) {
5100                                                         //look for class or ID
5101                                                         if (sSelector.charAt(0) == "#" || sSelector.charAt(0) == ".") {
5102                                                                 if (aRx = cssRegex.classNameOrId.exec(sSelector)) {
5103                                                                         if (sSelector.charAt(0) == "#") { //is ID
5104                                                                                 //define ID, remove escape chars
5105                                                                                 r[ri++] = [hasId, [aRx[2].replace(/\\/g, ""), null]];
5106                                                                         } else { //is class
5107                                                                                 r[ri++] = [hasClassName, [aRx[2].replace(/\\/g, ""), null]];
5108                                                                         }
5109                                                                         sSelector = sSelector.slice(aRx[0].length);
5110                                                                 } else {
5111                                                                         throw new Error("Invalid Selector " + originalSelector);
5112                                                                 }
5113                                                         } else {
5114                                                                 matchedCondition = false;
5115                                                         }
5116                                                 }
5117
5118                                                 firstLoop = false;
5119                                         }
5120
5121                                         if (sSelector !== "") {
5122                                                 throw new Error("Invalid Selector " + originalSelector);
5123                                         }
5124
5125                                         //add to cache and return
5126                                         return cssCache[sSelector] = r;
5127                                 }
5128
5129                                 /*
5130                                 PrivateFunction: fetchElements
5131                                         Get elements which match array of compiled conditions based on
5132                                         an initial context.
5133
5134                                 Arguments:
5135                                         a - (object) CSS selector AST object.
5136                                         initialContext - DOM Element or DOM Document to search within.
5137
5138                                 Returns:
5139                                         An array of matching elements.
5140                                 */
5141                                 function fetchElements(a, initialContext) {
5142                                         var context = initialContext; //elements to look within
5143
5144                                         for (var i = 0, al = a.length; i < al; i++) {
5145                                                 a[i][1][a[i][1].length - 1] = context;
5146                                                 context = a[i][0].apply(this, a[i][1]);
5147                                         }
5148                                         return context;
5149                                 }
5150
5151                                 /*
5152                                 PrivateFunction: getByIdQuick
5153                                         Get an element with a specific tag name, within a context.
5154
5155                                 Arguments:
5156                                         id - (string) the id of the element.
5157                                         tagName - (string) the name of the element.
5158                                         context - DOM Element or DOM Document in which to find the element.
5159
5160                                 Returns:
5161                                         A DOM Element matching the specified criteria.
5162                                 */
5163                                 function getByIdQuick(id, tagName, context) {
5164                                         var r = [], ri = 0, notQuick = [], notQuicki = 0, tmpNode;
5165                                         for (var i = 0, length = context.length; i < length; i++) {
5166                                                 if (context[i].getElementById) {
5167                                                         tmpNode = context[i].getElementById(id);
5168                                                         if (tmpNode && (tmpNode.tagName == tagName.toUpperCase() || tagName == "*" || tmpNode.tagName == tagName)) {
5169                                                                 r[ri++] = tmpNode;
5170                                                         }
5171                                                 } else {
5172                                                         notQuick[notQuicki++] = context[i];
5173                                                 }
5174                                         }
5175                                         //deal with the ones we couldn't do quick
5176                                         if (notQuick[0]) {
5177                                                 notQuick = getByTagName(tagName, notQuick);
5178                                                 notQuick = hasId(id, notQuick);
5179                                         }
5180                                         return r.concat(notQuick);
5181                                 }
5182
5183                                 function getChildren(context) {
5184                                         var r = [],
5185                                                 i = 0,
5186                                                 len = context.length;
5187                                                 
5188                                         for (; i < len; i++) {
5189                                                 append( r, getChildElms(context[i]) );
5190                                         }
5191                                         return r;
5192                                 }
5193
5194                                 function hasId(id, context) {
5195                                         for (var i = 0, length = context.length; i < length; i++) {
5196                                                 if (context[i].id == id) {
5197                                                         //is this a safe optimisation?
5198                                                         return [context[i]];
5199                                                 }
5200                                         }
5201                                         return [];
5202                                 }
5203
5204                                 /*
5205                                 PrivateFunction: isTag
5206                                         Get an array of elements within an array that have a given tag name.
5207
5208                                 Arguments:
5209                                         tagName - (string) the name of the element.
5210                                         context - (array) elements to match.
5211
5212                                 Returns:
5213                                         An array of matching elements.
5214                                 */
5215                                 function isTag(tagName, context) {
5216                                         var r = [], ri = 0;
5217                                         for (var i = 0, length = context.length; i < length; i++) {
5218                                                 if (context[i].tagName == tagName.toUpperCase() || context[i].tagName == tagName) {
5219                                                         r[ri++] = context[i];
5220                                                 }
5221                                         }
5222                                         return r;
5223                                 }
5224
5225                                 /*
5226                                 PrivateFunction: hasClassName
5227                                         Get elements that have a given class name from a provided array of elements.
5228
5229                                 Arguments:
5230                                         className - (string) the name of the class.
5231                                         context - (array) the DOM Elements to match.
5232
5233                                 Returns:
5234                                         An array of matching DOM Elements.
5235                                 */
5236                                 function hasClassName(className, context) {
5237                                         var r = [], ri = 0;
5238                                         for (var i = 0, length = context.length; i < length; i++) {
5239                                                 if ((" " + context[i].className + " ").indexOf(" " + className + " ") != -1) {
5240                                                         r[ri++] = context[i];
5241                                                 }
5242                                         }
5243                                         return r;
5244                                 }
5245
5246                                 /*
5247                                 PrivateFunction: getBySelector
5248                                         Get elements within a context by a CSS selector.
5249
5250                                 Arugments:
5251                                         sSelector - (string) CSS selector.
5252                                         context - DOM Document or DOM Element to search within.
5253
5254                                 Returns:
5255                                         An array of DOM Elements.
5256                                 */
5257                                 function getBySelector(sSelector, context) {
5258                                         var aCompiledCSS; // holds current compiled css statement
5259                                         var r = [];
5260                                         //split multiple selectors up
5261                                         var aSelectors = sSelector.split(",");
5262                                         //process each
5263                                         for (var i = 0, nSelLen = aSelectors.length; i < nSelLen; i++) {
5264                                                 aCompiledCSS = compileSelector(glow.lang.trim(aSelectors[i]));
5265                                                 //get elements from DOM
5266                                                 r = r.concat(fetchElements(aCompiledCSS, context));
5267                                         }
5268                                         return r;
5269                                 }
5270
5271                                 /*
5272                                 PrivateFunction: getIfWithinContext
5273                                         Get elements from a set of elements that are within at least one of another
5274                                         set of DOM Elements.
5275
5276                                 Arguments:
5277                                         nodes - DOM Elements to take the results from.
5278                                         context - DOM Elements that returned elements must be within.
5279
5280                                 Returns:
5281                                         An array of DOM Elements.
5282                                 */
5283                                 function getIfWithinContext(nodes, context) {
5284                                         nodes = nodes.length ? nodes : [nodes];
5285                                         var r = []; //to return
5286                                         var nl;
5287
5288                                         //loop through nodes
5289                                         for (var i = 0; nodes[i]; i++) {
5290                                                 nl = glow.dom.get(nodes[i]);
5291                                                 //loop through context nodes
5292                                                 for (var n = 0; context[n]; n++) {
5293                                                         if (nl.isWithin(context[n])) {
5294                                                                 r[r.length] = nl[0];
5295                                                                 break;
5296                                                         }
5297                                                 }
5298                                         }
5299                                         return r;
5300                                 }
5301                                 
5302                                 // main implementation
5303                                 return function(sSelector) {
5304                                         // no point trying if there's no current context
5305                                         if (!this.length) { return this; }
5306
5307                                         var r = []; //nodes to return
5308                                         // decide what to do by arg
5309                                         for (var i = 0, argLen = arguments.length; i < argLen; i++) {
5310                                                 if (typeof arguments[i] == "string") { // css selector string
5311                                                         r = r.concat(getBySelector(arguments[i], this));
5312                                                         // append(r, getBySelector(arguments[i], this));
5313                                                 } else { // nodelist, array, node
5314                                                         r = r.concat(getIfWithinContext(arguments[i], this));
5315                                                         // append(r, getIfWithinContext(arguments[i], this));
5316                                                 }
5317                                         }
5318
5319                                         // strip out duplicates, wrap in nodelist
5320                                         return glow.dom.get(unique(r));
5321                                 };
5322                         }()
5323                 };
5324
5325                 //some init stuff. Using u to make it quicker to get by tag name :)
5326                 placeholderElm = r.create('<u class="glow-placeholder"></u>');
5327                 //set up glow.dom
5328                 glow.dom = r;
5329
5330         }
5331 });
5332 /**
5333 @name glow.events
5334 @namespace
5335 @description Native browser and custom events
5336 @see <a href="../furtherinfo/events/">Using event listeners</a>
5337 @see <a href="../furtherinfo/events/firing_events.shtml">Firing event listeners</a>
5338 @see <a href="../furtherinfo/events/removing_event_listeners.shtml">Removing event listeners</a>
5339 */
5340 (window.gloader || glow).module({
5341         name: "glow.events",
5342         library: ["glow", "1.7.0"],
5343         depends: [["glow", "1.7.0", 'glow.dom']],
5344         builder: function(glow) {
5345                 
5346                 var $ = glow.dom.get;
5347                 var r = {};
5348                 var eventid = 1;
5349                 var objid = 1;
5350                 // object (keyed by obj id), containing object (keyed by event name), containing arrays of listeners
5351                 var listenersByObjId = {};
5352                 // object (keyed by ident) containing listeners
5353                 var listenersByEventId = {};
5354                 var domListeners = {};
5355                 var psuedoPrivateEventKey = '__eventId' + glow.UID;
5356                 var psuedoPreventDefaultKey = psuedoPrivateEventKey + 'PreventDefault';
5357                 var psuedoStopPropagationKey = psuedoPrivateEventKey + 'StopPropagation';
5358
5359                 var topKeyListeners = {};
5360                 var keyListenerId = 1;
5361                 var keyListeners = {};
5362                 var keyTypes = {};
5363
5364                 var CTRL = 1;
5365                 var ALT = 2;
5366                 var SHIFT = 4;
5367
5368                 var specialPrintables = {
5369                         TAB      : '\t',
5370                         SPACE    : ' ',
5371                         ENTER    : '\n',
5372                         BACKTICK : '`'
5373                 };
5374
5375                 var keyNameAliases = {
5376                         '96' : 223
5377                 };
5378
5379                 var keyNameToCode = {
5380                         CAPSLOCK : 20, NUMLOCK : 144, SCROLLLOCK : 145, BREAK : 19,
5381                         BACKTICK : 223, BACKSPACE : 8, PRINTSCREEN : 44, MENU : 93, SPACE : 32,
5382                         SHIFT : 16,  CTRL : 17,  ALT : 18,
5383                         ESC   : 27,  TAB  : 9,   META     : 91,  RIGHTMETA : 92, ENTER : 13,
5384                         F1    : 112, F2   : 113, F3       : 114, F4        : 115,
5385                         F5    : 116, F6   : 117, F7       : 118, F8        : 119,
5386                         F9    : 120, F10  : 121, F11      : 122, F12       : 123,
5387                         INS   : 45,  HOME : 36,  PAGEUP   : 33,
5388                         DEL   : 46,  END  : 35,  PAGEDOWN : 34,
5389                         LEFT  : 37,  UP   : 38,  RIGHT    : 39,  DOWN      : 40
5390                 };
5391                 var codeToKeyName = {};
5392                 for (var i in keyNameToCode) {
5393                         codeToKeyName['' + keyNameToCode[i]] = i;
5394                 }
5395
5396                 var operaBrokenChars = '0123456789=;\'\\\/#,.-';
5397
5398                 /*
5399                 PrivateMethod: removeKeyListener
5400                         Removes a listener for a key combination.
5401                 
5402                 Arguments:
5403                         ident - identifier returned from addKeyListener.
5404                 */
5405
5406                 function removeKeyListener (ident) {
5407                         var keyType = keyTypes[ident];
5408                         if (! keyType) { return false; }
5409                         var listeners = keyListeners[keyType];
5410                         if (! listeners) { return false; }
5411                         for (var i = 0, len = listeners.length; i < len; i++) {
5412                                 if (listeners[i][0] == ident) {
5413                                         listeners.splice(i, 1);
5414                                         return true;
5415                                 }
5416                         }
5417                         return false;
5418                 }
5419
5420                 /*
5421                 PrivateMethod: initTopKeyListner
5422                         Adds an event listener for keyboard events, for key listeners added by addKeyListener.
5423
5424                 Arguments:
5425                         type - press|down|up - the type of key event.
5426                 */
5427
5428                 function initTopKeyListener (type) {
5429                         topKeyListeners[type] = r.addListener(document, 'key' + type, function (e) {
5430                                 var mods = 0;
5431                                 if (e.ctrlKey) { mods += CTRL; }
5432                                 if (e.altKey) { mods += ALT; }
5433                                 if (e.shiftKey) { mods += SHIFT; }
5434                                 var keyType = e.chr ? e.chr.toLowerCase() : e.key ? e.key.toLowerCase() : e.keyCode;
5435                                 var eventType = mods + ':' + keyType + ':' + type;
5436                                 var listeners = keyListeners[eventType] ? keyListeners[eventType].slice(0) : [];
5437                                 // if the user pressed shift, but event didn't specify that, include it
5438                                 if (e.shiftKey) { // upper-case letter, should match regardless of shift
5439                                         var shiftEventType = (mods & ~ SHIFT) + ':' + keyType + ':' + type;
5440                                         if (keyListeners[shiftEventType]) {
5441                                                 for (var i = 0, len = keyListeners[shiftEventType].length; i < len; i++) {
5442                                                         listeners[listeners.length] = keyListeners[shiftEventType][i];
5443                                                 }
5444                                         }
5445                                 }
5446                                 
5447                                 if (! listeners) { return; }
5448                                 
5449                                 for (var i = 0, len = listeners.length; i < len; i++) {
5450                                         //call listener and look out for preventing the default
5451                                         if (listeners[i][2].call(listeners[i][3] || this, e) === false) {
5452                                                 e.preventDefault();
5453                                         }
5454                                 }
5455                                 return !e.defaultPrevented();
5456                         });
5457                 }
5458                 
5459                 /*
5460                 PrivateMethod: clearEvents
5461                         Removes all current listeners to avoid IE mem leakage
5462                 */
5463                 function clearEvents() {
5464                         var ident;
5465                         for (ident in listenersByEventId) {
5466                                 r.removeListener(ident);
5467                         }
5468                 }
5469
5470                 // for opera madness
5471                 var previousKeyDownKeyCode;
5472
5473                 var operaResizeListener,
5474                         operaDocScrollListener;
5475
5476                 /*
5477                 PrivateMethod: addDomListener
5478                         Adds an event listener to a browser object. Manages differences with certain events.
5479
5480                 Arguments:
5481                         attachTo - the browser object to attach the event to.
5482                         name - the generic name of the event (inc. mousewheel)
5483                 */
5484                 function addDomListener (attachTo, name, capturingMode) {
5485                         var wheelEventName;
5486
5487                         capturingMode = !!capturingMode;
5488                         
5489                         if (glow.env.opera) {
5490                                 if (name.toLowerCase() == 'resize' && !operaResizeListener && attachTo == window) {
5491                                         operaResizeListener = r.addListener(window.document.body, 'resize', function (e) { r.fire(window, 'resize', e); });
5492                                 } else if (name.toLowerCase() == 'scroll' && !operaDocScrollListener && attachTo == window) {
5493                                         operaDocScrollListener = r.addListener(window.document, 'scroll', function (e) { r.fire(window, 'scroll', e); });
5494                                 }
5495                         }
5496                         
5497                         var callback = function (e) {
5498                                 if (! e) { e = window.event; }
5499                                 var event = new r.Event(),
5500                                         lowerCaseName = name.toLowerCase();
5501                                 event.nativeEvent = e;
5502                                 event.source = e.target || e.srcElement;
5503
5504                                 event.relatedTarget = e.relatedTarget || (lowerCaseName == "mouseover" ? e.fromElement : e.toElement);
5505                             event.button = glow.env.ie ? (e.button & 1 ? 0 : e.button & 2 ? 2 : 1) : e.button;
5506                                 if (e.pageX || e.pageY) {
5507                                         event.pageX = e.pageX;
5508                                         event.pageY = e.pageY;
5509                                 } else if (e.clientX || e.clientY)      {
5510                                         event.pageX = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft;
5511                                         event.pageY = e.clientY + document.body.scrollTop + document.documentElement.scrollTop;
5512                                 }
5513                                 if (lowerCaseName == 'mousewheel') {
5514                                         // this works in latest opera, but have read that it needs to be switched in direction
5515                                         // if there was an opera bug, I can't find which version it was fixed in
5516                                         event.wheelDelta =
5517                                                 e.wheelDelta ? e.wheelDelta / 120 :
5518                                                 e.detail ? - e.detail / 3 :
5519                                                         0;
5520                                         if (event.wheelDelta == 0) { return; }
5521                                 }
5522                                 if (lowerCaseName.indexOf("key") != -1) {
5523                                         event.altKey = !! e.altKey;
5524                                         event.ctrlKey = !! e.ctrlKey;
5525                                         event.shiftKey = !! e.shiftKey;
5526
5527                                         if (name == 'keydown') {
5528                                                 previousKeyDownKeyCode = e.keyCode;
5529                                         }
5530
5531                                         event.charCode = e.keyCode && e.charCode !== 0 ? undefined : e.charCode;
5532
5533                                         if (lowerCaseName == 'keypress') {
5534                                                 if (typeof(event.charCode) == 'undefined') {
5535                                                         event.charCode = e.keyCode;
5536                                                 }
5537
5538                                                 if (glow.env.opera && event.charCode && event.charCode == previousKeyDownKeyCode &&
5539                                                         operaBrokenChars.indexOf(String.fromCharCode(event.charCode)) == -1
5540                                                 ) {
5541                                                         event.charCode = undefined;
5542                                                         event.keyCode = previousKeyDownKeyCode;
5543                                                 }
5544                                         }
5545                                         
5546                                         // make things a little more sane in opera
5547                                         if (event.charCode && event.charCode <= 49) { event.charCode = undefined; }
5548
5549                                         if (event.charCode) {
5550                                                 event.chr = String.fromCharCode(event.charCode);
5551                                         }
5552                                         else if (e.keyCode) {
5553                                                 event.charCode = undefined;
5554                                                 event.keyCode = keyNameAliases[e.keyCode.toString()] || e.keyCode;
5555                                                 event.key = codeToKeyName[event.keyCode];
5556                                                 if (specialPrintables[event.key]) {
5557                                                         event.chr = specialPrintables[event.key];
5558                                                         event.charCode = event.chr.charCodeAt(0);
5559                                                 }
5560                                         }
5561
5562                                         if (event.chr) {
5563                                                 event.capsLock =
5564                                                         event.chr.toUpperCase() != event.chr ? // is lower case
5565                                                                 event.shiftKey :
5566                                                         event.chr.toLowerCase() != event.chr ? // is upper case
5567                                                                 ! event.shiftKey :
5568                                                                 undefined; // can only tell for keys with case
5569                                         }
5570                                 }
5571
5572                                 r.fire(this, name, event);
5573                                 if (event.defaultPrevented()) {
5574                                         return false;
5575                                 }
5576                         };
5577
5578
5579                         if (attachTo.addEventListener && (!glow.env.webkit || glow.env.webkit > 418)) {
5580
5581                                 // This is to fix an issue between Opera and everything else.
5582                                 // Opera needs to have an empty eventListener attached to the parent
5583                                 // in order to fire a captured event (in our case we are using capture if
5584                                 // the event is focus/blur) on an element when the element is the eventTarget.
5585                                 //
5586                                 // It is only happening in Opera 9, Opera 10 doesn't show this behaviour.
5587                                 // It looks like Opera has a bug, but according to the W3C Opera is correct...
5588                                 //
5589                                 // "A capturing EventListener will not be triggered by events dispatched 
5590                                 // directly to the EventTarget upon which it is registered."
5591                                 // http://www.w3.org/TR/DOM-Level-2-Events/events.html#Events-flow-capture
5592                                 if (
5593                                         ( name == 'focus' || name == 'blur' )
5594                                         && (glow.env.opera)
5595                                 ) {
5596                                         attachTo.parentNode.addEventListener(name, function(){}, true);
5597                                 }
5598
5599                                 attachTo.addEventListener(name.toLowerCase() == 'mousewheel' && glow.env.gecko ? 'DOMMouseScroll' : name, callback, capturingMode);
5600
5601                         } else {
5602
5603                                 var onName = 'on' + name;
5604                                 var existing = attachTo[onName];
5605                                 if (existing) {
5606                                         attachTo[onName] = function () {
5607                                                 // we still need to return false if either the existing or new callback returns false
5608                                                 var existingReturn = existing.apply(this, arguments),
5609                                                         callbackReturn = callback.apply(this, arguments);
5610                                                 
5611                                                 return (existingReturn !== false) && (callbackReturn !== false);
5612                                         };
5613                                 } else {
5614                                         attachTo[onName] = callback;
5615                                 }
5616
5617                         }
5618
5619                         attachTo = null;
5620
5621                 }
5622                 
5623                 /**
5624                 Add mouseEnter or mouseLeave 'event' to an element
5625                 @private
5626                 @param {HTMLElement} attachTo Element to create mouseenter / mouseleave for
5627                 @param {Boolean} isLeave Create mouseleave or mouseenter?
5628                 */
5629                 function addMouseEnterLeaveEvent(attachTo, isLeave) {
5630                         var elm = $(attachTo),
5631                                 listenFor = isLeave ? "mouseout" : "mouseover",
5632                                 toFire = isLeave ? "mouseleave" : "mouseenter";
5633                         
5634                         r.addListener(attachTo, listenFor, function(e) {
5635                                 var relatedTarget = $(e.relatedTarget);
5636                                 // if the mouse has come from outside elm...
5637                                 if ( !relatedTarget.eq(elm) && !relatedTarget.isWithin(elm) ) {
5638                                         // return false if default is prevented by mouseEnter event
5639                                         return !r.fire(elm[0], toFire, e).defaultPrevented();
5640                                 }
5641                         })
5642                 }
5643                 
5644                 /**
5645                 @name glow.events._copyListeners 
5646                 @function
5647                 @private
5648                 @description Maps event listeners from one set of nodes to another in the order they appear in each NodeList.
5649                         Note, it doesn't copy events from a node's children.
5650                 
5651                 
5652                 @param {NodeList} from NodeList to copy events from
5653                 @param {NodeList} to NodeList to copy events to
5654         
5655                 @returns {Boolean}
5656                 
5657                 @example
5658                         var listener = glow.events.addListener(...);
5659                         glow.events.removeListener(listener);
5660                 */
5661                 r._copyListeners = function(from, to) {
5662                         // grab all the elements (including children)
5663                         var i = from.length,
5664                                 // events attached to a particular element
5665                                 elementEvents,
5666                                 // name of the current event we're looking at
5667                                 eventName,
5668                                 // current listener index
5669                                 listenerIndex,
5670                                 // number of listeners to an event
5671                                 listenersLen,
5672                                 // listener definition from listenersByObjId
5673                                 listener;
5674                         
5675                         // loop through all items
5676                         while(i--) {
5677                                 // has a glow event been assigned to this node?
5678                                 if ( from[i][psuedoPrivateEventKey] ) {
5679                                         // get listeners for that event
5680                                         elementEvents = listenersByObjId[ from[i][psuedoPrivateEventKey] ];
5681                                         // loop through event names
5682                                         for (eventName in elementEvents) {
5683                                                 listenerIndex = 0;
5684                                                 listenersLen = elementEvents[eventName].length;
5685                                                 // loop through listeners to that event
5686                                                 for (; listenerIndex < listenersLen; listenerIndex++) {
5687                                                         listener = elementEvents[eventName][listenerIndex];
5688                                                         // listen to them on the clone
5689                                                         r.addListener(to[i], eventName, listener[2], listener[3]);
5690                                                 }
5691                                         }
5692                                 }
5693                         }
5694                 }
5695
5696                 /**
5697                 @name glow.events.addListener
5698                 @function
5699                 @description Adds an event listener to an object (e.g. a DOM Element or Glow widget).
5700                 
5701                         Some non-standard dom events are available:
5702                         
5703                         <dl>
5704                                 <dt>mouseenter</dt>
5705                                 <dd>Fires when the mouse enters this element specifically, does not bubble</dd>
5706                                 <dt>mouseleave/dt>
5707                                 <dd>Fires when the mouse leaves this element specifically, does not bubble</dd>
5708                         </dl>
5709                 
5710                 @param {String | NodeList | Object} attachTo The object to attach the event listener to.
5711                 
5712                         If the parameter is a string, then it is treated as a CSS selector 
5713                         and the listener is attached to all matching elements.
5714                         
5715                         If the parameter is a {@link glow.dom.NodeList}, then the listener 
5716                         is attached to all elements in the NodeList.
5717                 
5718                 @param {String} name The event name. 
5719                 
5720                         Listeners for DOM events should not begin with 'on' (i.e. 'click' 
5721                         rather than 'onclick')
5722                                    
5723                 @param {Function} callback The function to be called when the event fires.
5724                         
5725                 @param {Object} [context] The execution scope of the callback.
5726                 
5727                         If this parameter is not passed then the attachTo object will be the 
5728                         scope of the callback.
5729                 
5730                 @returns {Number | Undefined}
5731                 
5732                         A unique identifier for the event suitable for passing to 
5733                         {@link glow.events.removeListener}. If an empty NodeList or CSS 
5734                         selector that returns no elements is passed, then undefined is returned.
5735                 
5736                 @example
5737                         glow.events.addListener('#nav', 'click', function () {
5738                                 alert('nav clicked');
5739                         });
5740
5741                         glow.events.addListener(myLightBox, 'close', this.showSurvey, this);
5742                 */
5743                 r.addListener = function (attachTo, name, callback, context) {
5744                         var capturingMode = false;
5745                         if (! attachTo) { throw 'no attachTo paramter passed to addListener'; }
5746
5747                         if (typeof attachTo == 'string') {
5748                                 if (! glow.dom) { throw "glow.dom must be loaded to use a selector as the first argument to glow.events.addListener"; }
5749                                 attachTo = $(attachTo);
5750                         }
5751                         
5752                         if (glow.dom && attachTo instanceof glow.dom.NodeList) {
5753                                 var listenerIds = [],
5754                                         i = attachTo.length;
5755
5756                                 //attach the event for each element, return an array of listener ids
5757                                 while (i--) {
5758                                         listenerIds[i] = r.addListener(attachTo[i], name, callback, context);
5759                                 }
5760                                 
5761                                 return listenerIds;
5762                         }
5763         
5764                         var objIdent;
5765                         if (! (objIdent = attachTo[psuedoPrivateEventKey])) {
5766                                 objIdent = attachTo[psuedoPrivateEventKey] = objid++;
5767                         }
5768                         var ident = eventid++;
5769                         var listener = [ objIdent, name, callback, context, ident ];
5770                         listenersByEventId[ident] = listener;
5771
5772                         var objListeners = listenersByObjId[objIdent];
5773                         if (! objListeners) { objListeners = listenersByObjId[objIdent] = {}; }
5774                         var objEventListeners = objListeners[name];
5775                         if (! objEventListeners) { objEventListeners = objListeners[name] = []; }
5776                         objEventListeners[objEventListeners.length] = listener;
5777                         
5778                         if ((attachTo.addEventListener || attachTo.attachEvent) && ! domListeners[objIdent + ':' + name]) {
5779                                 // handle 'special' dom events (ie, ones that aren't directly mapped to real dom events)
5780                                 // we don't actually add a dom listener for these event names
5781                                 switch (name) {
5782                                         case "mouseenter":
5783                                                 addMouseEnterLeaveEvent(attachTo, false);
5784                                                 return ident;
5785                                         case "mouseleave":
5786                                                 addMouseEnterLeaveEvent(attachTo, true);
5787                                                 return ident;
5788
5789                                         // Focus and blur events:
5790                                         // Convert focus/blur events to use capturing.
5791                                         // This allows form elements to be used with event delegation.
5792
5793                                         case "focus":
5794                                                 // IE
5795                                                 // If IE then change focus/blur events to focusin/focusout events.
5796                                                 // This allows input elements to bubble, so form elements work with event delegation.
5797                                                 if (glow.env.ie) {
5798                                                         addFocusInOutEvent(attachTo, true);
5799                                                         return ident;
5800                                                 }
5801                                                 // Everything else
5802                                                 else {
5803                                                         capturingMode = true
5804                                                 }
5805                                                 break;
5806
5807                                         case "blur":
5808                                                 // IE
5809                                                 // If IE then change focus/blur events to focusin/focusout events.
5810                                                 // This allows input elements to bubble, so form elements work with event delegation.
5811                                                 if (glow.env.ie) {
5812                                                         addFocusInOutEvent(attachTo, false);
5813                                                         return ident;
5814                                                 }
5815                                                 // Everything else
5816                                                 else {
5817                                                         capturingMode = true
5818                                                 }
5819                                                 break;
5820
5821                                 }
5822                                 
5823                                 addDomListener(attachTo, name, capturingMode);
5824                                 domListeners[objIdent + ':' + name] = true;
5825                         }
5826                         return ident;
5827                 };
5828
5829                 /**
5830                 Add focusIn or focusOut 'event' to an element
5831                 @private
5832                 @param {HTMLElement} attachTo Element to create focusIn / focusOut for
5833                 @param {Boolean} event Create focusIn or focusOut?
5834                 */
5835                 function addFocusInOutEvent(attachTo, event) {
5836                         var listenFor = event ? 'focusin' : 'focusout',
5837                                 toFire    = event ? 'focus'   : 'blur';
5838                         r.addListener(attachTo, listenFor, function(e) {
5839                                 return !r.fire(attachTo, toFire, e).defaultPrevented();
5840                         });
5841                 }
5842
5843                 /**
5844                 @name glow.events.removeListener 
5845                 @function
5846                 @description Removes a listener created with addListener
5847
5848                 @param {Number} ident An identifier returned from {@link glow.events.addListener}.
5849         
5850                 @returns {Boolean}
5851                 
5852                 @example
5853                         var listener = glow.events.addListener(...);
5854                         glow.events.removeListener(listener);
5855                 */
5856                 r.removeListener = function (ident) {
5857                         if (ident && ident.toString().indexOf('k:') != -1) {
5858                                 return removeKeyListener(ident);
5859                         }
5860                         if (ident instanceof Array) {
5861                                 //call removeListener for each array member
5862                                 var i = ident.length; while(i--) {
5863                                         r.removeListener(ident[i]);
5864                                 }
5865                                 return true;
5866                         }
5867                         var listener = listenersByEventId[ident];
5868                         if (! listener) { return false; }
5869                         delete listenersByEventId[ident];
5870                         var listeners = listenersByObjId[listener[0]][listener[1]];
5871                         for (var i = 0, len = listeners.length; i < len; i++) {
5872                                 if (listeners[i] == listener) {
5873                                         listeners.splice(i, 1);
5874                                         break;
5875                                 }
5876                         }
5877                         if (! listeners.length) {
5878                                 delete listenersByObjId[listener[0]][listener[1]];
5879                         }
5880                         var listenersLeft = false;
5881                         for (var i in listenersByObjId[listener[0]]) { listenersLeft = true; break;     }
5882                         if (! listenersLeft) {
5883                                 delete listenersByObjId[listener[0]];
5884                         }
5885                         return true;
5886                 };
5887                 
5888                 /**
5889                 @name glow.events.removeAllListeners 
5890                 @function
5891                 @description Removes all listeners attached to a given object
5892
5893                 @param {String | glow.dom.NodeList | Object | Object[] } detachFrom The object(s) to remove listeners from.
5894                 
5895                         If the parameter is a string, then it is treated as a CSS selector,
5896                         listeners are removed from all nodes.
5897                 
5898                 @returns glow.events
5899                 
5900                 @example
5901                         glow.events.removeAllListeners("#myDiv");
5902                 */
5903                 r.removeAllListeners = function(obj) {
5904                         var i,
5905                                 objId,
5906                                 listenerIds = [],
5907                                 listenerIdsLen = 0,
5908                                 eventName,
5909                                 events;
5910                         
5911                         // cater for selector
5912                         if (typeof obj == "string") {
5913                                 // get nodes
5914                                 obj = $(obj);
5915                         }
5916                         // cater for arrays & nodelists
5917                         if (obj instanceof Array || obj instanceof glow.dom.NodeList) {
5918                                 //call removeAllListeners for each array member
5919                                 i = obj.length; while(i--) {
5920                                         r.removeAllListeners(obj[i]);
5921                                 }
5922                                 return r;
5923                         }
5924                         
5925                         // get the objects id
5926                         objId = obj[psuedoPrivateEventKey];
5927                         
5928                         // if it doesn't have an id it doesn't have events... return
5929                         if (!objId) {
5930                                 return r;
5931                         }
5932                         events = listenersByObjId[objId];
5933                         for (eventName in events) {
5934                                 i = events[eventName].length; while(i--) {
5935                                         listenerIds[listenerIdsLen++] = events[eventName][i][4];
5936                                 }
5937                         }
5938                         // remove listeners for that object
5939                         if (listenerIds.length) {
5940                                 r.removeListener( listenerIds );
5941                         }
5942                         return r;
5943                 }
5944
5945                 /**
5946                 @name glow.events.fire
5947                 @function
5948                 @description Fires an event on an object.
5949
5950                 @param {Object} attachedTo The object that the event is associated with.
5951                 
5952                 @param {String} name The name of the event.
5953                 
5954                         Event names should not start with the word 'on'.
5955                         
5956                 @param {Object | glow.events.Event} [event] An event object or properties to add to a default event object
5957                 
5958                         If not specified, a generic event object is created. If you provide a simple
5959                         object, a default Event will be created with the properties from the provided object.
5960         
5961                 @returns {Object}
5962                 
5963                         The event object.
5964                 
5965                 @example
5966                         // firing a custom event
5967                         Ball.prototype.move = function () {
5968                                 // move the ball...
5969                                 // check its position
5970                                 if (this._height == 0) {
5971                                         var event = glow.events.fire(this, 'bounce', {
5972                                                 bounceCount: this._bounceCount
5973                                         });
5974                                         
5975                                         // handle what to do if a listener returned false
5976                                         if ( event.defaultPrevented() ) {
5977                                                 this.stopMoving();
5978                                         }
5979                                 }
5980                         };
5981                         
5982                 @example
5983                         // listening to a custom event
5984                         var myBall = new Ball();
5985                         
5986                         glow.events.addListener(myBall, "bounce", function(event) {
5987                                 if (event.bounceCount == 3) {
5988                                         // stop bouncing after 3 bounces
5989                                         return false;
5990                                 }
5991                         });
5992                 */
5993                 r.fire = function (attachedTo, name, e) {
5994                         if (! attachedTo) throw 'glow.events.fire: required parameter attachedTo not passed (name: ' + name + ')';
5995                         if (! name) throw 'glow.events.fire: required parameter name not passed';
5996                         if (! e) { e = new r.Event(); }
5997                         if ( e.constructor === Object ) { e = new r.Event( e ) }
5998
5999                         if (typeof attachedTo == 'string') {
6000                                 if (! glow.dom) { throw "glow.dom must be loaded to use a selector as the first argument to glow.events.addListener"; }
6001                                 attachedTo = $(attachedTo);
6002                         }
6003
6004                         e.type = name;
6005                         e.attachedTo = attachedTo;
6006                         if (! e.source) { e.source = attachedTo; }
6007
6008                         if (attachedTo instanceof glow.dom.NodeList) {
6009
6010                                 attachedTo.each(function(i){
6011
6012                                         callListeners(attachedTo[i], e);
6013
6014                                 });
6015
6016                         } else {
6017
6018                                 callListeners(attachedTo, e);
6019
6020                         }
6021
6022                         return e;
6023                 };
6024
6025                 function callListeners(attachedTo, e) {
6026                         
6027                         var objIdent,
6028                                 objListeners,
6029                                 objEventListeners = objListeners && objListeners[e.type];
6030
6031                         // 3 assignments, but stop assigning if any of them are false
6032                         (objIdent = attachedTo[psuedoPrivateEventKey]) &&
6033                         (objListeners = listenersByObjId[objIdent]) &&
6034                         (objEventListeners = objListeners[e.type]);
6035
6036                         if (! objEventListeners) { return e; }
6037
6038                         var listener;
6039                         var listeners = objEventListeners.slice(0);
6040
6041                         // we make a copy of the listeners before calling them, as the event handlers may
6042                         // remove themselves (took me a while to track this one down)
6043                         for (var i = 0, len = listeners.length; i < len; i++) {
6044                                 listener = listeners[i];
6045                                 if ( listener[2].call(listener[3] || attachedTo, e) === false ) {
6046                                         e.preventDefault();
6047                                 }
6048                         }
6049
6050                 }
6051
6052                 /**
6053                 @private
6054                 @name glow.events.addKeyListener
6055                 @deprecated
6056                 @function
6057                 @description Adds an event listener for a keyboard event HELLO.
6058                 
6059                         <p><em>Notes for Opera</em></p>
6060                         
6061                         It is currently impossible to differentiate certain key events in 
6062                         Opera (for example the RIGHT (the right arrow key) and the 
6063                         apostrope (') result in the same code). For this reason pressing 
6064                         either of these keys will result in key listeners specified as 
6065                         "RIGHT" and/or "'" to be fired.
6066                         
6067                         <p><em>Key Identifiers</em></p>
6068                         
6069                         The key param uses the following strings to refer to special keys, 
6070                         i.e. non alpha-numeric keys.
6071                         
6072                         <ul>
6073                         <li>CAPSLOCK</li>
6074                         <li>NUMLOCK</li>
6075                         <li>SCROLLLOCK</li>
6076                         <li>BREAK</li>
6077                         <li>BACKTICK</li>
6078                         <li>BACKSPACE</li>
6079                         <li>PRINTSCREEN</li>
6080                         <li>MENU</li>
6081                         <li>SPACE</li>
6082                         <li>ESC</li>
6083                         <li>TAB</li>
6084                         <li>META</li>
6085                         <li>RIGHTMETA</li>
6086                         <li>ENTER</li>
6087                         <li>F1</li>
6088                         <li>F2</li>
6089                         <li>F3</li>
6090                         <li>F4</li>
6091                         <li>F5</li>
6092                         <li>F6</li>
6093                         <li>F7</li>
6094                         <li>F8</li>
6095                         <li>F9</li>
6096                         <li>F10</li>
6097                         <li>F11</li>
6098                         <li>F12</li>
6099                         <li>INS</li>
6100                         <li>HOME</li>
6101                         <li>PAGEUP</li>
6102                         <li>DEL</li>
6103                         <li>END</li>
6104                         <li>PAGEDOWN</li>
6105                         <li>LEFT</li>
6106                         <li>UP</li>
6107                         <li>RIGHT</li>
6108                         <li>DOWN</li>
6109                         </ul>
6110
6111                 @param {String} key The key or key combination to listen to. 
6112                 
6113                         This parameter starts with modifier keys 'CTRL', 'ALT' and 
6114                         'SHIFT'. Modifiers can appear in any combination and order and are 
6115                         separated by a '+'.
6116                         
6117                         Following any modifiers is the key character. To specify a 
6118                         character code, use the appropriate escape sequence (e.g. 
6119                         "CTRL+\u0065" = CTRL+e"). 
6120                         
6121                         To specify a special key, the key character should be replaced with 
6122                         a key identifier, see description below (e.g. "RIGHT" specifies the 
6123                         right arrow key).
6124                         
6125                 @param {String} type The type of key press to listen to.
6126                         
6127                         Possible values for this parameter are:
6128                         
6129                         <dl>
6130                         <dt>press</dt><dd>the key is pressed (comparable to a mouse click)</dd>
6131                         <dt>down</dt><dd>the key is pushed down</dd>
6132                         <dt>up</dt><dd>the key is released</dd>
6133                         </dl>
6134                 
6135                 @param {Function} callback The function to be called when the event fires.
6136                         
6137                 @param {Object} [context] The execution scope of the callback.
6138         
6139                         If this parameter is not passed then the attachTo object will be the 
6140                         context of the callback.
6141         
6142                 @returns {Number}
6143                 
6144                         A unique identifier for the event suitable for passing to 
6145                         {@link glow.events.removeListener}.
6146                 
6147                 @example
6148                         glow.events.addKeyListener("CTRL+ALT+a", "press",
6149                         function () { alert("CTRL+ALT+a pressed"); }
6150                     );
6151                         glow.events.addKeyListener("SHIFT+\u00A9", "down",
6152                                 function () { alert("SHIFT+© pushed") }
6153                         );
6154                 */
6155                 var keyRegex = /^((?:(?:ctrl|alt|shift)\+)*)(?:(\w+|.)|[\n\r])$/i;
6156                 r.addKeyListener = function (key, type, callback, context) {
6157                         type.replace(/^key/i, "");
6158                         type = type.toLowerCase();
6159                         if (! (type == 'press' || type == 'down' || type == 'up')) {
6160                                 throw 'event type must be press, down or up';
6161                         }
6162                         if (! topKeyListeners[type]) { initTopKeyListener(type); }
6163                         var res = key.match(keyRegex),
6164                                 mods = 0,
6165                                 charCode;
6166                         if (! res) { throw 'key format not recognised'; }
6167                         if (res[1].toLowerCase().indexOf('ctrl') != -1)  { mods += CTRL;  }
6168                         if (res[1].toLowerCase().indexOf('alt') != -1)   { mods += ALT;   }
6169                         if (res[1].toLowerCase().indexOf('shift') != -1) { mods += SHIFT; }
6170                         var eventKey = mods + ':' + (res[2] ? res[2].toLowerCase() : '\n') + ':' + type;
6171                         var ident = 'k:' + keyListenerId++;
6172                         keyTypes[ident] = eventKey;
6173                         var listeners = keyListeners[eventKey];
6174                         if (! listeners) { listeners = keyListeners[eventKey] = []; }
6175                         listeners[listeners.length] = [ident, type, callback, context];
6176                         return ident;
6177                 };
6178
6179                 /**
6180                 @name  glow.events.Event
6181                 @class
6182                 @param {Object} [properties] Properties to add to the Event instance.
6183                         Each key-value pair in the object will be added to the Event as
6184                         properties
6185                 @description Object passed into all events
6186                         
6187                         Some of the properties described below are only available for 
6188                         certain event types. These are listed in the property descriptions 
6189                         where applicable.
6190                         
6191                         <p><em>Notes for Opera</em></p>
6192                         
6193                         The information returned from Opera about key events does not allow 
6194                         certain keys to be differentiated. This mainly applies to special 
6195                         keys, such as the arrow keys, function keys, etc. which conflict 
6196                         with some printable characters.
6197                         
6198                 */
6199                 r.Event = function ( obj ) {
6200                         if( obj ) {
6201                                 glow.lang.apply( this, obj );
6202                         }
6203                 };
6204                 
6205                 /**
6206                 @name glow.events.Event#attachedTo
6207                 @type Object | Element
6208                 @description The object/element that the listener is attached to.
6209                 
6210                         See the description for 'source' for more details.
6211                 */
6212                 
6213                 /**
6214                 @name glow.events.Event#source
6215                 @type Element
6216                 @description The actual object/element that the event originated from.
6217                         
6218                         For example, you could attach a listener to an 'ol' element to 
6219                         listen for clicks. If the user clicked on an 'li' the source property 
6220                         would be the 'li' element, and 'attachedTo' would be the 'ol'.
6221                 */
6222                 
6223                 /**
6224                 @name glow.events.Event#pageX
6225                 @type Number
6226                 @description The horizontal position of the mouse pointer in the page in pixels.
6227                 
6228                         <p><em>Only available for mouse events.</em></p>
6229                 */
6230                 
6231                 /**
6232                 @name glow.events.Event#pageY
6233                 @type Number
6234                 @description The vertical position of the mouse pointer in the page in pixels.
6235                 
6236                         <p><em>Only available for mouse events.</em></p>
6237                 */
6238                 
6239                 /**
6240                 @name glow.events.Event#button
6241                 @type Number
6242                 @description  A number representing which button was pressed.
6243                 
6244                         <p><em>Only available for mouse events.</em></p>
6245                         
6246                         0 for the left button, 1 for the middle button or 2 for the right button.
6247                 */
6248
6249                 /**
6250                 @name glow.events.Event#relatedTarget
6251                 @type Element
6252                 @description The element that the mouse has come from or is going to.
6253                 
6254                         <p><em>Only available for mouse over/out events.</em></p>
6255                 */
6256                 
6257                 /**
6258                 @name glow.events.Event#wheelDelta
6259                 @type Number
6260                 @description The number of clicks up (positive) or down (negative) that the user moved the wheel.
6261                 
6262                         <p><em>Only available for mouse wheel events.</em></p>
6263                 */
6264                 
6265                 /**
6266                 @name glow.events.Event#ctrlKey
6267                 @type Boolean
6268                 @description Whether the ctrl key was pressed during the key event.
6269                 
6270                         <p><em>Only available for keyboard events.</em></p>
6271                 */
6272                 
6273                 /**
6274                 @name glow.events.Event#shiftKey
6275                 @type Boolean
6276                 @description  Whether the shift key was pressed during the key event.
6277                 
6278                         <p><em>Only available for keyboard events.</em></p>
6279                 */
6280                 
6281                 /**
6282                 @name glow.events.Event#altKey
6283                 @type Boolean
6284                 @description Whether the alt key was pressed during the key event.
6285                 
6286                         <p><em>Only available for keyboard events.</em></p>
6287                 */
6288                 
6289                 /**
6290                 @name glow.events.Event#capsLock                        
6291                 @type Boolean | Undefined
6292                 @description Whether caps-lock was on during the key event
6293                 
6294                         <p><em>Only available for keyboard events.</em></p>
6295                 
6296                         If the key is not alphabetic, this property will be undefined 
6297                         as it is not possible to tell if caps-lock is on in this scenario.
6298                 */
6299                 
6300                 /**
6301                 @name glow.events.Event#keyCode
6302                 @type Number
6303                 @description An integer number represention of the keyboard key that was pressed.
6304                 
6305                         <p><em>Only available for keyboard events.</em></p>
6306                 */
6307                 
6308                 /**
6309                 @name glow.events.Event#key
6310                 @type String | Undefined
6311                 @description  A short identifier for the key for special keys.
6312                 
6313                         <p><em>Only available for keyboard events.</em></p>
6314                         
6315                         If the key was not a special key this property will be undefined.
6316                         
6317                         See the list of key identifiers in {@link glow.events.addKeyListener}
6318                 */
6319                 
6320                 /**
6321                 @name glow.events.Event#charCode
6322                 @type Number | Undefined
6323                 @description The unicode character code for a printable character.
6324                 
6325                         <p><em>Only available for keyboard events.</em></p>
6326                         
6327                         This will be undefined if the key was not a printable character.
6328                 */
6329                 
6330                 /**
6331                 @name glow.events.Event#chr
6332                 @type String
6333                 @description A printable character string.
6334                 
6335                         <p><em>Only available for keyboard events.</em></p>
6336                         
6337                         The string of the key that was pressed, for example 'j' or 's'.
6338                         
6339                         This will be undefined if the key was not a printable character.
6340                 */
6341                 
6342
6343                 /**
6344                 @name glow.events.Event#preventDefault
6345                 @function
6346                 @description Prevent the default action for events. 
6347                 
6348                         This can also be achieved by returning false from an event callback
6349                 
6350                 */
6351                 r.Event.prototype.preventDefault = function () {
6352                         if (this[psuedoPreventDefaultKey]) { return; }
6353                         this[psuedoPreventDefaultKey] = true;
6354                         if (this.nativeEvent && this.nativeEvent.preventDefault) {
6355                                 this.nativeEvent.preventDefault();
6356                                 this.nativeEvent.returnValue = false;
6357                         }
6358                 };
6359
6360                 /**
6361                 @name glow.events.Event#defaultPrevented
6362                 @function
6363                 @description Test if the default action has been prevented.
6364                 
6365                 @returns {Boolean}
6366                 
6367                         True if the default action has been prevented.
6368                 */
6369                 r.Event.prototype.defaultPrevented = function () {
6370                         return !! this[psuedoPreventDefaultKey];
6371                 };
6372
6373                 /**
6374                 @name glow.events.Event#stopPropagation
6375                 @function
6376                 @description Stops the event propagating. 
6377                 
6378                         For DOM events, this stops the event bubbling up through event 
6379                         listeners added to parent elements. The event object is marked as
6380                         having had propagation stopped (see 
6381                         {@link glow.events.Event#propagationStopped propagationStopped}).
6382                 
6383                 @example
6384                         // catch all click events that are not links
6385                         glow.events.addListener(
6386                                 document,
6387                                 'click',
6388                                 function () { alert('document clicked'); }
6389                         );
6390
6391                         glow.events.addListener(
6392                                 'a',
6393                                 'click',
6394                                 function (e) { e.stopPropagation(); }
6395                         );
6396                 */
6397                 r.Event.prototype.stopPropagation = function () {
6398                         if (this[psuedoStopPropagationKey]) { return; }
6399                         this[psuedoStopPropagationKey] = true;
6400                         var e = this.nativeEvent;
6401                         if (e) {
6402                                 e.cancelBubble = true;
6403                                 if (e.stopPropagation) { e.stopPropagation(); }
6404                         }
6405                 };
6406
6407                 /**
6408                 @name glow.events.Event#propagationStopped
6409                 @function
6410                 @description Tests if propagation has been stopped for this event.
6411                 
6412                 @returns {Boolean}
6413                 
6414                         True if event propagation has been prevented.
6415
6416                 */
6417                 r.Event.prototype.propagationStopped = function () {
6418                         return !! this[psuedoStopPropagationKey];
6419                 };
6420                 
6421                 //cleanup to avoid mem leaks in IE
6422                 if (glow.env.ie < 8 || glow.env.webkit < 500) {
6423                         r.addListener(window, "unload", clearEvents);
6424                 }
6425
6426                 glow.events = r;
6427                 glow.events.listenersByObjId = listenersByObjId;
6428         }
6429 });/**
6430 @name glow.data
6431 @namespace
6432 @description Serialising and de-serialising data
6433 @see <a href="../furtherinfo/data/data.shtml">Using glow.data</a>
6434 */
6435 (window.gloader || glow).module({
6436         name: "glow.data",
6437         library: ["glow", "1.7.0"],
6438         depends: [["glow", "1.7.0", "glow.dom"]],
6439         builder: function(glow) {
6440                 //private
6441
6442                 /*
6443                 PrivateProperty: TYPES
6444                         hash of strings representing data types
6445                 */
6446                 var TYPES = {
6447                         UNDEFINED : "undefined",
6448                         OBJECT    : "object",
6449                         NUMBER    : "number",
6450                         BOOLEAN   : "boolean",
6451                         STRING    : "string",
6452                         ARRAY     : "array",
6453                         FUNCTION  : "function",
6454                         NULL      : "null"
6455                 }
6456
6457                 /*
6458                 PrivateProperty: TEXT
6459                         hash of strings used in encoding/decoding
6460                 */
6461                 var TEXT = {
6462                         AT    : "@",
6463                         EQ    : "=",
6464                         DOT   : ".",
6465                         EMPTY : "",
6466                         AND   : "&",
6467                         OPEN  : "(",
6468                         CLOSE : ")"
6469                 }
6470
6471                 /*
6472                 PrivateProperty: JSON
6473                         nested hash of strings and regular expressions used in encoding/decoding Json
6474                 */
6475                 var JSON = {
6476                         HASH : {
6477                                 START     : "{",
6478                                 END       : "}",
6479                                 SHOW_KEYS : true
6480                         },
6481
6482                         ARRAY : {
6483                                 START     : "[",
6484                                 END       : "]",
6485                                 SHOW_KEYS : false
6486                         },
6487
6488                         DATA_SEPARATOR   : ",",
6489                         KEY_SEPARATOR    : ":",
6490                         KEY_DELIMITER    : "\"",
6491                         STRING_DELIMITER : "\"",
6492
6493                         SAFE_PT1 : /^[\],:{}\s]*$/,
6494                         SAFE_PT2 : /\\./g,
6495                         SAFE_PT3 : /\"[^\"\\\n\r]*\"|true|false|null|-?\d+(?:\.\d*)?(:?[eE][+\-]?\d+)?/g,
6496                         SAFE_PT4 : /(?:^|:|,)(?:\s*\[)+/g
6497                 }
6498
6499                 /*
6500                 PrivateProperty: SLASHES
6501                         hash of strings and regular expressions used in encoding strings
6502                 */
6503                 var SLASHES = {
6504                         TEST : /[\b\n\r\t\\\f\"]/g,
6505                         B : {PLAIN : "\b", ESC : "\\b"},
6506                         N : {PLAIN : "\n", ESC : "\\n"},
6507                         R : {PLAIN : "\r", ESC : "\\r"},
6508                         T : {PLAIN : "\t", ESC : "\\t"},
6509                         F : {PLAIN : "\f", ESC : "\\f"},
6510                         SL : {PLAIN : "\\", ESC : "\\\\"},
6511                         QU : {PLAIN : "\"", ESC : "\\\""}
6512                 }
6513
6514                 /*
6515                 PrivateMethod: _replaceSlashes
6516                         Callback function for glow.lang.replace to escape appropriate characters
6517
6518                 Arguments:
6519                         s - the regex match to be tested
6520
6521                 Returns:
6522                         The escaped version of the input.
6523                 */
6524                 function _replaceSlashes(s) {
6525                         switch (s) {
6526                                 case SLASHES.B.PLAIN: return SLASHES.B.ESC;
6527                                 case SLASHES.N.PLAIN: return SLASHES.N.ESC;
6528                                 case SLASHES.R.PLAIN: return SLASHES.R.ESC;
6529                                 case SLASHES.T.PLAIN: return SLASHES.T.ESC;
6530                                 case SLASHES.F.PLAIN: return SLASHES.F.ESC;
6531                                 case SLASHES.SL.PLAIN: return SLASHES.SL.ESC;
6532                                 case SLASHES.QU.PLAIN: return SLASHES.QU.ESC;
6533                                 default: return s;
6534                         }
6535                 }
6536
6537                 /*
6538                 PrivateMethod: _getType
6539                         Returns the data type of the object
6540
6541                 Arguments:
6542                         object - the object to be tested
6543
6544                 Returns:
6545                         A one of the TYPES constant properties that represents the data type of the object.
6546                 */
6547                 function _getType(object) {
6548                         if((typeof object) == TYPES.OBJECT) {
6549                                 if (object == null) {
6550                                         return TYPES.NULL;
6551                                 } else {
6552                                         return (object instanceof Array)?TYPES.ARRAY:TYPES.OBJECT;
6553                                 }
6554                         } else {
6555                                 return (typeof object);
6556                         }
6557                 }
6558
6559                 //public
6560                 glow.data = {
6561                         /**
6562                         @name glow.data.encodeUrl
6563                         @function
6564                         @description Encodes an object for use as a query string.
6565                         
6566                                 Returns a string representing the object suitable for use 
6567                                 as a query string, with all values suitably escaped.
6568                                 It does not include the initial question mark. Where the 
6569                                 input field was an array, the key is repeated in the output.
6570                         
6571                         @param {Object} object The object to be encoded.
6572                         
6573                                 This must be a hash whose values can only be primitives or 
6574                                 arrays of primitives.
6575                         
6576                         @returns {String}
6577                         
6578                         @example
6579                                 var getRef = glow.data.encodeUrl({foo: "Foo", bar: ["Bar 1", "Bar2"]});
6580                                 // will return "foo=Foo&bar=Bar%201&bar=Bar2"
6581                         */
6582                         encodeUrl : function (object) {
6583                                 var objectType = _getType(object);
6584                                 var paramsList = [];
6585                                 var listLength = 0;
6586
6587                                 if (objectType != TYPES.OBJECT) {
6588                                         throw new Error("glow.data.encodeUrl: cannot encode item");
6589                                 } else {
6590                                         for (var key in object) {
6591                                                 switch(_getType(object[key])) {
6592                                                         case TYPES.FUNCTION:
6593                                                         case TYPES.OBJECT:
6594                                                                 throw new Error("glow.data.encodeUrl: cannot encode item");
6595                                                                 break;
6596                                                         case TYPES.ARRAY:
6597                                                                 for(var i = 0, l = object[key].length; i < l; i++) {
6598                                                                         switch(_getType(object[key])[i]) {
6599                                                                                 case TYPES.FUNCTION:
6600                                                                                 case TYPES.OBJECT:
6601                                                                                 case TYPES.ARRAY:
6602                                                                                         throw new Error("glow.data.encodeUrl: cannot encode item");
6603                                                                                         break;
6604                                                                                 default:
6605                                                                                         paramsList[listLength++] = key + TEXT.EQ + encodeURIComponent(object[key][i]);
6606                                                                         }
6607                                                                 }
6608                                                                 break;
6609                                                         default:
6610                                                                 paramsList[listLength++] = key + TEXT.EQ + encodeURIComponent(object[key]);
6611                                                 }
6612                                         }
6613
6614                                         return paramsList.join(TEXT.AND);
6615                                 }
6616                         },
6617                         /**
6618                         @name glow.data.decodeUrl
6619                         @function
6620                         @description Decodes a query string into an object.
6621                         
6622                                 Returns an object representing the data given by the query 
6623                                 string, with all values suitably unescaped. All keys in the 
6624                                 query string are keys of the object. Repeated keys result 
6625                                 in an array.
6626                         
6627                         @param {String} string The query string to be decoded.
6628                         
6629                                 It should not include the initial question mark.
6630                         
6631                         @returns {Object}
6632                         
6633                         @example
6634                                 var getRef = glow.data.decodeUrl("foo=Foo&bar=Bar%201&bar=Bar2");
6635                                 // will return the object {foo: "Foo", bar: ["Bar 1", "Bar2"]}
6636                         */
6637                         decodeUrl : function (text) {
6638                                 if(_getType(text) != TYPES.STRING) {
6639                                         throw new Error("glow.data.decodeUrl: cannot decode item");
6640                                 } else if (text === "") {
6641                                         return {};
6642                                 }
6643
6644                                 var result = {};
6645                                 var keyValues = text.split(/[&;]/);
6646
6647                                 var thisPair, key, value;
6648
6649                                 for(var i = 0, l = keyValues.length; i < l; i++) {
6650                                         thisPair = keyValues[i].split(TEXT.EQ);
6651                                         if(thisPair.length != 2) {
6652                                                 throw new Error("glow.data.decodeUrl: cannot decode item");
6653                                         } else {
6654                                                 key   = glow.lang.trim( decodeURIComponent(thisPair[0]) );
6655                                                 value = glow.lang.trim( decodeURIComponent(thisPair[1]) );
6656
6657                                                 switch (_getType(result[key])) {
6658                                                         case TYPES.ARRAY:
6659                                                                 result[key][result[key].length] = value;
6660                                                                 break;
6661                                                         case TYPES.UNDEFINED:
6662                                                                 result[key] = value;
6663                                                                 break;
6664                                                         default:
6665                                                                 result[key] = [result[key], value];
6666                                                 }
6667                                         }
6668                                 }
6669
6670                                 return result;
6671                         },
6672                         /**
6673                         @name glow.data.encodeJson
6674                         @function
6675                         @description Encodes an object into a string JSON representation.
6676                         
6677                                 Returns a string representing the object as JSON.
6678                         
6679                         @param {Object} object The object to be encoded.
6680                          
6681                                 This can be arbitrarily nested, but must not contain 
6682                                 functions or cyclical structures.
6683                         
6684                         @returns {Object}
6685                         
6686                         @example
6687                                 var myObj = {foo: "Foo", bar: ["Bar 1", "Bar2"]};
6688                                 var getRef = glow.data.encodeJson(myObj);
6689                                 // will return '{"foo": "Foo", "bar": ["Bar 1", "Bar2"]}'
6690                         */
6691                         encodeJson : function (object, options) {
6692                                 function _encode(object, options)
6693                                 {
6694                                         if(_getType(object) == TYPES.ARRAY) {
6695                                                 var type = JSON.ARRAY;
6696                                         } else {
6697                                                 var type = JSON.HASH;
6698                                         }
6699
6700                                         var serial = [type.START];
6701                                         var len = 1;
6702                                         var dataType;
6703                                         var notFirst = false;
6704
6705                                         for(var key in object) {
6706                                                 dataType = _getType(object[key]);
6707
6708                                                 if(dataType != TYPES.UNDEFINED) { /* ignore undefined data */
6709                                                         if(notFirst) {
6710                                                                 serial[len++] = JSON.DATA_SEPARATOR;
6711                                                         }
6712                                                         notFirst = true;
6713
6714                                                         if(type.SHOW_KEYS) {
6715                                                                 serial[len++] = JSON.KEY_DELIMITER;
6716                                                                 serial[len++] = key;
6717                                                                 serial[len++] = JSON.KEY_DELIMITER;
6718                                                                 serial[len++] = JSON.KEY_SEPARATOR;
6719                                                         }
6720
6721                                                         switch(dataType) {
6722                                                                 case TYPES.FUNCTION:
6723                                                                         throw new Error("glow.data.encodeJson: cannot encode item");
6724                                                                         break;
6725                                                                 case TYPES.STRING:
6726                                                                 default:
6727                                                                         serial[len++] = JSON.STRING_DELIMITER;
6728                                                                         serial[len++] = glow.lang.replace(object[key], SLASHES.TEST, _replaceSlashes);
6729                                                                         serial[len++] = JSON.STRING_DELIMITER;
6730                                                                         break;
6731                                                                 case TYPES.NUMBER:
6732                                                                 case TYPES.BOOLEAN:
6733                                                                         serial[len++] = object[key];
6734                                                                         break;
6735                                                                 case TYPES.OBJECT:
6736                                                                 case TYPES.ARRAY:
6737                                                                         serial[len++] = _encode(object[key], options);
6738                                                                         break;
6739                                                                 case TYPES.NULL:
6740                                                                         serial[len++] = TYPES.NULL;
6741                                                                         break;
6742                                                         }
6743                                                 }
6744                                         }
6745                                         serial[len++] = type.END;
6746
6747                                         return serial.join(TEXT.EMPTY);
6748                                 }
6749
6750                                 options = options || {};
6751                                 var type = _getType(object);
6752
6753                                 if((type == TYPES.OBJECT) || (type == TYPES.ARRAY)) {
6754                                         return _encode(object, options);
6755                                 } else {
6756                                         throw new Error("glow.data.encodeJson: cannot encode item");
6757                                 }
6758                         },
6759                         /**
6760                         @name glow.data.decodeJson
6761                         @function
6762                         @description Decodes a string JSON representation into an object.
6763                                 
6764                                 Returns a JavaScript object that mirrors the data given.
6765                         
6766                         @param {String} string The string to be decoded.
6767                                 Must be valid JSON. 
6768                         
6769                         @param {Object} opts
6770                         
6771                                         Zero or more of the following as properties of an object:
6772                                         @param {Boolean} [opts.safeMode=false] Whether the string should only be decoded if it is  deemed "safe". 
6773                                         The json.org regular expression checks are used. 
6774                         
6775                         @returns {Object}
6776                         
6777                         @example
6778                                 var getRef = glow.data.decodeJson('{foo: "Foo", bar: ["Bar 1", "Bar2"]}');
6779                                 // will return {foo: "Foo", bar: ["Bar 1", "Bar2"]}
6780                         
6781                                 var getRef = glow.data.decodeJson('foobar', {safeMode: true});
6782                                 // will throw an error
6783                         */
6784                         decodeJson : function (text, options) {
6785                                 if(_getType(text) != TYPES.STRING) {
6786                                         throw new Error("glow.data.decodeJson: cannot decode item");
6787                                 }
6788
6789                                 options = options || {};
6790                                 options.safeMode = options.safeMode || false;
6791
6792                                 var canEval = true;
6793
6794                                 if(options.safeMode) {
6795                                         canEval = (JSON.SAFE_PT1.test(text.replace(JSON.SAFE_PT2, TEXT.AT).replace(JSON.SAFE_PT3, JSON.ARRAY.END).replace(JSON.SAFE_PT4, TEXT.EMPTY)));
6796                                 }
6797
6798                                 if(canEval) {
6799                                         try {
6800                                                 return eval(TEXT.OPEN + text + TEXT.CLOSE);
6801                                         }
6802                                         catch(e) {/* continue to error */}
6803                                 }
6804
6805                                 throw new Error("glow.data.decodeJson: cannot decode item");
6806                         },
6807                         /**
6808                         @name glow.data.escapeHTML
6809                         @function
6810                         @description Escape HTML entities.
6811                         
6812                                 Returns a string with HTML entities escaped.
6813                         
6814                         @param {String} string The string to be escaped.
6815                         
6816                         @returns {String}
6817                         
6818                         @example
6819                                 // useful for protecting against XSS attacks:
6820                                 var fieldName = '" onclick="alert(\'hacked\')" name="';
6821                         
6822                                 // but should be used in all cases like this:
6823                                 glow.dom.create('<input name="' + glow.data.escapeHTML(untrustedString) + '"/>');
6824                          */
6825                         escapeHTML : function (html) {
6826                                 return glow.dom.create('<div></div>').text(html).html();
6827                         }                  
6828                 };
6829         }
6830 });
6831 /**
6832 @name glow.net
6833 @namespace
6834 @description Sending data to & from the server
6835 @see <a href="../furtherinfo/net/net.shtml">Using glow.net</a>
6836 */
6837 (window.gloader || glow).module({
6838         name: "glow.net",
6839         library: ["glow", "1.7.0"],
6840         depends: [["glow", "1.7.0", "glow.data", "glow.events"]],
6841         builder: function(glow) {
6842                 //private
6843
6844                 var STR = {
6845                                 XML_ERR:"Cannot get response as XML, check the mime type of the data",
6846                                 POST_DEFAULT_CONTENT_TYPE:'application/x-www-form-urlencoded;'
6847                         },
6848                         endsPlusXml = /\+xml$/,
6849                         /**
6850                          * @name glow.net.scriptElements
6851                          * @private
6852                          * @description Script elements that have been added via {@link glow.net.loadScript loadScript}
6853                          * @type Array
6854                          */
6855                         scriptElements = [],
6856                         /**
6857                          * @name glow.net.callbackPrefix
6858                          * @private
6859                          * @description Callbacks in _jsonCbs will be named this + a number
6860                          * @type String
6861                          */
6862                         callbackPrefix = "c",
6863                         /**
6864                          * @name glow.net.globalObjectName
6865                          * @private
6866                          * @description Name of the global object used to store loadScript callbacks
6867                          * @type String
6868                          */
6869                         globalObjectName = "_" + glow.UID + "loadScriptCbs",
6870                         $ = glow.dom.get,
6871                         events = glow.events,
6872                         emptyFunc = function(){};
6873
6874                 /**
6875                  * @name glow.net.xmlHTTPRequest
6876                  * @private
6877                  * @function
6878                  * @description Creates an xmlHTTPRequest transport
6879                  * @returns Object
6880                  */
6881                 function xmlHTTPRequest() {
6882                         //try IE first. IE7's xmlhttprequest and XMLHTTP6 are broken. Avoid avoid avoid!
6883                         if (window.ActiveXObject) {
6884                                 return (xmlHTTPRequest = function() { return new ActiveXObject("Microsoft.XMLHTTP"); })();
6885                         } else {
6886                                 return (xmlHTTPRequest = function() { return new XMLHttpRequest(); })();
6887                         }
6888                 }
6889
6890                 /**
6891                  * @name glow.net.populateOptions
6892                  * @private
6893                  * @function
6894                  * @description Adds defaults to get / post option object
6895                  * @param {Object} opts Object to add defaults to
6896                  * @returns Object
6897                  */
6898                 function populateOptions(opts) {
6899                         return glow.lang.apply(
6900                                 {
6901                                         onLoad: emptyFunc,
6902                                         onError: emptyFunc,
6903                                         onAbort: emptyFunc,
6904                                         headers: {},
6905                                         async: true,
6906                                         useCache: false,
6907                                         data: null,
6908                                         defer: false,
6909                                         forceXml: false
6910                                 },
6911                                 opts || {}
6912                         );
6913                 }
6914
6915                 /*
6916                 PrivateMethod: noCacheUrl
6917                         Adds random numbers to the querystring of a url so the browser doesn't use a cached version
6918                 */
6919
6920                 function noCacheUrl(url) {
6921                         return [url, (/\?/.test(url) ? "&" : "?"), "a", new Date().getTime(), parseInt(Math.random()*100000)].join("");
6922                 }
6923
6924                 /*
6925                 PrivateMethod: makeXhrRequest
6926                         Makes an http request
6927                 */
6928                 /**
6929                  * @name glow.net.makeXhrRequest
6930                  * @private
6931                  * @function
6932                  * @description Makes an xhr http request
6933                  * @param {String} method HTTP Method
6934                  * @param {String} url URL of the request
6935                  * @param {Object} Options, see options for {@link glow.net.get}
6936                  * @returns Object
6937                  */
6938                 function makeXhrRequest(method, url, opts) {
6939                         var req = xmlHTTPRequest(), //request object
6940                                 data = opts.data && (typeof opts.data == "string" ? opts.data : glow.data.encodeUrl(opts.data)),
6941                                 i,
6942                                 request = new Request(req, opts);
6943
6944                         if (!opts.useCache) {
6945                                 url = noCacheUrl(url);
6946                         }
6947
6948                         //open needs to go first to maintain cross-browser support for readystates
6949                         req.open(method, url, opts.async);
6950
6951                         //add custom headers
6952                         for (i in opts.headers) {
6953                                 req.setRequestHeader(i, opts.headers[i]);
6954                         }
6955
6956                         function send() {
6957                                 request.send = emptyFunc;
6958                                 if (opts.async) {
6959                                         //sort out the timeout if there is one
6960                                         if (opts.timeout) {
6961                                                 request._timeout = setTimeout(function() {
6962                                                         abortRequest(request);
6963                                                         var response = new Response(req, true, request);
6964                                                         events.fire(request, "error", response);
6965                                                 }, opts.timeout * 1000);
6966                                         }
6967
6968                                         req.onreadystatechange = function() {
6969                                                 if (req.readyState == 4) {
6970                                                         //clear the timeout
6971                                                         request._timeout && clearTimeout(request._timeout);
6972                                                         //set as completed
6973                                                         request.completed = true;
6974                                                         var response = new Response(req, false, request);
6975                                                         if (response.wasSuccessful) {
6976                                                                 events.fire(request, "load", response);
6977                                                         } else {
6978                                                                 events.fire(request, "error", response);
6979                                                         }
6980                                                         // prevent parent scopes leaking (cross-page) in IE
6981                                                         req.onreadystatechange = new Function();
6982                                                 }
6983                                         };
6984                                         req.send(data);
6985                                         return request;
6986                                 } else {
6987                                         req.send(data);
6988                                         request.completed = true;
6989                                         var response = new Response(req, false, request);
6990                                         if (response.wasSuccessful) {
6991                                                 events.fire(request, "load", response);
6992                                         } else {
6993                                                 events.fire(request, "error", response);
6994                                         }
6995                                         return response;
6996                                 }
6997                         }
6998
6999                         request.send = send;
7000                         return opts.defer ? request : send();
7001                 }
7002
7003                 //public
7004                 var r = {}; //the module
7005
7006                 /**
7007                 @name glow.net.get
7008                 @function
7009                 @description Makes an HTTP GET request to a given url
7010
7011                 @param {String} url
7012                         Url to make the request to. This can be a relative path. You cannot make requests
7013                         for files on other domains, to do that you must put your data in a javascript
7014                         file and use {@link glow.net.loadScript} to fetch it.
7015                 @param {Object} opts
7016                         Options Object of options.
7017                         @param {Function} [opts.onLoad] Callback to execute when the request has sucessfully loaded
7018                                 The callback is passed a Response object as its first parameter.
7019                         @param {Function} [opts.onError] Callback to execute if the request was unsucessful
7020                                 The callback is passed a Response object as its first parameter.
7021                                 This callback will also be run if a request times out.
7022                         @param {Function} [opts.onAbort] Callback to execute if the request is aborted
7023                         @param {Object} [opts.headers] A hash of headers to send along with the request
7024                                 Eg {"Accept-Language": "en-gb"}
7025                         @param {Boolean} [opts.async=true] Should the request be performed asynchronously?
7026                         @param {Boolean} [opts.useCache=false] Allow a cached response
7027                                 If false, a random number is added to the query string to ensure a fresh version of the file is being fetched
7028                         @param {Number} [opts.timeout] Time to allow for the request in seconds
7029                                 No timeout is set by default. Only applies for async requests. Once
7030                                 the time is reached, the error event will fire with a "408" status code.
7031                         @param {Boolean} [opts.defer=false] Do not send the request straight away
7032                                 Deferred requests need to be triggered later using myRequest.send()
7033                         @param {Boolean} [opts.forceXml=false] Treat the response as XML.
7034                                 This will allow you to use {@link glow.net.Response#xml response.xml()}
7035                                 even if the response has a non-XML mime type.
7036
7037                 @returns {glow.net.Request|glow.net.Response}
7038                         A response object for non-defered sync requests, otherwise a
7039                         request object is returned
7040
7041                 @example
7042                         var request = glow.net.get("myFile.html", {
7043                                 onLoad: function(response) {
7044                                         alert("Got file:\n\n" + response.text());
7045                                 },
7046                                 onError: function(response) {
7047                                         alert("Error getting file: " + response.statusText());
7048                                 }
7049                         });
7050                 */
7051                 r.get = function(url, o) {
7052                         o = populateOptions(o);
7053                         return makeXhrRequest('GET', url, o);
7054                 };
7055
7056                 /**
7057                 @name glow.net.post
7058                 @function
7059                 @description Makes an HTTP POST request to a given url
7060
7061                 @param {String} url
7062                         Url to make the request to. This can be a relative path. You cannot make requests
7063                         for files on other domains, to do that you must put your data in a javascript
7064                         file and use {@link glow.net.loadScript} to fetch it.
7065                 @param {Object|String} data
7066                         Data to post, either as a JSON-style object or a urlEncoded string
7067                 @param {Object} opts
7068                         Same options as {@link glow.net.get}
7069
7070                 @returns {Number|glow.net.Response}
7071                         An integer identifying the async request, or the response object for sync requests
7072
7073                 @example
7074                         var postRef = glow.net.post("myFile.html",
7075                                 {key:"value", otherkey:["value1", "value2"]},
7076                                 {
7077                                         onLoad: function(response) {
7078                                                 alert("Got file:\n\n" + response.text());
7079                                         },
7080                                         onError: function(response) {
7081                                                 alert("Error getting file: " + response.statusText());
7082                                         }
7083                                 }
7084                         );
7085                 */
7086                 r.post = function(url, data, o) {
7087                         o = populateOptions(o);
7088                         o.data = data;
7089                         if (!o.headers["Content-Type"]) {
7090                                 o.headers["Content-Type"] = STR.POST_DEFAULT_CONTENT_TYPE;
7091                         }
7092                         return makeXhrRequest('POST', url, o);
7093                 };
7094
7095                 /**
7096                 @name glow.net.loadScript
7097                 @function
7098                 @description Loads data by adding a script element to the end of the page
7099                         This can be used cross domain, but should only be used with trusted
7100                         sources as any javascript included in the script will be executed.
7101
7102                 @param {String} url
7103                         Url of the script. Use "{callback}" in the querystring as the callback
7104                         name if the data source supports it, then you can use the options below
7105                 @param {Object} [opts]
7106                         An object of options to use if "{callback}" is specified in the url.
7107                         @param {Function} [opts.onLoad] Called when loadScript succeeds.
7108                                 The parameters are passed in by the external data source
7109                         @param {Function} [opts.onError] Called on timeout
7110                                 No parameters are passed
7111                         @param {Function} [opts.onAbort] Called if the request is aborted
7112                         @param {Boolean} [opts.useCache=false] Allow a cached response
7113                         @param {Number} [opts.timeout] Time to allow for the request in seconds
7114                         @param {String} [opts.charset] Charset attribute value for the script
7115
7116                 @returns {glow.net.Request}
7117
7118                 @example
7119                         glow.net.loadScript("http://www.server.com/json/tvshows.php?jsoncallback={callback}", {
7120                                 onLoad: function(data) {
7121                                         alert("Data loaded");
7122                                 }
7123                         });
7124                 */
7125                 r.loadScript = function(url, opts) {
7126                         //id of the request
7127                         var newIndex = scriptElements.length,
7128                                 //script element that gets inserted on the page
7129                                 script,
7130                                 //generated name of the callback, may not be used
7131                                 callbackName = callbackPrefix + newIndex,
7132                                 opts = populateOptions(opts),
7133                                 request = new Request(newIndex, opts),
7134                                 url = opts.useCache ? url : noCacheUrl(url),
7135                                 //the global property used to hide callbacks
7136                                 globalObject = window[globalObjectName] || (window[globalObjectName] = {});
7137
7138                         //assign onload
7139                         if (opts.onLoad != emptyFunc) {
7140                                 globalObject[callbackName] = function() {
7141                                         //clear the timeout
7142                                         request._timeout && clearTimeout(request._timeout);
7143                                         //set as completed
7144                                         request.completed = true;
7145                                         // call the user's callback
7146                                         opts.onLoad.apply(this, arguments);
7147                                         // cleanup references to prevent leaks
7148                                         request.destroy();
7149                                         script = globalObject[callbackName] = undefined;
7150                                         delete globalObject[callbackName];
7151                                 };
7152                                 url = glow.lang.interpolate(url, {callback: globalObjectName + "." + callbackName});
7153                         }
7154
7155                         script = scriptElements[newIndex] = document.createElement("script");
7156
7157                         if (opts.charset) {
7158                                 script.charset = opts.charset;
7159                         }
7160
7161                         //add abort event
7162                         events.addListener(request, "abort", opts.onAbort);
7163
7164                         glow.ready(function() {
7165                                 //sort out the timeout
7166                                 if (opts.timeout) {
7167                                         request._timeout = setTimeout(function() {
7168                                                 abortRequest(request);
7169                                                 opts.onError();
7170                                         }, opts.timeout * 1000);
7171                                 }
7172                                 //using setTimeout to stop Opera 9.0 - 9.26 from running the loaded script before other code
7173                                 //in the current script block
7174                                 if (glow.env.opera) {
7175                                         setTimeout(function() {
7176                                                 if (script) { //script may have been removed already
7177                                                         script.src = url;
7178                                                 }
7179                                         }, 0);
7180                                 } else {
7181                                         script.src = url;
7182                                 }
7183                                 //add script to page
7184                                 document.body.appendChild(script);
7185                         });
7186
7187                         return request;
7188                 }
7189
7190                 /**
7191                  *      @name glow.net.abortRequest
7192                  *      @private
7193                  *      @function
7194                  *      @description Aborts the request
7195                  *              Doesn't trigger any events
7196                  *
7197                  *      @param {glow.net.Request} req Request Object
7198                  *      @returns this
7199                  */
7200                 function abortRequest(req) {
7201                         var nativeReq = req.nativeRequest,
7202                                 callbackIndex = req._callbackIndex;
7203
7204                         //clear timeout
7205                         req._timeout && clearTimeout(req._timeout);
7206                         //different if request came from loadScript
7207                         if (nativeReq) {
7208                                 //clear listeners
7209                                 // prevent parent scopes leaking (cross-page) in IE
7210                                 nativeReq.onreadystatechange = new Function();
7211                                 nativeReq.abort();
7212                         } else if (callbackIndex) {
7213                                 //clear callback
7214                                 window[globalObjectName][callbackPrefix + callbackIndex] = emptyFunc;
7215                                 //remove script element
7216                                 glow.dom.get(scriptElements[callbackIndex]).destroy();
7217                         }
7218                 }
7219
7220                 /**
7221                  * @name glow.net.Request
7222                  * @class
7223                  * @description Returned by {@link glow.net.post post}, {@link glow.net.get get} async requests and {@link glow.net.loadScript loadScript}
7224                  * @glowPrivateConstructor There is no direct constructor, since {@link glow.net.post post} and {@link glow.net.get get} create the instances.
7225                  */
7226                  
7227                 /**
7228                  * @name glow.net.Request#event:load
7229                  * @event
7230                  * @param {glow.events.Event} event Event Object
7231                  * @description Fired when the request is sucessful
7232                  *   For a get / post request, this will be fired when request returns
7233                  *   with an HTTP code of 2xx. loadScript requests will fire 'load' only
7234                  *   if {callback} is used in the URL.
7235                  */
7236                  
7237                 /**
7238                  * @name glow.net.Request#event:abort
7239                  * @event
7240                  * @param {glow.events.Event} event Event Object
7241                  * @description Fired when the request is aborted
7242                  *   If you cancel the default (eg, by returning false) the request
7243                  *   will continue.
7244                  * @description Returned by {@link glow.net.post glow.net.post}, {@link glow.net.get glow.net.get} async requests and {@link glow.net.loadScript glow.net.loadScript}
7245                  * @see <a href="../furtherinfo/net/net.shtml">Using glow.net</a>
7246                  * @glowPrivateConstructor There is no direct constructor, since {@link glow.net.post glow.net.post} and {@link glow.net.get glow.net.get} create the instances.
7247                  */
7248                  
7249                 /**
7250                  * @name glow.net.Request#event:error
7251                  * @event
7252                  * @param {glow.events.Event} event Event Object
7253                  * @description Fired when the request is unsucessful
7254                  *   For a get/post request, this will be fired when request returns
7255                  *   with an HTTP code which isn't 2xx or the request times out. loadScript
7256                  *   calls will fire 'error' only if the request times out.
7257                  */
7258                  
7259                  
7260                 /*
7261                  We don't want users to create instances of this class, so the constructor is documented
7262                  out of view of jsdoc
7263
7264                  @param {Object} requestObj
7265                         Object which represents the request type.
7266                         For XHR requests it should be an XmlHttpRequest object, for loadScript
7267                         requests it should be a number, the Index of the callback in glow.net._jsonCbs
7268                  @param {Object} opts
7269                         Zero or more of the following as properties of an object:
7270                         @param {Function} [opts.onLoad] Called when the request is sucessful
7271                         @param {Function} [opts.onError] Called when a request is unsucessful
7272                         @param {Function} [opts.onAbort] Called when a request is aborted
7273
7274                 */
7275                 function Request(requestObj, opts) {
7276                         /**
7277                          * @name glow.net.Request#_timeout
7278                          * @private
7279                          * @description timeout ID. This is set by makeXhrRequest or loadScript
7280                          * @type Number
7281                          */
7282                         this._timeout = null;
7283                         
7284                         /*
7285                          @name glow.net.Request#_forceXml
7286                          @private
7287                          @type Boolean
7288                          @description Force the response to be treated as xml
7289                         */
7290                         this._forceXml = opts.forceXml;
7291                         
7292                         // force the reponse to be treated as xml
7293                         // IE doesn't support overrideMineType, we need to deal with that in {@link glow.net.Response#xml}
7294                         if (opts.forceXml && requestObj.overrideMimeType) {
7295                                 requestObj.overrideMimeType('application/xml');
7296                         }
7297                         
7298                         /**
7299                          * @name glow.net.Request#complete
7300                          * @description Boolean indicating whether the request has completed
7301                          * @example
7302                                 // request.complete with an asynchronous call
7303                                 var request = glow.net.get(
7304                                         "myFile.html", 
7305                                         {
7306                                                 async: true,
7307                                                 onload: function(response) {
7308                                                         alert(request.complete); // returns true
7309                                                 }
7310                                         }
7311                                 );
7312                                 alert(request.complete); // returns boolean depending on timing of asynchronous call
7313
7314                                 // request.complete with a synchronous call
7315                                 var request = glow.net.get("myFile.html", {async: false;});
7316                                 alert(request.complete); // returns true
7317                          * @type Boolean
7318                          */
7319                         this.complete = false;
7320
7321                         if (typeof requestObj == "number") {
7322                                 /**
7323                                  * @name glow.net.Request#_callbackIndex
7324                                  * @private
7325                                  * @description Index of the callback in glow.net._jsonCbs
7326                                  *   This is only relavent for requests made via loadscript using the
7327                                  *   {callback} placeholder
7328                                  * @type Number
7329                                  */
7330                                 this._callbackIndex = requestObj;
7331                         } else {
7332                                 /**
7333                                  * @name glow.net.Request#nativeRequest
7334                                  * @description The request object from the browser.
7335                                  *   This may not have the same properties and methods across user agents.
7336                                  *   Also, this will be undefined if the request originated from loadScript.
7337                                  * @example
7338                                 var request = glow.net.get(
7339                                         "myFile.html", 
7340                                         {
7341                                                 async: true,
7342                                                 onload: function(response) {
7343                                                         alert(request.NativeObject); // returns Object()
7344                                                 }
7345                                         }
7346                                 );
7347                                  * @type Object
7348                                  */
7349                                 this.nativeRequest = requestObj;
7350                         }
7351
7352                         //assign events
7353                         var eventNames = ["Load", "Error", "Abort"], i=0;
7354
7355                         for (; i < 3; i++) {
7356                                 events.addListener(this, eventNames[i].toLowerCase(), opts["on" + eventNames[i]]);
7357                         }
7358
7359                 }
7360                 Request.prototype = {
7361                         /**
7362                         @name glow.net.Request#send
7363                         @function
7364                         @description Sends the request.
7365                                 This is done automatically unless the defer option is set
7366                         @example
7367                                 var request = glow.net.get(
7368                                         "myFile.html", 
7369                                         {
7370                                                 onload : function(response) {alert("Loaded");},
7371                                                 defer: true
7372                                         }
7373                                 );
7374                                 request.send(); // returns "Loaded"
7375                         @returns {Object}
7376                                 This for async requests or a response object for sync requests
7377                         */
7378                         //this function is assigned by makeXhrRequest
7379                         send: function() {},
7380                         /**
7381                          *      @name glow.net.Request#abort
7382                          *      @function
7383                          *      @description Aborts an async request
7384                          *              The load & error events will not fire. If the request has been
7385                          *              made using {@link glow.net.loadScript loadScript}, the script
7386                          *              may still be loaded but the callback will not be fired.
7387                          * @example
7388                                 var request = glow.net.get(
7389                                         "myFile.html", 
7390                                         {
7391                                                 async: true,
7392                                                 defer: true,
7393                                                 onabort: function() {
7394                                                         alert("Something bad happened.  The request was aborted.");
7395                                                 }
7396                                         }
7397                                 );
7398                                 request.abort(); // returns "Something bad happened.  The request was aborted"
7399                          *      @returns this
7400                          */
7401                         abort: function() {
7402                                 if (!this.completed && !events.fire(this, "abort").defaultPrevented()) {
7403                                         abortRequest(this);
7404                                 }
7405                                 return this;
7406                         },
7407                         /**
7408                          @name glow.net.Request#destroy
7409                          @function
7410                          @description Release memory from a {@link glow.net.loadScript} call.
7411                                 
7412                                 This is called automatically by {@link glow.net.loadScript loadScript}
7413                                 calls that have {callback} in the URL. However, if you are not using
7414                                 {callback}, you can use this method manually to release memory when
7415                                 the request has finished.
7416                          
7417                          @example
7418                                 var request = glow.net.loadScript('http://www.bbc.co.uk/whatever.js');
7419                         
7420                          @returns this
7421                         */
7422                         destroy: function() {
7423                                 if (this._callbackIndex !== undefined) {
7424                                         // set timeout is used here to prevent a crash in IE7 (possibly other version) when the script is from the filesystem
7425                                         setTimeout(function() {
7426                                                 $( scriptElements[this._callbackIndex] ).destroy();
7427                                                 scriptElements[this._callbackIndex] = undefined;
7428                                                 delete scriptElements[this._callbackIndex];
7429                                         }, 0);
7430                                 }
7431                                 return this;
7432                         }
7433                 };
7434
7435                 /**
7436                 @name glow.net.Response
7437                 @class
7438                 @description Provided in callbacks to {@link glow.net.post glow.net.post} and {@link glow.net.get glow.net.get}
7439                 @see <a href="../furtherinfo/net/net.shtml">Using glow.net</a>
7440
7441                 @glowPrivateConstructor There is no direct constructor, since {@link glow.net.post glow.net.post} and {@link glow.net.get glow.net.get} create the instances.
7442                 */
7443                 /*
7444                  These params are hidden as we don't want users to try and create instances of this...
7445
7446                  @param {XMLHttpRequest} nativeResponse
7447                  @param {Boolean} [timedOut=false] Set to true if the response timed out
7448                  @param {glow.net.Request} [request] Original request object
7449                 */
7450                 function Response(nativeResponse, timedOut, request) {
7451                         //run Event constructor
7452                         events.Event.call(this);
7453                         
7454                         /**
7455                         @name glow.net.Response#_request
7456                         @private
7457                         @description Original request object
7458                         @type glow.net.Request
7459                         */
7460                         this._request = request;
7461                         
7462                         /**
7463                         @name glow.net.Response#nativeResponse
7464                         @description The response object from the browser.
7465                                 This may not have the same properties and methods across user agents.
7466                         @type Object
7467                         */
7468                         this.nativeResponse = nativeResponse;
7469                         /**
7470                         @name glow.net.Response#status
7471                         @description HTTP status code of the response
7472                         @type Number
7473                         */
7474                         //IE reports status as 1223 rather than 204, for laffs
7475                         this.status = timedOut ? 408 :
7476                                 nativeResponse.status == 1223 ? 204 : nativeResponse.status;
7477
7478                         /**
7479                          * @name glow.net.Response#timedOut
7480                          * @description Boolean indicating if the requests time out was reached.
7481                          * @type Boolean
7482                          */
7483                         this.timedOut = !!timedOut;
7484
7485                         /**
7486                          * @name glow.net.Response#wasSuccessful
7487                          * @description  Boolean indicating if the request returned successfully.
7488                          * @type Boolean
7489                          */
7490                         this.wasSuccessful = (this.status >= 200 && this.status < 300) ||
7491                                 //from cache
7492                                 this.status == 304 ||
7493                                 //watch our for requests from file://
7494                                 (this.status == 0 && nativeResponse.responseText);
7495
7496                 }
7497                 
7498                 /**
7499                 @name glow.net-shouldParseAsXml
7500                 @function
7501                 @description Should the response be treated as xml? This function is used by IE only
7502                         'this' is the response object
7503                 @returns {Boolean}
7504                 */
7505                 function shouldParseAsXml() {
7506                         var contentType = this.header("Content-Type");
7507                         // IE 6 & 7 fail to recognise Content-Types ending +xml (eg application/rss+xml)
7508                         // Files from the filesystem don't have a content type, but could be xml files, parse them to be safe
7509                         return endsPlusXml.test(contentType) || contentType === '';
7510                 }
7511                 
7512                 //don't want to document this inheritance, it'll just confuse the user
7513                 glow.lang.extend(Response, events.Event, {
7514                         /**
7515                         @name glow.net.Response#text
7516                         @function
7517                         @description Gets the body of the response as plain text
7518                         @returns {String}
7519                                 Response as text
7520                         */
7521                         text: function() {
7522                                 return this.nativeResponse.responseText;
7523                         },
7524                         /**
7525                         @name glow.net.Response#xml
7526                         @function
7527                         @description Gets the body of the response as xml
7528                         @returns {xml}
7529                                 Response as XML
7530                         */
7531                         xml: function() {
7532                                 var nativeResponse = this.nativeResponse;
7533                                 
7534                                 if (
7535                                         // IE fails to recognise the doc as XML in some cases
7536                                         ( glow.env.ie && shouldParseAsXml.call(this) )
7537                                         // If the _forceXml option is set, we need to turn the response text into xml
7538                                         || ( this._request._forceXml && !this._request.nativeRequest.overrideMimeType && window.ActiveXObject )
7539                                 ) {
7540                                         var doc = new ActiveXObject("Microsoft.XMLDOM");
7541                     doc.loadXML( nativeResponse.responseText );
7542                                         return doc;
7543                                 }
7544                                 else {
7545                                         // check property exists
7546                                         if (!nativeResponse.responseXML) {
7547                                                 throw new Error(STR.XML_ERR);
7548                                         }
7549                                         return nativeResponse.responseXML;
7550                                 }                               
7551                         },
7552
7553                         /**
7554                         @name glow.net.Response#json
7555                         @function
7556                         @description Gets the body of the response as a json object
7557
7558                         @param {Boolean} [safeMode=false]
7559                                 If true, the response will be parsed using a string parser which
7560                                 will filter out non-JSON javascript, this will be slower but
7561                                 recommended if you do not trust the data source.
7562
7563                         @returns {Object}
7564                         */
7565                         json: function(safe) {
7566                                 return glow.data.decodeJson(this.text(), {safeMode:safe});
7567                         },
7568
7569                         /**
7570                         @name glow.net.Response#header
7571                         @function
7572                         @description Gets a header from the response
7573
7574                         @param {String} name
7575                                 Header name
7576
7577                         @returns {String}
7578                                 Header value
7579
7580                         @example var contentType = myResponse.header("Content-Type");
7581                         */
7582                         header: function(name) {
7583                                 return this.nativeResponse.getResponseHeader(name);
7584                         },
7585
7586                         /**
7587                         @name glow.net.Response#statusText
7588                         @function
7589                         @description Gets the meaning of {@link glow.net.Response#status myResponse.status}
7590
7591                         @returns {String}
7592                         */
7593                         statusText: function() {
7594                                 return this.timedOut ? "Request Timeout" : this.nativeResponse.statusText;
7595                         }
7596                 })
7597
7598                 glow.net = r;
7599         }
7600 });
7601
7602 /**
7603 @name glow.tweens
7604 @namespace
7605 @description Functions for modifying animations
7606 @see <a href="../furtherinfo/tweens">What are tweens?</a>
7607
7608 */
7609 (window.gloader || glow).module({
7610         name: "glow.tweens",
7611         library: ["glow", "1.7.0"],
7612         depends: [],
7613         builder: function(glow) {
7614
7615                 /*
7616                 PrivateMethod: _reverse
7617                         Takes a tween function and returns a function which does the reverse
7618                 */
7619                 function _reverse(tween) {
7620                         return function(t) {
7621                                 return 1 - tween(1 - t);
7622                         }
7623                 }
7624
7625                 glow.tweens = {
7626                         /**
7627                         @name glow.tweens.linear
7628                         @function
7629                         @description Returns linear tween.
7630
7631                                 Will transition values from start to finish with no
7632                                 acceleration or deceleration.
7633
7634                         @returns {Function}
7635                         */
7636                         linear: function() {
7637                                 return function(t) { return t; };
7638                         },
7639                         /**
7640                         @name glow.tweens.easeIn
7641                         @function
7642                         @description Creates a tween which starts off slowly and accelerates.
7643
7644                         @param {Number} [strength=2] How strong the easing is.
7645
7646                                 A higher number means the animation starts off slower and
7647                                 ends quicker.
7648
7649                         @returns {Function}
7650                         */
7651                         easeIn: function(strength) {
7652                                 strength = strength || 2;
7653                                 return function(t) {
7654                                         return Math.pow(1, strength - 1) * Math.pow(t, strength);
7655                                 }
7656                         },
7657                         /**
7658                         @name glow.tweens.easeOut
7659                         @function
7660                         @description Creates a tween which starts off fast and decelerates.
7661
7662                         @param {Number} [strength=2] How strong the easing is.
7663
7664                                 A higher number means the animation starts off faster and
7665                                 ends slower
7666
7667                         @returns {Function}
7668                          */
7669                         easeOut: function(strength) {
7670                                 return _reverse(this.easeIn(strength));
7671                         },
7672                         /**
7673                         @name glow.tweens.easeBoth
7674                         @function
7675                         @description Creates a tween which starts off slowly, accelerates then decelerates after the half way point.
7676
7677                                 This produces a smooth and natural looking transition.
7678
7679                         @param {Number} [strength=2] How strong the easing is.
7680
7681                                 A higher number produces a greater difference between
7682                                 start / end speed and the mid speed.
7683
7684                         @returns {Function}
7685                         */
7686                         easeBoth: function(strength) {
7687                                 return this.combine(this.easeIn(strength), this.easeOut(strength));
7688                         },
7689                         /**
7690                         @name glow.tweens.overshootIn
7691                         @function
7692                         @description Returns the reverse of {@link glow.tweens.overshootOut overshootOut}
7693
7694                         @param {Number} [amount=1.70158] How much to overshoot.
7695
7696                                 The default is 1.70158 which results in a 10% overshoot.
7697
7698                         @returns {Function}
7699                         */
7700                         overshootIn: function(amount) {
7701                                 return _reverse(this.overshootOut(amount));
7702                         },
7703                         /**
7704                         @name glow.tweens.overshootOut
7705                         @function
7706                         @description Creates a tween which overshoots its end point then returns to its end point.
7707
7708                         @param {Number} [amount=1.70158] How much to overshoot.
7709
7710                                 The default is 1.70158 which results in a 10% overshoot.
7711
7712                         @returns {Function}
7713                         */
7714                         overshootOut: function(amount) {
7715                                 amount = amount || 1.70158;
7716                                 return function(t) {
7717                                         if (t == 0 || t == 1) { return t; }
7718                                         return ((t -= 1)* t * ((amount + 1) * t + amount) + 1);
7719                                 }
7720                         },
7721                         /**
7722                         @name glow.tweens.overshootBoth
7723                         @function
7724                         @description Returns a combination of {@link glow.tweens.overshootIn overshootIn} and {@link glow.tweens.overshootOut overshootOut}
7725
7726                         @param {Number} [amount=1.70158] How much to overshoot.
7727
7728                                 The default is 1.70158 which results in a 10% overshoot.
7729
7730                         @returns {Function}
7731                         */
7732                         overshootBoth: function(amount) {
7733                                 return this.combine(this.overshootIn(amount), this.overshootOut(amount));
7734                         },
7735                         /**
7736                         @name glow.tweens.bounceIn
7737                         @function
7738                         @description Returns the reverse of {@link glow.tweens.bounceOut bounceOut}
7739
7740                         @returns {Function}
7741                         */
7742                         bounceIn: function() {
7743                                 return _reverse(this.bounceOut());
7744                         },
7745                         /**
7746                         @name glow.tweens.bounceOut
7747                         @function
7748                         @description Returns a tween which bounces against the final value 3 times before stopping
7749
7750                         @returns {Function}
7751                         */
7752                         bounceOut: function() {
7753                                 return function(t) {
7754                                         if (t < (1 / 2.75)) {
7755                                                 return 7.5625 * t * t;
7756                                         } else if (t < (2 / 2.75)) {
7757                                                 return (7.5625 * (t -= (1.5 / 2.75)) * t + .75);
7758                                         } else if (t < (2.5 / 2.75)) {
7759                                                 return (7.5625 * (t -= (2.25 / 2.75)) * t + .9375);
7760                                         } else {
7761                                                 return (7.5625 * (t -= (2.625 / 2.75)) * t + .984375);
7762                                         }
7763                                 };
7764                         },
7765                         /**
7766                         @name glow.tweens.bounceBoth
7767                         @function
7768                         @description Returns a combination of {@link glow.tweens.bounceIn bounceIn} and {@link glow.tweens.bounceOut bounceOut}
7769
7770                         @returns {Function}
7771                         */
7772                         bounceBoth: function() {
7773                                 return this.combine(this.bounceIn(), this.bounceOut());
7774                         },
7775                         /**
7776                         @name glow.tweens.elasticIn
7777                         @function
7778                         @description Returns the reverse of {@link glow.tweens.elasticOut elasticOut}
7779
7780                         @param {Number} [amplitude=1] How strong the elasticity is.
7781
7782                         @param {Number} [period=0.3] The frequency period.
7783
7784                         @returns {Function}
7785                         */
7786                         elasticIn: function(a, p) {
7787                                 return _reverse(this.elasticOut(a, p));
7788                         },
7789                         /**
7790                         @name glow.tweens.elasticOut
7791                         @function
7792                         @description Creates a tween which has an elastic movement.
7793
7794                                 You can tweak the tween using the parameters but you'll
7795                                 probably find the defaults sufficient.
7796
7797                         @param {Number} [amplitude=1] How strong the elasticity is.
7798
7799                         @param {Number} [period=0.3] The frequency period.
7800
7801                         @returns {Function}
7802                         */
7803                         elasticOut: function(a, p) {
7804                                 return function (t) {
7805                                         if (t == 0 || t == 1) {
7806                                                 return t;
7807                                         }
7808                                         if (!p) {
7809                                                 p = 0.3;
7810                                         }
7811                                         if (!a || a < 1) {
7812                                                 a = 1;
7813                                                 var s = p / 4;
7814                                         } else {
7815                                                 var s = p / (2 * Math.PI) * Math.asin(1 / a);
7816                                         }
7817                                         return a * Math.pow(2, -10 * t) * Math.sin( (t-s) * (2 * Math.PI) / p) + 1;
7818                                 }
7819                         },
7820                         /**
7821                         @name glow.tweens.elasticBoth
7822                         @function
7823                         @description Returns a combination of {@link glow.tweens.elasticIn elasticIn} and {@link glow.tweens.elasticOut elasticOut}
7824
7825                         @param {Number} [amplitude=1] How strong the elasticity is.
7826
7827                         @param {Number} [period=0.3] The frequency period.
7828
7829                         @returns {Function}
7830                         */
7831                         elasticBoth: function(a, p) {
7832                                 p = p || 0.45;
7833                                 return this.combine(this.elasticIn(a, p), this.elasticOut(a, p));
7834                         },
7835                         /**
7836                         @name glow.tweens.combine
7837                         @function
7838                         @description Create a tween from two tweens.
7839
7840                                 This can be useful to make custom tweens which, for example,
7841                                 start with an easeIn and end with an overshootOut. To keep
7842                                 the motion natural, you should configure your tweens so the
7843                                 first ends and the same velocity that the second starts.
7844
7845                         @param {Function} tweenIn Tween to use for the first half
7846
7847                         @param {Function} tweenOut Tween to use for the second half
7848
7849                         @example
7850                                 // 4.5 has been chosen for the easeIn strength so it
7851                                 // ends at the same velocity as overshootOut starts.
7852                                 var myTween = glow.tweens.combine(
7853                                         glow.tweens.easeIn(4.5),
7854                                         glow.tweens.overshootOut()
7855                                 );
7856
7857                         @returns {Function}
7858                         */
7859                         combine: function(tweenIn, tweenOut) {
7860                                 return function (t) {
7861                                         if (t < 0.5) {
7862                                                 return tweenIn(t * 2) / 2;
7863                                         } else {
7864                                                 return tweenOut((t - 0.5) * 2) / 2 + 0.5;
7865                                         }
7866                                 }
7867                         }
7868                 };
7869         }
7870 });
7871 /**
7872 @name glow.anim
7873 @namespace
7874 @description Simple and powerful animations.
7875 @requires glow, glow.tweens, glow.events, glow.dom
7876 @see <a href="../furtherinfo/tweens/">What are tweens?</a>
7877 @see <a href="../furtherinfo/anim/">Guide to creating animations, with interactive examples</a>
7878 */
7879 (window.gloader || glow).module({
7880         name: "glow.anim",
7881         library: ["glow", "1.7.0"],
7882         depends: [["glow", "1.7.0", "glow.tweens", "glow.events", "glow.dom"]],
7883         builder: function(glow) {
7884                 //private
7885                 var $ = glow.dom.get,
7886                         manager,
7887                         events = glow.events,
7888                         dom = glow.dom,
7889                         get = dom.get,
7890                         hasUnits = /width|height|top$|bottom$|left$|right$|spacing$|indent$|font-size/,
7891                         noNegatives = /width|height|padding|opacity/,
7892                         usesYAxis = /height|top/,
7893                         getUnit = /(\D+)$/,
7894                         testElement = dom.create('<div style="position:absolute;visibility:hidden"></div>');
7895                 
7896                 /*
7897                   Converts event shortcuts in an options object to real events
7898                   instance - the object to add events to
7899                   opts - the options object containing the listener functions
7900                   eventProps - an array of property names of potential listeners, eg ['onFrame', 'onComplete']
7901                 */
7902                 function addEventsFromOpts(instance, opts, eventProps) {
7903                         for (var i = 0, len = eventProps.length; i < len; i++) {
7904                                 // does opts.onWhatever exist?
7905                                 if (opts[ eventProps[i] ]) {
7906                                         events.addListener(
7907                                                 instance,
7908                                                 // convert "onWhatever" to "whatever"
7909                                                 eventProps[i].slice(2).toLowerCase(),
7910                                                 opts[ eventProps[i] ]
7911                                         );
7912                                 }
7913                         }
7914                 }
7915                 
7916                 (function() {
7917                         var queue = [], //running animations
7918                                 queueLen = 0,
7919                                 intervalTime = 1, //ms between intervals
7920                                 interval; //holds the number for the interval
7921                         manager = {
7922                                 /**
7923                                 @name glow.anim-manager.addToQueue
7924                                 @private
7925                                 @function
7926                                 @description Adds an animation to the queue.
7927                                 */
7928                                 addToQueue: function(anim) {
7929                                         //add the item to the queue
7930                                         queue[queueLen++] = anim;
7931                                         anim._playing = true;
7932                                         anim._timeAnchor = anim._timeAnchor || new Date().valueOf();
7933                                         if (!interval) {
7934                                                 this.startInterval();
7935                                         }
7936                                 },
7937                                 /**
7938                                 @name glow.anim-manager.removeFromQueue
7939                                 @private
7940                                 @function
7941                                 @description Removes an animation from the queue.
7942                                 */
7943                                 removeFromQueue: function(anim) {
7944                                         for (var i = 0; i < queueLen; i++) {
7945                                                 if (queue[i] == anim) {
7946                                                         queue.splice(i, 1);
7947                                                         anim._timeAnchor = null;
7948                                                         anim._playing = false;
7949                                                         //stop the queue if there's nothing in it anymore
7950                                                         if (--queueLen == 0) {
7951                                                                 this.stopInterval();
7952                                                         }
7953                                                         return;
7954                                                 }
7955                                         }
7956                                 },
7957                                 /**
7958                                 @name glow.anim-manager.startInterval
7959                                 @private
7960                                 @function
7961                                 @description Start processing the queue every interval.
7962                                 */
7963                                 startInterval: function() {
7964                                         interval = window.setInterval(this.processQueue, intervalTime);
7965                                 },
7966                                 /**
7967                                 @name glow.anim-manager.stopInterval
7968                                 @private
7969                                 @function
7970                                 @description Stop processing the queue.
7971                                 */
7972                                 stopInterval: function() {
7973                                         window.clearInterval(interval);
7974                                         interval = null;
7975                                 },
7976                                 /**
7977                                 @name glow.anim-manager.processQueue
7978                                 @private
7979                                 @function
7980                                 @description Animate each animation in the queue.
7981                                 */
7982                                 processQueue: function() {
7983                                         var anim, i, now = new Date().valueOf();
7984                                         for (i = 0; i < queueLen; i++) {
7985                                                 anim = queue[i];
7986                                                 if (anim.position == anim.duration) {
7987                                                         manager.removeFromQueue(anim);
7988                                                         //need to decrement the index because we've just removed an item from the queue
7989                                                         i--;
7990                                                         events.fire(anim, "complete");
7991                                                         if (anim._opts.destroyOnComplete) {
7992                                                                 anim.destroy();
7993                                                         }
7994                                                         continue;
7995                                                 }
7996                                                 if (anim.useSeconds) {
7997                                                         anim.position = (now - anim._timeAnchor) / 1000;
7998                                                         if (anim.position > anim.duration) {
7999                                                                 anim.position = anim.duration;
8000                                                         }
8001                                                 } else {
8002                                                         anim.position++;
8003                                                 }
8004                                                 anim.value = anim.tween(anim.position / anim.duration);
8005                                                 events.fire(anim, "frame");
8006                                         }
8007                                 }
8008                         };
8009                 })();
8010
8011                 /**
8012                 @name glow.anim.convertCssUnit
8013                 @private
8014                 @function
8015                 @param {nodelist} element
8016                 @param {string|number} fromValue Assumed pixels.
8017                 @param {string} toUnit (em|%|pt...)
8018                 @param {string} axis (x|y)
8019                 @description Converts a css unit.
8020
8021                 We need to know the axis for calculating relative values, since they're
8022                 relative to the width / height of the parent element depending
8023                 on the situation.
8024
8025                 */
8026                 function convertCssUnit(element, fromValue, toUnit, axis) {
8027                         var elmStyle = testElement[0].style,
8028                                 axisProp = (axis == "x") ? "width" : "height",
8029                                 startPixelValue,
8030                                 toUnitPixelValue;
8031                         //reset stuff that may affect the width / height
8032                         elmStyle.margin = elmStyle.padding = elmStyle.border = "0";
8033                         startPixelValue = testElement.css(axisProp, fromValue).insertAfter(element)[axisProp]();
8034                         //using 10 of the unit then dividing by 10 to increase accuracy
8035                         toUnitPixelValue = testElement.css(axisProp, 10 + toUnit)[axisProp]() / 10;
8036                         testElement.remove();
8037                         return startPixelValue / toUnitPixelValue;
8038                 }
8039
8040                 /**
8041                 @name glow.anim.keepWithinRange
8042                 @private
8043                 @function
8044                 @param num
8045                 @param [start]
8046                 @param [end]
8047                 @description
8048                 Takes a number then an (optional) lower range and an (optional) upper range. If the number
8049                 is outside the range the nearest range boundary is returned, else the number is returned.
8050                 */
8051                 function keepWithinRange(num, start, end) {
8052                         if (start !== undefined && num < start) {
8053                                 return start;
8054                         }
8055                         if (end !== undefined && num > end) {
8056                                 return end;
8057                         }
8058                         return num;
8059                 }
8060
8061                 /**
8062                 @name glow.anim.buildAnimFunction
8063                 @private
8064                 @function
8065                 @param element
8066                 @param spec
8067                 @description Builds a function for an animation.
8068                 */
8069                 function buildAnimFunction(element, spec) {
8070                         var cssProp,
8071                                 r = ["a=(function(){"],
8072                                 rLen = 1,
8073                                 fromUnit,
8074                                 unitDefault = [0,"px"],
8075                                 to,
8076                                 from,
8077                                 unit,
8078                                 a;
8079
8080                         for (cssProp in spec) {
8081                                 r[rLen++] = 'element.css("' + cssProp + '", ';
8082                                 //fill in the blanks
8083                                 if (typeof spec[cssProp] != "object") {
8084                                         to = spec[cssProp];
8085                                 } else {
8086                                         to = spec[cssProp].to;
8087                                 }
8088                                 if ((from = spec[cssProp].from) === undefined) {
8089                                         if (cssProp == "font-size" || cssProp == "background-position") {
8090                                                 throw new Error("From value must be set for " + cssProp);
8091                                         }
8092                                         from = element.css(cssProp);
8093                                 }
8094                                 //TODO help some multi value things?
8095                                 if (hasUnits.test(cssProp)) {
8096                                         //normalise the units for unit-ed values
8097                                         unit = (getUnit.exec(to) || unitDefault)[1];
8098                                         fromUnit = (getUnit.exec(from) || unitDefault)[1];
8099                                         //make them numbers, we have the units seperate
8100                                         from = parseFloat(from) || 0;
8101                                         to = parseFloat(to) || 0;
8102                                         //if the units don't match, we need to have a play
8103                                         if (from && unit != fromUnit) {
8104                                                 if (cssProp == "font-size") {
8105                                                         throw new Error("Units must be the same for font-size");
8106                                                 }
8107                                                 from = convertCssUnit(element, from + fromUnit, unit, usesYAxis.test(cssProp) ? "y" : "x");
8108                                         }
8109                                         if (noNegatives.test(cssProp)) {
8110                                                 r[rLen++] = 'keepWithinRange((' + (to - from) + ' * this.value) + ' + from + ', 0) + "' + unit + '"';
8111                                         } else {
8112                                                 r[rLen++] = '(' + (to - from) + ' * this.value) + ' + from + ' + "' + unit + '"';
8113                                         }
8114                                 } else if (! (isNaN(from) || isNaN(to))) { //both pure numbers
8115                                         from = Number(from);
8116                                         to = Number(to);
8117                                         r[rLen++] = '(' + (to - from) + ' * this.value) + ' + from;
8118                                 } else if (cssProp.indexOf("color") != -1) {
8119                                         to = dom.parseCssColor(to);
8120                                         if (! glow.lang.hasOwnProperty(from, "r")) {
8121                                                 from = dom.parseCssColor(from);
8122                                         }
8123                                         r[rLen++] = '"rgb(" + keepWithinRange(Math.round(' + (to.r - from.r) + ' * this.value + ' + from.r +
8124                                                 '), 0, 255) + "," + keepWithinRange(Math.round(' + (to.g - from.g) + ' * this.value + ' + from.g +
8125                                                 '), 0, 255) + "," + keepWithinRange(Math.round(' + (to.b - from.b) + ' * this.value + ' + from.b +
8126                                                 '), 0, 255) + ")"';
8127                                 } else if (cssProp == "background-position") {
8128                                         var vals = {},
8129                                                 fromTo = ["from", "to"],
8130                                                 unit = (getUnit.exec(from) || unitDefault)[1];
8131                                         vals.fromOrig = from.toString().split(/\s/);
8132                                         vals.toOrig = to.toString().split(/\s/);
8133
8134                                         if (vals.fromOrig[1] === undefined) {
8135                                                 vals.fromOrig[1] = "50%";
8136                                         }
8137                                         if (vals.toOrig[1] === undefined) {
8138                                                 vals.toOrig[1] = "50%";
8139                                         }
8140
8141                                         for (var i = 0; i < 2; i++) {
8142                                                 vals[fromTo[i] + "X"] = parseFloat(vals[fromTo[i] + "Orig"][0]);
8143                                                 vals[fromTo[i] + "Y"] = parseFloat(vals[fromTo[i] + "Orig"][1]);
8144                                                 vals[fromTo[i] + "XUnit"] = (getUnit.exec(vals[fromTo[i] + "Orig"][0]) || unitDefault)[1];
8145                                                 vals[fromTo[i] + "YUnit"] = (getUnit.exec(vals[fromTo[i] + "Orig"][1]) || unitDefault)[1];
8146                                         }
8147
8148                                         if ((vals.fromXUnit !== vals.toXUnit) || (vals.fromYUnit !== vals.toYUnit)) {
8149                                                 throw new Error("Mismatched axis units cannot be used for " + cssProp);
8150                                         }
8151
8152                                         r[rLen++] = '(' + (vals.toX - vals.fromX) + ' * this.value + ' + vals.fromX + ') + "' + vals.fromXUnit + ' " + (' +
8153                                                                 (vals.toY - vals.fromY) + ' * this.value + ' + vals.fromY + ') + "' + vals.fromYUnit + '"';
8154                                 }
8155                                 r[rLen++] = ');';
8156                         }
8157                         r[rLen++] = "})";
8158                         return eval(r.join(""));
8159                 }
8160
8161                 //public
8162                 var r = {}; //return object
8163
8164                 /**
8165                 @name glow.anim.css
8166                 @function
8167                 @description Animates CSS properties of an element.
8168                 @param {String | glow.dom.NodeList | Element} element Element to animate.
8169
8170                         This can be a CSS selector (first match will be used),
8171                         {@link glow.dom.NodeList} (first node will be used), or a DOM element.
8172
8173                 @param {Number} duration Animation duration, in seconds by default.
8174
8175                 @param {Object} spec An object describing the properties to animate.
8176
8177                         This object should consist of property names corresponding to the
8178                         CSS properties you wish to animate, and values which are objects
8179                         with 'from' and 'to' properties with the values to animate between
8180                         or a number/string representing the value to animate to.
8181
8182                         If the 'from' property is absent, the elements current CSS value
8183                         will be used instead.
8184
8185                         See the spec example below for more information.
8186
8187                 @param {Object} opts Optional options object.
8188
8189                 @param {Boolean} [opts.useSeconds=true] Specifies whether duration should be in seconds rather than frames.
8190
8191                 @param {Function} [opts.tween=linear tween] The way the value moves through time. See {@link glow.tweens}.
8192                 
8193                 @param {Boolean} [opts.destroyOnComplete=false] Destroy the animation once it completes?
8194                         This will free any DOM references the animation may have created. Once
8195                         the animation completes, you won't be able to start it again.
8196                         
8197                 @param {Function} [opts.onStart] Shortcut for adding a "start" event listener
8198                 @param {Function} [opts.onFrame] Shortcut for adding a "frame" event listener
8199                 @param {Function} [opts.onStop] Shortcut for adding a "stop" event listener
8200                 @param {Function} [opts.onComplete] Shortcut for adding a "complete" event listener
8201                 @param {Function} [opts.onResume] Shortcut for adding a "resume" event listener
8202                 
8203                 @example
8204                 // an example of an spec object
8205                 {
8206                         "height": {from: "10px", to: "100px"},
8207                         "width": "100px",
8208                         "font-size": {from: "0.5em", to: "1.3em"}
8209                 }
8210
8211                 @example
8212                 // animate an elements height and opacity to 0 from current values over 1 second
8213                 glow.anim.css("#myElement", 1, {
8214                         "height" : 0,
8215                         "opacity" : 0
8216                 }).start();
8217
8218                 @returns {glow.anim.Animation}
8219                 */
8220                 r.css = function(element, duration, spec, opts) {
8221
8222                         element = get(element);
8223
8224                         var anim = new r.Animation(duration, opts);
8225
8226                         // Fix for trac 156 - glow.anim.css should fail better if the element doesn't exist
8227                         if (element[0]) {
8228                                 events.addListener(anim, "frame", buildAnimFunction(element, spec));
8229                         }
8230                         return anim;
8231                 };
8232                 
8233                 /**
8234                 @name glow.anim-slideElement
8235                 @private
8236                 @function
8237                 @param {String | glow.dom.NodeList} element Element to animate. CSS Selector can be used.
8238                 @param {Number} duration Animation duration in seconds.
8239                 @param {Function} [opts.tween=easeBoth tween] The way the value moves through time. See {@link glow.tweens}.
8240                 @param {Function} [opts.onStart] The function to be called when the first element in the NodeList starts the animation.  
8241                 @param {Function} [opts.onComplete] The function to be called when the first element in the NodeList completes the animation.
8242                 @description Builds a function for an animation.
8243                 */
8244                 slideElement = function slideElement(element, duration, action, opts) {
8245                         duration = duration || 0.5;
8246                         // normalise 'element' to NodeList
8247                         element = $(element);
8248                         
8249                         opts = glow.lang.apply({
8250                                 tween: glow.tweens.easeBoth(),
8251                                 onStart: function(){},
8252                                 onComplete: function(){}
8253                         }, opts);
8254                         
8255                         var i = 0,
8256                                 thatlength = element.length,
8257                                 completeHeight,
8258                                 fromHeight,
8259                                 channels = [],
8260                                 timeline;
8261                         
8262                         for(; i < thatlength; i++) {
8263                                 if (action == "up" || (action == "toggle" && element.slice(i, i+1).height() > 0)) {
8264                                         element[i].style.overflow = 'hidden';
8265                                         // give the element layout in IE
8266                                         if (glow.env.ie < 8) {
8267                                                 element[i].style.zoom = 1;
8268                                         }
8269                                         completeHeight = 0;
8270                                         fromHeight = element.slice(i, i+1).height();
8271                                 } else if (action == "down" || (action == "toggle" && element.slice(i, i+1).height() == 0)) {
8272                                         fromHeight = element.slice(i, i+1).height();
8273                                         element[i].style.height = "auto";
8274                                         completeHeight = element.slice(i, i+1).height();
8275                                         element[i].style.height = fromHeight + "px";
8276                                 }
8277
8278                                 channels[i] = [
8279                                         glow.anim.css(element[i], duration, {
8280                                                 'height': {from: fromHeight, to: completeHeight}
8281                                         }, { tween: opts.tween })
8282                                 ];
8283
8284                         }
8285                         
8286                         timeline = new glow.anim.Timeline(channels, {
8287                                 destroyOnComplete: true
8288                         });
8289                         
8290                         events.addListener(timeline, "complete", function() {
8291                                 // return heights to "auto" for slide down
8292                                 element.each(function() {
8293                                         if (this.style.height != "0px") {
8294                                                 this.style.height = "auto";
8295                                         }
8296                                 })
8297                         });
8298                         
8299                         events.addListener(timeline, "start", opts.onStart);
8300                         events.addListener(timeline, "complete", opts.onComplete);
8301                         
8302                         // return & start our new timeline
8303                         return timeline.start();
8304                 };
8305
8306                 /**
8307                 @name glow.anim.slideDown
8308                 @function
8309                 @description Slide a NodeList down from a height of 0 
8310                 
8311                 @param {String | glow.dom.NodeList} element Element to animate. CSS Selector can be used.
8312                 
8313                 @param {Number} duration Animation duration in seconds.
8314                 
8315                 @param {Function} opts Object
8316                  
8317                 @param {Function} [opts.tween=easeBoth tween] The way the value moves through time. See {@link glow.tweens}.
8318                   
8319                 @param {Function} [opts.onStart] The function to be called when the first element in the NodeList starts the animation.  
8320                  
8321                 @param {Function} [opts.onComplete] The function to be called when the first element in the NodeList completes the animation.
8322                 
8323                 @returns {glow.anim.Timeline}
8324                 
8325                         A started timeline
8326                 
8327                 @example
8328                 
8329                         glow.anim.slideDown("#menu", 1);
8330                                  
8331                 **/     
8332                 r.slideDown = function(element, duration, opts) {
8333                         return slideElement(element, duration, 'down', opts);
8334                 };
8335
8336                 /**
8337                 @name glow.anim.slideUp
8338                 @function
8339                 @description Slide a NodeList up to a height of 0 
8340                 
8341                 @param {String | glow.dom.NodeList} element Element to animate. CSS Selector can be used.
8342                 
8343                 @param {Number} duration Animation duration in seconds.
8344                 
8345                 @param {Function} opts Object
8346                  
8347                 @param {Function} [opts.tween=easeBoth tween] The way the value moves through time. See {@link glow.tweens}.
8348                   
8349                 @param {Function} [opts.onStart] The function to be called when the first element in the NodeList starts the animation.  
8350                  
8351                 @param {Function} [opts.onComplete] The function to be called when the first element in the NodeList completes the animation.
8352                 
8353                 @returns {glow.anim.Timeline}
8354                 
8355                         A started timeline
8356                 
8357                 @example
8358                                 
8359                         glow.anim.slideUp("#menu", 1);
8360                                  
8361                 **/             
8362                 r.slideUp = function(element, duration, opts) {
8363                         return slideElement(element, duration, 'up', opts);
8364                 };
8365                 
8366                 /**
8367                 @name glow.anim.slideToggle
8368                 @function
8369                 @description Toggle a NodeList Up or Down depending on it's present state. 
8370                 
8371                 @param {String | glow.dom.NodeList} element Element to animate. CSS Selector can be used.
8372                 
8373                 @param {Number} duration Animation duration in seconds.
8374                 
8375                 @param {Function} opts Object
8376                  
8377                 @param {Function} [opts.tween=easeBoth tween] The way the value moves through time. See {@link glow.tweens}.
8378                   
8379                 @param {Function} [opts.onStart] The function to be called when the first element in the NodeList starts the animation.  
8380                  
8381                 @param {Function} [opts.onComplete] The function to be called when the first element in the NodeList completes the animation.
8382                 
8383                 @returns {glow.anim.Timeline}
8384                 
8385                         A started timeline
8386                 
8387                 @example
8388                                 
8389                         glow.anim.slideToggle("#menu", 1);
8390                                  
8391                 **/                     
8392                 r.slideToggle = function(element, duration, opts) {
8393                         return slideElement(element, duration, 'toggle', opts);
8394                 };
8395
8396
8397                 /**
8398                 @name glow.anim.fadeOut
8399                 @function
8400                 @description Fade out a set of elements 
8401                 
8402                 @param {String | glow.dom.NodeList} element Element to animate. CSS Selector can be used.
8403                 
8404                 @param {Number} duration Animation duration in seconds.
8405                 
8406                 @param {Function} opts Object
8407                  
8408                 @param {Function} [opts.tween=easeBoth tween] The way the value moves through time. See {@link glow.tweens}.
8409                   
8410                 @param {Function} [opts.onStart] The function to be called when the first element in the NodeList starts the animation.  
8411                  
8412                 @param {Function} [opts.onComplete] The function to be called when the first element in the NodeList completes the animation.
8413                 
8414                 @returns {glow.anim.Timeline}
8415                 
8416                         A started timeline
8417                 
8418                 @example
8419                                 
8420                         glow.anim.fadeOut("#menu", 1);
8421                                  
8422                 **/
8423                 r.fadeOut = function(element, duration, opts) {
8424                         return r.fadeTo(element, 0, duration, opts)
8425         };
8426                 
8427                 /**
8428                 @name glow.anim.fadeIn
8429                 @function
8430                 @description Fade in a set of elements 
8431                  
8432                 @param {String | glow.dom.NodeList} element Element to animate. CSS Selector can be used.
8433                 
8434                 @param {Number} duration Animation duration in seconds.
8435                   
8436                 @param {Function} opts Object
8437                  
8438                 @param {Function} [opts.tween=easeBoth tween] The way the value moves through time. See {@link glow.tweens}.
8439                   
8440                 @param {Function} [opts.onStart] The function to be called when the first element in the NodeList starts the animation.  
8441                  
8442                 @param {Function} [opts.onComplete] The function to be called when the first element in the NodeList completes the animation.
8443                 
8444                 @returns {glow.anim.Timeline}
8445                 
8446                         A started timeline
8447                 
8448                 @example
8449                                 
8450                         glow.anim.fadeIn("#menu", 1);
8451                                  
8452                 **/
8453                 r.fadeIn = function(element, duration, opts){
8454                         r.fadeTo(element, 1, duration, opts);
8455         };
8456                 
8457                 /**
8458                 @name glow.anim.fadeTo
8459                 @function
8460                 @description Fade a set of elements to a given opacity
8461                  
8462                 @param {String | glow.dom.NodeList} element Element to animate. CSS Selector can be used.
8463                  
8464                 @param {Number} opacity fade to opacity level between 0 & 1. 
8465                  
8466                 @param {Number} duration Animation duration in seconds.
8467                   
8468                 @param {Function} opts Object
8469                  
8470                 @param {Function} [opts.tween=easeBoth tween] The way the value moves through time. See {@link glow.tweens}.
8471                   
8472                 @param {Function} [opts.onStart] The function to be called when the first element in the NodeList starts the animation.  
8473                  
8474                 @param {Function} [opts.onComplete] The function to be called when the first element in the NodeList completes the animation.
8475                 
8476                 @returns {glow.anim.Timeline}
8477                 
8478                         A started timeline
8479                 
8480                 @example
8481                                 
8482                         glow.anim.fadeTo("#menu", 0.5, 1);
8483                                  
8484                 **/
8485                 r.fadeTo = function(element, opacity, duration, opts){
8486                         duration = duration || 0.5;
8487                         // normalise 'element' to NodeList
8488                         element = $(element);
8489                         
8490                         opts = glow.lang.apply({
8491                                 tween: glow.tweens.easeBoth(),
8492                                 onStart: function(){},
8493                                 onComplete: function(){}
8494                         }, opts);
8495                         
8496                         var i = 0,
8497                                 thatlength = element.length,
8498                                 channels = [],
8499                                 timeline;
8500                         
8501                         for(; i < thatlength; i++) {
8502                                 channels[i] = [
8503                                         glow.anim.css(element[i], duration, {
8504                                                 'opacity': opacity
8505                                         }, { tween: opts.tween })
8506                                 ];
8507                         }
8508                         
8509                         timeline = new glow.anim.Timeline(channels, {
8510                                 destroyOnComplete: true
8511                         });
8512                         
8513                         events.addListener(timeline, "start", opts.onStart);
8514                         events.addListener(timeline, "complete", opts.onComplete);
8515                         
8516                         // return & start our new timeline
8517                         return timeline.start();
8518         };
8519         
8520
8521                 /**
8522                 @name glow.anim.highlight
8523                 @function
8524                 @description    Highlight an element by fading the background colour
8525                  
8526                 @param {String | glow.dom.NodeList} element Element to animate. CSS Selector can be used.
8527                 
8528                 @param {String} highlightColour highlight colour in hex, "rgb(r, g, b)" or css colour name. 
8529                  
8530                 @param {Number} duration Animation duration in seconds.
8531                   
8532                 @param {Function} opts Object
8533                  
8534                 @param {Function} [opts.completeColour] The background colour of the element once the highlight is complete.
8535                 
8536                                                   If none supplied Glow assumes the element's existing background color (e.g. #336699),
8537                                                   if the element has no background color specified (e.g. Transparent)
8538                                                   the highlight will transition to white.
8539                 
8540                 @param {Function} [opts.tween=easeBoth tween] The way the value moves through time. See {@link glow.tweens}.
8541                   
8542                 @param {Function} [opts.onStart] The function to be called when the first element in the NodeList starts the animation.  
8543                  
8544                 @param {Function} [opts.onComplete] The function to be called when the first element in the NodeList completes the animation.
8545                 
8546                 @returns {glow.anim.Timeline}
8547                 
8548                         A started timeline
8549                 
8550                 @example
8551                                 
8552                         glow.anim.highlight("#textInput", "#ff0", 1);
8553                 **/
8554                 r.highlight = function(element, highlightColour, duration, opts){
8555                         // normalise element
8556                         element = $(element);
8557                         
8558                         duration = duration || 1;
8559                         highlightColour = highlightColour || '#ffff99';
8560                         
8561                         opts = glow.lang.apply({
8562                                 tween: glow.tweens.easeBoth(),
8563                                 onStart: function(){},
8564                                 onComplete: function(){}
8565                         }, opts);
8566                         
8567                         var i = 0, 
8568                                 transArray = [],
8569                                 elmsLength = element.length,
8570                                 completeColour,
8571                                 channels = [],
8572                                 timeline;
8573                         
8574                         for(; i < elmsLength; i++) {
8575                                 
8576                                 completeColour = opts.completeColour || element.slice(i, i+1).css("background-color");
8577
8578                                 if (completeColour == "transparent" || completeColour == "") { 
8579                                         completeColour = "#fff";
8580                                 }
8581                                 channels[i] = [
8582                                         r.css(element[i], duration, {
8583                                                 "background-color" : {from:highlightColour, to:completeColour}
8584                                         }, {tween: opts.tween})
8585                                 ];
8586                         }
8587                         
8588                         timeline = new glow.anim.Timeline(channels, {
8589                                 destroyOnComplete: true
8590                         });
8591                         
8592                         events.addListener(timeline, "start", opts.onStart);
8593                         events.addListener(timeline, "complete", opts.onComplete);
8594                         return timeline.start();
8595         };
8596
8597                 /**
8598                 @name glow.anim.Animation
8599                 @class
8600                 @description Controls modifying values over time.
8601
8602                         You can create an animtion instance using the constructor, or use
8603                         one of the helper methods in {@link glow.anim}.
8604
8605                         Once you have created your animation instance, you can use
8606                         events such as "frame" to change values over time.
8607
8608                 @param {Number} duration Length of the animation in seconds / frames.
8609
8610                         Animations which are given a duration in seconds may drop frames to
8611                         finish in the given time.
8612
8613                 @param {Object} opts Object of options.
8614
8615                 @param {Boolean} [opts.useSeconds=true] Specifies whether duration should be in seconds rather than frames.
8616
8617                 @param {Function} [opts.tween=linear tween] The way the value moves through time.
8618
8619                         See {@link glow.tweens}.
8620                         
8621                 @param {Boolean} [opts.destroyOnComplete=false] Destroy the animation once it completes?
8622                         This will free any DOM references the animation may have created. Once
8623                         the animation completes, you won't be able to start it again.
8624                         
8625                 @param {Function} [opts.onStart] Shortcut for adding a "start" event listener
8626                 @param {Function} [opts.onFrame] Shortcut for adding a "frame" event listener
8627                 @param {Function} [opts.onStop] Shortcut for adding a "stop" event listener
8628                 @param {Function} [opts.onComplete] Shortcut for adding a "complete" event listener
8629                 @param {Function} [opts.onResume] Shortcut for adding a "resume" event listener
8630
8631                 @example
8632                         var myAnim = new glow.anim.Animation(5, {
8633                                 tween:glow.tweens.easeBoth()
8634                         });
8635
8636                 */
8637
8638                 /**
8639                 @name glow.anim.Animation#event:start
8640                 @event
8641                 @description Fired when the animation is started from the beginning.
8642                 @param {glow.events.Event} event Event Object
8643                 @example
8644                         var myAnim = new glow.anim.Animation(5, {
8645                                 tween:glow.tweens.easeBoth()
8646                         });
8647                         glow.events.addListener(myAnim, "start", function() {
8648                                 alert("Started animation which lasts " + this.duration + " seconds");
8649                         });
8650                         myAnim.start();
8651                 */
8652
8653                 /**
8654                 @name glow.anim.Animation#event:frame
8655                 @event
8656                 @description Fired in each frame of the animation.
8657
8658                         This is where you'll specify what your animation does.
8659                 
8660                 @param {glow.events.Event} event Event Object
8661                 @example
8662                         var myAnim = new glow.anim.Animation(5, {
8663                                 tween:glow.tweens.easeBoth()
8664                         });
8665                         
8666                         var myDiv = glow.dom.get("#myDiv"),
8667                             divStartHeight = myDiv.height(),
8668                             divEndHeight = 500,
8669                             divHeightChange = divEndHeight - divStartHeight;
8670
8671                         glow.events.addListener(myAnim, "frame", function() {
8672                                 myDiv.height(divStartHeight + (divHeightChange * this.value));
8673                         });
8674                         myAnim.start();
8675                 */
8676
8677                 /**
8678                 @name glow.anim.Animation#event:stop
8679                 @event
8680                 @description Fired when the animation is stopped before its end.
8681
8682                         If your listener prevents the default action (for instance,
8683                         by returning false) the animtion will not be stopped.
8684                 
8685                 @param {glow.events.Event} event Event Object
8686                 */
8687
8688                 /**
8689                 @name glow.anim.Animation#event:complete
8690                 @event
8691                 @description Fired when the animation ends.
8692                 @param {glow.events.Event} event Event Object
8693                 */
8694
8695                 /**
8696                 @name glow.anim.Animation#event:resume
8697                 @event
8698                 @description Fired when the animation resumes after being stopped.
8699
8700                         If your listener prevents the default action (for instance, by
8701                         returning false) the animation will not be resumed.
8702
8703                 @param {glow.events.Event} event Event Object
8704                 */
8705                 // constructor items that relate to events
8706                 var animationEventConstructorNames = ["onStart", "onStop", "onComplete", "onResume", "onFrame"];
8707                 
8708                 r.Animation = function(duration, opts) {
8709                         this._opts = opts = glow.lang.apply({
8710                                 useSeconds: true,
8711                                 tween: glow.tweens.linear(),
8712                                 destroyOnComplete: false,
8713                                 onStart: null,
8714                                 onStop: null,
8715                                 onComplete: null,
8716                                 onResume: null,
8717                                 onFrame: null
8718                         }, opts);
8719
8720                         /**
8721                         @name glow.anim.Animation#_playing
8722                         @type Boolean
8723                         @private
8724                         @default false
8725                         @description Indicates whether the animation is playing.
8726                         */
8727                         this._playing = false;
8728
8729                         /**
8730                         @name glow.anim.Animation#_timeAnchor
8731                         @type Number
8732                         @private
8733                         @default null
8734                         @description A timestamp used to keep the animation in the right position.
8735                         */
8736                         this._timeAnchor = null;
8737
8738                         /**
8739                         @name glow.anim.Animation#duration
8740                         @type Number
8741                         @description Length of the animation in seconds / frames.
8742                         */
8743                         this.duration = duration;
8744
8745                         /**
8746                         @name glow.anim.Animation#useSeconds
8747                         @type Boolean
8748                         @description Indicates whether duration is in seconds rather than frames.
8749                         */
8750                         this.useSeconds = opts.useSeconds;
8751
8752                         /**
8753                         @name glow.anim.Animation#tween
8754                         @type Function
8755                         @description The tween used by the animation.
8756                         */
8757                         this.tween = opts.tween;
8758
8759                         /**
8760                         @name glow.anim.Animation#position
8761                         @type Number
8762                         @default 0
8763                         @description Seconds since starting, or current frame.
8764                         */
8765                         this.position = 0;
8766
8767                         /**
8768                         @name glow.anim.Animation#value
8769                         @type Number
8770                         @default 0
8771                         @description Current tweened value of the animtion, usually between 0 & 1.
8772                                 The value may become greater than 1 or less than 0 depending
8773                                 on the tween used.
8774                                 
8775                                 {@link glow.tweens.elasticOut} for instance will result
8776                                 in values higher than 1, but will still end at 1.
8777                         */
8778                         this.value = 0;
8779                         
8780                         // add events from constructor opts
8781                         addEventsFromOpts(this, opts, animationEventConstructorNames);
8782                 };
8783                 r.Animation.prototype = {
8784
8785                         /**
8786                         @name glow.anim.Animation#start
8787                         @function
8788                         @description Starts playing the animation from the beginning.
8789                         @example
8790                                 var myAnim = new glow.anim.Animation(5, {
8791                                         tween:glow.tweens.easeBoth()
8792                                 });
8793                                 //attach events here
8794                                 myAnim.start();
8795                         @returns {glow.anim.Animation}
8796                         */
8797                         start: function() {
8798                                 if (this._playing) {
8799                                         this.stop();
8800                                 }
8801                                 var e = events.fire(this, "start");
8802                                 if (e.defaultPrevented()) { return this; }
8803                                 this._timeAnchor = null;
8804                                 this.position = 0;
8805                                 manager.addToQueue(this);
8806
8807                                 return this;
8808                         },
8809
8810                         /**
8811                         @name glow.anim.Animation#stop
8812                         @function
8813                         @description Stops the animation playing.
8814                         @returns {glow.anim.Animation}
8815                         */
8816                         stop: function() {
8817                                 if (this._playing) {
8818                                         var e = events.fire(this, "stop");
8819                                         if (e.defaultPrevented()) { return this; }
8820                                         manager.removeFromQueue(this);
8821                                 }
8822                                 return this;
8823                         },
8824                         
8825                         /**
8826                         @name glow.anim.Animation#destroy
8827                         @function
8828                         @description Destroys the animation & detatches references to DOM nodes
8829                                 Call this on animations you no longer need to free memory.
8830                         @returns {glow.anim.Animation}
8831                         */
8832                         destroy: function() {
8833                                 // stop the animation in case it's still playing
8834                                 this.stop();
8835                                 events.removeAllListeners(this);
8836                                 return this;
8837                         },
8838
8839                         /**
8840                         @name glow.anim.Animation#resume
8841                         @function
8842                         @description Resumes the animation from where it was stopped.
8843                         @returns {glow.anim.Animation}
8844                         */
8845                         resume: function() {
8846                                 if (! this._playing) {
8847                                         var e = events.fire(this, "resume");
8848                                         if (e.defaultPrevented()) { return this; }
8849                                         //set the start time to cater for the pause
8850                                         this._timeAnchor = new Date().valueOf() - (this.position * 1000);
8851                                         manager.addToQueue(this);
8852                                 }
8853                                 return this;
8854                         },
8855
8856                         /**
8857                         @name glow.anim.Animation#isPlaying
8858                         @function
8859                         @description Returns true if the animation is playing.
8860                         @returns {Boolean}
8861                         */
8862                         isPlaying: function() {
8863                                 return this._playing;
8864                         },
8865                         /**
8866                         @name glow.anim.Animation#goTo
8867                         @function
8868                         @description Goes to a specific point in the animation.
8869                         @param {Number} pos Position in the animation to go to.
8870
8871                                 This should be in the same units as the duration of your
8872                                 animation (seconds or frames).
8873
8874                         @example
8875                                 var myAnim = new glow.anim.Animation(5, {
8876                                         tween:glow.tweens.easeBoth()
8877                                 });
8878                                 //attach events here
8879                                 //start the animation from half way through
8880                                 myAnim.goTo(2.5).resume();
8881                         @returns this
8882                         */
8883                         goTo: function(pos) {
8884                                 this._timeAnchor = new Date().valueOf() - ((this.position = pos) * 1000);
8885                                 this.value = this.tween(this.duration && this.position / this.duration);
8886                                 events.fire(this, "frame");
8887                                 return this;
8888                         }
8889                 };
8890
8891                 /**
8892                 @name glow.anim.Timeline
8893                 @class
8894                 @description Synchronises and chains animations.
8895                 @param {Array | Array[]} channels An array of channels or a single channel.
8896
8897                         A channel is defined as an array containing numbers, animations and
8898                         functions.
8899
8900                         Numbers indicate a number of seconds to wait before proceeding to
8901                         the next item. Animations will be played, when the animation is
8902                         complete the next item is processed. Functions will be called, then
8903                         the next item is processed.
8904
8905                 @param {Object} opts An object of options.
8906                 @param {Boolean} [opts.loop=false] Specifies whether the timeline loops.
8907
8908                         The "complete" event does not fire for looping animations.
8909                         
8910                 @param {Boolean} [opts.destroyOnComplete=false] Destroy the animation once it completes?
8911                         This will free any DOM references the animation may have created. Once
8912                         the animation completes, you won't be able to start it again.
8913                         
8914                 @param {Function} [opts.onStart] Shortcut for adding a "start" event listener
8915                 @param {Function} [opts.onStop] Shortcut for adding a "stop" event listener
8916                 @param {Function} [opts.onComplete] Shortcut for adding a "complete" event listener
8917                 @param {Function} [opts.onResume] Shortcut for adding a "resume" event listener
8918
8919                 @example
8920                         // in the simplest form, a timeline can be used to
8921                         // string multiple animations together:
8922                         
8923
8924                         // make our animations
8925                         var moveUp = glow.anim.css(myDiv, 2, {
8926                                 "top": {to:"0"}
8927                         });
8928                         var moveDown = glow.anim.css(myDiv, 1, {
8929                                 "top": {to:"100px"}
8930                         });
8931                         // string them together
8932                         new glow.anim.Timeline([moveUp, moveDown]).start();
8933
8934                 @example
8935                         // if you wanted a one second gap between the animations, the last line would be:
8936                         new glow.anim.Timeline([moveUp, 1, moveDown]).start();
8937
8938                 @example
8939                         // you can run animations simutainiously with multiple channels.
8940                         new glow.anim.Timeline([
8941                                 [moveDivUp, 1, moveDivDown],
8942                                 [moveListDown, 1, moveListUp]
8943                         ]).start();
8944                 @see <a href="../furtherinfo/animtimeline/">Creating a mexican wave with an animation timeline</a>
8945                 */
8946                 /**
8947                 @name glow.anim.Timeline#event:start
8948                 @event
8949                 @description Fired when the timeline is started from the beginning.
8950
8951                         This event will also trigger during each loop of a looping animation.
8952                         If your listener prevents the default action (for instance, by
8953                         returning false) the timeline will not start.
8954                 
8955                 @param {glow.events.Event} event Event Object
8956                 @example
8957                         var myTimeline = new glow.anim.Timeline([anim1, anim2]);
8958                         glow.events.addListener(myTimeline, "start", function() {
8959                                 alert("Started timeline");
8960                         });
8961                         myTimeline.start();
8962                 */
8963                 /**
8964                 @name glow.anim.Timeline#event:stop
8965                 @event
8966                 @description Fired when the timeline is stopped before its end.
8967
8968                         If your listener prevents the default action (for instance, by
8969                         returning false) the timeline will not stop.
8970                 
8971                 @param {glow.events.Event} event Event Object
8972                 */
8973                 /**
8974                 @name glow.anim.Timeline#event:complete
8975                 @event
8976                 @description Fired when the timeline ends.
8977
8978                         This event does not fire on looping timelines.
8979                 
8980                 @param {glow.events.Event} event Event Object
8981                 */
8982                 /**
8983                 @name glow.anim.Timeline#event:resume
8984                 @event
8985                 @description Fired when the timeline resumes after being stopped.
8986
8987                         If your listener prevents the default action (for instance, by
8988                         returning false) the timeline will not resume.
8989                 
8990                 @param {glow.events.Event} event Event Object
8991                 */
8992                 var timelineEventConstructorNames = ["onStart", "onStop", "onComplete", "onResume"];
8993                 r.Timeline = function(channels, opts) {
8994                         this._opts = opts = glow.lang.apply({
8995                                 loop: false,
8996                                 destroyOnComplete: false,
8997                                 onStart: null,
8998                                 onStop: null,
8999                                 onComplete: null,
9000                                 onResume: null
9001                         }, opts);
9002                         /*
9003                         PrivateProperty: _channels
9004                                 Array of channels
9005                         */
9006                         //normalise channels so it's always an array of array(s)
9007                         this._channels = (channels[0] && channels[0].push) ? channels : [channels];
9008                         /*
9009                         PrivateProperty: _channelPos
9010                                 index of each currently playing animation
9011                         */
9012                         this._channelPos = [];
9013                         /*
9014                         PrivateProperty: _playing
9015                                 Is the timeline playing?
9016                         */
9017                         this._playing = false;
9018
9019                         /**
9020                         @name glow.anim.Timeline#loop
9021                         @type Boolean
9022                         @description Inidcates whether the timeline loops.
9023
9024                                 The "complete" event does not fire for looping animations.
9025                                 This can be set while a timeline is playing.
9026                         */
9027                         this.loop = opts.loop;
9028
9029                         var i, j, iLen, jLen,
9030                                 channel,
9031                                 allChannels = this._channels,
9032                                 totalDuration = 0,
9033                                 channelDuration;
9034
9035                         //process channels
9036                         for (i = 0, iLen = allChannels.length; i < iLen; i++) {
9037                                 channel = allChannels[i];
9038                                 channelDuration = 0;
9039                                 for (j = 0, jLen = channel.length; j < jLen; j++) {
9040                                         //create a blank animation for time waiting
9041                                         if (typeof channel[j] == "number") {
9042                                                 channel[j] = new r.Animation(channel[j]);
9043                                         }
9044                                         if (channel[j] instanceof r.Animation) {
9045                                                 if (! channel[j].useSeconds) {
9046                                                         throw new Error("Timelined animations must be timed in seconds");
9047                                                 }
9048                                                 channel[j]._timelineOffset = channelDuration * 1000;
9049                                                 channelDuration += channel[j].duration;
9050                                                 channel[j]._channelIndex = i;
9051                                         }
9052                                 }
9053                                 /**
9054                                 @name glow.anim.Timeline#duration
9055                                 @type Number
9056                                 @description Length of the animation in seconds
9057                                 */
9058                                 this.duration = totalDuration = Math.max(channelDuration, totalDuration);
9059                         }
9060                         /*
9061                         PrivateProperty: _controlAnim
9062                                 This is used to keep the animation in time
9063                         */
9064                         this._controlAnim = new r.Animation(totalDuration);
9065                         events.addListener(this._controlAnim, "frame", this._processFrame, this);
9066                         events.addListener(this._controlAnim, "complete", this._complete, this);
9067                         
9068                         // add events from constructor
9069                         addEventsFromOpts(this, opts, timelineEventConstructorNames);
9070                 };
9071                 r.Timeline.prototype = {
9072                         /*
9073                         PrivateMethod: _advanceChannel
9074                                 Move to the next position in a particular channel
9075                         */
9076                         _advanceChannel: function(i) {
9077                                 //is there a next animation in the channel?
9078                                 var currentAnim = this._channels[i][this._channelPos[i]],
9079                                         nextAnim = this._channels[i][++this._channelPos[i]];
9080
9081                                 if (currentAnim && currentAnim._playing) {
9082                                         currentAnim._playing = false;
9083                                         events.fire(currentAnim, "complete");
9084                                         if (currentAnim._opts.destroyOnComplete) {
9085                                                 currentAnim.destroy();
9086                                         }
9087                                 }
9088                                 if ((nextAnim) !== undefined) {
9089                                         if (typeof nextAnim == "function") {
9090                                                 nextAnim();
9091                                                 this._advanceChannel(i);
9092                                         } else {
9093                                                 nextAnim.position = 0;
9094                                                 nextAnim._channelIndex = i;
9095                                                 events.fire(nextAnim, "start");
9096                                                 nextAnim._playing = true;
9097                                         }
9098                                 }
9099                         },
9100                         _complete: function() {
9101                                 if (this.loop) {
9102                                         this.start();
9103                                         return;
9104                                 }
9105                                 this._playing = false;
9106                                 events.fire(this, "complete");
9107                                 if (this._opts.destroyOnComplete) {
9108                                         this.destroy();
9109                                 }
9110                         },
9111                         _processFrame: function() {
9112                                 var i, len, anim, controlAnim = this._controlAnim,
9113                                         msFromStart = (new Date().valueOf()) - controlAnim._timeAnchor;
9114
9115                                 for (i = 0, len = this._channels.length; i < len; i++) {
9116                                         if (! (anim = this._channels[i][this._channelPos[i]])) { continue; }
9117                                         anim.position = (msFromStart - anim._timelineOffset) / 1000;
9118                                         if (anim.position > anim.duration) {
9119                                                 anim.position = anim.duration;
9120                                         }
9121                                         anim.value = anim.tween(anim.position / anim.duration);
9122                                         events.fire(anim, "frame");
9123                                         if (anim.position == anim.duration) {
9124                                                 this._advanceChannel(i);
9125                                         }
9126                                 }
9127                         },
9128
9129                         /**
9130                         @name glow.anim.Timeline#start
9131                         @function
9132                         @description Starts playing the timeline from the beginning.
9133                         @returns this
9134                         */
9135                         start: function() {
9136                                 var e = events.fire(this, "start");
9137                                 if (e.defaultPrevented()) { return this; }
9138                                 var i, iLen, j, jLen, anim;
9139                                 this._playing = true;
9140                                 for (i = 0, iLen = this._channels.length; i < iLen; i++) {
9141                                         this._channelPos[i] = -1;
9142                                         this._advanceChannel(i);
9143                                         for (j = this._channels[i].length; j; j--) {
9144                                                 anim = this._channels[i][j];
9145                                                 if (anim instanceof r.Animation) {
9146                                                         anim.goTo(0);
9147                                                 }
9148                                         }
9149                                 }
9150                                 this._controlAnim.start();
9151                                 return this;
9152                         },
9153
9154                         /**
9155                         @name glow.anim.Timeline#stop
9156                         @function
9157                         @description Stops the timeline.
9158                         @returns this
9159                         */
9160                         stop: function() {
9161                                 if (this._playing) {
9162                                         var e = events.fire(this, "stop");
9163                                         if (e.defaultPrevented()) { return this; }
9164                                         this._playing = false;
9165                                         var anim;
9166                                         for (var i = 0, len = this._channels.length; i<len; i++) {
9167                                                 anim = this._channels[i][this._channelPos[i]];
9168                                                 if (anim instanceof r.Animation && anim._playing) {
9169                                                         events.fire(anim, "stop");
9170                                                         anim._playing = false;
9171                                                 }
9172                                         }
9173                                         this._controlAnim.stop();
9174                                 }
9175                                 return this;
9176                         },
9177                         
9178                         /**
9179                         @name glow.anim.Timeline#destroy
9180                         @function
9181                         @description Destroys the timeline & animations within it
9182                                 Call this on timeline you no longer need to free memory.
9183                         @returns this
9184                         */
9185                         destroy: function() {
9186                                 var i, j;
9187                                 // stop the animation in case it's still playing
9188                                 this.stop();
9189                                 events.removeAllListeners(this);
9190                                 this._controlAnim.destroy();
9191                                 
9192                                 // loop through all the channels
9193                                 i = this._channels.length; while (i--) {
9194                                         // loop through all the animations
9195                                         j = this._channels[i].length; while (j--) {
9196                                                 // check it has a destroy method (making sure it's an animation & not a function)
9197                                                 if (this._channels[i][j].destroy) {
9198                                                         // DESTROYYYYY!
9199                                                         this._channels[i][j].destroy();
9200                                                 }
9201                                         }
9202                                 }
9203                                 return this;
9204                         },
9205
9206                         /**
9207                         @name glow.anim.Timeline#resume
9208                         @function
9209                         @description Resumes the timeline from wherever it was stopped.
9210                         @returns this
9211                         */
9212                         resume: function() {
9213                                 if (! this._playing) {
9214                                         var e = events.fire(this, "resume");
9215                                         if (e.defaultPrevented()) { return this; }
9216                                         this._playing = true;
9217                                         var anim;
9218                                         for (var i = 0, len = this._channels.length; i<len; i++) {
9219                                                 anim = this._channels[i][ this._channelPos[i] ];
9220                                                 if (anim instanceof r.Animation && !anim._playing) {
9221                                                         events.fire(anim, "resume");
9222                                                         anim._playing = true;
9223                                                 }
9224                                         }
9225                                         this._controlAnim.resume();
9226                                 }
9227                                 return this;
9228                         },
9229
9230                         /**
9231                         @name glow.anim.Timeline#isPlaying
9232                         @function
9233                         @returns {Boolean}
9234                         @description Returns true if the timeline is playing.
9235                         */
9236                         isPlaying: function() {
9237                                 return this._playing;
9238                         },
9239                         
9240                         /**
9241                         @name glow.anim.Timeline#goTo
9242                         @function
9243                         @returns this
9244                         @description Go to a specific point in the timeline
9245                         @param {Number|glow.anim.Animation} pos Position in the timeline to go to.
9246
9247                                 You can go to a specific point in time (in seconds) or provide
9248                                 a reference to a particular animation to begin at.
9249
9250                         @example
9251                                 var myTimeline = new glow.anim.Timeline([anim1, anim2]);
9252                                 
9253                                 //start the Timeline 2.5 seconds in
9254                                 myTimeline.goTo(2.5).resume();
9255                                 
9256                         @example
9257                                 var myTimeline = new glow.anim.Timeline([anim1, anim2]);
9258                                 
9259                                 //start the Timeline from anim2
9260                                 myTimeline.goTo(anim2).resume();
9261                         */
9262                         goTo: function(pos) {
9263                                 var i,
9264                                         j,
9265                                         k,
9266                                         channelsLen = this._channels.length,
9267                                         channelLen,
9268                                         // holding var for an anim
9269                                         anim,
9270                                         runningDuration;
9271                                 
9272                                 if (typeof pos == "number") {
9273                                         
9274                                         if (pos > this.duration) {
9275                                                 // if the position is greater than the total, 'loop' the value
9276                                                 if (this.loop) {
9277                                                         pos = pos % this.duration;
9278                                                 } else {
9279                                                         pos = this.duration;
9280                                                 }
9281                                         }
9282                                         
9283                                         // advance the control anim
9284                                         this._controlAnim.goTo(pos);
9285                                         
9286                                         // loop through the animations in all the channels, find out which
9287                                         // one to start playing from
9288                                         for (i = 0; i < channelsLen; i++) {
9289
9290                                                 runningDuration = 0;
9291                                                 // go through animations in that channel
9292                                                 for (j = 0, channelLen = this._channels[i].length; j < channelLen; j++) {
9293                                                         anim = this._channels[i][j];
9294                                                         
9295                                                         if (anim instanceof r.Animation) {
9296                                                                 // we found an animation we should be playing
9297                                                                 if ( (runningDuration + anim.duration) > pos) {
9298                                                                         // record its position and leave the loop
9299                                                                         this._channelPos[i] = j;
9300                                                                         anim.goTo(pos - runningDuration);
9301                                                                         break;
9302                                                                 }
9303                                                                 // we're moving to a position where this animation has
9304                                                                 // finished playing, need to fire its final frame
9305                                                                 anim.goTo(anim.duration);
9306                                                                 // add that anim to the running total
9307                                                                 runningDuration += anim.duration;
9308                                                         }
9309                                                         
9310                                                 }
9311                                                 // right, now we need to move all animations after this
9312                                                 // one to the start...
9313                                                 for (k = channelLen; k > j; k--) {
9314                                                         anim.goTo(0);
9315                                                 }
9316                                         }
9317                                 } else {
9318                                         // ok, we've been provided with an object rather than a number of seconds
9319                                         // Let's convert it to seconds and rerun this function
9320                                         for (i = 0; i < channelsLen; i++) {
9321                                                 // let's count this to find out what "time" the item the user wants to play is at
9322                                                 runningDuration = 0;
9323                                                 
9324                                                 // let's loop through animations in that channel
9325                                                 for (j = 0, channelLen = this._channels[i].length; j < channelLen; j++) {
9326                                                         anim = this._channels[i][j];
9327                                                         
9328                                                         if (anim === pos) {
9329                                                                 // oh! We've found the thing they want to play
9330                                                                 return this.goTo(runningDuration);
9331                                                         }
9332                                                         if (anim instanceof r.Animation) {
9333                                                                 // add that anim to the running total
9334                                                                 runningDuration += anim.duration;
9335                                                         }
9336                                                 }
9337                                         }
9338                                         throw "Animation not found in animation channels";
9339                                 }
9340                                 return this;
9341                         }
9342                 };
9343                 glow.anim = r;
9344         }
9345 });
9346 /**
9347         @name glow.forms
9348         @namespace
9349         @see <a href="../furtherinfo/forms/">Validating Forms</a>
9350         @see <a href="../furtherinfo/forms/defaultfeedback/">Using default form feedback</a>
9351         @see <a href="../furtherinfo/forms/example.shtml">Working example</a>
9352         @description Validating HTML Forms.
9353                 To get started, you'll need to create a {@link glow.forms.Form Form instance}.
9354  */
9355 (window.gloader || glow).module({
9356         name: "glow.forms",
9357         library: ["glow", "1.7.0"],
9358         depends: [["glow", "1.7.0", 'glow.dom', 'glow.events', 'glow.anim', 'glow.net', 'glow.i18n']],
9359         builder: function(glow) {
9360
9361         var $i18n = glow.i18n,
9362                 $interpolate = glow.lang.interpolate;
9363
9364         $i18n.addLocaleModule("GLOW_FORMS", "en", {
9365                 TEST_MESSAGE_REQUIRED  : "Value is required",
9366                 TEST_MESSAGE_IS_NUMBER : "Must be a number.",
9367                 TEST_MESSAGE_MIN       : "The value must be at least {arg}.",
9368                 TEST_MESSAGE_MAX       : "The value must be less than {arg}.",
9369                 TEST_MESSAGE_RANGE     : "The value must be {min} or greater, and less than {max}.",
9370                 TEST_MESSAGE_MIN_COUNT : "Must be have at least {arg} values.",
9371                 TEST_MESSAGE_MAX_COUNT : "Must be have at most {arg} values.",
9372                 TEST_MESSAGE_COUNT     : "Must have {arg} values.",
9373                 TEST_MESSAGE_REGEX     : "Must be in the correct format.",
9374                 TEST_MESSAGE_MIN_LEN   : "Must be at least {arg} characters.",
9375                 TEST_MESSAGE_MAX_LEN   : "Must be at most {arg} characters.",
9376                 TEST_MESSAGE_IS_EMAIL  : "Must be a valid email address.",
9377                 TEST_MESSAGE_SAME_AS   : "Must be the same as: {arg}",
9378                 TEST_MESSAGE_AJAX      : "server responded",
9379                 TEST_MESSAGE_IS        : "Must be {arg}",
9380                 TEST_MESSAGE_IS_NOT    : "Must not be {arg}"
9381         });
9382
9383 glow.forms = {};
9384
9385 /**
9386         @name glow.forms.Form
9387         @constructor
9388         @description Create an object to add tests to.
9389         @see <a href="../furtherinfo/forms/">Validating Forms</a>
9390         @see <a href="../furtherinfo/forms/defaultfeedback/">Using default form feedback</a>
9391         @param {glow.dom.NodeList | Selector} formNode
9392         @param {Object} [opts]
9393         @param {Function} [opts.onValidate] Handles the 'validate' event when all tests are complete. Default is glow.forms.feedback.defaultFeedback.
9394         @example
9395         myForm = new glow.forms.Form(
9396                 glow.dom.get("#htmlFormId"),
9397                 {
9398                         onValidate: function(results) {
9399                                 // ...
9400                         }
9401                 }
9402         );
9403  */
9404 glow.forms.Form = function(formNode, opts) { /*debug*///console.log("glow.forms.Form#new("+formNode+", "+opts+")");
9405         /**
9406         @name glow.forms.Form#formNode
9407         @type glow.dom.NodeList
9408         @description NodeList containing the form element
9409         */
9410         this.formNode = glow.dom.get(formNode);
9411         if (!this.formNode[0]) throw "Could not find form. Possibly run before DOM ready.";
9412         this._fields = [];
9413         this._result = null;
9414         this.opts = opts || {};
9415         glow.events.addListener(this, "validate", this.opts.onValidate || feedback.defaultFeedback);
9416
9417         this._idleTimer = null;
9418         
9419         this._localeModule = $i18n.getLocaleModule("GLOW_FORMS");
9420
9421         // add event listener to form
9422         var thisForm = this;
9423         glow.events.addListener(
9424                 this.formNode,
9425                 "submit",
9426                 function() {
9427                         thisForm.validate('submit');
9428                         return false; // submit() will be called from the nextField method instead
9429                 }
9430         );
9431 }
9432
9433 /**
9434         @name glow.forms.Form#event:validate
9435         @event
9436         @description Fired whenever the glow tries to validate a form.
9437         
9438         If you prefer you can set a handler for this event via the <code>onValidate</code> option of the glow.forms.Form constructor.
9439         
9440         @param {glow.forms.ValidateResult} event A specialised Event object.
9441         @example
9442         
9443         glow.events.addListener(myForm, "validate", function(e) {
9444                 if (e.eventName == 'submit') {
9445                         if (e.errorCount == 0) { alert("Well done!"); }
9446                         else { e.preventDefault(); } // stop the submit from happening
9447                 }
9448         });
9449  */
9450
9451 /**
9452         @name glow.forms.Form#validate
9453         @function
9454         @description Run validation tests.
9455                 This is called automatically depending on the tests added to the form.
9456                 However, you can trigger validation manually
9457         @param {String} [eventName='submit'] Run only tests tied to this eventname.
9458         @param {String} [fieldName] Run only tests attached to the form element with this name.
9459  */
9460 glow.forms.Form.prototype.validate = function(eventName, fieldName) { /*debug*///console.log("glow.forms.Form#validate("+eventName+", "+fieldName+")");
9461         this.eventName = eventName || 'submit'; // default
9462         this._result = new glow.forms.ValidateResult(this.eventName);
9463         this._result.form = this;
9464
9465         this._fieldCur = 0;
9466         this._testCur = -1;
9467
9468         this._fieldName = fieldName;
9469         nextTest.call(this);
9470 }
9471
9472 /**
9473         Advance the test cursor to get the next test in the queue and then run it.
9474         @function
9475         @name nextTest
9476         @this {glow.forms.Form}
9477         @calledBy glow.forms.Form#validate
9478         @calledBy onTestResult
9479         @private
9480  */
9481 var nextTest = function() { /*debug*///console.log("glow.forms.Form#nextTest()");
9482         this._testCur++;
9483         if (this._testCur >= this._fields[this._fieldCur]._tests.length) { // run out of tests for the current field?
9484                 if (!nextField.call(this)) return;
9485         }
9486
9487         var currentTest = this._fields[this._fieldCur]._tests[this._testCur]; // shortcut
9488
9489         // get value from form element, normalize into an array
9490         var fieldValue;
9491         if (currentTest.opts.field) { // a conditional test
9492                 fieldValue = this.formNode.val()[currentTest.opts.field] || "";
9493                 currentTest.isConditional = true;
9494         }
9495         else {
9496                 fieldValue = this.formNode.val()[this._fields[this._fieldCur].name] || "";
9497         }
9498
9499         // values should always be an array
9500         if (!fieldValue.join) fieldValue = [fieldValue];
9501
9502         var callback = function(that) { // closure
9503                 return function() { onTestResult.apply(that, arguments) };
9504         }(this);
9505
9506         // only run tests that are tied to the eventName being validated
9507         currentTest.opts.on = currentTest.opts.on || "submit";
9508         if (
9509                 this._result.eventName
9510                 && (" "+currentTest.opts.on+" ").indexOf(" "+this._result.eventName+" ") != -1 // assume space delimited event names
9511         ) {
9512                 // skip tests that are not tied to the fieldName being validated
9513                 if (this._fieldName && this._fieldName != currentTest.name) {
9514                         nextTest.call(this);
9515                         return;
9516                 }
9517
9518                 // run the test, if it exists
9519                 if (typeof glow.forms.tests[currentTest.type] != "function") {
9520                         throw "Unimplemented test: no test exists of type '"+currentTest.type+"'.";
9521                 }
9522                 
9523                 currentTest.opts._localeModule = this._localeModule;
9524                 glow.forms.tests[currentTest.type](fieldValue, currentTest.opts, callback, this.formNode.val());
9525         }
9526         else {
9527                 nextTest.call(this);
9528         }
9529 }
9530
9531 /**
9532         Advance the field cursor to get the next field in the queue.
9533         @function
9534         @name nextField
9535         @this {glow.forms.Form}
9536         @calledBy glow.forms.Form#nextTest
9537         @private
9538  */
9539 var nextField = function() { /*debug*///console.log("glow.forms.Form#nextField()");
9540         // start at the beginning of the next field
9541         this._fieldCur++;
9542         this._testCur = 0;
9543
9544         if (this._fieldCur >= this._fields.length) { // run out of fields?
9545                 this._fieldCur = 0;
9546                 // ready to fire the validate event now
9547                 glow.events.fire(this, "validate", this._result);
9548                 
9549                 if ( this.eventName == "submit" && this._result && !this._result.defaultPrevented() ) { // ready to submit now
9550                         try {
9551                                 // we cannot initiate a form submit by simply returning true or false from
9552                                 // this event handler because there may be asynchronous tests still pending at this point,
9553                                 // so we must call submit ourselves, after the last field has finally been tested
9554                                 this.formNode[0].submit();
9555                         }
9556                         catch(e) {
9557                                 throw new Error("Glow can't submit the form because the submit function can't be called. Perhaps that form's submit was replaced by an input element named 'submit'?");
9558                         }
9559                 }
9560                 
9561                 return false; // don't keep going
9562         }
9563
9564         return true; // do keep going
9565 }
9566
9567 /**
9568         @name onTestResult
9569         @function
9570         @this {glow.forms.Form}
9571         @calledBy glow.forms.tests.*
9572         @param {Number} result One of: glow.forms.PASS, glow.forms.FAIL
9573         @param {String} message
9574         @private
9575  */
9576 var onTestResult = function(result, message) { /*debug*///console.log("glow.forms.Form#onTestResult("+result+", "+message+")");
9577         // convert result from a boolean to glow.forms.FAIL / glow.forms.PASS
9578         if (typeof result == "boolean") result = (result)? glow.forms.PASS : glow.forms.FAIL;
9579
9580         // a conditional test has failed?
9581         if (this._fields[this._fieldCur]._tests[this._testCur].isConditional && result === glow.forms.FAIL) {
9582                 result = glow.forms.SKIP; // failure of a conditional test becomes a skip
9583         }
9584
9585         this._result.fields.push(
9586                 {
9587                         name: this._fields[this._fieldCur].name,
9588                         result: result,
9589                         message: message
9590                 }
9591         );
9592
9593         if (result !== glow.forms.PASS) { // might be a fail or a skip
9594                 if (result === glow.forms.FAIL) this._result.errorCount++;
9595
9596                 // skip over all further tests for this field
9597                 this._testCur = this._fields[this._fieldCur]._tests.length;
9598         }
9599
9600         nextTest.call(this);
9601 }
9602
9603 /**
9604         @name glow.forms.Form#addTests
9605         @function
9606         @description Add one or more tests to a field.
9607         @param {String} fieldName The name of the field to add tests to.
9608         @param {Array} [spec]
9609         
9610                 Test specifications identify the type of test to be run on a field to
9611                 determine whether it contains desired data. See docs on the
9612                 {@link glow.forms.tests types of tests}.
9613         
9614         @example
9615         //pattern for a test specification
9616         [
9617           "testName", //name of the test to run
9618           {
9619             arg     : 5,                //an argument for the test, not all tests need this
9620             on      : "submit change",  //when should this test be run?
9621             message : "Incorrect value" //a custom error message to display
9622           }
9623         ]
9624         
9625         @example
9626         //setting a form up for validation
9627         var myForm = new glow.forms.Form(glow.dom.get("#myFormId"))
9628                 .addTests(
9629                         "username",
9630                         ["required"],
9631                         ["maxLen", {
9632                                 arg: 12,
9633                                 message: "Name must be les than 12 characters long."
9634                         }]
9635                 )
9636                 .addTests(
9637                         "email",
9638                         ["isEmail"]
9639                 );
9640  */
9641 glow.forms.Form.prototype.addTests = function(fieldName /*...*/) { /*debug*///console.log("glow.forms.Form#addTests("+fieldName+", ...)");
9642         var field = {name: fieldName, _tests:[]};
9643
9644         var changeCallback = function(that) {
9645                 return function() {
9646                         that.validate.apply(that, ["change", fieldName])
9647                 };
9648         }(this);
9649
9650         var clickCallback = function(that) {
9651                 return function() {
9652                         that.validate.apply(that, ["click", fieldName])
9653                 };
9654         }(this);
9655
9656         var idleCallback = function(that) {
9657                 return function() {
9658                         that.validate.apply(that, ["idle", fieldName]);
9659                 };
9660         }(this);
9661
9662         // loop over test specifications
9663         for (var i = 1; i < arguments.length; i++) {
9664                 var testType = arguments[i][0];
9665                 var testOpts = (arguments[i].length > 1)? arguments[i][1] : {}; // default opts
9666
9667                 field._tests.push({name: fieldName, type: testType, opts: testOpts});
9668
9669                 // add event listeners to form fields for change events
9670                 if (!changeCallback.added && (" "+testOpts.on+" ").indexOf(" change ") != -1) {
9671                         var inputs = this.formNode.get("*").each(function (i) {
9672                                 if (this.name == fieldName) {
9673                                         glow.events.addListener(this, "change", changeCallback);
9674                                         changeCallback.added = true;
9675                                 }
9676                         });
9677                 }
9678
9679                 // add event listeners to form fields for click events
9680                 if (!clickCallback.added && (" "+testOpts.on+" ").indexOf(" click ") != -1) {
9681                         var inputs = this.formNode.get("*").each(function (i) {
9682                                 if (this.name == fieldName) {
9683                                         glow.events.addListener(this, "click", clickCallback);
9684                                         clickCallback.added = true;
9685                                 }
9686                         });
9687                 }
9688
9689                 if (!idleCallback.added && (" "+testOpts.on+" ").indexOf(" idle ") != -1) {
9690                         var idleDelay = (typeof testOpts.delay != "undefined")? parseInt(testOpts.delay) : 1000; // default delay before idle handler is run
9691
9692                         var inputs = this.formNode.get("*").each(function (i) {
9693                                 if (this.name == fieldName) {
9694                                         // FIXME: adding idleTimeoutID to HTML element, is this the best way?
9695                                         glow.events.addListener(this, "keyup", function(t){ return function() {window.clearTimeout(this.idleTimeoutID); if (this.value) this.idleTimeoutID = window.setTimeout(idleCallback, t)} }(idleDelay));
9696                                         glow.events.addListener(this, "blur", function() {window.clearTimeout(this.idleTimeoutID)});
9697
9698                                         idleCallback.added = true;
9699                                 }
9700                         });
9701                 }
9702         }
9703
9704         this._fields.push(field);
9705
9706         return this; // chained
9707 }
9708
9709 /**
9710         @name glow.forms.ValidateResult
9711         @constructor
9712         @extends glow.events.Event
9713         @description The overall result returned by an attempt to validate the current state of the form.
9714         
9715         The ValidateResult object is used by glow.forms.Form to accumulate and record the test results as they are run. It is created automatically by the running validation so it is not necessary for the user to instantiate it directly, but it is useful to know the properties and their meanings as these will likely be referred to when a custom <code>onValidate</code> handler is run.
9716         @param {String} eventName
9717         @property {String} eventName The name of the event that was associated with this validation event.
9718         
9719         Validation can happen based on one of several different user interactions, this property allows you to identify the type of interaction that initiated this validation. Examples are:
9720         
9721                 <dl>
9722                         <dt>submit</dt>
9723                         <dd>The user has done something to submit the form, for example by pressing the Submit button.</dd>
9724                         <dt>change</dt>
9725                         <dd>The user has modified the value of a form field.</dd>
9726                         <dt>idle</dt>
9727                         <dd>The user is typing in a form field but has paused for a moment (by default 1 second).</dd>
9728                         <dt>click</dt>
9729                         <dd>The user has clicked the mouse on or in a form field.</dd>
9730                 </dl>
9731         
9732         Which user interaction is associated with which tests is determined by the options you used when you added the test. See the documentation for {@link glow.forms.Form#addTests} for more information. 
9733         
9734         @property {Object[]} fields Each object in this array has a name of a field, a test result such as glow.forms.PASS, glow.forms.FAIL or glow.forms.SKIP, and a message describing the test result.
9735         
9736         The effect of validation is that the value or state of each field in the form is compared to the tests the developer has added to the fields. In each tested field a determination is made that the current value either passes or fails (or, if the test wasn't run at all, is skipped). The <code>fields</code> property provides information on the overall result of the validation, on each field that was tested and the results.
9737         
9738         @property {Number} errorCount The number of fields that had a failing test.
9739         
9740         From the <code>fields</code> property you can determine how many fields have failing or passing values; this is property is simply a more convenient way to access the total failing count of tests that fail. If no tests fail then this value will be 0 and you can consider the form to have validated.
9741         
9742  */
9743 glow.forms.ValidateResult = function(eventName) {
9744         glow.events.Event.apply(this);
9745
9746         this.eventName = eventName;
9747         this.errorCount = 0;
9748         this.value = undefined;
9749         this.fields = [];
9750 }
9751
9752 glow.lang.extend(glow.forms.ValidateResult, glow.events.Event);
9753
9754 /**
9755         @name glow.forms.PASS
9756         @type Number
9757         @description Constant for a passed test.
9758                 This indicates that the value in a field passes all the tests associated with it. You can use this when creating {@link glow.forms.tests.custom custom tests}
9759  */
9760 glow.forms.PASS =  1;
9761 /**
9762         @name glow.forms.FAIL
9763         @type Number
9764         @description Constant for a failing test.
9765                 This indicates that the value in a field fails at least one of the tests associated with it. You can use this when creating {@link glow.forms.tests.custom custom tests}
9766  */
9767 glow.forms.FAIL =  0;
9768 /**
9769         @name glow.forms.SKIP
9770         @type Number
9771         @description Constant for a skipped test.
9772                 This indicates that there was some unmet condition associated with the applied tests, so they were not run. This state is not considered a fail, and will not affect glow.forms.ValidateResult#errorCount. You can use this when creating {@link glow.forms.tests.custom custom tests}.
9773  */
9774 glow.forms.SKIP = -1;
9775
9776 /**
9777         @name glow.forms.tests
9778         @namespace
9779         @see <a href="../furtherinfo/forms/">Validating Forms</a>
9780         @see <a href="../furtherinfo/forms/example.shtml">Working example</a>
9781         @description Collection of built-in tests that can be added to any form field as a way of validating that field's value.
9782
9783         <p>You do not need to call these functions directly, the devloper use a test by passing its name to the {@link glow.forms.Form#addTests addTests} method.</p>
9784
9785         <p>For more information about tests, how to create your own custom tests, or to see what arguments these tests take, you may refer to the <a href="../furtherinfo/forms/#custom_tests">Creating
9786         Custom Tests</a> section of the Validating Forms user guide.</p>
9787  */
9788 glow.forms.tests = {
9789         /**
9790                 @name glow.forms.tests.required
9791                 @function
9792                 @description The value must contain at least one non-whitespace character.
9793                 
9794                 A text input field that is empty, or contains only spaces for example will fail this test.
9795                 
9796                 @example
9797                 myForm.addTests(
9798                         "fieldName",
9799                         ["required"]
9800                 );
9801          */
9802         required: function(values, opts, callback) { /*debug*///console.log("glow.forms.tests.required()");
9803                 var message = opts.message || opts._localeModule.TEST_MESSAGE_REQUIRED;
9804
9805                 for (var i = 0, len = values.length; i < len; i++) {
9806                         if (/^\s*$/.test(values[i])) {
9807                                 callback(glow.forms.FAIL, message);
9808                                 return;
9809                         }
9810                 }
9811                 callback(glow.forms.PASS, message);
9812         }
9813         ,
9814         /**
9815                 @name glow.forms.tests.isNumber
9816                 @function
9817                 @description The value must be a valid number.
9818                 
9819                 A field that is empty, or contains a value that is not a number like 1 or 3.14 will fail this test.
9820                 
9821                 @example
9822                 myForm.addTests(
9823                         "fieldName",
9824                         ["isNumber"]
9825                 );
9826          */
9827         isNumber: function(values, opts, callback) { /*debug*///console.log("glow.forms.tests.isNumber()");
9828                 var message = opts.message || opts._localeModule.TEST_MESSAGE_IS_NUMBER;
9829
9830                 for (var i = 0, len = values.length; i < len; i++) {
9831                         if (values[i] == "" || isNaN(values[i])) {
9832                                 callback(glow.forms.FAIL, message);
9833                                 return;
9834                         }
9835                 }
9836                 callback(glow.forms.PASS, message);
9837         }
9838         ,
9839         /**
9840                 @name glow.forms.tests.min
9841                 @function
9842                 @description The numeric value must be at least the given value.
9843                 
9844                 A field whose value, when converted to a number, is not less than the given arg will fail this test.
9845                 
9846                 @example
9847                 myForm.addTests(
9848                         "fieldName",
9849                         ["min", {
9850                                 arg: "1"
9851                         }]
9852                 );
9853          */
9854         min: function(values, opts, callback) { /*debug*///console.log("glow.forms.tests.min()");
9855                 var message = opts.message || $interpolate(opts._localeModule.TEST_MESSAGE_MIN, {arg: opts.arg});
9856                 for (var i = 0, len = values.length; i < len; i++) {
9857                         if (Number(values[i]) < Number(opts.arg)) {
9858                                 callback(glow.forms.FAIL, message);
9859                                 return;
9860                         }
9861                 }
9862                 callback(glow.forms.PASS, message);
9863         }
9864         ,
9865         /**
9866                 @name glow.forms.tests.max
9867                 @function
9868                 @description The numeric value must be no more than the given value.
9869                 
9870                 A field whose value, when converted to a number, is not more than the given arg will fail this test.
9871                 
9872                 @example
9873                 myForm.addTests(
9874                         "fieldName",
9875                         ["max", {
9876                                 arg: "100"
9877                         }]
9878                 );
9879          */
9880         max: function(values, opts, callback) { /*debug*///console.log("glow.forms.tests.max()");
9881                 var message = opts.message || $interpolate(opts._localeModule.TEST_MESSAGE_MAX, {arg: opts.arg});
9882                 for (var i = 0, len = values.length; i < len; i++) {
9883                         if (Number(values[i]) > Number(opts.arg)) {
9884                                 callback(glow.forms.FAIL, message);
9885                                 return;
9886                         }
9887                 }
9888                 callback(glow.forms.PASS, message);
9889         }
9890         ,
9891         /**
9892                 @name glow.forms.tests.range
9893                 @function
9894                 @description The numeric value must be between x..y.
9895                 
9896                 A field whose value, when converted to a number, is not more than x and less than y will fail this test.
9897                 
9898                 @example
9899                 myForm.addTests(
9900                         "fieldName",
9901                         ["range", {
9902                                 arg: "18..118"
9903                         }]
9904                 );
9905          */
9906         range: function(values, opts, callback) { /*debug*///console.log("glow.forms.tests.range()");
9907                 var minmax = opts.arg.split(".."); // like "0..10"
9908                 if (typeof minmax[0] == "undefined" || typeof minmax[1] == "undefined") {
9909                         throw "Range test requires a parameter like 0..10."
9910                 }
9911                 var message = opts.message || $interpolate(opts._localeModule.TEST_MESSAGE_RANGE, {min: minmax[0], max : minmax[1]});
9912
9913                 // cast to numbers to avoid stringy comparisons
9914                 minmax[0] *= 1;
9915                 minmax[1] *= 1;
9916
9917                 // reverse if the order is hi..lo
9918                 if (minmax[0] > minmax[1]) {
9919                         var temp = minmax[0];
9920                         minmax[0] = minmax[1];
9921                         minmax[1] = temp;
9922                 }
9923
9924                 for (var i = 0, len = values.length; i < len; i++) {
9925                         if (values[i] < minmax[0] || values[i] > minmax[1]) {
9926                                 callback(glow.forms.FAIL, message);
9927                                 return;
9928                         }
9929                 }
9930                 callback(glow.forms.PASS, message);
9931         }
9932         ,
9933         /**
9934                 @name glow.forms.tests.minCount
9935                 @function
9936                 @description There must be at least the given number of values submitted with this name.
9937                         This is useful for multiple selects and checkboxes that have the same name.
9938                 @example
9939                 myForm.addTests(
9940                         "fieldName",
9941                         ["minCount", {
9942                                 arg: "1"
9943                         }]
9944                 );
9945          */
9946         minCount: function(values, opts, callback) { /*debug*///console.log("glow.forms.tests.minCount()");
9947                 var message = opts.message || $interpolate(opts._localeModule.TEST_MESSAGE_MIN_COUNT, {arg: opts.arg});
9948
9949                 var count = 0;
9950
9951                 for (var i = 0; i < values.length; i++ ) {
9952                         if (values[i] != "") count++;
9953                 }
9954
9955                 if (count < opts.arg) {
9956                         callback(glow.forms.FAIL, message);
9957                         return;
9958                 }
9959                 callback(glow.forms.PASS, message);
9960         }
9961         ,
9962         /**
9963                 @name glow.forms.tests.maxCount
9964                 @function
9965                 @description There must be no more than the given number of values submitted with this name.
9966                         This is useful for multiple selects and checkboxes that have the same name.
9967                 @example
9968                 myForm.addTests(
9969                         "fieldName",
9970                         ["maxCount", {
9971                                 arg: "10"
9972                         }]
9973                 );
9974          */
9975         maxCount: function(values, opts, callback) { /*debug*///console.log("glow.forms.tests.maxCount()");
9976                 var message = opts.message || $interpolate(opts._localeModule.TEST_MESSAGE_MAX_COUNT, {arg: opts.arg});
9977
9978                 var count = 0;
9979
9980                 for (var i = 0; i < values.length; i++ ) {
9981                         if (values[i] != "") count++;
9982                 }
9983
9984                 if (count > opts.arg) {
9985                         callback(glow.forms.FAIL, message);
9986                         return;
9987                 }
9988                 callback(glow.forms.PASS, message);
9989         }
9990         ,
9991         /**
9992                 @name glow.forms.tests.count
9993                 @function
9994                 @description There must be exactly the given number of values submitted with this name.
9995                         This is useful for multiple selects and checkboxes that have the same name.
9996                 @example
9997                 myForm.addTests(
9998                         "fieldName",
9999                         ["count", {
10000                                 arg: "2"
10001                         }]
10002                 );
10003          */
10004         count: function(values, opts, callback) { /*debug*///console.log("glow.forms.tests.count()");
10005                 var message = opts.message || $interpolate(opts._localeModule.TEST_MESSAGE_COUNT, {arg: opts.arg});
10006
10007                 var count = 0;
10008
10009                 for (var i = 0; i < values.length; i++ ) {
10010                         if (values[i] != "") count++;
10011                 }
10012
10013                 if (count != opts.arg) {
10014                         callback(glow.forms.FAIL, message);
10015                         return;
10016                 }
10017                 callback(glow.forms.PASS, message);
10018         }
10019         ,
10020         /**
10021                 @name glow.forms.tests.regex
10022                 @function
10023                 @description The value must match the given regular expression.
10024                 @example
10025                 myForm.addTests(
10026                         "fieldName",
10027                         ["regex", {
10028                                 arg: /^[A-Z0-9]*$/
10029                         }]
10030                 );
10031          */
10032         regex: function(values, opts, callback) { /*debug*///console.log("glow.forms.tests.regex()");
10033                 var message = opts.message || opts._localeModule.TEST_MESSAGE_REGEX;
10034
10035                 var regex = (typeof opts.arg == "string")? new RegExp(opts.arg) : opts.arg; // if its not a string assume its a regex literal
10036                 for (var i = 0, len = values.length; i < len; i++) {
10037                         if (!regex.test(values[i])) {
10038                                 callback(glow.forms.FAIL, message);
10039                                 return;
10040                         }
10041                 }
10042                 callback(glow.forms.PASS, message);
10043         }
10044         ,
10045         /**
10046                 @name glow.forms.tests.minLen
10047                 @function
10048                 @description The value must be at least the given number of characters long.
10049                 @example
10050                 myForm.addTests(
10051                         "fieldName",
10052                         ["minLen", {
10053                                 arg: "3"
10054                         }]
10055                 );
10056          */
10057         minLen: function(values, opts, callback) { /*debug*///console.log("glow.forms.tests.minLen()");
10058                 var message = opts.message || $interpolate(opts._localeModule.TEST_MESSAGE_MIN_LEN, {arg: opts.arg});
10059
10060                 for (var i = 0, len = values.length; i < len; i++) {
10061                         if (values[i].length < opts.arg) {
10062                                 callback(glow.forms.FAIL, message);
10063                                 return;
10064                         }
10065                 }
10066                 callback(glow.forms.PASS, message);
10067         }
10068         ,
10069         /**
10070                 @name glow.forms.tests.maxLen
10071                 @function
10072                 @description The value must be at most the given number of characters long.
10073                 @example
10074                 myForm.addTests(
10075                         "fieldName",
10076                         ["maxLen", {
10077                                 arg: "24"
10078                         }]
10079                 );
10080          */
10081         maxLen: function(values, opts, callback) { /*debug*///console.log("glow.forms.tests.maxLen()");
10082                 var message = opts.message || $interpolate(opts._localeModule.TEST_MESSAGE_MAX_LEN, {arg: opts.arg});
10083
10084                 for (var i = 0, len = values.length; i < len; i++) {
10085                         if (values[i].length > opts.arg) {
10086                                 callback(glow.forms.FAIL, message);
10087                                 return;
10088                         }
10089                 }
10090                 callback(glow.forms.PASS, message);
10091         }
10092         ,
10093         /**
10094                 @name glow.forms.tests.isEmail
10095                 @function
10096                 @description The value must be a valid email address.
10097                         This checks the formatting of the address, not whether the address
10098                         exists.
10099                 @example
10100                 myForm.addTests(
10101                         "fieldName",
10102                         ["isEmail"]
10103                 );
10104          */
10105         isEmail: function(values, opts, callback) { /*debug*///console.log("glow.forms.tests.isEmail()");
10106                 var message = opts.message || opts._localeModule.TEST_MESSAGE_IS_EMAIL;
10107
10108                 for (var i = 0, len = values.length; i < len; i++) {
10109                         if (!/^\s*[a-z0-9._%+-]+@[a-z0-9.-]+\.[a-z]{2,}\s*$/i.test(values[i])) {
10110                                 callback(glow.forms.FAIL, message);
10111                                 return;
10112                         }
10113                 }
10114                 callback(glow.forms.PASS, message);
10115         }
10116         ,
10117         /**
10118                 @name glow.forms.tests.sameAs
10119                 @function
10120                 @description The value must be the same as the value in the given field.
10121                 @example
10122                 myForm.addTests(
10123                         "email_confirm",
10124                         ["sameAs", {
10125                                 arg: "email"
10126                         }]
10127                 );
10128          */
10129         sameAs: function(values, opts, callback, formValues) { /*debug*///console.log("glow.forms.tests.sameAs()");
10130                 var message = opts.message || $interpolate(opts._localeModule.TEST_MESSAGE_SAME_AS, {arg: opts.arg});
10131                 var compareTo = formValues[opts.arg];
10132
10133                 for (var i = 0, len = values.length; i < len; i++) {
10134                         if (values[i] != compareTo) {
10135                                 callback(glow.forms.FAIL, message);
10136                                 return;
10137                         }
10138                 }
10139                 callback(glow.forms.PASS, message);
10140         }
10141         ,
10142         /**
10143                 @name glow.forms.tests.ajax
10144                 @function
10145                 @description Send the data to the server for testing.
10146                         A request to the given URL will be made and the response will be passed to the given callback.
10147                         
10148                         'arg' is the function to handle the response from the server.
10149                         
10150                         This function should return a truthy value to indicate whether the server 
10151                         response should be considered a PASS or a FAIL. Optionally you can include a bespoke
10152                         error message by returning an array of two elements, the first being
10153                         the PASS or FAIL verdict, and the second being the error message to display.
10154                         
10155                         'url' is the url to call. You can use placeholders in here for form values (see example).
10156                 @example
10157                 
10158                 myForm.addTests(
10159                         "username",
10160                         ["ajax", {
10161                                 url: "/cgi/checkname.cgi?name={username}",
10162                                 arg: function (response) {
10163                                         if (response.text() == "OK") {
10164                                                 return glow.forms.PASS;
10165                                         } else {
10166                                                 return [glow.forms.FAIL, "That name is already taken."];
10167                                         }
10168                                 },
10169                                 message: "The server responded: that name is not permitted."
10170                         }]
10171                 );
10172          */
10173         ajax: function(values, opts, callback, formValues) { /*debug*///console.log("glow.forms.tests.ajax() - "+opts.url);
10174                 var queryValues = {},
10175                     message = (opts.message || opts._localeModule.TEST_MESSAGE_AJAX);
10176                     
10177                 for (var p in formValues) {
10178                         if (typeof formValues[p] == "string") {
10179                                 queryValues[p] = escape(formValues[p]);
10180                         }
10181                         else if (typeof formValues[p].push != "undefined") {
10182                                 queryValues[p] = glow.lang.map(formValues[p], function(i) { return escape(i); }).join(",");
10183                         }
10184                 }
10185                 var url = glow.lang.interpolate(opts.url, queryValues);
10186
10187                 var request = glow.net.get(url, {
10188                         onLoad: function(response) { /*debug*///console.log("glow.forms.tests.ajax - onLoad()");
10189                                 var verdict = opts.arg(response);
10190                                 if (typeof verdict.push == "undefined") verdict = [verdict, message];
10191                                 callback(verdict[0], verdict[1]);
10192                         },
10193                         onError: function(response) {
10194                                 alert("Error getting file: "+url);
10195                         }
10196                 });
10197         }
10198         ,
10199         /**
10200                 @name glow.forms.tests.custom
10201                 @function
10202                 @description Create a custom test.
10203                 
10204                         'arg' is a function which tests the form value.
10205                         
10206                         The function is given the following parameters:
10207                         
10208                         <dl>
10209                                 <dt>values</dt>
10210                                 <dd>
10211                                         An array of values submitted for that form field. If you
10212                                         are only expecting one value, it can be accessed via values[0]
10213                                 </dd>
10214                                 <dt>opts</dt>
10215                                 <dd>
10216                                         An object of any additional data included with the test
10217                                 </dd>
10218                                 <dt>callback</dt>
10219                                 <dd>
10220                                         This is a function used to tell Glow whether the test has
10221                                         passed or not. A callback is used rather than 'return' to
10222                                         allow async tests. The first parameter is either glow.forms.PASS
10223                                         or glow.forms.FAIL, the second is the success or failure message.
10224                                 </dd>
10225                                 <dt>formData</dt>
10226                                 <dd>
10227                                         This is an object of all values captured in the form.
10228                                 </dd>
10229                         </dl>
10230                 
10231                 @example
10232                 myForm.addTests(
10233                         "username",
10234                         ["custom", {
10235                                 arg: function(values, opts, callback, formData) {
10236                                         for (var i = 0, len = values.length; i < len; i++) {
10237                                                 if (values[i] == "Jake") {
10238                                                         callback(glow.forms.FAIL, "The name Jake is not allowed.");
10239                                                         return;
10240                                                 }
10241                                         }
10242                                         callback(glow.forms.PASS, "Good name.");
10243                                 }
10244                         }]
10245                 );
10246          */
10247         custom: function(values, opts, callback) {  /*debug*///console.log("glow.forms.tests.custom()");
10248                 opts.arg.apply(this, arguments);
10249         }
10250         ,
10251         /**
10252                 @name glow.forms.tests.is
10253                 @function
10254                 @description The value must be equal to a particular value
10255                 @example
10256                 // this test ensures "other" is required *if* the "reason" field is equal to "otherReason"
10257                 myForm.addTests(
10258                         "other",
10259                         ["is", {
10260                                 field: "reason",
10261                                 arg: "otherReason"
10262                         }],
10263                         ["required"]
10264                 );
10265          */
10266         "is": function(values, opts, callback) {
10267                 var message = opts.message || $interpolate(opts._localeModule.TEST_MESSAGE_IS, {arg: opts.arg});
10268
10269                 for (var i = 0, len = values.length; i < len; i++) {
10270                         if (values[i] != opts.arg) {
10271                                 callback(glow.forms.FAIL, message);
10272                                 return;
10273                         }
10274                 }
10275                 callback(glow.forms.PASS, message);
10276         }
10277         ,
10278         /**
10279                 @name glow.forms.tests.isNot
10280                 @function
10281                 @description The value must not be equal to a particular value
10282                 @example
10283                 // you may have a dropdown select where the first option is "none" for serverside reasons
10284                 myForm.addTests(
10285                         "gender",
10286                         ["isNot", {
10287                                 arg: "none"
10288                         }]
10289                 );
10290          */
10291         "isNot": function(values, opts, callback) {
10292                 var message = opts.message || $interpolate(opts._localeModule.TEST_MESSAGE_IS_NOT, {arg: opts.arg});
10293
10294                 for (var i = 0, len = values.length; i < len; i++) {
10295                         if (values[i] == opts.arg) {
10296                                 callback(glow.forms.FAIL, message);
10297                                 return;
10298                         }
10299                 }
10300                 callback(glow.forms.PASS, message);
10301         }
10302 }
10303
10304
10305 /**
10306 @name glow.forms.feedback
10307 @namespace
10308 @description Collection of functions for displaying validation results to the user
10309
10310   <p>These functions should be used as handlers for {@link glow.forms.Form}'s validate event. At the
10311   moment there is only one provided handler, more may be added in the future.</p>
10312
10313   <p>Of course, you don't have to use any of the methods here, you can provide your own.</p>
10314
10315 @see <a href="../furtherinfo/forms/defaultfeedback">Using the default form feedback</a>
10316 */
10317
10318 var feedback = glow.forms.feedback = {};
10319
10320 /**
10321 @name glow.forms.feedback.defaultFeedback
10322 @function
10323 @description Default handler used by {@link glow.forms.Form}.
10324
10325   <p>This method outputs messages to the user informing them which fields
10326   contain invalid data. The output is unstyled and flexible.</p>
10327
10328 @param {glow.forms.ValidateResult} result Object provided by the validate event
10329
10330 @see <a href="../furtherinfo/forms/defaultfeedback">Using the default form feedback</a>
10331 */
10332 feedback.defaultFeedback = (function() {
10333         
10334         //a hidden form element used to update a screenreader's buffer
10335         var screenReaderBufferUpdater;
10336         
10337         //attempts to update the buffer of the screen reader
10338         function updateScreenReaderBuffer() {
10339                 if (!screenReaderBufferUpdater) {
10340                         screenReaderBufferUpdater = glow.dom.create('<input type="hidden" value="0" name="1.7.0" id="1.7.0" />').appendTo(document.body);
10341                 }
10342                 screenReaderBufferUpdater[0].value++;
10343         }
10344         
10345         //write out the messages which appear next to (or near) the fields
10346         function inlineErrors(response) {
10347                 var fields = response.fields, //field test results
10348                         fieldElm, //holder for the field element(s) being processed
10349                         msgContainer, //holder for contextual message holder
10350                         labelError, //error holder within label element
10351                         i, len;
10352
10353                 for (i = 0, len = fields.length; i < len; i++) {
10354                         fieldElm = glow.dom.get(response.form.formNode[0].elements[fields[i].name]);
10355                         //here's where we get the error container, which is the label by default
10356                         //also we need to escape invalid css chars CSS
10357                         msgContainer = glow.dom.get("." + fields[i].name.replace(/(\W)/g, "\\$1") + "-msgContainer");
10358                         if (!msgContainer[0] && fieldElm.length == 1) {
10359                                 //none found, try and get the label
10360                                 msgContainer = response.form.formNode.get("label").filter(function() { return this.htmlFor == fieldElm[0].id })
10361                         }
10362
10363                         labelError = msgContainer.get("span.glow-errorMsg");
10364
10365                         if (fields[i].result) {
10366                                 //clear error messages & classes
10367                                 labelError.remove();
10368                                 fieldElm.removeClass("glow-invalid");
10369                         } else {
10370                                 if (msgContainer.length) {
10371                                         //add the error span to the label if it isn't already there
10372                                         if (!labelError[0]) {
10373                                                 msgContainer.append( (labelError = glow.dom.create('<span class="glow-errorMsg"></span>')) );
10374                                         }
10375                                         labelError.text(fields[i].message);
10376                                         fieldElm.addClass("glow-invalid");
10377                                 }
10378                         }
10379                 }
10380         }
10381
10382         //write out a list of errors to appear at the top of the form
10383         function summaryError(response) {
10384                 var fields = response.fields, //field test results
10385                         fieldElm, //holder for the field element(s) being processed
10386                         errorSummary, //div containing error summary
10387                         errorList, //list of errors inside the error summary
10388                         promptContainer, //holds the 'question' of the field
10389                         prompt, //text to prefix each error line with
10390                         i,
10391                         len;
10392
10393                 //remove existing summary
10394                 response.form.formNode.get("div.glow-errorSummary").remove();
10395                 //create a summary div
10396                 errorSummary = glow.dom.create('<div class="glow-errorSummary" tabindex="-1"><ul></ul></div>');
10397                 errorList = errorSummary.get("ul");
10398                 for (i = 0, len = fields.length; i < len; i++) {
10399                         fieldElm = glow.dom.get(response.form.formNode[0].elements[fields[i].name]);
10400                         promptContainer = glow.dom.get("." + fields[i].name.replace(/(\W)/g, "\\$1") + "-prompt");
10401                         if (!promptContainer[0] && fieldElm.length == 1) {
10402                                 //we don't have a default, get the label
10403                                 promptContainer = response.form.formNode.get("label").filter(function() { return this.htmlFor == fieldElm[0].id })
10404                         }
10405                         //did we get a prompt container?
10406                         if (promptContainer[0]) {
10407                                 //get rid of superflous content in the prompt container (such as errors)
10408                                 promptContainer.get("span.glow-errorMsg").remove();
10409                                 prompt = glow.lang.trim(promptContainer.text());
10410                                 if (prompt.slice(-1) == ":") {
10411                                         prompt = prompt.slice(0, -1);
10412                                 }
10413                         } else {
10414                                 //else we just use the field name
10415                                 prompt = fields[i].name.replace(/^\w/, function(s) { return s.toUpperCase() } );
10416                         }
10417
10418                         if (!fields[i].result) {
10419                                 errorList.append( glow.dom.create("<li></li>").text(prompt + ": " + fields[i].message) );
10420                         }
10421                 }
10422                 response.form.formNode.prepend(errorSummary.css("opacity", "0"));
10423                 glow.anim.css(errorSummary, "0.5", {
10424                         opacity: {from: 0, to: 1}
10425                 }, {tween: glow.tweens.easeOut()}).start();
10426                 
10427                 // if the error summary has been hidden, IE7 throws an exception here
10428                 try {           
10429                         errorSummary[0].focus();
10430                 } catch (e) {}
10431                 
10432                 updateScreenReaderBuffer();
10433         }
10434
10435         return function(response) {
10436                 if (response.eventName == "submit") {
10437                         //do we have any errors?
10438                         if (!response.errorCount) {
10439                                 //remove existing summary
10440                                 response.form.formNode.get("div.glow-errorSummary").remove();
10441                                 return;
10442                         }
10443                         summaryError(response);
10444                 }
10445                 // display inline errors
10446                 // we put this inside setTimeout to avoid an IE bug that can result
10447                 // in the cursor appearing outside the form element
10448                 setTimeout(function() {
10449                         inlineErrors(response);
10450                 }, 0);
10451                 
10452                 return false;
10453         }
10454 }());
10455
10456         }
10457 });
10458 /**
10459 @name glow.embed
10460 @namespace
10461 @description Detect and embed Flash objects
10462 @see <a href="../furtherinfo/embed/">Flash embedding</a>
10463 */
10464 (window.gloader || glow).module({
10465         name: "glow.embed",
10466         library: ["glow", "1.7.0"],
10467         depends: [["glow", "1.7.0", "glow.dom", "glow.data", "glow.i18n"]],
10468         builder: function(glow) {
10469
10470                 var $i18n = glow.i18n;
10471                 
10472                 $i18n.addLocaleModule("GLOW_EMBED", "en", {
10473                         FLASH_MESSAGE : "This content requires Flash Player version {min} (installed version: {installed})",
10474                         NO_PLAYER_MESSAGE : "No Flash Flayer installed, or version is pre 6.0.0"
10475                 });
10476
10477                 /**
10478                 @name to_attributes
10479                 @private
10480                 @function
10481                 @param object
10482                 @returns attribute string suitable for inclusion within Flash embed/object tag
10483                 @description converts a hash to a space delimited string of attribute assignments
10484
10485                         Simple values are assumed for the object properties, with the exception of
10486                         'flashVars' which is treated as a special case. If 'flashVars' is itself an object,
10487                         it will be serialised in querystring format.
10488                         returns string representation of object as attribute assignments eg:
10489                         {id:"myId",name:"my-name"}  becomes  'id="myId" name="my-name"'
10490
10491                 */
10492                 function to_attributes(object){
10493
10494                         var attributes = "";
10495
10496                         for (var $param in object){
10497                                 if ($param.toLowerCase() == "flashvars" && typeof object[$param] == "object"){
10498                                         attributes += ' FlashVars="' + glow.data.encodeUrl(object[$param]) + '"';
10499                                 }
10500                                 else {
10501                                         attributes += ' ' + $param + '="' + object[$param] + '"';
10502                                 }
10503                         }
10504                         return attributes;
10505                 }
10506
10507                 /**
10508                 @name toParams
10509                 @private
10510                 @function
10511                 @param object
10512                 @returns string of param tags or an object element
10513                 @description converts a hash to a string of param tags
10514
10515                         Simple values are assumed for the object properties, with the exception of
10516                         'flashVars' which is treated as a special case. If 'flashVars' is itself an object,
10517                         it will be serialised in querystring format.
10518                 */
10519                 function toParams(object) {
10520                         var r = "",
10521                                 key,
10522                                 value;
10523
10524                         for (key in object) {
10525                                 if (key.toLowerCase() == "flashvars" && typeof object[key] == "object"){
10526                                         value = glow.data.encodeUrl(object[key]);
10527                                 } else {
10528                                         value = object[key];
10529                                 }
10530
10531                                 r += '<param name="' + key + '" value="' + value + '" />\n';
10532                         }
10533                         return r;
10534                 }
10535
10536                 /**
10537                 @name _set_defaults
10538                 @private
10539                 @function
10540                 @param target
10541                 @param options
10542                 @returns
10543                 @description applies a hash of default property values to a target object
10544
10545                         Properties on the defaults object are copied to the target object if no such property is present.
10546
10547                 */
10548                 function _set_defaults(target,defaults){
10549                         target = target || {};
10550
10551                         for (var param in defaults) {
10552                                 if (typeof target[param] == "undefined") {
10553                                         // is it safe to assign an object reference or should it be cloned ?
10554                                         target[param] = defaults[param];
10555                                 }
10556                                 else if (typeof defaults[param] == "object") {
10557                                         target[param] = _set_defaults(target[param],defaults[param]);
10558                                 }
10559                         }
10560
10561                         return target;
10562                 }
10563
10564                 /**
10565                  @name _platform
10566                  @private
10567                  @returns {string} 'win' or 'mac' or 'other'
10568                  @description identify the operating system
10569                  */
10570                 function _platform(){
10571                         var platform = (navigator.platform || navigator.userAgent);
10572                         return platform.match(/win/i) ? "win" : platform.match(/mac/i) ? "mac" : "other";
10573                 }
10574
10575                 /**
10576                 @name _askFlashPlayerForVersion
10577                 @private
10578                 @function
10579                 @param {Shockwave.Flash} flash_player a reference to an ActiveX Shockwave Flash player object
10580                 @returns version object with structure: {major:n,minor:n,release:n,actual:"string"}
10581                 @description returns flash_player version, as reported by the GetVariable("$version") method
10582                 */
10583                 function _askFlashPlayerForVersion(flash_player){
10584
10585                         var $regexFLASH_VERSION = /^WIN (\d+),(\d+),(\d+),\d+$/;
10586                         var $version = flash_player.GetVariable("$version");
10587                         if ($match = $regexFLASH_VERSION.exec($version)){
10588                                 return {
10589                                         major   : parseInt($match[1]),
10590                                         minor   : parseInt($match[2]),
10591                                         release : parseInt($match[3]),
10592                                         actual  : $version
10593                                 };
10594                         }
10595                         else {
10596                                 // throw an exception, something very strange going on if flash player returns version in any other format ?
10597
10598                         }
10599                 }
10600
10601                 /**
10602                 @name _getFlashPlayerVersion
10603                 @private
10604                 @function
10605                 @returns version object with structure: {major:n,minor:n,release:n,actual:"string"}
10606                 @description returns flash_player version
10607
10608                         Query installed Flash player version, using either ActiveX object creation (for Internet Explorer) or
10609                         navigator plugins collection.
10610
10611                 */
10612                 function _getFlashPlayerVersion(){
10613                         var $match, flash_player, NO_FLASH = {major : 0, minor : 0, release : 0}, result = NO_FLASH;
10614                         
10615                         if (glow.env.ie){
10616                                 try {
10617                                         flash_player = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.7");
10618                                         result = _askFlashPlayerForVersion(flash_player);
10619                                 }
10620                                 catch(e){
10621                                         // Version 6 needs kid-glove treatment as releases 21 thru 29 crash if GetVariable("$version") is called
10622                                         try {
10623                                                 flash_player = new ActiveXObject("ShockwaveFlash.ShockwaveFlash.6");
10624                                                 try {
10625                                                         // This works from release 47 onward...(first public release after 29)
10626                                                         flash_player.AllowScriptAccess = "always";
10627                                                         result = _askFlashPlayerForVersion(flash_player);
10628                                                 }
10629                                                 catch(e){
10630                                                         // we cannot safely test between releases 21...29, so assume the latest and hope for the best...
10631                                                         result = {major:6,minor:0,release:29};
10632                                                 }
10633                                         }
10634                                         catch (e){
10635                                                 // nothing more we can do, either no flash installed, flash player version is ancient or ActiveX is disabled
10636                                         }
10637                                 }
10638                         }
10639                         else {
10640                                 var regexFLASH_VERSION = /^Shockwave Flash\s*(\d+)\.(\d+)\s*\w(\d+)$/;
10641
10642                                 if ((flash_player = navigator.plugins["Shockwave Flash"]) && ($match = regexFLASH_VERSION.exec(flash_player.description))){
10643                                         result = {
10644                                                 major   : parseInt($match[1]),
10645                                                 minor   : parseInt($match[2]),
10646                                                 release : parseInt($match[3]),
10647                                                 actual  : flash_player.description
10648                                         };
10649                                 }
10650                         }
10651
10652                         result.toString = function(){return this.major ? [this.major,this.minor,this.release].join(".") : $i18n.getLocaleModule("GLOW_EMBED").NO_PLAYER_MESSAGE};
10653
10654                         return result;
10655
10656                 }
10657
10658                 /**
10659                  @name installed_flash_player
10660                  @private
10661                  @description version of installed Flash player, initialised at startup.
10662                  */
10663                 var installed_flash_player = _getFlashPlayerVersion();
10664
10665
10666                 /**
10667                 @name _meetsVersionRequirements
10668                 @private
10669                 @function
10670                 @param {string|object} requiredVersion string or version object
10671
10672                         major version must be specified. minor version is optional. release is optional, but both major and
10673                         minor versions must also be supplied if release is specified.
10674                         eg, as string:  "9.0.0",  "9.1",  "9"
10675                         or, as object:  {major:9,minor:0,release:0},  {major:9,minor:0}, {major:9}
10676
10677                 @returns {boolean}.
10678                 @description returns true if installed Flash player version meets requested version requirements
10679
10680                 */
10681                 function _meetsVersionRequirements(requiredVersion){
10682
10683                         if (typeof requiredVersion != "object"){
10684                                 var match = String(requiredVersion).match(/^(\d+)(?:\.(\d+)(?:\.(\d+))?)?$/);
10685                                 if (!match){
10686                                         throw new Error('glow.embed._meetsVersionRequirements: invalid format for version string, require "n.n.n" or "n.n" or simply "n" where n is a numeric value');
10687                                 }
10688
10689                                 requiredVersion = {
10690                                         major   : parseInt(match[1],10),
10691                                         minor   : parseInt(match[2]||0,10),
10692                                         release : parseInt(match[3]||0,10)
10693                                 }
10694                         }
10695
10696                         var v = installed_flash_player, rv = requiredVersion;
10697
10698                         // return true if we meet the minimum version requirement...
10699                         return (v.major > rv.major ||
10700                            (v.major == rv.major && v.minor > rv.minor) ||
10701                            (v.major == rv.major && v.minor == rv.minor && v.release >= rv.release));
10702
10703                 }
10704
10705                 /**
10706                 @name _wrap_embedding_tag
10707                 @private
10708                 @function
10709                 @param {string} src
10710                 @param {object} attributes Hash of attributes to be included
10711                 @param {string} params Hash of params to be included. These will be included as attributes for all but IE
10712                 @returns {string} object or embed tag
10713                 @description returns a complete object or embed tag, as appropriate (ie browser dependent)
10714                 */
10715                 var _wrap_embedding_tag = glow.env.ie ? _wrap_object_tag : _wrap_embed_tag;
10716                 function _wrap_embed_tag(src, attributes, params){
10717                         return '<embed type="application/x-shockwave-flash" src="' + src + '"' + to_attributes(attributes) + to_attributes(params) + '></embed>';
10718                 }
10719                 function _wrap_object_tag(src, attributes, params){
10720                         return '<object classid="clsid:D27CDB6E-AE6D-11cf-96B8-444553540000" ' + to_attributes(attributes) + '><param name="movie" value="' + src + '" />' + toParams(params) + '</object>';
10721                 }
10722
10723                 var r = {},
10724                         idIndex = 0; // Used with private function _getId()
10725
10726                 /**
10727                 @name _getId()
10728                 @private
10729                 @function
10730                 @returns {string} unique string to use for id of a flash object
10731                 @description Returns a unique to the page string to use for the id of the flash object.  This function ensures that all flash objects embedded into the page get an id.
10732                 */
10733                 function _getId() {
10734                         return glow.UID + "FlashEmbed" + (idIndex++);
10735                 }
10736
10737                 /**
10738                 @name glow.embed.Flash
10739                 @class
10740                 @description A wrapper for a Flash movie so it can be embedded into a page
10741                 @see <a href="../furtherinfo/embed/">Flash embedding</a>
10742
10743                 @param {String} src Absolute or relative URL of a Flash movie file to embed
10744                 @param {selector | glow.dom.NodeList} container The element to embed the movie into
10745
10746                         <p>If a CSS selector is provided then the first matching element is used as the
10747                         container.</p>
10748
10749                         <p>If the parameter is a {@link glow.dom.NodeList}, then the first
10750                         element of the list is used as the container.</p>
10751
10752                 @param {String|Object} minVersion The minimum required version of the Flash plugin.
10753
10754                         <p>The Flash plugin has a version numbering scheme comprising of  major, minor and
10755                         release numbers.</p>
10756
10757                         <p>This param can be a string with the major number only, major plus minor numbers, or
10758                         full three-part version number, e.g. "9",  "9.1" , "6.0.55" are all valid values.</p>
10759
10760                         <p>If minVersion is set as an object, it must use a similar structure to the object
10761                         returned by the {@link glow.embed.Flash#version} method, e.g: <code>{major: 9, minor:0,
10762                         release:0}</code>.</p>
10763
10764                 @param {Object} [opts]
10765
10766                         Hash of optional parameters.
10767
10768                         @param {String} [opts.width] Width of the Flash movie. Defaults to "100%"
10769                         @param {String} [opts.height] Height of the Flash movie. Defaults to "100%"
10770                         @param {String} [opts.id] Unique id to be assigned to Flash movie instance.
10771                         @param {String} [opts.className] CSS class to be assigned to the embedding element.
10772                         @param {Object} [opts.attributes] A hash of attributes to assign to the embedded element tag.
10773                         @param {Object} [opts.params] A hash of optional Flash-specific parameters.
10774                                 For example quality, wmode, bgcolor, flashvars.
10775                         @param {String|Function} [opts.message] Error handling message or function.
10776
10777                                 A message to display in the event that the Flash player is either not
10778                                 installed or is an earlier version than the specified minVersion. This message will
10779                                 be written into the container element instead of the Flash movie.
10780
10781                                 If a function is supplied, it will be invoked and any return value will be used
10782                                 as the message to write into the container.
10783
10784                         @example
10785                                 var myFlash = new glow.embed.Flash("/path/to/flash.swf", "#flashContainer", "9");
10786
10787                         @example
10788                                 var myFlash = new glow.embed.Flash("/path/to/flash.swf", "#flashContainer", "9", {
10789                                         width: "400px",
10790                                         height: "300px"
10791                                 });
10792
10793                         @example
10794                                 var myFlash = new glow.embed.Flash("/path/to/flash.swf", "#flashContainer", "9", {
10795                                         width: "400px",
10796                                         height: "300px",
10797                                         params: {
10798                                                 wmode: "transparent",
10799                                                 flashvars: {
10800                                                         navColour: "red",
10801                                                         username: "Frankie"
10802                                                 }
10803                                         }
10804                                 });
10805                  */
10806                 r.Flash = function(src, container, minVersion, opts){
10807
10808                         opts = _set_defaults(opts,{
10809                                         width   : "100%",
10810                                         height  : "100%",
10811                                         params  : {
10812                                                 allowscriptaccess       : "always",
10813                                                 allowfullscreen         : "true",
10814                                                 quality                         : "high"
10815                                         },
10816                                         attributes : {},
10817                                         //expressInstall : false, // TODO add this in a later release
10818                                         message : glow.lang.interpolate($i18n.getLocaleModule("GLOW_EMBED").FLASH_MESSAGE, {min: minVersion, installed: installed_flash_player}),
10819                                         // create a default ID one hasn't been created as an attribute
10820                                         id : (opts && opts.attributes && opts.attributes.id) || _getId() // Fix for trac 165
10821                                 }
10822                         );
10823
10824                         /**
10825                          @name glow.embed.Flash#container
10826                          @type glow.dom.NodeList
10827                          @description The element containing the embedded movie.
10828                          */
10829                         container = glow.dom.get(container);
10830                         if (!container.length){
10831                                 throw new Error("glow.embed.Flash unable to locate container");
10832                         }
10833                         this.container = container;
10834
10835                         //this.expressInstall = opts.expressInstall; // TODO add this in a later release
10836
10837                         /**
10838                          @name glow.embed.Flash#movie
10839                          @type Element
10840                          @description A reference to the actual Flash movie element, for direct script access.
10841                          @example
10842                                 myFlash.movie.exposedFlashMethod();
10843                          */
10844                         this.movie = null;
10845
10846                         this._displayErrorMessage = typeof opts.message == "function" ? opts.message : function(){return opts.message};
10847
10848                         /**
10849                          @name glow.embed.Flash#isSupported
10850                          @type Boolean
10851                          @description Does the user have the correct version of Flash?
10852                                 This will be false if the user has an earler version of Flash
10853                                 installed than this movie requires, or the user doesn't have
10854                                 any version of Flash instaled.
10855                          @example
10856                                 if ( !myFlash.isSupported ) {
10857                                         alert('Please download the latest version of Flash');
10858                                 }
10859                         */
10860                         this.isSupported;
10861
10862                         // Check that the min version requirement is satisfied and store this status, so we don't later try to embed the thing
10863                         // if we don't can't meet the version requirements.
10864
10865                         if (this.isSupported = _meetsVersionRequirements(minVersion)){
10866                                 var attrs = opts.attributes,
10867                                         overwrites = ["id", "width", "height"],
10868                                         i = overwrites.length;
10869
10870                                 // copies stuff like opts.id to attr.id
10871                                 while (i--) {
10872                                         if (opts[overwrites[i]]) { attrs[overwrites[i]] = opts[overwrites[i]]; }
10873                                 }
10874
10875                                 if (opts.className) { attrs["class"] = opts.className; }
10876                                 this._embed_tag = _wrap_embedding_tag(src, attrs, opts.params);
10877                         }
10878                         /*
10879                         else if (this.expressInstall && _meetsVersionRequirements("6.0.65") && _platform().match(/^win|mac$/)) {
10880
10881                                 // Callback to be invokes in case of express install error
10882                                 window[glow.UID + "flashExpressInstallCancelled"] = function(){
10883                                         alert("Flash update cancelled");
10884                                 }
10885                                 window[glow.UID + "flashExpressInstallFailed"] = function(){
10886                                         alert("Unable to complete update, please go to adobe.com...");
10887                                 }
10888                                 window[glow.UID + "flashExpressInstallComplete"] = function(){
10889
10890                                         alert("New version of flash installed");
10891
10892                                 }
10893
10894                                 new glow.embed.Flash("expressInstall.swf",
10895                                         this.container,
10896                                         "6.0.65", {
10897                                         //TODO check minimum width/height
10898                                                 width:opts.width,
10899                                                 height:opts.height,
10900                                                 params : {
10901                                                         flashVars : {
10902                                                                 MMredirectURL :  window.location.toString(),
10903                                                                 MMplayerType : glow.env.ie ? "ActiveX" : "PlugIn",
10904                                                                 MMdoctitle : document.title,
10905                                                                 GlowCallback : glow.UID + "flashExpressInstall"
10906                                                         }
10907                                                 }
10908                                         }
10909                                 ).embed();
10910
10911                                 this.expressInstalling = true;
10912                         }*/
10913                 };
10914
10915                 /**
10916                  @name glow.embed.Flash.version
10917                  @function
10918                  @description Get details of the current users Flash plugin
10919                  @returns An object with details of the currently installed Flash plugin.
10920
10921                         <dl>
10922                                 <dt>major (number)</dt><dd>Flash player major version mumber</dd>
10923                                 <dt>minor (number)</dt><dd>Flash player minor version mumber.</dd>
10924                                 <dt>release (number)</dt><dd>Flash player release version mumber.</dd>
10925                                 <dt>actual (string)</dt><dd>The Flash version exactly as reported by the Flash player.</dd>
10926                                 <dt>toString (function)</dt><dd>toString implementation in the form "major.minor.release" Eg "9.0.2"</dd>
10927                         </dl>
10928
10929                 @example
10930                         var version = glow.embed.Flash.version();
10931                         alert("curr = " + version.major) // "curr = 9"
10932                         alert("curr = " + version) // "curr = 9.0.2"
10933                  */
10934                 r.Flash.version = function(){
10935                         return installed_flash_player;
10936                 };
10937
10938                 /**
10939                  @name glow.embed.Flash#embed
10940                  @function
10941                  @description Embed the Flash movie into the document
10942                  @returns {glow.embed.Flash}
10943
10944                  @example
10945                         var myFlash = new glow.embed.Flash(...);
10946                         myFlash.embed();
10947                 */
10948                 r.Flash.prototype.embed = function(){
10949                         var containerElm = this.container[0];
10950                         if (this.isSupported){
10951
10952                                 containerElm.innerHTML = this._embed_tag;
10953
10954                                 this.movie = containerElm.firstChild;
10955
10956                         }/*
10957                         else if (this.expressInstalling){
10958                                 // wait for expressInstall to complete
10959
10960                         }*/
10961                         else {
10962                                 var message = this._displayErrorMessage();
10963                                 if (message){
10964                                         containerElm.innerHTML = message;
10965                                 }
10966                         }
10967
10968                         return this;
10969
10970                 };
10971
10972                 glow.embed = r;
10973
10974         }
10975 });
10976 /**
10977 @name glow.dragdrop
10978 @namespace
10979 @description Simplifying drag and drop behaviour
10980 */
10981 (window.gloader || glow).module({
10982         name: "glow.dragdrop",
10983         library: ["glow", "1.7.0"],
10984         depends: [["glow", "1.7.0", "glow.tweens", "glow.events", "glow.dom", "glow.anim"]],
10985         builder: function(glow) {
10986                 var events                 = glow.events,
10987                         addListener        = events.addListener,
10988                         fire               = events.fire,
10989                         removeListener = events.removeListener,
10990                         dom                        = glow.dom,
10991                         $                          = dom.get,
10992                         create             = dom.create;
10993
10994                 //public
10995                 var r = {},
10996                         _zIndex = 1000,
10997                         _ieStrict = (document.compatMode == "CSS1Compat" && glow.env.ie >= 5) ? true : false,
10998                         _ieTrans= (document.compatMode != "CSS1Compat" && glow.env.ie >= 5) ? true : false,
10999                         _ie = glow.env.ie >= 5,
11000                         sides = ['top', 'right', 'bottom', 'left'];
11001
11002                 /*
11003                 PrivateFunction: memoize(clss, name)
11004
11005                 Replace a method with a version that caches the result after the first run.
11006
11007                 Arguments:
11008
11009                         *clss* (function)
11010
11011                         The class whose method is being memoized.
11012
11013                         *name*
11014
11015                         The name of the method to memoize.
11016                 */
11017
11018                 function memoize (clss, name) {
11019                         var orig = clss.prototype[name];
11020                         var cachedName = 'cached_' + name;
11021                         clss.prototype[name] = function () {
11022                                 if (cachedName in this) return this[cachedName];
11023                                 return this[cachedName] = orig.apply(this, arguments);
11024                         };
11025                 }
11026                 
11027                 /*
11028                  Copy margins from one element to another
11029                 */
11030                 function copyMargins(to, from) {
11031                         var i = sides.length, margin;
11032                         
11033                         while (i--) {
11034                                 margin = 'margin-' + sides[i];
11035                                 to.css(margin, from.css(margin));
11036                         }
11037                 }
11038
11039                 /*
11040                 PrivateFunction: memoizeNamed(clss, methodName)
11041
11042                 Replace a method that takes a name with a version that caches the result for each name after the first run.
11043
11044                 Arguments:
11045
11046                         *clss* (function)
11047
11048                         The class whose method is being memoized.
11049
11050                         *methodName*
11051
11052                         The name of the method to memoize.
11053                 */
11054
11055                 function memoizeNamed (clss, methodName) {
11056                         var orig = clss.prototype[methodName];
11057                         var cachedName = 'cached_' + methodName;
11058                         clss.prototype[methodName] = function (name) {
11059                                 if (! this[cachedName]) this[cachedName] = {};
11060                                 if (name in this[cachedName]) return this[cachedName][name];
11061                                 return this[cachedName][name] = orig.apply(this, arguments);
11062                         };
11063                 }
11064
11065                 /*
11066                 PrivateFunction: reset(obj, names)
11067
11068                 Remove cached values for a set of memoized methods.
11069
11070                 Arguments:
11071
11072                         *obj* (object)
11073
11074                         The object containing cached values.
11075
11076                         *names* (array of strings)
11077
11078                         The names of methods whose values have been cached.
11079                 */
11080
11081                 function reset (obj, names) {
11082                         for (var i = 0, l = names.length; i < l; i++) {
11083                                 delete obj['cached_' + names[i]];
11084                         }
11085                 }
11086
11087                 /*
11088                 PrivateFunction: resetNamed(obj, meth, names)
11089
11090                 Remove cached values for a set of named properties for a method.
11091
11092                 Arguments:
11093
11094                         *obj* (object)
11095
11096                         The object containing cached values.
11097
11098                         *meth* (string)
11099
11100                         The name of the method whose values have been cached.
11101
11102                         *names* (array of strings)
11103
11104                         The names of the cached properties.
11105
11106                 function resetNamed (obj, meth, names) {
11107                         var cache = obj['cached_' + meth];
11108                         if (! cache) return;
11109                         for (var i = 0, l = names.length; i < l; i++) {
11110                                 delete cache[names[i]];
11111                         }
11112                 }
11113                 */
11114
11115                 /*
11116                 PrivateClass: Box
11117
11118                 Calculates and caches information about an element in the box model.
11119
11120                 Constructor:
11121
11122                          (code)
11123                          new Box(el)
11124                          (end)
11125
11126                 Arguments:
11127
11128                         *el* (glow.dom.NodeList)
11129
11130                         The element that calculations will be performed on.
11131                 */
11132
11133                 var Box = function (el) {
11134                         this.el = el;
11135                 };
11136
11137                 Box.prototype = {
11138
11139                         /*
11140                         PrivateMethod: val
11141
11142                         Get an pixel value for a CSS style.
11143
11144                         Arguments:
11145
11146                                 *style* (string)
11147
11148                                 The name of a CSS style (e.g. "margin-top".
11149
11150                         Returns:
11151                                 An integer number of pixels.
11152                         */
11153
11154                         val: function (style) {
11155                                 var val = parseInt(this.el.css(style));
11156                                 // TODO - fix dom so margin-left return value is always defined?
11157 //                              if (isNaN(val)) throw 'got NaN in val for ' + style + ': ' + this.el.css(style);
11158                                 return val || 0;
11159 //                              return val;
11160                         },
11161
11162                         /*
11163                         PrivateMethod: width
11164
11165                         Get the width of the element.
11166
11167                         Returns:
11168                                 An integer number of pixels.
11169                         */
11170
11171                         width: function () {
11172                                 return this.borderWidth()
11173                                          - this.val('border-left-width')
11174                                          - this.val('border-right-width');
11175                         },
11176
11177                         /*
11178                         PrivateMethod: height
11179
11180                         Get the height of the element.
11181
11182                         Returns:
11183                                 An integer number of pixels.
11184                         */
11185
11186                         height: function () {
11187                                 return this.borderHeight()
11188                                          - this.val('border-top-width')
11189                                          - this.val('border-bottom-width');
11190                         },
11191
11192                         /*
11193                         PrivateMethod: offsetParentPageTop
11194
11195                         Get the number of pixels from the top of nearest element with absolute, relative or fixed position to the
11196                         top of the page.
11197
11198                         Returns:
11199                                 An integer number of pixels.
11200                         */
11201                         offsetParentPageTop: function () {
11202                                 var el = this.el[0], pos, top;
11203                                 while (el = el.offsetParent) {
11204                                         if ( $(el).css('position') != 'static' ) {
11205                                                 break;
11206                                         }
11207                                 }
11208                                 return el ?
11209                                         $(el).offset().top :
11210                                         0;
11211                         },
11212
11213                         /*
11214                         PrivateMethod: offsetTop
11215
11216                         This gets what CSS 'top' would be if the element were position "absolute"
11217
11218                         Returns:
11219                                 An integer number of pixels.
11220                         */
11221                         offsetTop: function () {
11222                                 return this.el.position().top;
11223                         },
11224
11225                         /*
11226                         PrivateMethod: offsetLeft
11227
11228                         This gets what CSS 'left' would be if the element were position "absolute"
11229
11230                         Returns:
11231                                 An integer number of pixels.
11232                         */
11233                         offsetLeft: function () {
11234                                 return this.el.position().left;
11235                         },
11236
11237                         /*
11238                         PrivateMethod: borderWidth
11239
11240                         Get the width of the element from the left edge of the left border to the right
11241                         edge of the right border.
11242
11243                         Returns:
11244                                 An integer number of pixels.
11245                         */
11246
11247                         borderWidth: function () {
11248                                 var width = this.el[0].offsetWidth;
11249                                 if (glow.env.khtml) {
11250                                         width -= this.val('margin-left')
11251                                                         + this.val('margin-right')
11252                                                         + this.val('border-left-width')
11253                                                         + this.val('border-right-width');
11254                                 }
11255                                 return width;
11256                         },
11257
11258                         /*
11259                         PrivateMethod: borderHeight
11260
11261                         Get the height of an element from the top edge of the top border to the bottom
11262                         edge of the bottom border.
11263
11264                         Returns:
11265                                 An integer number of pixels.
11266                         */
11267
11268                         borderHeight: function () {
11269                                 if (this._logicalBottom) {
11270                                         return this._logicalBottom - this.offsetTop();
11271                                 }
11272                                 var height = this.el[0].offsetHeight;
11273                                 if (glow.env.khtml) {
11274                                         height -= this.val('margin-top')
11275                                                         + this.val('margin-bottom')
11276                                                         + this.val('border-top-width')
11277                                                         + this.val('border-bottom-width');
11278                                 }
11279                                 return height;
11280                         },
11281
11282
11283                         /*
11284                         PrivateMethod: outerWidth
11285
11286                         Get the width of the element in including margin, borders and padding.
11287
11288                         Returns:
11289                                 An integer number of pixels.
11290                         */
11291
11292                         outerWidth: function () {
11293                                 return this.borderWidth() + this.val('margin-left') + this.val('margin-right');
11294                         },
11295
11296                         /*
11297                         PrivateMethod: outerHeight
11298
11299                         Get the height of the element in including margin, borders and padding. This
11300                         does not take account of collapsable margins (i.e. it assumes the margins are
11301                         present).
11302
11303                         Returns:
11304                                 An integer number of pixels.
11305                         */
11306
11307                         outerHeight: function () {
11308                                 return this.borderHeight() + this.val('margin-top') + this.val('margin-bottom');
11309                         },
11310
11311                         /*
11312                         PrivateMethod: innerLeftPos
11313
11314                         Get the offset of the left edge of the content of the box (i.e. excluding
11315                         margin, border and padding).
11316
11317                         Returns:
11318                                 An integer number of pixels.
11319                         */
11320
11321                         innerLeftPos: function () {
11322                                 return this.offsetLeft()
11323                                          + this.val('margin-left')
11324                                          + this.val('border-left-width')
11325                                          + this.val('padding-left');
11326                         },
11327
11328                         /*
11329                         PrivateMethod: innerTopPos
11330
11331                         Get the offset of the top edge of the content of the box (i.e. excluding
11332                         margin, border and padding).
11333
11334                         Returns:
11335                                 An integer number of pixels.
11336                         */
11337
11338                         innerTopPos: function () {
11339                                 return this.offsetTop()
11340                                          + this.val('margin-top')
11341                                          + this.val('border-top-width')
11342                                          + this.val('padding-top');
11343                         },
11344
11345                         /*
11346                         PrivateMethod: surroundWidth
11347
11348                         Get the combined width of the horizontal margins, borders and paddings.
11349
11350                         Returns:
11351                                 An integer number of pixels.
11352                         */
11353
11354                         surroundWidth: function () {
11355                                 return this.val('border-left-width')
11356                                          + this.val('padding-left')
11357                                          + this.val('padding-right')
11358                                          + this.val('border-right-width');
11359                         },
11360
11361                         /*
11362                         PrivateMethod: surroundHeight
11363
11364                         Get the combined height of the horizontal margins, borders and paddings.
11365
11366                         Returns:
11367                                 An integer number of pixels.
11368                         */
11369
11370                         surroundHeight: function () {
11371                                 return this.val('border-top-width')
11372                                          + this.val('padding-top')
11373                                          + this.val('padding-bottom')
11374                                          + this.val('border-bottom-width');
11375                         },
11376
11377                         /*
11378                         PrivateMethod: verticalCenter
11379
11380                         Get the vertical offset of the center of the element from it's offset parent.
11381
11382                         Returns:
11383                                 An integer number of pixels.
11384                         */
11385
11386                         verticalCenter: function () {
11387                                 return this.offsetTop() + (this.outerHeight() / 2);
11388                         },
11389
11390                         /*
11391                         PrivateMethod: verticalCenter
11392
11393                         Get the vertical offset of the center of the element from it's offset parent.
11394
11395                         Returns:
11396                                 An integer number of pixels.
11397                         */
11398
11399                         horizontalCenter: function () {
11400                                 return this.offsetTop() + (this.outerWidth() / 2);
11401                         }
11402
11403                 };
11404
11405                 for (var i in Box.prototype) {
11406                         if (i == 'val') memoizeNamed(Box, i);
11407                         else memoize(Box, i);
11408                 }
11409
11410                 glow.lang.apply(Box.prototype, {
11411
11412                         /*
11413                         PrivateMethod: resetPosition
11414
11415                         Reset cached position values for the element.
11416                         */
11417
11418                         resetPosition: function () {
11419                                 reset(this, [
11420                                         'offsetTop',
11421                                         'offsetLeft',
11422                                         'borderTopPos',
11423                                         'borderLeftPos',
11424                                         'innerTopPos',
11425                                         'innerLeftPos',
11426                                         'verticalCenter',
11427                                         'horizontalCenter'
11428                                 ]);
11429                         },
11430
11431                         /*
11432                         PrivateMethod: setLogicalBottom
11433
11434                         Set the logical value for the position of the bottom of the border (offsetTop + offsetHeight).
11435
11436                         Arguments:
11437
11438                                 *bottom* (integer)
11439
11440                                 The value to use for the bottom of the box.
11441                         */
11442                         setLogicalBottom: function (bottom) {
11443                                 this._logicalBottom = bottom;
11444                         },
11445
11446                         /*
11447                         PrivateMethod: boundsFor
11448
11449                         Get the the bounds for the left and top css properties of a child box to
11450                         ensure that it stays within this element.
11451
11452                         Arguments:
11453
11454                                 *childBox* (Box)
11455
11456                                 A Box object representing the space taken up by the child element.
11457
11458                         Returns:
11459                                 An array of top, right, bottom and left pixel bounds for the top and left
11460                                 css properties of the child element.
11461                         */
11462
11463                         boundsFor: function (childBox) {
11464                                 var top, left, pos = this.el.css('position');
11465                                 if (pos != 'static') {
11466                                     top = left = 0;
11467                                 }
11468                                 else {
11469                                         top = this.innerTopPos();
11470                                         left = this.innerLeftPos();
11471                                 }
11472                                 return [
11473                                         top,                                                                               // top
11474                                         left + this.width() - childBox.outerWidth(),   // right
11475                                         top  + this.height() - childBox.outerHeight(), // bottom
11476                                         left                                                                               // left
11477                                 ];
11478                         },
11479
11480                         /*
11481                         PrivateMethod: outerBounds
11482
11483                         Get the top, right, bottom and left offsets of the outside edge of the border
11484                         of the box.
11485
11486                         Returns:
11487                                 An array of integer pixel offsets for the top, right, bottom, left edges of the
11488                                 boxes border.
11489                         */
11490
11491                         outerBounds: function () {
11492                                 var offset = this.el.offset(),
11493                                         left = offset.left,
11494                                         top = offset.top;
11495                                 return [
11496                                         top,
11497                                         left + this.borderWidth(),
11498                                         top + this.borderHeight(),
11499                                         left
11500                                 ];
11501                         },
11502
11503                         /*
11504                         PrivateMethod: intersectSize
11505
11506                         Get the intersection of this box with another box.
11507
11508                         Arguments:
11509
11510                                 *that* (Box)
11511
11512                                 A Box object to test for intersection with this box.
11513
11514                                 *touches* (boolean)
11515
11516                                 If true, then the boxes don't have to intersect but can merely touch.
11517
11518                         Returns:
11519                                 An integer number of square pixels that the the outside of the
11520                                 edge of the border of this box intersects with that of the passed
11521                                 in box.
11522                         */
11523
11524                         intersectSize: function (that, touches) {
11525                                 var a = this.outerBounds(), b = that.outerBounds();
11526                                 
11527                                 if (touches) {
11528                                         a[1]++; b[1]++; a[2]++; b[2]++;
11529                                 }
11530                                 return (
11531                                         a[2] < b[0] ? 0 :
11532                                         b[2] < a[0] ? 0 :
11533                                         a[0] < b[0] ? (a[2] < b[2] ? a[2] - b[0] : b[2] - b[0]) :
11534                                         b[2] < a[2] ? b[2] - a[0] : a[2] - a[0]
11535                                 ) * (
11536                                         a[1] < b[3] ? 0 :
11537                                         b[1] < a[3] ? 0 :
11538                                         a[3] < b[3] ? (a[1] < b[1] ? a[1] - b[3] : b[1] - b[3]) :
11539                                         b[1] < a[1] ? b[1] - a[3] : a[1] - a[3]
11540                                 );
11541                         },
11542
11543                         /*
11544                         PrivateMethod: sizePlaceholder
11545
11546                         Size and position a placeholder/drop indicator element to match that
11547                         of the element.
11548
11549                         Arguments:
11550
11551                                 *placeholder* (glow.dom.NodeList)
11552
11553                                 The element that will be sized.
11554
11555                                 *pos* (optional string)
11556
11557                                 The value for the placeholder's CSS position. Defaults to the position
11558                                 of this element.
11559
11560                                 *startLeft* (integer)
11561
11562                                 The original left position of the element.
11563
11564                                 *startTop* (integer)
11565
11566                                 The original top position of the element.
11567                         */
11568
11569                         sizePlaceholder: function (placeholder, pos, startLeft, startTop) {
11570                                 var placeholderBox = new Box(placeholder),
11571                                         el = this.el,
11572                                         position = pos || el.css('position');
11573                                 
11574                                 placeholder.css('display', 'none');
11575                                 
11576                                 el.after(placeholder);
11577                                 
11578                                 placeholder.css('width', (el[0].offsetWidth - placeholderBox.surroundWidth()) + 'px')
11579                                         .css('height', (el[0].offsetHeight - placeholderBox.surroundHeight()) + 'px');
11580                                 
11581                                 // copy margin values
11582                                 copyMargins(placeholder, el);
11583                                 
11584                                 placeholder.remove();
11585                                 
11586                                 placeholder.css('display', 'block');
11587                                 
11588                                 if (position != 'static') {
11589                                         placeholder.css('left', startLeft + 'px');
11590                                         placeholder.css('top', startTop + 'px');
11591                                 }
11592                                 placeholder.css('position', position);
11593                         },
11594
11595                         /*
11596                         PrivateMethod: contains
11597
11598                         Check if a box is contained within this box.
11599
11600                         Arguments:
11601
11602                                 *box* (Box)
11603
11604                                 The box to test.
11605
11606                         Returns:
11607                                 Boolean, true if contained.
11608                         */
11609
11610                         contains: function (box) {
11611                                 var bounds = this.boundsFor(box),
11612                                         position = box.el.position(),
11613                                         top = position.top,
11614                                         left = position.left;
11615                                 
11616                                 return top  >= bounds[0]  // top
11617                                         && left <= bounds[1]  // right
11618                                         && top  <= bounds[2]  // bottom
11619                                         && left >= bounds[3]; // left
11620                         },
11621
11622                         /*
11623                         PrivateMethod: containsPoint
11624
11625                         Arguments:
11626
11627                                 *offset* (object)
11628
11629                                 The offset to check - an object containing x and y integer pixel values.
11630
11631                         Returns:
11632                                 Boolean, true if the point over the visible part of the element (i.e. including the borders).
11633                         */
11634
11635                         containsPoint: function (offset) {
11636                                 var elementOffset = this.el.offset();
11637                                 return offset.x >= elementOffset.left
11638                                         && offset.y >= elementOffset.top
11639                                         && offset.x <= elementOffset.left + this.borderWidth()
11640                                         && offset.y <= elementOffset.top  + this.borderHeight();
11641                         },
11642
11643                         /*
11644                         PrivateMethod: positionedAncestorBox
11645
11646                         Get a new Box for nearest ancestor of the element that has position 'absolute', 'fixed' or 'relative'.
11647
11648                         Returns:
11649                                 An integer pixel offset.
11650                         */
11651
11652                         positionedAncestorBox: function () {
11653                                 var el = this.el.parent(), pos;
11654                                 while (el[0]) {
11655                                         pos = el.css('position') || 'static';
11656                                         if (pos == 'relative' || pos == 'absolute' || pos == 'fixed')
11657                                                 return new Box(el);
11658                                         el = el.parent();
11659                                 }
11660                                 return null;
11661                         }
11662                 });
11663
11664                 function placeholderElement (el) {
11665                         var tag = el[0].tagName.toLowerCase() == 'li' ? 'li' : 'div';
11666                         var placeholder = create('<' + tag + '></' + tag + '>');
11667                         if (tag == 'li') placeholder.css('list-style-type', 'none');
11668                         return placeholder;
11669                 }
11670
11671                 /**
11672                 @name glow.dragdrop.Draggable
11673                 @class
11674                 @description An element that can be dragged using the mouse.
11675                 @see <a href="../furtherinfo/dragdrop/draggables.shtml">Draggable examples</a>
11676
11677                 @param {String | Element | glow.dom.NodeList} element The element or CSS selector for an element to be made draggable.
11678
11679                         If a {@link glow.dom.NodeList NodeList} or CSS selector matching
11680                         multiple elements is passed only the first element is made draggable.
11681
11682                 @param {Object} [opts]
11683
11684                         An object of options.
11685
11686                         The opts object allows you to pass in functions to use as event
11687                         listeners. This is purely for convenience, you can also use
11688                         {@link glow.events.addListener} to add them the normal way.
11689
11690                 @param {String} [opts.placeholder=spacer] Defines what to leave in place of the draggable whilst being dragged.
11691
11692                         Possible values for this param are:
11693
11694                         <dl>
11695                         <dt>spacer</dt><dd>an empty div is created where the draggable started.</dd>
11696                         <dt>clone</dt><dd>an exact clone of the original element.</dd>
11697                         <dt>none</dt><dd>no placeholder will be created.</dd>
11698                         </dl>
11699
11700                 @param {String} [opts.placeholderClass=glow-dragdrop-placeholder] A class be applied to the placeholder element.
11701
11702                         This can be used to to add styling for indicating where the element
11703                         has been dragged from, add opacity, etc.
11704
11705                 @param {Selector | Element | glow.dom.NodeList} [opts.handle] Restrict the drag 'handle' to an element within the draggable.
11706
11707                 @param {Selector | Element | glow.dom.NodeList} [opts.container] Constrain dragging to within the bounds of the specified element.
11708
11709                 @param {Array} [opts.dropTargets] An array of {@link glow.dragdrop.DropTarget DropTargets}.
11710
11711                         Specifies which {@link glow.dragdrop.DropTarget DropTargets} this draggable is associated with.
11712
11713                 @param {String} [opts.axis] Restrict dragging to an axis.
11714
11715                         Possible values for this param are:
11716
11717                         <dl>
11718                         <dt>x</dt><dd>Restricts dragging to the x-axis</dd>
11719                         <dt>y</dt><dd>Restricts dragging to the y-axis</dd>
11720                         </dl>
11721
11722                 @param {String[]} [opts.dragPrevention=input, textarea, button, select, option, a] Disables dragging from the specified array of element names
11723
11724                         By default dragging will not work when the user clicks in form
11725                         elements, otherwise these elements would be unusable.
11726                         
11727                 @param {Number|Object} [opts.step=1] The pixel interval the draggable snaps to.
11728                         If a number, the draggable will step by that number of pixels on the x and y axis. You
11729                         can provide an object in the form <code>{x:2, y:4}</code> to set different steps to each
11730                         axis.
11731
11732                 @param {Function} [opts.onDrag] An event listener that fires when the draggable starts being dragged.
11733
11734                 @param {Function} [opts.onEnter] An event listener that fires when the draggable is dragged over a drop target.
11735
11736                 @param {Function} [opts.onLeave] An event listener that fires when the draggable is dragged out of a drop target.
11737
11738                 @param {Function} [opts.onDrop] An event listener that fires when the draggable is dropped.
11739                 
11740                 @param {Function} [opts.onAfterDrop] An event listener that fires after the element has dropped, including any animations
11741
11742                         The default action is to animate the draggable back to it's start
11743                         position. This can be cancelled by returning false from the listener
11744                         or calling {@link glow.events.Event.preventDefault} on the
11745                         {@link glow.events.Event} param.
11746                         
11747                 @example
11748                         // create a draggable element with a corresponding DropTarget,
11749                         // container and two event listeners
11750                         var myDraggable = new glow.dragdrop.Draggable('#draggable', {
11751                                 dropTargets : [ myDropTarget ],
11752                                 container : '#container',
11753                                 onDrag : function () {
11754                                         this.element.css('opacity', '0.7');
11755                                 },
11756                                 onDrop : function () {
11757                                         this.element.css('opacity', '1');
11758                                 }
11759                         });
11760         */
11761         /**
11762                 @name glow.dragdrop.Draggable#event:drag
11763                 @event
11764                 @description Fired when the draggable starts being dragged.
11765
11766                         Concelling this event results in the user being unable to pick up
11767                         the draggable.
11768                 
11769                 @param {glow.events.Event} event Event Object
11770         */
11771         /**
11772                 @name glow.dragdrop.Draggable#event:enter
11773                 @event
11774                 @description Fired when the draggable is dragged over a drop target.
11775                 @param {glow.events.Event} event Event Object
11776         */
11777         /**
11778                 @name glow.dragdrop.Draggable#event:leave
11779                 @event
11780                 @description Fired when the draggable is dragged out of a drop target.
11781                 @param {glow.events.Event} event Event Object
11782         */
11783         /**
11784                 @name glow.dragdrop.Draggable#event:drop
11785                 @event
11786                 @description Fired when the draggable is dropped.
11787                 @param {glow.events.Event} event Event Object
11788         */
11789         /**
11790                 @name glow.dragdrop.Draggable#event:afterDrop
11791                 @event
11792                 @description Fired after the element has dropped, including any animations
11793                 @param {glow.events.Event} event Event Object
11794         */
11795                 r.Draggable = function (el, opts) {
11796
11797                         /**
11798                         @name glow.dragdrop.Draggable#element
11799                         @type glow.dom.NodeList
11800                         @description glow.dom.NodeList containing the draggable element
11801                         */
11802                         this.element = $(el);
11803                         this._opts = opts = glow.lang.apply({
11804                                 dragPrevention   : ['input', 'textarea', 'button', 'select', 'option', 'a'],
11805                                 placeholder          : 'spacer',
11806                                 placeholderClass : 'glow-dragdrop-placeholder',
11807                                 step                     : {x:1, y:1}
11808                         }, opts || {});
11809                         
11810                         //normalise the step param to an object
11811                         if (typeof opts.step == "number") {
11812                                 opts.step = {x: opts.step, y: opts.step};
11813                         } else {
11814                                 opts.step.x = opts.step.x || 1;
11815                                 opts.step.y = opts.step.y || 1;
11816                         }
11817
11818                         this._preventDrag = [];
11819                         for (var i = 0, l = opts.dragPrevention.length; i < l; i++) {
11820                                 this._preventDrag[i] = opts.dragPrevention[i].toLowerCase();
11821                         }
11822
11823                         if (opts.container) { this.container = $(opts.container); }
11824                         this._handle = opts.handle && this.element.get(opts.handle) || this.element;
11825
11826                         if (opts.dropTargets) this.dropTargets = $(opts.dropTargets);
11827
11828                                 //used for IE literal edge case bug fix
11829                                 //this._mouseUp = true;
11830                                 //bug fix to get document.body.scrollTop to return true value (not 0) if using transitional 4.01 doctype
11831                                 //get('body')[0].style.overflow = 'auto';
11832                                 //this._opts = o, this._targetCoords = [], this.isOverTarget = false;
11833
11834                         var listeners = this._listeners = [],
11835                                 i = 0;
11836
11837                         if (opts.onDrag)  listeners[i++] = addListener(this, 'drag',  this._opts.onDrag,  this);
11838                         if (opts.onEnter) listeners[i++] = addListener(this, 'enter', this._opts.onEnter, this);
11839                         if (opts.onLeave) listeners[i++] = addListener(this, 'leave', this._opts.onLeave, this);
11840                         if (opts.onDrop)  listeners[i++] = addListener(this, 'drop',  this._opts.onDrop,  this);
11841
11842                         this._dragListener = addListener(this._handle, 'mousedown', this._startDragMouse, this);
11843
11844                         return;
11845                 };
11846
11847
11848                 /*
11849                 Group: Methods
11850                 */
11851
11852                 //var applyFloatBugfix = glow.env.ie;
11853
11854                 r.Draggable.prototype = {
11855
11856                         /*
11857                         PrivateMethod: _createPlaceholder
11858
11859                         Create an element that occupies the space where the draggable has been dragged from.
11860                         */
11861
11862                         _createPlaceholder: function () {
11863                                 var el = this.element,
11864                                         placeholder,
11865                                         box = this._box;
11866
11867                                 if (this._opts.placeholder == 'clone') {
11868                                         placeholder = el.clone();
11869                                 }
11870                                 else { // placeholder == 'spacer'
11871                                         placeholder = placeholderElement(el);
11872                                 }
11873                                 if (this._opts.placeholderClass) {
11874                                         placeholder.addClass(this._opts.placeholderClass);
11875                                 }
11876                                 box.sizePlaceholder(placeholder, null, this._startLeft, this._startTop);
11877                                 el.after(placeholder);
11878                                 this._placeholder = placeholder;
11879                         },
11880
11881                         /*
11882                         PrivateMethod: _removePlaceholder
11883
11884                         Removes the placeholder (see above) from the document.
11885                         */
11886
11887                         _removePlaceholder: function () {
11888                                 this._placeholder.remove();
11889                         },
11890
11891                         /*
11892                         PrivateMethod: _resetPosition
11893
11894                         Sets the position CSS property to what it started as without moving the draggable. If the
11895                         original position was 'static' and making it 'static' again would mean moving the draggable,
11896                         then the position is set to 'relative'.
11897                         */
11898
11899                         _resetPosition: function () {
11900                                 var origPos = this._preDragPosition,
11901                                         el = this.element,
11902                                         box = this._box,
11903                                         startOffset = this._startOffset,
11904                                         pos = el.css('position'),
11905                                         newLeft,
11906                                         newTop;
11907                                 
11908                                 box.resetPosition();
11909                                 
11910                                 var position = box.el.position(),
11911                                         offset = {
11912                                                 x: position.left,
11913                                                 y: position.top
11914                                         };
11915
11916                                 if (this._placeholder || this._dropIndicator) {
11917                                         el.remove();
11918                                 }
11919                                 
11920                                 if (origPos == 'static' && offset.y == startOffset.y && offset.x == startOffset.x) {
11921                                         el.css('position', 'static');
11922                                         el.css('left', '');
11923                                         el.css('top', '');
11924                                 }
11925                                 else {
11926                                         el.css('z-index', this._preDragZIndex);
11927                                         el.css('position', origPos == 'static' ? 'relative' : origPos);
11928                                         if (origPos == 'static') {
11929                                                 newLeft = offset.x - startOffset.x;
11930                                                 newTop = offset.y - startOffset.y;
11931                                         }
11932                                         else if (origPos == 'relative' && pos != 'relative') {
11933                                                 newLeft = this._startLeft + (offset.x - startOffset.x);
11934                                                 newTop = this._startTop + (offset.y - startOffset.y);
11935                                         }
11936                                         if (pos != origPos) {
11937                                                 el.css('left', newLeft ? newLeft + 'px' : '');
11938                                                 el.css('top', newTop ? newTop + 'px' : '');
11939                                         }
11940                                 }
11941                                 if (this._dropIndicator) {
11942                                         var parent = this._dropIndicator.parent()[0];
11943                                         if (parent)     parent.replaceChild(el[0], this._dropIndicator[0]);
11944                                         delete this._dropIndicator;
11945                                         if (this._placeholder) {
11946                                                 this._placeholder.remove();
11947                                                 delete this._placeholder;
11948                                         }
11949                                         // this is canceling out some of the stuff done in the if statement above, could be done better
11950                                         el.css('position', origPos);
11951                                         if (origPos == 'relative' && pos != 'relative') {
11952                                                 el.css('left', this._startLeft);
11953                                                 el.css('top', this._startTop);
11954                                         }
11955                                 }
11956                                 else if (this._placeholder) {
11957                                         var parent = this._placeholder.parent()[0];
11958                                         if (parent)     parent.replaceChild(el[0], this._placeholder[0]);
11959                                         delete this._placeholder;
11960                                 }
11961                         },
11962
11963                         /*
11964                         PrivateFunction: _startDragMouse
11965
11966                         Start the draggable dragging when the mousedown event is fired.
11967
11968                         Arguments:
11969
11970                                 *e* (glow.events.Event)
11971
11972                                 The mousedown event that caused the listener to be fired.
11973                         */
11974
11975                         _startDragMouse: function (e) {
11976                                 var preventDrag = this._preventDrag,
11977                                         source = e.source,
11978                                         tag = source.tagName.toLowerCase();
11979
11980                                 for (var i = 0, l = preventDrag.length; i < l; i++) {
11981                                         if (preventDrag[i] == tag) {
11982                                                 return;
11983                                         }
11984                                 }
11985                                 
11986                                 //fire the drag event
11987                                 if (fire(this, 'drag').defaultPrevented()) {
11988                                         //the default action was prevented, don't do any dragging
11989                                         return;
11990                                 }
11991
11992                                 if (this._dragging == 1)
11993                                         return this.endDrag();
11994                                 else if (this._dragging)
11995                                         return;
11996
11997                                 // _dragging set to 1 during drag, 2 while ending drag and back to 0 when ready for new drag
11998                                 this._dragging = 1;
11999
12000                                 var el = this.element,
12001                                         container = this.container,
12002                                         opts = this._opts,
12003                                         box = this._box = new Box(el),
12004                                         step = opts.step;
12005
12006                                 this._preDragPosition = el.css('position');
12007                                 
12008                                 var position = box.el.position(), 
12009                                         startOffset = this._startOffset = {
12010                                                 x: position.left,
12011                                                 y: position.top
12012                                         };
12013                                 
12014                                 if (container) {
12015                                         this._containerBox = new Box(container);
12016                                         this._bounds = this._containerBox.boundsFor(box);
12017                                         //we may need to decrease the bounds to keep the element in step (if it's using stepped dragging)
12018                                         //basically makes the bounding box smaller to fit in with the stepping
12019                                         if (step.x != 1) {
12020                                                 this._bounds[3] -= (this._bounds[3] - startOffset.x) % step.x;
12021                                                 this._bounds[1] -= (this._bounds[1] - startOffset.x) % step.x;
12022                                         }
12023                                         if (step.y != 1) {
12024                                                 this._bounds[0] -= (this._bounds[0] - startOffset.y) % step.y;
12025                                                 this._bounds[2] -= (this._bounds[2] - startOffset.y) % step.y;
12026                                         }
12027                                 }
12028                                 else {
12029                                         delete this._bounds;
12030                                 }
12031
12032                                 this._mouseStart = {
12033                                         x: e.pageX,
12034                                         y: e.pageY
12035                                 };
12036
12037                                 
12038
12039                                 this._preDragStyle = el.attr('style');
12040
12041                                 this._preDragZIndex = el.css('z-index');
12042
12043                                 el.css('z-index', _zIndex++);
12044
12045                                 this._startLeft = el[0].style.left ? parseInt(el[0].style.left) : 0;
12046                                 this._startTop = el[0].style.top ? parseInt(el[0].style.top) : 0;
12047
12048
12049                                 if (opts.placeholder && opts.placeholder != 'none') {
12050                                         this._createPlaceholder();
12051                                 }
12052
12053                                 el.css('position', 'absolute');
12054                                 el.css('left', startOffset.x + 'px');
12055                                 el.css('top', startOffset.y + 'px');
12056
12057                                 if(_ieStrict) {
12058                                         this._scrollY = document.documentElement.scrollTop;
12059                                         this._innerHeight = document.documentElement.clientHeight;
12060                                 }
12061                                 else if(_ieTrans){
12062                                         this._scrollY = document.body.scrollTop;
12063                                         this._innerHeight = document.body.clientHeight;
12064                                 }
12065                                 else {
12066                                         this._scrollY = window.scrollY;
12067                                         this._innerHeight = window.innerHeight;
12068                                 }
12069
12070                                 var cancelFunc = function () { return false },
12071                                         doc = document.documentElement;
12072
12073                                 if (this.dropTargets) {
12074                                         var event = new events.Event();
12075                                         event.draggable = this;
12076                                         for (var i = 0, l = this.dropTargets.length; i < l; i++) {
12077                                                 fire(this.dropTargets[i], 'active', event);
12078                                         }
12079
12080                                         this._mousePos = {
12081                                                 x: e.pageX,
12082                                                 y: e.pageY
12083                                         };
12084                                         this._testForDropTargets();
12085                                 }
12086
12087                                 this._dragListeners = [
12088                                         addListener(doc, 'selectstart', cancelFunc),
12089                                         addListener(doc, 'dragstart',   cancelFunc),
12090                                         addListener(doc, 'mousedown',   cancelFunc),
12091                                         addListener(doc, 'mousemove',   this._dragMouse, this),
12092                                         addListener(doc, 'mouseup',      this._releaseElement, this)
12093                                 ];
12094                                 return false;
12095                         },
12096
12097                         /*
12098                         PrivateFunction: _dragMouse
12099
12100                         Move the draggable when a mousemove event is received.
12101
12102                         Arguments:
12103
12104                                 *e* (glow.events.Event)
12105
12106                                 The mousedown event that caused the listener to be fired.
12107                         */
12108
12109                         _dragMouse: function (e) {
12110                                 var element = this.element,
12111                                         axis = this._opts.axis,
12112                                         //do we need to do axis here, or just not apply the newX/Y if axis is used? May be faster
12113                                         newX = axis == 'y' ?
12114                                                 this._startOffset.x:
12115                                                 (this._startOffset.x + e.pageX - this._mouseStart.x),
12116                                         newY = axis == 'x' ?
12117                                                 this._startOffset.y:
12118                                                 (this._startOffset.y + e.pageY - this._mouseStart.y),
12119                                         bounds = this._bounds,
12120                                         step = this._opts.step;
12121                                 
12122                                 
12123                                 //round position to the nearest step
12124                                 if (step.x != 1) {
12125                                         newX = Math.round((newX - this._startOffset.x) / step.x) * step.x + this._startOffset.x;
12126                                 }
12127                                 if (step.y != 1) {
12128                                         newY = Math.round((newY - this._startOffset.y) / step.y) * step.y + this._startOffset.y;
12129                                 }
12130                                 
12131                                 // only pay for the function call if we have a container or an axis
12132                                 if (bounds) {
12133                                         // only apply bounds on the axis we're using
12134                                         if (axis != 'y') {
12135                                                 newX = newX < bounds[3] ? bounds[3] : newX > bounds[1] ? bounds[1] : newX;
12136                                         }
12137                                         if (axis != 'x') {
12138                                                 newY = newY < bounds[0] ? bounds[0] : newY > bounds[2] ? bounds[2] : newY;
12139                                         }
12140                                 }
12141                                 
12142                                 // set the new position
12143                                 element[0].style.left = newX + 'px';
12144                                 element[0].style.top = newY + 'px';
12145
12146                                 //if there are dragTargets check if the draggable is over the target
12147                                 if (this.dropTargets) {
12148                                         this._mousePos = { x: e.pageX, y: e.pageY };
12149                                 }
12150                                 // check for IE mouseup outside of page boundary
12151                                 if(_ie && e.nativeEvent.button == 0) {
12152                                         this._releaseElement(e);
12153                                         return false;
12154                                 };
12155                                 return false;
12156                         },
12157
12158                         /*
12159                         PrivateFunction: _testForDropTarget
12160
12161                         Check if the draggable is over a drop target. Sets the activeTarget property of the draggable
12162                         to the drop target that the draggable is over, if any.
12163
12164                         Arguments:
12165
12166                                 *mousePos* (object)
12167
12168                                 The position of the mouse pointer relative to the document. The object has x and y integer
12169                                 pixel properties.
12170                         */
12171                         _testForDropTargets: function (fromTimeout) {
12172
12173                                 if (! this._lock) this._lock = 0;
12174                                 if (fromTimeout) this._lock--;
12175                                 else if (this.lock) return;
12176
12177                                 if (this._dragging != 1) return;
12178
12179                                 var previousTarget = this.activeTarget,
12180                                         activeTarget,
12181                                         targets = this.dropTargets,
12182                                         target,
12183                                         targetBox,
12184                                         box = this._box,
12185                                         mousePos = this._mousePos;
12186
12187                                 box.resetPosition();
12188
12189                                 var maxIntersectSize = 0;
12190                                 for (var i = 0, l = targets.length; i < l; i++) {
12191                                         target = targets[i];
12192                                         targetBox = target._box;
12193                                         if (target._opts.tolerance == 'contained') {
12194                                                 if (targetBox.contains(box)) {
12195                                                         activeTarget = target;
12196                                                         break;
12197                                                 }
12198                                         }
12199                                         else if (target._opts.tolerance == 'cursor') {
12200                                                 if (targetBox.containsPoint(mousePos)) {
12201                                                         activeTarget = target;
12202                                                         break;
12203                                                 }
12204                                         }
12205                                         else {
12206                                                 var intersectSize = targetBox.intersectSize(box, true);
12207                                                 if (intersectSize > maxIntersectSize) {
12208                                                         maxIntersectSize = intersectSize;
12209                                                         activeTarget = target;
12210                                                 }
12211                                         }
12212                                 }
12213                                 this.activeTarget = activeTarget;
12214
12215                                 // enter events
12216                                 if (activeTarget !== previousTarget) {
12217                                         if (activeTarget) {
12218                                                 // enter on the target
12219                                                 var draggableEnterEvent = new events.Event();
12220                                                 draggableEnterEvent.draggable = this;
12221                                                 fire(activeTarget, 'enter', draggableEnterEvent);
12222
12223                                                 // enter on this (the draggable)
12224                                                 var enterTargetEvent = new events.Event();
12225                                                 enterTargetEvent.dropTarget = activeTarget;
12226                                                 fire(this, 'enter', enterTargetEvent);
12227                                         }
12228
12229                                         if (previousTarget) {
12230                                                 // leave on target
12231                                                 var draggableLeaveEvent = new events.Event();
12232                                                 draggableLeaveEvent.draggable = this;
12233                                                 fire(previousTarget, 'leave', draggableLeaveEvent);
12234
12235                                                 // leave on this (draggable)
12236                                                 var leaveTargetEvent = new events.Event();
12237                                                 leaveTargetEvent.dropTarget = previousTarget;
12238                                                 fire(this, 'leave', leaveTargetEvent);
12239                                         }
12240                                 }
12241                                 // place the drop indicator in the drop target (not in the drop target class for speed)
12242                                 if (activeTarget && activeTarget._opts.dropIndicator != 'none') {
12243                                         var childBox,
12244                                                 childBoxes = activeTarget._childBoxes,
12245                                                 children = activeTarget._children;
12246                                         box.resetPosition();
12247                                         var totalHeight = activeTarget._box.innerTopPos();
12248                                         var draggablePosition = mousePos.y - box.offsetParentPageTop();
12249                                         var placed = 0;
12250                                         for (var i = 0, l = childBoxes.length; i < l; i++) {
12251                                                 if (children[i] == this.element[0]) continue;
12252                                                 childBox = childBoxes[i];
12253                                                 totalHeight += childBox.outerHeight();
12254                                                 if (draggablePosition <= totalHeight) {
12255                                                         if (activeTarget._dropIndicatorAt != i) {
12256                                                                 $(childBox.el).before(activeTarget._dropIndicator);
12257                                                                 activeTarget._dropIndicatorAt = i;
12258                                                         }
12259                                                         placed = 1;
12260                                                         break;
12261                                                 }
12262                                         }
12263                                         if (! placed) {
12264                                                 if (childBox) {
12265                                                         $(childBox.el).after(activeTarget._dropIndicator);
12266                                                         activeTarget._dropIndicatorAt = i + 1;
12267                                                 }
12268                                                 else {
12269                                                         activeTarget.element.append(activeTarget._dropIndicator);
12270                                                         activeTarget._dropIndicatorAt = 0;
12271                                                 }
12272                                         }
12273                                 }
12274
12275                                 this._lock++;
12276                                 var this_ = this;
12277                                 setTimeout(function () { this_._testForDropTargets(1) }, 100);
12278                         },
12279
12280                         /*
12281                         PrivateMethod: releaseElement
12282
12283                         Finish the drag when a mouseup event is recieved.
12284
12285                         Arguments:
12286
12287                                 *e* (glow.events.Event)
12288
12289                                 The mouseup event that caused the listener to be fired.
12290                         */
12291
12292                         _releaseElement: function () {
12293                                 if (this._dragging != 1) return;
12294                                 this._dragging = 2;
12295
12296                                 var i, l;
12297
12298                                 //call the onInactive function on all the dropTargets for this draggable
12299                                 var dropTargets = this.dropTargets,
12300                                         activeTarget = this.activeTarget;
12301
12302                                 if (dropTargets) {
12303                                         for (i = 0, l = dropTargets.length; i < l; i++) {
12304                                                 var event = new events.Event();
12305                                                 event.draggable = this;
12306                                                 event.droppedOnThis = activeTarget && activeTarget == dropTargets[i];
12307                                                 fire(dropTargets[i], 'inactive', event);
12308                                         }
12309                                 }
12310
12311                                 if (activeTarget) {
12312                                         var event = new events.Event();
12313                                         event.draggable = this;
12314                                         fire(activeTarget, 'drop', event);
12315                                 }
12316
12317                                 var dragListeners = this._dragListeners;
12318                                 for (i = 0, l = dragListeners.length; i < l; i++) {
12319                                         events.removeListener(dragListeners[i]);
12320                                 }
12321
12322                                 var dropEvent = fire(this, "drop");
12323                                 if (! dropEvent.defaultPrevented() && this.dropTargets) {
12324                                         this.returnHome();
12325                                 }
12326                                 else {
12327                                         this.endDrag();
12328                                 }
12329
12330                         },
12331
12332                         /*
12333                         Method: endDrag
12334
12335                         Finishes dragging the draggable. Removes the placeholder (if any) and resets the position CSS property
12336                         of the draggable.
12337
12338                         TODO - revist this code example
12339
12340                         N.B. This is called by default but if you overwrite the onDrop function then you will have to call it youoriginal
12341                         (code)
12342                         // empty NodeList
12343                         var myDraggable = new glow.dragdrop.Draggable('#draggable', {
12344                                 onDrop = function(e){
12345                                         do some stuff that takes a while.....
12346                                         this.endDrag();
12347                                         false;
12348                                 }
12349                         });
12350                         (end)
12351                         */
12352
12353                         endDrag: function(){
12354                                 if (this._dragging != 2) return;
12355                                 this._dragging = 0;
12356
12357                                 //remove any helpers/placeholders
12358                                 
12359                                 if (this._reset) {
12360                                         this._reset();
12361                                         delete this._reset;
12362                                 }
12363                                 
12364                                 if (this.placeholder) {
12365                                         this.placeholder.remove();
12366                                 }
12367                                 this._resetPosition();
12368                                 delete this.activeTarget;
12369                                 fire(this, "afterDrop");
12370                         },
12371
12372                         /*
12373                         Event: returnHome
12374
12375                         Animates the Draggable back to it's start position and calls endDrag() at the end of the
12376                         transition. This is called by default when the Draggable, that has a DragTarget, is dropped.
12377                         However if you override the default onDrop function you may want to call this function your
12378                         original
12379
12380                         Arguments
12381                                 tween (function)
12382                                 The animation you wish to used for easing the Draggable. See <glow.tweens>. This is optional, the default is a linear tween. e.g. glow.tweens.easeboth
12383                         */
12384
12385                         returnHome: function(tween){
12386                                 var mytween = (tween) ? tween : glow.tweens.linear(),
12387                                         leftDestination,
12388                                         topDestination,
12389                                         el = this.element,
12390                                         position = this._box.el.position(),
12391                                         distance = Math.pow(
12392                                                 Math.pow(this._startOffset.x - position.left, 2)
12393                                                 + Math.pow(this._startOffset.y - position.top, 2),
12394                                                 0.5
12395                                         ),
12396                                         duration = 0.3 + (distance / 1000);
12397                                 
12398                                 var channels = [[
12399                                         glow.anim.css(el, duration, {
12400                                                 left: this._startOffset.x,
12401                                                 top : this._startOffset.y
12402                                         }, { tween: mytween })
12403                                 ]];
12404
12405                                 if (this._dropIndicator) {
12406                                         channels.push([glow.anim.css(this._dropIndicator, duration - 0.1, { opacity: { to: 0 } })]);
12407                                 }
12408
12409                                 var timeline = new glow.anim.Timeline(channels);
12410                                 addListener(timeline, 'complete', function () {
12411                                         this.endDrag();
12412                                 }, this);
12413                                 timeline.start();
12414                                 return;
12415                         }
12416                 };
12417
12418
12419                 var dropTargetId = 0;
12420
12421                 /**
12422                 @name glow.dragdrop.DropTarget
12423                 @class
12424                 @description An element that can react to Draggables.
12425                 @see <a href="../furtherinfo/dragdrop/droptargets.shtml">DropTarget examples</a>
12426
12427                 @param {String | Element | glow.dom.NodeList} element The element or CSS selector for an element to be made droppable.
12428
12429                         If a {@link glow.dom.NodeList NodeList} or CSS selector matching
12430                         multiple elements is passed only the first element is made droppable.
12431
12432                 @param {Object} [opts]
12433
12434                         An object of options.
12435
12436                         The opts object allows you to pass in functions to use as event
12437                         listeners. This is purely for convenience, you can also use
12438                         {@link glow.events.addListener} to add them the normal way.
12439
12440                 @param {String} [opts.tolerance=intersect] The point at which the target becomes active when a draggable moves over it.
12441
12442                         Possible values for this param are:
12443
12444                         <dl>
12445                         <dt>intersect</dt><dd>The target becomes active as soon as any part of the draggable is over the target.</dd>
12446                         <dt>cursor</dt><dd>The target becomes active when the cursor is over the target.</dd>
12447                         <dt>contained</dt><dd>The target only becomes active once the whole draggable is within the target.</dd>
12448                         </dl>
12449
12450                 @param {String} [opts.dropIndicator=none] Whether to create an element when a Draggable is over the DropTarget.
12451
12452                         Possible values for this param are:
12453
12454                         <dl>
12455                         <dt>spacer</dt><dd>an empty div will be added to the drop target to indicate where the Draggable will be dropped.</dd>
12456                         <dt>none</dt><dd>no drop indicator will be created.</dd>
12457                         </dl>
12458
12459                 @param {String} [opts.dropIndicatorClass=glow-dragdrop-dropindicator] The class apply to the dropIndicator element.
12460
12461                         This is useful if you want to style the drop indicator.
12462
12463                 @param {Function} [opts.onEnter] An event listener to fire when an associated Draggable is dragged over the drop target.
12464
12465                 @param {Function} [opts.onLeave] An event listener to fire when an associated Draggable is dragged out of the drop target.
12466
12467                 @param {Function} [opts.onDrop] An event listener to fire when an associated Draggable is dropped on the drop target.
12468
12469                 @param {Function} [opts.onActive] An event listener to fire when an associated Draggable starts being dragged.
12470
12471                 @param {Function} [opts.onInactive] An event listener to fire when an associated Draggable stops being dragged.
12472                 
12473                 @example
12474                         var myDropTarget = new glow.dragdrop.DropTarget('#dropTarget', {
12475                                 onActive: function(e){
12476                                                 this.element.css('border', '2px solid blue');
12477                                 },
12478                                 onInactive: function(e){
12479                                                 this.element.css('border', '');
12480                                                 this.element.css('opacity', '1');
12481                                 },
12482                                 onEnter: function(e){
12483                                                 this.element.css('opacity', '0.2');
12484                                 },
12485                                 onLeave: function(e){
12486                                                 this.element.css('opacity', '1');
12487                                 },
12488                                 onDrop: function(e){
12489                                                 this.element.css('backgroundColor', 'green');
12490                                 }
12491                         });
12492                 */
12493                 /**
12494                 @name glow.dragdrop.DropTarget#event:active
12495                 @event
12496                 @description Fired when a draggable linked to this drop target starts being dragged.
12497                 @param {glow.events.Event} event Event Object
12498                 */
12499                 /**
12500                 @name glow.dragdrop.DropTarget#event:inactive
12501                 @event
12502                 @description Fired when a draggable linked to this drop target stops dragging.
12503                 @param {glow.events.Event} event Event Object
12504                 */
12505                 /**
12506                 @name glow.dragdrop.DropTarget#event:enter
12507                 @event
12508                 @description Fired when a draggable linked to this drop target is dragged over the target.
12509                 @param {glow.events.Event} event Event Object
12510                 */
12511                 /**
12512                 @name glow.dragdrop.DropTarget#event:leave
12513                 @event
12514                 @description Fired when a draggable linked to this drop target is dragged out of the target.
12515                 @param {glow.events.Event} event Event Object
12516                 */
12517                 /**
12518                 @name glow.dragdrop.DropTarget#event:drop
12519                 @event
12520                 @description Fired when a draggable linked is dropped on this drop target.
12521                 @param {glow.events.Event} event Event Object
12522                 */
12523                 r.DropTarget = function(el, opts) {
12524                         /**
12525                         @name glow.dragdrop.DropTarget#element
12526                         @type glow.dom.NodeList
12527                         @description glow.dom.NodeList containing the draggable element
12528                         */
12529                         el = this.element = $(el);
12530                         if (! el.length) throw 'no element passed into DropTarget constuctor';
12531                         if (el.length > 1) throw 'more than one element passed into DropTarget constructor';
12532
12533                         // id is for indexing drop targets in an object for getting to them quickly
12534                         this._id = ++dropTargetId;
12535
12536                         this._opts = opts = glow.lang.apply({
12537                                 dropIndicator      : 'none',
12538                                 dropIndicatorClass : 'glow-dragdrop-dropindicator',
12539                                 tolerance          : 'intersect'
12540                         }, opts || {});
12541
12542                         if (opts.onActive)   addListener(this, 'active',   opts.onActive);
12543                         if (opts.onInactive) addListener(this, 'inactive', opts.onInactive);
12544                         if (opts.onEnter)        addListener(this, 'enter',     opts.onEnter);
12545                         if (opts.onLeave)        addListener(this, 'leave',     opts.onLeave);
12546                         if (opts.onDrop)         addListener(this, 'drop',       opts.onDrop);
12547
12548                         addListener(this, 'active', this._onActive);
12549                         addListener(this, 'inactive', this._onInactive);
12550
12551                         return this;
12552                 };
12553
12554
12555                 r.DropTarget.prototype = {
12556
12557                         /*
12558                         Method: setLogicalBottom(height)
12559
12560                         Set a bottom pos to use for detecting if a draggable is over the drop target to use
12561                         other than the actual bottom of the drop target (offsetTop + offsetHeight).
12562
12563                         Arguments:
12564
12565                                 *bottom* (integer)
12566
12567                                 The number of pixels to use for the bottom of the drop target.
12568                         */
12569                         setLogicalBottom: function (bottom) {
12570                                 this._logicalBottom = bottom;
12571                         },
12572
12573                         /*
12574                         PrivateMethod: _onActive
12575
12576                         Respond to an associated draggable when it starts to be dragged.
12577
12578                         Arguments:
12579
12580                                 *e* (glow.events.Event)
12581
12582                                 The active event that caused the event listener to be fired.
12583                         */
12584
12585                         _onActive: function (e) {
12586                                 var draggable = e.draggable;
12587
12588                                 this._box = new Box(this.element);
12589                                 if (this._logicalBottom) this._box.setLogicalBottom(this._logicalBottom);
12590
12591                                 if (this._opts.dropIndicator == 'none') return;
12592
12593                                 this._onEnterListener = addListener(this, 'enter', this._onEnter);
12594                                 this._onLeaveListener = addListener(this, 'leave', this._onLeave);
12595
12596                                 this._dropIndicator = placeholderElement(draggable.element);
12597
12598                                 if (this._opts.dropIndicatorClass) {
12599                                         this._dropIndicator.addClass(this._opts.dropIndicatorClass);
12600                                 }
12601                                 draggable._box.sizePlaceholder(this._dropIndicator, 'relative', 0, 0);
12602
12603
12604                                 var children = this._children = $(this.element.children()).filter(function () {
12605                                         var el = $(this);
12606                                         return (! e.draggable._placeholder || ! el.eq(e.draggable._placeholder))
12607                                                 && (! this._dropIndicator || ! el.eq(this._dropIndicator));
12608                                 });
12609                                 var childBoxes = this._childBoxes = [];
12610                                 children.each(function (i) {
12611                                         childBoxes[i] = new Box($(children[i]));
12612                                 });
12613                         },
12614
12615                         /*
12616                         PrivateMethod: _onInactive
12617
12618                         Respond to an associated draggable when it finishes being dragged.
12619
12620                         Arguments:
12621
12622                                 *e* (glow.events.Event)
12623
12624                                 The inactive event that caused the event listener to be fired.
12625                         */
12626
12627                         _onInactive: function (e) {
12628                                 removeListener(this._onEnterListener);
12629                                 removeListener(this._onLeaveListener);
12630
12631                                 delete this._box;
12632
12633                                 if (this._opts.dropIndicator == 'none') return;
12634
12635                                 if (! e.droppedOnThis && this._dropIndicator) {
12636                                         this._dropIndicator.remove();
12637                                         delete this._dropIndicator;
12638                                 }
12639                                 delete this._childBoxes;
12640                                 delete this._children;
12641                         },
12642
12643                         /*
12644                         PrivateMethod: _onEnter
12645
12646                         Respond to an associated draggable being dragged over the drop target.
12647
12648                         Arguments:
12649
12650                                 *e* (glow.events.Event)
12651
12652                                 The enter event that caused the event listener to be fired.
12653                         */
12654
12655                         _onEnter: function () {
12656                                 this._dropIndicatorAt = -1;
12657                         },
12658
12659                         /*
12660                         PrivateMethod: _onLeave
12661
12662                         Respond to an associated draggable being dragged out of the drop target.
12663
12664                         Arguments:
12665
12666                                 *e* (glow.events.Event)
12667
12668                                 The leave event that caused the event listener to be fired.
12669                         */
12670
12671                         _onLeave: function () {
12672                                 this._dropIndicator.remove();
12673                         },
12674
12675                         /*
12676                         Method: moveToPosition
12677
12678                         Insert the draggable's element within the drop target where the drop indicator currently is. Sets
12679                         the start offset of the drag to the position of the drop indicator so that it will be animated
12680                         to it's final location, rather than where the drag started.
12681                         */
12682
12683                         moveToPosition : function (draggable) {
12684                                 var dropIndicator = this._dropIndicator,
12685                                         box = new Box(dropIndicator);
12686                                 //dropIndicator.after(draggable.element);
12687                                 var marginLeft = parseInt(dropIndicator.css('margin-left')) || 0,
12688                                         marginTop  = parseInt(dropIndicator.css('margin-top')) || 0,
12689                                         position = box.el.position();
12690                                         
12691                                 draggable._startOffset = {
12692                                         x: position.left,
12693                                         y: position.top
12694                                 };
12695                                 draggable._dropIndicator = dropIndicator;
12696                                 delete this._dropIndicator;
12697                         }
12698
12699                 }
12700                 glow.dragdrop = r;
12701
12702
12703         }
12704 });
12705 /*@end @*/