1 /*
  2  * Copyright (C) 2008, 2009 Mihai Şucan
  3  *
  4  * This file is part of libmacrame.
  5  *
  6  * Libmacrame is free software: you can redistribute it and/or modify
  7  * it under the terms of the GNU General Public License as published by
  8  * the Free Software Foundation, either version 3 of the License, or
  9  * (at your option) any later version.
 10  *
 11  * Libmacrame is distributed in the hope that it will be useful,
 12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
 13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 14  * GNU General Public License for more details.
 15  *
 16  * You should have received a copy of the GNU General Public License
 17  * along with Libmacrame.  If not, see <http://www.gnu.org/licenses/>.
 18  *
 19  * $URL: http://code.google.com/p/libmacrame $
 20  * $Date: 2009-04-21 13:37:06 +0300 $
 21  *
 22  */
 23 
 24 /**
 25  * @author <a lang="ro" href="http://www.robodesign.ro/mihai">Mihai Şucan</a>
 26  * @version pre-alpha
 27  * @requires core.js
 28  * @requires core-js.js
 29  *
 30  * @fileOverview This is a plugin for libmacrame which adds important methods 
 31  * for manipulation the DOM.
 32  */
 33 
 34 
 35 (function () {
 36 // We will use $ to refer to libmacrame in this plugin.
 37 var $ = libmacrame;
 38 
 39 /*
 40  * If extend_objects is set to true:
 41  * the script will add during runtime new methods and properties to the DOM 
 42  * objects: Node, Element, Text, NodeList, HTMLCollection, NamedNodeMap, 
 43  * HTMLElement, HTMLLinkElement, HTMLAnchorElement, HTMLAreaElement, Event, 
 44  * EventTarget, KeyboardEvent.
 45  *
 46  * Note that some of the implementations of the new methods rely on the fact 
 47  * that this setting is set to true.
 48  */
 49 var extend_objects = true;
 50 
 51 // We store all the DOM-related methods in $.dom, for quick reuse in any case 
 52 // where they are needed.
 53 
 54 /**
 55  * Holds the DOM-related methods.
 56  *
 57  * <p>This plugin extends the global DOM objects, like Node, Element and 
 58  * NodeList.
 59  *
 60  * <p>The {@link $.dom.NodeList} methods are also used for extending the 
 61  * HTMLCollection object prototype, since the two interfaces serve similar 
 62  * purpose. However, due to <a 
 63  * href="https://bugzilla.mozilla.org/show_bug.cgi?id=300519">bug 300519</a>, in 
 64  * Gecko any changes to the HTMLCollection.prototype do not yield the expected 
 65  * results. As such, a work-around needs to be implemented. (TODO)
 66  *
 67  * <p>The new methods implement common functionality needed when working with 
 68  * the DOM.
 69  *
 70  * @namespace Holds the DOM-related methods.
 71  */
 72 $.dom = {};
 73 
 74 /**
 75  * Holds the DOM Node methods.
 76  *
 77  * <p>By default, the global <var>Node.prototype</var> object is extended to 
 78  * contain all the methods defined in this namespace.
 79  *
 80  * <p>All of these methods use their <var>this</var> object. As such, you need 
 81  * to pass the correct <var>this</var> object.
 82  *
 83  * <p>In the examples provided for each method we will assume that the objects 
 84  * defined are already extended. Thus code like 
 85  * <code><var>node</var>.prependChild(<var>child</var>)</code> can be written 
 86  * directly. Such code can be read as "add the node <var>child</var> as a new 
 87  * child to the <var>node</var> node, making sure it's the first child".
 88  *
 89  * @namespace Holds the DOM Node methods.
 90  *
 91  * @see $.dom
 92  * @see $.dom.Element
 93  * @see $.dom.NodeList
 94  */
 95 $.dom.Node = {
 96   /**
 97    * Insert a new node as the first child to the current node.
 98    *
 99    * @example
100    * <code>var <var>elem</var> = document.createElement('div');
101    * // Insert the element as the first child to the document.body.
102    * document.body.prependChild(<var>elem</var>);</code>
103    *
104    * @param {Node} child The node to prepend as a child.
105    *
106    * @throws {TypeError} If <var>child</var> is not an instance of Node.
107    *
108    * @returns {Node} The prepended child node.
109    *
110    * @see $.dom.NodeList.prependChild
111    * @see $.dom.NodeList.appendChild
112    */
113   prependChild : function (child) {
114     if (!(child instanceof Node)) {
115       throw new TypeError('The first argument must be a DOM node.');
116     }
117 
118     return this.insertBefore(child, this.firstChild);
119   },
120 
121   /**
122    * Remove the first child from the current node.
123    *
124    * @example
125    * // Remove the first child from the document.body.
126    * <code>document.body.removeFirstChild();</code>
127    *
128    * @returns {Node|null} The removed child node, or null if nothing was 
129    * removed.
130    *
131    * @see $.dom.Node.removeLastChild
132    * @see $.dom.NodeList.removeFirstChild
133    * @see $.dom.NodeList.removeLastChild
134    */
135   removeFirstChild : function () {
136     return this.firstChild ? this.removeChild(this.firstChild) : null;
137   },
138 
139   /**
140    * Remove the last child from the current node.
141    *
142    * @example
143    * // Remove the last child from the document.body.
144    * <code>document.body.removeLastChild();</code>
145    *
146    * @returns {Node|null} The removed child node, or null if nothing was 
147    * removed.
148    *
149    * @see $.dom.Node.removeFirstChild
150    * @see $.dom.NodeList.removeFirstChild
151    * @see $.dom.NodeList.removeLastChild
152    */
153   removeLastChild : function () {
154     return this.lastChild ? this.removeChild(this.lastChild) : null;
155   },
156 
157   /**
158    * Append the current node to the <var>target</var> node.
159    *
160    * <p>The current node is added to the list of children of the 
161    * <var>target</var> node, becoming the last child.
162    *
163    * @example
164    * <code>var <var>elem</var> = document.createElement('div');
165    * // Add the element to the body.
166    * <var>elem</var>.appendTo(document.body);</code>
167    *
168    * @param {Node} target The target node where to append the current node.
169    *
170    * @throws {TypeError} If <var>target</var> is not an instance of Node.
171    *
172    * @returns {Node} The node that was appended.
173    *
174    * @see $.dom.Node.prependTo
175    * @see $.dom.NodeList.prependTo
176    * @see $.dom.NodeList.appendTo
177    */
178   appendTo : function (target) {
179     if (!(target instanceof Node)) {
180       throw new TypeError('The first argument must be a DOM node.');
181     }
182 
183     return target.appendChild(this);
184   },
185 
186   /**
187    * Prepend the current node to the <var>target</var> node.
188    *
189    * <p>The current node is added to the list of children of the 
190    * <var>target</var> node, becoming the first child.
191    *
192    * @example
193    * <code>var <var>elem</var> = document.createElement('div');
194    * // Add the element to the body.
195    * <var>elem</var>.prependTo(document.body);</code>
196    *
197    * @param {Node} target The target node where to prepend the current node.
198    *
199    * @throws {TypeError} If <var>target</var> is not an instance of Node.
200    *
201    * @returns {Node} The node that was prepended.
202    *
203    * @see $.dom.Node.appendTo
204    * @see $.dom.NodeList.prependTo
205    * @see $.dom.NodeList.appendTo
206    */
207   prependTo : function (target) {
208     if (!(target instanceof Node)) {
209       throw new TypeError('The first argument must be a DOM node.');
210     }
211 
212     return target.insertBefore(this, target.firstChild);
213   },
214 
215   /**
216    * Insert the specified node after a reference node to the current node.
217    * 
218    * <p>The <var>new_node</var> will be added as a child of the current node, 
219    * after the <var>ref_node</var>.
220    *
221    * <p>This method works like the native <code>node.insertBefore()</code> 
222    * method, which is actually used in the implementation.
223    *
224    * @example
225    * <code>var <var>elem</var> = document.createElement('div'),
226    *     <var>ref</var> = $('#foo');
227    * document.body.insertAfter(<var>elem</var>, <var>ref</var>);</code>
228    *
229    * @param {Node} new_node The new node to insert as a child.
230    * @param {Node} ref_node The reference child node after which you want to 
231    * insert the given new child node. If this argument is null, the new node is 
232    * simply appended as a child, becoming the last in the list of children.
233    *
234    * @throws {TypeError} If <var>new_node</var> is not an instance of Node.
235    *
236    * @returns {Node} The node that was inserted.
237    *
238    * @see $.dom.NodeList.insertAfter
239    * @see $.dom.NodeList.insertBefore
240    */
241   insertAfter : function (new_node, ref_node) {
242     if (!(new_node instanceof Node)) {
243       throw new TypeError('The first argument must be a DOM node.');
244     }
245 
246     var before = ref_node ? ref_node.nextSibling : null;
247     return this.insertBefore(new_node, before);
248   }
249 };
250 
251 /**
252  * Holds the DOM Element methods.
253  *
254  * <p>By default, the global <var>Element.prototype</var> object is extended to 
255  * contain all the methods defined in this namespace.
256  *
257  * <p>The global <var>Text.prototype</var> and <var>Comment.prototype</var> DOM 
258  * objects are also extended. However, these two prototypes are only extended to 
259  * contain the new {@link $.dom.Element.wrap} method.
260  *
261  * <p>All of these methods use their <var>this</var> object. As such, you need 
262  * to pass the correct <var>this</var> object.
263  *
264  * <p>In the examples provided for each method we will assume that the objects 
265  * defined are already extended. Thus code like 
266  * <code><var>element</var>.wrap('<p>')</code> can be written directly.
267  *
268  * @namespace Holds the DOM Element methods.
269  *
270  * @see $.dom.Node
271  * @see $.dom.NodeList
272  */
273 $.dom.Element = {
274   /**
275    * Wrap the element inside a given wrapper.
276    *
277    * @example
278    * <code>var <var>elem</var> = document.createElement('p');
279    *
280    * document.body.appendChild(<var>elem</var>);</code>
281    *
282    * // Now the body would serialize to something like:
283    * // <body><p></p></body>
284    *
285    * <code>var <var>result</var> = <var>elem</var>.wrap('<div>');</code>
286    *
287    * // Now <var>result</var> = the <div> node.
288    * // The body would serialize to something like:
289    * // <body><div><p></p></div></body>
290    *
291    * @example
292    * <code>var <var>elem</var> = document.createElement('p');
293    *
294    * document.body.appendChild(<var>elem</var>);</code>
295    *
296    * // Now the body would serialize to something like:
297    * // <body><p></p></body>
298    *
299    * <code>var <var>wrapper</var> = '<div id="test1">' +
300    *               '<div id="test2">';
301    *
302    * var <var>result</var> = <var>elem</var>.wrap(<var>wrapper</var>);</code>
303    *
304    * // Now <var>result</var> = the #test1 node.
305    * // The body would serialize to something like:
306    * // <body><div id="test1"><div id="test2"><p>
307    * // </p></div></div></body>
308    *
309    * @example
310    * <code>var <var>elem</var> = document.createElement('p');
311    *
312    * document.body.appendChild(<var>elem</var>);</code>
313    *
314    * // Now the body would serialize to something like:
315    * // <body><p></p></body>
316    *
317    * <code>var <var>wrapper1</var> = document.createElement('div'),
318    *     <var>wrapper2</var> = document.createElement('div');
319    *
320    * <var>wrapper1</var>.id = 'test1';
321    * <var>wrapper1</var>.appendChild(<var>wrapper2</var>);
322    * <var>wrapper2</var>.id = 'test2';
323    *
324    * var <var>result</var> = <var>elem</var>.wrap(<var>wrapper1</var>);</code>
325    *
326    * // Now <code><var>result</var> = <var>wrapper1</var></code>.
327    * // The body would serialize to something like:
328    * // <body><div id="test1"><div id="test2"><p>
329    * // </p></div></div></body>
330    *
331    * @param {Element|String} wrapper The element or the string to use for 
332    * wrapping the current element.
333    *
334    * <p>If the wrapper is an element, then it is be used as-is for wrapping.
335    *
336    * <p>If the wrapper is a string, it is be parsed as HTML code. Then, the 
337    * firstElementChild of the DOM tree generated by the string becomes the 
338    * wrapper.
339    *
340    * <p>For both cases the determined wrapper gets added to the parent node of 
341    * the current element (the <var>this</var> object), if it's available.
342    *
343    * <p>The deepest child element of the wrapper is searched, so that the 
344    * current element will be appended into it.
345    *
346    * @throws {TypeError} If the current node is not an element/text/comment 
347    * node. This method cannot wrap other kinds of objects.
348    *
349    * @throws {ReferenceError} If the wrapper is a string and the current element 
350    * has no owner document.
351    *
352    * @throws {ReferenceError} If the wrapper is a string with no elements in it.
353    *
354    * @returns {Element} The wrapper element.
355    *
356    * @see $.dom.Element.wrapInner
357    */
358   wrap : function (wrapper) {
359     if (this.nodeType != Node.ELEMENT_NODE && this.nodeType != 
360         Node.TEXT_NODE && this.nodeType != Node.COMMENT_NODE) {
361       throw new TypeError('This node must be an DOM element/text/comment node.');
362     }
363 
364     var elem;
365 
366     if (typeof wrapper == 'string') {
367       var doc = this.ownerDocument;
368       if (doc.nodeType != Node.DOCUMENT_NODE) {
369         throw new ReferenceError('This node does not have an owner document. You cannot wrap it into a string.');
370       }
371 
372       var div = doc.createElement('div');
373       div.innerHTML = wrapper;
374       elem = div.firstElementChild;
375       if (!elem) {
376         throw new ReferenceError('The provided string does not contain any element tag.');
377       }
378 
379     } else if (wrapper.nodeType == Node.ELEMENT_NODE) {
380       elem = wrapper;
381     } else {
382       throw new TypeError('The first argument must be a DOM element or a string.');
383     }
384 
385     if (this.parentNode) {
386       this.parentNode.insertBefore(elem, this);
387     }
388 
389     // Find the deepest child element.
390     var first = elem;
391     while (first.firstElementChild) {
392       first = first.firstElementChild;
393     }
394 
395     first.appendChild(this);
396 
397     return elem;
398   },
399 
400   /**
401    * Wrap all the child nodes of the current element.
402    *
403    * <p>The <var>wrapper</var> argument is the same as for {@link 
404    * $.dom.Element.wrap}.
405    *
406    * @example
407    * <code>var <var>child1</var> = document.createElement('p'),
408    *     <var>child2</var> = document.createElement('p');
409    *
410    * <var>child1</var>.id = 'child1';
411    * <var>child2</var>.id = 'child2';
412    *
413    * document.body.appendChild(<var>child1</var>);
414    * document.body.appendChild(<var>child2</var>);</code>
415    * // Now the body contains the two children.
416    *
417    * <code>var result = document.body.wrapInner('<div>');</code>
418    * // Now <var>result</var> is a NodeList containing <var>child1</var>
419    * // and <var>child2</var>.
420    *
421    * // The body would serialize to something like:
422    * // <body><div><p id="child1"></p>
423    * // <p id="chil2"></p></div></body>
424    * 
425    * @param {Element|String} wrapper The element or the string to use for 
426    * wrapping the child nodes of the current element.
427    *
428    * @throws {TypeError} If the current node is not an element node. This method 
429    * cannot wrap other kinds of objects.
430    *
431    * @throws {ReferenceError} If the wrapper is a string and the current element 
432    * has no owner document.
433    *
434    * @throws {ReferenceError} If the wrapper is a string with no elements in it.
435    *
436    * @returns {NodeList} The list of wrapped child nodes.
437    * (<code><var>wrapper</var>.childNodes</code>)
438    *
439    * @see $.dom.Element.wrap
440    */
441   wrapInner : function (wrapper) {
442     if (this.nodeType != Node.ELEMENT_NODE) {
443       throw new TypeError('This node must be an DOM element.');
444     }
445 
446     if (this.hasChildNodes()) {
447       return $.dom.NodeList.wrapAll.call(this.childNodes, wrapper);
448     } else {
449       return null;
450     }
451   }
452 };
453 
454 // Make sure we can wrap text and comment nodes as well.
455 $.dom.Text = $.dom.Comment = {
456   wrap : $.dom.Element.wrap
457 };
458 
459 /**
460  * Holds the DOM NodeList methods, which allow the manipulation of multiple 
461  * nodes in a single step.
462  *
463  * <p>By default, the global <var>NodeList.prototype</var> object is extended to 
464  * contain all the methods defined in this namespace.
465  *
466  * <p>These methods are also used for extending the global 
467  * <var>HTMLCollection.prototype</var> object.
468  *
469  * <p>All of these methods use their <var>this</var> object. As such, you need 
470  * to pass the correct <var>this</var> object.
471  *
472  * <p>In the examples provided for each method we will assume that the objects 
473  * defined are already extended. Thus code like 
474  * <code><var>element.childNodes</var>.wrap('<p>')</code> can be written 
475  * directly.
476  *
477  * @example
478  * // DOM elements have the <var>.childNodes</var> property object which holds
479  * // the list of child nodes. This object is an instance of NodeList. By
480  * // extending the <var>NodeList.prototype</var> you can do the following:
481  *
482  * var <var>new_child</var> = document.createElement('p');
483  * document.body.childNodes.appendChild(<var>new_child</var>);
484  *
485  * // The above code appends a clone of the <var>new_child</var> into each child
486  * // node of the document body.
487  *
488  * // As expected, you can use any of the methods defined in this namespace.
489  *
490  * @namespace Holds the DOM NodeList methods, which allow the manipulation of 
491  * multiple nodes in a single step.
492  *
493  * @requires $.js.Array
494  *
495  * @see $.dom.Node
496  * @see $.dom.Element
497  */
498 $.dom.NodeList = {
499   /**
500    * Convert the current NodeList to an array.
501    *
502    * <p>Each NodeList item becomes an array element. The array has the same 
503    * length as the NodeList object.
504    *
505    * <p>The returned array is a static list. If the NodeList changes, the array 
506    * does not.
507    *
508    * @returns {Array} The current NodeList converted to an array, having the 
509    * same length.
510    *
511    * @see $.js.Array
512    */
513   toArray : function () {
514     var n = this.length;
515     var resArr = new Array(n);
516 
517     for (var i = 0; i < n; i++) {
518       resArr[i] = this[i];
519     }
520 
521     return resArr;
522   },
523 
524   // Array methods
525 
526   /**
527    * Extract a section of the current NodeList and return it.
528    *
529    * @function
530    *
531    * @param {Number} begin
532    *
533    * @param {Number} [end=this.length]
534    *
535    * @throws {TypeError} If <var>begin</var> is not a number, or if it is 
536    * a negative number.
537    *
538    * @throws {TypeError} If <var>end</var> is provided, but it is not a number.
539    *
540    * @returns {Array} The new array returned contains all the nodes starting 
541    * from the <var>begin</var> index (including it) up to the <var>end</var> 
542    * index (excluding it).
543    *
544    * @requires $.js.Array.slice
545    * @see $.js.Array.slice for more details and examples
546    */
547   slice : $.js.Array.slice,
548 
549   /**
550    * Filter the current NodeList using the <var>callback</var> function.
551    *
552    * @function
553    *
554    * @param {Function} callback
555    *
556    * @param {Object} [thisObject]
557    *
558    * @throws {TypeError} If <var>callback</var> is not a function.
559    *
560    * @returns {Array} The new array contains the nodes for which the 
561    * <var>callback</var> function returned true.
562    *
563    * @requires $.js.Array.filter
564    * @see $.js.Array.filter for more details and examples
565    */
566   filter : $.js.Array.filter,
567 
568   /**
569    * Execute the <var>callback</var> function for each node in the current 
570    * NodeList.
571    *
572    * @function
573    *
574    * @param {Function} callback
575    *
576    * @param {Object} [thisObject]
577    *
578    * @throws {TypeError} If <var>callback</var> is not a function.
579    *
580    * @requires $.js.Array.forEach
581    * @see $.js.Array.forEach for more details and examples
582    */
583   forEach : $.js.Array.forEach,
584 
585   /**
586    * Check if the <var>callback</var> function returns true for every node in 
587    * the current NodeList.
588    *
589    * @function
590    *
591    * @param {Function} callback
592    *
593    * @param {Object} [thisObject]
594    *
595    * @throws {TypeError} If <var>callback</var> is not a function.
596    *
597    * @returns {Boolean} False is returned if the <var>callback</var> returns 
598    * false once.  Otherwise, this method returns true.
599    *
600    * @requires $.js.Array.every
601    * @see $.js.Array.every for more details and examples
602    */
603   every : $.js.Array.every,
604 
605   /**
606    * Check if the <var>callback</var> function returns true for at least one 
607    * node in the current NodeList.
608    *
609    * @function
610    *
611    * @param {Function} callback
612    *
613    * @param {Object} [thisObject]
614    *
615    * @throws {TypeError} If <var>callback</var> is not a function.
616    *
617    * @returns {Boolean} True is returned if the <var>callback</var> returns true 
618    * once. Otherwise, this method returns false.
619    *
620    * @requires $.js.Array.some
621    * @see $.js.Array.some for more details and examples
622    */
623   some : $.js.Array.some,
624 
625   /**
626    * Create a new array with the same length as the current NodeList using the 
627    * values returned by the <var>callback</var> function.
628    *
629    * @function
630    *
631    * @param {Function} callback
632    *
633    * @param {Object} [thisObject]
634    *
635    * @throws {TypeError} If <var>callback</var> is not a function.
636    *
637    * @returns {Boolean} The new array has the same length, but the values are 
638    * those returned by the <var>callback</var> function.
639    *
640    * @requires $.js.Array.map
641    * @see $.js.Array.map for more details and examples
642    */
643   map : $.js.Array.map,
644 
645   /**
646    * Apply the <var>callback</var> function to two values in the current 
647    * NodeList, from left to right, simultaneously, for the purpose of reducing 
648    * the NodeList to a single value.
649    *
650    * @function
651    *
652    * @param {Function} callback
653    *
654    * @param [initialValue]
655    *
656    * @throws {TypeError} If <var>callback</var> is not a function.
657    *
658    * @returns The result of the last <var>callback</var> function invocation.
659    *
660    * @requires $.js.Array.reduce
661    * @see $.js.Array.reduce for more details and examples
662    * @see $.dom.NodeList.reduceRight
663    */
664   reduce : $.js.Array.reduce,
665 
666   /**
667    * Apply the <var>callback</var> function to two values from the current 
668    * NodeList, from right to left, simultaneously, for the purpose of reducing 
669    * the NodeList to a single value.
670    *
671    * @function
672    *
673    * @param {Function} callback
674    *
675    * @param [initialValue]
676    *
677    * @throws {TypeError} If <var>callback</var> is not a function.
678    *
679    * @returns The result of the last <var>callback</var> function invocation.
680    *
681    * @requires $.js.Array.reduceRight
682    * @see $.js.Array.reduceRight for more details and examples
683    * @see $.dom.NodeList.reduce
684    */
685   reduceRight : $.js.Array.reduceRight,
686 
687   /**
688    * Create a new array containing the list of child elements of each item in 
689    * the current NodeList.
690    *
691    * @example
692    * // Given a DOM tree like:
693    * <code><body>
694    *   <ul>
695    *     <li>test 1.1</li>
696    *     <li>test 1.2</li>
697    *   </ul>
698    *   <ol>
699    *     <li>test 2.1</li>
700    *     <li>test 2.2</li>
701    *   </ol>
702    * </body></code>
703    *
704    * // You can do:
705    * <code>var <var>elems</var> = document.body.childNodes.children();</code>
706    *
707    * // Now <var>elems</var> is an array having four elements: the <li>
708    * // nodes from the two body child nodes, the <ul> and the <ol>.
709    *
710    * @returns {Array} The array containing the child elements of each item in 
711    * the current NodeList.
712    */
713   children : function () {
714     var resArr = [];
715 
716     this.forEach(function (elem) {
717       if (elem.nodeType == Node.ELEMENT_NODE && elem.children) {
718         $.js.Array.forEach.call(elem.children, function (child) {
719           resArr.push(child);
720         });
721       }
722     });
723 
724     return resArr;
725   },
726 
727   /**
728    * Add the <var>token</var> to the class list of each element in the current 
729    * NodeList.
730    *
731    * <p>This method takes a single token, no spaces allowed.
732    *
733    * @example
734    * <code>document.body.childNodes.addClass('test');</code>
735    *
736    * @param {String} token The token to add to the list of class names, for each 
737    * element in the current NodeList.
738    *
739    * @throws {DOMException.INVALID_CHARACTER_ERR|Error} If the <var>token</var> 
740    * contains a space character.
741    *
742    * @see $.dom.NodeList.hasClass
743    * @see $.dom.NodeList.toggleClass
744    * @see $.dom.NodeList.removeClass
745    */
746   addClass : function (token) {
747     if (!token) {
748       return;
749     }
750 
751     this.forEach(function (elem) {
752       if (elem.nodeType == Node.ELEMENT_NODE && elem.classList) {
753         elem.classList.add(token);
754       }
755     });
756   },
757 
758   /**
759    * Remove the <var>token</var> from the class list of each element in the 
760    * current NodeList.
761    *
762    * <p>This method takes a single token, no spaces allowed.
763    *
764    * @example
765    * <code>document.body.childNodes.removeClass('test');</code>
766    *
767    * @param {String} token The token you want removed from the list of class 
768    * names, for each element in the current NodeList.
769    *
770    * @throws {DOMException.INVALID_CHARACTER_ERR|Error} If the <var>token</var> 
771    * contains a space character.
772    *
773    * @see $.dom.NodeList.addClass
774    * @see $.dom.NodeList.hasClass
775    * @see $.dom.NodeList.toggleClass
776    */
777   removeClass : function (token) {
778     if (!token) {
779       return;
780     }
781 
782     this.forEach(function (elem) {
783       if (elem.nodeType == Node.ELEMENT_NODE && elem.className && 
784         elem.classList) {
785         elem.classList.remove(token);
786       }
787     });
788   },
789 
790   /**
791    * Toggle the presence of <var>token</var> in the class list of each element 
792    * in the current NodeList.
793    *
794    * <p>This method takes a single token, no spaces allowed.
795    *
796    * <p>For each element, it is checked if the <var>token</var> is found in the 
797    * list of class names. If yes, then the <var>token</var> is removed. If not, 
798    * then the <var>token</var> is added.
799    *
800    * @example
801    * <code>document.body.childNodes.toggleClass('test');</code>
802    *
803    * @param {String} token The token you want to toggle in the list of class 
804    * names, for each element in the current NodeList.
805    *
806    * @throws {DOMException.INVALID_CHARACTER_ERR|Error} If the <var>token</var> 
807    * contains a space character.
808    *
809    * @see $.dom.NodeList.addClass
810    * @see $.dom.NodeList.hasClass
811    * @see $.dom.NodeList.removeClass
812    */
813   toggleClass : function (token) {
814     if (!token) {
815       return;
816     }
817 
818     this.forEach(function (elem) {
819       if (elem.nodeType == Node.ELEMENT_NODE && elem.classList) {
820         elem.classList.toggle(token);
821       }
822     });
823   },
824 
825   /**
826    * Check if all the elements in the current NodeList have the <var>token</var> 
827    * in their list of class names.
828    *
829    * <p>This method takes a single token, no spaces allowed.
830    *
831    * @example
832    * <code>document.body.childNodes.hasClass('test');</code>
833    *
834    * @param {String} token The token you want to toggle in the list of class 
835    * names, for each element in the current NodeList.
836    *
837    * @throws {DOMException.INVALID_CHARACTER_ERR|Error} If the <var>token</var> 
838    * contains a space character.
839    *
840    * @returns {Boolean} This method returns true if all the elements have the 
841    * <var>token</var> in their class list. Otherwise, false is returned.
842    *
843    * @see $.dom.NodeList.addClass
844    * @see $.dom.NodeList.toggleClass
845    * @see $.dom.NodeList.removeClass
846    */
847   hasClass : function (token) {
848     if (!token) {
849       return false;
850     }
851 
852     var found_elem = false;
853     var res = this.every(function (elem) {
854       if (elem.nodeType != Node.ELEMENT_NODE) {
855         return true;
856       }
857 
858       found_elem = true;
859 
860       if (elem.className && elem.classList) {
861         return elem.classList.has(token);
862       } else {
863         return false;
864       }
865     });
866 
867     if (!found_elem) {
868       res = false;
869     }
870 
871     return res;
872   },
873 
874   /**
875    * Set the value of an attribute, for each element in the current NodeList.
876    *
877    * @example
878    * // Set the border attribute for all images in the document.
879    * <code>var imgs = $('img');
880    * imgs.setAttribute('border', 0);</code>
881    *
882    * @param {String} attr The attribute name.
883    * @param {String} val The attribute value you want to set.
884    *
885    * @see $.dom.NodeList.setAttributeNS
886    * @see $.dom.NodeList.removeAttribute
887    * @see $.dom.NodeList.removeAttributeNS
888    * @see $.dom.NodeList.hasAttribute
889    * @see $.dom.NodeList.hasAttributeNS
890    */
891   setAttribute : function (attr, val) {
892     this.forEach(function (elem) {
893       if (elem.nodeType == Node.ELEMENT_NODE) {
894         elem.setAttribute(attr, val);
895       }
896     });
897   },
898 
899   /**
900    * Set the value of a namespaced attribute, for each element in the current 
901    * NodeList.
902    *
903    * @example
904    * // Set the border attribute for all images in the document.
905    * <code>var imgs = $('img'),
906    *     ns = '<a href="http://www.w3.org/1999/xhtml">http://www.w3.org/1999/xhtml</a>';
907    * imgs.setAttributeNS(ns, 'border', 0);</code>
908    *
909    * @param {String} ns The attribute XML namespace.
910    * @param {String} attr The attribute name.
911    * @param {String} val The attribute value you want to set.
912    *
913    * @see $.dom.NodeList.setAttribute
914    * @see $.dom.NodeList.removeAttribute
915    * @see $.dom.NodeList.removeAttributeNS
916    * @see $.dom.NodeList.hasAttribute
917    * @see $.dom.NodeList.hasAttributeNS
918    */
919   setAttributeNS : function (ns, attr, val) {
920     this.forEach(function (elem) {
921       if (elem.nodeType == Node.ELEMENT_NODE) {
922         elem.setAttributeNS(ns, attr, val);
923       }
924     });
925   },
926 
927   /**
928    * Remove an attribute from all the elements in the current NodeList.
929    *
930    * @example
931    * // Remove the border attribute from all the images in the document.
932    * <code>var imgs = $('img');
933    * imgs.removeAttribute('border');</code>
934    *
935    * @param {String} attr The attribute name to be removed.
936    *
937    * @see $.dom.NodeList.setAttribute
938    * @see $.dom.NodeList.setAttributeNS
939    * @see $.dom.NodeList.removeAttributeNS
940    * @see $.dom.NodeList.hasAttribute
941    * @see $.dom.NodeList.hasAttributeNS
942    */
943   removeAttribute : function (attr) {
944     this.forEach(function (elem) {
945       if (elem.nodeType == Node.ELEMENT_NODE) {
946         elem.removeAttribute(attr);
947       }
948     });
949   },
950 
951   /**
952    * Remove a namespaced attribute from all the elements in the current 
953    * NodeList.
954    *
955    * @example
956    * // Remove the border attribute from all the images in the document.
957    * <code>var imgs = $('img'),
958    *     ns = '<a href="http://www.w3.org/1999/xhtml">http://www.w3.org/1999/xhtml</a>';
959    * imgs.removeAttributeNS(ns, 'border');</code>
960    *
961    * @param {String} ns The attribute XML namespace.
962    * @param {String} attr The attribute name to be removed.
963    *
964    * @see $.dom.NodeList.setAttribute
965    * @see $.dom.NodeList.setAttributeNS
966    * @see $.dom.NodeList.removeAttribute
967    * @see $.dom.NodeList.hasAttribute
968    * @see $.dom.NodeList.hasAttributeNS
969    */
970   removeAttributeNS : function (ns, attr) {
971     this.forEach(function (elem) {
972       if (elem.nodeType == Node.ELEMENT_NODE) {
973         elem.removeAttributeNS(ns, attr);
974       }
975     });
976   },
977 
978   /**
979    * Check if an attribute is defined for all the elements in the current 
980    * NodeList.
981    *
982    * @example
983    * // Check if the border attribute is set for all the image elements.
984    * <code>var imgs = $('img');
985    * imgs.hasAttribute('border');</code>
986    *
987    * @param {String} attr The attribute name to be checked.
988    *
989    * @see $.dom.NodeList.setAttribute
990    * @see $.dom.NodeList.setAttributeNS
991    * @see $.dom.NodeList.removeAttribute
992    * @see $.dom.NodeList.removeAttributeNS
993    * @see $.dom.NodeList.hasAttributeNS
994    */
995   hasAttribute : function (attr) {
996     var found_elem = false;
997 
998     var res = this.every(function (elem) {
999       if (elem.nodeType == Node.ELEMENT_NODE) {
1000         found_elem = true;
1001         return elem.hasAttribute(attr);
1002       } else {
1003         return true;
1004       }
1005     });
1006 
1007     if (!found_elem) {
1008       res = false;
1009     }
1010 
1011     return res;
1012   },
1013 
1014   /**
1015    * Check if a namespaced attribute is defined for all the elements in the 
1016    * current NodeList.
1017    *
1018    * @example
1019    * // Check if the border attribute is set for all the image elements.
1020    * <code>var imgs = $('img'),
1021    *     ns = '<a href="http://www.w3.org/1999/xhtml">http://www.w3.org/1999/xhtml</a>';
1022    * imgs.hasAttributeNS(ns, 'border');</code>
1023    *
1024    * @param {String} ns The attribute XML namespace.
1025    * @param {String} attr The attribute name to be checked.
1026    *
1027    * @see $.dom.NodeList.setAttribute
1028    * @see $.dom.NodeList.setAttributeNS
1029    * @see $.dom.NodeList.removeAttribute
1030    * @see $.dom.NodeList.removeAttributeNS
1031    * @see $.dom.NodeList.hasAttribute
1032    */
1033   hasAttributeNS : function (ns, attr) {
1034     var found_elem = false;
1035 
1036     var res = this.every(function (elem) {
1037       if (elem.nodeType == Node.ELEMENT_NODE) {
1038         found_elem = true;
1039         return elem.hasAttributeNS(ns, attr);
1040       } else {
1041         return true;
1042       }
1043     });
1044 
1045     if (!found_elem) {
1046       res = false;
1047     }
1048 
1049     return res;
1050   },
1051 
1052   /**
1053    * Insert a new node as the first child into each element from the current 
1054    * NodeList.
1055    *
1056    * <p>Given a node, this method clones it for every element in the current 
1057    * NodeList. The clone is then inserted as the first child into each element.
1058    *
1059    * @example
1060    * // Given a DOM tree like:
1061    * <code><body>
1062    *   <ul>
1063    *     <li>test 1.1</li>
1064    *     <li>test 1.2</li>
1065    *   </ul>
1066    *   <ol>
1067    *     <li>test 2.1</li>
1068    *     <li>test 2.2</li>
1069    *   </ol>
1070    * </body></code>
1071    *
1072    * // You can do:
1073    * <code>var <var>elem</var> = document.createElement('li');
1074    * <var>elem</var>.className = 'test';</code>
1075    *
1076    * // Prepend the element to the document.body child nodes.
1077    * <code>document.body.childNodes.prependChild(<var>elem</var>);</code>
1078    *
1079    * // The body would then serialize to something like:
1080    * <code><body>
1081    *   <ul>
1082    *     <li class="test"></li>
1083    *     <li>test 1.1</li>
1084    *     <li>test 1.2</li>
1085    *   </ul>
1086    *   <ol>
1087    *     <li class="test"></li>
1088    *     <li>test 2.1</li>
1089    *     <li>test 2.2</li>
1090    *   </ol>
1091    * </body></code>
1092    *
1093    * @param {Node} child The node to prepend as a child.
1094    *
1095    * @returns {Array} An array containing the inserted child nodes.
1096    *
1097    * @see $.dom.NodeList.appendChild
1098    * @see $.dom.Node.prependChild
1099    */
1100   prependChild : function (child) {
1101     var resArr = [];
1102 
1103     this.forEach(function (elem) {
1104       if (elem.nodeType == Node.ELEMENT_NODE) {
1105         var clone = child.cloneNode(true);
1106         resArr.push(elem.insertBefore(clone, elem.firstChild));
1107       }
1108     });
1109 
1110     return resArr;
1111   },
1112 
1113   /**
1114    * Insert a new node as the last child into each element from the current 
1115    * NodeList.
1116    *
1117    * <p>Given a node, this method clones it for every element in the current 
1118    * NodeList. The clone is then appended as the last child into each element.
1119    *
1120    * @example
1121    * // Given a DOM tree like:
1122    * <code><body>
1123    *   <ul>
1124    *     <li>test 1.1</li>
1125    *     <li>test 1.2</li>
1126    *   </ul>
1127    *   <ol>
1128    *     <li>test 2.1</li>
1129    *     <li>test 2.2</li>
1130    *   </ol>
1131    * </body></code>
1132    *
1133    * // You can do:
1134    * <code>var <var>elem</var> = document.createElement('li');
1135    * <var>elem</var>.className = 'test';</code>
1136    *
1137    * // Append the element to the document.body child nodes.
1138    * <code>document.body.childNodes.appendChild(<var>elem</var>);</code>
1139    *
1140    * // The body would then serialize to something like:
1141    * <code><body>
1142    *   <ul>
1143    *     <li>test 1.1</li>
1144    *     <li>test 1.2</li>
1145    *     <li class="test"></li>
1146    *   </ul>
1147    *   <ol>
1148    *     <li>test 2.1</li>
1149    *     <li>test 2.2</li>
1150    *     <li class="test"></li>
1151    *   </ol>
1152    * </body></code>
1153    *
1154    * @param {Node} child The node to append as a child.
1155    *
1156    * @returns {Array} An array containing the inserted child nodes.
1157    *
1158    * @see $.dom.NodeList.prependChild
1159    * @see $.dom.Node.prependChild
1160    */
1161   appendChild : function (child) {
1162     var resArr = [];
1163 
1164     this.forEach(function (elem) {
1165       if (elem.nodeType == Node.ELEMENT_NODE) {
1166         var clone = child.cloneNode(true);
1167         resArr.push(elem.appendChild(clone));
1168       }
1169     });
1170 
1171     return resArr;
1172   },
1173 
1174   /**
1175    * Prepend all the nodes in the current NodeList to the <var>target</var> 
1176    * node. All the nodes are inserted maintaining the same order as in the 
1177    * current NodeList.
1178    *
1179    * @example
1180    * // Given a DOM tree like:
1181    * <code><body>
1182    *   <div>
1183    *     <p>test a</p>
1184    *     <p>test b</p>
1185    *   </div>
1186    *   <p>Test 1</p>
1187    *   <p>Test 2</p>
1188    * </body></code>
1189    *
1190    * // You can do the following:
1191    * <code>var <var>div</var> = document.body.firstElementChild;</code>
1192    *
1193    * // Move the <div> child nodes into the body element.
1194    * <code><var>div</var>.childNodes.prependTo(document.body);</code>
1195    *
1196    * // Now the body would serialize to something like:
1197    * <code><body>
1198    *   <p>test a</p>
1199    *   <p>test b</p>
1200    *   <div></div>
1201    *   <p>Test 1</p>
1202    *   <p>Test 2</p>
1203    * </body></code>
1204    *
1205    * @param {Node} target The target node where to prepend all of the nodes from 
1206    * the current NodeList.
1207    *
1208    * @throws {TypeError} If <var>target</var> is not an instance of Node.
1209    *
1210    * @returns {NodeList} The child nodes of the <var>target</var> node.
1211    * (<code><var>target</var>.childNodes</code>)
1212    *
1213    * @see $.dom.NodeList.appendTo
1214    * @see $.dom.Node.prependTo
1215    * @see $.dom.Node.appendTo
1216    */
1217   prependTo : function (target) {
1218     if (!this.length) {
1219       return;
1220     }
1221 
1222     if (!(target instanceof Node)) {
1223       throw new TypeError('The first argument must be a DOM node.');
1224     }
1225 
1226     var first = target.firstChild;
1227 
1228     while(this[0]) {
1229       target.insertBefore(this[0], first);
1230     }
1231 
1232     return target.childNodes;
1233   },
1234 
1235   /**
1236    * Append all the nodes in the current NodeList to the <var>target</var> node.  
1237    * All the nodes are inserted maintaining the same order as in the current 
1238    * NodeList.
1239    *
1240    * @example
1241    * // Given a DOM tree like:
1242    * <code><body>
1243    *   <div>
1244    *     <p>test a</p>
1245    *     <p>test b</p>
1246    *   </div>
1247    *   <p>Test 1</p>
1248    *   <p>Test 2</p>
1249    * </body></code>
1250    *
1251    * // You can do the following:
1252    * <code>var <var>div</var> = document.body.firstElementChild;</code>
1253    *
1254    * // Move the <div> child nodes into the body element.
1255    * <code><var>div</var>.childNodes.appendTo(document.body);</code>
1256    *
1257    * // Now the body would serialize to something like:
1258    * <code><body>
1259    *   <div></div>
1260    *   <p>Test 1</p>
1261    *   <p>Test 2</p>
1262    *   <p>test a</p>
1263    *   <p>test b</p>
1264    * </body></code>
1265    *
1266    * @param {Node} target The target node where to append all of the nodes from 
1267    * the current NodeList.
1268    *
1269    * @throws {TypeError} If <var>target</var> is not an instance of Node.
1270    *
1271    * @returns {NodeList} The child nodes of the <var>target</var> node.
1272    * (<code><var>target</var>.childNodes</code>)
1273    *
1274    * @see $.dom.NodeList.prependTo
1275    * @see $.dom.Node.prependTo
1276    * @see $.dom.Node.appendTo
1277    */
1278   appendTo : function (target) {
1279     if (!this.length) {
1280       return;
1281     }
1282 
1283     if (!(target instanceof Node)) {
1284       throw new TypeError('The first argument must be a DOM node.');
1285     }
1286 
1287     while(this[0]) {
1288       target.appendChild(this[0]);
1289     }
1290 
1291     return target.childNodes;
1292   },
1293 
1294   /**
1295    * Insert all the nodes in the current NodeList before a reference element.
1296    *
1297    * <p>The reference element must have a parent element. All the nodes from the 
1298    * current NodeList will be inserted into the parent element. The inserted 
1299    * nodes will be positioned before the reference element.
1300    *
1301    * @example
1302    * // Given a DOM tree like:
1303    * <code><body>
1304    *   <div>
1305    *     <p>test a</p>
1306    *     <p>test b</p>
1307    *   </div>
1308    *   <p id="test1">Test 1</p>
1309    *   <p>Test 2</p>
1310    * </body></code>
1311    *
1312    * // You can do the following:
1313    * <code>var <var>div</var> = document.body.firstElementChild,
1314    *     <var>test1</var> = $('#test1');</code>
1315    *
1316    * // Move the <div> child nodes before <var>test1</var>.
1317    * <code><var>div</var>.childNodes.insertBefore(<var>test1</var>);</code>
1318    *
1319    * // Now the body would serialize to something like:
1320    * <code><body>
1321    *   <div></div>
1322    *   <p>test a</p>
1323    *   <p>test b</p>
1324    *   <p id="test1">Test 1</p>
1325    *   <p>Test 2</p>
1326    * </body></code>
1327    *
1328    * @param {Element} ref The reference element.
1329    *
1330    * @throws {TypeError} If <var>ref</var> is not an element node.
1331    * @throws {ReferenceError} If <var>ref.parentNode</var> is not an element 
1332    * node.
1333    *
1334    * @returns {NodeList} The NodeList containing the child nodes of the 
1335    * reference parent element.
1336    * (<code><var>ref</var>.parentNode.childNodes</code>)
1337    *
1338    * @see $.dom.NodeList.insertAfter
1339    * @see $.dom.Node.insertAfter
1340    */
1341   insertBefore : function (ref) {
1342     if (ref.nodeType != Node.ELEMENT_NODE) {
1343       throw new TypeError('The first argument must be a DOM element.');
1344     }
1345 
1346     var parentNode = ref.parentNode;
1347     if (parentNode.nodeType != Node.ELEMENT_NODE) {
1348       throw new ReferenceError('The first argument must have a parent element.');
1349     }
1350 
1351     while(this[0]) {
1352       parentNode.insertBefore(this[0], ref);
1353     }
1354 
1355     return parentNode.childNodes;
1356   },
1357 
1358   /**
1359    * Insert all the nodes in the current NodeList after a reference element.
1360    *
1361    * <p>The reference element must have a parent element. All the nodes from the 
1362    * current NodeList will be inserted into the parent element. The inserted 
1363    * nodes will be positioned after the reference element.
1364    *
1365    * @example
1366    * // Given a DOM tree like:
1367    * <code><body>
1368    *   <div>
1369    *     <p>test a</p>
1370    *     <p>test b</p>
1371    *   </div>
1372    *   <p id="test1">Test 1</p>
1373    *   <p>Test 2</p>
1374    * </body></code>
1375    *
1376    * // You can do the following:
1377    * <code>var <var>div</var> = document.body.firstElementChild,
1378    *     <var>test1</var> = $('#test1');</code>
1379    *
1380    * // Move the <div> child nodes after <var>test1</var>.
1381    * <code><var>div</var>.childNodes.insertAfter(<var>test1</var>);</code>
1382    *
1383    * // Now the body would serialize to something like:
1384    * <code><body>
1385    *   <div></div>
1386    *   <p id="test1">Test 1</p>
1387    *   <p>test a</p>
1388    *   <p>test b</p>
1389    *   <p>Test 2</p>
1390    * </body></code>
1391    *
1392    * @param {Element} ref The reference element.
1393    *
1394    * @throws {TypeError} If <var>ref</var> is not an element node.
1395    * @throws {ReferenceError} If <var>ref.parentNode</var> is not an element 
1396    * node.
1397    *
1398    * @returns {NodeList} The NodeList containing the child nodes of the 
1399    * reference parent element.
1400    * (<code><var>ref</var>.parentNode.childNodes</code>)
1401    *
1402    * @see $.dom.NodeList.insertBefore
1403    * @see $.dom.Node.insertAfter
1404    */
1405   insertAfter : function (ref) {
1406     if (!this.length) {
1407       return null;
1408     }
1409 
1410     if (ref.nodeType != Node.ELEMENT_NODE) {
1411       throw new TypeError('The first argument must be a DOM element.');
1412     }
1413 
1414     var parentNode = ref.parentNode;
1415     if (parentNode.nodeType != Node.ELEMENT_NODE) {
1416       throw new ReferenceError('The first argument must have a parent element.');
1417     }
1418 
1419     var before = ref.nextSibling;
1420     while (this[0]) {
1421       parentNode.insertBefore(this[0], before);
1422     }
1423 
1424     return parentNode.childNodes;
1425   },
1426 
1427   /**
1428    * Remove the first child from each element in the current NodeList.
1429    *
1430    * @returns {Array} The array returned contains references to the removed 
1431    * nodes.
1432    *
1433    * @see $.dom.NodeList.removeLastChild
1434    * @see $.dom.Node.removeFirstChild
1435    * @see $.dom.Node.removeLastChild
1436    */
1437   removeFirstChild : function () {
1438     var resArr = [];
1439 
1440     this.forEach(function (node) {
1441       resArr.push(node.removeChild(node.firstChild));
1442     });
1443 
1444     return resArr;
1445   },
1446 
1447   /**
1448    * Remove the last child from each element in the current NodeList.
1449    *
1450    * @returns {Array} The array returned contains references to the removed 
1451    * nodes.
1452    *
1453    * @see $.dom.NodeList.removeFirstChild
1454    * @see $.dom.Node.removeFirstChild
1455    * @see $.dom.Node.removeLastChild
1456    */
1457   removeLastChild : function () {
1458     var resArr = [];
1459 
1460     this.forEach(function (node) {
1461       resArr.push(node.removeChild(node.lastChild));
1462     });
1463 
1464     return resArr;
1465   },
1466 
1467   /**
1468    * Remove all the child nodes from each element in the current NodeList.
1469    *
1470    * @returns {Array} The array returned contains references to the removed 
1471    * nodes.
1472    */
1473   empty : function () {
1474     var resArr = [];
1475 
1476     this.forEach(function (node) {
1477       while (node.hasChildNodes()) {
1478         resArr.push(node.removeChild(node.firstChild));
1479       }
1480     });
1481 
1482     return resArr;
1483   },
1484 
1485   /**
1486    * Wrap each node from the current NodeList inside the given 
1487    * <var>wrapper</var>.
1488    *
1489    * <p>This method clones the <var>wrapper</var> for each node in the current 
1490    * NodeList. Then each node is inserted into its own wrapper clone. The effect 
1491    * of this action is that the current NodeList will only hold the cloned 
1492    * wrapper nodes themselves, after this method executes. The length of the 
1493    * NodeList does not change.
1494    *
1495    * @example
1496    * // Given a DOM tree like the following:
1497    * <code><body>
1498    *   <ul>
1499    *     <p>test 1</p>
1500    *     <p>test 2</p>
1501    *   </ul>
1502    * </body></code>
1503    *
1504    * // You can do:
1505    * <code>$('body > ul')[0].childNodes.wrapEach('<li>');</code>
1506    *
1507    * // The body would then serialize to:
1508    * <code><body>
1509    *   <ul>
1510    *     <li><p>test 1</p></li>
1511    *     <li><p>test 2</p></li>
1512    *   </ul>
1513    * </body></code>
1514    *
1515    * @param {Element|String} wrapper The element or the string to use for 
1516    * wrapping each node.
1517    *
1518    * @see $.dom.Element.wrap for more details and examples
1519    */
1520   wrapEach : function (wrapper) {
1521     this.forEach(function (node) {
1522       if (node.nodeType != Node.ELEMENT_NODE && node.nodeType != Node.TEXT_NODE 
1523         && node.nodeType != Node.COMMENT_NODE) {
1524         return;
1525       }
1526 
1527       var clone = wrapper;
1528       if (wrapper instanceof Node) {
1529         clone = wrapper.cloneNode(true);
1530       }
1531 
1532       $.dom.Element.wrap.call(node, clone);
1533     });
1534   },
1535 
1536   /**
1537    * Wrap all the nodes from the current NodeList inside a single 
1538    * <var>wrapper</var>.
1539    *
1540    * <p>This method moves all the nodes from the current NodeList into the given 
1541    * <var>wrapper</var>. The effect of this action is that the current NodeList 
1542    * will only hold one child node (the <var>wrapper</var>) after this method 
1543    * executes.
1544    *
1545    * @example
1546    * // Given a DOM tree like the following:
1547    * <code><body>
1548    *   <p>test 1</p>
1549    *   <p>test 2</p>
1550    * </body></code>
1551    *
1552    * // You can do:
1553    * <code>document.body.childNodes.wrapAll('<div>');</code>
1554    *
1555    * // The body would then serialize to:
1556    * <code><body>
1557    *   <div>
1558    *     <p>test 1</p>
1559    *     <p>test 2</p>
1560    *   </div>
1561    * </body></code>
1562    *
1563    * @param {Element|String} wrapper The element or the string to use for 
1564    * wrapping all the nodes.
1565    *
1566    * @returns {NodeList} The list of wrapped child nodes.
1567    * (<code><var>wrapper</var>.childNodes</code>)
1568    *
1569    * @see $.dom.Element.wrap for more details and examples
1570    */
1571   wrapAll : function (wrapper) {
1572     if (!this.length) {
1573       return;
1574     }
1575 
1576     // Make a static list of nodes.
1577     var arr   = this.toArray();
1578     var first = arr.shift();
1579 
1580     $.dom.Element.wrap.call(first, wrapper);
1581 
1582     var pNode = first.parentNode;
1583 
1584     // Add the rest of the child nodes into the wrapper.
1585     $.js.Array.forEach.call(arr, function (child) {
1586       pNode.appendChild(child);
1587     });
1588 
1589     return pNode.childNodes;
1590   },
1591 
1592   /**
1593    * Wrap the content of the nodes from the current NodeList inside the given 
1594    * <var>wrapper</var>.
1595    *
1596    * <p>This method wraps the content of the nodes from the current NodeList 
1597    * into clones of the given <var>wrapper</var>.
1598    *
1599    * @example
1600    * // Given a DOM tree like the following:
1601    * <code><body>
1602    *   <p>test 1</p>
1603    *   <p>test 2</p>
1604    * </body></code>
1605    *
1606    * // You can do:
1607    * <code>document.body.childNodes.wrapInner('<strong>');</code>
1608    *
1609    * // The body would then serialize to:
1610    * <code><body>
1611    *   <p><strong>test 1</strong></p>
1612    *   <p><strong>test 2</strong></p>
1613    * </body></code>
1614    *
1615    * @param {Element|String} wrapper The element or the string to use for 
1616    * wrapping the content of all the nodes in the current NodeList.
1617    *
1618    * @see $.dom.Element.wrap for more details and examples
1619    */
1620   wrapInner : function (wrapper) {
1621     this.forEach(function (node) {
1622       if (node.nodeType != Node.ELEMENT_NODE || !node.hasChildNodes()) {
1623         return;
1624       }
1625 
1626       var clone = wrapper;
1627       if (wrapper instanceof Node) {
1628         clone = wrapper.cloneNode(true);
1629       }
1630       $.dom.NodeList.wrapAll.call(node.childNodes, clone);
1631     });
1632   },
1633 
1634   /**
1635    * Get/set the style for all the elements in the current NodeList.
1636    *
1637    * <p>If the <var>property</var> argument is a string, but the 
1638    * <var>value</var> is undefined, then this method finds the first element 
1639    * child in the current NodeList. The computed style of <var>property</var> is 
1640    * returned.
1641    *
1642    * <p>If the <var>property</var> and the <var>value</var> arguments are both 
1643    * strings, then this method changes the style <var>property</var> to 
1644    * <var>value</var>. This is done for all the elements in the current 
1645    * NodeList.
1646    *
1647    * <p>Similarly, if the <var>property</var> argument is an object, then all 
1648    * the values and properties in the object are used for changing the styles 
1649    * for all the elements in the current NodeList.
1650    *
1651    * @param {String|Object} property The style property name to get/set (if the 
1652    * argument is a string), or the list of properties to set (if the argument is 
1653    * an object).
1654    *
1655    * @param {String} [value] The value you want to set for the given 
1656    * <var>property</var>.
1657    *
1658    * @throws {TypeError} If the <var>property</var> argument is not a string, 
1659    * nor an object.
1660    *
1661    * @returns {String|undefined} If the <var>property</var> argument is a string 
1662    * and  if the <var>value</var> argument is undefined, then this method 
1663    * returns the computed style of the given <var>property</var>. Otherwise, 
1664    * this method does not return anything.
1665    *
1666    * @see $.dom.getStyle
1667    */
1668   style : function (property, value) {
1669     var list = {};
1670 
1671     if (typeof property == 'string' && typeof value == 'undefined') {
1672 
1673       var i = 0, first = this[0];
1674       while (this[i]) {
1675         if (this[i].nodeType == Node.ELEMENT_NODE) {
1676           first = this[i];
1677         }
1678         i++;
1679       }
1680 
1681       return $.dom.getStyle(first, property);
1682 
1683     } else if (typeof property == 'string') {
1684       list[property] = value;
1685     } else if (property instanceof Object) {
1686       list = property;
1687     } else {
1688       throw new TypeError('Invalid arguments.');
1689     }
1690 
1691     this.forEach(function (elem) {
1692       if (elem.nodeType != Node.ELEMENT_NODE) {
1693         return;
1694       }
1695 
1696       for (property in list) {
1697         elem.style[property] = list[property];
1698       }
1699     });
1700   }
1701 };
1702 
1703 // Extending the HTMLCollection object has no effect in Gecko (tested Firefox 
1704 // 3.2 pre-alpha, build 20090303).
1705 // See: https://bugzilla.mozilla.org/show_bug.cgi?id=300519
1706 $.dom.HTMLCollection = $.dom.NodeList;
1707 
1708 
1709 // Add the Array methods to the NamedNodeMap.
1710 // From the spec: Objects implementing the NamedNodeMap interface are used to 
1711 // represent collections of nodes that can be accessed by name. Note that 
1712 // NamedNodeMap does not inherit from NodeList; NamedNodeMaps are not maintained 
1713 // in any particular order. Objects contained in an object implementing 
1714 // NamedNodeMap may also be accessed by an ordinal index, but this is simply to 
1715 // allow convenient enumeration of the contents of a NamedNodeMap, and does not 
1716 // imply that the DOM specifies an order to these Nodes.
1717 $.dom.NamedNodeMap = {
1718   'slice'       : $.js.Array.slice,
1719   'filter'      : $.js.Array.filter,
1720   'forEach'     : $.js.Array.forEach,
1721   'every'       : $.js.Array.every,
1722   'some'        : $.js.Array.some,
1723   'map'         : $.js.Array.map,
1724   'reduce'      : $.js.Array.reduce,
1725   'reduceRight' : $.js.Array.reduceRight,
1726   'indexOf'     : $.js.Array.indexOf,
1727   'lastIndexOf' : $.js.Array.lastIndexOf
1728 };
1729 
1730 
1731 /*
1732  * NOTE: This is only tested and known to semi-work in MSIE 8.
1733  * 
1734  * Emulate minimal support for DOM Events. We also check for the presence of the 
1735  * methods, maybe MSIE will one day implement them.
1736  *
1737  * This works fine only if you add event listeners. You won't be able to detach 
1738  * events.
1739  */
1740 if ($.browser.msie && document.attachEvent && document.detachEvent && 
1741     !document.addEventListener && !document.removeEventListener) {
1742   $.dom.EventTarget = {
1743     addEventListener : function (ev, fn) {
1744       var _self = this,
1745           listener = function () {
1746             return fn.call(_self, window.event);
1747           };
1748 
1749       this.attachEvent('on' + ev, listener);
1750     },
1751 
1752     // Stub!
1753     removeEventListener : function (ev, fn) {
1754       this.detachEvent('on' + ev, fn);
1755     }
1756   };
1757 
1758   $.dom.Event = {
1759     preventDefault: function () {
1760       this.returnValue = false;
1761     }
1762   };
1763 }
1764 
1765 /**
1766  * @namespace Holds the list of virtual key identifiers and a few characters, 
1767  * each being associated to a key code commonly used by Web browsers.
1768  *
1769  * @private
1770  */
1771 $.dom.keyNames = {
1772   Help:          6,
1773   Backspace:     8,
1774   Tab:           9,
1775   Clear:         12,
1776   Enter:         13,
1777   Shift:         16,
1778   Control:       17,
1779   Alt:           18,
1780   Pause:         19,
1781   CapsLock:      20,
1782   Cancel:        24,
1783   'Escape':      27,
1784   Space:         32,
1785   PageUp:        33,
1786   PageDown:      34,
1787   End:           35,
1788   Home:          36,
1789   Left:          37,
1790   Up:            38,
1791   Right:         39,
1792   Down:          40,
1793   PrintScreen:   44,
1794   Insert:        45,
1795   'Delete':      46,
1796   Win:           91,
1797   ContextMenu:   93,
1798   '*':           106,
1799   '+':           107,
1800   F1:            112,
1801   F2:            113,
1802   F3:            114,
1803   F4:            115,
1804   F5:            116,
1805   F6:            117,
1806   F7:            118,
1807   F8:            119,
1808   F9:            120,
1809   F10:           121,
1810   F11:           122,
1811   F12:           123,
1812   NumLock:       144,
1813   ';':           186,
1814   '=':           187,
1815   ',':           188,
1816   '-':           189,
1817   '.':           190,
1818   '/':           191,
1819   '`':           192,
1820   '[':           219,
1821   '\\':          220,
1822   ']':           221,
1823   "'":           222
1824 };
1825 
1826 /**
1827  * @namespace Holds the list of codes, each being associated to a virtual key 
1828  * identifier.
1829  *
1830  * @private
1831  */
1832 $.dom.keyCodes = {
1833   /*
1834    * For almost each key code, these comments give the key name, the 
1835    * keyIdentifier from the DOM 3 Events spec and the Unicode character 
1836    * information (if you would use the decimal code for direct conversion to 
1837    * a character, e.g. String.fromCharCode()). Obviously, the Unicode character 
1838    * information is not to be used, since these are only virtual key codes (not 
1839    * really char codes) associated to key names.
1840    *
1841    * Each key name in here tries to follow the same style as the defined 
1842    * keyIdentifiers from the DOM 3 Events. Thus for the Page Down button, 
1843    * 'PageDown' is used (not other variations like 'pag-up'), and so on.
1844    *
1845    * Multiple key codes might be associated to the same key - it's not an error.
1846    *
1847    * Note that this list is not an exhaustive key codes list. This means that 
1848    * for key A or for key 0, the script will do String.fromCharCode(keyCode), to 
1849    * determine the key. For the case of alpha-numeric keys, this works fine.
1850    */
1851 
1852   /*
1853    * Key: Enter
1854    * Unicode: U+0003 [End of text]
1855    *
1856    * Note 1: This keyCode is only used in Safari 2 (older Webkit) for the Enter 
1857    * key.
1858    *
1859    * Note 2: In Gecko this keyCode is used for the Cancel key (see 
1860    * DOM_VK_CANCEL).
1861    */
1862   3: 'Enter',
1863 
1864   /*
1865    * Key: Help
1866    * Unicode: U+0006 [Acknowledge]
1867    *
1868    * Note: Taken from Gecko (DOM_VK_HELP).
1869    */
1870   6: 'Help',
1871 
1872   /*
1873    * Key: Backspace
1874    * Unicode: U+0008 [Backspace]
1875    * keyIdentifier: U+0008
1876    */
1877   8: 'Backspace',
1878 
1879   /*
1880    * Key: Tab
1881    * Unicode: U+0009 [Horizontal tab]
1882    * keyIdentifier: U+0009
1883    */
1884   9: 'Tab',
1885 
1886   /*
1887    * Key: Enter
1888    * Unicode: U+0010 [Line feed (LF) / New line (NL) / End of line (EOL)]
1889    *
1890    * Note: Taken from the Unicode characters list. If it ends up as a keyCode in 
1891    * some event, it's simply considered as being the Enter key.
1892    */
1893   10: 'Enter',
1894 
1895   /*
1896    * Key: NumPad_Center
1897    * Unicode: U+000C [Form feed]
1898    * keyIdentifier: Clear
1899    *
1900    * Note 1: This keyCode is used when NumLock is off, and the user pressed the 
1901    * 5 key on the numeric pad.
1902    *
1903    * Note 2: Safari 2 (older Webkit) assigns this keyCode to the NumLock key 
1904    * itself.
1905    */
1906   12: 'Clear',
1907 
1908   /*
1909    * Key: Enter
1910    * Unicode: U+000D [Carriage return (CR)]
1911    * keyIdentifier: Enter
1912    *
1913    * Note 1: This is the keyCode used by most of the Web browsers when the Enter 
1914    * key is pressed.
1915    *
1916    * Note 2: Gecko associates the DOM_VK_RETURN to this keyCode.
1917    */
1918   13: 'Enter',
1919 
1920   /*
1921    * Key: Enter
1922    * Unicode: U+000E [Shift out]
1923    *
1924    * Note: Taken from Gecko (DOM_VK_ENTER).
1925    */
1926   14: 'Enter',
1927 
1928   /*
1929    * Key: Shift
1930    * Unicode: U+0010 [Data link escape]
1931    * keyIdentifier: Shift
1932    *
1933    * Note: In older Safari (Webkit) versions Shift+Tab is assigned a different 
1934    * keyCode: keyCode 25.
1935    */
1936   16: 'Shift',
1937 
1938   /*
1939    * Key: Control
1940    * Unicode: U+0011 [Device control one]
1941    * keyIdentifier: Control
1942    */
1943   17: 'Control',
1944 
1945   /*
1946    * Key: Alt
1947    * Unicode: U+0012 [Device control two]
1948    * keyIdentifier: Alt
1949    */
1950   18: 'Alt',
1951 
1952   /*
1953    * Key: Pause
1954    * Unicode: U+0013 [Device control three]
1955    * keyIdentifier: Pause
1956    */
1957   19: 'Pause',
1958 
1959   /*
1960    * Key: CapsLock
1961    * Unicode: U+0014 [Device control four]
1962    * keyIdentifier: CapsLock
1963    */
1964   20: 'CapsLock',
1965 
1966   /*
1967    * Key: Cancel
1968    * Unicode: U+0018 [Cancel]
1969    * keyIdentifier: U+0018
1970    */
1971   24: 'Cancel',
1972 
1973   /*
1974    * Key: Escape
1975    * Unicode: U+001B [Escape]
1976    * keyIdentifier: U+001B
1977    */
1978   27: 'Escape',
1979 
1980   /*
1981    * Key: Space
1982    * Unicode: U+0020 [Space]
1983    * keyIdentifier: U+0020
1984    */
1985   32: 'Space',
1986 
1987   /*
1988    * Key: PageUp or NumPad_North_East
1989    * Unicode: U+0021 ! [Exclamation mark]
1990    * keyIdentifier: PageUp
1991    */
1992   33: 'PageUp',
1993 
1994   /*
1995    * Key: PageDown or NumPad_South_East
1996    * Unicode: U+0022 " [Quotation mark]
1997    * keyIdentifier: PageDown
1998    */
1999   34: 'PageDown',
2000 
2001   /*
2002    * Key: End or NumPad_South_West
2003    * Unicode: U+0023 # [Number sign]
2004    * keyIdentifier: PageDown
2005    */
2006   35: 'End',
2007 
2008   /*
2009    * Key: Home or NumPad_North_West
2010    * Unicode: U+0024 $ [Dollar sign]
2011    * keyIdentifier: Home
2012    */
2013   36: 'Home',
2014 
2015   /*
2016    * Key: Left or NumPad_West
2017    * Unicode: U+0025 % [Percent sign]
2018    * keyIdentifier: Left
2019    */
2020   37: 'Left',
2021 
2022   /*
2023    * Key: Up or NumPad_North
2024    * Unicode: U+0026 & [Ampersand]
2025    * keyIdentifier: Up
2026    */
2027   38: 'Up',
2028 
2029   /*
2030    * Key: Right or NumPad_East
2031    * Unicode: U+0027 ' [Apostrophe]
2032    * keyIdentifier: Right
2033    */
2034   39: 'Right',
2035 
2036   /*
2037    * Key: Down or NumPad_South
2038    * Unicode: U+0028 ( [Left parenthesis]
2039    * keyIdentifier: Down
2040    */
2041   40: 'Down',
2042 
2043   /*
2044    * Key: PrintScreen
2045    * Unicode: U+002C , [Comma]
2046    * keyIdentifier: PrintScreen
2047    */
2048   //44: 'PrintScreen',
2049 
2050   /*
2051    * Key: Insert or NumPad_Insert
2052    * Unicode: U+002D - [Hyphen-Minus]
2053    * keyIdentifier: Insert
2054    */
2055   45: 'Insert',
2056 
2057   /*
2058    * Key: Delete or NumPad_Delete
2059    * Unicode: U+002E . [Full stop / period]
2060    * keyIdentifier: U+007F
2061    */
2062   46: 'Delete',
2063 
2064   /*
2065    * Key: WinLeft
2066    * Unicode: U+005B [ [Left square bracket]
2067    * keyIdentifier: Win
2068    *
2069    * Disabled: rarely needed.
2070    */
2071   //91: 'Win',
2072 
2073   /*
2074    * Key: WinRight
2075    * Unicode: U+005C \ [Reverse solidus / Backslash]
2076    * keyIdentifier: Win
2077    */
2078   //92: 'Win',
2079 
2080   /*
2081    * Key: Menu/ContextMenu
2082    * Unicode: U+005D ] [Right square bracket]
2083    * keyIdentifier: ...
2084    *
2085    * Disabled: Is it Meta? Is it Menu, ContextMenu, what? Too much mess.
2086    */
2087   //93: 'ContextMenu',
2088 
2089   /*
2090    * Key: NumPad_0
2091    * Unicode: U+0060 ` [Grave accent]
2092    * keyIdentifier: 0
2093    */
2094   96: '0',
2095 
2096   /*
2097    * Key: NumPad_1
2098    * Unicode: U+0061 a [Latin small letter a]
2099    * keyIdentifier: 1
2100    */
2101   97: '1',
2102 
2103   /*
2104    * Key: NumPad_2
2105    * Unicode: U+0062 b [Latin small letter b]
2106    * keyIdentifier: 2
2107    */
2108   98: '2',
2109 
2110   /*
2111    * Key: NumPad_3
2112    * Unicode: U+0063 c [Latin small letter c]
2113    * keyIdentifier: 3
2114    */
2115   99: '3',
2116 
2117   /*
2118    * Key: NumPad_4
2119    * Unicode: U+0064 d [Latin small letter d]
2120    * keyIdentifier: 4
2121    */
2122   100: '4',
2123 
2124   /*
2125    * Key: NumPad_5
2126    * Unicode: U+0065 e [Latin small letter e]
2127    * keyIdentifier: 5
2128    */
2129   101: '5',
2130 
2131   /*
2132    * Key: NumPad_6
2133    * Unicode: U+0066 f [Latin small letter f]
2134    * keyIdentifier: 6
2135    */
2136   102: '6',
2137 
2138   /*
2139    * Key: NumPad_7
2140    * Unicode: U+0067 g [Latin small letter g]
2141    * keyIdentifier: 7
2142    */
2143   103: '7',
2144 
2145   /*
2146    * Key: NumPad_8
2147    * Unicode: U+0068 h [Latin small letter h]
2148    * keyIdentifier: 8
2149    */
2150   104: '8',
2151 
2152   /*
2153    * Key: NumPad_9
2154    * Unicode: U+0069 i [Latin small letter i]
2155    * keyIdentifier: 9
2156    */
2157   105: '9',
2158 
2159   /*
2160    * Key: NumPad_Multiply
2161    * Unicode: U+0070 j [Latin small letter j]
2162    * keyIdentifier: U+002A * [Asterisk / Star]
2163    */
2164   106: '*',
2165 
2166   /*
2167    * Key: NumPad_Plus
2168    * Unicode: U+0071 k [Latin small letter k]
2169    * keyIdentifier: U+002B + [Plus]
2170    */
2171   107: '+',
2172 
2173   /*
2174    * Key: NumPad_Minus
2175    * Unicode: U+0073 m [Latin small letter m]
2176    * keyIdentifier: U+002D + [Hyphen / Minus]
2177    */
2178   109: '-',
2179 
2180   /*
2181    * Key: NumPad_Period
2182    * Unicode: U+0074 n [Latin small letter n]
2183    * keyIdentifier: U+002E . [Period]
2184    */
2185   110: '.',
2186 
2187   /*
2188    * Key: NumPad_Division
2189    * Unicode: U+0075 o [Latin small letter o]
2190    * keyIdentifier: U+002F / [Solidus / Slash]
2191    */
2192   111: '/',
2193 
2194   112: 'F1',                // p
2195   113: 'F2',                // q
2196   114: 'F3',                // r
2197   115: 'F4',                // s
2198   116: 'F5',                // t
2199   117: 'F6',                // u
2200   118: 'F7',                // v
2201   119: 'F8',                // w
2202   120: 'F9',                // x
2203   121: 'F10',               // y
2204   122: 'F11',               // z
2205   123: 'F12',               // {
2206 
2207   /*
2208    * Key: Delete
2209    * Unicode: U+007F [Delete]
2210    * keyIdentifier: U+007F
2211    */
2212   127: 'Delete',
2213 
2214   /*
2215    * Key: NumLock
2216    * Unicode: U+0090 [Device control string]
2217    * keyIdentifier: NumLock
2218    */
2219   144: 'NumLock',
2220 
2221   186: ';',                 // º (Masculine ordinal indicator)
2222   187: '=',                 // »
2223   188: ',',                 // ¼
2224   189: '-',                 // ½
2225   190: '.',                 // ¾
2226   191: '/',                 // ¿
2227   192: '`',                 // À
2228   219: '[',                 // Û
2229   220: '\\',                // Ü
2230   221: ']',                 // Ý
2231   222: "'"                  // Þ (Latin capital letter thorn)
2232 
2233   //224: 'Win',               // à
2234   //229: 'WinIME',            // å or WinIME or something else in Webkit
2235   //255: 'NumLock',           // ÿ, Gecko and Chrome, Windows XP in VirtualBox
2236   //376: 'NumLock'            // Ÿ, Opera, Windows XP in VirtualBox
2237 };
2238 
2239 if ($.browser.gecko) {
2240   $.dom.keyCodes[3] = 'Cancel'; // DOM_VK_CANCEL
2241 }
2242 
2243 /**
2244  * @namespace Holds a list of common wrong key codes in Web browsers.
2245  *
2246  * @private
2247  */
2248 $.dom.keyCodes_fixes = {
2249   42:   $.dom.keyNames['*'],            // char * to key *
2250   47:   $.dom.keyNames['/'],            // char / to key /
2251   59:   $.dom.keyNames[';'],            // char ; to key ;
2252   61:   $.dom.keyNames['='],            // char = to key =
2253   96:   48,                             // NumPad_0 to char 0
2254   97:   49,                             // NumPad_1 to char 1
2255   98:   50,                             // NumPad_2 to char 2
2256   99:   51,                             // NumPad_3 to char 3
2257   100:  52,                             // NumPad_4 to char 4
2258   101:  53,                             // NumPad_5 to char 5
2259   102:  54,                             // NumPad_6 to char 6
2260   103:  55,                             // NumPad_7 to char 7
2261   104:  56,                             // NumPad_8 to char 8
2262   105:  57,                             // NumPad_9 to char 9
2263   //106:  56,                           // NumPad_Multiply to char 8
2264   //107:  187,                          // NumPad_Plus to key =
2265   109:  $.dom.keyNames['-'],            // NumPad_Minus to key -
2266   110:  $.dom.keyNames['.'],            // NumPad_Period to key .
2267   111:  $.dom.keyNames['/']             // NumPad_Division to key /
2268 };
2269 
2270 /**
2271  * @namespace Holds the list of broken key codes generated by older Webkit 
2272  * (Safari 2).
2273  *
2274  * @private
2275  */
2276 $.dom.keyCodes_Safari2 = {
2277   63232: $.dom.keyNames.Up,               // 38
2278   63233: $.dom.keyNames.Down,             // 40
2279   63234: $.dom.keyNames.Left,             // 37
2280   63235: $.dom.keyNames.Right,            // 39
2281   63236: $.dom.keyNames.F1,               // 112
2282   63237: $.dom.keyNames.F2,               // 113
2283   63238: $.dom.keyNames.F3,               // 114
2284   63239: $.dom.keyNames.F4,               // 115
2285   63240: $.dom.keyNames.F5,               // 116
2286   63241: $.dom.keyNames.F6,               // 117
2287   63242: $.dom.keyNames.F7,               // 118
2288   63243: $.dom.keyNames.F8,               // 119
2289   63244: $.dom.keyNames.F9,               // 120
2290   63245: $.dom.keyNames.F10,              // 121
2291   63246: $.dom.keyNames.F11,              // 122
2292   63247: $.dom.keyNames.F12,              // 123
2293   63248: $.dom.keyNames.PrintScreen,      // 44
2294   63272: $.dom.keyNames['Delete'],        // 46
2295   63273: $.dom.keyNames.Home,             // 36
2296   63275: $.dom.keyNames.End,              // 35
2297   63276: $.dom.keyNames.PageUp,           // 33
2298   63277: $.dom.keyNames.PageDown,         // 34
2299   63289: $.dom.keyNames.NumLock,          // 144
2300   63302: $.dom.keyNames.Insert            // 45
2301 };
2302 
2303 
2304 /**
2305  * A complex keyboard events cross-browser compatibility layer.
2306  *
2307  * <p>Unfortunately, due to the important differences across Web browsers, 
2308  * simply using the available properties in a single keyboard event is not 
2309  * enough to accurately determine the key the user pressed. Thus, one needs to 
2310  * have event handlers for all keyboard-related events <code>keydown</code>, 
2311  * <code>keypress</code> and <code>keyup</code>.
2312  *
2313  * <p>This class provides a complete keyboard event compatibility layer. For any 
2314  * new instance you provide the DOM element you want to listen events for, and 
2315  * the event handlers for any of the three events <code>keydown</code> 
2316  * / <code>keypress</code> / <code>keyup</code>.
2317  *
2318  * <p>Your event handlers will receive the original DOM Event object, with 
2319  * several new properties defined:
2320  *
2321  * <ul>
2322  *   <li><var>event.keyCode_</var> holds the correct code for event key.
2323  *
2324  *   <li><var>event.key_</var> holds the key the user pressed. It can be either 
2325  *   a key name like "PageDown", "Delete", "Enter", or it is a character like 
2326  *   "A", "1", or "[".
2327  *
2328  *   <li><var>event.charCode_</var> holds the Unicode character decimal code.
2329  *
2330  *   <li><var>event.char_</var> holds the character generated by the event.
2331  *
2332  *   <li><var>event.repeat_</var> is a boolean property telling if the 
2333  *   <code>keypress</code> event is repeated - the user is holding down the key 
2334  *   for a long-enough period of time to generate multiple events.
2335  * </ul>
2336  *
2337  * <p>The character-related properties, <var>charCode_</var> and 
2338  * <var>char_</var> are only available in the <code>keypress</code> and 
2339  * <code>keyup</code> event objects.
2340  *
2341  * <p>This class will ensure that the <code>keypress</code> event is always 
2342  * fired in Webkit and MSIE for all keys, except modifiers. For modifier keys 
2343  * like <kbd>Shift</kbd>, <kbd>Control</kbd>, and <kbd>Alt</kbd>, the 
2344  * <code>keypress</code> event will not be fired, even if the Web browser does 
2345  * it.
2346  *
2347  * <p>Some user agents like Webkit repeat the <code>keydown</code> event while 
2348  * the user holds down a key. This class will ensure that only the 
2349  * <code>keypress</code> event is repeated.
2350  *
2351  * <p>If you want to prevent the default action for an event, you should prevent 
2352  * it on <code>keypress</code>. This class will prevent the default action for 
2353  * <code>keydown</code> if need (in MSIE).
2354  *
2355  * @example
2356  * <code>var <var>klogger</var> = function (<var>ev</var>) {
2357  *   console.log(<var>ev</var>.type +
2358  *     ' keyCode_ ' + <var>ev</var>.keyCode_ +
2359  *     ' key_ ' + <var>ev</var>.key_ +
2360  *     ' charCode_ ' + <var>ev</var>.charCode_ +
2361  *     ' char_ ' + <var>ev</var>.char_ +
2362  *     ' repeat_ ' + <var>ev</var>.repeat_);
2363  * };
2364  *
2365  * var <var>kbListener</var> = new $.dom.KeyboardEventListener(window,
2366  *               {keydown: <var>klogger</var>,
2367  *                keypress: <var>klogger</var>,
2368  *                keyup: <var>klogger</var>});</code>
2369  *
2370  * // later when you're done...
2371  * <code><var>kbListener</var>.detach();</code>
2372  *
2373  * @class A complex keyboard events cross-browser compatibility layer.
2374  *
2375  * @param {Element} elem_ The DOM Element you want to listen events for.
2376  *
2377  * @param {Object} handlers_ The object holding the list of event handlers 
2378  * associated to the name of each keyboard event you want to listen. To listen 
2379  * for all the three keyboard events use <code>{keydown: <var>fn1</var>, 
2380  * keypress: <var>fn2</var>, keyup: <var>fn3</var>}</code>.
2381  *
2382  * @throws {TypeError} If the <var>handlers_</var> object does not contain any 
2383  * event handler.
2384  *
2385  * @see $.dom.KeyboardEvent.getKey The simpler approach to keyboard event 
2386  * compatibility.
2387  */
2388 $.dom.KeyboardEventListener = function (elem_, handlers_) {
2389   /*
2390     Technical details:
2391 
2392     For the keyup and keydown events the keyCode provided is that of the virtual 
2393     key irrespective of other modifiers (e.g. Shift). Generally, during the 
2394     keypress event, the keyCode holds the Unicode value of the character 
2395     resulted from the key press, say an alphabetic upper/lower-case char, 
2396     depending on the actual intent of the user and depending on the currently 
2397     active keyboard layout.
2398 
2399     Examples:
2400     * Pressing p you get keyCode 80 in keyup/keydown, and keyCode 112 in 
2401     keypress.  String.fromCharCode(80) = 'P' and String.fromCharCode(112) = 'p'.
2402     * Pressing P you get keyCode 80 in all events.
2403     * Pressing F1 you get keyCode 112 in keyup, keydown and keypress.
2404     * Pressing 9 you get keyCode 57 in all events.
2405     * Pressing Shift+9 you get keyCode 57 in keyup/keydown, and keyCode 40 in 
2406     keypress. String.fromCharCode(57) = '9' and String.fromCharCode(40) = '('.
2407 
2408     * Using the Greek layout when you press v on an US keyboard you get the 
2409     output character ω. The keyup/keydown events hold keyCode 86 which is V.  
2410     This does make sense, since it's the virtual key code we are dealing with 
2411     - not the character code, not the result of pressing the key. The keypress 
2412     event will hold keyCode 969 (ω).
2413 
2414     * Pressing NumPad_Minus you get keyCode 109 in keyup/keydown and keyCode 45 
2415     in keypress. Again, this happens because in keyup/keydown you don't get the 
2416     character code, you get the key code, as indicated above. For
2417     your information: String.fromCharCode(109) = 'm' and String.fromCharCode(45) 
2418     = '-'.
2419 
2420     Therefore, we need to map all the codes of several keys, like F1-F12, 
2421     Escape, Enter, Tab, etc. This map is held by $.dom.keyCodes. It associates, 
2422     for example, code 112 to F1, or 13 to Enter. This map is used to detect 
2423     virtual keys in all events.
2424 
2425     (This is only the general story, details about browser-specific differences 
2426     follow below.)
2427 
2428     If the code given by the browser doesn't appear in keyCode maps, it's used 
2429     as is.  The key_ returned is that of String.fromCharCode(keyCode).
2430 
2431     In all browsers we consider all events having keyCode <= 32, as being events  
2432     generated by a virtual key (not a character). As such, the keyCode value is 
2433     always searched in $.dom.keyCodes.
2434 
2435     As you might notice from the above description, in the keypress event we 
2436     cannot tell the difference from say F1 and p, because both give the code 
2437     112. In Gecko and Webkit we can tell the difference because these UAs also 
2438     set the charCode event property when the key generates a character. If F1 is 
2439     pressed, or some other virtual key, charCode is never set.
2440 
2441     In Opera the charCode property is never set. However, the 'which' event 
2442     property is not set for several virtual keys. This means we can tell the 
2443     difference between a character and a virtual key. However, there's a catch: 
2444     not *all* virtual keys have the 'which' property unset. Known exceptions: 
2445     Backspace (8), Tab (9), Enter (13), Shift (16), Control (17), Alt (18), 
2446     Pause (19), Escape (27), End (35), Home (36), Insert (45), Delete (46) and 
2447     NumLock (144). Given we already consider any keyCode <= 32 being one of some 
2448     virtual key, fewer exceptions remain. We only have the End, Home, Insert, 
2449     Delete and the NumLock keys which cannot be 100% properly detected in the 
2450     keypress event, in Opera. To properly detect End/Home we can check if the 
2451     Shift modifier is active or not. If the user wants # instead of End, then 
2452     Shift must be active. The same goes for $ and Home. Thus we now only have 
2453     the '-' (Insert) and the '.' (Delete) characters incorrectly detected as 
2454     being Insert/Delete.
2455     
2456     The above brings us to one of the main visible difference, when comparing 
2457     the $.dom.KeyboardEventListener class and the simple 
2458     $.dom.KeyboardEvent.getKey() function. In getKey(), for the keypress event 
2459     we cannot accurately determine the exact key, because it requires checking
2460     the keyCode used for the keydown event. The KeyboardEventListener
2461     class monitors all the keyboard events, ensuring a more accurate key 
2462     detection.
2463 
2464     Different keyboard layouts and international characters are generally 
2465     supported. Tested and known to work with the Cyrillic alphabet (Greek 
2466     keyboard layout) and with the US Dvorak keyboard layouts.
2467 
2468     Opera does not fire the keyup event for international characters when 
2469     running on Linux. For example, this happens with the Greek keyboard layout, 
2470     when trying Cyrillic characters.
2471 
2472     Gecko gives no keyCode/charCode/which for international characters when 
2473     running on Linux, in the keyup/keydown events. Thus, all such keys remain 
2474     unidentified for these two events. For the keypress event there are no 
2475     issues with such characters.
2476 
2477     Webkit and Konqueror 4 also implement the keyIdentifier property from the 
2478     DOM 3 Events specification. In theory, this should be great, but it's not 
2479     without problems.  Sometimes keyCode/charCode/which are all 0, but 
2480     keyIdentifier is properly set. For several virtual keys the keyIdentifier 
2481     value is simply 'U+0000'. Thus, the keyIdentifier is used only if the value 
2482     is not 'Unidentified' / 'U+0000', and only when keyCode/charCode/which are 
2483     not available.
2484 
2485     Konqueror 4 does not use the 'U+XXXX' notation for Unicode characters. It 
2486     simply gives the character, directly.
2487 
2488     Additionally, Konqueror seems to have some problems with several keyCodes in 
2489     keydown/keyup. For example, the key '[' gives keyCode 91 instead of 219.  
2490     Thus, it effectively gives the Unicode for the character, not the key code.  
2491     This issue is visible with other key as well.
2492 
2493     NumPad_Clear is unidentified on Linux in all browsers, but it works on 
2494     Windows.
2495 
2496     In MSIE the keypress event is only fired for characters and for Escape, 
2497     Space and Enter. Similarly, Webkit only fires the keypress event for 
2498     characters. However, Webkit does not fire keypress for Escape.
2499 
2500     International characters and different keyboard layouts seem to work fine in 
2501     MSIE as well.
2502 
2503     As of MSIE 4.0, the keypress event fires for the following keys:
2504       * Letters: A - Z (uppercase and lowercase)
2505       * Numerals: 0 - 9
2506       * Symbols: ! @ # $ % ^ & * ( ) _ - + = < [ ] { } , . / ? \ | ' ` " ~
2507       * System: Escape (27), Space (32), Enter (13)
2508 
2509     Documentation about the keypress event:
2510     http://msdn.microsoft.com/en-us/library/ms536939(VS.85).aspx
2511 
2512     As of MSIE 4.0, the keydown event fires for the following keys:
2513       * Editing: Delete (46), Insert (45)
2514       * Function: F1 - F12
2515       * Letters: A - Z (uppercase and lowercase)
2516       * Navigation: Home, End, Left, Right, Up, Down
2517       * Numerals: 0 - 9
2518       * Symbols: ! @ # $ % ^ & * ( ) _ - + = < [ ] { } , . / ? \ | ' ` " ~
2519       * System: Escape (27), Space (32), Shift (16), Tab (9)
2520 
2521     As of MSIE 5, the event also fires for the following keys:
2522       * Editing: Backspace (8)
2523       * Navigation: PageUp (33), PageDown (34)
2524       * System: Shift+Tab (9)
2525 
2526     Documentation about the keydown event:
2527     http://msdn.microsoft.com/en-us/library/ms536938(VS.85).aspx
2528 
2529     As of MSIE 4.0, the keyup event fires for the following keys:
2530       * Editing: Delete, Insert
2531       * Function: F1 - F12
2532       * Letters: A - Z (uppercase and lowercase)
2533       * Navigation: Home (36), End (35), Left (37), Right (39), Up (38), Down (40)
2534       * Numerals: 0 - 9
2535       * Symbols: ! @ # $ % ^ & * ( ) _ - + = < [ ] { } , . / ? \ | ' ` " ~
2536       * System: Escape (27), Space (32), Shift (16), Tab (9)
2537 
2538     As of MSIE 5, the event also fires for the following keys:
2539       * Editing: Backspace (8)
2540       * Navigation: PageUp (33), PageDown (34)
2541       * System: Shift+Tab (9)
2542 
2543     Documentation about the keyup event:
2544     http://msdn.microsoft.com/en-us/library/ms536940(VS.85).aspx
2545 
2546     For further gory details and a different implementation see:
2547     http://code.google.com/p/doctype/source/browse/trunk/goog/events/keycodes.js
2548     http://code.google.com/p/doctype/source/browse/trunk/goog/events/keyhandler.js
2549 
2550     Opera keydown/keyup:
2551       These events fire for all keys, including for modifiers.
2552       keyCode is always set.
2553       charCode is never set.
2554       which is always set.
2555       keyIdentifier is always undefined.
2556 
2557     Opera keypress:
2558       This event fires for all keys, except for modifiers themselves.
2559       keyCode is always set.
2560       charCode is never set.
2561       which is set for all characters. which = 0 for several virtual keys.
2562       which is known to be set for: Backspace (8), Tab (9), Enter (13), Shift 
2563       (16), Control (17), Alt (18), Pause (19), Escape (27), End (35), Home 
2564       (36), Insert (45), Delete (46), NumLock (144).
2565       which is known to be unset for: F1 - F12, PageUp (33), PageDown (34), Left 
2566       (37), Up (38), Right (39), Down (40).
2567       keyIdentifier is always undefined.
2568 
2569     MSIE keyup/keypress/keydown:
2570       Event firing conditions are described above.
2571       keyCode is always set.
2572       charCode is never set.
2573       which is never set.
2574       keyIdentifier is always undefined.
2575 
2576     Webkit keydown/keyup:
2577       These events fires for all keys, including for modifiers.
2578       keyCode is always set.
2579       charCode is never set.
2580       which is always set.
2581       keyIdentifier is always set.
2582 
2583     Webkit keypress:
2584       This event fires for characters keys, similarly to MSIE (see above info).
2585       keyCode is always set.
2586       charCode is always set for all characters.
2587       which is always set.
2588       keyIdentifier is null.
2589 
2590     Gecko keydown/keyup:
2591       These events fire for all keys, including for modifiers.
2592       keyCode is always set.
2593       charCode is never set.
2594       which is always set.
2595       keyIdentifier is always undefined.
2596 
2597     Gecko keypress:
2598       This event fires for all keys, except for modifiers themselves.
2599       keyCode is only set for virtual keys, not for characters.
2600       charCode is always set for all characters.
2601       which is always set for all characters and for the Enter virtual key.
2602       keyIdentifier is always undefined.
2603 
2604     Another important difference between the KeyboardEventListener class and the 
2605     getKey() function is that the class tries to ensure that the keypress event 
2606     is fired for the handler, even if the Web browser does not do it natively.  
2607     Also, the class tries to provide a consistent approach to keyboard event 
2608     repetition when the user holds down a key for longer periods of time, by 
2609     repeating only the keypress event.
2610 
2611     On Linux, Opera, Firefox and Konqueror do not repeat the keydown event, only 
2612     keypress. On Windows, Opera, Firefox and MSIE do repeat the keydown and 
2613     keypress events while the user holds down the key. Webkit  repeats the 
2614     keydown and the keypress (when it fires) events on both systems.
2615 
2616     The default action can be prevented for during keydown in MSIE, and during 
2617     keypress for the other browsers. In Webkit when keypress doesn't fire, 
2618     keydown needs to be prevented.
2619 
2620     The KeyboardEventListener class tries to bring consistency. The keydown 
2621     event never repeats, only the keypress event repeats and it always fires for 
2622     all keys. The keypress event never fires for modifiers. Events should always 
2623     be prevented during keypress - the class deals with preventing the event 
2624     during keydown or keypress as needed in Webkit and MSIE.
2625 
2626     If no code/keyIdentifier is given by the browser, the getKey() function 
2627     returns null. In the case of the KeyboardEventListener class, keyCode_ 
2628     / key_ / charCode_ / char_ will be null or undefined.
2629    */
2630 
2631   /**
2632    * During a keyboard event flow, this holds the current key code, starting 
2633    * from the <code>keydown</code> event.
2634    *
2635    * @private
2636    * @type Number
2637    */
2638   var keyCode_ = null;
2639 
2640   /**
2641    * During a keyboard event flow, this holds the current key, starting from the 
2642    * <code>keydown</code> event.
2643    *
2644    * @private
2645    * @type String
2646    */
2647   var key_ = null;
2648 
2649   /**
2650    * During a keyboard event flow, this holds the current character code, 
2651    * starting from the <code>keypress</code> event.
2652    *
2653    * @private
2654    * @type Number
2655    */
2656   var charCode_ = null;
2657 
2658   /**
2659    * During a keyboard event flow, this holds the current character, starting 
2660    * from the <code>keypress</code> event.
2661    *
2662    * @private
2663    * @type String
2664    */
2665   var char_ = null;
2666 
2667   /**
2668    * True if the current keyboard event is repeating. This happens when the user 
2669    * holds down a key for longer periods of time.
2670    *
2671    * @private
2672    * @type Boolean
2673    */
2674   var repeat_ = false;
2675 
2676 
2677   if (!handlers_) {
2678     throw new TypeError('The first argument must be of type an object.');
2679   }
2680 
2681   if (!handlers_.keydown && !handlers_.keypress && !handlers_.keyup) {
2682     throw new TypeError('The provided handlers object has no keyboard event' +
2683         'handler.');
2684   }
2685 
2686   if (handlers_.keydown && typeof handlers_.keydown != 'function') {
2687     throw new TypeError('The keydown event handler is not a function!');
2688   }
2689   if (handlers_.keypress && typeof handlers_.keypress != 'function') {
2690     throw new TypeError('The keypress event handler is not a function!');
2691   }
2692   if (handlers_.keyup && typeof handlers_.keyup != 'function') {
2693     throw new TypeError('The keyup event handler is not a function!');
2694   }
2695 
2696 
2697   /**
2698    * Attach the keyboard event listeners to the current DOM element.
2699    */
2700   this.attach = function () {
2701     keyCode_ = null;
2702     key_ = null;
2703     charCode_ = null;
2704     char_ = null;
2705     repeat_ = false;
2706 
2707     // FIXME: I have some ideas for a solution to the problem of having multiple 
2708     // event handlers like these attached to the same element. Only one should 
2709     // do all the needed work.
2710 
2711     elem_.addEventListener('keydown',  keydown,  false);
2712     elem_.addEventListener('keypress', keypress, false);
2713     elem_.addEventListener('keyup',    keyup,    false);
2714   };
2715 
2716   /**
2717    * Detach the keyboard event listeners from the current DOM element.
2718    */
2719   this.detach = function () {
2720     elem_.removeEventListener('keydown',  keydown,  false);
2721     elem_.removeEventListener('keypress', keypress, false);
2722     elem_.removeEventListener('keyup',    keyup,    false);
2723 
2724     keyCode_ = null;
2725     key_ = null;
2726     charCode_ = null;
2727     char_ = null;
2728     repeat_ = false;
2729   };
2730 
2731   /**
2732    * Dispatch an event.
2733    *
2734    * <p>This function simply invokes the handler for the event of the given 
2735    * <var>type</var>. The handler receives the <var>ev</var> event.
2736    *
2737    * @private
2738    *
2739    * @param {String} type The event type to dispatch.
2740    * @param {Event} ev The DOM Event object to dispatch to the handler.
2741    */
2742   function dispatch (type, ev) {
2743     if (!handlers_[type]) {
2744       return;
2745     }
2746 
2747     var handler = handlers_[type];
2748 
2749     if (type == ev.type) {
2750       handler.call(elem_, ev);
2751 
2752     } else {
2753       // This happens when the keydown event tries to dispatch a keypress event.
2754 
2755       // FIXME: I could use createEvent() ... food for thought for later
2756       var ev_new = {};
2757       $.extend(ev_new, ev);
2758       ev_new.type = type;
2759 
2760       // Make sure preventDefault() is not borked...
2761       ev_new.preventDefault = function () {
2762         ev.preventDefault();
2763       };
2764 
2765       handler.call(elem_, ev_new);
2766     }
2767   };
2768 
2769   /**
2770    * The <code>keydown</code> event handler. This function determines the key 
2771    * pressed by the user, and checks if the <code>keypress</code> event will 
2772    * fire in the current Web browser, or not. If it does not, a synthetic 
2773    * <code>keypress</code> event will be fired.
2774    *
2775    * @private
2776    * @param {Event} ev The DOM Event object.
2777    */
2778   function keydown (ev) {
2779     var prevKey = key_;
2780 
2781     charCode_ = null;
2782     char_ = null;
2783 
2784     findKeyCode(ev);
2785 
2786     ev.keyCode_ = keyCode_;
2787     ev.key_ = key_;
2788     ev.repeat_ = key_ && prevKey == key_ ? true : false;
2789 
2790     repeat_ = ev.repeat_;
2791 
2792     // When the user holds down a key for a longer period of time, the keypress 
2793     // event is generally repeated. However, in some user agents like Webkit 
2794     // keydown is repeated (and keypress if it fires keypress for the key). As 
2795     // such, we do not dispatch the keydown event when a key event starts to be 
2796     // repeated.
2797     if (!repeat_) {
2798       dispatch('keydown', ev);
2799     }
2800 
2801     // MSIE and Webkit only fire the keypress event for characters 
2802     // (alpha-numeric and symbols).
2803     if (!isModifierKey(key_) && !firesKeyPress(ev)) {
2804       ev.type_ = 'keydown';
2805       keypress(ev);
2806     }
2807   };
2808 
2809   /**
2810    * The <code>keypress</code> event handler. This function determines the 
2811    * character generated by the keyboard event.
2812    *
2813    * @private
2814    * @param {Event} ev The DOM Event object.
2815    */
2816   function keypress (ev) {
2817     // We reuse the keyCode_/key_ from the keydown event, because ev.keyCode 
2818     // generally holds the character code during the keypress event.
2819     // However, if keyCode_ is not available, try to determine the key for this 
2820     // event as well.
2821     if (!keyCode_) {
2822       findKeyCode(ev);
2823       repeat_ = false;
2824     }
2825 
2826     ev.keyCode_ = keyCode_;
2827     ev.key_ = key_;
2828 
2829     findCharCode(ev);
2830 
2831     ev.charCode_ = charCode_;
2832     ev.char_ = char_;
2833 
2834     // Any subsequent keypress event is considered a repeated keypress (the user 
2835     // is holding down the key).
2836     ev.repeat_ = repeat_;
2837     if (!repeat_) {
2838       repeat_ = true;
2839     }
2840 
2841     // All keys should dispatch a keypress event, except the modifiers.
2842     if (!isModifierKey(key_)) {
2843       dispatch('keypress', ev);
2844     }
2845   };
2846 
2847   /**
2848    * The <code>keyup</code> event handler.
2849    *
2850    * @private
2851    * @param {Event} ev The DOM Event object.
2852    */
2853   function keyup (ev) {
2854     /*
2855      * Try to determine the keyCode_ for keyup again, even if we might already 
2856      * have it from keydown. This is needed because the user might press some 
2857      * key which only generates the keydown and keypress events, after which 
2858      * a sudden keyup event is fired for a completely different key.
2859      *
2860      * Example: in Opera press F2 then Escape. It will first generate two 
2861      * events, keydown and keypress, for the F2 key. When you press Escape to 
2862      * close the dialog box, the script receives keyup for Escape.
2863      */
2864     findKeyCode(ev);
2865 
2866     ev.keyCode_ = keyCode_;
2867     ev.key_ = key_;
2868 
2869     // Provide the character info from the keypress event in keyup as well.
2870     ev.charCode_ = charCode_;
2871     ev.char_ = char_;
2872 
2873     dispatch('keyup', ev);
2874 
2875     keyCode_ = null;
2876     key_ = null;
2877     charCode_ = null;
2878     char_ = null;
2879     repeat_ = false;
2880   };
2881 
2882   /**
2883    * Tells if the <var>key</var> is a modifier or not.
2884    *
2885    * @private
2886    * @param {String} key The key name.
2887    * @returns {Boolean} True if the <var>key</var> is a modifier, or false if 
2888    * not.
2889    */
2890   function isModifierKey (key) {
2891     switch (key) {
2892       case 'Shift':
2893       case 'Control':
2894       case 'Alt':
2895       case 'Meta':
2896       case 'Win':
2897         return true;
2898       default:
2899         return false;
2900     }
2901   };
2902 
2903   /**
2904    * Tells if the current Web browser will fire the <code>keypress</code> event 
2905    * for the current <code>keydown</code> event object.
2906    *
2907    * @private
2908    *
2909    * @param {Event} ev The DOM Event object.
2910    *
2911    * @returns {Boolean} True if the Web browser will fire 
2912    * a <code>keypress</code> event, or false if not.
2913    */
2914   function firesKeyPress (ev) {
2915     if (!$.browser.msie && !$.browser.webkit) {
2916       return true;
2917     }
2918 
2919     // Check if the key is a character key, or not.
2920     // If it's not a character, then keypress will not fire.
2921     // Known exceptions: keypress fires for Space, Enter and Escape in MSIE.
2922     if (key_ && key_ != 'Space' && key_ != 'Enter' && key_ != 'Escape' && 
2923         key_.length != 1) {
2924       return false;
2925     }
2926 
2927     // Webkit doesn't fire keypress for Escape as well ...
2928     if ($.browser.webkit && key_ == 'Escape') {
2929       return false;
2930     }
2931 
2932     // MSIE does not fire keypress if you hold Control / Alt down, while Shift 
2933     // is off. Albeit, based on testing I am not completely sure if Shift needs 
2934     // to be down or not. Sometimes MSIE won't fire keypress even if I hold 
2935     // Shift down, and sometimes it does. Eh.
2936     if ($.browser.msie && !ev.shiftKey && (ev.ctrlKey || ev.altKey)) {
2937       return false;
2938     }
2939 
2940     return true;
2941   };
2942 
2943   /**
2944    * Determine the key and the key code for the current DOM Event object. This 
2945    * function updates the <var>keyCode_</var> and the <var>key_</var> variables 
2946    * to hold the result.
2947    *
2948    * @private
2949    * @param {Event} ev The DOM Event object.
2950    */
2951   function findKeyCode (ev) {
2952     /*
2953      * If the event has no keyCode/which/keyIdentifier values, then simply do 
2954      * not overwrite any existing keyCode_/key_.
2955      */
2956     if (ev.type == 'keyup' && !ev.keyCode && !ev.which && (!ev.keyIdentifier || 
2957           ev.keyIdentifier == 'Unidentified' || ev.keyIdentifier == 'U+0000')) {
2958       return;
2959     }
2960 
2961     keyCode_ = null;
2962     key_ = null;
2963 
2964     // Try to use keyCode/which.
2965     if (ev.keyCode || ev.which) {
2966       keyCode_ = ev.keyCode || ev.which;
2967 
2968       // Fix Webkit quirks
2969       if ($.browser.webkit) {
2970         // Old Webkit gives keyCode 25 when Shift+Tab is used.
2971         if (keyCode_ == 25 && this.shiftKey) {
2972           keyCode_ = $.dom.keyNames.Tab;
2973         } else if (keyCode_ >= 63232 && keyCode_ in $.dom.keyCodes_Safari2) {
2974           // Old Webkit gives wrong values for several keys.
2975           keyCode_ = $.dom.keyCodes_Safari2[keyCode_];
2976         }
2977       }
2978 
2979       // Fix keyCode quirks in all browsers.
2980       if (keyCode_ in $.dom.keyCodes_fixes) {
2981         keyCode_ = $.dom.keyCodes_fixes[keyCode_];
2982       }
2983 
2984       key_ = $.dom.keyCodes[keyCode_] || String.fromCharCode(keyCode_);
2985 
2986       return;
2987     }
2988 
2989     // Try to use ev.keyIdentifier. This is only available in Webkit and 
2990     // Konqueror 4, each having some quirks. Sometimes the property is needed, 
2991     // because keyCode/which are not always available.
2992 
2993     var key = null,
2994         keyCode = null,
2995         id = ev.keyIdentifier;
2996 
2997     if (!id || id == 'Unidentified' || id == 'U+0000') {
2998       return;
2999     }
3000 
3001     if (id.substr(0, 2) == 'U+') {
3002       // Webkit gives character codes using the 'U+XXXX' notation, as per spec.
3003       keyCode = parseInt(id.substr(2), 16);
3004 
3005     } else if (id.length == 1) {
3006       // Konqueror 4 implements keyIdentifier, and they provide the Unicode 
3007       // character directly, instead of using the 'U+XXXX' notation.
3008       keyCode = id.charCodeAt(0);
3009       key = id;
3010 
3011     } else {
3012       /*
3013        * Common keyIdentifiers like 'PageDown' are used as they are.
3014        * We determine the common keyCode used by Web browsers, from the 
3015        * $.dom.keyNames object.
3016        */
3017       keyCode_ = $.dom.keyNames[id] || null;
3018       key_ = id;
3019 
3020       return;
3021     }
3022 
3023     // Some keyIdentifiers like 'U+007F' (127: Delete) need to become key names.
3024     if (keyCode in $.dom.keyCodes && (keyCode <= 32 || keyCode == 127 || keyCode 
3025           == 144)) {
3026       key_ = $.dom.keyCodes[keyCode];
3027     } else {
3028       if (!key) {
3029         key = String.fromCharCode(keyCode);
3030       }
3031 
3032       // Konqueror gives lower-case chars
3033       key_ = key.toUpperCase();
3034       if (key != key_) {
3035         keyCode = key_.charCodeAt(0);
3036       }
3037     }
3038 
3039     // Correct the keyCode, make sure it's a common keyCode, not the Unicode 
3040     // decimal representation of the character.
3041     if (key_ == 'Delete' || key_.length == 1 && key_ in $.dom.keyNames) {
3042       keyCode = $.dom.keyNames[key_];
3043     }
3044 
3045     keyCode_ = keyCode;
3046   };
3047 
3048   /**
3049    * Determine the character and the character code for the current DOM Event 
3050    * object. This function updates the <var>charCode_</var> and the 
3051    * <var>char_</var> variables to hold the result.
3052    *
3053    * @private
3054    * @param {Event} ev The DOM Event object.
3055    */
3056   function findCharCode (ev) {
3057     charCode_ = null;
3058     char_ = null;
3059 
3060     // Webkit and Gecko implement ev.charCode.
3061     if (ev.charCode) {
3062       charCode_ = ev.charCode;
3063       char_ = String.fromCharCode(ev.charCode);
3064 
3065       return;
3066     }
3067 
3068     // Try the keyCode mess.
3069     if (ev.keyCode || ev.which) {
3070       var keyCode = ev.keyCode || ev.which;
3071 
3072       var force = false;
3073 
3074       // We accept some keyCodes.
3075       switch (keyCode) {
3076         case $.dom.keyNames.Tab:
3077         case $.dom.keyNames.Enter:
3078         case $.dom.keyNames.Space:
3079           force = true;
3080       }
3081 
3082       // Do not consider the keyCode a character code, if during the keydown 
3083       // event it was determined the key does not generate a character, unless 
3084       // it's Tab, Enter or Space.
3085       if (!force && key_ && key_.length != 1) {
3086         return;
3087       }
3088 
3089       // If the keypress event at hand is synthetically dispatched by keydown, 
3090       // then special treatment is needed. This happens only in Webkit and MSIE.
3091       if (ev.type_ == 'keydown') {
3092         var key = $.dom.keyCodes[keyCode];
3093         // Check if the keyCode points to a single character.
3094         // If it does, use it.
3095         if (key && key.length == 1) {
3096           charCode_ = key.charCodeAt(0); // keyCodes != charCodes
3097           char_ = key;
3098         }
3099 
3100       } else if (keyCode >= 32 || force) {
3101         // For normal keypress events, we are done.
3102         charCode_ = keyCode;
3103         char_ = String.fromCharCode(keyCode);
3104       }
3105 
3106       if (charCode_) {
3107         return;
3108       }
3109     }
3110 
3111     /*
3112      * Webkit and Konqueror do not provide a keyIdentifier in the keypress 
3113      * event, as per spec. However, in the unlikely case when the keyCode is 
3114      * missing, and the keyIdentifier is available, we use it.
3115      *
3116      * This property might be used when a synthetic keypress event is generated 
3117      * by the keydown event, and keyCode/charCode/which are all not available.
3118      */
3119 
3120     var c = null,
3121         charCode = null,
3122         id = ev.keyIdentifier;
3123 
3124     if (id && id != 'Unidentified' && id != 'U+0000' &&
3125         (id.substr(0, 2) == 'U+' || id.length == 1)) {
3126 
3127       // Characters in Konqueror...
3128       if (id.length == 1) {
3129         charCode = id.charCodeAt(0);
3130         c = id;
3131 
3132       } else {
3133         // Webkit uses the 'U+XXXX' notation as per spec.
3134         charCode = parseInt(id.substr(2), 16);
3135       }
3136 
3137       if (charCode == $.dom.keyNames.Tab ||
3138           charCode == $.dom.keyNames.Enter ||
3139           charCode >= 32 && charCode != 127 &&
3140           charCode != $.dom.keyNames.NumLock) {
3141 
3142         charCode_ = charCode;
3143         char_ = c || String.fromCharCode(charCode);
3144 
3145         return;
3146       }
3147     }
3148 
3149     // Try to use the key determined from the previous keydown event, if it 
3150     // holds a character.
3151     if (key_ && key_.length == 1) {
3152       charCode_ = key_.charCodeAt(0);
3153       char_ = key_;
3154     }
3155   };
3156 
3157   this.attach();
3158 };
3159 
3160 /**
3161  * Holds a DOM Keyboard Event method which provides a simple way to determine 
3162  * the key the user pressed.
3163  *
3164  * <p>By default, the global <var>KeyboardEvent.prototype</var> object is 
3165  * extended to contain the method defined in this namespace. If the 
3166  * <var>KeyboardEvent</var> object is not available in the browser, then the 
3167  * <var>Event.prototype</var> object is extended.
3168  *
3169  * <p>The <code>getKey()</code> method uses the <var>this</var> object to refer 
3170  * to the current event. As such, you need to pass the correct <var>this</var> 
3171  * object.
3172  *
3173  * <p>In the examples provided we will assume that the objects defined are 
3174  * already extended. Thus, code like <code><var>ev</var>.getKey()</code> can be 
3175  * written directly.
3176  *
3177  * <p>For more advanced keyboard event handling needs, please check out the 
3178  * {@link $.dom.KeyboardEventListener} class.
3179  *
3180  * @example
3181  * <code>var <var>klogger</var> = function (<var>ev</var>) {
3182  *   console.log(<var>ev</var>.type + ' key ' + <var>ev</var>.getKey());
3183  * };
3184  *
3185  * window.addEventListener('keydown',  <var>klogger</var>, false);
3186  * window.addEventListener('keypress', <var>klogger</var>, false);
3187  * window.addEventListener('keyup',    <var>klogger</var>, false);</code>
3188  *
3189  * @namespace Holds a DOM Keyboard Event method which provides a simple way to 
3190  * determine the key the user pressed.
3191  */
3192 $.dom.KeyboardEvent = {};
3193 
3194 /**
3195  * This method returns the character / key identifier for the current keyboard 
3196  * event.
3197  *
3198  * <p>Some of the key identifiers this method returns are listed in the <a 
3199  * href="http://www.w3.org/TR/DOM-Level-3-Events/keyset.html#KeySet-Set">DOM 
3200  * 3 Events specification</a>. The notable differences are that this method 
3201  * uses names for several of the Unicode characters in that list:
3202  *
3203  * <ul>
3204  *   <li>'U+0008' = 'Backspace'
3205  *   <li>'U+0009' = 'Tab'
3206  *   <li>'U+0018' = 'Cancel'
3207  *   <li>'U+001B' = 'Escape'
3208  *   <li>'U+0020' = 'Space'
3209  *   <li>'U+007F' = 'Delete'
3210  * </ul>
3211  *
3212  * <p>Additionally, all Unicode characters of the form 'U+XXXX' are converted to 
3213  * the actual character. For example 'U+0053' becomes 'S'.
3214  *
3215  * <p>This method is known to work in Opera, Firefox, Chrome, Safari, MSIE 
3216  * 8 and Konqueror.
3217  *
3218  * <p>For more advanced keyboard event handling, please check {@link 
3219  * $.dom.KeyboardEventListener}.
3220  *
3221  * @example
3222  * <code>var <var>dialog_keydown</var> = function (<var>ev</var>) {
3223  *   var <var>key</var> = <var>ev</var>.getKey();
3224  *   if (<var>key</var> == 'F1') {
3225  *     // show help
3226  *   } else if (<var>key</var> == 'Escape') {
3227  *     // close dialog
3228  *   } else if (<var>key</var> == 'Enter') {
3229  *     // accept changes and close dialog
3230  *   }
3231  * };
3232  *
3233  * window.addEventListener('keydown', <var>dialog_keydown</var>, false);</code>
3234  *
3235  * @returns {String} The character / key identifier for the current keyboard 
3236  * event. If the key is unrecognized null is returned.
3237  *
3238  * @see $.dom.KeyboardEventListener The more advanced keyboard event 
3239  * compatibility layer.
3240  */
3241 $.dom.KeyboardEvent.getKey = function () {
3242   // Use any existing key_/char_ property that might have been already generated 
3243   // by the "advanced" keyboard event handler.
3244   if (this.key_ || this.char_) {
3245     return this.key_ || this.char_;
3246   }
3247 
3248   var key = this.keyIdentifier,
3249       is_char = false,
3250       Unidentified = null,
3251       allow_keyId = false;
3252 
3253   // We allow the use of keyIdentifier only if no keyCode/charCode/which is 
3254   // available, as a 'backup'.
3255   if (!this.keyCode && !this.charCode && !this.which) {
3256     allow_keyId = true;
3257   }
3258 
3259   // Check if the browser implements DOM 3 Events keyIdentifier.
3260   // Currently, only Webkit and Konqueror implement this, albeit with some 
3261   // errors. For now it will report 'U+0000' for some virtual keys (like Alt).
3262   if (allow_keyId && key && key != 'Unidentified' && key != 'U+0000') {
3263     var c = null;
3264 
3265     // Unicode character, as per spec (in Webkit).
3266     if (key.substr(0, 2) == 'U+') {
3267       key = parseInt(key.substr(2), 16);
3268 
3269     } else if (key.length == 1) {
3270       // The character is directly provided by Konqueror, without using the 
3271       // 'U+XXXX' notation.
3272       c = key;
3273       key = key.charCodeAt(0);
3274 
3275     } else {
3276       // Use the keyIdentifier as is. We consider it's a key name like 
3277       // 'PageDown', 'Pause', etc.
3278       return key;
3279     }
3280 
3281     // All codes below 32 are virtual keys, as well as code 127 (the Delete 
3282     // key).
3283     if (key in $.dom.keyCodes && (key <= 32 || key == 127 || key == 144)) {
3284       key = $.dom.keyCodes[key];
3285     } else {
3286       key = (c || String.fromCharCode(key)).toUpperCase();
3287     }
3288 
3289     return key;
3290   }
3291 
3292   // Opera never uses charCode. However, it does set the .which property, when 
3293   // a character key is pressed. This only happens for the 'keypress' event.
3294   if (this.which && this.type == 'keypress' && $.browser.presto) {
3295     key = this.which;
3296 
3297     /*
3298      * Exceptions: Backspace (8), Tab (9), Enter (13), Shift (16), Control 
3299      * (17), Alt (18), Pause (19), Escape (27), End (35), Home (36), Insert 
3300      * (45), Delete (46) and NumLock (144).
3301      *
3302      * Here we only make an exception for End, Home, Insert, Delete and 
3303      * NumLock. The other key codes are automatically considered virtual keys, 
3304      * because keyCode <= 32.
3305      *
3306      * An additional requirement is for the user to not have the Shift key 
3307      * modifier active. This helps telling the difference between the 
3308      * character # and key End (both having code 35), and character $ and key 
3309      * Home (both having code 36).
3310      */
3311     if (this.shiftKey ||
3312         key != $.dom.keyNames.End && key != $.dom.keyNames.Home &&
3313         key != $.dom.keyNames.Insert && key != $.dom.keyNames['Delete'] &&
3314         key != $.dom.keyNames.NumLock) {
3315       is_char = true;
3316     }
3317 
3318   // Gecko always sets charCode for characters.
3319   } else if (this.charCode) {
3320     key = this.charCode;
3321     is_char = true;
3322 
3323   } else {
3324     key = this.keyCode || this.which;
3325   }
3326 
3327   if (!key || isNaN(key)) {
3328     return Unidentified;
3329   }
3330 
3331   // MSIE does not fire the keypress event for virtual keys, with the 
3332   // exception of Enter (13), Escape (27) and Space (32). Thus, we consider 
3333   // all such key codes as character codes in the keypress event.
3334   if ($.browser.msie && this.type == 'keypress' && !is_char &&
3335       key != $.dom.keyNames.Enter && key != $.dom.keyNames.Space &&
3336       key != $.dom.keyNames['Escape']) {
3337     is_char = true;
3338   }
3339 
3340   // Keys from code 32 to lower should not be considered chars.
3341   if (key <= 32 && is_char) {
3342     is_char = false;
3343   }
3344 
3345   if ($.browser.webkit) {
3346     // Old Webkit gives keyCode 25 when Shift+Tab is used.
3347     if (key == 25 && this.shiftKey) {
3348       key = $.dom.keyNames.Tab;
3349     } else if (key >= 63232 && $.dom.keyCodes_Safari2[key]) {
3350       // Old Webkit gives wrong values for several keys.
3351       key = $.dom.keyCodes_Safari2[key];
3352     }
3353   }
3354 
3355   var res = $.dom.keyCodes[key];
3356   if (is_char || !res) {
3357     return String.fromCharCode(key).toUpperCase();
3358   } else {
3359     return res;
3360   }
3361 
3362   return Unidentified;
3363 };
3364 
3365 
3366 /**
3367  * Creates a new DOMTokenList object.
3368  *
3369  * <p>The <a 
3370  * href="http://www.whatwg.org/specs/web-apps/current-work/#domtokenlist">DOMTokenList 
3371  * interface</a> is defined by the HTML 5 specification.  It is used for all 
3372  * HTMLElements for the new classList property, and for other properties on 
3373  * other HTML elements.
3374  *
3375  * <p>This library implements the DOMTokenList interface, to be used in Web 
3376  * browsers which do not have it natively implemented. The level of the 
3377  * implementation should be close to a native one, in terms of usage from other 
3378  * scripts. However, it's slower and might have some "gotchas" in corner-cases.
3379  *
3380  * @example
3381  * // Let's work with the list of class names for the document body.
3382  * <code>var <var>elem</var> = document.body;
3383  * var <var>list</var> = new $.dom.DOMTokenList(<var>elem</var>, 'className');
3384  * <var>list</var>.add('test');
3385  * <var>list</var>.add('test2');
3386  * <var>list</var>[0]; // returns 'test'
3387  * <var>list</var>.length; // returns 2
3388  * <var>elem</var>.className; // returns 'test test2'
3389  * <var>list</var>.toggle('test2'); // returns false
3390  * <var>list</var>.has('test2'); // returns false
3391  * <var>list</var>.has('test'); // returns true
3392  * <var>list</var>.remove('test');</code>
3393  *
3394  * @class Represents a DOMTokenList object.
3395  *
3396  * @param {HTMLElement} elem The element you want the DOMTokenList object for.
3397  * @param {String} attr The attribute you want the DOMTokenList for.
3398  *
3399  * @returns {DOMTokenList} The DOMTokenList object.
3400  */
3401 $.dom.DOMTokenList = function (elem, attr) {
3402   var _self = this, tokens = '', list = [];
3403 
3404   /*
3405    * Note: the implementation can have some "gotchas". For example:
3406    * elem.className = 'ana maria'
3407    * cl = elem.classList
3408    * cl[0] and cl[1] are available
3409    * elem.className = 'michael john'
3410    * cl[0] and cl[1] still hold the old values. They are updated only when the 
3411    * classList object is constructed, and when any of its methods are used (e.g.  
3412    * item(), add(), remove(), has(), toggle(), toString()).
3413    */
3414 
3415   /**
3416    * @private
3417    */
3418   function update () {
3419     if (!elem || !attr) {
3420       return;
3421     }
3422     list = [];
3423     tokens = '';
3424 
3425     var tmp_tokens = elem[attr];
3426     if (!tmp_tokens) {
3427       return;
3428     }
3429 
3430     tmp_tokens = tmp_tokens.replace(/\s+/g, ' ').trim();
3431 
3432     if (tmp_tokens == '') {
3433       return;
3434     }
3435 
3436     tokens = tmp_tokens;
3437 
3438     // remove any duplicate tokens
3439     var tmp_list = tokens.split(' ');
3440     tmp_list.forEach(function (val) {
3441       if (list.indexOf(val) == -1) {
3442         list.push(val);
3443       }
3444     });
3445 
3446     if (list.length != tmp_list.length) {
3447       tokens = elem[attr] = list.join(' ');
3448     }
3449 
3450     update_indexes();
3451   }
3452 
3453   /**
3454    * @private
3455    */
3456   function update_indexes () {
3457     var max = Math.max(len, list.length);
3458     for (var i = 0; i < max; i++) {
3459       if (list[i]) {
3460         _self[i] = list[i];
3461       } else {
3462         delete _self[i];
3463       }
3464     }
3465 
3466     len = list.length;
3467   }
3468 
3469   /**
3470    * The number of tokens in the current list.
3471    *
3472    * @type Number
3473    */
3474   this.length = 0;
3475 
3476   var len = 0;
3477   this.__defineGetter__('length', function () {
3478     update();
3479     return list.length;
3480   });
3481 
3482   /**
3483    * Retrieve the <var>index</var>th token.
3484    *
3485    * @param {Number} index The zero-based index/position of the token you want.
3486    *
3487    * @returns {String} The token found at the <var>index</var> position in the 
3488    * list.
3489    */
3490   this.item = function (index) {
3491     update();
3492     return list[index];
3493   };
3494 
3495   /**
3496    * Check if the <var>token</var> is in the current list.
3497    *
3498    * @param {String} token The token you want to check.
3499    *
3500    * @throws {DOMException.INVALID_CHARACTER_ERR|Error} If <var>token</var> 
3501    * contains a space character.
3502    *
3503    * @returns {Boolean} True is returned if the <var>token</var> was found in 
3504    * the current list, otherwise false is returned.
3505    */
3506   this.has = function (token) {
3507     if (!token) {
3508       return false;
3509     }
3510 
3511     if (token.indexOf(' ') != -1) {
3512       // This should be DOMException.INVALID_CHARACTER_ERR, but...
3513       throw new Error("The token must not contain spaces.");
3514     }
3515 
3516     update();
3517 
3518     if (list.indexOf(token) == -1) {
3519       return false;
3520     } else {
3521       return true;
3522     }
3523   };
3524 
3525   /**
3526    * Add the <var>token</var> to the current list.
3527    *
3528    * @param {String} token The token you want to add.
3529    *
3530    * @throws {DOMException.INVALID_CHARACTER_ERR|Error} If <var>token</var> 
3531    * contains a space character.
3532    */
3533   this.add = function (token) {
3534     if (!token) {
3535       return;
3536     }
3537 
3538     if (token.indexOf(' ') != -1) {
3539       // This should be DOMException.INVALID_CHARACTER_ERR, but...
3540       throw new Error("The token must not contain spaces.");
3541     }
3542 
3543     update();
3544 
3545     if (list.indexOf(token) != -1) {
3546       return;
3547     }
3548 
3549     if (tokens != '') {
3550       tokens += ' ';
3551     }
3552 
3553     tokens += token;
3554     list.push(token);
3555     this[len] = token;
3556     len++;
3557 
3558     if (elem && attr) {
3559       elem[attr] = tokens;
3560     }
3561   };
3562 
3563   /**
3564    * Remove the <var>token</var> from the current list.
3565    *
3566    * @param {String} token The token you want to remove.
3567    *
3568    * @throws {DOMException.INVALID_CHARACTER_ERR|Error} If <var>token</var> 
3569    * contains a space character.
3570    */
3571   this.remove = function (token) {
3572     if (!token) {
3573       return;
3574     }
3575 
3576     if (token.indexOf(' ') != -1) {
3577       // This should be DOMException.INVALID_CHARACTER_ERR, but...
3578       throw new Error("The token must not contain spaces.");
3579     }
3580 
3581     update();
3582 
3583     var pos = list.indexOf(token);
3584     if (pos == -1) {
3585       return;
3586     }
3587 
3588     list.splice(pos, 1);
3589     tokens = list.join(' ');
3590     update_indexes();
3591 
3592     if (elem && attr) {
3593       elem[attr] = tokens;
3594     }
3595   };
3596 
3597   /**
3598    * Toggle the presence of <var>token</var> in the current list.
3599    *
3600    * <p>If the <var>token</var> is found in the current list, it is removed.  
3601    * Otherwise, the <var>token</var> is added.
3602    *
3603    * @param {String} token The token you want to add/remove.
3604    *
3605    * @throws {DOMException.INVALID_CHARACTER_ERR|Error} If <var>token</var> 
3606    * contains a space character.
3607    *
3608    * @returns {Boolean} True is returned if the <var>token</var> was added.  
3609    * Otherwise, false is returned.
3610    */
3611   this.toggle = function (token) {
3612     if (!token) {
3613       return;
3614     }
3615 
3616     if (token.indexOf(' ') != -1) {
3617       // This should be DOMException.INVALID_CHARACTER_ERR, but...
3618       throw new Error("The token must not contain spaces.");
3619     }
3620 
3621     update();
3622 
3623     var pos = list.indexOf(token);
3624     if (pos == -1) {
3625       list.push(token);
3626     } else {
3627       list.splice(pos, 1);
3628     }
3629 
3630     tokens = list.join(' ');
3631     update_indexes();
3632 
3633     if (elem && attr) {
3634       elem[attr] = tokens;
3635     }
3636 
3637     return pos == -1 ? true : false;
3638   };
3639 
3640   /**
3641    * Convert the current list to a string. All the tokens are separated by 
3642    * a space character, something like "token1 token2 tokenn".
3643    *
3644    * @returns {String} The list of tokens, separated by a space character.
3645    */
3646   this.toString = function () {
3647     update();
3648     return list.join(' ');
3649   };
3650 
3651   update();
3652 };
3653 
3654 /**
3655  * @namespace Holds the HTMLElement-related methods.
3656  */
3657 $.dom.HTMLElement = {
3658   /**
3659    * The classList property.
3660    *
3661    * <p>Any HTML element will have the classList property. This allows you to 
3662    * quickly add/remove/read the list of class names associated with the 
3663    * element.
3664    *
3665    * @example
3666    * <code>var <var>elem</var> = document.body;
3667    * <var>elem</var>.classList.add('test');
3668    * <var>elem</var>.classList.add('test2');
3669    * <var>elem</var>.classList[0]; // returns 'test'
3670    * <var>elem</var>.classList.length; // returns 2
3671    * <var>elem</var>.classList.toggle('test2'); // returns false
3672    * <var>elem</var>.classList.has('test2'); // returns false
3673    * <var>elem</var>.classList.has('test'); // returns true
3674    * <var>elem</var>.classList.remove('test');</code>
3675    *
3676    * @field
3677    * @type $.dom.DOMTokenList
3678    * @see $.dom.DOMTokenList for more details
3679    */
3680   classList : function () {
3681     return new $.dom.DOMTokenList(this, 'className');
3682   }
3683 };
3684 
3685 $.dom.HTMLLinkElement = {
3686   relList : function () {
3687     return new $.dom.DOMTokenList(this, 'rel');
3688   }
3689 };
3690 
3691 $.dom.HTMLAnchorElement = {
3692   relList : function () {
3693     return new $.dom.DOMTokenList(this, 'rel');
3694   }
3695 };
3696 
3697 $.dom.HTMLAnchorElement = {
3698   relList : function () {
3699     return new $.dom.DOMTokenList(this, 'rel');
3700   }
3701 };
3702 
3703 $.dom.HTMLAreaElement = {
3704   relList : function () {
3705     return new $.dom.DOMTokenList(this, 'rel');
3706   }
3707 };
3708 
3709 
3710 /**
3711  * Get the computed style for a property of an element.
3712  *
3713  * @param {Element} elem The element you want to get the computed style from.
3714  *
3715  * @param {String} prop The style property name you want.
3716  *
3717  * @returns {String} The computed style for the given property.
3718  */
3719 $.dom.getStyle = function (elem, prop) {
3720   if (!elem) {
3721     return false;
3722   }
3723 
3724   var doc = elem.ownerDocument;
3725   if (!doc) {
3726     doc = document;
3727   }
3728 
3729   var win = doc.defaultView, cs = false;
3730 
3731   // <sarcasm type=":)">TODO:</sarcasm> MSIE is unsupported for now.
3732   if (win && win.getComputedStyle) {
3733     cs = win.getComputedStyle(elem, null);
3734   }
3735 
3736   if (cs) {
3737     return cs[prop];
3738   } else if (elem.style) {
3739     return elem.style[prop];
3740   }
3741 
3742   return false;
3743 };
3744 
3745 // Extend the global DOM object prototypes.
3746 (function () {
3747   if (!extend_objects) {
3748     return;
3749   }
3750 
3751   // Improve MSIE compatibility by allowing the extension of window.Element 
3752   // instead of window.Node.
3753   var node_proto = window.Node ? window.Node.prototype : (window.Element 
3754       ? window.Element.prototype : null);
3755 
3756   if (node_proto) {
3757     $.extend(node_proto, $.dom.Node);
3758   }
3759 
3760   var globals = ['Element', 'Text', 'Comment', 'NodeList', 'HTMLCollection', 
3761   'NamedNodeMap'];
3762 
3763   $.js.Array.forEach.call(globals, function (val) {
3764     if (window[val]) {
3765       $.extend(window[val].prototype, $.dom[val]);
3766     }
3767   });
3768 
3769   // Preferably pick KeyboardEvent, instead of the more-generic Event object.
3770   var kev_proto = window.KeyboardEvent ? window.KeyboardEvent.prototype 
3771     : (window.Event ? window.Event.prototype : null);
3772 
3773   if (kev_proto) {
3774     $.extend(kev_proto, $.dom.KeyboardEvent);
3775   }
3776 
3777   // The following chokes MSIE
3778   if (!window.DOMTokenList && !$.browser.msie) {
3779     //window.DOMTokenList = $.dom.DOMTokenList;
3780 
3781     if (window.HTMLElement) {
3782       HTMLElement.prototype.__defineGetter__('classList', 
3783           $.dom.HTMLElement.classList);
3784     }
3785 
3786     if (window.HTMLLinkElement) {
3787       HTMLLinkElement.prototype.__defineGetter__('relList', 
3788           $.dom.HTMLLinkElement.relList);
3789     }
3790     
3791     if (window.HTMLAnchorElement) {
3792       HTMLAnchorElement.prototype.__defineGetter__('relList', 
3793           $.dom.HTMLAnchorElement.relList);
3794     }
3795 
3796     if (window.HTMLAreaElement) {
3797       HTMLAreaElement.prototype.__defineGetter__('relList', 
3798           $.dom.HTMLAreaElement.relList);
3799     }
3800   }
3801 
3802   if ($.browser.msie && $.dom.EventTarget && document.attachEvent && 
3803       document.detachEvent && !document.addEventListener && 
3804       !document.removeEventListener) {
3805 
3806     // MSIE does not have EventTarget (only Webkit does). As such, we extend the 
3807     // Element object.
3808     var evt = $.dom.EventTarget, proto = window.Element.prototype;
3809 
3810     proto.addEventListener = evt.addEventListener;
3811     proto.removeEventListener = evt.removeEventListener;
3812 
3813     // Add the methods to the window object as well.
3814     window.addEventListener = evt.addEventListener;
3815     window.removeEventListener = evt.removeEventListener;
3816 
3817     // Add the preventDefault() method to MSIE events.
3818     $.extend(window.Event.prototype, $.dom.Event);
3819   }
3820 
3821   // Dear Microsoft, ...
3822   if (!window.Node) {
3823     window.Node = {
3824       ELEMENT_NODE                   : 1,
3825       ATTRIBUTE_NODE                 : 2,
3826       TEXT_NODE                      : 3,
3827       CDATA_SECTION_NODE             : 4,
3828       ENTITY_REFERENCE_NODE          : 5,
3829       ENTITY_NODE                    : 6,
3830       PROCESSING_INSTRUCTION_NODE    : 7,
3831       COMMENT_NODE                   : 8,
3832       DOCUMENT_NODE                  : 9,
3833       DOCUMENT_TYPE_NODE             : 10,
3834       DOCUMENT_FRAGMENT_NODE         : 11,
3835       NOTATION_NODE                  : 12
3836     };
3837   }
3838 })(); // end of extend_objects
3839 
3840 })();
3841 
3842 
3843 // vim:set spell spl=en fo=wan1croqlt tw=80 ts=2 sw=2 sts=2 sta et ai cin fenc=utf-8 ff=unix:
3844 
3845