1 /*! 2 Copyright 2010 British Broadcasting Corporation 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 /** 17 @name glow 18 @namespace 19 @version @VERSION@ 20 @description The glow library namespace 21 The library can also be used as a function, which is a shortcut to 22 {@link glow.NodeList}. 23 24 @example 25 var links = glow('a'); 26 // is the same as 27 var links = new glow.NodeList('a'); 28 */ 29 if (!window.Glow) { // loading packages via user SCRIPT tags? 30 window.Glow = { 31 provide: function(f) { 32 f(glow); 33 }, 34 complete: function(n, version) { 35 glow.version = version; 36 } 37 }; 38 39 window.glow = function(nodeListContents) { 40 return new glow.NodeList(nodeListContents); 41 }; 42 43 glow.load = function() { 44 throw new Error('Method load() is not available without glow.js'); 45 } 46 } 47 48 49 Glow.provide(function(glow) { 50 /*!debug*/ 51 var glowbug = { 52 errors: [] 53 , 54 log: function(message, fileName, lineNumber) { 55 this._add('Log', message, fileName, lineNumber); 56 } 57 , 58 warn: function(message, fileName, lineNumber) { 59 this._add('Warn', message, fileName, lineNumber); 60 } 61 , 62 error: function(message, fileName, lineNumber) { 63 this._add('Error', message, fileName, lineNumber); 64 } 65 , 66 _add: function(level, message, fileName, lineNumber) { 67 var e = new Error(message, fileName, lineNumber); 68 69 e.message = message; 70 e.name = 'Glow'+level; 71 e.level = level.toLowerCase(); 72 73 var match = /\[([^\]]+)\]/.exec(message); 74 if (match) e.type = match[1]; // may be undefined 75 else e.type = 'message'; 76 77 this.errors.push(e); 78 79 this.out(e); 80 } 81 , 82 out: function(e) { 83 var message = '['+e.level+'] '+e.message; 84 85 if (window.console) { 86 if (e.level === 'warn' && window.console.warn) { 87 console.warn(message); 88 } 89 else if (e.level === 'error' && window.console.error) { 90 console.error(message); 91 } 92 else if (window.console.log) { 93 console.log(message); 94 } 95 } 96 else if (window.opera && opera.postError) { 97 opera.postError(message); 98 } 99 else { // use our own console 100 glowbug.console.log(e.level, message); 101 } 102 } 103 }; 104 105 glowbug.console = { 106 messages: [], 107 log: function(level, message) { 108 if (!this._w) { 109 try { 110 this._w = window.open('', 'report', 'width=350,height=250,menubar=0,toolbar=0,location=no,status=0,scrollbars=1,resizable=1'); 111 this._w.document.write( 112 '<html><head><title>Console<\/title><style>body{background-color: #ddd;} .message{background-color:#FFF;padding:4px;margin:0px;border-bottom:1px solid #ccc;} .warn {background-color: #E5E6B6;} .error{background-color: #D39C9E;}<\/style><\/head>' 113 + '<body style="font: 11px monaco"><code id="messages"><\/code><\/body><\/html>' 114 ) 115 this._w.document.close(); 116 } 117 catch(ignored) { 118 this._w = null; 119 } 120 } 121 122 if (this._w) { 123 var p = this._w.document.createElement('P'); 124 p.className = 'message ' + level; 125 p.innerHTML = message; 126 this._w.document.getElementById('messages').appendChild(p); 127 128 var dh = this._w.document.body.scrollHeight 129 var ch = this._w.document.body.clientHeight 130 if (dh > ch) { this._w.scrollTo(0, dh-ch); } 131 } 132 } 133 } 134 if (typeof glowbug != 'undefined') { glow.debug = glowbug; } 135 /*gubed!*/ 136 }); 137 Glow.provide(function(glow) { 138 /** 139 @name glow.env 140 @namespace 141 @description Information about the browser and characteristics 142 */ 143 144 // parse the useragent string, setting NaN if match isn't found 145 var ua = navigator.userAgent.toLowerCase(), 146 nanArray = [0, NaN], 147 opera = (/opera[\s\/]([\w\.]+)/.exec(ua) || nanArray)[1], 148 ie = opera ? NaN : (/msie ([\w\.]+)/.exec(ua) || nanArray)[1], 149 gecko = (/rv:([\w\.]+).*gecko\//.exec(ua) || nanArray)[1], 150 webkit = (/applewebkit\/([\w\.]+)/.exec(ua) || nanArray)[1], 151 khtml = (/khtml\/([\w\.]+)/.exec(ua) || nanArray)[1], 152 toNumber = parseFloat, 153 env = {}; 154 155 /** 156 @name glow.env.gecko 157 @type number 158 @description Gecko version number to one decimal place (eg 1.9) or NaN on non-gecko browsers. 159 The most popular browser using the Gecko engine is Firefox. 160 161 @see <a href="http://en.wikipedia.org/wiki/Gecko_(layout_engine)#Usage">Versions of Gecko used by browsers</a> 162 163 @example 164 if (glow.env.gecko < 1.9) { 165 // runs in Firefox 2 and other browsers that use Gecko earlier than 1.9 166 } 167 */ 168 env.gecko = toNumber(gecko); 169 170 /** 171 @name glow.env.ie 172 @type number 173 174 @description IE version number to one decimal place (eg 6.0) or NaN on non-IE browsers. 175 This number will also be populated for browser based on IE's trident engine 176 177 @example 178 if (glow.env.ie < 9) { 179 // runs in IE pre-9.0 180 glow('#content').css('background', 'deadmoomin.png'); 181 } 182 */ 183 env.ie = toNumber(ie); 184 185 /** 186 @name glow.env.opera 187 @type number 188 189 @description Opera version number to one decimal place (eg 10.0) or NaN on non-Opera browsers. 190 191 @example 192 if (glow.env.opera < 10) { 193 // runs in Opera pre-10.0 194 } 195 */ 196 env.opera = toNumber(opera); 197 198 /** 199 @name glow.env.webkit 200 @type number 201 202 @description Webkit version number to one decimal place (eg 531.9) or NaN on non-Webkit browsers. 203 Safari and Google Chrome are the most popular browsers using Webkit. 204 205 @see <a href="http://en.wikipedia.org/wiki/Safari_version_history#Release_history">Versions of Webkit used by Safari</a> 206 @see <a href="http://en.wikipedia.org/wiki/Google_Chrome#Release_history">Versions of Webkit used by Google Chrome</a> 207 208 @example 209 if (glow.env.webkit < 526) { 210 // runs in Safari pre-4.0, and Chrome pre-1.0 211 } 212 */ 213 env.webkit = toNumber(webkit); 214 215 /** 216 @name glow.env.khtml 217 @type number 218 219 @description KHTML version number to one decimal place or NaN on non-KHTML browsers. 220 Konqueror is the most popular browsers using KHTML. 221 */ 222 env.khtml = toNumber(khtml); 223 224 /** 225 @name glow.env.standardsMode 226 @type boolean 227 @description True if the browser reports itself to be in 'standards mode' 228 Otherwise, the browser is in 'quirks mode' 229 230 @see <a href="http://en.wikipedia.org/wiki/Quirks_mode">Quirks Mode vs Standards Mode</a> 231 */ 232 env.standardsMode = document.compatMode != "BackCompat" && (!env.ie || env.ie >= 6); 233 234 /** 235 @name glow.env.version 236 @type string 237 @description Version number of the browser in use as a string. 238 This caters for version numbers that aren't 'real' numbers, like "7b" or "1.9.1" 239 */ 240 env.version = ie || gecko || webkit || opera || khtml || ''; 241 242 // export 243 glow.env = env; 244 }); 245 // start-source: core/ready.js 246 /*debug*///log.info('executing core/ready.js'); 247 Glow.provide( 248 function(glow) { 249 var readyQueue = [], 250 domReadyQueue = [], 251 blockersActive = 0, 252 processingReadyQueue = false; 253 254 glow._readyBlockers = {}; 255 256 /*debug*///log.info('overwriting Glow ready with glow.ready'); 257 glow.ready = function (f) { /*debug*///log.info('glow.ready()'); 258 if (this.isReady) { 259 f(); 260 } 261 else { 262 readyQueue.push(f); 263 } 264 return glow; 265 }; 266 267 glow.onDomReady = function(f) { 268 //just run function if already ready 269 if (glow.isDomReady) { 270 f(); 271 } 272 else { 273 domReadyQueue.push(f); 274 } 275 }; 276 277 glow._addReadyBlock = function(name) { /*debug*///log.info('_addReadyBlock('+name+')'); 278 if (typeof glow._readyBlockers[name] === 'undefined') { glow._readyBlockers[name] = 0; } 279 glow._readyBlockers[name]++; 280 glow.isReady = false; 281 blockersActive++; /*debug*///log.info(' » blockersActive '+blockersActive+'.'); 282 return glow; 283 } 284 285 glow._removeReadyBlock = function(name) { /*debug*///log.info('_removeReadyBlock('+name+')'); 286 if (glow._readyBlockers[name]) { 287 glow._readyBlockers[name]--; 288 blockersActive--; /*debug*///log.info(' » blockersActive '+blockersActive+'.'); 289 // if we're out of blockers 290 if (!blockersActive) { 291 // call our queue 292 glow.isReady = true; 293 runReadyQueue(); 294 } 295 } 296 return glow; 297 } 298 299 // add blockers for any packages that started loading before core (this package) was built 300 if (glow._build) { // only defined when using big Glow 301 for (var i = 0, len = glow._build.loading.length; i < len; i++) { 302 glow._addReadyBlock('glow_loading_'+glow._build.loading[i]); 303 } 304 305 for (var j = 0, lenj = glow._build.callbacks.length; j < lenj; j++) { 306 if (glow._addReadyBlock) { glow._addReadyBlock('glow_loading_loadedcallback'); } 307 } 308 } 309 310 function runDomReadyQueue() { /*debug*///log.info('runDomReadyQueue()'); 311 glow.isDomReady = true; 312 // run all functions in the array 313 for (var i = 0, len = domReadyQueue.length; i < len; i++) { 314 domReadyQueue[i](); 315 } 316 } 317 318 function runReadyQueue() { /*debug*///log.info('runReadyQueue()'); 319 // if we're already processing the queue, just exit, the other instance will take care of it 320 if (processingReadyQueue) { return; } 321 322 /*debug*///log.info('readyQueue: '+readyQueue.length); 323 processingReadyQueue = true; 324 while (readyQueue.length) { 325 var callback = readyQueue.shift(); 326 /*debug*///log.info('callback: '+callback); 327 callback(glow); 328 329 // check if the previous function has created a blocker 330 if (blockersActive) { break; } 331 } 332 processingReadyQueue = false; 333 } 334 335 /** 336 @private 337 @function 338 @name bindReady 339 @description Add listener to document to detect when page is ready. 340 */ 341 function bindReady() { /*debug*///log.info('bindReady()'); 342 //don't do this stuff if the dom is already ready 343 if (glow.isDomReady) { return; } 344 glow._addReadyBlock('glow_domReady'); // wait for dom to be ready 345 346 function onReady() { /*debug*///log.info('onReady()'); 347 runReadyQueue(); 348 glow._removeReadyBlock('glow_domReady'); 349 } 350 351 if (document.readyState == 'complete') { // already here! 352 /*debug*///log.info('already complete'); 353 onReady(); 354 } 355 else if (glow.env.ie && document.attachEvent) { /*debug*///log.info('bindready() - document.attachEvent'); 356 // like IE 357 358 // not an iframe... 359 if (document.documentElement.doScroll && window == top) { 360 (function() { /*debug*///log.info('doScroll'); 361 try { 362 document.documentElement.doScroll('left'); 363 } 364 catch(error) { 365 setTimeout(arguments.callee, 0); 366 return; 367 } 368 369 // and execute any waiting functions 370 onReady(); 371 })(); 372 } 373 else { 374 // an iframe... 375 document.attachEvent( 376 'onreadystatechange', 377 function() { /*debug*///log.info('onreadystatechange'); 378 if (document.readyState == 'complete') { 379 document.detachEvent('onreadystatechange', arguments.callee); 380 onReady(); 381 } 382 } 383 ); 384 } 385 } 386 else if (document.readyState) { /*debug*///log.info('bindready() - document.readyState'); 387 // like pre Safari 388 (function() { /*debug*///log.info('loaded|complete'); 389 if ( /loaded|complete/.test(document.readyState) ) { 390 onReady(); 391 } 392 else { 393 setTimeout(arguments.callee, 0); 394 } 395 })(); 396 } 397 else if (document.addEventListener) {/*debug*///log.info('bindready() - document.addEventListener'); 398 // like Mozilla, Opera and recent webkit 399 document.addEventListener( 400 'DOMContentLoaded', 401 function(){ /*debug*///log.info('glow DOMContentLoaded'); 402 document.removeEventListener('DOMContentLoaded', arguments.callee, false); 403 onReady(); 404 }, 405 false 406 ); 407 } 408 else { 409 throw new Error('Unable to bind glow ready listener to document.'); 410 } 411 } 412 413 glow.notSupported = ( // here are the browsers we don't support 414 glow.env.ie < 6 || 415 (glow.env.gecko < 1.9 && !/^1\.8\.1/.test(env.version)) || 416 glow.env.opera < 9 || 417 glow.env.webkit < 412 418 ); 419 // deprecated 420 glow.isSupported = !glow.notSupported; 421 422 // block 'ready' if browser isn't supported 423 if (glow.notSupported) { 424 glow._addReadyBlock('glow_browserSupport'); 425 } 426 427 bindReady(); 428 } 429 ); 430 // end-source: core/ready.js 431 /** 432 @name glow.util 433 @namespace 434 @description Core JavaScript helpers 435 */ 436 Glow.provide(function(glow) { 437 var util = {}, 438 undefined; 439 440 /** 441 @name glow.util.apply 442 @function 443 @description Copies properties from one object to another 444 All properties from 'source' will be copied onto 445 'destination', potentially overwriting existing properties 446 on 'destination'. 447 448 Properties from 'source's prototype chain will not be copied. 449 450 @param {Object} destination Destination object 451 452 @param {Object} source Properties of this object will be copied onto the destination 453 454 @returns {Object} The destination object. 455 456 @example 457 var obj = glow.util.apply({foo: "hello", bar: "world"}, {bar: "everyone"}); 458 //results in {foo: "hello", bar: "everyone"} 459 */ 460 util.apply = function(destination, source) { 461 /*!debug*/ 462 if (arguments.length != 2) { 463 glow.debug.warn('[wrong count] glow.util.apply expects 2 arguments, not '+arguments.length+'.'); 464 } 465 if (typeof destination != 'object') { 466 glow.debug.warn('[wrong type] glow.util.apply expects argument "destination" to be of type object, not ' + typeof destination + '.'); 467 } 468 if (typeof source != 'object') { 469 glow.debug.warn('[wrong type] glow.util.apply expects argument "source" to be of type object, not ' + typeof source + '.'); 470 } 471 /*gubed!*/ 472 for (var i in source) { 473 if ( source.hasOwnProperty(i) ) { 474 destination[i] = source[i]; 475 } 476 } 477 return destination; 478 }; 479 480 /** 481 @name glow.util.extend 482 @function 483 @description Copies the prototype of one object to another 484 The 'subclass' can also access the 'base class' via subclass.base 485 486 @param {Function} sub Class which inherits properties. 487 @param {Function} base Class to inherit from. 488 @param {Object} additionalProperties An object of properties and methods to add to the subclass. 489 490 @example 491 function MyClass(arg) { 492 this.prop = arg; 493 } 494 MyClass.prototype.showProp = function() { 495 alert(this.prop); 496 }; 497 function MyOtherClass(arg) { 498 //call the base class's constructor 499 arguments.callee.base.apply(this, arguments); 500 } 501 glow.util.extend(MyOtherClass, MyClass, { 502 setProp: function(newProp) { 503 this.prop = newProp; 504 } 505 }); 506 507 var test = new MyOtherClass("hello"); 508 test.showProp(); // alerts "hello" 509 test.setProp("world"); 510 test.showProp(); // alerts "world" 511 */ 512 util.extend = function(sub, base, additionalProperties) { 513 /*!debug*/ 514 if (arguments.length < 2) { 515 glow.debug.warn('[wrong count] glow.util.extend expects at least 2 arguments, not '+arguments.length+'.'); 516 } 517 if (typeof sub != 'function') { 518 glow.debug.error('[wrong type] glow.util.extend expects argument "sub" to be of type function, not ' + typeof sub + '.'); 519 } 520 if (typeof base != 'function') { 521 glow.debug.error('[wrong type] glow.util.extend expects argument "base" to be of type function, not ' + typeof base + '.'); 522 } 523 /*gubed!*/ 524 var f = function () {}, p; 525 f.prototype = base.prototype; 526 p = new f(); 527 sub.prototype = p; 528 p.constructor = sub; 529 sub.base = base; 530 if (additionalProperties) { 531 util.apply(sub.prototype, additionalProperties); 532 } 533 }; 534 535 // export 536 glow.util = util; 537 }); 538 Glow.provide(function(glow) { 539 /** 540 @name glow.events 541 @namespace 542 @description Handling custom events 543 */ 544 var events = {}; 545 546 /* storage variables */ 547 548 var eventListeners = {}, 549 eventId = 1, /* TODO: camelCase */ 550 objIdCounter = 1, 551 eventKey = '__eventId' + glow.UID; 552 553 554 /** 555 @name glow.events.addListeners 556 @function 557 @param {Object[]} attachTo Array of objects to add listeners to. 558 @param {string} name Name of the event to listen for. 559 Event names are case sensitive. 560 @param {function} callback Function to call when the event is fired. 561 The callback will be passed a single event object. The type of this 562 object depends on the event (see documentation for the event 563 you're listening to). 564 @param {Object} [thisVal] Value of 'this' within the callback. 565 By default, this is the object being listened to. 566 @see glow.events.Target#fire 567 @description Convenience method to add listeners to many objects at once. 568 If you want to add a listener to a single object, use its 569 'on' method. 570 */ 571 events.addListeners = function (attachTo, name, callback, thisVal) { 572 var listenerIds = [], 573 objIdent, 574 listener, 575 eventsOnObject, 576 currentListeners; 577 578 //attach the event for each element, return an array of listener ids 579 var i = attachTo.length; 580 while (i--) { 581 objIdent = attachTo[i][eventKey]; 582 if (!objIdent){ 583 objIdent = attachTo[i][eventKey] = objIdCounter++; 584 } 585 586 listener = [ callback, thisVal ]; 587 eventsOnObject = eventListeners[objIdent]; 588 if(!eventsOnObject){ 589 eventsOnObject = eventListeners[objIdent] = {}; 590 } 591 592 currentListeners = eventsOnObject[name]; 593 if(!currentListeners){ 594 eventsOnObject[name] = [listener]; 595 } 596 else{ 597 currentListeners[currentListeners.length] = listener; 598 } 599 } 600 return events; 601 }; 602 603 events._getPrivateEventKey = function(node) { 604 if (!node[eventKey]) { 605 node[eventKey] = objid++; 606 } 607 608 return node[eventKey]; 609 } 610 611 /** 612 @name glow.events.fire 613 @function 614 @param {Object[]} items Array of objects to add listeners to 615 @param {string} eventName Name of the event to fire 616 @param {glow.events.Event|Object} [event] Event object to pass into listeners. 617 You can provide a simple object of key-value pairs which will 618 be added as properties on the glow.events.Event instance. 619 620 @description Convenience method to fire events on multiple items at once. 621 If you want to fire events on a single object, use its 622 'fire' method. 623 */ 624 625 events.fire = function (items, eventName, event) { 626 if (! event) { 627 event = new events.Event(); 628 } 629 else if ( event.constructor === Object ) { 630 event = new events.Event( event ) 631 } 632 633 // for loop, because order matters! 634 for(var i = 0, len = items.length; i < len; i++) { 635 callListeners(items[i], eventName, event); 636 } 637 638 return event; 639 }; 640 641 642 /** 643 @name glow.events-callListeners 644 @private 645 */ 646 function callListeners(item, eventName, event, thisVal) { 647 var objIdent = item[eventKey], 648 listenersForEvent, 649 returnedVal; 650 651 if (!objIdent || !eventListeners[objIdent]) { 652 return event; 653 } 654 655 listenersForEvent = eventListeners[objIdent][eventName]; 656 657 if (!listenersForEvent) { 658 return event; 659 } 660 // Slice to make sure we get a unique copy. 661 listenersForEvent = listenersForEvent.slice(0); 662 for (var i = 0, len = listenersForEvent.length; i < len; i++){ 663 returnVal = listenersForEvent[i][0].call((listenersForEvent[i][1] || thisVal || item), event); 664 if (returnVal === false){ 665 event.preventDefault(); 666 } 667 } 668 669 return event; 670 } 671 events._callListeners = callListeners; 672 673 674 /** 675 @name glow.events.removeAllListeners 676 @function 677 @param {Object[]} items Items to remove events from 678 @description Removes all listeners attached to a given object. 679 This removes not only listeners you added, but listeners others 680 added too. For this reason it should only be used as part of a cleanup 681 operation on objects that are about to be destroyed. 682 */ 683 684 events.removeAllListeners = function (items) { 685 var objIdent, 686 i = items.length; 687 688 while(i--){ 689 690 objIdent = items[i][eventKey]; 691 692 if (!objIdent) { 693 return false; 694 } 695 else { 696 delete eventListeners[objIdent]; 697 } 698 } 699 700 return true; 701 }; 702 703 704 /** 705 @name glow.events.removeListeners 706 @function 707 @param {Object[]} items Items to remove events from. 708 @param {string} eventName Name of the event to remove. 709 @param {function} callback A reference to the original callback used when the listener was added. 710 @decription Removes listeners for an event. 711 */ 712 events.removeListeners = function (item, eventName, callback) { /* TODO: items! */ 713 var objIdent, 714 listenersForEvent, 715 i = item.length; 716 717 718 while(i--){ 719 720 objIdent = item[i][eventKey]; 721 722 if(!objIdent || !eventListeners[objIdent]){ 723 return events; 724 } 725 726 727 listenersForEvent = eventListeners[objIdent][eventName]; 728 if(!listenersForEvent){ 729 return events; 730 } 731 732 // for loop, because order matters 733 for(var j = 0, lenj = listenersForEvent.length; j < lenj; j++){ 734 if (listenersForEvent[j][0] === callback){ 735 listenersForEvent.splice(j, 1); 736 break; 737 } 738 739 } 740 } 741 742 return events; 743 }; 744 745 /** 746 Copies the events from one nodelist to another 747 @private 748 @name glow.events._copyEvent 749 @see glow.NodeList#clone 750 @function 751 */ 752 events._copyEvent = function(from, to){ 753 var listenersToCopy, 754 i = [from].length, 755 listenersForEvent, 756 name, 757 callback, 758 thisVal; 759 760 while(i--){ 761 762 var objIdent = [from][i][eventKey]; 763 764 listenersForEvent = eventListeners[objIdent]; 765 766 767 if(!objIdent){ 768 769 return false; 770 } 771 else{ 772 for ( var eventName in eventListeners[objIdent] ) { 773 name = eventName; 774 callback = eventListeners[objIdent][eventName][0][0]; 775 thisVal = eventListeners[objIdent][eventName][0][1]; 776 } 777 events._addDomEventListener([to], name, callback, thisVal); 778 } 779 780 return; 781 } 782 783 } 784 ///** 785 //@name glow.events.getListeners 786 //@function 787 //@param {Object[]} item Item to find events for 788 //@decription Returns a list of listeners attached for the given item. 789 // 790 //*/ 791 //glow.events.getListeners = function(item){ 792 // var objIdent; 793 // for (var i = 0, len = item.length; i < len; i++) { 794 // 795 // objIdent = item[i][eventKey]; 796 // 797 // if (!objIdent) { 798 // return false; 799 // } 800 // else { 801 // // todo: need to return listeners in a sensible format 802 // return eventListeners[objIdent]; 803 // } 804 // } 805 // 806 // 807 // return false; 808 //}; 809 // 810 ///** 811 //@name glow.events.hasListener 812 //@function 813 //@param {Object[]} item Item to find events for 814 //@param {String} eventName Name of the event to match 815 //@decription Returns true if an event is found for the item supplied 816 // 817 //*/ 818 // 819 //glow.events.hasListener = function (item, eventName) { 820 // var objIdent, 821 // listenersForEvent; 822 // 823 // for (var i = 0, len = item.length; i < len; i++) { 824 // objIdent = item[i][eventKey]; 825 // 826 // if (!objIdent || !eventListeners[objIdent]) { 827 // return false; 828 // } 829 // 830 // listenersForEvent = eventListeners[objIdent][eventName]; 831 // if (!listenersForEvent) { 832 // return false; 833 // } 834 // else { 835 // return true; 836 // } 837 // } 838 // 839 // return false; 840 //}; 841 842 /** 843 @name glow.events.Target 844 @class 845 @description An object that can have event listeners and fire events. 846 Extend this class to make your own objects have 'on' and 'fire' 847 methods. 848 849 @example 850 // Ball is our constructor 851 function Ball() { 852 // ... 853 } 854 855 // make Ball inherit from Target 856 glow.util.extend(Ball, glow.events.Target, { 857 // additional methods for Ball here, eg: 858 bowl: function() { 859 // ... 860 } 861 }); 862 863 // now instances of Ball can receive event listeners 864 var myBall = new Ball(); 865 myBall.on('bounce', function() { 866 alert('BOING!'); 867 }); 868 869 // and events can be fired from Ball instances 870 myBall.fire('bounce'); 871 */ 872 873 events.Target = function () { 874 875 }; 876 var targetProto = events.Target.prototype; 877 878 /** 879 @name glow.events.Target.extend 880 @function 881 @param {Object} obj Object to add Target instance methods to. 882 883 @description Convenience method to add Target instance methods onto an object. 884 If you want to add Target methods to a class, extend glow.events.Target instead. 885 886 @example 887 var myApplication = {}; 888 889 glow.events.Target.extend(myApplication); 890 891 // now myApplication can fire events... 892 myApplication.fire('load'); 893 894 // and other objects can listen for those events 895 myApplication.on('load', function(e) { 896 alert('App loaded'); 897 }); 898 */ 899 900 events.Target.extend = function (obj) { 901 glow.util.apply( obj, glow.events.Target.prototype ); 902 }; 903 904 /** 905 @name glow.events.Target#on 906 @function 907 @param {string} eventName Name of the event to listen for. 908 @param {function} callback Function to call when the event fires. 909 The callback is passed a single event object. The type of this 910 object depends on the event (see documentation for the event 911 you're listening to). 912 @param {Object} [thisVal] Value of 'this' within the callback. 913 By default, this is the object being listened to. 914 915 @description Listen for an event 916 917 @returns this 918 919 @example 920 myObj.on('show', function() { 921 // do stuff 922 }); 923 */ 924 925 targetProto.on = function(eventName, callback, thisVal) { 926 glow.events.addListeners([this], eventName, callback, thisVal); 927 return this; 928 } 929 930 /** 931 @name glow.events.Target#detach 932 @function 933 @param {string} eventName Name of the event to remove. 934 @param {function} callback Callback to detach. 935 @param {Object} [thisVal] Value of 'this' within the callback. 936 By default, this is the object being listened to. 937 @description Remove an event listener. 938 939 @returns this Target object 940 941 @example 942 function showListener() { 943 // ... 944 } 945 946 // add listener 947 myObj.on('show', showListener); 948 949 // remove listener 950 myObj.detach('show', showListener); 951 952 @example 953 // note the following WILL NOT WORK 954 955 // add listener 956 myObj.on('show', function() { 957 alert('hi'); 958 }); 959 960 // remove listener 961 myObj.detach('show', function() { 962 alert('hi'); 963 }); 964 965 // this is because both callbacks are different function instances 966 967 */ 968 969 targetProto.detach = function(eventName, callback) { 970 glow.events.removeListeners(this, eventName, callback); 971 return this; 972 } 973 974 /** 975 @name glow.events.Target#fire 976 @function 977 @param {string} eventName Name of the event to fire. 978 @param {glow.events.Event|Object} [event] Event object to pass into listeners. 979 You can provide a simple object of key-value pairs which will 980 be added as properties of a glow.events.Event instance. 981 982 @description Fire an event. 983 984 @returns glow.events.Event 985 986 @example 987 myObj.fire('show'); 988 989 @example 990 // adding properties to the event object 991 myBall.fire('bounce', { 992 velocity: 30 993 }); 994 995 @example 996 // BallBounceEvent extends glow.events.Event but has extra methods 997 myBall.fire( 'bounce', new BallBounceEvent(myBall) ); 998 */ 999 1000 targetProto.fire = function(eventName, event) { 1001 return callListeners(this, eventName, event); 1002 } 1003 1004 /** 1005 @name glow.events.Event 1006 @class 1007 @param {Object} [properties] Properties to add to the Event instance. 1008 Each key-value pair in the object will be added to the Event as 1009 properties. 1010 1011 @description Describes an event that occurred. 1012 You don't need to create instances of this class if you're simply 1013 listening to events. One will be provided as the first argument 1014 in your callback. 1015 1016 @example 1017 // creating a simple event object 1018 var event = new glow.events.Event({ 1019 velocity: 50, 1020 direction: 180 1021 }); 1022 1023 // 'velocity' and 'direction' are simple made-up properties 1024 // you may want to add to your event object 1025 1026 @example 1027 // inheriting from glow.events.Event to make a more 1028 // specialised event object 1029 1030 function RocketEvent() { 1031 // ... 1032 } 1033 1034 // inherit from glow.events.Event 1035 glow.util.extend(RocketEvent, glow.events.Event, { 1036 getVector: function() { 1037 return // ... 1038 } 1039 }); 1040 1041 // firing the event 1042 rocketInstance.fire( 'landingGearDown', new RocketEvent() ); 1043 1044 // how a user would listen to the event 1045 rocketInstance.on('landingGearDown', function(rocketEvent) { 1046 var vector = rocketEvent.getVector(); 1047 }); 1048 */ 1049 1050 events.Event = function(obj) { 1051 if (obj) { 1052 glow.util.apply(this, obj); 1053 } 1054 }; 1055 var eventProto = events.Event.prototype; 1056 /** 1057 @name glow.events.Event#attachedTo 1058 @type {Object} 1059 @description The object the listener was attached to. 1060 If null, this value will be populated by {@link glow.events.Target#fire} 1061 */ 1062 1063 /** 1064 @name glow.events.Event#source 1065 @type Element 1066 @description The actual object/element that the event originated from. 1067 1068 For example, you could attach a listener to an 'ol' element to 1069 listen for clicks. If the user clicked on an 'li' the source property 1070 would be the 'li' element, and 'attachedTo' would be the 'ol'. 1071 */ 1072 1073 1074 1075 /** 1076 @name glow.events.Event#preventDefault 1077 @function 1078 @description Prevent the default action of the event. 1079 Eg, if the click event on a link is cancelled, the link 1080 is not followed. 1081 1082 Returning false from an event listener has the same effect 1083 as calling this function. 1084 1085 For custom events, it's down to whatever fired the event 1086 to decide what to do in this case. See {@link glow.events.Event#defaultPrevented defaultPrevented} 1087 1088 @example 1089 myLinks.on('click', function(event) { 1090 event.preventDefault(); 1091 }); 1092 1093 // same as... 1094 1095 myLinks.on('click', function(event) { 1096 return false; 1097 }); 1098 */ 1099 1100 eventProto.preventDefault = function () { 1101 this._defaultPrevented = true; 1102 }; 1103 1104 1105 /** 1106 @name glow.events.Event#defaultPrevented 1107 @function 1108 @description Has the default been prevented for this event? 1109 This should be used by whatever fires the event to determine if it should 1110 carry out of the default action. 1111 1112 @returns {Boolean} Returns true if {@link glow.events.Event#preventDefault preventDefault} has been called for this event. 1113 1114 @example 1115 // fire the 'show' event 1116 // read if the default action has been prevented 1117 if ( overlayInstance.fire('show').defaultPrevented() == false ) { 1118 // go ahead and show 1119 } 1120 */ 1121 1122 eventProto.defaultPrevented = function () { 1123 return this._defaultPrevented; 1124 }; 1125 1126 1127 /* Export */ 1128 glow.events = events; 1129 }); 1130 Glow.provide(function(glow) { 1131 var document = window.document, 1132 undef = undefined, 1133 domEventHandlers = []; // like: domEventHandlers[uniqueId][eventName].count, domEventHandlers[uniqueId][eventName].callback 1134 1135 /** 1136 @name glow.events.DomEvent 1137 @constructor 1138 @extends glow.events.Event 1139 1140 @param {Event|string} nativeEvent A native browser event read properties from, or the name of a native event. 1141 1142 @param {Object} [properties] Properties to add to the Event instance. 1143 Each key-value pair in the object will be added to the Event as 1144 properties 1145 1146 @description Describes a DOM event that occurred 1147 You don't need to create instances of this class if you're simply 1148 listening to events. One will be provided as the first argument 1149 in your callback. 1150 */ 1151 function DomEvent(e, properties) { 1152 /** 1153 @name glow.events.DomEvent#nativeEvent 1154 @type {Event | MouseEvent | UIEvent} 1155 @description The native event object provided by the browser. 1156 */ 1157 this.nativeEvent = e; 1158 1159 /** 1160 @name glow.events.DomEvent#type 1161 @type {string} 1162 @description The native type of the event, like 'click' or 'keydown'. 1163 */ 1164 this.type = e.type; 1165 1166 /** 1167 @name glow.events.DomEvent#source 1168 @type {HTMLElement} 1169 @description The element that the event originated from. 1170 For example, you could attach a listener to an <ol> element to listen for 1171 clicks. If the user clicked on an <li> the source property would be the 1172 <li> element, and {@link glow.DomEvent#attachedTo attachedTo} would be 1173 the <ol>. 1174 */ 1175 if (e.target) { this.source = e.target; } // like FF 1176 else if (e.srcElement) { this.source = e.srcElement; } // like IE 1177 if (this.source && this.source.nodeType !== 1) { // like a textNode 1178 this.source = this.source.parentNode; 1179 } 1180 1181 /** 1182 @name glow.events.DomEvent#related 1183 @type {HTMLElement} 1184 @description A related HTMLElement 1185 For mouseover / mouseenter events, this will refer to the previous element 1186 the mouse was over. 1187 1188 For mouseout / mouseleave events, this will refer to the element the mouse 1189 is now over. 1190 */ 1191 this.related = e.relatedTarget || (this.type == 'mouseover' ? e.fromElement : e.toElement); 1192 1193 /** 1194 @name glow.events.DomEvent#shiftKey 1195 @type {boolean | undefined} 1196 @description Was the shift key pressed during the event? 1197 */ 1198 this.shiftKey = (e.shiftKey === undef)? undef : !!e.shiftKey; 1199 1200 /** 1201 @name glow.events.DomEvent#altKey 1202 @type {boolean | undefined} 1203 @description Was the alt key pressed during the event? 1204 */ 1205 this.altKey = (e.altKey === undef)? undef : !!e.altKey; 1206 1207 /** 1208 @name glow.events.DomEvent#ctrlKey 1209 @type {boolean | undefined} 1210 @description Was the ctrl key pressed during the event? 1211 */ 1212 this.ctrlKey = (e.ctrlKey === undef)? undef : !!e.ctrlKey; 1213 1214 /** 1215 @name glow.events.DomEvent#button 1216 @type {number | undefined} 1217 @description A number representing which button was pressed. 1218 0 for the left button, 1 for the middle button or 2 for the right button. 1219 */ 1220 this.button = glow.env.ie ? (e.button & 1 ? 0 : e.button & 2 ? 2 : 1) : e.button; 1221 1222 /** 1223 @name glow.events.DomEvent#mouseTop 1224 @type {number} 1225 @description The vertical position of the mouse pointer in the page in pixels. 1226 */ 1227 /** 1228 @name glow.events.DomEvent#mouseLeft 1229 @type {number} 1230 @description The horizontal position of the mouse pointer in the page in pixels. 1231 */ 1232 if (e.pageX !== undef || e.pageY !== undef) { 1233 this.mouseTop = e.pageY; 1234 this.mouseLeft = e.pageX; 1235 } 1236 else if (e.clientX !== undef || e.clientY !== undef) { 1237 this.mouseTop = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; 1238 this.mouseLeft = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; 1239 } 1240 1241 /** 1242 @name glow.events.DomEvent#wheelData 1243 @type {number} 1244 @description The number of clicks the mouse wheel moved. 1245 Up values are positive, down values are negative. 1246 */ 1247 if (this.type == 'mousewheel') { 1248 // this works in latest opera, but have read that it needs to be switched in direction 1249 // if there was an opera bug, I can't find which version it was fixed in 1250 this.wheelDelta = 1251 e.wheelDelta ? e.wheelDelta / 120 : 1252 e.detail ? - e.detail / 3 : 1253 0; 1254 } 1255 1256 for (var key in properties) { 1257 this[key] = properties[key]; 1258 } 1259 } 1260 1261 glow.util.extend(DomEvent, glow.events.Event); // DomEvent extends Event 1262 1263 1264 /** 1265 Add listener for an event fired by the browser. 1266 @private 1267 @name glow.events._addDomEventListener 1268 @see glow.NodeList#on 1269 @function 1270 */ 1271 glow.events._addDomEventListener = function(nodeList, eventName, callback, thisVal, selector) { 1272 var i = nodeList.length, // TODO: should we check that this nodeList is deduped? 1273 attachTo, 1274 id, 1275 eId = eventName + (selector? '/'+selector : ''); 1276 1277 while (i-- && nodeList[i]) { 1278 attachTo = nodeList[i]; 1279 1280 // will add a unique id to this node, if there is not one already 1281 glow.events.addListeners([attachTo], eventName, callback, thisVal); 1282 id = glow.events._getPrivateEventKey(attachTo); 1283 1284 // check if there is already a handler for this kind of event attached 1285 // to this node (which will run all associated callbacks in Glow) 1286 if (!domEventHandlers[id]) { domEventHandlers[id] = {}; } 1287 1288 if (domEventHandlers[id][eId] && domEventHandlers[id][eId].count > 0) { // already have handler in place 1289 domEventHandlers[id][eId].count++; 1290 continue; 1291 } 1292 1293 // no bridge in place yet 1294 domEventHandlers[id][eId] = { callback: null, count:1 }; 1295 1296 // attach a handler to tell Glow to run all the associated callbacks 1297 (function(attachTo) { 1298 var handler = domHandle(attachTo, eventName, selector); 1299 1300 if (attachTo.addEventListener) { // like DOM2 browsers 1301 attachTo.addEventListener(handler.domName, handler, (eventName === 'focus' || eventName === 'blur')); // run in bubbling phase except for focus and blur, see: http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html 1302 } 1303 else if (attachTo.attachEvent) { // like IE 1304 if (eventName === 'focus') attachTo.attachEvent('onfocusin', handler); // see: http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html 1305 else if (eventName === 'blur') attachTo.attachEvent('onfocusout', handler); // cause that's how IE rolls... 1306 attachTo.attachEvent('on' + handler.domName, handler); 1307 } 1308 // older browsers? 1309 1310 domEventHandlers[id][eId].callback = handler; 1311 })(attachTo); 1312 } 1313 } 1314 1315 function domHandle(attachTo, eventName, selector) { 1316 var handler; 1317 1318 if (eventName === 'mouseenter') { 1319 handler = function(nativeEvent, node) { 1320 var e = new glow.events.DomEvent(nativeEvent), 1321 container = node || attachTo; 1322 1323 if (!new glow.NodeList(container).contains(e.related)) { 1324 var result = glow.events._callListeners(attachTo, eventName, e, node); // fire() returns result of callback 1325 1326 if (typeof result === 'boolean') { return result; } 1327 else { return !e.defaultPrevented(); } 1328 } 1329 }; 1330 1331 if (selector) { 1332 handler = delegate(attachTo, eventName, selector, handler); 1333 } 1334 1335 handler.domName = 'mouseover'; 1336 } 1337 else if (eventName === 'mouseleave') { 1338 handler = function(nativeEvent, node) { 1339 var e = new glow.events.DomEvent(nativeEvent), 1340 container = node || attachTo; 1341 1342 if (!new glow.NodeList(container).contains(e.related)) { 1343 var result = glow.events._callListeners(attachTo, eventName, e, node); // fire() returns result of callback 1344 1345 if (typeof result === 'boolean') { return result; } 1346 else { return !e.defaultPrevented(); } 1347 } 1348 }; 1349 1350 if (selector) { 1351 handler = delegate(attachTo, eventName, selector, handler); 1352 } 1353 1354 handler.domName = 'mouseout'; 1355 } 1356 else { 1357 handler = function(nativeEvent, node) { 1358 var domEvent = new glow.events.DomEvent(nativeEvent); 1359 var result = glow.events._callListeners(attachTo, eventName, domEvent, node); // fire() returns result of callback 1360 1361 if (typeof result === 'boolean') { return result; } 1362 else { return !domEvent.defaultPrevented(); } 1363 }; 1364 1365 if (selector) { 1366 handler = delegate(attachTo, eventName, selector, handler); 1367 } 1368 1369 handler.domName = eventName; 1370 } 1371 1372 return handler; 1373 } 1374 1375 // wraps a handler in code to detect delegation 1376 function delegate(attachTo, eventName, selector, handler) { 1377 1378 return function(nativeEvent) { //console.log('dispatched, selector is: '+selector); 1379 var e = new glow.events.DomEvent(nativeEvent); 1380 node = e.source; 1381 1382 // if the source matches the selector 1383 while (node) { 1384 if (!!glow._sizzle.matches(selector, [node]).length) { 1385 // the wrapped handler is called here, pass in the node that matched so it can be used as `this` 1386 return handler(nativeEvent, node); 1387 // 1388 } 1389 1390 if (node === attachTo) { break; } // don't check parents above the attachTo 1391 1392 node = node.parentNode; 1393 } 1394 }; 1395 } 1396 1397 /** 1398 Remove listener for an event fired by the browser. 1399 @private 1400 @name glow.events._removeDomEventListener 1401 @see glow.NodeList#detach 1402 @function 1403 */ 1404 glow.events._removeDomEventListener = function(nodeList, eventName, callback, selector) { 1405 var i = nodeList.length, // TODO: should we check that this nodeList is deduped? 1406 attachTo, 1407 id, 1408 eId = eventName + (selector? '/'+selector : ''), 1409 bridge, 1410 handler; 1411 1412 while (i-- && nodeList[i]) { 1413 attachTo = nodeList[i]; 1414 1415 // skip if there is no bridge for this kind of event attached 1416 id = glow.events._getPrivateEventKey(attachTo); 1417 if (!domEventHandlers[id] && !domEventHandlers[id][eId]) { continue; } 1418 1419 glow.events.removeListeners([attachTo], eventName, callback); 1420 1421 bridge = domEventHandlers[id][eId] 1422 1423 if (bridge.count > 0) { 1424 bridge.count--; // one less listener associated with this event 1425 1426 if (bridge.count === 0) { // no more listeners associated with this event 1427 // detach bridge handler to tell Glow to run all the associated callbacks 1428 1429 handler = bridge.callback; 1430 1431 if (attachTo.removeEventListener) { // like DOM2 browsers 1432 attachTo.removeEventListener(handler.domName, handler, (eventName === 'focus' || eventName === 'blur')); // run in bubbling phase except for focus and blur, see: http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html 1433 } 1434 else if (attachTo.detachEvent) { // like IE 1435 if (eventName === 'focus') attachTo.detachEvent('onfocusin', handler); // see: http://www.quirksmode.org/blog/archives/2008/04/delegating_the.html 1436 else if (eventName === 'blur') attachTo.detachEvent('onfocusout', handler); // cause that's how IE rolls... 1437 attachTo.detachEvent('on' + handler.domName, handler); 1438 } 1439 } 1440 } 1441 } 1442 } 1443 1444 // see: http://developer.yahoo.com/yui/3/event/#eventsimulation 1445 // see: http://developer.yahoo.com/yui/docs/YAHOO.util.UserAction.html 1446 // function simulateDomEvent(nodeList, domEvent) { 1447 // var i = nodeList.length, 1448 // eventName = domEvent.type, 1449 // nativeEvent, 1450 // node, 1451 // fire; 1452 // 1453 // if (document.createEvent) { 1454 // var nativeEvent = document.createEvent('MouseEvent'); // see: 1455 // nativeEvent.initEvent(eventName, true, true); 1456 // 1457 // fire = function(el) { 1458 // return !el.dispatchEvent(nativeEvent); 1459 // } 1460 // } 1461 // else { 1462 // fire = function(el) { 1463 // var nativeEvent = document.createEventObject(); 1464 // return el.fireEvent('on'+eventName, nativeEvent); 1465 // } 1466 // } 1467 // 1468 // while (i--) { 1469 // node = nodeList[i]; 1470 // if (node.nodeType !== 1) { continue; } 1471 // fire(node); 1472 // } 1473 // } 1474 1475 // export 1476 glow.events.DomEvent = DomEvent; 1477 }); 1478 Glow.provide(function(glow) { 1479 var document = window.document, 1480 undefined, 1481 keyboardEventProto, 1482 $env = glow.env, 1483 // the keyCode for the last keydown (returned to undefined on keyup) 1484 activeKey, 1485 // the charCode for the last keypress (returned to undefined on keyup & keydown) 1486 activeChar, 1487 DomEvent = glow.events.DomEvent, 1488 _callListeners = glow.events._callListeners, 1489 _getPrivateEventKey = glow.events._getPrivateEventKey, 1490 // object of event names & listeners, eg: 1491 // { 1492 // eventId: [ 1493 // 2, // the number of glow listeners added for this node 1494 // keydownListener, 1495 // keypressListener, 1496 // keyupListener 1497 // ] 1498 // } 1499 // This lets us remove these DOM listeners from the node when the glow listeners reaches zero 1500 eventKeysRegistered = {}; 1501 1502 /** 1503 @name glow.events.KeyboardEvent 1504 @constructor 1505 @extends glow.events.DomEvent 1506 1507 @param {Event} nativeEvent A native browser event read properties from 1508 1509 @param {Object} [properties] Properties to add to the Event instance. 1510 Each key-value pair in the object will be added to the Event as 1511 properties 1512 1513 @description Describes a keyboard event that occurred 1514 You don't need to create instances of this class if you're simply 1515 listening to events. One will be provided as the first argument 1516 in your callback. 1517 */ 1518 function KeyboardEvent(nativeEvent) { 1519 if (activeKey) { 1520 this.key = keyCodeToId(activeKey); 1521 } 1522 if (activeChar) { 1523 this.keyChar = String.fromCharCode(activeChar); 1524 } 1525 DomEvent.call(this, nativeEvent); 1526 } 1527 1528 glow.util.extend(KeyboardEvent, DomEvent, { 1529 /** 1530 @name glow.events.KeyboardEvent#key 1531 @type {string} 1532 @description The key pressed 1533 This is a string representing the key pressed. 1534 1535 Alphanumeric keys are represented by 0-9 and A-Z uppercase. Other safe cross-browser values are: 1536 1537 <dl> 1538 <li>backspace</li> 1539 <li>tab</li> 1540 <li>return</li> 1541 <li>shift</li> 1542 <li>alt</li> 1543 <li>escape</li> 1544 <li>space</li> 1545 <li>pageup</li> 1546 <li>pagedown</li> 1547 <li>end</li> 1548 <li>home</li> 1549 <li>left</li> 1550 <li>up</li> 1551 <li>right</li> 1552 <li>down</li> 1553 <li>insert</li> 1554 <li>delete</li> 1555 <li>;</li> 1556 <li>=</li> 1557 <li>-</li> 1558 <li>f1</li> 1559 <li>f2</li> 1560 <li>f3</li> 1561 <li>f4</li> 1562 <li>f5</li> 1563 <li>f6</li> 1564 <li>f7</li> 1565 <li>f8</li> 1566 <li>f9</li> 1567 <li>f10</li> 1568 <li>f11</li> 1569 <li>f12</li> 1570 <li>numlock</li> 1571 <li>scrolllock</li> 1572 <li>pause</li> 1573 <li>,</li> 1574 <li>.</li> 1575 <li>/</li> 1576 <li>[</li> 1577 <li>\</li> 1578 <li>]</li> 1579 </dl> 1580 1581 Some keys may trigger actions in your browser and operating system, some 1582 are not cancelable. 1583 1584 @example 1585 glow(document).on('keypress', function(event) { 1586 switch (event.key) { 1587 case 'up': 1588 // do stuff 1589 break; 1590 case 'down': 1591 // do stuff 1592 break; 1593 } 1594 }); 1595 */ 1596 key: '', 1597 /** 1598 @name glow.events.KeyboardEvent#keyChar 1599 @type {string} 1600 @description The character entered. 1601 This is only available during 'keypress' events. 1602 1603 If the user presses shift and 1, event.key will be "1", but event.keyChar 1604 will be "!". 1605 1606 @example 1607 // only allow numbers to be entered into the ageInput field 1608 glow('#ageInput').on('keypress', function(event) { 1609 return !isNaN( Number(event.keyChar) ); 1610 }); 1611 */ 1612 keyChar: '' 1613 }); 1614 1615 // add a dom listener 1616 function addListener(elm, name, callback) { 1617 if (elm.addEventListener) { // like DOM2 browsers 1618 elm.addEventListener(name, callback, false); 1619 } 1620 else if (elm.attachEvent) { // like IE 1621 elm.attachEvent('on' + name, callback); 1622 } 1623 } 1624 1625 // remove a dom listener 1626 function removeListener(elm, name, callback) { 1627 if (elm.removeEventListener) { // like DOM2 browsers 1628 elm.removeEventListener(name, callback, false); 1629 } 1630 else if (elm.detachEvent) { // like IE 1631 elm.detachEvent('on' + name, callback); 1632 } 1633 } 1634 1635 // takes a keyCode from a keydown listener and returns true if the browser will also fire a keypress 1636 function expectKeypress(keyCode, defaultPrevented) { 1637 var keyName; 1638 1639 // for browsers that fire keypress for the majority of keys 1640 if ($env.gecko || $env.opera) { 1641 return !noKeyPress[keyCode]; 1642 } 1643 1644 // for browsers that only fire keypress for printable chars 1645 keyName = keyCodeToId(keyCode); 1646 1647 // is this a printable char? 1648 if (keyName.length === 1 && !noKeyPress[keyCode]) { 1649 // webkit doesn't fire keypress if the keydown has been prevented 1650 return !($env.webkit && defaultPrevented); 1651 } 1652 return false; 1653 } 1654 1655 // Add the key listeners for firing glow's normalised key events. 1656 // returns an entry for eventKeysRegistered 1657 function addDomKeyListeners(attachTo) { 1658 var keydownHandler, keypressHandler, keyupHandler, 1659 // Even though the user may only be interested in one key event, we need all 3 listeners to normalise any of them 1660 // hash of which keys are down, keyed by keyCode 1661 keysDown = {}; 1662 1663 keydownHandler = function(nativeEvent) { 1664 var keyCode = nativeEvent.keyCode, 1665 preventDefault, 1666 preventDefaultKeyPress; 1667 1668 // some browsers repeat this event while a key is held down, we don't want to do that 1669 if ( !keysDown[keyCode] ) { 1670 activeKey = keyCode; 1671 activeChar = undefined; 1672 preventDefault = _callListeners( attachTo, 'keydown', new KeyboardEvent(nativeEvent) ).defaultPrevented(); 1673 keysDown[keyCode] = true; 1674 } 1675 // we want to fire a keyPress event here if the browser isn't going to fire one itself 1676 if ( !expectKeypress(keyCode, preventDefault) ) { 1677 preventDefaultKeyPress = _callListeners( attachTo, 'keypress', new KeyboardEvent(nativeEvent) ).defaultPrevented(); 1678 } 1679 // return false if either the keydown or fake keypress event was cancelled 1680 return !(preventDefault || preventDefaultKeyPress); 1681 }; 1682 1683 keypressHandler = function(nativeEvent) { 1684 // some browsers store the charCode in .charCode, some in .keyCode 1685 activeChar = nativeEvent.charCode || nativeEvent.keyCode; 1686 // some browsers fire this event for non-printable chars, look at the previous keydown and see if we're expecting a printable char 1687 if ( keyCodeToId(activeKey).length > 1 ) { 1688 // non-printable chars have an ID length greater than 1 1689 activeChar = undefined; 1690 } 1691 var preventDefault = _callListeners( attachTo, 'keypress', new KeyboardEvent(nativeEvent) ).defaultPrevented(); 1692 return !preventDefault; 1693 }; 1694 1695 keyupHandler = function(nativeEvent) { 1696 var keyCode = nativeEvent.keyCode, 1697 preventDefault; 1698 1699 activeKey = keyCode; 1700 activeChar = undefined; 1701 preventDefault = _callListeners( attachTo, 'keyup', new KeyboardEvent(nativeEvent) ).defaultPrevented(); 1702 keysDown[keyCode] = false; 1703 activeKey = undefined; 1704 return !preventDefault; 1705 }; 1706 1707 // add listeners to the dom 1708 addListener(attachTo, 'keydown', keydownHandler); 1709 addListener(attachTo, 'keypress', keypressHandler); 1710 addListener(attachTo, 'keyup', keyupHandler); 1711 1712 return [1, keydownHandler, keypressHandler, keyupHandler]; 1713 } 1714 1715 /** 1716 @name glow.events._addKeyListener 1717 @private 1718 @function 1719 @description Add listener for a key event fired by the browser. 1720 @see glow.NodeList#on 1721 */ 1722 glow.events._addKeyListener = function(nodeList, name, callback, thisVal) { 1723 var i = nodeList.length, 1724 attachTo, 1725 eventKey; 1726 1727 // will add a unique id to this node, if there is not one already 1728 glow.events.addListeners(nodeList, name, callback, thisVal); 1729 1730 while (i--) { 1731 attachTo = nodeList[i]; 1732 1733 // get the ID for this event 1734 eventKey = _getPrivateEventKey(attachTo); 1735 1736 // if we've already attached DOM listeners for this, don't add them again 1737 if ( eventKeysRegistered[eventKey] ) { 1738 eventKeysRegistered[eventKey][0]++; 1739 continue; 1740 } 1741 else { 1742 eventKeysRegistered[eventKey] = addDomKeyListeners(attachTo); 1743 } 1744 } 1745 } 1746 1747 /** 1748 Remove listener for an event fired by the browser. 1749 @private 1750 @name glow.events._removeKeyListener 1751 @see glow.NodeList#detach 1752 @function 1753 */ 1754 glow.events._removeKeyListener = function(nodeList, name, callback) { 1755 var i = nodeList.length, 1756 attachTo, 1757 eventKey, 1758 eventRegistry; 1759 1760 // remove the glow events 1761 glow.events.removeListeners(nodeList, name, callback); 1762 1763 while (i--) { 1764 attachTo = nodeList[i]; 1765 1766 // get the ID for this event 1767 eventKey = _getPrivateEventKey(attachTo); 1768 eventRegistry = eventKeysRegistered[eventKey]; 1769 // exist if there are no key events registered for this node 1770 if ( !eventRegistry ) { 1771 return; 1772 } 1773 if ( --eventRegistry[0] === 0 ) { 1774 // our glow listener count is zero, we have no need for the dom listeners anymore 1775 removeListener( attachTo, 'keydown', eventRegistry[1] ); 1776 removeListener( attachTo, 'keypress', eventRegistry[2] ); 1777 removeListener( attachTo, 'keyup', eventRegistry[3] ); 1778 eventKeysRegistered[eventKey] = undefined; 1779 } 1780 } 1781 } 1782 1783 // convert a keyCode to a string name for that key 1784 function keyCodeToId(keyCode) { 1785 // key codes for 0-9 A-Z are the same as their char codes 1786 if ( (keyCode >= keyCodeA && keyCode <= keyCodeZ) || (keyCode >= keyCode0 && keyCode <= keyCode9) ) { 1787 return String.fromCharCode(keyCode).toLowerCase(); 1788 } 1789 return keyIds[keyCode] || 'unknown' + keyCode; 1790 } 1791 1792 // keyCode to key name translation 1793 var keyCodeA = 'A'.charCodeAt(0), 1794 keyCodeZ = 'Z'.charCodeAt(0), 1795 keyCode0 = '0'.charCodeAt(0), 1796 keyCode9 = '9'.charCodeAt(0), 1797 // key codes for non-alphanumeric keys 1798 keyIds = { 1799 8: 'backspace', 1800 9: 'tab', 1801 13: 'return', 1802 16: 'shift', 1803 17: 'control', 1804 18: 'alt', 1805 19: 'pause', 1806 27: 'escape', 1807 32: 'space', 1808 33: 'pageup', 1809 34: 'pagedown', 1810 35: 'end', 1811 36: 'home', 1812 37: 'left', 1813 38: 'up', 1814 39: 'right', 1815 40: 'down', 1816 44: 'printscreen', // Only fires keyup in firefox, IE. Doesn't fire in webkit, opera. 1817 45: 'insert', 1818 46: 'delete', 1819 59: ';', 1820 61: '=', 1821 91: 'meta', 1822 93: 'menu', // no keycode in opera, doesn't fire in Chrome 1823 1824 // these are number pad numbers, but Opera doesn't distinguish them from normal number keys so we normalise on that 1825 96: '0', 1826 97: '1', 1827 98: '2', 1828 99: '3', 1829 100: '4', 1830 101: '5', 1831 102: '6', 1832 103: '7', 1833 104: '8', 1834 105: '9', 1835 106: '*', // opera fires 2 keypress events 1836 107: '+', // opera fires 2 keypress events 1837 109: '-', // opera sees - as insert 1838 110: '.', // opera sees this as n 1839 111: '/', 1840 // end of numpad 1841 1842 112: 'f1', 1843 113: 'f2', 1844 114: 'f3', 1845 115: 'f4', 1846 116: 'f5', 1847 117: 'f6', 1848 118: 'f7', 1849 119: 'f8', 1850 120: 'f9', 1851 121: 'f10', 1852 122: 'f11', 1853 123: 'f12', 1854 144: 'numlock', 1855 145: 'scrolllock', 1856 188: ',', 1857 189: '-', 1858 190: '.', 1859 191: '/', 1860 192: "'", 1861 219: '[', 1862 220: '\\', 1863 221: ']', 1864 222: '#', // opera sees # key as 3. Pah. 1865 223: '`', 1866 224: 'meta', // same as [ in opera 1867 226: '\\' // this key appears on a US layout in webkit windows 1868 }, 1869 noKeyPress = {}; 1870 1871 // corrections for particular browsers :( 1872 if ($env.gecko) { 1873 keyIds[107] = '='; 1874 1875 noKeyPress = { 1876 16: 1, // shift 1877 17: 1, // control 1878 18: 1, // alt 1879 144: 1, // numlock 1880 145: 1 // scrolllock 1881 }; 1882 } 1883 else if ($env.opera) { 1884 keyIds[42] = '*'; 1885 keyIds[43] = '+'; 1886 keyIds[47] = '/'; 1887 keyIds[222] = "'"; 1888 keyIds[192] = '`'; 1889 1890 noKeyPress = { 1891 16: 1, // shift 1892 17: 1, // control 1893 18: 1 // alt 1894 }; 1895 } 1896 else if ($env.webkit || $env.ie) { 1897 keyIds[186] = ';'; 1898 keyIds[187] = '='; 1899 } 1900 1901 // export 1902 glow.events.KeyboardEvent = KeyboardEvent; 1903 }); 1904 Glow.provide(function(glow) { 1905 var NodeListProto, undefined, 1906 // shortcuts to aid compression 1907 document = window.document, 1908 arraySlice = Array.prototype.slice, 1909 arrayPush = Array.prototype.push; 1910 1911 /** 1912 @name glow.NodeList 1913 @constructor 1914 @description An array-like collection of DOM Nodes 1915 It is recommended to create a NodeList using the shortcut function {@link glow}. 1916 1917 @param {string | glow.NodeList | Node | Node[] | Window} contents Items to populate the NodeList with. 1918 This parameter will be passed to {@link glow.NodeList#push}. 1919 1920 Strings will be treated as CSS selectors unless they start with '<', in which 1921 case they'll be treated as an HTML string. 1922 1923 @example 1924 // empty NodeList 1925 var myNodeList = glow(); 1926 1927 @example 1928 // using glow to return a NodeList then chaining methods 1929 glow('p').addClass('eg').append('<div>Hello!</div>'); 1930 1931 @example 1932 // creating an element from a string 1933 glow('<div>Hello!</div>').appendTo('body'); 1934 1935 @see <a href="http://wiki.github.com/jeresig/sizzle/">Supported CSS selectors</a> 1936 */ 1937 function NodeList(contents) { 1938 // call push if we've been given stuff to add 1939 contents && this.push(contents); 1940 } 1941 NodeListProto = NodeList.prototype; 1942 1943 /** 1944 @name glow.NodeList#length 1945 @type Number 1946 @description Number of nodes in the NodeList 1947 @example 1948 // get the number of paragraphs on the page 1949 glow('p').length; 1950 */ 1951 NodeListProto.length = 0; 1952 1953 /** 1954 @name glow.NodeList._strToNodes 1955 @private 1956 @function 1957 @description Converts a string to an array of nodes 1958 1959 @param {string} str HTML string 1960 1961 @returns {Node[]} Array of nodes (including text / comment nodes) 1962 */ 1963 NodeList._strToNodes = (function() { 1964 var tmpDiv = document.createElement('div'), 1965 // these wraps are in the format [depth to children, opening html, closing html] 1966 tableWrap = [1, '<table>', '</table>'], 1967 emptyWrap = [0, '', ''], 1968 // webkit won't accept <link> elms to be the only child of an element, 1969 // it steals them and hides them in the head for some reason. Using 1970 // broken html fixes it for some reason 1971 paddingWrap = [1, 'b<div>', '</div>'], 1972 trWrap = [3, '<table><tbody><tr>', '</tr></tbody></table>'], 1973 wraps = { 1974 caption: tableWrap, 1975 thead: tableWrap, 1976 th: trWrap, 1977 colgroup: tableWrap, 1978 tbody: tableWrap, 1979 tr: [2, '<table><tbody>', '</tbody></table>'], 1980 td: trWrap, 1981 tfoot: tableWrap, 1982 option: [1, '<select multiple="multiple">', '</select>'], 1983 legend: [1, '<fieldset>', '</fieldset>'], 1984 link: paddingWrap, 1985 script: paddingWrap, 1986 style: paddingWrap, 1987 '!': paddingWrap 1988 }; 1989 1990 function strToNodes(str) { 1991 var r = [], 1992 tagName = ( /^<([\w!]+)/.exec(str) || [] )[1], 1993 // This matches str content with potential elements that cannot 1994 // be a child of <div>. elmFilter declared at top of page. 1995 wrap = wraps[tagName] || emptyWrap, 1996 nodeDepth = wrap[0], 1997 childElm = tmpDiv, 1998 exceptTbody, 1999 rLen = 0, 2000 firstChild; 2001 2002 // Create the new element using the node tree contents available in filteredElm. 2003 childElm.innerHTML = (wrap[1] + str + wrap[2]); 2004 2005 // Strip newElement down to just the required elements' parent 2006 while(nodeDepth--) { 2007 childElm = childElm.lastChild; 2008 } 2009 2010 // pull nodes out of child 2011 if (wrap === tableWrap && str.indexOf('<tbody') === -1) { 2012 // IE7 (and earlier) sometimes gives us a <tbody> even though we didn't ask for one 2013 while (firstChild = childElm.firstChild) { 2014 if (firstChild.nodeName != 'TBODY') { 2015 r[rLen++] = firstChild; 2016 } 2017 childElm.removeChild(firstChild); 2018 } 2019 } 2020 else { 2021 while (firstChild = childElm.firstChild) { 2022 r[rLen++] = childElm.removeChild(firstChild); 2023 } 2024 } 2025 2026 return r; 2027 } 2028 2029 return strToNodes; 2030 })(); 2031 2032 // takes a collection and returns an array 2033 var collectionToArray = function(collection) { 2034 return arraySlice.call(collection, 0); 2035 }; 2036 2037 try { 2038 // look out for an IE bug 2039 arraySlice.call( document.documentElement.childNodes, 0 ); 2040 } 2041 catch(e) { 2042 collectionToArray = function(collection) { 2043 // We can't use this trick on IE collections that are com-based, like HTMLCollections 2044 // Thankfully they don't have a constructor, so that's how we detect those 2045 if (collection instanceof Object) { 2046 return arraySlice.call(collection, 0); 2047 } 2048 var i = collection.length, 2049 arr = []; 2050 2051 while (i--) { 2052 arr[i] = collection[i]; 2053 } 2054 return arr; 2055 } 2056 } 2057 2058 /** 2059 @name glow.NodeList#push 2060 @function 2061 @description Adds nodes to the NodeList 2062 2063 @param {string | Node | Node[] | glow.NodeList} nodes Node(s) to add to the NodeList 2064 Strings will be treated as CSS selectors or HTML strings. 2065 2066 @returns {glow.NodeList} 2067 2068 @example 2069 myNodeList.push('<div>Foo</div>').push('h1'); 2070 */ 2071 NodeListProto.push = function(nodes) { 2072 /*!debug*/ 2073 if (arguments.length !== 1) { 2074 glow.debug.warn('[wrong count] glow.NodeList#push expects 1 argument, not '+arguments.length+'.'); 2075 } 2076 /*gubed!*/ 2077 2078 if (nodes) { 2079 if (typeof nodes === 'string') { 2080 // if the string begins <, treat it as html, otherwise it's a selector 2081 if (nodes.charAt(0) === '<') { 2082 nodes = NodeList._strToNodes(nodes); 2083 } 2084 else { 2085 nodes = glow._sizzle(nodes) 2086 } 2087 arrayPush.apply(this, nodes); 2088 } 2089 2090 else if ( nodes.nodeType || nodes.window == nodes ) { 2091 if (this.length) { 2092 arrayPush.call(this, nodes); 2093 } 2094 else { 2095 this[0] = nodes; 2096 this.length = 1; 2097 } 2098 } 2099 else if (nodes.length !== undefined) { 2100 if (nodes.constructor != Array) { 2101 // convert array-like objects into an array 2102 nodes = collectionToArray(nodes); 2103 } 2104 arrayPush.apply(this, nodes); 2105 } 2106 /*!debug*/ 2107 else { 2108 glow.debug.warn('[wrong type] glow.NodeList#push: Ignoring unexpected argument type, failing silently'); 2109 } 2110 /*gubed!*/ 2111 } 2112 /*!debug*/ 2113 else { 2114 glow.debug.warn('[wrong type] glow.NodeList#push: Ignoring false argument type, failing silently'); 2115 } 2116 /*gubed!*/ 2117 return this; 2118 }; 2119 2120 /** 2121 @name glow.NodeList#eq 2122 @function 2123 @description Compares this NodeList to another 2124 Returns true if both NodeLists contain the same items in the same order 2125 2126 @param {Node | Node[] | glow.NodeList} nodeList The NodeList to compare to. 2127 2128 @returns {boolean} 2129 2130 @see {@link glow.NodeList#is} for testing if a NodeList item matches a selector 2131 2132 @example 2133 // the following returns true 2134 glow('#blah').eq( document.getElementById('blah') ); 2135 */ 2136 NodeListProto.eq = function(nodeList) { 2137 /*!debug*/ 2138 if (arguments.length !== 1) { 2139 glow.debug.warn('[wrong count] glow.NodeList#eq expects 1 argument, not ' + arguments.length + '.'); 2140 } 2141 if (typeof nodeList !== 'object') { 2142 glow.debug.warn('[wrong type] glow.NodeList#eq expects object argument, not ' + typeof nodeList + '.'); 2143 } 2144 /*gubed!*/ 2145 2146 var len = this.length, 2147 i = len; 2148 2149 // normalise param to NodeList 2150 if ( !(nodeList instanceof NodeList) ) { 2151 nodeList = new NodeList(nodeList); 2152 } 2153 2154 // quickly return false if lengths are different 2155 if (len != nodeList.length) { 2156 return false; 2157 } 2158 2159 // loop through and return false on inequality 2160 while (i--) { 2161 if (this[i] !== nodeList[i]) { 2162 return false; 2163 } 2164 } 2165 2166 return true; 2167 }; 2168 2169 /** 2170 @name glow.NodeList#slice 2171 @function 2172 @description Get a section of an NodeList 2173 Operates in the same way as an Array's slice method 2174 2175 @param {number} start Start index 2176 If negative, it specifies a position measured from the end of the list 2177 2178 @param {number} [end] End index 2179 By default, this is the end of the list. A negative end specifies 2180 a position measured from the end of the list. 2181 2182 @returns {glow.NodeList} A new sliced NodeList 2183 2184 @example 2185 var myNodeList = glow("<div></div><p></p>"); 2186 myNodeList.slice(1, 2); // selects the paragraph 2187 myNodeList.slice(-1); // same thing, selects the paragraph 2188 */ 2189 NodeListProto.slice = function(/*start, end*/) { 2190 return new NodeList( arraySlice.apply(this, arguments) ); 2191 }; 2192 2193 /** 2194 @name glow.NodeList#sort 2195 @function 2196 @description Sort the elements in the list. 2197 Items will already be in document order if a CSS selector 2198 was used to fetch them. 2199 2200 @param {Function} [func] Function to determine sort order 2201 This function will be passed 2 elements (elementA, elementB). The function 2202 should return a number less than 0 to sort elementA lower than elementB 2203 and greater than 0 to sort elementA higher than elementB. 2204 2205 If no function is provided, elements will be sorted in document order. 2206 2207 @returns {glow.NodeList} A new sorted NodeList 2208 2209 @example 2210 //get links in alphabetical (well, lexicographical) order 2211 var links = glow("a").sort(function(elementA, elementB) { 2212 return glow(elementA).text() < glow(elementB).text() ? -1 : 1; 2213 }) 2214 */ 2215 NodeListProto.sort = function(func) { 2216 var items = collectionToArray(this), 2217 sortedElms = func ? items.sort(func) : glow._sizzle.uniqueSort(items); 2218 2219 return new NodeList(sortedElms); 2220 }; 2221 2222 /** 2223 @name glow.NodeList#item 2224 @function 2225 @description Get a single item from the list as an NodeList 2226 Negative numbers can be used to get items from the end of the 2227 list. 2228 2229 @param {number} index The numeric index of the node to return. 2230 2231 @returns {glow.NodeList} A new NodeList containing a single item 2232 2233 @example 2234 // get the html from the fourth element 2235 myNodeList.item(3).html(); 2236 2237 @example 2238 // add a class name to the last item 2239 myNodeList.item(-1).addClass('last'); 2240 */ 2241 NodeListProto.item = function(index) { 2242 /*!debug*/ 2243 if ( arguments.length !== 1 ) { 2244 glow.debug.warn('[wrong count] glow.NodeList#item expects 1 argument, got ' + arguments.length); 2245 } 2246 /*gubed!*/ 2247 // TODO: test which of these methods is faster (use the current one unless significantly slower) 2248 return this.slice(index, (index + 1) || this.length); 2249 // return new NodeList( index < 0 ? this[this.length + index] : this[index] ); 2250 }; 2251 2252 /** 2253 @name glow.NodeList#each 2254 @function 2255 @description Calls a function for each node in the list. 2256 2257 @param {Function} callback The function to call for each node. 2258 The function will be passed 2 arguments, the index of the current item, 2259 and the NodeList being iterated over. 2260 2261 Inside the function 'this' refers to the Node. 2262 2263 Returning false from this function stops further iterations 2264 2265 @returns {glow.NodeList} 2266 2267 @example 2268 // add "link number: x" to each link, where x is the index of the link 2269 glow("a").each(function(i, nodeList) { 2270 glow(this).append(' link number: ' + i); 2271 }); 2272 @example 2273 // breaking out of an each loop 2274 glow("a").each(function(i, nodeList) { 2275 // do stuff 2276 if ( glow(this).hasClass('whatever') ) { 2277 // we don't want to process any more links 2278 return false; 2279 } 2280 }); 2281 */ 2282 NodeListProto.each = function(callback) { 2283 /*!debug*/ 2284 if ( arguments.length !== 1 ) { 2285 glow.debug.warn('[wrong count] glow.NodeList#each expects 1 argument, got ' + arguments.length); 2286 } 2287 if (typeof callback != 'function') { 2288 glow.debug.warn('[wrong type] glow.NodeList#each expects "function", got ' + typeof callback); 2289 } 2290 /*gubed!*/ 2291 for (var i = 0, len = this.length; i<len; i++) { 2292 if ( callback.call(this[i], i, this) === false ) { 2293 break; 2294 } 2295 } 2296 return this; 2297 }; 2298 2299 /** 2300 @name glow.NodeList#filter 2301 @function 2302 @description Filter the NodeList 2303 2304 @param {Function|string} test Filter test 2305 If a string is provided it's treated as a CSS selector. Elements 2306 which match the CSS selector are added to the new NodeList. 2307 2308 If 'test' is a function, it will be called per node in the NodeList. 2309 2310 The function is passed 2 arguments, the index of the current item, 2311 and the ElementList being itterated over. 2312 2313 Inside the function 'this' refers to the node. 2314 Return true to add the element to the new NodeList. 2315 2316 @returns {glow.NodeList} A new NodeList containing the filtered nodes 2317 2318 @example 2319 // return images with a width greater than 320 2320 glow("img").filter(function () { 2321 return glow(this).width() > 320; 2322 }); 2323 2324 @example 2325 // Get items that don't have an alt attribute 2326 myElementList.filter(':not([alt])'); 2327 */ 2328 NodeListProto.filter = function(test) { 2329 /*!debug*/ 2330 if ( arguments.length !== 1 ) { 2331 glow.debug.warn('[wrong count] glow.NodeList#filter expects 1 argument, got ' + arguments.length); 2332 } 2333 if ( !/^(function|string)$/.test(typeof test) ) { 2334 glow.debug.warn('[wrong type] glow.NodeList#each expects function/string, got ' + typeof test); 2335 } 2336 /*gubed!*/ 2337 var r = [], 2338 ri = 0; 2339 2340 if (typeof test === 'string') { 2341 r = glow._sizzle.matches(test, this); 2342 } 2343 else { 2344 for (var i = 0, len = this.length; i<len; i++) { 2345 if ( test.call(this[i], i, this) ) { 2346 r[ri++] = this[i]; 2347 } 2348 } 2349 } 2350 2351 return new NodeList(r); 2352 }; 2353 2354 2355 /** 2356 @name glow.NodeList#is 2357 @function 2358 @description Tests if the first element matches a CSS selector 2359 2360 @param {string} selector CSS selector 2361 2362 @returns {boolean} 2363 2364 @example 2365 if ( myNodeList.is(':visible') ) { 2366 // ... 2367 } 2368 */ 2369 NodeListProto.is = function(selector) { 2370 /*!debug*/ 2371 if ( arguments.length !== 1 ) { 2372 glow.debug.warn('[wrong count] glow.NodeList#is expects 1 argument, got ' + arguments.length); 2373 } 2374 if ( typeof selector !== 'string' ) { 2375 glow.debug.warn('[wrong type] glow.NodeList#is expects string, got ' + typeof selector); 2376 } 2377 /*gubed!*/ 2378 if ( !this[0] ) { 2379 return false; 2380 } 2381 return !!glow._sizzle.matches( selector, [ this[0] ] ).length; 2382 }; 2383 2384 // export 2385 glow.NodeList = NodeList; 2386 }); 2387 Glow.provide(function(glow) { 2388 var undef 2389 , NodeListProto = glow.NodeList.prototype 2390 2391 /** 2392 @private 2393 @name glow.NodeList-dom0PropertyMapping 2394 @description Mapping of HTML attribute names to DOM0 property names. 2395 */ 2396 , dom0PropertyMapping = { // keys must be lowercase 2397 'class' : 'className', 2398 'for' : 'htmlFor', 2399 'maxlength' : 'maxLength' 2400 } 2401 2402 /** 2403 @private 2404 @name glow.NodeList-dataPropName 2405 @type String 2406 @description The property name added to the DomElement by the NodeList#data method. 2407 */ 2408 , dataPropName = '_uniqueData' + glow.UID 2409 2410 /** 2411 @private 2412 @name glow.NodeList-dataIndex 2413 @type String 2414 @description The value of the dataPropName added by the NodeList#data method. 2415 */ 2416 , dataIndex = 1 // must be a truthy value 2417 2418 /** 2419 @private 2420 @name glow.NodeList-dataCache 2421 @type Object 2422 @description Holds the data used by the NodeList#data method. 2423 2424 The structure is like: 2425 [ 2426 { 2427 myKey: "my data" 2428 } 2429 ] 2430 */ 2431 , dataCache = []; 2432 2433 /** 2434 @name glow.NodeList#addClass 2435 @function 2436 @description Adds a class to each node. 2437 2438 @param {string} name The name of the class to add. 2439 2440 @returns {glow.NodeList} 2441 2442 @example 2443 glow("#login a").addClass("highlight"); 2444 */ 2445 NodeListProto.addClass = function(name) { 2446 var i = this.length; 2447 2448 /*!debug*/ 2449 if (arguments.length !== 1) { 2450 glow.debug.warn('[wrong count] glow.NodeList#addClass expects 1 argument, not '+arguments.length+'.'); 2451 } 2452 else if (typeof arguments[0] !== 'string') { 2453 glow.debug.warn('[wrong type] glow.NodeList#addClass expects argument 1 to be of type string, not '+typeof arguments[0]+'.'); 2454 } 2455 /*gubed!*/ 2456 2457 while (i--) { 2458 if (this[i].nodeType === 1) { 2459 _addClass(this[i], name); 2460 } 2461 } 2462 2463 return this; 2464 }; 2465 2466 function _addClass(node, name) { // TODO: handle classnames separated by non-space characters? 2467 if ( (' ' + node.className + ' ').indexOf(' ' + name + ' ') === -1 ) { 2468 node.className += (node.className? ' ' : '') + name; 2469 } 2470 } 2471 2472 /** 2473 @name glow.NodeList#attr 2474 @function 2475 @description Gets or sets attributes. 2476 2477 When getting an attribute, it is retrieved from the first 2478 node in this NodeList. Setting attributes applies the change 2479 to each element in this NodeList. 2480 2481 To set an attribute, pass in the name as the first 2482 parameter and the value as a second parameter. 2483 2484 To set multiple attributes in one call, pass in an object of 2485 name/value pairs as a single parameter. 2486 2487 For browsers that don't support manipulating attributes 2488 using the DOM, this method will try to do the right thing 2489 (i.e. don't expect the semantics of this method to be 2490 consistent across browsers as this is not possible with 2491 currently supported browsers). 2492 2493 @param {string | Object} name The name of the attribute, or an object of name/value pairs 2494 @param {string} [value] The value to set the attribute to. 2495 2496 @returns {string | undefined | glow.NodeList} 2497 2498 When setting attributes this method returns its own NodeList, otherwise 2499 returns the attribute value. The attribute name is always treated as 2500 case-insensitive. When getting, the returned value will be of type string unless 2501 that particular attribute was never set and there is no default value, in which 2502 case the returned value will be an empty string. 2503 2504 @example 2505 var myNodeList = glow(".myImgClass"); 2506 2507 // get an attribute 2508 myNodeList.attr("class"); 2509 2510 // set an attribute 2511 myNodeList.attr("class", "anotherImgClass"); 2512 2513 // set multiple attributes 2514 myNodeList.attr({ 2515 src: "a.png", 2516 alt: "Cat jumping through a field" 2517 }); 2518 */ 2519 // see: http://tobielangel.com/2007/1/11/attribute-nightmare-in-ie/ 2520 NodeListProto.attr = function(/*arguments*/) { 2521 var args = arguments, 2522 argsLen = args.length, 2523 thisLen = this.length, 2524 name = keyvals = args[0], // using this API: attr(name) or attr({key: val}) ? 2525 dom0Property = '', 2526 node, 2527 attrNode; 2528 2529 /*!debug*/ 2530 if (arguments.length === 2 && typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#attr expects name to be of type string, not '+typeof arguments[0]+'.'); } 2531 else if (arguments.length === 1 && (typeof arguments[0] !== 'string' && arguments[0].constructor !== Object)) {glow.debug.warn('[wrong type] glow.NodeList#attr expects argument 1 to be of type string or an instance of Object.'); } 2532 else if (arguments.length === 0 || arguments.length > 2) { glow.debug.warn('[wrong count] glow.NodeList#attr expects 1 or 2 arguments, not '+arguments.length+'.'); } 2533 /*gubed!*/ 2534 2535 if (this.length === 0) { // is this an empty nodelist? 2536 return (argsLen > 1)? this : undef; 2537 } 2538 2539 if (typeof keyvals === 'object') { // SETting value from {name: value} object 2540 for (name in keyvals) { 2541 if (!keyvals.hasOwnProperty(name)) { continue; } 2542 2543 // in IE6 and IE7 the attribute name needs to be translated into dom property name 2544 if (glow.env.ie < 8) { 2545 dom0Property = dom0PropertyMapping[name.toLowerCase()]; 2546 } 2547 2548 var i = thisLen; 2549 while (i--) { 2550 node = this[i]; 2551 2552 if (node.nodeType !== 1) { continue; } 2553 2554 if (dom0Property) { 2555 node[dom0Property] = keyvals[name]; 2556 } 2557 else { 2558 node.setAttribute(name, keyvals[name], 0); // IE flags, 0: case-insensitive 2559 } 2560 } 2561 } 2562 2563 return this; 2564 } 2565 else { 2566 node = this[0]; 2567 2568 if (node.nodeType !== 1) { 2569 return (argsLen > 1)? this : undef; 2570 } 2571 2572 if (argsLen === 1) { // GETting value from name 2573 if (node.attributes[name]) { // in IE node.getAttributeNode sometimes returns unspecified default values so we look for specified attributes if we can 2574 return (!node.attributes[name].specified)? '' : node.attributes[name].value; 2575 } 2576 else if (node.getAttributeNode) { // in IE getAttribute() does not always work so we use getAttributeNode if we can 2577 attrNode = node.getAttributeNode(name, 0); 2578 return (attrNode === null)? '' : attrNode.value; 2579 } 2580 else { 2581 value = node.getAttribute(name, 0, 2); // IE flags, 0: case-insensitive, 2: as string 2582 return (value === null)? '' : value; 2583 } 2584 } 2585 else { // SETting a single value like attr(name, value), normalize to an keyval object 2586 if (glow.env.ie < 8) { 2587 dom0Property = dom0PropertyMapping[name.toLowerCase()]; 2588 } 2589 2590 if (dom0Property) { 2591 node[dom0Property] = args[1]; 2592 } 2593 else { 2594 node.setAttribute(name, args[1], 0); // IE flags, 0: case-insensitive 2595 } 2596 return this; 2597 } 2598 } 2599 }; 2600 /** 2601 Copies the data from one nodelist to another 2602 @private 2603 @name glow.NodeList._copyData 2604 @see glow.NodeList#clone 2605 @function 2606 */ 2607 glow.NodeList._copyData = function(from, to){ 2608 if ( !from[dataPropName] ){ 2609 return; 2610 } 2611 else{ 2612 to = new glow.NodeList(to); 2613 to.data( dataCache[from[dataPropName]] ); 2614 return; 2615 } 2616 2617 } 2618 /** 2619 Used to remove the data when a node is destroyed 2620 @private 2621 @name glow.NodeList._copyData 2622 @see glow.NodeList#destroy 2623 @function 2624 */ 2625 glow.NodeList._destroyData = function(removeFrom){ 2626 if ( !removeFrom && !removeFrom[0][dataPropName] ){ 2627 return; 2628 } 2629 else{ 2630 removeFromNode = new glow.NodeList(removeFrom); 2631 removeFromNode.removeData(); 2632 return; 2633 } 2634 2635 } 2636 /** 2637 @name glow.NodeList#data 2638 @function 2639 @description Use this to safely attach arbitrary data to any DOM Element. 2640 2641 This method is useful when you wish to avoid memory leaks that are possible when adding your own data directly to DOM Elements. 2642 2643 When called with no arguments, will return glow's entire data store for the first node in this NodeList. 2644 2645 Otherwise, when given a name, will return the associated value from the first node in this NodeList. 2646 2647 When given both a name and a value, will store that data on every node in this NodeList. 2648 2649 Optionally you can pass in a single object composed of multiple name, value pairs. 2650 2651 @param {string|Object} [key] The name of the value in glow's data store. 2652 @param {Object} [val] The value you wish to associate with the given name. 2653 @see glow.NodeList#removeData 2654 @example 2655 2656 glow("p").data("tea", "milky"); 2657 var colour = glow("p").data("tea"); // milky 2658 @returns {Object} When setting a value this method can be chained, as in that case it will return itself. 2659 @see glow.NodeList#removeData 2660 */ 2661 NodeListProto.data = function (key, val) { /*debug*///console.log("data("+key+", "+val+")"); 2662 var args = arguments, 2663 argsLen = args.length, 2664 keyvals = key, // like: data({key: val}) or data(key, val) 2665 index, 2666 node; 2667 2668 /*!debug*/ 2669 if (arguments.length === 2 && typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#data expects name argument to be of type string.'); } 2670 else if (arguments.length === 1 && (typeof arguments[0] !== 'string' && arguments[0].constructor !== Object)) {glow.debug.warn('[wrong type] glow.NodeList#data expects argument 1 to be of type string or an instance of Object.'); } 2671 else if (arguments.length > 2) { glow.debug.warn('[wrong count] glow.NodeList#data expects 0, 1 or 2 arguments.'); } 2672 /*gubed!*/ 2673 2674 if (argsLen > 1) { // SET key, val on every node 2675 var i = this.length; 2676 while (i--) { 2677 node = this[i]; 2678 if (node.nodeType !== 1) { continue; } 2679 2680 index = node[dataPropName]; 2681 if (!index) { // assumes index is always > 0 2682 index = dataIndex++; 2683 2684 node[dataPropName] = index; 2685 dataCache[index] = {}; 2686 } 2687 dataCache[index][key] = val; 2688 } 2689 2690 return this; // chainable with (key, val) signature 2691 } 2692 else if (typeof keyvals === 'object') { // SET keyvals on every node 2693 var i = this.length; 2694 while (i--) { 2695 node = this[i]; 2696 if (node.nodeType !== 1) { continue; } 2697 2698 index = node[dataPropName]; 2699 if (!index) { // assumes index is always > 0 2700 index = dataIndex++; 2701 2702 node[dataPropName] = index; 2703 dataCache[index] = {}; 2704 } 2705 for (key in keyvals) { 2706 dataCache[index][key] = keyvals[key]; 2707 } 2708 } 2709 2710 return this; // chainable with ({key, val}) signature 2711 } 2712 else { // GET from first node 2713 node = this[0]; 2714 if (node === undef || node.nodeType !== 1) { return undef; } 2715 2716 if ( !(index = node[dataPropName]) ) { 2717 return undef; 2718 } 2719 2720 if (key) { 2721 return dataCache[index][key]; 2722 } 2723 2724 // get the entire data cache object for this node 2725 return dataCache[index]; 2726 } 2727 }; 2728 2729 /** 2730 @name glow.NodeList#hasAttr 2731 @function 2732 @description Does the node have a particular attribute? 2733 2734 The first node in this NodeList is tested. 2735 2736 @param {string} name The name of the attribute to test for. 2737 2738 @returns {boolean|undefined} Returns undefined if the first node is not an element, 2739 or if the NodeList is empty, otherwise returns true/false to indicate if that attribute exists 2740 on the first element. 2741 2742 @example 2743 if ( glow("#myImg").hasAttr("alt") ){ 2744 // ... 2745 } 2746 */ 2747 NodeListProto.hasAttr = function(name) { 2748 var node; 2749 2750 /*!debug*/ 2751 if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.NodeList#hasAttr expects 1 argument.'); } 2752 else if (typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#hasAttr expects argument 1 to be of type string.'); } 2753 /*gubed!*/ 2754 2755 node = this[0]; 2756 2757 if (this.length && node.nodeType === 1) { 2758 if (node.attributes[name]) { // is an object in IE, or else: undefined in IE < 8, null in IE 8 2759 return !!node.attributes[name].specified; 2760 } 2761 2762 if (node.hasAttribute) { return node.hasAttribute(name); } // like FF, Safari, etc 2763 else { return node.attributes[name] !== undef; } // like IE7 2764 } 2765 }; 2766 2767 /** 2768 @name glow.NodeList#hasClass 2769 @function 2770 @description Does the node have a particular class? 2771 2772 The first node in this NodeList is tested. 2773 2774 @param {string} name The name of the class to test for. 2775 2776 @returns {boolean} 2777 2778 @example 2779 if ( glow("#myInput").hasClass("errored") ){ 2780 // ... 2781 } 2782 */ 2783 NodeListProto.hasClass = function (name) { 2784 /*!debug*/ 2785 if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.NodeList#hasClass expects 1 argument.'); } 2786 else if (typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#hasClass expects argument 1 to be of type string.'); } 2787 /*gubed!*/ 2788 2789 if (this.length && this[0].nodeType === 1) { 2790 return ( (' ' + this[0].className + ' ').indexOf(' ' + name + ' ') > -1 ); 2791 } 2792 }; 2793 2794 /** 2795 @name glow.NodeList#prop 2796 @function 2797 @description Gets or sets node properties. 2798 2799 This function gets / sets node properties, to get attributes, 2800 see {@link glow.NodeList#attr NodeList#attr}. 2801 2802 When getting a property, it is retrieved from the first 2803 node in this NodeList. Setting properties to each element in 2804 this NodeList. 2805 2806 To set multiple properties in one call, pass in an object of 2807 name/value pairs. 2808 2809 @param {string | Object} name The name of the property, or an object of name/value pairs 2810 @param {string} [value] The value to set the property to. 2811 2812 @returns {string | glow.NodeList} 2813 2814 When setting properties it returns the NodeList, otherwise 2815 returns the property value. 2816 2817 @example 2818 var myNodeList = glow("#formElement"); 2819 2820 // get the node name 2821 myNodeList.prop("nodeName"); 2822 2823 // set a property 2824 myNodeList.prop("_secretValue", 10); 2825 2826 // set multiple properties 2827 myNodeList.prop({ 2828 checked: true, 2829 _secretValue: 10 2830 }); 2831 */ 2832 NodeListProto.prop = function(name, val) { 2833 var hash = name, 2834 argsLen = arguments.length; 2835 2836 /*!debug*/ 2837 if (arguments.length === 1 && (typeof name !== 'string' && name.constructor !== Object)) {glow.debug.warn('[wrong type] glow.NodeList#prop expects argument 1 to be of type string or Object.'); } 2838 else if (arguments.length === 2 && typeof name !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#prop expects name to be of type string.'); } 2839 else if (arguments.length === 0 || arguments.length > 2) { glow.debug.warn('[wrong count] glow.NodeList#prop expects 1 or 2 arguments.'); } 2840 /*gubed!*/ 2841 2842 if (this.length === 0) return; 2843 2844 if (argsLen === 2 && typeof name === 'string') { 2845 for (var i = 0, ilen = this.length; i < ilen; i++) { 2846 if (this[i].nodeType === 1) { this[i][name] = val; } 2847 } 2848 return this; 2849 } 2850 else if (argsLen === 1 && hash.constructor === Object) { 2851 for (var key in hash) { 2852 for (var i = 0, ilen = this.length; i < ilen; i++) { 2853 if (this[i].nodeType === 1) { this[i][key] = hash[key]; } 2854 } 2855 } 2856 return this; 2857 } 2858 else if (argsLen === 1 && typeof name === 'string') { 2859 if (this[0].nodeType === 1) { return this[0][name]; } 2860 } 2861 else { 2862 throw new Error('Invalid parameters.'); 2863 } 2864 }; 2865 2866 /** 2867 @name glow.NodeList#removeAttr 2868 @function 2869 @description Removes an attribute from each node. 2870 2871 @param {string} name The name of the attribute to remove. 2872 2873 @returns {glow.NodeList} 2874 2875 @example 2876 glow("a").removeAttr("target"); 2877 */ 2878 NodeListProto.removeAttr = function (name) { 2879 var dom0Property; 2880 2881 /*!debug*/ 2882 if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.NodeList#removeAttr expects 1 argument.'); } 2883 else if (typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#removeAttr expects argument 1 to be of type string.'); } 2884 /*gubed!*/ 2885 2886 for (var i = 0, leni = this.length; i < leni; i++) { 2887 if (this[i].nodeType === 1) { 2888 if (glow.env.ie < 8) { 2889 if ( (dom0Property = dom0PropertyMapping[name.toLowerCase()]) ) { 2890 this[i][dom0Property] = ''; 2891 } 2892 } 2893 2894 if (this[i].removeAttribute) this[i].removeAttribute(name); 2895 } 2896 } 2897 return this; 2898 }; 2899 2900 /** 2901 @name glow.NodeList#removeClass 2902 @function 2903 @description Removes a class from each node. 2904 2905 @param {string} name The name of the class to remove. 2906 2907 @returns {glow.NodeList} 2908 2909 @example 2910 glow("#footer #login a").removeClass("highlight"); 2911 */ 2912 NodeListProto.removeClass = function(name) { 2913 var node; 2914 2915 /*!debug*/ 2916 if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.NodeList#removeClass() expects 1 argument.'); } 2917 else if (typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#removeClass() expects argument 1 to be of type string.'); } 2918 /*gubed!*/ 2919 2920 var i = this.length; 2921 while (i--) { 2922 node = this[i]; 2923 if (node.className) { 2924 _removeClass(node, name); 2925 } 2926 } 2927 return this; 2928 }; 2929 2930 function _removeClass(node, name) { 2931 var oldClasses = node.className.split(' '), 2932 newClasses = []; 2933 2934 oldClasses = node.className.split(' '); 2935 newClasses = []; 2936 2937 var i = oldClasses.length; 2938 while (i--) { 2939 if (oldClasses[i] !== name) { 2940 oldClasses[i] && newClasses.unshift(oldClasses[i]); // unshift to maintain original order 2941 } 2942 } 2943 node.className = (newClasses.length)? newClasses.join(' ') : ''; 2944 } 2945 2946 /** 2947 @name glow.NodeList#removeData 2948 @function 2949 @description Removes data previously added by {@link glow.NodeList#data} from each node in this NodeList. 2950 2951 When called with no arguments, will delete glow's entire data store for each node in this NodeList. 2952 2953 Otherwise, when given a name, will delete the associated value from each node in this NodeList. 2954 2955 @param {string} [key] The name of the value in glow's data store. 2956 @see glow.NodeList#data 2957 */ 2958 NodeListProto.removeData = function(key) { 2959 var elm, 2960 i = this.length, 2961 index; 2962 // uses private scoped variables: dataCache, dataPropName 2963 2964 /*!debug*/ 2965 if (arguments.length > 1) { glow.debug.warn('[wrong count] glow.NodeList#removeData expects 0 or 1 arguments.'); } 2966 else if (arguments.length === 1 && typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#removeData expects argument 1 to be of type string.'); } 2967 /*gubed!*/ 2968 2969 while (i--) { 2970 elm = this[i]; 2971 index = elm[dataPropName]; 2972 2973 if (index !== undef) { 2974 switch (arguments.length) { 2975 case 0: 2976 dataCache[index] = undef; 2977 elm[dataPropName] = undef; 2978 try { 2979 delete elm[dataPropName]; // IE 6 goes wobbly here 2980 } 2981 catch(e) { // remove expando from IE 6 2982 elm.removeAttribute && elm.removeAttribute(dataPropName); 2983 } 2984 break; 2985 case 1: 2986 dataCache[index][key] = undef; 2987 delete dataCache[index][key]; 2988 break; 2989 } 2990 } 2991 } 2992 2993 return this; // chainable 2994 }; 2995 2996 /** 2997 @name glow.NodeList#toggleClass 2998 @function 2999 @description Toggles a class on each node. 3000 3001 @param {string} name The name of the class to toggle. 3002 3003 @returns {glow.NodeList} 3004 3005 @example 3006 glow(".onOffSwitch").toggleClass("on"); 3007 */ 3008 NodeListProto.toggleClass = function(name) { 3009 var node; 3010 3011 /*!debug*/ 3012 if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.NodeList#toggleClass() expects 1 argument.'); } 3013 else if (typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#toggleClass() expects argument 1 to be of type string.'); } 3014 /*gubed!*/ 3015 3016 for (var i = 0, leni = this.length; i < leni; i++) { 3017 node = this[i]; 3018 if (node.className) { 3019 if ( (' ' + node.className + ' ').indexOf(' ' + name + ' ') > -1 ) { 3020 _removeClass(node, name); 3021 } 3022 else { 3023 _addClass(node, name); 3024 } 3025 } 3026 } 3027 3028 return this; 3029 }; 3030 3031 /** 3032 @name glow.NodeList#val 3033 @function 3034 @description Gets or sets form values for the first node. 3035 3036 <p><em>This method is not applicable to XML NodeLists.</em></p> 3037 3038 <p><em>Getting values from form elements</em></p> 3039 3040 The returned value depends on the type of element, see below: 3041 3042 <dl> 3043 <dt>Radio button or checkbox</dt> 3044 <dd>If checked, then the contents of the value attribute, otherwise an empty string.</dd> 3045 <dt>Select</dt> 3046 <dd>The contents of value attribute of the selected option</dd> 3047 <dt>Select (multiple)</dt> 3048 <dd>An array of selected option values.</dd> 3049 <dt>Other form element</dt> 3050 <dd>The value of the input.</dd> 3051 </dl> 3052 3053 <p><em>Getting values from a form</em></p> 3054 3055 If the first element in the NodeList is a form, then an 3056 object is returned containing the form data. Each item 3057 property of the object is a value as above, apart from when 3058 multiple elements of the same name exist, in which case the 3059 it will contain an array of values. 3060 3061 <p><em>Setting values for form elements</em></p> 3062 3063 If a value is passed and the first element of the NodeList 3064 is a form element, then the form element is given that value. 3065 For select elements, this means that the first option that 3066 matches the value will be selected. For selects that allow 3067 multiple selection, the options which have a value that 3068 exists in the array of values/match the value will be 3069 selected and others will be deselected. 3070 3071 Currently checkboxes and radio buttons are not checked or 3072 unchecked, just their value is changed. This does mean that 3073 this does not do exactly the reverse of getting the value 3074 from the element (see above) and as such may be subject to 3075 change 3076 3077 <p><em>Setting values for forms</em></p> 3078 3079 If the first element in the NodeList is a form and the 3080 value is an object, then each element of the form has its 3081 value set to the corresponding property of the object, using 3082 the method described above. 3083 3084 @param {String | Object} [value] The value to set the form element/elements to. 3085 3086 @returns {glow.dom.NodeList | String | Object} 3087 3088 When used to set a value it returns the NodeList, otherwise 3089 returns the value as described above. 3090 3091 @example 3092 // get a value 3093 var username = glow.dom.get("input#username").val(); 3094 3095 @example 3096 / get values from a form 3097 var userDetails = glow.dom.get("form").val(); 3098 3099 @example 3100 // set a value 3101 glow.dom.get("input#username").val("example username"); 3102 3103 @example 3104 // set values in a form 3105 glow.dom.get("form").val({ 3106 username : "another", 3107 name : "A N Other" 3108 }); 3109 */ 3110 NodeListProto.val = function(){ 3111 /* 3112 PrivateFunction: elementValue 3113 Get a value for a form element. 3114 */ 3115 3116 function elementValue (el) { 3117 var elType = el.type, 3118 elChecked = el.checked, 3119 elValue = el.value, 3120 vals = [], 3121 i = 0; 3122 3123 if (elType == "radio") { 3124 return elChecked ? elValue : ""; 3125 } else if (elType == "checkbox") { 3126 return elChecked ? elValue : ""; 3127 } else if (elType == "select-one") { 3128 return el.selectedIndex > -1 ? 3129 el.options[el.selectedIndex].value : ""; 3130 } else if (elType == "select-multiple") { 3131 for (var length = el.options.length; i < length; i++) { 3132 if (el.options[i].selected) { 3133 vals[vals.length] = el.options[i].value; 3134 } 3135 } 3136 return vals; 3137 } else { 3138 return elValue; 3139 } 3140 } 3141 3142 /* 3143 PrivateMethod: formValues 3144 Get an object containing form data. 3145 */ 3146 function formValues (form) { 3147 var vals = {}, 3148 radios = {}, 3149 formElements = form.elements, 3150 i = 0, 3151 length = formElements.length, 3152 name, 3153 formElement, 3154 j, 3155 radio, 3156 nodeName; 3157 3158 for (; i < length; i++) { 3159 formElement = formElements[i]; 3160 nodeName = formElement.nodeName.toLowerCase(); 3161 name = formElement.name; 3162 3163 // fieldsets & objects come back as form elements, but we don't care about these 3164 // we don't bother with fields that don't have a name 3165 // switch to whitelist? 3166 if ( 3167 nodeName == "fieldset" || 3168 nodeName == "object" || 3169 !name 3170 ) { continue; } 3171 if (formElement.type == "checkbox" && ! formElement.checked) { 3172 if (! name in vals) { 3173 vals[name] = undefined; 3174 } 3175 } else if (formElement.type == "radio") { 3176 if (radios[name]) { 3177 radios[name][radios[name].length] = formElement; 3178 } else { 3179 radios[name] = [formElement]; 3180 } 3181 } else { 3182 var value = elementValue(formElement); 3183 if (name in vals) { 3184 if (vals[name].push) { 3185 vals[name][vals[name].length] = value; 3186 } else { 3187 vals[name] = [vals[name], value]; 3188 } 3189 } else { 3190 vals[name] = value; 3191 } 3192 } 3193 } 3194 for (i in radios) { 3195 j = 0; 3196 for (length = radios[i].length; j < length; j++) { 3197 radio = radios[i][j]; 3198 name = radio.name; 3199 if (radio.checked) { 3200 vals[radio.name] = radio.value; 3201 break; 3202 } 3203 } 3204 if (! name in vals) { vals[name] = undefined; } 3205 } 3206 return vals; 3207 } 3208 3209 /* 3210 PrivateFunction: setFormValues 3211 Set values of a form to those in passed in object. 3212 */ 3213 function setFormValues (form, vals) { 3214 var prop, currentField, 3215 fields = {}, 3216 storeType, i = 0, n, len, foundOne, currentFieldType; 3217 3218 for (prop in vals) { 3219 currentField = form[prop]; 3220 if (currentField && currentField[0] && !currentField.options) { // is array of fields 3221 //normalise values to array of vals 3222 vals[prop] = vals[prop] && vals[prop].push ? vals[prop] : [vals[prop]]; 3223 //order the fields by types that matter 3224 fields.radios = []; 3225 fields.checkboxesSelects = []; 3226 fields.multiSelects = []; 3227 fields.other = []; 3228 3229 for (i = 0; currentField[i]; i++) { 3230 currentFieldType = currentField[i].type; 3231 if (currentFieldType == "radio") { 3232 storeType = "radios"; 3233 } else if (currentFieldType == "select-one" || currentFieldType == "checkbox") { 3234 storeType = "checkboxesSelects"; 3235 } else if (currentFieldType == "select-multiple") { 3236 storeType = "multiSelects"; 3237 } else { 3238 storeType = "other"; 3239 } 3240 //add it to the correct array 3241 fields[storeType][fields[storeType].length] = currentField[i]; 3242 } 3243 3244 for (i = 0; fields.multiSelects[i]; i++) { 3245 vals[prop] = setValue(fields.multiSelects[i], vals[prop]); 3246 } 3247 for (i = 0; fields.checkboxesSelects[i]; i++) { 3248 setValue(fields.checkboxesSelects[i], ""); 3249 for (n = 0, len = vals[prop].length; n < len; n++) { 3250 if (setValue(fields.checkboxesSelects[i], vals[prop][n])) { 3251 vals[prop].slice(n, 1); 3252 break; 3253 } 3254 } 3255 } 3256 for (i = 0; fields.radios[i]; i++) { 3257 fields.radios[i].checked = false; 3258 foundOne = false; 3259 for (n = 0, len = vals[prop].length; n < len; n++) { 3260 if (setValue(fields.radios[i], vals[prop][n])) { 3261 vals[prop].slice(n, 1); 3262 foundOne = true; 3263 break; 3264 } 3265 if (foundOne) { break; } 3266 } 3267 } 3268 for (i = 0; fields.other[i] && vals[prop][i] !== undefined; i++) { 3269 setValue(fields.other[i], vals[prop][i]); 3270 } 3271 } else if (currentField && currentField.nodeName) { // is single field, easy 3272 setValue(currentField, vals[prop]); 3273 } 3274 } 3275 } 3276 3277 /* 3278 PrivateFunction: setValue 3279 Set the value of a form element. 3280 Returns: 3281 values that weren't able to set if array of vals passed (for multi select). Otherwise true if val set, false if not 3282 */ 3283 function setValue (el, val) { 3284 var i = 0, 3285 length, 3286 n = 0, 3287 nlen, 3288 elOption, 3289 optionVal; 3290 3291 if (el.type == "select-one") { 3292 for (length = el.options.length; i < length; i++) { 3293 if (el.options[i].value == val) { 3294 el.selectedIndex = i; 3295 return true; 3296 } 3297 } 3298 return false; 3299 } else if (el.type == "select-multiple") { 3300 var isArray = !!val.push; 3301 for (i = 0, length = el.options.length; i < length; i++) { 3302 elOption = el.options[i]; 3303 optionVal = elOption.value; 3304 if (isArray) { 3305 elOption.selected = false; 3306 for (nlen = val.length; n < nlen; n++) { 3307 if (optionVal == val[n]) { 3308 elOption.selected = true; 3309 val.splice(n, 1); 3310 break; 3311 } 3312 } 3313 } else { 3314 return elOption.selected = val == optionVal; 3315 } 3316 } 3317 return false; 3318 } else if (el.type == "radio" || el.type == "checkbox") { 3319 el.checked = val == el.value; 3320 return val == el.value; 3321 } else { 3322 el.value = val; 3323 return true; 3324 } 3325 } 3326 3327 // toplevel implementation 3328 3329 var args = arguments, 3330 val = args[0], 3331 that = this, 3332 i = 0, 3333 length = that.length; 3334 3335 if (args.length === 0) { 3336 return that[0].nodeName == 'FORM' ? 3337 formValues(that[0]) : 3338 elementValue(that[0]); 3339 } 3340 if (that[0].nodeName == 'FORM') { 3341 if (! typeof val == 'object') { 3342 throw 'value for FORM must be object'; 3343 } 3344 setFormValues(that[0], val); 3345 } else { 3346 for (; i < length; i++) { 3347 setValue(that[i], val); 3348 } 3349 } 3350 return that; 3351 }; 3352 }); 3353 /*! 3354 * Sizzle CSS Selector Engine - v1.0 3355 * Copyright 2009, The Dojo Foundation 3356 * Released under the MIT, BSD, and GPL Licenses. 3357 * More information: http://sizzlejs.com/ 3358 */ 3359 (function(){ 3360 3361 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, 3362 done = 0, 3363 toString = Object.prototype.toString, 3364 hasDuplicate = false, 3365 baseHasDuplicate = true; 3366 3367 // Here we check if the JavaScript engine is using some sort of 3368 // optimization where it does not always call our comparision 3369 // function. If that is the case, discard the hasDuplicate value. 3370 // Thus far that includes Google Chrome. 3371 [0, 0].sort(function(){ 3372 baseHasDuplicate = false; 3373 return 0; 3374 }); 3375 3376 var Sizzle = function(selector, context, results, seed) { 3377 results = results || []; 3378 var origContext = context = context || document; 3379 3380 if ( context.nodeType !== 1 && context.nodeType !== 9 ) { 3381 return []; 3382 } 3383 3384 if ( !selector || typeof selector !== "string" ) { 3385 return results; 3386 } 3387 3388 var parts = [], m, set, checkSet, extra, prune = true, contextXML = isXML(context), 3389 soFar = selector; 3390 3391 // Reset the position of the chunker regexp (start from head) 3392 while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) { 3393 soFar = m[3]; 3394 3395 parts.push( m[1] ); 3396 3397 if ( m[2] ) { 3398 extra = m[3]; 3399 break; 3400 } 3401 } 3402 3403 if ( parts.length > 1 && origPOS.exec( selector ) ) { 3404 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { 3405 set = posProcess( parts[0] + parts[1], context ); 3406 } else { 3407 set = Expr.relative[ parts[0] ] ? 3408 [ context ] : 3409 Sizzle( parts.shift(), context ); 3410 3411 while ( parts.length ) { 3412 selector = parts.shift(); 3413 3414 if ( Expr.relative[ selector ] ) { 3415 selector += parts.shift(); 3416 } 3417 3418 set = posProcess( selector, set ); 3419 } 3420 } 3421 } else { 3422 // Take a shortcut and set the context if the root selector is an ID 3423 // (but not if it'll be faster if the inner selector is an ID) 3424 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && 3425 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { 3426 var ret = Sizzle.find( parts.shift(), context, contextXML ); 3427 context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; 3428 } 3429 3430 if ( context ) { 3431 var ret = seed ? 3432 { expr: parts.pop(), set: makeArray(seed) } : 3433 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); 3434 set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; 3435 3436 if ( parts.length > 0 ) { 3437 checkSet = makeArray(set); 3438 } else { 3439 prune = false; 3440 } 3441 3442 while ( parts.length ) { 3443 var cur = parts.pop(), pop = cur; 3444 3445 if ( !Expr.relative[ cur ] ) { 3446 cur = ""; 3447 } else { 3448 pop = parts.pop(); 3449 } 3450 3451 if ( pop == null ) { 3452 pop = context; 3453 } 3454 3455 Expr.relative[ cur ]( checkSet, pop, contextXML ); 3456 } 3457 } else { 3458 checkSet = parts = []; 3459 } 3460 } 3461 3462 if ( !checkSet ) { 3463 checkSet = set; 3464 } 3465 3466 if ( !checkSet ) { 3467 throw "Syntax error, unrecognized expression: " + (cur || selector); 3468 } 3469 3470 if ( toString.call(checkSet) === "[object Array]" ) { 3471 if ( !prune ) { 3472 results.push.apply( results, checkSet ); 3473 } else if ( context && context.nodeType === 1 ) { 3474 for ( var i = 0; checkSet[i] != null; i++ ) { 3475 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { 3476 results.push( set[i] ); 3477 } 3478 } 3479 } else { 3480 for ( var i = 0; checkSet[i] != null; i++ ) { 3481 if ( checkSet[i] && checkSet[i].nodeType === 1 ) { 3482 results.push( set[i] ); 3483 } 3484 } 3485 } 3486 } else { 3487 makeArray( checkSet, results ); 3488 } 3489 3490 if ( extra ) { 3491 Sizzle( extra, origContext, results, seed ); 3492 Sizzle.uniqueSort( results ); 3493 } 3494 3495 return results; 3496 }; 3497 3498 Sizzle.uniqueSort = function(results){ 3499 if ( sortOrder ) { 3500 hasDuplicate = baseHasDuplicate; 3501 results.sort(sortOrder); 3502 3503 if ( hasDuplicate ) { 3504 for ( var i = 1; i < results.length; i++ ) { 3505 if ( results[i] === results[i-1] ) { 3506 results.splice(i--, 1); 3507 } 3508 } 3509 } 3510 } 3511 3512 return results; 3513 }; 3514 3515 Sizzle.matches = function(expr, set){ 3516 return Sizzle(expr, null, null, set); 3517 }; 3518 3519 Sizzle.find = function(expr, context, isXML){ 3520 var set, match; 3521 3522 if ( !expr ) { 3523 return []; 3524 } 3525 3526 for ( var i = 0, l = Expr.order.length; i < l; i++ ) { 3527 var type = Expr.order[i], match; 3528 3529 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { 3530 var left = match[1]; 3531 match.splice(1,1); 3532 3533 if ( left.substr( left.length - 1 ) !== "\\" ) { 3534 match[1] = (match[1] || "").replace(/\\/g, ""); 3535 set = Expr.find[ type ]( match, context, isXML ); 3536 if ( set != null ) { 3537 expr = expr.replace( Expr.match[ type ], "" ); 3538 break; 3539 } 3540 } 3541 } 3542 } 3543 3544 if ( !set ) { 3545 set = context.getElementsByTagName("*"); 3546 } 3547 3548 return {set: set, expr: expr}; 3549 }; 3550 3551 Sizzle.filter = function(expr, set, inplace, not){ 3552 var old = expr, result = [], curLoop = set, match, anyFound, 3553 isXMLFilter = set && set[0] && isXML(set[0]); 3554 3555 while ( expr && set.length ) { 3556 for ( var type in Expr.filter ) { 3557 if ( (match = Expr.match[ type ].exec( expr )) != null ) { 3558 var filter = Expr.filter[ type ], found, item; 3559 anyFound = false; 3560 3561 if ( curLoop === result ) { 3562 result = []; 3563 } 3564 3565 if ( Expr.preFilter[ type ] ) { 3566 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); 3567 3568 if ( !match ) { 3569 anyFound = found = true; 3570 } else if ( match === true ) { 3571 continue; 3572 } 3573 } 3574 3575 if ( match ) { 3576 for ( var i = 0; (item = curLoop[i]) != null; i++ ) { 3577 if ( item ) { 3578 found = filter( item, match, i, curLoop ); 3579 var pass = not ^ !!found; 3580 3581 if ( inplace && found != null ) { 3582 if ( pass ) { 3583 anyFound = true; 3584 } else { 3585 curLoop[i] = false; 3586 } 3587 } else if ( pass ) { 3588 result.push( item ); 3589 anyFound = true; 3590 } 3591 } 3592 } 3593 } 3594 3595 if ( found !== undefined ) { 3596 if ( !inplace ) { 3597 curLoop = result; 3598 } 3599 3600 expr = expr.replace( Expr.match[ type ], "" ); 3601 3602 if ( !anyFound ) { 3603 return []; 3604 } 3605 3606 break; 3607 } 3608 } 3609 } 3610 3611 // Improper expression 3612 if ( expr === old ) { 3613 if ( anyFound == null ) { 3614 throw "Syntax error, unrecognized expression: " + expr; 3615 } else { 3616 break; 3617 } 3618 } 3619 3620 old = expr; 3621 } 3622 3623 return curLoop; 3624 }; 3625 3626 var Expr = Sizzle.selectors = { 3627 order: [ "ID", "NAME", "TAG" ], 3628 match: { 3629 ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/, 3630 CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/, 3631 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/, 3632 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, 3633 TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/, 3634 CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, 3635 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, 3636 PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/ 3637 }, 3638 leftMatch: {}, 3639 attrMap: { 3640 "class": "className", 3641 "for": "htmlFor" 3642 }, 3643 attrHandle: { 3644 href: function(elem){ 3645 return elem.getAttribute("href"); 3646 } 3647 }, 3648 relative: { 3649 "+": function(checkSet, part){ 3650 var isPartStr = typeof part === "string", 3651 isTag = isPartStr && !/\W/.test(part), 3652 isPartStrNotTag = isPartStr && !isTag; 3653 3654 if ( isTag ) { 3655 part = part.toLowerCase(); 3656 } 3657 3658 for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { 3659 if ( (elem = checkSet[i]) ) { 3660 while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} 3661 3662 checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? 3663 elem || false : 3664 elem === part; 3665 } 3666 } 3667 3668 if ( isPartStrNotTag ) { 3669 Sizzle.filter( part, checkSet, true ); 3670 } 3671 }, 3672 ">": function(checkSet, part){ 3673 var isPartStr = typeof part === "string"; 3674 3675 if ( isPartStr && !/\W/.test(part) ) { 3676 part = part.toLowerCase(); 3677 3678 for ( var i = 0, l = checkSet.length; i < l; i++ ) { 3679 var elem = checkSet[i]; 3680 if ( elem ) { 3681 var parent = elem.parentNode; 3682 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; 3683 } 3684 } 3685 } else { 3686 for ( var i = 0, l = checkSet.length; i < l; i++ ) { 3687 var elem = checkSet[i]; 3688 if ( elem ) { 3689 checkSet[i] = isPartStr ? 3690 elem.parentNode : 3691 elem.parentNode === part; 3692 } 3693 } 3694 3695 if ( isPartStr ) { 3696 Sizzle.filter( part, checkSet, true ); 3697 } 3698 } 3699 }, 3700 "": function(checkSet, part, isXML){ 3701 var doneName = done++, checkFn = dirCheck; 3702 3703 if ( typeof part === "string" && !/\W/.test(part) ) { 3704 var nodeCheck = part = part.toLowerCase(); 3705 checkFn = dirNodeCheck; 3706 } 3707 3708 checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); 3709 }, 3710 "~": function(checkSet, part, isXML){ 3711 var doneName = done++, checkFn = dirCheck; 3712 3713 if ( typeof part === "string" && !/\W/.test(part) ) { 3714 var nodeCheck = part = part.toLowerCase(); 3715 checkFn = dirNodeCheck; 3716 } 3717 3718 checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); 3719 } 3720 }, 3721 find: { 3722 ID: function(match, context, isXML){ 3723 if ( typeof context.getElementById !== "undefined" && !isXML ) { 3724 var m = context.getElementById(match[1]); 3725 return m ? [m] : []; 3726 } 3727 }, 3728 NAME: function(match, context){ 3729 if ( typeof context.getElementsByName !== "undefined" ) { 3730 var ret = [], results = context.getElementsByName(match[1]); 3731 3732 for ( var i = 0, l = results.length; i < l; i++ ) { 3733 if ( results[i].getAttribute("name") === match[1] ) { 3734 ret.push( results[i] ); 3735 } 3736 } 3737 3738 return ret.length === 0 ? null : ret; 3739 } 3740 }, 3741 TAG: function(match, context){ 3742 return context.getElementsByTagName(match[1]); 3743 } 3744 }, 3745 preFilter: { 3746 CLASS: function(match, curLoop, inplace, result, not, isXML){ 3747 match = " " + match[1].replace(/\\/g, "") + " "; 3748 3749 if ( isXML ) { 3750 return match; 3751 } 3752 3753 for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { 3754 if ( elem ) { 3755 if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) { 3756 if ( !inplace ) { 3757 result.push( elem ); 3758 } 3759 } else if ( inplace ) { 3760 curLoop[i] = false; 3761 } 3762 } 3763 } 3764 3765 return false; 3766 }, 3767 ID: function(match){ 3768 return match[1].replace(/\\/g, ""); 3769 }, 3770 TAG: function(match, curLoop){ 3771 return match[1].toLowerCase(); 3772 }, 3773 CHILD: function(match){ 3774 if ( match[1] === "nth" ) { 3775 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' 3776 var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( 3777 match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || 3778 !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); 3779 3780 // calculate the numbers (first)n+(last) including if they are negative 3781 match[2] = (test[1] + (test[2] || 1)) - 0; 3782 match[3] = test[3] - 0; 3783 } 3784 3785 // TODO: Move to normal caching system 3786 match[0] = done++; 3787 3788 return match; 3789 }, 3790 ATTR: function(match, curLoop, inplace, result, not, isXML){ 3791 var name = match[1].replace(/\\/g, ""); 3792 3793 if ( !isXML && Expr.attrMap[name] ) { 3794 match[1] = Expr.attrMap[name]; 3795 } 3796 3797 if ( match[2] === "~=" ) { 3798 match[4] = " " + match[4] + " "; 3799 } 3800 3801 return match; 3802 }, 3803 PSEUDO: function(match, curLoop, inplace, result, not){ 3804 if ( match[1] === "not" ) { 3805 // If we're dealing with a complex expression, or a simple one 3806 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { 3807 match[3] = Sizzle(match[3], null, null, curLoop); 3808 } else { 3809 var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); 3810 if ( !inplace ) { 3811 result.push.apply( result, ret ); 3812 } 3813 return false; 3814 } 3815 } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { 3816 return true; 3817 } 3818 3819 return match; 3820 }, 3821 POS: function(match){ 3822 match.unshift( true ); 3823 return match; 3824 } 3825 }, 3826 filters: { 3827 enabled: function(elem){ 3828 return elem.disabled === false && elem.type !== "hidden"; 3829 }, 3830 disabled: function(elem){ 3831 return elem.disabled === true; 3832 }, 3833 checked: function(elem){ 3834 return elem.checked === true; 3835 }, 3836 selected: function(elem){ 3837 // Accessing this property makes selected-by-default 3838 // options in Safari work properly 3839 elem.parentNode.selectedIndex; 3840 return elem.selected === true; 3841 }, 3842 parent: function(elem){ 3843 return !!elem.firstChild; 3844 }, 3845 empty: function(elem){ 3846 return !elem.firstChild; 3847 }, 3848 has: function(elem, i, match){ 3849 return !!Sizzle( match[3], elem ).length; 3850 }, 3851 header: function(elem){ 3852 return /h\d/i.test( elem.nodeName ); 3853 }, 3854 text: function(elem){ 3855 return "text" === elem.type; 3856 }, 3857 radio: function(elem){ 3858 return "radio" === elem.type; 3859 }, 3860 checkbox: function(elem){ 3861 return "checkbox" === elem.type; 3862 }, 3863 file: function(elem){ 3864 return "file" === elem.type; 3865 }, 3866 password: function(elem){ 3867 return "password" === elem.type; 3868 }, 3869 submit: function(elem){ 3870 return "submit" === elem.type; 3871 }, 3872 image: function(elem){ 3873 return "image" === elem.type; 3874 }, 3875 reset: function(elem){ 3876 return "reset" === elem.type; 3877 }, 3878 button: function(elem){ 3879 return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; 3880 }, 3881 input: function(elem){ 3882 return /input|select|textarea|button/i.test(elem.nodeName); 3883 } 3884 }, 3885 setFilters: { 3886 first: function(elem, i){ 3887 return i === 0; 3888 }, 3889 last: function(elem, i, match, array){ 3890 return i === array.length - 1; 3891 }, 3892 even: function(elem, i){ 3893 return i % 2 === 0; 3894 }, 3895 odd: function(elem, i){ 3896 return i % 2 === 1; 3897 }, 3898 lt: function(elem, i, match){ 3899 return i < match[3] - 0; 3900 }, 3901 gt: function(elem, i, match){ 3902 return i > match[3] - 0; 3903 }, 3904 nth: function(elem, i, match){ 3905 return match[3] - 0 === i; 3906 }, 3907 eq: function(elem, i, match){ 3908 return match[3] - 0 === i; 3909 } 3910 }, 3911 filter: { 3912 PSEUDO: function(elem, match, i, array){ 3913 var name = match[1], filter = Expr.filters[ name ]; 3914 3915 if ( filter ) { 3916 return filter( elem, i, match, array ); 3917 } else if ( name === "contains" ) { 3918 return (elem.textContent || elem.innerText || getText([ elem ]) || "").indexOf(match[3]) >= 0; 3919 } else if ( name === "not" ) { 3920 var not = match[3]; 3921 3922 for ( var i = 0, l = not.length; i < l; i++ ) { 3923 if ( not[i] === elem ) { 3924 return false; 3925 } 3926 } 3927 3928 return true; 3929 } else { 3930 throw "Syntax error, unrecognized expression: " + name; 3931 } 3932 }, 3933 CHILD: function(elem, match){ 3934 var type = match[1], node = elem; 3935 switch (type) { 3936 case 'only': 3937 case 'first': 3938 while ( (node = node.previousSibling) ) { 3939 if ( node.nodeType === 1 ) { 3940 return false; 3941 } 3942 } 3943 if ( type === "first" ) { 3944 return true; 3945 } 3946 node = elem; 3947 case 'last': 3948 while ( (node = node.nextSibling) ) { 3949 if ( node.nodeType === 1 ) { 3950 return false; 3951 } 3952 } 3953 return true; 3954 case 'nth': 3955 var first = match[2], last = match[3]; 3956 3957 if ( first === 1 && last === 0 ) { 3958 return true; 3959 } 3960 3961 var doneName = match[0], 3962 parent = elem.parentNode; 3963 3964 if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { 3965 var count = 0; 3966 for ( node = parent.firstChild; node; node = node.nextSibling ) { 3967 if ( node.nodeType === 1 ) { 3968 node.nodeIndex = ++count; 3969 } 3970 } 3971 parent.sizcache = doneName; 3972 } 3973 3974 var diff = elem.nodeIndex - last; 3975 if ( first === 0 ) { 3976 return diff === 0; 3977 } else { 3978 return ( diff % first === 0 && diff / first >= 0 ); 3979 } 3980 } 3981 }, 3982 ID: function(elem, match){ 3983 return elem.nodeType === 1 && elem.getAttribute("id") === match; 3984 }, 3985 TAG: function(elem, match){ 3986 return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; 3987 }, 3988 CLASS: function(elem, match){ 3989 return (" " + (elem.className || elem.getAttribute("class")) + " ") 3990 .indexOf( match ) > -1; 3991 }, 3992 ATTR: function(elem, match){ 3993 var name = match[1], 3994 result = Expr.attrHandle[ name ] ? 3995 Expr.attrHandle[ name ]( elem ) : 3996 elem[ name ] != null ? 3997 elem[ name ] : 3998 elem.getAttribute( name ), 3999 value = result + "", 4000 type = match[2], 4001 check = match[4]; 4002 4003 return result == null ? 4004 type === "!=" : 4005 type === "=" ? 4006 value === check : 4007 type === "*=" ? 4008 value.indexOf(check) >= 0 : 4009 type === "~=" ? 4010 (" " + value + " ").indexOf(check) >= 0 : 4011 !check ? 4012 value && result !== false : 4013 type === "!=" ? 4014 value !== check : 4015 type === "^=" ? 4016 value.indexOf(check) === 0 : 4017 type === "$=" ? 4018 value.substr(value.length - check.length) === check : 4019 type === "|=" ? 4020 value === check || value.substr(0, check.length + 1) === check + "-" : 4021 false; 4022 }, 4023 POS: function(elem, match, i, array){ 4024 var name = match[2], filter = Expr.setFilters[ name ]; 4025 4026 if ( filter ) { 4027 return filter( elem, i, match, array ); 4028 } 4029 } 4030 } 4031 }; 4032 4033 var origPOS = Expr.match.POS; 4034 4035 for ( var type in Expr.match ) { 4036 Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); 4037 Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source ); 4038 } 4039 4040 var makeArray = function(array, results) { 4041 array = Array.prototype.slice.call( array, 0 ); 4042 4043 if ( results ) { 4044 results.push.apply( results, array ); 4045 return results; 4046 } 4047 4048 return array; 4049 }; 4050 4051 // Perform a simple check to determine if the browser is capable of 4052 // converting a NodeList to an array using builtin methods. 4053 try { 4054 Array.prototype.slice.call( document.documentElement.childNodes, 0 ); 4055 4056 // Provide a fallback method if it does not work 4057 } catch(e){ 4058 makeArray = function(array, results) { 4059 var ret = results || []; 4060 4061 if ( toString.call(array) === "[object Array]" ) { 4062 Array.prototype.push.apply( ret, array ); 4063 } else { 4064 if ( typeof array.length === "number" ) { 4065 for ( var i = 0, l = array.length; i < l; i++ ) { 4066 ret.push( array[i] ); 4067 } 4068 } else { 4069 for ( var i = 0; array[i]; i++ ) { 4070 ret.push( array[i] ); 4071 } 4072 } 4073 } 4074 4075 return ret; 4076 }; 4077 } 4078 4079 var sortOrder; 4080 4081 if ( document.documentElement.compareDocumentPosition ) { 4082 sortOrder = function( a, b ) { 4083 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { 4084 if ( a == b ) { 4085 hasDuplicate = true; 4086 } 4087 return a.compareDocumentPosition ? -1 : 1; 4088 } 4089 4090 var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; 4091 if ( ret === 0 ) { 4092 hasDuplicate = true; 4093 } 4094 return ret; 4095 }; 4096 } else if ( "sourceIndex" in document.documentElement ) { 4097 sortOrder = function( a, b ) { 4098 if ( !a.sourceIndex || !b.sourceIndex ) { 4099 if ( a == b ) { 4100 hasDuplicate = true; 4101 } 4102 return a.sourceIndex ? -1 : 1; 4103 } 4104 4105 var ret = a.sourceIndex - b.sourceIndex; 4106 if ( ret === 0 ) { 4107 hasDuplicate = true; 4108 } 4109 return ret; 4110 }; 4111 } else if ( document.createRange ) { 4112 sortOrder = function( a, b ) { 4113 if ( !a.ownerDocument || !b.ownerDocument ) { 4114 if ( a == b ) { 4115 hasDuplicate = true; 4116 } 4117 return a.ownerDocument ? -1 : 1; 4118 } 4119 4120 var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); 4121 aRange.setStart(a, 0); 4122 aRange.setEnd(a, 0); 4123 bRange.setStart(b, 0); 4124 bRange.setEnd(b, 0); 4125 var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); 4126 if ( ret === 0 ) { 4127 hasDuplicate = true; 4128 } 4129 return ret; 4130 }; 4131 } 4132 4133 // Utility function for retreiving the text value of an array of DOM nodes 4134 function getText( elems ) { 4135 var ret = "", elem; 4136 4137 for ( var i = 0; elems[i]; i++ ) { 4138 elem = elems[i]; 4139 4140 // Get the text from text nodes and CDATA nodes 4141 if ( elem.nodeType === 3 || elem.nodeType === 4 ) { 4142 ret += elem.nodeValue; 4143 4144 // Traverse everything else, except comment nodes 4145 } else if ( elem.nodeType !== 8 ) { 4146 ret += getText( elem.childNodes ); 4147 } 4148 } 4149 4150 return ret; 4151 } 4152 4153 // Check to see if the browser returns elements by name when 4154 // querying by getElementById (and provide a workaround) 4155 (function(){ 4156 // We're going to inject a fake input element with a specified name 4157 var form = document.createElement("div"), 4158 id = "script" + (new Date).getTime(); 4159 form.innerHTML = "<a name='" + id + "'/>"; 4160 4161 // Inject it into the root element, check its status, and remove it quickly 4162 var root = document.documentElement; 4163 root.insertBefore( form, root.firstChild ); 4164 4165 // The workaround has to do additional checks after a getElementById 4166 // Which slows things down for other browsers (hence the branching) 4167 if ( document.getElementById( id ) ) { 4168 Expr.find.ID = function(match, context, isXML){ 4169 if ( typeof context.getElementById !== "undefined" && !isXML ) { 4170 var m = context.getElementById(match[1]); 4171 return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; 4172 } 4173 }; 4174 4175 Expr.filter.ID = function(elem, match){ 4176 var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); 4177 return elem.nodeType === 1 && node && node.nodeValue === match; 4178 }; 4179 } 4180 4181 root.removeChild( form ); 4182 root = form = null; // release memory in IE 4183 })(); 4184 4185 (function(){ 4186 // Check to see if the browser returns only elements 4187 // when doing getElementsByTagName("*") 4188 4189 // Create a fake element 4190 var div = document.createElement("div"); 4191 div.appendChild( document.createComment("") ); 4192 4193 // Make sure no comments are found 4194 if ( div.getElementsByTagName("*").length > 0 ) { 4195 Expr.find.TAG = function(match, context){ 4196 var results = context.getElementsByTagName(match[1]); 4197 4198 // Filter out possible comments 4199 if ( match[1] === "*" ) { 4200 var tmp = []; 4201 4202 for ( var i = 0; results[i]; i++ ) { 4203 if ( results[i].nodeType === 1 ) { 4204 tmp.push( results[i] ); 4205 } 4206 } 4207 4208 results = tmp; 4209 } 4210 4211 return results; 4212 }; 4213 } 4214 4215 // Check to see if an attribute returns normalized href attributes 4216 div.innerHTML = "<a href='#'></a>"; 4217 if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && 4218 div.firstChild.getAttribute("href") !== "#" ) { 4219 Expr.attrHandle.href = function(elem){ 4220 return elem.getAttribute("href", 2); 4221 }; 4222 } 4223 4224 div = null; // release memory in IE 4225 })(); 4226 4227 if ( document.querySelectorAll ) { 4228 (function(){ 4229 var oldSizzle = Sizzle, div = document.createElement("div"); 4230 div.innerHTML = "<p class='TEST'></p>"; 4231 4232 // Safari can't handle uppercase or unicode characters when 4233 // in quirks mode. 4234 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { 4235 return; 4236 } 4237 4238 Sizzle = function(query, context, extra, seed){ 4239 context = context || document; 4240 4241 // Only use querySelectorAll on non-XML documents 4242 // (ID selectors don't work in non-HTML documents) 4243 if ( !seed && context.nodeType === 9 && !isXML(context) ) { 4244 try { 4245 return makeArray( context.querySelectorAll(query), extra ); 4246 } catch(e){} 4247 } 4248 4249 return oldSizzle(query, context, extra, seed); 4250 }; 4251 4252 for ( var prop in oldSizzle ) { 4253 Sizzle[ prop ] = oldSizzle[ prop ]; 4254 } 4255 4256 div = null; // release memory in IE 4257 })(); 4258 } 4259 4260 (function(){ 4261 var div = document.createElement("div"); 4262 4263 div.innerHTML = "<div class='test e'></div><div class='test'></div>"; 4264 4265 // Opera can't find a second classname (in 9.6) 4266 // Also, make sure that getElementsByClassName actually exists 4267 if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { 4268 return; 4269 } 4270 4271 // Safari caches class attributes, doesn't catch changes (in 3.2) 4272 div.lastChild.className = "e"; 4273 4274 if ( div.getElementsByClassName("e").length === 1 ) { 4275 return; 4276 } 4277 4278 Expr.order.splice(1, 0, "CLASS"); 4279 Expr.find.CLASS = function(match, context, isXML) { 4280 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { 4281 return context.getElementsByClassName(match[1]); 4282 } 4283 }; 4284 4285 div = null; // release memory in IE 4286 })(); 4287 4288 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { 4289 for ( var i = 0, l = checkSet.length; i < l; i++ ) { 4290 var elem = checkSet[i]; 4291 if ( elem ) { 4292 elem = elem[dir]; 4293 var match = false; 4294 4295 while ( elem ) { 4296 if ( elem.sizcache === doneName ) { 4297 match = checkSet[elem.sizset]; 4298 break; 4299 } 4300 4301 if ( elem.nodeType === 1 && !isXML ){ 4302 elem.sizcache = doneName; 4303 elem.sizset = i; 4304 } 4305 4306 if ( elem.nodeName.toLowerCase() === cur ) { 4307 match = elem; 4308 break; 4309 } 4310 4311 elem = elem[dir]; 4312 } 4313 4314 checkSet[i] = match; 4315 } 4316 } 4317 } 4318 4319 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { 4320 for ( var i = 0, l = checkSet.length; i < l; i++ ) { 4321 var elem = checkSet[i]; 4322 if ( elem ) { 4323 elem = elem[dir]; 4324 var match = false; 4325 4326 while ( elem ) { 4327 if ( elem.sizcache === doneName ) { 4328 match = checkSet[elem.sizset]; 4329 break; 4330 } 4331 4332 if ( elem.nodeType === 1 ) { 4333 if ( !isXML ) { 4334 elem.sizcache = doneName; 4335 elem.sizset = i; 4336 } 4337 if ( typeof cur !== "string" ) { 4338 if ( elem === cur ) { 4339 match = true; 4340 break; 4341 } 4342 4343 } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { 4344 match = elem; 4345 break; 4346 } 4347 } 4348 4349 elem = elem[dir]; 4350 } 4351 4352 checkSet[i] = match; 4353 } 4354 } 4355 } 4356 4357 var contains = document.compareDocumentPosition ? function(a, b){ 4358 return a.compareDocumentPosition(b) & 16; 4359 } : function(a, b){ 4360 return a !== b && (a.contains ? a.contains(b) : true); 4361 }; 4362 4363 var isXML = function(elem){ 4364 // documentElement is verified for cases where it doesn't yet exist 4365 // (such as loading iframes in IE - #4833) 4366 var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; 4367 return documentElement ? documentElement.nodeName !== "HTML" : false; 4368 }; 4369 4370 var posProcess = function(selector, context){ 4371 var tmpSet = [], later = "", match, 4372 root = context.nodeType ? [context] : context; 4373 4374 // Position selectors must be done after the filter 4375 // And so must :not(positional) so we move all PSEUDOs to the end 4376 while ( (match = Expr.match.PSEUDO.exec( selector )) ) { 4377 later += match[0]; 4378 selector = selector.replace( Expr.match.PSEUDO, "" ); 4379 } 4380 4381 selector = Expr.relative[selector] ? selector + "*" : selector; 4382 4383 for ( var i = 0, l = root.length; i < l; i++ ) { 4384 Sizzle( selector, root[i], tmpSet ); 4385 } 4386 4387 return Sizzle.filter( later, tmpSet ); 4388 }; 4389 4390 // Add Sizzle to Glow 4391 // This file is injected into sizzle.js by the ant "deps" target 4392 Glow.provide(function(glow) { 4393 glow._sizzle = Sizzle; 4394 }); 4395 4396 return; 4397 4398 4399 window.Sizzle = Sizzle; 4400 4401 })(); 4402 Glow.provide(function(glow) { 4403 var NodeListProto = glow.NodeList.prototype 4404 /* 4405 PrivateVar: ucheck 4406 Used by unique(), increased by 1 on each use 4407 */ 4408 , ucheck = 1 4409 /* 4410 PrivateVar: ucheckPropName 4411 This is the property name used by unique checks 4412 */ 4413 4414 , ucheckPropName = "_unique" + glow.UID; 4415 /* 4416 PrivateMethod: unique 4417 Get an array of nodes without duplicate nodes from an array of nodes. 4418 4419 Arguments: 4420 aNodes - (Array|<NodeList>) 4421 4422 Returns: 4423 An array of nodes without duplicates. 4424 */ 4425 //worth checking if it's an XML document? 4426 if (glow.env.ie) { 4427 var unique = function(aNodes) { 4428 if (aNodes.length == 1) { return aNodes; } 4429 4430 //remove duplicates 4431 var r = [], 4432 ri = 0, 4433 i = 0; 4434 4435 for (; aNodes[i]; i++) { 4436 if (aNodes[i].getAttribute(ucheckPropName) != ucheck && aNodes[i].nodeType == 1) { 4437 r[ri++] = aNodes[i]; 4438 } 4439 aNodes[i].setAttribute(ucheckPropName, ucheck); 4440 } 4441 for (i=0; aNodes[i]; i++) { 4442 aNodes[i].removeAttribute(ucheckPropName); 4443 } 4444 ucheck++; 4445 return r; 4446 } 4447 } else { 4448 var unique = function(aNodes) { 4449 if (aNodes.length == 1) { return aNodes; } 4450 4451 //remove duplicates 4452 var r = [], 4453 ri = 0, 4454 i = 0; 4455 4456 for (; aNodes[i]; i++) { 4457 if (aNodes[i][ucheckPropName] != ucheck && aNodes[i].nodeType == 1) { 4458 r[ri++] = aNodes[i]; 4459 } 4460 aNodes[i][ucheckPropName] = ucheck; 4461 } 4462 ucheck++; 4463 return r; 4464 } 4465 }; 4466 /** 4467 @name glow.NodeList#parent 4468 @function 4469 @description Gets the unique parent nodes of each node as a new NodeList. 4470 @param {string | HTMLElement | NodeList} [search] Search value 4471 If provided, will seek the next parent element until a match is found 4472 @returns {glow.NodeList} 4473 4474 Returns a new NodeList containing the parent nodes, with 4475 duplicates removed 4476 4477 @example 4478 // elements which contain links 4479 var parents = glow.dom.get("a").parent(); 4480 */ 4481 NodeListProto.parent = function(search) { 4482 var ret = [], 4483 ri = 0, 4484 i = this.length, 4485 node; 4486 4487 while (i--) { 4488 node = this[i]; 4489 if (node.nodeType == 1) { 4490 if(search){ 4491 while(node = node.parentNode){ 4492 if (glow._sizzle.filter(search, [node]).length) { 4493 ret[ri++] = node; 4494 break; 4495 } 4496 } 4497 } 4498 4499 else if(node = node.parentNode){ 4500 ret[ri++] = node; 4501 } 4502 4503 } 4504 4505 } 4506 4507 return new glow.NodeList(unique(ret)); 4508 }; 4509 4510 /* Private method for prev() and next() */ 4511 function getNextOrPrev(nodelist, dir, search) { 4512 var ret = [], 4513 ri = 0, 4514 node, 4515 i = 0, 4516 length = nodelist.length; 4517 4518 while (i < length) { 4519 node = nodelist[i]; 4520 if(search){ 4521 while (node = node[dir + 'Sibling']) { 4522 if (node.nodeType == 1 && node.nodeName != '!') { 4523 if (glow._sizzle.filter(search, [node]).length) { 4524 ret[ri++] = node; 4525 break; 4526 } 4527 } 4528 } 4529 } 4530 else{ 4531 while (node = node[dir + 'Sibling']) { 4532 if (node.nodeType == 1 && node.nodeName != '!') { 4533 ret[ri++] = node; 4534 break; 4535 } 4536 } 4537 } 4538 i++; 4539 } 4540 return new glow.NodeList(ret); 4541 } 4542 4543 /** 4544 @name glow.NodeList#prev 4545 @function 4546 @description Gets the previous sibling element for each node in the ElementList. 4547 If a filter is provided, the previous item that matches the filter is returned, or 4548 none if no match is found. 4549 @param {string | HTMLElement | NodeList} [search] Search value 4550 If provided, will seek the previous sibling element until a match is found 4551 @returns {glow.ElementList} 4552 A new ElementList containing the previous sibling elements that match the (optional) 4553 filter. 4554 @example 4555 // gets the element before #myLink (if there is one) 4556 var next = glow.get("#myLink").prev(); 4557 @example 4558 // get the previous sibling link element before #skipLink 4559 glow.get('#skipLink').prev('a') 4560 */ 4561 NodeListProto.prev = function(search) { 4562 return getNextOrPrev(this, 'previous', search); 4563 }; 4564 4565 /** 4566 @name glow.NodeList#next 4567 @function 4568 @description Gets the next sibling element for each node in the ElementList. 4569 If a filter is provided, the next item that matches the filter is returned, or 4570 none if no match is found. 4571 @param {string | HTMLElement | NodeList} [search] Search value 4572 If provided, will seek the next sibling element until a match is found 4573 @returns {glow.ElementList} 4574 A new ElementList containing the next sibling elements that match the (optional) 4575 filter. 4576 @example 4577 // gets the element following #myLink (if there is one) 4578 var next = glow.get("#myLink").next(); 4579 @example 4580 // get the next sibling link element after #skipLink 4581 glow.get('#skipLink').next('a') 4582 */ 4583 NodeListProto.next = function(search) { 4584 return getNextOrPrev(this, 'next', search); 4585 }; 4586 4587 4588 /** 4589 @name glow.NodeList#get 4590 @function 4591 @description Gets decendents of nodes that match a CSS selector. 4592 4593 @param {String} selector CSS selector 4594 4595 @returns {glow.NodeList} 4596 Returns a new NodeList containing matched elements 4597 4598 @example 4599 // create a new NodeList 4600 var myNodeList = glow.dom.create("<div><a href='s.html'>Link</a></div>"); 4601 4602 // get 'a' tags that are decendants of the NodeList nodes 4603 myNewNodeList = myNodeList.get("a"); 4604 */ 4605 NodeListProto.get = function(selector) { 4606 var ret = [], 4607 i = this.length; 4608 4609 while (i--) { 4610 glow._sizzle(selector, this[i], ret); 4611 4612 } 4613 // need to remove uniqueSorts because they're slow. Replace with own method for unique. 4614 return new glow.NodeList(unique(ret)); 4615 }; 4616 4617 4618 4619 /** 4620 @name glow.NodeList#ancestors 4621 @function 4622 @description Gets the unique ancestor nodes of each node as a new NodeList. 4623 @param {Function|string} [filter] Filter test 4624 If a string is provided, it is used in a call to {@link glow.ElementList#is ElementList#is}. 4625 If a function is provided it will be passed 2 arguments, the index of the current item, 4626 and the ElementList being itterated over. 4627 Inside the function 'this' refers to the HTMLElement. 4628 Return true to keep the node, or false to remove it. 4629 @returns {glow.dom.NodeList} 4630 Returns NodeList 4631 4632 @example 4633 // get ancestor elements for anchor elements 4634 var ancestors = glow.dom.get("a").ancestors(); 4635 */ 4636 NodeListProto.ancestors = function(filter) { 4637 var ret = [], 4638 ri = 0, 4639 i = 0, 4640 length = this.length, 4641 node; 4642 4643 while (i < length) { 4644 node = this[i].parentNode; 4645 4646 while (node && node.nodeType == 1) { 4647 ret[ri++] = node; 4648 node = node.parentNode; 4649 } 4650 i++; 4651 } 4652 if(filter){ 4653 ret = new glow.NodeList(ret); 4654 ret = ret.filter(filter); 4655 } 4656 return new glow.NodeList(unique(ret)); 4657 }; 4658 4659 /* 4660 Private method to get the child elements for an html node (used by children()) 4661 */ 4662 function getChildElms(node) { 4663 var r = [], 4664 childNodes = node.childNodes, 4665 i = 0, 4666 ri = 0; 4667 4668 for (; childNodes[i]; i++) { 4669 if (childNodes[i].nodeType == 1 && childNodes[i].nodeName != '!') { 4670 r[ri++] = childNodes[i]; 4671 } 4672 } 4673 return r; 4674 } 4675 4676 /** 4677 @name glow.NodeList#children 4678 @function 4679 @description Gets the child elements of each node as a new NodeList. 4680 4681 @returns {glow.dom.NodeList} 4682 4683 Returns a new NodeList containing all the child nodes 4684 4685 @example 4686 // get all list items 4687 var items = glow.dom.get("ul, ol").children(); 4688 */ 4689 NodeListProto.children = function() { 4690 var ret = [], 4691 i = this.length; 4692 4693 while(i--) { 4694 ret = ret.concat( getChildElms(this[i]) ); 4695 } 4696 return new glow.NodeList(ret); 4697 }; 4698 4699 /** 4700 @name glow.NodeList#contains 4701 @function 4702 @description Find if this NodeList contains the given element 4703 4704 @param {string | HTMLELement | NodeList} Single element to check for 4705 4706 @returns {boolean} 4707 myElementList.contains(elm) 4708 // Returns true if an element in myElementList contains elm, or IS elm. 4709 */ 4710 NodeListProto.contains = function(elm) { 4711 var i = 0, 4712 node = new glow.NodeList(elm)[0], 4713 length = this.length, 4714 newNodes, 4715 toTest; 4716 4717 // missing some nodes? Return false 4718 if ( !node || !this.length ) { 4719 return false; 4720 } 4721 4722 if (this[0].compareDocumentPosition) { //w3 method 4723 while (i < length) { 4724 //break out if the two are teh same 4725 if(this[i] == node){ 4726 break; 4727 } 4728 //check against bitwise to see if node is contained in this 4729 else if (!(this[i].compareDocumentPosition(node) & 16)) { 4730 return false; 4731 } 4732 i++; 4733 } 4734 } 4735 else if(node.contains){ 4736 for (; i < length; i++) { 4737 if ( !( this[i].contains( node ) ) ) { 4738 return false; 4739 } 4740 } 4741 } 4742 else { //manual method for last chance corale 4743 while (i < length) { 4744 toTest = node; 4745 while (toTest = toTest.parentNode) { 4746 if (this[i] == toTest) { break; } 4747 } 4748 if (!toTest) { 4749 return false; 4750 } 4751 i++; 4752 } 4753 } 4754 4755 return true; 4756 }; 4757 }); 4758 Glow.provide(function(glow) { 4759 var NodeListProto = glow.NodeList.prototype, 4760 document = window.document, 4761 undefined; 4762 4763 // create a fragment and insert a set of nodes into it 4764 function createFragment(nodes) { 4765 var fragment = document.createDocumentFragment(), 4766 i = 0, 4767 node; 4768 4769 while ( node = nodes[i++] ) { 4770 fragment.appendChild(node); 4771 } 4772 4773 return fragment; 4774 } 4775 4776 // generate the #before and #after methods 4777 // after: 1 for #(insert)after, 0 for #(insert)before 4778 // insert: 1 for #insert(After|Before), 0 for #(after|before) 4779 function afterAndBefore(after, insert) { 4780 return function(elements) { 4781 var toAddList, 4782 toAddToList, 4783 fragmentToAdd, 4784 nextFragmentToAdd, 4785 item, 4786 itemParent; 4787 4788 if (!this.length) { return this; } 4789 4790 // normalise 'elements' 4791 // if we're dealing with append/prepend then strings are always treated as HTML strings 4792 if (!insert && typeof elements === 'string') { 4793 elements = new glow.NodeList( glow.NodeList._strToNodes(elements) ); 4794 } 4795 else { 4796 elements = new glow.NodeList(elements); 4797 } 4798 4799 // set the element we're going to add to, and the elements we're going to add 4800 if (insert) { 4801 toAddToList = elements; 4802 toAddList = new glow.NodeList(this); 4803 } 4804 else { 4805 toAddToList = this; 4806 toAddList = elements; 4807 } 4808 4809 nextFragmentToAdd = createFragment(toAddList); 4810 4811 for (var i = 0, leni = toAddToList.length, lasti = leni - 1; i < leni; i++) { 4812 item = toAddToList[i]; 4813 fragmentToAdd = nextFragmentToAdd; 4814 4815 // we can only append after if the element has a parent right? 4816 if (itemParent = item.parentNode) { 4817 if (i !== lasti) { // if not the last item 4818 nextFragmentToAdd = fragmentToAdd.cloneNode(true); 4819 insert && toAddList.push(nextFragmentToAdd.childNodes); 4820 } 4821 itemParent.insertBefore(fragmentToAdd, after ? item.nextSibling : item); 4822 } 4823 } 4824 4825 return insert ? toAddList : toAddToList; 4826 } 4827 } 4828 4829 // generate the #append, #appendTo, #prepend and #prependTo methods 4830 // append: 1 for #append(To), 0 for #prepend(To) 4831 // to: 1 for #(append|prepend)To, 0 for #(append|prepend) 4832 function appendAndPrepend(append, to) { 4833 return function(elements) { 4834 var toAddList, 4835 toAddToList, 4836 fragmentToAdd, 4837 nextFragmentToAdd, 4838 item; 4839 4840 if (!this.length) { return this; } 4841 4842 // normalise 'elements' 4843 // if we're dealing with append/prepend then strings are always treated as HTML strings 4844 if (!to && typeof elements === 'string') { 4845 elements = new glow.NodeList( glow.NodeList._strToNodes(elements) ); 4846 } 4847 else { 4848 elements = new glow.NodeList(elements); 4849 } 4850 4851 // set the element we're going to add to, and the elements we're going to add 4852 if (to) { 4853 toAddToList = elements; 4854 toAddList = new glow.NodeList(this); 4855 } 4856 else { 4857 toAddToList = this; 4858 toAddList = elements; 4859 } 4860 4861 nextFragmentToAdd = createFragment(toAddList); 4862 4863 for (var i = 0, leni = toAddToList.length, lasti = leni - 1; i < leni; i++) { 4864 item = toAddToList[i]; 4865 fragmentToAdd = nextFragmentToAdd; 4866 4867 // avoid trying to append to non-elements 4868 if (item.nodeType === 1) { 4869 if (i !== lasti) { // if not the last item 4870 nextFragmentToAdd = fragmentToAdd.cloneNode(true); 4871 // add the clones to the return element for appendTo / prependTo 4872 to && toAddList.push(nextFragmentToAdd.childNodes); 4873 } 4874 item.insertBefore(fragmentToAdd, append ? null : item.firstChild); 4875 } 4876 } 4877 4878 return to ? toAddList : toAddToList; 4879 } 4880 } 4881 4882 /** 4883 @name glow.NodeList#after 4884 @function 4885 @description Insert node(s) after each node in this NodeList. 4886 If there is more than one node in this NodeList, 'nodes' 4887 will be inserted after the first element and clones will be 4888 inserted after each subsequent element. 4889 4890 @param {string | HTMLElement | HTMLElement[] | glow.NodeList} nodes Node(s) to insert 4891 Strings will be treated as HTML strings. 4892 4893 @returns {glow.NodeList} Original NodeList 4894 4895 @example 4896 // adds a paragraph after each heading 4897 glow('h1, h2, h3').after('<p>That was a nice heading.</p>'); 4898 */ 4899 NodeListProto.after = afterAndBefore(1); 4900 4901 /** 4902 @name glow.NodeList#before 4903 @function 4904 @description Insert node(s) before each node in this NodeList. 4905 If there is more than one node in this NodeList, 'nodes' 4906 will be inserted before the first element and clones will be 4907 inserted before each subsequent element. 4908 4909 @param {string | HTMLElement | HTMLElement[] | glow.NodeList} nodes Node(s) to insert 4910 Strings will be treated as HTML strings. 4911 4912 @returns {glow.NodeList} Original NodeList 4913 4914 @example 4915 // adds a div before each paragraph 4916 glow('p').before('<div>Here comes a paragraph!</div>'); 4917 */ 4918 NodeListProto.before = afterAndBefore(0); 4919 4920 /** 4921 @name glow.NodeList#append 4922 @function 4923 @description Appends node to each node in this NodeList. 4924 If there is more than one node in this NodeList, then the given nodes 4925 are appended to the first node and clones are appended to the other 4926 nodes. 4927 4928 @param {string | HTMLElement | HTMLElement[] | glow.NodeList} nodes Nodes(s) to append 4929 Strings will be treated as HTML strings. 4930 4931 @returns {glow.NodeList} Original NodeList 4932 4933 @example 4934 // ends every paragraph with '...' 4935 glow('p').append('<span>...</span>'); 4936 */ 4937 NodeListProto.append = appendAndPrepend(1); 4938 4939 /** 4940 @name glow.NodeList#prepend 4941 @function 4942 @description Prepends nodes to each node in this NodeList. 4943 If there is more than one node in this NodeList, then the given nodes 4944 are prepended to the first node and clones are prepended to the other 4945 nodes. 4946 4947 @param {string | HTMLElement | HTMLElement[] | glow.NodeList} nodes Nodes(s) to prepend 4948 Strings will be treated as HTML strings. 4949 4950 @returns {glow.NodeList} Original NodeList 4951 4952 @example 4953 // prepends every paragraph with 'Paragraph: ' 4954 glow('p').prepend('<span>Paragraph: </span>'); 4955 */ 4956 NodeListProto.prepend = appendAndPrepend(0); 4957 4958 /** 4959 @name glow.NodeList#appendTo 4960 @function 4961 @description Appends nodes in this NodeList to given node(s) 4962 If appending to more than one node, the NodeList is appended 4963 to the first node and clones are appended to the others. 4964 4965 @param {string | HTMLElement | HTMLElement[] | glow.NodeList} node Node(s) to append to. 4966 Strings will be treated as CSS selectors or HTML strings. 4967 4968 @returns {glow.NodeList} The appended nodes. 4969 4970 @example 4971 // appends '...' to every paragraph 4972 glow('<span>...</span>').appendTo('p'); 4973 */ 4974 NodeListProto.appendTo = appendAndPrepend(1, 1); 4975 4976 /** 4977 @name glow.NodeList#prependTo 4978 @function 4979 @description Prepends nodes in this NodeList to given node(s) 4980 If prepending to more than one node, the NodeList is prepended 4981 to the first node and clones are prepended to the others. 4982 4983 @param {string | HTMLElement | HTMLElement[] | glow.NodeList} node Node(s) to prepend to 4984 Strings will be treated as CSS selectors or HTML strings. 4985 4986 @returns {glow.NodeList} The prepended nodes. 4987 4988 @example 4989 // prepends 'Paragraph: ' to every paragraph 4990 glow('<span>Paragraph: </span>').prependTo('p'); 4991 */ 4992 NodeListProto.prependTo = appendAndPrepend(0, 1); 4993 4994 /** 4995 @name glow.NodeList#insertAfter 4996 @function 4997 @description Insert this NodeList after the given nodes 4998 If inserting after more than one node, the NodeList is inserted 4999 after the first node and clones are inserted after the others. 5000 5001 @param {string | HTMLElement | HTMLElement[] | glow.NodeList} nodes Node(s) to insert after 5002 Strings will be treated as CSS selectors. 5003 5004 @returns {glow.NodeList} Inserted nodes. 5005 5006 @example 5007 // adds a paragraph after each heading 5008 glow('<p>HAI!</p>').insertAfter('h1, h2, h3'); 5009 */ 5010 NodeListProto.insertAfter = afterAndBefore(1, 1); 5011 5012 /** 5013 @name glow.NodeList#insertBefore 5014 @function 5015 @description Insert this NodeList before the given nodes 5016 If inserting before more than one node, the NodeList is inserted 5017 before the first node and clones are inserted before the others. 5018 5019 @param {string | HTMLElement | HTMLElement[] | glow.NodeList} nodes Node(s) to insert before 5020 Strings will be treated as CSS selectors. 5021 5022 @returns {glow.NodeList} Inserted nodes. 5023 5024 @example 5025 // adds a div before each paragraph 5026 glow('<div>Here comes a paragraph!</div>').insertBefore('p'); 5027 */ 5028 NodeListProto.insertBefore = afterAndBefore(0, 1); 5029 5030 /** 5031 @name glow.NodeList#destroy 5032 @function 5033 @description Removes each element from the document 5034 The element, attached listeners & attached data will be 5035 destroyed to free up memory. 5036 5037 Detroyed elements may not be reused in some browsers. 5038 5039 @returns {glow.NodeList} An empty NodeList 5040 5041 @example 5042 // destroy all links in the document 5043 glow("a").destroy(); 5044 */ 5045 var tmpDiv = document.createElement('div'); 5046 5047 NodeListProto.destroy = function() { 5048 glow.NodeList._destroyData(this); 5049 this.appendTo(tmpDiv); 5050 tmpDiv.innerHTML = ''; 5051 return new glow.NodeList(); 5052 }; 5053 5054 /** 5055 @name glow.NodeList#remove 5056 @function 5057 @description Removes each element from the document 5058 If you no longer need the elements, consider using 5059 {@link glow.NodeList#destroy destroy} 5060 5061 @returns {glow.NodeList} The removed elements 5062 5063 @example 5064 // take all the links out of a document 5065 glow("a").remove(); 5066 */ 5067 NodeListProto.remove = function() { 5068 var parent, 5069 node, 5070 i = this.length; 5071 5072 while (i--) { 5073 node = this[i]; 5074 if (parent = node.parentNode) { 5075 parent.removeChild(node); 5076 } 5077 } 5078 5079 return this; 5080 }; 5081 5082 /** 5083 @name glow.NodeList#empty 5084 @function 5085 @description Removes the nodes' contents 5086 5087 @returns {glow.NodeList} Original nodes 5088 5089 @example 5090 // remove the contents of all textareas 5091 glow("textarea").empty(); 5092 */ 5093 // TODO: is this shortcut worth doing? 5094 NodeListProto.empty = glow.env.ie ? 5095 // When you clean an element out using innerHTML it destroys its inner text nodes in IE8 and below 5096 // Here's an alternative method for IE: 5097 function() { 5098 var i = this.length, node, child; 5099 5100 while (i--) { 5101 node = this[i]; 5102 while (child = node.firstChild) { 5103 node.removeChild(child); 5104 } 5105 } 5106 5107 return this; 5108 } : 5109 // method for most browsers 5110 function() { 5111 var i = this.length; 5112 5113 while (i--) { 5114 this[i].innerHTML = ''; 5115 } 5116 5117 return this; 5118 } 5119 5120 /** 5121 @name glow.NodeList#replaceWith 5122 @function 5123 @description Replace elements with another 5124 5125 @param {string | HTMLElement | HTMLElement[] | glow.NodeList} elements Element(s) to insert into the document 5126 If there is more than one element in the NodeList, then the given elements 5127 replace the first element, clones are appended to the other elements. 5128 5129 @returns {glow.NodeList} The replaced elements 5130 Call {@link glow.NodeList#destroy destroy} on these if you 5131 no longer need them. 5132 */ 5133 NodeListProto.replaceWith = function(elements) { 5134 return this.after(elements).remove(); 5135 }; 5136 5137 /** 5138 @name glow.NodeList#wrap 5139 @function 5140 @description Wraps the given NodeList with the specified element(s). 5141 The given NodeList items will always be placed in the first 5142 child element that contains no further elements. 5143 5144 Each item in a given NodeList will be wrapped individually. 5145 5146 @param {string | HTMLElement | HTMLElement[] | glow.NodeList} wrapper Element to use as a wrapper 5147 Strings will be treated as HTML strings if they begin with <, else 5148 they'll be treated as a CSS selector. 5149 5150 @returns {glow.NodeList} The NodeList with new wrapper parents 5151 5152 @example 5153 // <span id="mySpan">Hello</span> 5154 glow("#mySpan").wrap("<div><p></p></div>"); 5155 // Makes: 5156 // <div> 5157 // <p> 5158 // <span id="mySpan">Hello</span> 5159 // </p> 5160 // </div> 5161 5162 */ 5163 // get first child element node of an element, otherwise undefined 5164 function getFirstChildElm(parent) { 5165 for (var child = parent.firstChild; child; child = child.nextSibling) { 5166 if (child.nodeType == 1) { 5167 return child; 5168 } 5169 } 5170 return undefined; 5171 } 5172 5173 NodeListProto.wrap = function(wrapper) { 5174 // normalise input 5175 wrapper = new glow.NodeList(wrapper); 5176 5177 // escape if the wraper is non-existant or not an element 5178 if (!wrapper[0] || wrapper[0].nodeType != 1) { 5179 return this; 5180 } 5181 5182 var toWrap, 5183 toWrapTarget, 5184 firstChildElm; 5185 5186 for (var i = 0, leni = this.length; i<leni; i++) { 5187 toWrap = this[i]; 5188 // get target element to insert toWrap in 5189 toWrapTarget = wrapper[0]; 5190 5191 while (toWrapTarget) { 5192 firstChildElm = getFirstChildElm(toWrapTarget); 5193 5194 if (!firstChildElm) { 5195 break; 5196 } 5197 toWrapTarget = firstChildElm; 5198 } 5199 5200 if (toWrap.parentNode) { 5201 wrapper.insertBefore(toWrap); 5202 } 5203 5204 // If wrapping multiple nodes, we need to take a clean copy of the wrapping nodes 5205 if (i != leni-1) { 5206 wrapper = wrapper.clone(); 5207 } 5208 5209 toWrapTarget.appendChild(toWrap); 5210 } 5211 5212 return this; 5213 }; 5214 5215 /** 5216 @name glow.NodeList#unwrap 5217 @function 5218 @description Removes the parent of each item in the list 5219 5220 @returns {glow.NodeList} The now unwrapped elements 5221 5222 @example 5223 // Before: <div><p><span id="mySpan">Hello</span></p></div> 5224 // unwrap the given element 5225 glow("#mySpan").unwrap(); 5226 // After: <div><span id="mySpan">Hello</span></div> 5227 */ 5228 NodeListProto.unwrap = function() { 5229 var parentToRemove, 5230 childNodes, 5231 // get unique parents 5232 parentsToRemove = this.parent(); 5233 5234 for (var i = 0, leni = parentsToRemove.length; i < leni; i++) { 5235 parentToRemove = parentsToRemove.slice(i, i+1); 5236 // make sure we get all children, including text nodes 5237 childNodes = new glow.NodeList( parentToRemove[0].childNodes ); 5238 5239 // if the item we're removing has no new parent (i.e. is not in document), then we just remove the child and destroy the old parent 5240 if (!parentToRemove[0].parentNode){ 5241 childNodes.remove(); 5242 parentToRemove.destroy(); 5243 } 5244 else { 5245 childNodes.insertBefore(parentToRemove); 5246 parentToRemove.destroy(); 5247 } 5248 } 5249 return this; 5250 }; 5251 5252 /** 5253 @name glow.NodeList#clone 5254 @function 5255 @description Clones each node in the NodeList, along with data & event listeners 5256 5257 @returns {glow.NodeList} 5258 Returns a new NodeList containing clones of all the nodes in 5259 the NodeList 5260 5261 @example 5262 // get a copy of all heading elements 5263 var myClones = glow.get("h1, h2, h3, h4, h5, h6").clone(); 5264 */ 5265 NodeListProto.clone = function() { 5266 var nodes = [], 5267 eventIdProp = '__eventId' + glow.UID, 5268 i = this.length; 5269 5270 5271 while (i--) { 5272 nodes[i] = this[i].cloneNode(true); 5273 // some browsers (ie) also clone node properties as attributes 5274 // we need to get rid of the eventId. 5275 allCloneElms = new glow.NodeList( nodes ).get("*").push( nodes ); 5276 j = allCloneElms.length; 5277 while(j--) { 5278 nodes[i][eventIdProp] = null; 5279 5280 // now copy over the data and events 5281 glow.events._copyEvent(this[i], nodes[i]); 5282 glow.NodeList._copyData(this[i], nodes[i]); 5283 } 5284 5285 } 5286 // some browsers (ie) also clone node properties as attributes 5287 // we need to get rid of the eventId. 5288 //allCloneElms = new glow.NodeList( nodes ).get("*").push( nodes ); 5289 //j = allCloneElms.length; 5290 5291 //while(j--) { 5292 //allCloneElms[j][eventIdProp] = null; 5293 //} 5294 5295 5296 5297 // copy data from base elements to clone elements 5298 /*allBaseElms = this.get("*").push( this ); 5299 i = allCloneElms.length; 5300 while (i--) { 5301 allCloneElms[i].removeAttribute(dataPropName); 5302 glow.dom.get(allCloneElms[i]).data( 5303 glow.dom.get(allBaseElms[i]).data() 5304 ); 5305 }*/ 5306 5307 return new glow.NodeList(nodes); 5308 }; 5309 5310 5311 /** 5312 @name glow.NodeList#copy 5313 @function 5314 @description Copies each node in the NodeList, excluding data & event listeners 5315 5316 @returns {glow.NodeList} 5317 Returns a new NodeList containing copies of all the nodes in 5318 the NodeList 5319 5320 @example 5321 // get a copy of all heading elements 5322 var myCopies = glow.get("h1, h2, h3, h4, h5, h6").copy(); 5323 */ 5324 NodeListProto.copy = function() { 5325 var nodes = [], 5326 i = this.length; 5327 5328 while (i--) { 5329 nodes[i] = this[i].cloneNode(true); 5330 } 5331 5332 return new glow.NodeList(nodes); 5333 }; 5334 5335 /** 5336 @name glow.NodeList#html 5337 @function 5338 @description Gets / sets HTML content 5339 Either gets content of the first element, or sets the content 5340 for all elements in the list 5341 5342 @param {String} [htmlString] String to set as the HTML of elements 5343 If omitted, the html for the first element in the list is 5344 returned. 5345 5346 @returns {glow.NodeList | string} 5347 Returns the original NodeList when setting, 5348 or the HTML content when getting. 5349 5350 @example 5351 // get the html in #footer 5352 var footerContents = glow("#footer").html(); 5353 5354 @example 5355 // set a new footer 5356 glow("#footer").html("<strong>Hello World!</strong>"); 5357 */ 5358 NodeListProto.html = function(htmlString) { 5359 // getting 5360 if (!arguments.length) { 5361 return this[0] ? this[0].innerHTML : ''; 5362 } 5363 5364 // setting 5365 var i = this.length, 5366 node; 5367 5368 // normalise the string 5369 htmlString = htmlString ? String(htmlString): ''; 5370 5371 while (i--) { 5372 node = this[i]; 5373 if (node.nodeType == 1) { 5374 try { 5375 // this has a habit of failing in IE for some elements 5376 node.innerHTML = htmlString; 5377 } 5378 catch (e) { 5379 new glow.NodeList(node).empty().append(htmlString); 5380 } 5381 } 5382 } 5383 5384 return this; 5385 }; 5386 5387 /** 5388 @name glow.NodeList#text 5389 @function 5390 @description Gets / set the text content 5391 Either gets content of the first element, or sets the content 5392 for all elements in the list 5393 5394 @param {String} [text] String to set as the text of elements 5395 If omitted, the test for the first element in the list is 5396 returned. 5397 5398 @returns {glow.NodeList | String} 5399 Returns the original NodeList when setting, 5400 or the text content when getting. 5401 5402 @example 5403 // set text 5404 var div = glow("<div></div>").text("Fun & games!"); 5405 // <div>Func & games!</div> 5406 5407 @example 5408 // get text 5409 var mainHeading = glow('#mainHeading').text(); 5410 */ 5411 NodeListProto.text = function(textString) { 5412 var firstNode = this[0], 5413 i = this.length, 5414 node; 5415 5416 // getting 5417 if (!arguments.length) { 5418 // get the text by checking a load of properties in priority order 5419 return firstNode ? 5420 firstNode.textContent || 5421 firstNode.innerText || 5422 firstNode.nodeValue || '' // nodeValue for comment & text nodes 5423 : ''; 5424 } 5425 5426 // setting 5427 // normalise the string 5428 textString = textString ? String(textString): ''; 5429 5430 this.empty(); 5431 while (i--) { 5432 node = this[i]; 5433 if (node.nodeType == 1) { 5434 node.appendChild( document.createTextNode(textString) ); 5435 } 5436 else { 5437 node.nodeValue = textString; 5438 } 5439 } 5440 5441 return this; 5442 }; 5443 }); 5444 Glow.provide(function(glow) { 5445 var NodeListProto = glow.NodeList.prototype, 5446 doc = document, 5447 win = window; 5448 5449 /********************************PRIVATE METHODS*****************************************/ 5450 5451 /* 5452 PrivateMethod: toStyleProp 5453 Converts a css property name into its javascript name, such as "background-color" to "backgroundColor". 5454 5455 Arguments: prop - (String) CSS Property name 5456 5457 Returns: String, javascript style property name 5458 */ 5459 5460 function toStyleProp(prop) { 5461 if (prop == 'float') { 5462 return glow.env.ie ? 'styleFloat' : 'cssFloat'; 5463 } 5464 return prop.replace(/-(\w)/g, function(match, p1) { 5465 return p1.toUpperCase(); 5466 }); 5467 } 5468 /* 5469 PrivateMethod: getCssValue 5470 Get a computed css property 5471 5472 Arguments: 5473 elm - element 5474 prop - css property or array of properties to add together 5475 5476 Returns: String, value 5477 */ 5478 function getCssValue(elm, prop) { 5479 var r, //return value 5480 total = 0, 5481 i = 0, 5482 /*regex for detecting which css properties need to be calculated relative to the y axis*/ 5483 usesYAxis = /height|top/, 5484 propLen = prop.length, 5485 cssPropRegex = /^(?:(width|height)|(border-(top|bottom|left|right)-width))$/, 5486 compStyle = doc.defaultView && (doc.defaultView.getComputedStyle(elm, null) || doc.defaultView.getComputedStyle), 5487 elmCurrentStyle = elm.currentStyle, 5488 oldDisplay, 5489 match, 5490 propTest = prop.push || cssPropRegex.exec(prop) || []; 5491 5492 5493 if (prop.push) { //multiple properties, add them up 5494 for (; i < propLen; i++) { 5495 total += parseInt( getCssValue(elm, prop[i]), 10 ) || 0; 5496 } 5497 return total + 'px'; 5498 } 5499 5500 if (propTest[1]) { // is width / height 5501 if (!isVisible(elm)) { //element may be display: none 5502 return tempBlock(elm, function() { 5503 return getElmDimension(elm, propTest[1]) + 'px'; 5504 }); 5505 } 5506 return getElmDimension(elm, propTest[1]) + 'px'; 5507 } 5508 else if (propTest[2] //is border-*-width 5509 && glow.env.ie 5510 && getCssValue(elm, 'border-' + propTest[3] + '-style') == 'none' 5511 ) { 5512 return '0'; 5513 } 5514 else if (compStyle) { //W3 Method 5515 //this returns computed values 5516 if (typeof compStyle == 'function') { 5517 //safari returns null for compStyle when element is display:non 5518 oldDisplay = elm.style.display; 5519 r = tempBlock(elm, function() { 5520 if (prop == 'display') { //get true value for display, since we've just fudged it 5521 elm.style.display = oldDisplay; 5522 if (!doc.defaultView.getComputedStyle(elm, null)) { 5523 return 'none'; 5524 } 5525 elm.style.display = 'block'; 5526 } 5527 return getCssValue(elm, prop); 5528 }); 5529 } else { 5530 // assume equal horizontal margins in safari 3 5531 // http://bugs.webkit.org/show_bug.cgi?id=13343 5532 // The above bug doesn't appear to be closed, but it works fine in Safari 4 5533 if (glow.env.webkit > 500 && glow.env.webkit < 526 && prop == 'margin-right' && compStyle.getPropertyValue('position') != 'absolute') { 5534 prop = 'margin-left'; 5535 } 5536 r = compStyle.getPropertyValue(prop); 5537 } 5538 } else if (elmCurrentStyle) { //IE method 5539 if (prop == 'opacity') { 5540 match = /alpha\(opacity=([^\)]+)\)/.exec(elmCurrentStyle.filter); 5541 return match ? String(parseInt(match[1], 10) / 100) : '1'; 5542 } 5543 //this returns cascaded values so needs fixing 5544 r = String(elmCurrentStyle[toStyleProp(prop)]); 5545 if (/^-?[\d\.]+(?!px)[%a-z]+$/i.test(r) && prop != 'font-size') { 5546 r = getPixelValue(elm, r, usesYAxis.test(prop)) + 'px'; 5547 } 5548 } 5549 //some results need post processing 5550 if (prop.indexOf('color') != -1) { //deal with colour values 5551 r = normaliseCssColor(r).toString(); 5552 } else if (r.indexOf('url') == 0) { //some browsers put quotes around the url, get rid 5553 r = r.replace(/\"/g,''); 5554 } 5555 return r; 5556 } 5557 /* 5558 PrivateMethod: isVisible 5559 Is the element visible? 5560 */ 5561 function isVisible(elm) { 5562 //this is a bit of a guess, if there's a better way to do this I'm interested! 5563 return elm.offsetWidth || 5564 elm.offsetHeight; 5565 } 5566 /* 5567 PrivateMethod: normaliseCssColor 5568 Converts a CSS colour into "rgb(255, 255, 255)" or "transparent" format 5569 */ 5570 5571 function normaliseCssColor(val) { 5572 if (/^(transparent|rgba\(0, ?0, ?0, ?0\))$/.test(val)) { return 'transparent'; } 5573 var match, //tmp regex match holder 5574 r, g, b, //final colour vals 5575 hex, //tmp hex holder 5576 mathRound = Math.round, 5577 parseIntFunc = parseInt, 5578 parseFloatFunc = parseFloat, 5579 htmlColorNames = { 5580 black: 0, 5581 silver: 0xc0c0c0, 5582 gray: 0x808080, 5583 white: 0xffffff, 5584 maroon: 0x800000, 5585 red: 0xff0000, 5586 purple: 0x800080, 5587 fuchsia: 0xff00ff, 5588 green: 0x8000, 5589 lime: 0xff00, 5590 olive: 0x808000, 5591 yellow: 0xffff00, 5592 navy: 128, 5593 blue: 255, 5594 teal: 0x8080, 5595 aqua: 0xffff, 5596 orange: 0xffa500 5597 }, 5598 colorRegex = /^rgb\(([\d\.]+)(%?),\s*([\d\.]+)(%?),\s*([\d\.]+)(%?)/i; 5599 5600 if (match = colorRegex.exec(val)) { //rgb() format, cater for percentages 5601 r = match[2] ? mathRound(((parseFloatFunc(match[1]) / 100) * 255)) : parseIntFunc(match[1]); 5602 g = match[4] ? mathRound(((parseFloatFunc(match[3]) / 100) * 255)) : parseIntFunc(match[3]); 5603 b = match[6] ? mathRound(((parseFloatFunc(match[5]) / 100) * 255)) : parseIntFunc(match[5]); 5604 } else { 5605 if (typeof val == 'number') { 5606 hex = val; 5607 } else if (val.charAt(0) == '#') { 5608 if (val.length == '4') { //deal with #fff shortcut 5609 val = '#' + val.charAt(1) + val.charAt(1) + val.charAt(2) + val.charAt(2) + val.charAt(3) + val.charAt(3); 5610 } 5611 hex = parseIntFunc(val.slice(1), 16); 5612 } else { 5613 hex = htmlColorNames[val]; 5614 } 5615 5616 r = (hex) >> 16; 5617 g = (hex & 0x00ff00) >> 8; 5618 b = (hex & 0x0000ff); 5619 } 5620 5621 val = new String('rgb(' + r + ', ' + g + ', ' + b + ')'); 5622 val.r = r; 5623 val.g = g; 5624 val.b = b; 5625 return val; 5626 } 5627 /* 5628 PrivateMethod: getElmDimension 5629 Gets the size of an element as an integer, not including padding or border 5630 */ 5631 var horizontalBorderPadding = [ 5632 'border-left-width', 5633 'border-right-width', 5634 'padding-left', 5635 'padding-right' 5636 ], 5637 verticalBorderPadding = [ 5638 'border-top-width', 5639 'border-bottom-width', 5640 'padding-top', 5641 'padding-bottom' 5642 ]; 5643 function getElmDimension(elm, cssProp /* (width|height) */) { 5644 var r, 5645 doc = document, 5646 docElm = doc.documentElement, 5647 docBody = document.body, 5648 docElmOrBody = glow.env.standardsMode ? docElm : docBody, 5649 isWidth = (cssProp == 'width'), 5650 cssPropCaps = isWidth ? 'Width' : 'Height', 5651 cssBorderPadding; 5652 5653 if (elm.window) { // is window 5654 r = glow.env.webkit < 522.11 ? (isWidth ? elm.innerWidth : elm.innerHeight) : 5655 glow.env.webkit ? (isWidth ? docBody.clientWidth : elm.innerHeight) : 5656 glow.env.opera < 9.5 ? (isWidth ? docBody.clientWidth : docBody.clientHeight) : 5657 /* else */ (isWidth ? docElmOrBody.clientWidth : docElmOrBody.clientHeight); 5658 5659 } 5660 else if (elm.getElementById) { // is document 5661 // we previously checked offsetWidth & clientWidth here 5662 // but they returned values too large in IE6 scrollWidth seems enough 5663 r = Math.max( 5664 docBody['scroll' + cssPropCaps], 5665 docElm['scroll' + cssPropCaps] 5666 ) 5667 } 5668 else { 5669 // get an array of css borders & padding 5670 cssBorderPadding = isWidth ? horizontalBorderPadding : verticalBorderPadding; 5671 r = elm['offset' + cssPropCaps] - parseInt( getCssValue(elm, cssBorderPadding) ); 5672 } 5673 return r; 5674 } 5675 5676 /* 5677 PrivateMethod: setElmsSize 5678 Set element's size 5679 5680 Arguments: 5681 elms - (<NodeList>) Elements 5682 val - (Mixed) Set element height / width. In px unless stated 5683 type - (String) "height" or "width" 5684 5685 Returns: 5686 Nowt. 5687 */ 5688 function setElmsSize(elms, val, type) { 5689 if (typeof val == 'number' || /\d$/.test(val)) { 5690 val += 'px'; 5691 } 5692 for (var i = 0, len = elms.length; i < len; i++) { 5693 elms[i].style[type] = val; 5694 } 5695 } 5696 5697 /* 5698 PrivateMethod: tempBlock 5699 Gives an element display:block (but keeps it hidden) and runs a function, then sets the element back how it was 5700 5701 Arguments: 5702 elm - element 5703 func - function to run 5704 5705 Returns: 5706 Return value of the function 5707 */ 5708 function tempBlock(elm, func) { 5709 //TODO: rather than recording individual style properties, just cache cssText? This was faster for getting the element size 5710 var r, 5711 elmStyle = elm.style, 5712 oldDisp = elmStyle.display, 5713 oldVis = elmStyle.visibility, 5714 oldPos = elmStyle.position; 5715 5716 elmStyle.visibility = 'hidden'; 5717 elmStyle.position = 'absolute'; 5718 elmStyle.display = 'block'; 5719 if (!isVisible(elm)) { 5720 elmStyle.position = oldPos; 5721 r = tempBlock(elm.parentNode, func); 5722 elmStyle.display = oldDisp; 5723 elmStyle.visibility = oldVis; 5724 } else { 5725 r = func(); 5726 elmStyle.display = oldDisp; 5727 elmStyle.position = oldPos; 5728 elmStyle.visibility = oldVis; 5729 } 5730 return r; 5731 } 5732 5733 /* 5734 PrivateMethod: getPixelValue 5735 Converts a relative value into an absolute pixel value. Only works in IE with Dimension value (not stuff like relative font-size). 5736 Based on some Dean Edwards' code 5737 5738 Arguments: 5739 element - element used to calculate relative values 5740 value - (string) relative value 5741 useYAxis - (string) calulate relative values to the y axis rather than x 5742 5743 Returns: 5744 Number 5745 */ 5746 function getPixelValue(element, value, useYAxis) { 5747 // Remember the original values 5748 var axisPos = useYAxis ? 'top' : 'left', 5749 axisPosUpper = useYAxis ? 'Top' : 'Left', 5750 elmStyle = element.style, 5751 positionVal = elmStyle[axisPos], 5752 runtimePositionVal = element.runtimeStyle[axisPos], 5753 r; 5754 5755 // copy to the runtime type to prevent changes to the display 5756 element.runtimeStyle[axisPos] = element.currentStyle[axisPos]; 5757 // set value to left / top 5758 elmStyle[axisPos] = value; 5759 // get the pixel value 5760 r = elmStyle['pixel' + axisPosUpper]; 5761 5762 // revert values 5763 elmStyle[axisPos] = positionVal; 5764 element.runtimeStyle[axisPos] = runtimePositionVal; 5765 5766 return r; 5767 } 5768 5769 /*************************************** API METHODS ******************************************/ 5770 /** 5771 @name glow.NodeList#css 5772 @function 5773 @description Get / set a CSS property value 5774 5775 @param {string | Object} property The CSS property name, or object of property-value pairs to set 5776 5777 @param {string | number} [value] The value to apply 5778 Number values will be treated as 'px' unless the CSS property 5779 accepts a unitless value. 5780 5781 If value is omitted, the value for the given property will be returned 5782 5783 @returns {glow.NodeList | string} Returns the NodeList when setting value, or the CSS value when getting values. 5784 CSS values are strings. For instance, "height" will return 5785 "25px" for an element 25 pixels high. You can use 5786 parseInt to convert these values. 5787 5788 @example 5789 // get value from first node 5790 glow("#subNav").css("display"); 5791 5792 @example 5793 // set left padding to 10px on all nodes 5794 glow("#subNav li").css("padding-left", "2em"); 5795 5796 @example 5797 // where appropriate, px is assumed when no unit is passed 5798 glow("#mainPromo").css("margin-top", 300); 5799 5800 @example 5801 // set multiple CSS values at once 5802 // NOTE: Property names containing a hyphen such as font-weight must be quoted 5803 glow("#myDiv").css({ 5804 'font-weight': 'bold', 5805 'padding' : '10px', 5806 'color' : '#00cc99' 5807 }); 5808 */ 5809 5810 5811 NodeListProto.css = function(prop, val) { 5812 var thisStyle, 5813 i = this.length, 5814 len = this.length, 5815 originalProp = prop, 5816 hasUnits = /width|height|top$|bottom$|left$|right$|spacing$|indent$|font-size/, 5817 style; 5818 5819 if (prop.constructor === Object) { // set multiple values 5820 for (style in prop) { 5821 this.css(style, prop[style]); 5822 } 5823 return this; 5824 } 5825 else if (val != undefined) { //set one CSS value 5826 prop = toStyleProp(prop); 5827 while (i--) { 5828 thisStyle = this[i].style; 5829 5830 if (typeof val == 'number' && hasUnits.test(originalProp)) { 5831 val = val.toString() + 'px'; 5832 } 5833 if (prop == 'opacity' && glow.env.ie) { 5834 //in IE the element needs hasLayout for opacity to work 5835 thisStyle.zoom = '1'; 5836 if (val === '') { 5837 thisStyle.filter = ''; 5838 } else { 5839 thisStyle.filter = 'alpha(opacity=' + Math.round(Number(val, 10) * 100) + ')'; 5840 } 5841 } else { 5842 thisStyle[prop] = val; 5843 } 5844 } 5845 return this; 5846 } else { //getting stuff 5847 if (!len) { return; } 5848 return getCssValue(this[0], prop); 5849 } 5850 }; 5851 5852 /** 5853 @name glow.NodeList#height 5854 @function 5855 @description Gets / set element height 5856 Return value does not include the padding or border of the element in 5857 browsers supporting the correct box model. 5858 5859 You can use this to easily get the height of the document or 5860 window, see example below. 5861 5862 @param {Number} [height] New height in pixels for each element in the list 5863 If ommited, the height of the first element is returned 5864 5865 @returns {glow.NodeList | number} 5866 Height of first element, or original NodeList when setting heights. 5867 5868 @example 5869 // get the height of #myDiv 5870 glow("#myDiv").height(); 5871 5872 @example 5873 // set the height of list items in #myList to 200 pixels 5874 glow("#myList > li").height(200); 5875 5876 @example 5877 // get the height of the document 5878 glow(document).height(); 5879 5880 @example 5881 // get the height of the window 5882 glow(window).height(); 5883 */ 5884 NodeListProto.height = function(height) { 5885 if (height == undefined) { 5886 return getElmDimension(this[0], 'height'); 5887 } 5888 setElmsSize(this, height, 'height'); 5889 return this; 5890 }; 5891 5892 /** 5893 @name glow.NodeList#width 5894 @function 5895 @description Gets / set element width 5896 Return value does not include the padding or border of the element in 5897 browsers supporting the correct box model. 5898 5899 You can use this to easily get the width of the document or 5900 window, see example below. 5901 5902 @param {Number} [width] New width in pixels for each element in the list 5903 If ommited, the width of the first element is returned 5904 5905 @returns {glow.NodeList | number} 5906 width of first element, or original NodeList when setting widths. 5907 5908 @example 5909 // get the width of #myDiv 5910 glow("#myDiv").width(); 5911 5912 @example 5913 // set the width of list items in #myList to 200 pixels 5914 glow("#myList > li").width(200); 5915 5916 @example 5917 // get the width of the document 5918 glow(document).width(); 5919 5920 @example 5921 // get the width of the window 5922 glow(window).width(); 5923 */ 5924 NodeListProto.width = function(width) { 5925 if (width == undefined) { 5926 return getElmDimension(this[0], 'width'); 5927 } 5928 setElmsSize(this, width, 'width'); 5929 return this; 5930 }; 5931 5932 /** 5933 @name glow.NodeList#scrollLeft 5934 @function 5935 @description Gets/sets the number of pixels the element has scrolled horizontally 5936 To get/set the scroll position of the window, use this method on 5937 a nodelist containing the window object. 5938 5939 @param {Number} [val] New left scroll position 5940 Omit this to get the current scroll position 5941 5942 @returns {glow.NodeList | number} 5943 Current scrollLeft value, or NodeList when setting scroll position. 5944 5945 @example 5946 // get the scroll left value of #myDiv 5947 var scrollPos = glow("#myDiv").scrollLeft(); 5948 // scrollPos is a number, eg: 45 5949 5950 @example 5951 // set the scroll left value of #myDiv to 20 5952 glow("#myDiv").scrollLeft(20); 5953 5954 @example 5955 // get the scrollLeft of the window 5956 glow(window).scrollLeft(); 5957 // scrollPos is a number, eg: 45 5958 */ 5959 NodeListProto.scrollLeft = function(val) { 5960 return scrollOffset(this, true, val); 5961 }; 5962 5963 /** 5964 @name glow.NodeList#scrollTop 5965 @function 5966 @description Gets/sets the number of pixels the element has scrolled vertically 5967 To get/set the scroll position of the window, use this method on 5968 a nodelist containing the window object. 5969 5970 @param {Number} [val] New top scroll position 5971 Omit this to get the current scroll position 5972 5973 @returns {glow.NodeList | number} 5974 Current scrollTop value, or NodeList when setting scroll position. 5975 5976 @example 5977 // get the scroll top value of #myDiv 5978 var scrollPos = glow("#myDiv").scrollTop(); 5979 // scrollPos is a number, eg: 45 5980 5981 @example 5982 // set the scroll top value of #myDiv to 20 5983 glow("#myDiv").scrollTop(20); 5984 5985 @example 5986 // get the scrollTop of the window 5987 glow(window).scrollTop(); 5988 // scrollPos is a number, eg: 45 5989 */ 5990 NodeListProto.scrollTop = function(val) { 5991 return scrollOffset(this, false, val); 5992 }; 5993 /** 5994 @name glow.dom-getScrollOffset 5995 @private 5996 @description Get the scrollTop / scrollLeft of a particular element 5997 @param {Element} elm Element (or window object) to get the scroll position of 5998 @param {Boolean} isLeft True if we're dealing with left scrolling, otherwise top 5999 */ 6000 function getScrollOffset(elm, isLeft) { 6001 var r, 6002 scrollProp = 'scroll' + (isLeft ? 'Left' : 'Top'); 6003 6004 // are we dealing with the window object or the document object? 6005 if (elm.window) { 6006 // get the scroll of the documentElement or the pageX/Yoffset 6007 // - some browsers use one but not the other 6008 r = elm.document.documentElement[scrollProp] 6009 || (isLeft ? elm.pageXOffset : elm.pageYOffset) 6010 || 0; 6011 } else { 6012 r = elm[scrollProp]; 6013 } 6014 return r; 6015 } 6016 6017 /** 6018 @name glow.dom-setScrollOffset 6019 @private 6020 @description Set the scrollTop / scrollLeft of a particular element 6021 @param {Element} elm Element (or window object) to get the scroll position of 6022 @param {Boolean} isLeft True if we're dealing with left scrolling, otherwise top 6023 @param {Number} newVal New scroll value 6024 */ 6025 function setScrollOffset(elm, isLeft, newVal) { 6026 // are we dealing with the window object or the document object? 6027 if (elm.window) { 6028 // we need to get whichever value we're not setting 6029 elm.scrollTo( 6030 isLeft ? newVal : getScrollOffset(elm, true), 6031 !isLeft ? newVal : getScrollOffset(elm, false) 6032 ); 6033 } else { 6034 elm['scroll' + (isLeft ? 'Left' : 'Top')] = newVal; 6035 } 6036 } 6037 6038 /** 6039 @name glow.dom-scrollOffset 6040 @private 6041 @description Set/get the scrollTop / scrollLeft of a NodeList 6042 @param {glow.dom.NodeList} nodeList Elements to get / set the position of 6043 @param {Boolean} isLeft True if we're dealing with left scrolling, otherwise top 6044 @param {Number} [val] Val to set (if not provided, we'll get the value) 6045 @returns NodeList for sets, Number for gets 6046 */ 6047 function scrollOffset(nodeList, isLeft, val) { 6048 var i = nodeList.length; 6049 6050 if (val !== undefined) { 6051 while (i--) { 6052 setScrollOffset(nodeList[i], isLeft, val); 6053 } 6054 return nodeList; 6055 } else { 6056 return getScrollOffset(nodeList[0], isLeft); 6057 } 6058 } 6059 /** 6060 @name glow.NodeList#hide 6061 @function 6062 @description Hides all items in the NodeList. 6063 6064 @returns {glow.NodeList} 6065 6066 @example 6067 // Hides all list items within #myList 6068 glow("#myList li").hide(); 6069 */ 6070 NodeListProto.hide = function() { 6071 return this.css('display', 'none').css('visibility', 'hidden'); 6072 }; 6073 6074 /** 6075 @name glow.NodeList#show 6076 @function 6077 @description Shows all hidden items in the NodeList. 6078 6079 @returns {glow.NodeList} 6080 6081 @example 6082 // Show element with ID myDiv 6083 glow("#myDiv").show(); 6084 6085 @example 6086 // Show all list items within #myList 6087 glow("#myList li").show(); 6088 */ 6089 NodeListProto.show = function() { 6090 var i = this.length, 6091 len = this.length, 6092 currItem, 6093 itemStyle; 6094 while (i--) { 6095 /* Create a NodeList for the current item */ 6096 currItem = new glow.NodeList(this[i]); 6097 itemStyle = currItem[0].style; 6098 if (currItem.css('display') == 'none') { 6099 itemStyle.display = ''; 6100 itemStyle.visibility = 'visible'; 6101 /* If display is still none, set to block */ 6102 if (currItem.css('display') == 'none') { 6103 itemStyle.display = 'block'; 6104 } 6105 } 6106 } 6107 return this; 6108 }; 6109 6110 /** 6111 @name glow.NodeList#offset 6112 @function 6113 @description Gets the offset from the top left of the document. 6114 If the NodeList contains multiple items, the offset of the 6115 first item is returned. 6116 6117 @returns {Object} 6118 Returns an object with "top" & "left" properties in pixels 6119 6120 @example 6121 glow("#myDiv").offset().top 6122 */ 6123 NodeListProto.offset = function() { 6124 // http://weblogs.asp.net/bleroy/archive/2008/01/29/getting-absolute-coordinates-from-a-dom-element.aspx - great bit of research, most bugfixes identified here (and also jquery trac) 6125 var elm = this[0], 6126 doc = document, 6127 docElm = doc.documentElement, 6128 docScrollPos = { 6129 x: getScrollOffset(window, true), 6130 y: getScrollOffset(window, false) 6131 } 6132 6133 //this is simple(r) if we can use 'getBoundingClientRect' 6134 // Sorry but the sooper dooper simple(r) way is not accurate in Safari 4 6135 if (!glow.env.webkit && elm.getBoundingClientRect) { 6136 var rect = elm.getBoundingClientRect(); 6137 return { 6138 top: Math.floor(rect.top) 6139 /* 6140 getBoundingClientRect is realive to top left of 6141 the viewport, so we need to sort out scrolling offset 6142 */ 6143 + docScrollPos.y 6144 /* 6145 IE adds the html element's border to the value. We can 6146 deduct this value using client(Top|Left). However, if 6147 the user has done html{border:0} clientTop will still 6148 report a 2px border in IE quirksmode so offset will be off by 2. 6149 Hopefully this is an edge case but we may have to revisit this 6150 in future 6151 */ 6152 - docElm.clientTop, 6153 6154 left: Math.floor(rect.left) //see above for docs on all this stuff 6155 + docScrollPos.x 6156 - docElm.clientLeft 6157 }; 6158 } else { //damnit, let's go the long way around 6159 var top = elm.offsetTop, 6160 left = elm.offsetLeft, 6161 originalElm = elm, 6162 nodeNameLower, 6163 docBody = document.body, 6164 //does the parent chain contain a position:fixed element 6165 involvesFixedElement = false, 6166 offsetParentBeforeBody = elm; 6167 6168 //add up all the offset positions 6169 while (elm = elm.offsetParent) { 6170 left += elm.offsetLeft; 6171 top += elm.offsetTop; 6172 6173 //if css position is fixed, we need to add in the scroll offset too, catch it here 6174 if (getCssValue(elm, 'position') == 'fixed') { 6175 involvesFixedElement = true; 6176 } 6177 6178 //gecko & webkit (safari 3) don't add on the border for positioned items 6179 if (glow.env.gecko || glow.env.webkit > 500) { 6180 left += parseInt(getCssValue(elm, 'border-left-width')) || 0; 6181 top += parseInt(getCssValue(elm, 'border-top-width')) || 0; 6182 } 6183 6184 //we need the offset parent (before body) later 6185 if (elm.nodeName.toLowerCase() != 'body') { 6186 offsetParentBeforeBody = elm; 6187 } 6188 } 6189 6190 //deduct all the scroll offsets 6191 elm = originalElm; 6192 while ((elm = elm.parentNode) && (elm != docBody) && (elm != docElm)) { 6193 left -= elm.scrollLeft; 6194 top -= elm.scrollTop; 6195 6196 //FIXES 6197 //gecko doesn't add the border of contained elements to the offset (overflow!=visible) 6198 if (glow.env.gecko && getCssValue(elm, 'overflow') != 'visible') { 6199 left += parseInt(getCssValue(elm, 'border-left-width')); 6200 top += parseInt(getCssValue(elm, 'border-top-width')); 6201 } 6202 } 6203 6204 //if we found a fixed position element we need to add the scroll offsets 6205 if (involvesFixedElement) { 6206 left += docScrollPos.x; 6207 top += docScrollPos.y; 6208 } 6209 6210 //FIXES 6211 // Webkit < 500 body's offset gets counted twice for absolutely-positioned elements (or if there's a fixed element) 6212 // Gecko - non-absolutely positioned elements that are direct children of body get the body offset counted twice 6213 if ( 6214 (glow.env.webkit < 500 && (involvesFixedElement || getCssValue(offsetParentBeforeBody, 'position') == 'absolute')) || 6215 (glow.env.gecko && getCssValue(offsetParentBeforeBody, 'position') != 'absolute') 6216 ) { 6217 left -= docBody.offsetLeft; 6218 top -= docBody.offsetTop; 6219 } 6220 6221 return {left:left, top:top}; 6222 } 6223 }; 6224 6225 /** 6226 @name glow.NodeList#position 6227 @function 6228 @description Get the top & left position of an element relative to its positioned parent 6229 This is useful if you want to make a position:static element position:absolute 6230 and retain the original position of the element 6231 6232 @returns {Object} 6233 An object with 'top' and 'left' number properties 6234 6235 @example 6236 // get the top distance from the positioned parent 6237 glow("#elm").position().top 6238 */ 6239 NodeListProto.position = function() { 6240 var positionedParent = new glow.NodeList( getPositionedParent(this[0]) ), 6241 hasPositionedParent = !!positionedParent[0], 6242 6243 // element margins to deduct 6244 marginLeft = parseInt( this.css('margin-left') ) || 0, 6245 marginTop = parseInt( this.css('margin-top') ) || 0, 6246 6247 // offset parent borders to deduct, set to zero if there's no positioned parent 6248 positionedParentBorderLeft = ( hasPositionedParent && parseInt( positionedParent.css('border-left-width') ) ) || 0, 6249 positionedParentBorderTop = ( hasPositionedParent && parseInt( positionedParent.css('border-top-width') ) ) || 0, 6250 6251 // element offsets 6252 elOffset = this.offset(), 6253 positionedParentOffset = hasPositionedParent ? positionedParent.offset() : {top: 0, left: 0}; 6254 6255 return { 6256 left: elOffset.left - positionedParentOffset.left - marginLeft - positionedParentBorderLeft, 6257 top: elOffset.top - positionedParentOffset.top - marginTop - positionedParentBorderTop 6258 } 6259 }; 6260 /* 6261 Get the 'real' positioned parent for an element, otherwise return null. 6262 */ 6263 function getPositionedParent(elm) { 6264 var offsetParent = elm.offsetParent, 6265 doc = document, 6266 docElm = doc.documentElement; 6267 6268 // get the real positioned parent 6269 // IE places elements with hasLayout in the offsetParent chain even if they're position:static 6270 // Also, <body> and <html> can appear in the offsetParent chain, but we don't want to return them if they're position:static 6271 while (offsetParent && new glow.NodeList(offsetParent).css('position') == 'static') { 6272 offsetParent = offsetParent.offsetParent; 6273 } 6274 6275 // sometimes the <html> element doesn't appear in the offsetParent chain, even if it has position:relative 6276 if (!offsetParent && new glow.NodeList(docElm).css('position') != 'static') { 6277 offsetParent = docElm; 6278 } 6279 6280 return offsetParent || null; 6281 } 6282 }); 6283 Glow.provide(function(glow) { 6284 var NodeListProto = glow.NodeList.prototype, 6285 document = window.document, 6286 undefined, 6287 keyEventNames = ' keypress keydown keyup '; 6288 6289 /** 6290 @name glow.NodeList#on 6291 @function 6292 @description Listen for an event. 6293 This will listen for a particular event on each dom node 6294 in the NodeList. 6295 6296 If you're listening to many children of a particular item, 6297 you may get better performance from {@link glow.NodeList#delegate}. 6298 6299 @param {String} eventName Name of the event to listen for. 6300 This can be any regular DOM event ('click', 'mouseover' etc) or 6301 a special event of NodeList. 6302 6303 @param {Function} callback Function to call when the event fires. 6304 The callback is passed a single event object. The type of this 6305 object is {@link glow.DomEvent} unless otherwise stated. 6306 6307 @param {Object} [thisVal] Value of 'this' within the callback. 6308 By default, this is the dom node being listened to. 6309 6310 @returns this 6311 6312 @example 6313 glow.get('#testLink').on('click', function(domEvent) { 6314 // do stuff 6315 6316 // if you want to cancel the default action (following the link)... 6317 return false; 6318 }); 6319 */ 6320 NodeListProto.on = function(eventName, callback, thisVal) { 6321 var isKeyEvent = (keyEventNames.indexOf(' ' + eventName + ' ') > -1); 6322 6323 if (isKeyEvent) { 6324 glow.events._addKeyListener(this, eventName, callback, thisVal); 6325 } 6326 else { // assume it's a DOM event 6327 glow.events._addDomEventListener(this, eventName, callback, thisVal); 6328 } 6329 6330 return this; 6331 } 6332 6333 /** 6334 @name glow.NodeList#detach 6335 @function 6336 @description detach a listener from elements 6337 This will detach the listener from each dom node in the NodeList. 6338 6339 @param {String} eventName Name of the event to detach the listener from 6340 6341 @param {Function} callback Listener callback to detach 6342 6343 @returns this 6344 6345 @example 6346 function clickListener(domEvent) { 6347 // ... 6348 } 6349 6350 // adding listeners 6351 glow.get('a').on('click', clickListener); 6352 6353 // removing listeners 6354 glow.get('a').detach('click', clickListener); 6355 */ 6356 NodeListProto.detach = function(eventName, callback, thisVal) { 6357 var isKeyEvent = (keyEventNames.indexOf(' ' + eventName + ' ') > -1); 6358 if (isKeyEvent) { 6359 glow.events._removeKeyListener(this, eventName, callback); 6360 } 6361 else { // assume it's a DOM event 6362 glow.events._removeDomEventListener(this, eventName, callback, thisVal); 6363 } 6364 6365 return this; 6366 } 6367 6368 /** 6369 @name glow.NodeList#delegate 6370 @function 6371 @description Listen for an event occurring on child elements matching a selector. 6372 'delegate' will catch events which occur on matching items created after 6373 the listener was added. 6374 6375 @param {String} eventName Name of the event to listen for. 6376 This can be any regular DOM event ('click', 'mouseover' etc) or 6377 a special event of NodeList. 6378 6379 @param {String} selector CSS selector of child elements to listen for events on 6380 For example, if you were wanting to hear events from links, this 6381 would be 'a'. 6382 6383 @param {Function} callback Function to call when the event fires. 6384 The callback is passed a single event object. The type of this 6385 object is {@link glow.DomEvent} unless otherwise stated. 6386 6387 @param {Object} [thisVal] Value of 'this' within the callback. 6388 By default, this is the dom node matched by 'selector'. 6389 6390 @returns this 6391 6392 @example 6393 // Using 'on' to catch clicks on links in a list 6394 glow.get('#nav a').on('click', function() { 6395 // do stuff 6396 }); 6397 6398 // The above adds a listener to each link, any links created later 6399 // will not have this listener, so we won't hear about them. 6400 6401 // Using 'delegate' to catch clicks on links in a list 6402 glow.get('#nav').delegate('click', 'a', function() { 6403 // do stuff 6404 }); 6405 6406 // The above only adds one listener to #nav which tracks clicks 6407 // to any links within. This includes elements created after 'delegate' 6408 // was called. 6409 6410 @example 6411 // Using delegate to change class names on table rows so :hover 6412 // behaviour can be emulated in IE6 6413 glow.get('#contactData').delegate('mouseover', 'tr', function() { 6414 glow.get(this).addClass('hover'); 6415 }); 6416 6417 glow.get('#contactData').delegate('mouseout', 'tr', function() { 6418 glow.get(this).removeClass('hover'); 6419 }); 6420 */ 6421 NodeListProto.delegate = function(eventName, selector, callback, thisVal) { 6422 var i = this.length, 6423 attachTo, 6424 isKeyEvent = (keyEventNames.indexOf(' ' + eventName + ' ') > -1); 6425 6426 while (i--) { 6427 attachTo = this[i]; 6428 6429 if (isKeyEvent) { 6430 // glow.events._addKeyListener([attachTo], eventName, handler); 6431 } 6432 else { // assume it's a DOM event 6433 glow.events._addDomEventListener([attachTo], eventName, callback, thisVal, selector); 6434 } 6435 } 6436 6437 return this; 6438 } 6439 6440 /** 6441 @name glow.NodeList#detachDelegate 6442 @function 6443 @description detach a delegated listener from elements 6444 This will detach the listener from each dom node in the NodeList. 6445 6446 @param {String} eventName Name of the event to detach the listener from 6447 6448 @param {String} selector CSS selector of child elements the listener is listening to 6449 6450 @param {Function} callback Listener callback to detach 6451 6452 @returns this 6453 6454 @example 6455 function clickListener(domEvent) { 6456 // ... 6457 } 6458 6459 // adding listeners 6460 glow.get('#nav').delegate('click', 'a', clickListener); 6461 6462 // removing listeners 6463 glow.get('#nav').detachDelegate('click', 'a', clickListener); 6464 */ 6465 NodeListProto.detachDelegate = function(eventName, selector, callback, thisVal) { 6466 var i = this.length, 6467 attachTo, 6468 isKeyEvent = (keyEventNames.indexOf(' ' + eventName + ' ') > -1), 6469 handler; 6470 6471 while (i--) { 6472 attachTo = this[i]; 6473 6474 if (isKeyEvent) { 6475 // glow.events._removeKeyListener([attachTo], eventName, handler); 6476 } 6477 else { 6478 glow.events._removeDomEventListener([attachTo], eventName, callback, selector); 6479 } 6480 } 6481 6482 return this; 6483 } 6484 6485 /** 6486 @name glow.NodeList#fire 6487 @function 6488 @param {String} eventName Name of the event to fire 6489 @param {glow.events.Event} [event] Event object to pass into listeners. 6490 You can provide a simple object of key / value pairs which will 6491 be added as properties of a glow.events.Event instance. 6492 6493 @description Fire an event on dom nodes within the NodeList 6494 Note, this will only trigger event listeners to be called, it won't 6495 for example, move the mouse or click a link on the page. 6496 6497 @returns glow.events.Event 6498 6499 @example 6500 glow.get('#testLink').on('click', function() { 6501 alert('Link clicked!'); 6502 }); 6503 6504 // The following causes 'Link clicked!' to be alerted, but doesn't 6505 // cause the browser to follow the link 6506 glow.get('#testLink').fire('click'); 6507 */ 6508 NodeListProto.fire = function(eventName, event) { 6509 return glow.events.fire(this, eventName, event); 6510 } 6511 6512 /** 6513 @name glow.NodeList#event:mouseenter 6514 @event 6515 @description Fires when the mouse enters the element specifically, does not bubble 6516 6517 @param {glow.events.DomEvent} event Event Object 6518 */ 6519 6520 /** 6521 @name glow.NodeList#event:mouseleave 6522 @event 6523 @description Fires when the mouse leaves the element specifically, does not bubble 6524 6525 @param {glow.events.DomEvent} event Event Object 6526 */ 6527 6528 /** 6529 @name glow.NodeList#event:keydown 6530 @event 6531 @description Fires when the user presses a key 6532 Only fires if the element has focus, listen for this event on 6533 the document to catch all keydowns. 6534 6535 This event related to the user pressing a key on the keyboard, 6536 if you're more concerned about the character entered, see the 6537 {@link glow.NodeList#event:keypress keypress} event. 6538 6539 keydown will only fire once, when the user presses the key. 6540 6541 The order of events is keydown, keypress*, keyup. keypress may 6542 fire many times if the user holds the key down. 6543 6544 @param {glow.events.KeyboardEvent} event Event Object 6545 */ 6546 6547 /** 6548 @name glow.NodeList#event:keypress 6549 @event 6550 @description Fires when a key's command executes. 6551 For instance, if you hold down a key, it's action will occur many 6552 times. This event will fire on each action. 6553 6554 This event is useful when you want to react to keyboard repeating, or 6555 to detect when a character is entered into a field. 6556 6557 The order of events is keydown, keypress*, keyup. keypress may 6558 fire many times if the user holds the key down. 6559 6560 @param {glow.events.KeyboardEvent} event Event Object 6561 */ 6562 6563 /** 6564 @name glow.NodeList#event:keyup 6565 @event 6566 @description Fires when the user releases a key 6567 Only fires if the element has focus, listen for this event on 6568 the document to catch all keyups. 6569 6570 This event related to the user pressing a key on the keyboard, 6571 if you're more concerned about the character entered, see the 6572 {@link glow.NodeList#event:keypress keypress} event. 6573 6574 The order of events is keydown, keypress*, keyup. keypress may 6575 fire many times if the user holds the key down. 6576 6577 @param {glow.events.KeyboardEvent} event Event Object 6578 */ 6579 }); 6580 Glow.complete('core', '2.0.0-alpha1'); 6581