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