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 glow.UID = 'glow' + Math.floor(Math.random() * (1<<30)); 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 /** 337 @private 338 @function 339 @name bindReady 340 @description Add listener to document to detect when page is ready. 341 */ 342 var bindReady = function() { // use `var bindReady= function` form instead of `function bindReady()` to prevent FireBug 'cannot access optimized closure' error 343 //don't do this stuff if the dom is already ready 344 if (glow.isDomReady) { return; } 345 glow._addReadyBlock('glow_domReady'); // wait for dom to be ready 346 347 function onReady() { /*debug*///log.info('onReady()'); 348 runReadyQueue(); 349 glow._removeReadyBlock('glow_domReady'); 350 } 351 352 if (document.readyState == 'complete') { // already here! 353 /*debug*///log.info('already complete'); 354 onReady(); 355 } 356 else if (glow.env.ie && document.attachEvent) { /*debug*///log.info('bindready() - document.attachEvent'); 357 // like IE 358 359 // not an iframe... 360 if (document.documentElement.doScroll && window == top) { 361 (function() { /*debug*///log.info('doScroll'); 362 try { 363 document.documentElement.doScroll('left'); 364 } 365 catch(error) { 366 setTimeout(arguments.callee, 0); 367 return; 368 } 369 370 // and execute any waiting functions 371 onReady(); 372 })(); 373 } 374 else { 375 // an iframe... 376 document.attachEvent( 377 'onreadystatechange', 378 function() { /*debug*///log.info('onreadystatechange'); 379 if (document.readyState == 'complete') { 380 document.detachEvent('onreadystatechange', arguments.callee); 381 onReady(); 382 } 383 } 384 ); 385 } 386 } 387 else if (document.readyState) { /*debug*///log.info('bindready() - document.readyState'); 388 // like pre Safari 389 (function() { /*debug*///log.info('loaded|complete'); 390 if ( /loaded|complete/.test(document.readyState) ) { 391 onReady(); 392 } 393 else { 394 setTimeout(arguments.callee, 0); 395 } 396 })(); 397 } 398 else if (document.addEventListener) {/*debug*///log.info('bindready() - document.addEventListener'); 399 // like Mozilla, Opera and recent webkit 400 document.addEventListener( 401 'DOMContentLoaded', 402 function(){ /*debug*///log.info('glow DOMContentLoaded'); 403 document.removeEventListener('DOMContentLoaded', arguments.callee, false); 404 onReady(); 405 }, 406 false 407 ); 408 } 409 else { 410 throw new Error('Unable to bind glow ready listener to document.'); 411 } 412 }; 413 414 glow.notSupported = ( // here are the browsers we don't support 415 glow.env.ie < 6 || 416 (glow.env.gecko < 1.9 && !/^1\.8\.1/.test(glow.env.version)) || 417 glow.env.opera < 9 || 418 glow.env.webkit < 412 419 ); 420 // deprecated 421 glow.isSupported = !glow.notSupported; 422 423 // block 'ready' if browser isn't supported 424 if (glow.notSupported) { 425 glow._addReadyBlock('glow_browserSupport'); 426 } 427 428 bindReady(); 429 } 430 ); 431 // end-source: core/ready.js 432 /** 433 @name glow.util 434 @namespace 435 @description Core JavaScript helpers 436 */ 437 Glow.provide(function(glow) { 438 var util = {}, 439 undefined, 440 TYPES = { 441 UNDEFINED : "undefined", 442 OBJECT : "object", 443 NUMBER : "number", 444 BOOLEAN : "boolean", 445 STRING : "string", 446 ARRAY : "array", 447 FUNCTION : "function", 448 NULL : "null" 449 }, 450 /* 451 PrivateProperty: TEXT 452 hash of strings used in encoding/decoding 453 */ 454 TEXT = { 455 AT : "@", 456 EQ : "=", 457 DOT : ".", 458 EMPTY : "", 459 AND : "&", 460 OPEN : "(", 461 CLOSE : ")" 462 }, 463 /* 464 PrivateProperty: JSON 465 nested hash of strings and regular expressions used in encoding/decoding Json 466 */ 467 JSON = { 468 HASH : { 469 START : "{", 470 END : "}", 471 SHOW_KEYS : true 472 }, 473 474 ARRAY : { 475 START : "[", 476 END : "]", 477 SHOW_KEYS : false 478 }, 479 480 DATA_SEPARATOR : ",", 481 KEY_SEPARATOR : ":", 482 KEY_DELIMITER : "\"", 483 STRING_DELIMITER : "\"", 484 485 SAFE_PT1 : /^[\],:{}\s]*$/, 486 SAFE_PT2 : /\\./g, 487 SAFE_PT3 : /\"[^\"\\\n\r]*\"|true|false|null|-?\d+(?:\.\d*)?(:?[eE][+\-]?\d+)?/g, 488 SAFE_PT4 : /(?:^|:|,)(?:\s*\[)+/g 489 }; 490 /** 491 @private 492 @name glow.util-_getType 493 @param {Object} object The object to be tested. 494 @returns {string} The data type of the object. 495 */ 496 function _getType(object) { 497 var typeOfObject = typeof object, 498 constructorStr, 499 type; 500 501 if (object === null) { return 'null'; } // warn: won't work across frames? 502 else if (isFunction(object)) { return 'Function'; } 503 else if (isArray(object)) { return 'Array'; } 504 else if (typeOfObject === 'object') { 505 506 constructorStr = object.constructor.toString(); 507 508 if ( /^function (\S+?)\(/.test(constructorStr) ) { 509 type = RegExp.$1; 510 if (type === 'Object') { return 'object'; } 511 else { return type; } 512 } 513 } 514 515 return typeOfObject; 516 } 517 518 function isArray(o) { 519 return {}.toString.call(o) === '[object Array]'; 520 } 521 522 function isFunction(o) { 523 return {}.toString.call(o) === '[object Function]'; 524 } 525 526 /** 527 @name glow.util.getType 528 @function 529 @description Get the native type or constructor name of an object. 530 This allows you to safely get the type of an object, even 531 if it came from another frame. 532 533 @param {Object} object Object to get the type of. 534 535 @example 536 glow.util.getType( null ); // 'null' 537 glow.util.getType( undefined ); // 'undefined' 538 glow.util.getType('Hello'); // 'string' 539 glow.util.getType( {} ); // 'Object' 540 glow.util.getType(12); // 'number' 541 glow.util.getType( [] ); // 'Array' 542 glow.util.getType( function(){} ); // 'Function' 543 glow.util.getType( glow('#whatever') ); // 'NodeList' 544 545 @example 546 var MyConstructor = function() {}, 547 obj = new MyConstructor; 548 549 glow.util.getType(obj); // '' 550 // The above returns an empty string as the constructor 551 // is an anonymous function and therefore has no name 552 */ 553 util.getType = _getType; 554 555 /** 556 @name glow.util.apply 557 @function 558 @description Copies properties from one object to another 559 All properties from 'source' will be copied onto 560 'destination', potentially overwriting existing properties 561 on 'destination'. 562 563 Properties from 'source's prototype chain will not be copied. 564 565 @param {Object} [destination] Destination object. 566 If this object is undefined or falsey, a new object will be created. 567 568 @param {Object} [source] Properties of this object will be copied onto the destination 569 If this object is undefined or falsey, a new object will be created. 570 571 @returns {Object} The destination object. 572 573 @example 574 var obj = glow.util.apply({foo: "hello", bar: "world"}, {bar: "everyone"}); 575 //results in {foo: "hello", bar: "everyone"} 576 */ 577 util.apply = function(destination, source) { 578 destination = destination || {}; 579 source = source || {}; 580 581 /*!debug*/ 582 if (typeof destination != 'object') { 583 glow.debug.warn('[wrong type] glow.util.apply expects argument "destination" to be of type object, not ' + typeof destination + '.'); 584 } 585 if (typeof source != 'object') { 586 glow.debug.warn('[wrong type] glow.util.apply expects argument "source" to be of type object, not ' + typeof source + '.'); 587 } 588 /*gubed!*/ 589 for (var i in source) { 590 if ( source.hasOwnProperty(i) ) { 591 destination[i] = source[i]; 592 } 593 } 594 return destination; 595 }; 596 597 /** 598 @name glow.util.extend 599 @function 600 @description Copies the prototype of one object to another 601 The 'subclass' can also access the 'base class' via subclass.base 602 603 @param {Function} sub Class which inherits properties. 604 @param {Function} base Class to inherit from. 605 @param {Object} additionalProperties An object of properties and methods to add to the subclass. 606 607 @example 608 function MyClass(arg) { 609 this.prop = arg; 610 } 611 MyClass.prototype.showProp = function() { 612 alert(this.prop); 613 }; 614 function MyOtherClass(arg) { 615 //call the base class's constructor 616 MyOtherClass.base.apply(this, arguments); 617 } 618 glow.util.extend(MyOtherClass, MyClass, { 619 setProp: function(newProp) { 620 this.prop = newProp; 621 } 622 }); 623 624 var test = new MyOtherClass("hello"); 625 test.showProp(); // alerts "hello" 626 test.setProp("world"); 627 test.showProp(); // alerts "world" 628 */ 629 util.extend = function(sub, base, additionalProperties) { 630 /*!debug*/ 631 if (arguments.length < 2) { 632 glow.debug.warn('[wrong count] glow.util.extend expects at least 2 arguments, not '+arguments.length+'.'); 633 } 634 if (typeof sub != 'function') { 635 glow.debug.error('[wrong type] glow.util.extend expects argument "sub" to be of type function, not ' + typeof sub + '.'); 636 } 637 if (typeof base != 'function') { 638 glow.debug.error('[wrong type] glow.util.extend expects argument "base" to be of type function, not ' + typeof base + '.'); 639 } 640 /*gubed!*/ 641 var f = function () {}, p; 642 f.prototype = base.prototype; 643 p = new f(); 644 sub.prototype = p; 645 p.constructor = sub; 646 sub.base = base; 647 if (additionalProperties) { 648 util.apply(sub.prototype, additionalProperties); 649 } 650 }; 651 652 /** 653 @name glow.util.escapeRegex 654 @function 655 @description Escape special regex chars from a string 656 657 @param {string} str String to escape 658 659 @returns {string} Escaped string 660 661 @example 662 var str = glow.util.escapeRegex('[Hello. Is this escaped?]'); 663 // Outputs: 664 // \[Hello\. Is this escaped\?\] 665 */ 666 util.escapeRegex = function(str) { 667 /*!debug*/ 668 if (arguments.length !== 1) { 669 glow.debug.warn('[wrong count] glow.util.escapeRegex expects 1 argument, not '+arguments.length+'.'); 670 } 671 /*gubed!*/ 672 return String(str).replace(/[.*+?^${}()|[\]\/\\]/g, '\\$&'); 673 }; 674 675 /** 676 @name glow.util.encodeUrl 677 @function 678 @description Encodes an object for use as a query string. 679 680 Returns a string representing the object suitable for use 681 as a query string, with all values suitably escaped. 682 It does not include the initial question mark. Where the 683 input field was an array, the key is repeated in the output. 684 685 @param {Object} object The object to be encoded. 686 687 This must be a hash whose values can only be primitives or 688 arrays of primitives. 689 690 @returns {String} 691 692 @example 693 var getRef = glow.util.encodeUrl({foo: "Foo", bar: ["Bar 1", "Bar2"]}); 694 // will return "foo=Foo&bar=Bar%201&bar=Bar2" 695 */ 696 util.encodeUrl = function (object) { 697 var type = _getType(object), 698 paramsList = [], 699 listLength = 0; 700 701 /*!debug*/ 702 if (typeof object !== 'object') { 703 throw new Error('glow.util.encodeUrl: cannot encode item'); 704 } 705 /*gubed!*/ 706 707 for (var key in object) { 708 type = _getType( object[key] ); 709 710 /*!debug*/ 711 if (type !== 'Array' || type !== 'string') { 712 glow.debug.warn('[wrong type] glow.util.encodeUrl expected Array or String value for "' + key + '", not ' + type + '.'); 713 } 714 /*gubed!*/ 715 if (type === 'Array') { 716 for(var i = 0, l = object[key].length; i < l; i++) { 717 /*!debug*/ 718 if (_getType(object[key])[i] !== 'string') { 719 glow.debug.warn('[wrong type] glow.util.encodeUrl expected string value for "' + key + '" value at index ' + i + ', not ' + _getType(object[key])[i] + '.'); 720 } 721 /*gubed!*/ 722 paramsList[listLength++] = key + '=' + encodeURIComponent(object[key][i]); 723 } 724 } 725 else { // assume string 726 paramsList[listLength++] = key + '=' + encodeURIComponent(object[key]); 727 } 728 } 729 730 return paramsList.join('&'); 731 }; 732 733 /** 734 @name glow.util.decodeUrl 735 @function 736 @description Decodes a query string into an object. 737 738 Returns an object representing the data given by the query 739 string, with all values suitably unescaped. All keys in the 740 query string are keys of the object. Repeated keys result 741 in an array. 742 743 @param {String} string The query string to be decoded. 744 745 It should not include the initial question mark. 746 747 @returns {Object} 748 749 @example 750 var getRef = glow.util.decodeUrl("foo=Foo&bar=Bar%201&bar=Bar2"); 751 // will return the object {foo: "Foo", bar: ["Bar 1", "Bar2"]} 752 */ 753 util.decodeUrl = function(text) { 754 /*!debug*/ 755 if (arguments.length !== 1) { 756 glow.debug.warn('[wrong count] glow.util.decodeUrl expects 1 argument, not '+arguments.length+'.'); 757 } 758 if (typeof text !== 'string') { 759 glow.debug.warn('[wrong type] glow.util.decodeUrl expects argument "text" to be of type string, not ' + typeof text + '.'); 760 } 761 /*gubed!*/ 762 763 var result = {}, 764 keyValues = text.split(/[&;]/), 765 thisPair, 766 key, 767 value; 768 769 for(var i = 0, leni = keyValues.length; i < leni; i++) { 770 thisPair = keyValues[i].split('='); 771 772 if (thisPair.length < 2) { 773 key = keyValues[i]; 774 value = ''; 775 } 776 else { 777 key = '' + decodeURIComponent(thisPair[0]); 778 value = '' + decodeURIComponent(thisPair[1]); 779 } 780 781 // will be either: undefined, string or [object Array] 782 switch (typeof result[key]) { 783 case 'string': 784 result[key] = [result[key], value]; 785 break; 786 case 'undefined': 787 result[key] = value; 788 break; 789 default: 790 result[key].push(value); 791 } 792 } 793 794 return result; 795 }; 796 797 /** 798 @name glow.util.encodeJson 799 @function 800 @description Encodes an object into a string JSON representation. 801 802 Returns a string representing the object as JSON. 803 804 @param {Object} object The object to be encoded. 805 806 This can be arbitrarily nested, but must not contain 807 functions or cyclical structures. 808 809 @returns {Object} 810 811 @example 812 var myObj = {foo: "Foo", bar: ["Bar 1", "Bar2"]}; 813 var getRef = glow.util.encodeJson(myObj); 814 // will return '{"foo": "Foo", "bar": ["Bar 1", "Bar2"]}' 815 */ 816 util.encodeJson = function(object, options){ 817 function _encode(object, options) 818 { 819 if(_getType(object) == TYPES.ARRAY) { 820 var type = JSON.ARRAY; 821 } else { 822 var type = JSON.HASH; 823 } 824 825 var serial = [type.START]; 826 var len = 1; 827 var dataType; 828 var notFirst = false; 829 830 for(var key in object) { 831 dataType = _getType(object[key]); 832 833 if(dataType != TYPES.UNDEFINED) { /* ignore undefined data */ 834 if(notFirst) { 835 serial[len++] = JSON.DATA_SEPARATOR; 836 } 837 notFirst = true; 838 839 if(type.SHOW_KEYS) { 840 serial[len++] = JSON.KEY_DELIMITER; 841 serial[len++] = key; 842 serial[len++] = JSON.KEY_DELIMITER; 843 serial[len++] = JSON.KEY_SEPARATOR; 844 } 845 846 switch(dataType) { 847 case TYPES.FUNCTION: 848 throw new Error("glow.data.encodeJson: cannot encode item"); 849 break; 850 case TYPES.STRING: 851 default: 852 serial[len++] = JSON.STRING_DELIMITER; 853 serial[len++] = glow.lang.replace(object[key], SLASHES.TEST, _replaceSlashes); 854 serial[len++] = JSON.STRING_DELIMITER; 855 break; 856 case TYPES.NUMBER: 857 case TYPES.BOOLEAN: 858 serial[len++] = object[key]; 859 break; 860 case TYPES.OBJECT: 861 case TYPES.ARRAY: 862 serial[len++] = _encode(object[key], options); 863 break; 864 case TYPES.NULL: 865 serial[len++] = TYPES.NULL; 866 break; 867 } 868 } 869 } 870 serial[len++] = type.END; 871 872 return serial.join(TEXT.EMPTY); 873 } 874 875 options = options || {}; 876 var type = _getType(object); 877 878 if((type == TYPES.OBJECT) || (type == TYPES.ARRAY)) { 879 return _encode(object, options); 880 } else { 881 throw new Error("glow.data.encodeJson: cannot encode item"); 882 } 883 884 }; 885 /** 886 @name glow.util.decodeJson 887 @function 888 @description Decodes a string JSON representation into an object. 889 890 Returns a JavaScript object that mirrors the data given. 891 892 @param {String} string The string to be decoded. 893 Must be valid JSON. 894 895 @param {Object} opts 896 897 Zero or more of the following as properties of an object: 898 @param {Boolean} [opts.safeMode=false] Whether the string should only be decoded if it is deemed "safe". 899 The json.org regular expression checks are used. 900 901 @returns {Object} 902 903 @example 904 var getRef = glow.util.decodeJson('{foo: "Foo", bar: ["Bar 1", "Bar2"]}'); 905 // will return {foo: "Foo", bar: ["Bar 1", "Bar2"]} 906 907 var getRef = glow.util.decodeJson('foobar', {safeMode: true}); 908 // will throw an error 909 */ 910 util.decodeJson = function(text, options){ 911 if(_getType(text) != TYPES.STRING) { 912 throw new Error("glow.data.decodeJson: cannot decode item"); 913 } 914 915 options = options || {}; 916 options.safeMode = options.safeMode || false; 917 918 var canEval = true; 919 920 if(options.safeMode) { 921 canEval = (JSON.SAFE_PT1.test(text.replace(JSON.SAFE_PT2, TEXT.AT).replace(JSON.SAFE_PT3, JSON.ARRAY.END).replace(JSON.SAFE_PT4, TEXT.EMPTY))); 922 } 923 924 if(canEval) { 925 try { 926 return eval(TEXT.OPEN + text + TEXT.CLOSE); 927 } 928 catch(e) {/* continue to error */} 929 } 930 931 throw new Error("glow.data.decodeJson: cannot decode item"); 932 }; 933 /** 934 @name glow.util.trim 935 @function 936 @description Removes leading and trailing whitespace from a string 937 938 @param {string} str String to trim 939 940 @returns {String} 941 942 String without leading and trailing whitespace 943 944 @example 945 glow.util.trim(" Hello World "); // "Hello World" 946 */ 947 util.trim = function(str) { 948 //this optimisation from http://blog.stevenlevithan.com/archives/faster-trim-javascript 949 return str.trim ? str.trim() : str.replace(/^\s*((?:[\S\s]*\S)?)\s*$/, '$1'); 950 }; 951 952 /** 953 @name glow.util.interpolate 954 @function 955 @description Replaces placeholders in a string with data from an object 956 957 @param {String} template The string containing {placeholders} 958 @param {Object} data Object containing the data to be merged in to the template 959 <p>The object can contain nested data objects and arrays, with nested object properties and array elements are accessed using dot notation. eg foo.bar or foo.0.</p> 960 <p>The data labels in the object cannot contain characters used in the template delimiters, so if the data must be allowed to contain the default { and } delimiters, the delimters must be changed using the option below.</p> 961 @param {Object} opts Options object 962 @param {String} [opts.delimiter="{}"] Alternative label delimiter(s) for the template 963 The first character supplied will be the opening delimiter, and the second the closing. If only one character is supplied, it will be used for both ends. 964 @param {Boolean} [opts.escapeHtml=false] Escape any special html characters found in the data object 965 Use this to safely inject data from the user into an HTML template. The glow.dom module 966 must be present for this feature to work (an error will be thrown otherwise). 967 968 @returns {String} 969 970 @example 971 var data = { 972 name: "Domino", 973 colours: ["black", "white"], 974 family: { 975 mum: "Spot", 976 dad: "Patch", 977 siblings: [] 978 } 979 }; 980 var template = "My cat's name is {name}. His colours are {colours.0} & {colours.1}. His mum is {family.mum}, his dad is {family.dad} and he has {family.siblings.length} brothers or sisters."; 981 var result = glow.util.interpolate(template, data); 982 // result == "My cat's name is Domino. His colours are black & white. His mum is Spot, his dad is Patch and he has 0 brothers or sisters." 983 984 @example 985 var data = { 986 name: 'Haxors!!1 <script src="hackhackhack.js"></script>' 987 } 988 var template = '<p>Hello, my name is {name}</p>'; 989 var result = glow.util.interpolate(template, data, { 990 escapeHtml: true 991 }); 992 // result == '<p>Hello, my name is Haxors!!1 <script src="hackhackhack.js"></script></p>' 993 */ 994 util.interpolate = function(template, data, opts) { 995 var placeHolderRx, 996 leftDelimiter, 997 rightDelimiter, 998 // div used for html escaping 999 div; 1000 1001 opts = opts || {}; 1002 1003 // make sure the dom module is around 1004 if (opts.escapeHtml) { 1005 div = glow('<div></div>'); 1006 } 1007 1008 if (opts.delimiter == undefined) { 1009 placeHolderRx = /\{[^{}]+\}/g; 1010 } 1011 else { 1012 leftDelimiter = opts.delimiter.substr(0, 1).replace(regexEscape, "\\$1"); 1013 rightDelimiter = opts.delimiter.substr(1, 1).replace(regexEscape, "\\$1") || leftDelimiter; 1014 placeHolderRx = new RegExp(leftDelimiter + "[^" + leftDelimiter + rightDelimiter + "]+" + rightDelimiter, "g"); 1015 } 1016 1017 return template.replace(placeHolderRx, function (placeholder) { 1018 var key = placeholder.slice(1, -1), 1019 keyParts = key.split("."), 1020 val, 1021 i = 0, 1022 len = keyParts.length; 1023 1024 if (key in data) { 1025 // need to be backwards compatible with "flattened" data. 1026 val = data[key]; 1027 } 1028 else { 1029 // look up the chain 1030 val = data; 1031 for (; i < len; i++) { 1032 if (keyParts[i] in val) { 1033 val = val[ keyParts[i] ]; 1034 } 1035 else { 1036 return placeholder; 1037 } 1038 } 1039 } 1040 1041 if (opts.escapeHtml) { 1042 val = div.text(val).html(); 1043 } 1044 return val; 1045 }); 1046 }; 1047 1048 /** 1049 @example 1050 glow.util.cookie(key); // get value for key 1051 glow.util.cookie({key: val, key2: val2}, opts); // set all keys, vals 1052 glow.util.cookie(key, val, opts); // set key, val 1053 glow.util.cookie(); // get all keys, vals 1054 1055 // use value of undefined 1056 */ 1057 util.cookie = function(key, value, opts) { 1058 /*!debug*/ 1059 if (arguments.length > 3) { 1060 glow.debug.warn('[wrong count] glow.util.cookie expects 3 or less arguments, not '+arguments.length+'.'); 1061 } 1062 1063 if (arguments.length === 1 && _getType(key) !== 'string' && _getType(key) !== 'object') { 1064 glow.debug.warn('[wrong type] glow.util.cookie expects argument "key" to be of type string or object, not ' + _getType(key) + '.'); 1065 } 1066 1067 if ( 1068 arguments.length === 2 1069 && 1070 ( 1071 ! (_getType(key) === 'string' && _getType(value) === 'string') 1072 || 1073 ! (_getType(key) === 'object' && _getType(value) === 'object') 1074 ) 1075 ) { 1076 glow.debug.warn('[wrong type] glow.util.cookie expects arguments to be (key, val) or (keyVals, opts).'); 1077 } 1078 1079 if (arguments.length === 3 && _getType(key) !== 'string' && _getType(value) !== 'string' && _getType(opts) !== 'object') { 1080 glow.debug.warn('[wrong type] glow.util.cookie expects argument "key" and "value" to be strings and "options" to be an object.'); 1081 } 1082 1083 if (opts && opts.debug && (typeof opts.expires !== 'number' || !opts.expires.toUTCString)) { 1084 glow.debug.warn('[wrong type] glow.util.cookie expects opts.expires to be a number or a Date.'); 1085 } 1086 /*gubed!*/ 1087 1088 1089 var date = '', 1090 expires = '', 1091 path = '', 1092 domain = '', 1093 secure = '', 1094 keyValues, 1095 thisPair, 1096 key, 1097 val, 1098 cookieValues; 1099 1100 if (opts) { 1101 if (opts.expires) { 1102 if (typeof opts.expires === 'number') { 1103 date = new Date(); 1104 date.setTime(date.getTime() + (opts.expires * 24 * 60 * 60 * 1000)); // opts.expires days 1105 } 1106 else { // is already a Date 1107 date = opts.expires; 1108 } 1109 expires = '; expires=' + date.toUTCString(); 1110 } 1111 1112 path = opts.path ? '; path=' + (opts.path) : ''; 1113 domain = opts.domain ? '; domain=' + (opts.domain) : ''; 1114 secure = opts.secure ? '; secure' : ''; 1115 } 1116 else { 1117 opts = {}; 1118 } 1119 1120 if (typeof key === 'string' && typeof value === 'string') { // a single setter 1121 document.cookie = key + '=' + encodeURIComponent(value) + expires + path + domain + secure; 1122 } 1123 else if (typeof key === 'object') { // an all setter 1124 for (var p in key) { 1125 document.cookie = p + '=' + encodeURIComponent(key[p]) + expires + path + domain + secure; 1126 } 1127 } 1128 else { // a getter 1129 cookieValues = {}; 1130 if (document.cookie && document.cookie != '') { 1131 keyValues = document.cookie.split(/; ?/); 1132 for (var i = 0, leni = keyValues.length; i < leni; i++) { 1133 thisPair = keyValues[i].split('='); 1134 1135 cookieValues[thisPair[0]] = decodeURIComponent(thisPair[1]); 1136 } 1137 } 1138 1139 if (typeof key === 'string') { // a single getter 1140 return cookieValues[key]; 1141 } 1142 else if (typeof key === 'undefined') { // an all getter 1143 return cookieValues; 1144 } 1145 } 1146 }; 1147 1148 util.removeCookie = function(key) { 1149 util.cookie(key, '', {expires: -1}); 1150 }; 1151 1152 // export 1153 glow.util = util; 1154 }); 1155 Glow.provide(function(glow) { 1156 /** 1157 @name glow.events 1158 @namespace 1159 @description Handling custom events 1160 */ 1161 var events = {}; 1162 1163 /* storage variables */ 1164 1165 var eventListeners = {}, // eventName: [ [callback, thisVal], ... ] 1166 eventId = 1, 1167 objIdCounter = 1, 1168 eventKey = '__eventId' + glow.UID; 1169 1170 1171 /** 1172 @name glow.events.addListeners 1173 @function 1174 @param {Object[]} attachTo Array of objects to add listeners to. 1175 @param {string} name Name of the event to listen for. 1176 Event names are case sensitive. 1177 @param {function} callback Function to call when the event is fired. 1178 The callback will be passed a single event object. The type of this 1179 object depends on the event (see documentation for the event 1180 you're listening to). 1181 @param {Object} [thisVal] Value of 'this' within the callback. 1182 By default, this is the object being listened to. 1183 @see glow.events.Target#fire 1184 @description Convenience method to add listeners to many objects at once. 1185 If you want to add a listener to a single object, use its 1186 'on' method. 1187 */ 1188 events.addListeners = function (attachTo, name, callback, thisVal) { 1189 var listenerIds = [], 1190 objIdent, 1191 listener, 1192 eventsOnObject, 1193 currentListeners; 1194 1195 //attach the event for each element, return an array of listener ids 1196 var i = attachTo.length; 1197 while (i--) { 1198 objIdent = attachTo[i][eventKey]; 1199 if (!objIdent){ 1200 objIdent = attachTo[i][eventKey] = objIdCounter++; 1201 } 1202 1203 listener = [ callback, thisVal ]; 1204 eventsOnObject = eventListeners[objIdent]; 1205 if(!eventsOnObject){ 1206 eventsOnObject = eventListeners[objIdent] = {}; 1207 } 1208 1209 currentListeners = eventsOnObject[name]; 1210 if(!currentListeners){ 1211 eventsOnObject[name] = [listener]; 1212 } 1213 else{ 1214 currentListeners[currentListeners.length] = listener; 1215 } 1216 } 1217 return events; 1218 }; 1219 1220 events._getPrivateEventKey = function(node) { 1221 if (!node[eventKey]) { 1222 node[eventKey] = objIdCounter++; 1223 } 1224 1225 return node[eventKey]; 1226 } 1227 1228 /** 1229 @name glow.events.fire 1230 @function 1231 @param {Object[]} items Array of objects to add listeners to 1232 @param {string} eventName Name of the event to fire 1233 @param {glow.events.Event|Object} [event] Event object to pass into listeners. 1234 You can provide a simple object of key-value pairs which will 1235 be added as properties on the glow.events.Event instance. 1236 1237 @description Convenience method to fire events on multiple items at once. 1238 If you want to fire events on a single object, use its 1239 'fire' method. 1240 */ 1241 1242 events.fire = function (items, eventName, event) { 1243 if (! event) { 1244 event = new events.Event(); 1245 } 1246 else if ( event.constructor === Object ) { 1247 event = new events.Event( event ) 1248 } 1249 1250 // for loop, because order matters! 1251 for(var i = 0, len = items.length; i < len; i++) { 1252 callListeners(items[i], eventName, event); 1253 } 1254 1255 return event; 1256 }; 1257 1258 1259 /** 1260 @name glow.events-callListeners 1261 @private 1262 */ 1263 function callListeners(item, eventName, event, thisVal) { 1264 var objIdent = item[eventKey], 1265 listenersForEvent, 1266 returnedVal; 1267 1268 // set the attachedTo value for this event 1269 event.attachedTo = event.attachedTo || item; 1270 1271 if (!objIdent || !eventListeners[objIdent]) { 1272 return event; 1273 } 1274 1275 listenersForEvent = eventListeners[objIdent][eventName]; 1276 1277 if (!listenersForEvent) { 1278 return event; 1279 } 1280 // Slice to make sure we get a unique copy. 1281 listenersForEvent = listenersForEvent.slice(0); 1282 for (var i = 0, len = listenersForEvent.length; i < len; i++){ 1283 returnedVal = listenersForEvent[i][0].call((listenersForEvent[i][1] || thisVal || item), event); 1284 if (returnedVal === false){ 1285 event.preventDefault(); 1286 } 1287 } 1288 1289 return event; 1290 } 1291 events._callListeners = callListeners; 1292 1293 1294 /** 1295 @name glow.events.removeAllListeners 1296 @function 1297 @param {Object[]} items Items to remove events from 1298 @description Removes all listeners attached to a given object. 1299 This removes not only listeners you added, but listeners others 1300 added too. For this reason it should only be used as part of a cleanup 1301 operation on objects that are about to be destroyed. 1302 */ 1303 1304 events.removeAllListeners = function (items) { 1305 var objIdent, 1306 i = items.length; 1307 1308 while(i--){ 1309 1310 objIdent = items[i][eventKey]; 1311 1312 if (!objIdent) { 1313 return false; 1314 } 1315 else { 1316 delete eventListeners[objIdent]; 1317 } 1318 } 1319 1320 return true; 1321 }; 1322 1323 1324 /** 1325 @name glow.events.removeListeners 1326 @function 1327 @param {Object[]} items Items to remove events from. 1328 @param {string} eventName Name of the event to remove. 1329 @param {function} callback A reference to the original callback used when the listener was added. 1330 @decription Removes listeners for an event. 1331 */ 1332 events.removeListeners = function (item, eventName, callback) { /* TODO: items! */ 1333 var objIdent, 1334 listenersForEvent, 1335 i = item.length; 1336 1337 1338 while(i--){ 1339 1340 objIdent = item[i][eventKey]; 1341 1342 if(!objIdent || !eventListeners[objIdent]){ 1343 return events; 1344 } 1345 1346 1347 listenersForEvent = eventListeners[objIdent][eventName]; 1348 if(!listenersForEvent){ 1349 return events; 1350 } 1351 1352 // for loop, because order matters 1353 for(var j = 0, lenj = listenersForEvent.length; j < lenj; j++){ 1354 if (listenersForEvent[j][0] === callback){ 1355 listenersForEvent.splice(j, 1); 1356 break; 1357 } 1358 1359 } 1360 } 1361 1362 return events; 1363 }; 1364 1365 /** 1366 Copies the events from one NodeList to another 1367 @private 1368 @name glow.events._copyEvents 1369 @see glow.NodeList#clone 1370 @function 1371 */ 1372 events._copyDomEvents = function(from, to){ 1373 var objIdent, 1374 i = from.length, 1375 j, jLen, 1376 listeners, 1377 listenersForEvent, 1378 eventName, 1379 toItem; 1380 1381 // loop over elements 1382 while(i--){ 1383 objIdent = from[i][eventKey]; 1384 listeners = eventListeners[objIdent]; 1385 1386 if (objIdent){ 1387 toItem = to.slice(i, i+1); 1388 1389 // loop over event names (of listeners attached) 1390 for ( eventName in listeners ) { 1391 listenersForEvent = listeners[eventName]; 1392 1393 // loop over individual listeners and add them to the 'to' item 1394 // loop forward to preserve event order 1395 for (j = 0, jLen = listenersForEvent.length; j < jLen; j++) { 1396 // add listener 1397 toItem.on( eventName, listenersForEvent[j][0], listenersForEvent[j][1] ); 1398 } 1399 } 1400 } 1401 } 1402 } 1403 /** 1404 @name glow.events._getListeners 1405 @private 1406 @function 1407 @param {Object} item Item to find events for 1408 @decription Returns a list of listeners attached for the given item. 1409 1410 */ 1411 events._getListeners = function(item){ 1412 var objIdent = item[eventKey]; 1413 1414 if (!objIdent) { 1415 return {}; 1416 } 1417 else { 1418 // todo: need to return listeners in a sensible format 1419 return eventListeners[objIdent]; 1420 } 1421 1422 }; 1423 1424 ///** 1425 //@name glow.events.hasListener 1426 //@function 1427 //@param {Object[]} item Item to find events for 1428 //@param {String} eventName Name of the event to match 1429 //@decription Returns true if an event is found for the item supplied 1430 // 1431 //*/ 1432 // 1433 //glow.events.hasListener = function (item, eventName) { 1434 // var objIdent, 1435 // listenersForEvent; 1436 // 1437 // for (var i = 0, len = item.length; i < len; i++) { 1438 // objIdent = item[i][eventKey]; 1439 // 1440 // if (!objIdent || !eventListeners[objIdent]) { 1441 // return false; 1442 // } 1443 // 1444 // listenersForEvent = eventListeners[objIdent][eventName]; 1445 // if (!listenersForEvent) { 1446 // return false; 1447 // } 1448 // else { 1449 // return true; 1450 // } 1451 // } 1452 // 1453 // return false; 1454 //}; 1455 1456 /** 1457 @name glow.events.Target 1458 @class 1459 @description An object that can have event listeners and fire events. 1460 Extend this class to make your own objects have 'on' and 'fire' 1461 methods. 1462 1463 @example 1464 // Ball is our constructor 1465 function Ball() { 1466 // ... 1467 } 1468 1469 // make Ball inherit from Target 1470 glow.util.extend(Ball, glow.events.Target, { 1471 // additional methods for Ball here, eg: 1472 bowl: function() { 1473 // ... 1474 } 1475 }); 1476 1477 // now instances of Ball can receive event listeners 1478 var myBall = new Ball(); 1479 myBall.on('bounce', function() { 1480 alert('BOING!'); 1481 }); 1482 1483 // and events can be fired from Ball instances 1484 myBall.fire('bounce'); 1485 */ 1486 1487 events.Target = function () { 1488 1489 }; 1490 var targetProto = events.Target.prototype; 1491 1492 /** 1493 @name glow.events.Target.extend 1494 @function 1495 @param {Object} obj Object to add Target instance methods to. 1496 1497 @description Convenience method to add Target instance methods onto an object. 1498 If you want to add Target methods to a class, extend glow.events.Target instead. 1499 1500 @example 1501 var myApplication = {}; 1502 1503 glow.events.Target.extend(myApplication); 1504 1505 // now myApplication can fire events... 1506 myApplication.fire('load'); 1507 1508 // and other objects can listen for those events 1509 myApplication.on('load', function(e) { 1510 alert('App loaded'); 1511 }); 1512 */ 1513 1514 events.Target.extend = function (obj) { 1515 glow.util.apply( obj, glow.events.Target.prototype ); 1516 }; 1517 1518 /** 1519 @name glow.events.Target#on 1520 @function 1521 @param {string} eventName Name of the event to listen for. 1522 @param {function} callback Function to call when the event fires. 1523 The callback is passed a single event object. The type of this 1524 object depends on the event (see documentation for the event 1525 you're listening to). 1526 @param {Object} [thisVal] Value of 'this' within the callback. 1527 By default, this is the object being listened to. 1528 1529 @description Listen for an event 1530 1531 @returns this 1532 1533 @example 1534 myObj.on('show', function() { 1535 // do stuff 1536 }); 1537 */ 1538 1539 targetProto.on = function(eventName, callback, thisVal) { 1540 glow.events.addListeners([this], eventName, callback, thisVal); 1541 return this; 1542 } 1543 1544 /** 1545 @name glow.events.Target#detach 1546 @function 1547 @param {string} eventName Name of the event to remove. 1548 @param {function} callback Callback to detach. 1549 @param {Object} [thisVal] Value of 'this' within the callback. 1550 By default, this is the object being listened to. 1551 @description Remove an event listener. 1552 1553 @returns this Target object 1554 1555 @example 1556 function showListener() { 1557 // ... 1558 } 1559 1560 // add listener 1561 myObj.on('show', showListener); 1562 1563 // remove listener 1564 myObj.detach('show', showListener); 1565 1566 @example 1567 // note the following WILL NOT WORK 1568 1569 // add listener 1570 myObj.on('show', function() { 1571 alert('hi'); 1572 }); 1573 1574 // remove listener 1575 myObj.detach('show', function() { 1576 alert('hi'); 1577 }); 1578 1579 // this is because both callbacks are different function instances 1580 1581 */ 1582 1583 targetProto.detach = function(eventName, callback) { 1584 glow.events.removeListeners(this, eventName, callback); 1585 return this; 1586 } 1587 1588 /** 1589 @name glow.events.Target#fire 1590 @function 1591 @param {string} eventName Name of the event to fire. 1592 @param {glow.events.Event|Object} [event] Event object to pass into listeners. 1593 You can provide a simple object of key-value pairs which will 1594 be added as properties of a glow.events.Event instance. 1595 1596 @description Fire an event. 1597 1598 @returns glow.events.Event 1599 1600 @example 1601 myObj.fire('show'); 1602 1603 @example 1604 // adding properties to the event object 1605 myBall.fire('bounce', { 1606 velocity: 30 1607 }); 1608 1609 @example 1610 // BallBounceEvent extends glow.events.Event but has extra methods 1611 myBall.fire( 'bounce', new BallBounceEvent(myBall) ); 1612 */ 1613 1614 targetProto.fire = function(eventName, event) { 1615 if (! event) { 1616 event = new events.Event(); 1617 } 1618 else if ( event.constructor === Object ) { 1619 event = new events.Event( event ) 1620 } 1621 1622 return callListeners(this, eventName, event); 1623 } 1624 1625 /** 1626 @name glow.events.Event 1627 @class 1628 @param {Object} [properties] Properties to add to the Event instance. 1629 Each key-value pair in the object will be added to the Event as 1630 properties. 1631 1632 @description Describes an event that occurred. 1633 You don't need to create instances of this class if you're simply 1634 listening to events. One will be provided as the first argument 1635 in your callback. 1636 1637 @example 1638 // creating a simple event object 1639 var event = new glow.events.Event({ 1640 velocity: 50, 1641 direction: 180 1642 }); 1643 1644 // 'velocity' and 'direction' are simple made-up properties 1645 // you may want to add to your event object 1646 1647 @example 1648 // inheriting from glow.events.Event to make a more 1649 // specialised event object 1650 1651 function RocketEvent() { 1652 // ... 1653 } 1654 1655 // inherit from glow.events.Event 1656 glow.util.extend(RocketEvent, glow.events.Event, { 1657 getVector: function() { 1658 return // ... 1659 } 1660 }); 1661 1662 // firing the event 1663 rocketInstance.fire( 'landingGearDown', new RocketEvent() ); 1664 1665 // how a user would listen to the event 1666 rocketInstance.on('landingGearDown', function(rocketEvent) { 1667 var vector = rocketEvent.getVector(); 1668 }); 1669 */ 1670 1671 events.Event = function(obj) { 1672 if (obj) { 1673 glow.util.apply(this, obj); 1674 } 1675 }; 1676 var eventProto = events.Event.prototype; 1677 /** 1678 @name glow.events.Event#attachedTo 1679 @type {Object} 1680 @description The object the listener was attached or delegated to. 1681 */ 1682 1683 1684 /** 1685 @name glow.events.Event#preventDefault 1686 @function 1687 @description Prevent the default action of the event. 1688 Eg, if the click event on a link is cancelled, the link 1689 is not followed. 1690 1691 Returning false from an event listener has the same effect 1692 as calling this function. 1693 1694 For custom events, it's down to whatever fired the event 1695 to decide what to do in this case. See {@link glow.events.Event#defaultPrevented defaultPrevented} 1696 1697 @example 1698 myLinks.on('click', function(event) { 1699 event.preventDefault(); 1700 }); 1701 1702 // same as... 1703 1704 myLinks.on('click', function(event) { 1705 return false; 1706 }); 1707 */ 1708 1709 eventProto.preventDefault = function () { 1710 this._defaultPrevented = true; 1711 }; 1712 1713 1714 /** 1715 @name glow.events.Event#defaultPrevented 1716 @function 1717 @description Has the default been prevented for this event? 1718 This should be used by whatever fires the event to determine if it should 1719 carry out of the default action. 1720 1721 @returns {Boolean} Returns true if {@link glow.events.Event#preventDefault preventDefault} has been called for this event. 1722 1723 @example 1724 // fire the 'show' event 1725 // read if the default action has been prevented 1726 if ( overlayInstance.fire('show').defaultPrevented() == false ) { 1727 // go ahead and show 1728 } 1729 */ 1730 1731 eventProto.defaultPrevented = function () { 1732 return this._defaultPrevented; 1733 }; 1734 1735 1736 /* Export */ 1737 glow.events = events; 1738 }); 1739 Glow.provide(function(glow) { 1740 var document = window.document, 1741 undef = undefined, 1742 domEventHandlers = [], // like: domEventHandlers[uniqueId][eventName].count, domEventHandlers[uniqueId][eventName].callback 1743 // shortcuts to aim compression 1744 events = glow.events, 1745 _callListeners = events._callListeners, 1746 _getPrivateEventKey = events._getPrivateEventKey, 1747 // used for feature detection 1748 supportsActivateDeactivate = (document.createElement('div').onactivate !== undefined); 1749 1750 /** 1751 @name glow.events.DomEvent 1752 @constructor 1753 @extends glow.events.Event 1754 1755 @param {Event|string} nativeEvent A native browser event read properties from, or the name of a native event. 1756 1757 @param {Object} [properties] Properties to add to the Event instance. 1758 Each key-value pair in the object will be added to the Event as 1759 properties 1760 1761 @description Describes a DOM event that occurred 1762 You don't need to create instances of this class if you're simply 1763 listening to events. One will be provided as the first argument 1764 in your callback. 1765 */ 1766 function DomEvent(e, properties) { 1767 /** 1768 @name glow.events.DomEvent#nativeEvent 1769 @type {Event | MouseEvent | UIEvent} 1770 @description The native event object provided by the browser. 1771 */ 1772 this.nativeEvent = e; 1773 1774 /** 1775 @name glow.events.DomEvent#type 1776 @type {string} 1777 @description The native type of the event, like 'click' or 'keydown'. 1778 */ 1779 this.type = e.type; 1780 1781 /** 1782 @name glow.events.DomEvent#source 1783 @type {HTMLElement} 1784 @description The element that the event originated from. 1785 For example, you could attach a listener to an <ol> element to listen for 1786 clicks. If the user clicked on an <li> the source property would be the 1787 <li> element, and {@link glow.DomEvent#attachedTo attachedTo} would be 1788 the <ol>. 1789 */ 1790 this.source = e.target || e.srcElement || undefined; 1791 1792 // some rare cases crop up in Firefox where the source is a text node 1793 if (this.source && this.source.nodeType === 3) { 1794 this.source = this.source.parentNode; 1795 } 1796 1797 /** 1798 @name glow.events.DomEvent#related 1799 @type {HTMLElement} 1800 @description A related HTMLElement 1801 For mouseover / mouseenter events, this will refer to the previous element 1802 the mouse was over. 1803 1804 For mouseout / mouseleave events, this will refer to the element the mouse 1805 is now over. 1806 */ 1807 this.related = e.relatedTarget || (this.type == 'mouseover' ? e.fromElement : e.toElement); 1808 1809 /** 1810 @name glow.events.DomEvent#shiftKey 1811 @type {boolean | undefined} 1812 @description Was the shift key pressed during the event? 1813 */ 1814 this.shiftKey = (e.shiftKey === undef)? undef : !!e.shiftKey; 1815 1816 /** 1817 @name glow.events.DomEvent#altKey 1818 @type {boolean | undefined} 1819 @description Was the alt key pressed during the event? 1820 */ 1821 this.altKey = (e.altKey === undef)? undef : !!e.altKey; 1822 1823 /** 1824 @name glow.events.DomEvent#ctrlKey 1825 @type {boolean | undefined} 1826 @description Was the ctrl key pressed during the event? 1827 */ 1828 this.ctrlKey = (e.ctrlKey === undef)? undef : !!e.ctrlKey; 1829 1830 /** 1831 @name glow.events.DomEvent#button 1832 @type {number | undefined} 1833 @description A number representing which button was pressed. 1834 0 for the left button, 1 for the middle button or 2 for the right button. 1835 */ 1836 this.button = glow.env.ie ? (e.button & 1 ? 0 : e.button & 2 ? 2 : 1) : e.button; 1837 1838 /** 1839 @name glow.events.DomEvent#mouseTop 1840 @type {number} 1841 @description The vertical position of the mouse pointer in the page in pixels. 1842 */ 1843 /** 1844 @name glow.events.DomEvent#mouseLeft 1845 @type {number} 1846 @description The horizontal position of the mouse pointer in the page in pixels. 1847 */ 1848 if (e.pageX !== undef || e.pageY !== undef) { 1849 this.mouseTop = e.pageY; 1850 this.mouseLeft = e.pageX; 1851 } 1852 else if (e.clientX !== undef || e.clientY !== undef) { 1853 this.mouseTop = e.clientY + document.body.scrollTop + document.documentElement.scrollTop; 1854 this.mouseLeft = e.clientX + document.body.scrollLeft + document.documentElement.scrollLeft; 1855 } 1856 1857 /** 1858 @name glow.events.DomEvent#wheelData 1859 @type {number} 1860 @description The number of clicks the mouse wheel moved. 1861 Up values are positive, down values are negative. 1862 */ 1863 if (this.type == 'mousewheel') { 1864 // this works in latest opera, but have read that it needs to be switched in direction 1865 // if there was an opera bug, I can't find which version it was fixed in 1866 this.wheelDelta = 1867 e.wheelDelta ? e.wheelDelta / 120 : 1868 e.detail ? - e.detail / 3 : 1869 0; 1870 } 1871 1872 for (var key in properties) { 1873 this[key] = properties[key]; 1874 } 1875 } 1876 1877 glow.util.extend(DomEvent, events.Event, { 1878 // no docs for this as it simply adds DOM behaviour to glow.events.Event#preventDefault 1879 preventDefault: function() { 1880 var nativeEvent = this.nativeEvent; 1881 if (nativeEvent) { 1882 nativeEvent.preventDefault && nativeEvent.preventDefault(); 1883 nativeEvent.returnValue = false; 1884 } 1885 // call the original method 1886 events.Event.prototype.preventDefault.call(this); 1887 return this; 1888 }, 1889 /** 1890 @name glow.events.DomEvent#stopPropagation 1891 @function 1892 @description Stop an event bubbling any further. 1893 For instance, if you had 2 click listeners, one on a link and 1894 one on a parent element, if you stopped the event propogating in the 1895 link listener, the event will never be fired on the parent element. 1896 1897 @returns this 1898 */ 1899 stopPropagation: function() { 1900 var nativeEvent = this.nativeEvent; 1901 1902 if (nativeEvent) { 1903 // the ie way 1904 nativeEvent.cancelBubble = true; 1905 // the proper way 1906 nativeEvent.stopPropagation && nativeEvent.stopPropagation(); 1907 } 1908 return this; 1909 } 1910 }); 1911 1912 /** 1913 Add listener for an event fired by the browser. 1914 @private 1915 @name glow.events._addDomEventListener 1916 @see glow.NodeList#on 1917 @function 1918 */ 1919 events._addDomEventListener = function(nodeList, eventName) { 1920 var i = nodeList.length, // TODO: should we check that this nodeList is deduped? 1921 attachTo, 1922 id; 1923 1924 while (i--) { 1925 attachTo = nodeList[i]; 1926 1927 id = _getPrivateEventKey(attachTo); 1928 1929 // check if there is already a handler for this kind of event attached 1930 // to this node (which will run all associated callbacks in Glow) 1931 if (!domEventHandlers[id]) { domEventHandlers[id] = {}; } 1932 1933 if (domEventHandlers[id][eventName] && domEventHandlers[id][eventName].count > 0) { // already have handler in place 1934 domEventHandlers[id][eventName].count++; 1935 continue; 1936 } 1937 1938 // no bridge in place yet 1939 domEventHandlers[id][eventName] = { count:1 }; 1940 1941 // attach a handler to tell Glow to run all the associated callbacks 1942 (function(attachTo) { 1943 var handler = domHandle(attachTo, eventName); 1944 1945 if (attachTo.addEventListener) { // like DOM2 browsers 1946 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 1947 } 1948 else if (attachTo.attachEvent) { // like IE 1949 attachTo.attachEvent('on' + handler.domName, handler); 1950 } 1951 // older browsers? 1952 1953 domEventHandlers[id][eventName].callback = handler; 1954 })(attachTo); 1955 } 1956 } 1957 1958 function domHandle(attachTo, eventName) { 1959 var handler; 1960 1961 if (eventName === 'mouseenter' || eventName === 'mouseleave') { 1962 // mousenter and mouseleave handle their own delegation as its non-standard 1963 handler = function(nativeEvent) { 1964 var domEvent = new DomEvent(nativeEvent), 1965 container, 1966 selector, 1967 elementsToTest = _getDelegateMatches(attachTo, eventName, domEvent); 1968 1969 // add this element to the delegates 1970 elementsToTest.push( [attachTo] ); 1971 1972 for (var i = 0, leni = elementsToTest.length; i < leni; i++) { 1973 container = elementsToTest[i][0]; 1974 selector = elementsToTest[i][1]; 1975 1976 if (!new glow.NodeList(container).contains(domEvent.related)) { 1977 _callListeners(attachTo, selector ? eventName + '/' + selector : eventName, domEvent, container); // fire() returns result of callback 1978 } 1979 } 1980 return !domEvent.defaultPrevented(); 1981 }; 1982 1983 handler.domName = (eventName === 'mouseenter') ? 'mouseover' : 'mouseout'; 1984 } 1985 // handle blur & focus differently for IE so it bubbles 1986 else if ( supportsActivateDeactivate && (eventName === 'focus' || eventName === 'blur') ) { 1987 // activate and deactivate are like focus and blur but bubble 1988 // However, <body> and <html> also activate so we need to fix that 1989 handler = function(nativeEvent) { 1990 var nodeName = nativeEvent.srcElement.nodeName; 1991 if (nodeName !== 'HTML' && nodeName !== 'BODY') { 1992 _callDomListeners( attachTo, eventName, new DomEvent(nativeEvent) ); 1993 } 1994 } 1995 1996 handler.domName = (eventName === 'focus') ? 'activate' : 'deactivate'; 1997 } 1998 else { 1999 handler = function(nativeEvent) { 2000 var domEvent = new DomEvent(nativeEvent); 2001 _callDomListeners(attachTo, eventName, domEvent); // fire() returns result of callback 2002 2003 return !domEvent.defaultPrevented(); 2004 }; 2005 2006 handler.domName = eventName; 2007 } 2008 2009 return handler; 2010 } 2011 2012 2013 /** 2014 Remove listener for an event fired by the browser. 2015 @private 2016 @name glow.events._removeDomEventListener 2017 @see glow.NodeList#detach 2018 @function 2019 */ 2020 events._removeDomEventListener = function(nodeList, eventName) { 2021 var i = nodeList.length, 2022 attachTo, 2023 id, 2024 bridge, 2025 handler; 2026 2027 while (i--) { 2028 attachTo = nodeList[i]; 2029 2030 // skip if there is no bridge for this kind of event attached 2031 id = _getPrivateEventKey(attachTo); 2032 if (!domEventHandlers[id] || !domEventHandlers[id][eventName]) { continue; } 2033 2034 bridge = domEventHandlers[id][eventName]; 2035 2036 // one less listener associated with this event 2037 if ( !--bridge.count ) { 2038 // no more listeners associated with this event 2039 handler = bridge.callback; 2040 2041 if (attachTo.removeEventListener) { // like DOM2 browsers 2042 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 2043 } 2044 else if (attachTo.detachEvent) { // like IE 2045 attachTo.detachEvent('on' + handler.domName, handler); 2046 } 2047 domEventHandlers[id][eventName] = undefined; 2048 } 2049 } 2050 } 2051 2052 // see: http://developer.yahoo.com/yui/3/event/#eventsimulation 2053 // see: http://developer.yahoo.com/yui/docs/YAHOO.util.UserAction.html 2054 // function simulateDomEvent(nodeList, domEvent) { 2055 // var i = nodeList.length, 2056 // eventName = domEvent.type, 2057 // nativeEvent, 2058 // node, 2059 // fire; 2060 // 2061 // if (document.createEvent) { 2062 // var nativeEvent = document.createEvent('MouseEvent'); // see: 2063 // nativeEvent.initEvent(eventName, true, true); 2064 // 2065 // fire = function(el) { 2066 // return !el.dispatchEvent(nativeEvent); 2067 // } 2068 // } 2069 // else { 2070 // fire = function(el) { 2071 // var nativeEvent = document.createEventObject(); 2072 // return el.fireEvent('on'+eventName, nativeEvent); 2073 // } 2074 // } 2075 // 2076 // while (i--) { 2077 // node = nodeList[i]; 2078 // if (node.nodeType !== 1) { continue; } 2079 // fire(node); 2080 // } 2081 // } 2082 2083 /* 2084 The following is a proposal for dealing with event delegation without 2085 multiple bridges. This allows us to have only one listener per element per event 2086 therefore only one search for delegates per event. 2087 */ 2088 2089 // structure: 2090 // delegates[eventId][eventName][selector] = number of delegates listening for that selector (for that event on that element) 2091 var delegates = {} 2092 2093 /** 2094 @name glow.events._registerDelegate 2095 @private 2096 @function 2097 @description Register a delegated event 2098 This allows selectors for a given element & eventName to be retrieved later 2099 2100 @param {glow.NodeList} nodeList Elements to register 2101 @param {string} eventName 2102 @param {string} selector Selector to match for the delegate 2103 */ 2104 events._registerDelegate = function(nodeList, eventName, selector) { 2105 var id, 2106 i = nodeList.length, 2107 delegatesForEvent; 2108 2109 while (i--) { 2110 id = _getPrivateEventKey( nodeList[i] ); 2111 delegates[id] = delegates[id] || {}; 2112 delegatesForEvent = delegates[id][eventName] = delegates[id][eventName] || {}; 2113 // increment the count or set it to 1 2114 delegatesForEvent[selector] = delegatesForEvent[selector] + 1 || 1; 2115 } 2116 }; 2117 2118 /** 2119 @name glow.events._unregisterDelegate 2120 @private 2121 @function 2122 @description Unregister a delegated event 2123 2124 @param {glow.NodeList} nodeList Elements to unregister 2125 @param {string} eventName 2126 @param {string} selector Selector to match for the delegate 2127 */ 2128 events._unregisterDelegate = function(nodeList, eventName, selector) { 2129 var id, 2130 selectorCounts, 2131 i = nodeList.length; 2132 2133 while (i--) { 2134 id = _getPrivateEventKey( nodeList[i] ); 2135 if ( !delegates[id] || !( selectorCounts = delegates[id][eventName] ) ) { continue; } 2136 2137 // either decrement the count or delete the entry 2138 if ( selectorCounts[selector] && --selectorCounts[selector] === 0 ) { 2139 delete selectorCounts[selector]; 2140 } 2141 } 2142 }; 2143 2144 /** 2145 @name glow.events._getDelegateMatches 2146 @private 2147 @function 2148 @description Get the elements which qualify for a delegated event 2149 2150 @param {HTMLElement} element Element the listener is attached to 2151 @param {string} eventName 2152 @param {glow.events.DomEvent} event DOM event for the original event 2153 The events source will be used as a place to start searching 2154 2155 @returns {Array[]} An array of arrays like [matchedNode, selectorMatched] 2156 */ 2157 var _getDelegateMatches = events._getDelegateMatches = function(element, eventName, event) { 2158 var id = _getPrivateEventKey(element), 2159 selectorCounts, 2160 selector, 2161 node, 2162 r = []; 2163 2164 // get delegated listeners 2165 if ( delegates[id] && ( selectorCounts = delegates[id][eventName] ) ) { 2166 for (selector in selectorCounts) { 2167 node = event.source; 2168 // if the source matches the selector 2169 while (node && node !== element) { 2170 if (glow._sizzle.matches( selector, [node] ).length) { 2171 r.push( [node, selector] ); 2172 break; 2173 } 2174 2175 node = node.parentNode; 2176 } 2177 } 2178 } 2179 return r; 2180 } 2181 2182 /** 2183 @name glow.events._callDomListeners 2184 @private 2185 @function 2186 @description Call delegated listeners and normal listeners for an event 2187 Events that don't bubble (like mouseenter and mouseleave) need 2188 to handle their own delegation rather than use this. 2189 2190 @param {HTMLElement} element Element to fire event on 2191 @param {string} eventName 2192 @param {glow.events.DomEvent} event 2193 2194 @returns {glow.events.DomEvent} Original event passed in 2195 */ 2196 var _callDomListeners = events._callDomListeners = function(element, eventName, event) { 2197 var delegateMatches = _getDelegateMatches(element, eventName, event); 2198 2199 // call delegated listeners 2200 for (var i = 0, leni = delegateMatches.length; i < leni; i++) { 2201 event.attachedTo = delegateMatches[i][0]; 2202 _callListeners( element, eventName + '/' + delegateMatches[i][1], event, delegateMatches[i][0] ); 2203 } 2204 2205 // call non-delegated listeners 2206 event.attachedTo = element; 2207 _callListeners(element, eventName, event); 2208 2209 return event; 2210 } 2211 2212 // export 2213 events.DomEvent = DomEvent; 2214 }); 2215 Glow.provide(function(glow) { 2216 var document = window.document, 2217 undefined, 2218 keyboardEventProto, 2219 env = glow.env, 2220 // the keyCode for the last keydown (returned to undefined on keyup) 2221 activeKey, 2222 // the charCode for the last keypress (returned to undefined on keyup & keydown) 2223 activeChar, 2224 DomEvent = glow.events.DomEvent, 2225 _callDomListeners = glow.events._callDomListeners, 2226 _getPrivateEventKey = glow.events._getPrivateEventKey, 2227 // object of event names & listeners, eg: 2228 // { 2229 // eventId: [ 2230 // 2, // the number of glow listeners added for this node 2231 // keydownListener, 2232 // keypressListener, 2233 // keyupListener 2234 // ] 2235 // } 2236 // This lets us remove these DOM listeners from the node when the glow listeners reaches zero 2237 eventKeysRegistered = {}; 2238 2239 /** 2240 @name glow.events.KeyboardEvent 2241 @constructor 2242 @extends glow.events.DomEvent 2243 2244 @description Describes a keyboard event. 2245 You don't need to create instances of this class if you're simply 2246 listening to events. One will be provided as the first argument 2247 in your callback. 2248 2249 @param {Event} nativeEvent A native browser event read properties from. 2250 2251 @param {Object} [properties] Properties to add to the Event instance. 2252 Each key-value pair in the object will be added to the Event as 2253 properties. 2254 */ 2255 function KeyboardEvent(nativeEvent) { 2256 if (activeKey) { 2257 this.key = keyCodeToId(activeKey); 2258 } 2259 if (activeChar) { 2260 this.keyChar = String.fromCharCode(activeChar); 2261 } 2262 DomEvent.call(this, nativeEvent); 2263 } 2264 2265 glow.util.extend(KeyboardEvent, DomEvent, { 2266 /** 2267 @name glow.events.KeyboardEvent#key 2268 @type {string} 2269 @description The key pressed 2270 This is a string representing the key pressed. 2271 2272 Alphanumeric keys are represented by 0-9 and a-z (always lowercase). Other safe cross-browser values are: 2273 2274 <ul> 2275 <li>backspace</li> 2276 <li>tab</li> 2277 <li>return</li> 2278 <li>shift</li> 2279 <li>alt</li> 2280 <li>escape</li> 2281 <li>space</li> 2282 <li>pageup</li> 2283 <li>pagedown</li> 2284 <li>end</li> 2285 <li>home</li> 2286 <li>left</li> 2287 <li>up</li> 2288 <li>right</li> 2289 <li>down</li> 2290 <li>insert</li> 2291 <li>delete</li> 2292 <li>;</li> 2293 <li>=</li> 2294 <li>-</li> 2295 <li>f1</li> 2296 <li>f2</li> 2297 <li>f3</li> 2298 <li>f4</li> 2299 <li>f5</li> 2300 <li>f6</li> 2301 <li>f7</li> 2302 <li>f8</li> 2303 <li>f9</li> 2304 <li>f10</li> 2305 <li>f11</li> 2306 <li>f12</li> 2307 <li>numlock</li> 2308 <li>scrolllock</li> 2309 <li>pause</li> 2310 <li>,</li> 2311 <li>.</li> 2312 <li>/</li> 2313 <li>[</li> 2314 <li>\</li> 2315 <li>]</li> 2316 </ul> 2317 2318 Some keys may trigger actions in your browser and operating system, some 2319 are not cancelable. 2320 2321 @example 2322 glow(document).on('keypress', function(event) { 2323 switch (event.key) { 2324 case 'up': 2325 // do stuff 2326 break; 2327 case 'down': 2328 // do stuff 2329 break; 2330 } 2331 }); 2332 */ 2333 key: '', 2334 /** 2335 @name glow.events.KeyboardEvent#keyChar 2336 @type {string} 2337 @description The character entered. 2338 This is only available during 'keypress' events. 2339 2340 If the user presses shift and 1, event.key will be "1", but event.keyChar 2341 will be "!". 2342 2343 @example 2344 // only allow numbers to be entered into the ageInput field 2345 glow('#ageInput').on('keypress', function(event) { 2346 // Convert keyChar to a number and see if we get 2347 // a valid number back 2348 return !isNaN( Number(event.keyChar) ); 2349 }); 2350 */ 2351 keyChar: '' 2352 }); 2353 2354 /** 2355 @private 2356 @description Add a listener onto a DOM element 2357 2358 @param {HTMLElement} elm 2359 @param {string} name Event name, without 'on' at the start 2360 @param {function} callback Callback for the event 2361 */ 2362 function addListener(elm, name, callback) { 2363 if (elm.addEventListener) { // like DOM2 browsers 2364 elm.addEventListener(name, callback, false); 2365 } 2366 else if (elm.attachEvent) { // like IE 2367 elm.attachEvent('on' + name, callback); 2368 } 2369 } 2370 2371 /** 2372 @private 2373 @description Removes a listener onto a DOM element 2374 2375 @param {HTMLElement} elm 2376 @param {string} name Event name, without 'on' at the start 2377 @param {function} callback Callback for the event 2378 */ 2379 function removeListener(elm, name, callback) { 2380 if (elm.removeEventListener) { // like DOM2 browsers 2381 elm.removeEventListener(name, callback, false); 2382 } 2383 else if (elm.detachEvent) { // like IE 2384 elm.detachEvent('on' + name, callback); 2385 } 2386 } 2387 2388 /** 2389 @private 2390 @description Do we expect the browser to fire a keypress after a given keydown? 2391 Also fills in activeChar for webkit. 2392 2393 @param {number} keyCode The keyCode from a keydown listener. 2394 @param {boolean} defaultPrevented Was the keydown prevented? 2395 */ 2396 function expectKeypress(keyCode, defaultPrevented) { 2397 var keyName; 2398 2399 // for browsers that fire keypress for the majority of keys 2400 if (env.gecko || env.opera || env.webkit < 525) { 2401 return !noKeyPress[keyCode]; 2402 } 2403 2404 // for browsers that only fire keypress for printable chars 2405 keyName = keyCodeToId(keyCode); 2406 2407 // is this a printable char? 2408 if (keyName.length === 1 || keyName === 'tab' || keyName === 'space') { 2409 // webkit doesn't fire keypress if the keydown has been prevented 2410 // take a good guess at the active char for webkit 2411 activeChar = ( keyNameToChar[keyName] || keyName ).charCodeAt(0); 2412 return !(env.webkit && defaultPrevented); 2413 } 2414 return false; 2415 } 2416 2417 /** 2418 @private 2419 @description Add the key listeners for firing glow's normalised key events. 2420 2421 @param {HTMLElement} attachTo Element to attach listeners to. 2422 2423 @returns {Object[]} An entry for eventKeysRegistered. 2424 */ 2425 function addDomKeyListeners(attachTo) { 2426 var keydownHandler, 2427 keypressHandler, 2428 keyupHandler, 2429 // Even though the user may only be interested in one key event, 2430 // we need all 3 listeners to normalise any of them. 2431 // Hash of which keys are down, keyed by keyCode 2432 // Like: {123: true, 124: false} 2433 keysDown = {}; 2434 2435 keydownHandler = function(nativeEvent) { 2436 var keyCode = nativeEvent.keyCode, 2437 preventDefault, 2438 preventDefaultKeyPress; 2439 2440 // some browsers repeat this event while a key is held down, we don't want to do that 2441 if ( !keysDown[keyCode] ) { 2442 activeKey = keyCode; 2443 activeChar = undefined; 2444 preventDefault = _callDomListeners( attachTo, 'keydown', new KeyboardEvent(nativeEvent) ).defaultPrevented(); 2445 keysDown[keyCode] = true; 2446 } 2447 // we want to fire a keyPress event here if the browser isn't going to fire one itself 2448 if ( !expectKeypress(keyCode, preventDefault) ) { 2449 preventDefaultKeyPress = _callDomListeners( attachTo, 'keypress', new KeyboardEvent(nativeEvent) ).defaultPrevented(); 2450 } 2451 // return false if either the keydown or fake keypress event was cancelled 2452 return !(preventDefault || preventDefaultKeyPress); 2453 }; 2454 2455 keypressHandler = function(nativeEvent) { 2456 var keyName, preventDefault; 2457 // some browsers store the charCode in .charCode, some in .keyCode 2458 activeChar = nativeEvent.charCode || nativeEvent.keyCode; 2459 keyName = keyCodeToId(activeKey); 2460 2461 // some browsers fire this event for non-printable chars, look at the previous keydown and see if we're expecting a printable char 2462 if ( keyName.length > 1 && keyName !== 'tab' && keyName !== 'space' ) { 2463 // non-printable chars usually have an ID length greater than 1 2464 activeChar = undefined; 2465 } 2466 2467 preventDefault = _callDomListeners( attachTo, 'keypress', new KeyboardEvent(nativeEvent) ).defaultPrevented(); 2468 return !preventDefault; 2469 }; 2470 2471 keyupHandler = function(nativeEvent) { 2472 var keyCode = nativeEvent.keyCode, 2473 preventDefault; 2474 2475 // set the active key so KeyboardEvent picks it up 2476 activeKey = keyCode; 2477 activeChar = undefined; 2478 preventDefault = _callDomListeners( attachTo, 'keyup', new KeyboardEvent(nativeEvent) ).defaultPrevented(); 2479 keysDown[keyCode] = false; 2480 activeKey = undefined; 2481 return !preventDefault; 2482 }; 2483 2484 // add listeners to the dom 2485 addListener(attachTo, 'keydown', keydownHandler); 2486 addListener(attachTo, 'keypress', keypressHandler); 2487 addListener(attachTo, 'keyup', keyupHandler); 2488 2489 return [1, keydownHandler, keypressHandler, keyupHandler]; 2490 } 2491 2492 /** 2493 @name glow.events._addKeyListener 2494 @private 2495 @function 2496 @description Add DOM listeners for key events fired by the browser. 2497 Won't add more than one. 2498 2499 @param {glow.NodeList} nodeList Elements to add listeners to. 2500 2501 @see glow.NodeList#on 2502 */ 2503 glow.events._addKeyListener = function(nodeList) { 2504 var i = nodeList.length, 2505 attachTo, 2506 eventKey; 2507 2508 while (i--) { 2509 attachTo = nodeList[i]; 2510 2511 // get the ID for this event 2512 eventKey = _getPrivateEventKey(attachTo); 2513 2514 // if we've already attached DOM listeners for this, don't add them again 2515 if ( eventKeysRegistered[eventKey] ) { 2516 // increment the number of things listening to this 2517 // This lets us remove these DOM listeners from the node when 2518 // the glow listeners reaches zero 2519 eventKeysRegistered[eventKey][0]++; 2520 continue; 2521 } 2522 else { 2523 eventKeysRegistered[eventKey] = addDomKeyListeners(attachTo); 2524 } 2525 } 2526 } 2527 2528 /** 2529 @name glow.events._removeKeyListener 2530 @private 2531 @function 2532 @description Remove DOM listeners for key events fired by the browser 2533 Avoids removing DOM listeners until all Glow listeners have been removed 2534 2535 @param {glow.NodeList} nodeList Elements to remove listeners from 2536 2537 @see glow.NodeList#detach 2538 */ 2539 glow.events._removeKeyListener = function(nodeList) { 2540 var i = nodeList.length, 2541 attachTo, 2542 eventKey, 2543 eventRegistry; 2544 2545 while (i--) { 2546 attachTo = nodeList[i]; 2547 2548 // get the ID for this event 2549 eventKey = _getPrivateEventKey(attachTo); 2550 eventRegistry = eventKeysRegistered[eventKey]; 2551 // exist if there are no key events registered for this node 2552 if ( !eventRegistry ) { 2553 continue; 2554 } 2555 if ( --eventRegistry[0] === 0 ) { 2556 // our glow listener count is zero, we have no need for the dom listeners anymore 2557 removeListener( attachTo, 'keydown', eventRegistry[1] ); 2558 removeListener( attachTo, 'keypress', eventRegistry[2] ); 2559 removeListener( attachTo, 'keyup', eventRegistry[3] ); 2560 eventKeysRegistered[eventKey] = undefined; 2561 } 2562 } 2563 } 2564 /** 2565 @private 2566 @function 2567 @description convert a keyCode to a string name for that key 2568 2569 @param {number} keyCode 2570 2571 @returns {string} ID for that key. Is a letter a-z, number 0-9, or id from 'keyIds' 2572 */ 2573 function keyCodeToId(keyCode) { 2574 // key codes for 0-9 A-Z are the same as their char codes 2575 if ( (keyCode >= keyCodeA && keyCode <= keyCodeZ) || (keyCode >= keyCode0 && keyCode <= keyCode9) ) { 2576 return String.fromCharCode(keyCode).toLowerCase(); 2577 } 2578 return keyIds[keyCode] || 'unknown' + keyCode; 2579 } 2580 2581 // keyCode to key name translation 2582 var keyCodeA = 65, 2583 keyCodeZ = 90, 2584 keyCode0 = 48, 2585 keyCode9 = 57, 2586 // key codes for non-alphanumeric keys 2587 keyIds = { 2588 8: 'backspace', 2589 9: 'tab', 2590 13: 'return', 2591 16: 'shift', 2592 17: 'control', 2593 18: 'alt', 2594 19: 'pause', 2595 27: 'escape', 2596 32: 'space', 2597 33: 'pageup', 2598 34: 'pagedown', 2599 35: 'end', 2600 36: 'home', 2601 37: 'left', 2602 38: 'up', 2603 39: 'right', 2604 40: 'down', 2605 //44: 'printscreen', // Only fires keyup in firefox, IE. Doesn't fire in webkit, opera. 2606 45: 'insert', 2607 46: 'delete', 2608 59: ';', 2609 61: '=', 2610 //91: 'meta', 2611 //93: 'menu', // no keycode in opera, doesn't fire in Chrome 2612 2613 // these are number pad numbers, but Opera doesn't distinguish them from normal number keys so we normalise on that 2614 96: '0', 2615 97: '1', 2616 98: '2', 2617 99: '3', 2618 100: '4', 2619 101: '5', 2620 102: '6', 2621 103: '7', 2622 104: '8', 2623 105: '9', 2624 //106: '*', // opera fires 2 keypress events 2625 //107: '+', // opera fires 2 keypress events 2626 109: '-', // opera sees - as insert, but firefox 3.0 see the normal - key the same as the numpad one 2627 //110: '.', // opera sees this as n 2628 111: '/', 2629 // end of numpad 2630 2631 112: 'f1', 2632 113: 'f2', 2633 114: 'f3', 2634 115: 'f4', 2635 116: 'f5', 2636 117: 'f6', 2637 118: 'f7', 2638 119: 'f8', 2639 120: 'f9', 2640 121: 'f10', 2641 122: 'f11', 2642 123: 'f12', 2643 144: 'numlock', 2644 145: 'scrolllock', 2645 188: ',', 2646 189: '-', 2647 190: '.', 2648 191: '/', 2649 192: "'", 2650 219: '[', 2651 220: '\\', 2652 221: ']', 2653 222: '#', // opera sees # key as 3. Pah. 2654 223: '`', 2655 //224: 'meta', // same as [ in opera 2656 226: '\\' // this key appears on a US layout in webkit windows 2657 }, 2658 // converting key names to chars, for key names greater than 1 char 2659 keyNameToChar = { 2660 space: ' ', 2661 tab: '\t' 2662 } 2663 noKeyPress = {}; 2664 2665 // corrections for particular browsers :( 2666 if (env.gecko) { 2667 keyIds[107] = '='; 2668 2669 noKeyPress = { 2670 16: 1, // shift 2671 17: 1, // control 2672 18: 1, // alt 2673 144: 1, // numlock 2674 145: 1 // scrolllock 2675 }; 2676 } 2677 else if (env.opera) { 2678 keyIds[42] = '*'; 2679 keyIds[43] = '+'; 2680 keyIds[47] = '/'; 2681 keyIds[222] = "'"; 2682 keyIds[192] = '`'; 2683 2684 noKeyPress = { 2685 16: 1, // shift 2686 17: 1, // control 2687 18: 1 // alt 2688 }; 2689 } 2690 else if (env.webkit || env.ie) { 2691 keyIds[186] = ';'; 2692 keyIds[187] = '='; 2693 } 2694 2695 // export 2696 glow.events.KeyboardEvent = KeyboardEvent; 2697 }); 2698 Glow.provide(function(glow) { 2699 var NodeListProto, undefined, 2700 // shortcuts to aid compression 2701 document = window.document, 2702 arraySlice = Array.prototype.slice, 2703 arrayPush = Array.prototype.push; 2704 2705 /** 2706 @name glow.NodeList 2707 @constructor 2708 @description An array-like collection of DOM Nodes 2709 It is recommended to create a NodeList using the shortcut function {@link glow}. 2710 2711 @param {string | glow.NodeList | Node | Node[] | Window} contents Items to populate the NodeList with. 2712 This parameter will be passed to {@link glow.NodeList#push}. 2713 2714 Strings will be treated as CSS selectors unless they start with '<', in which 2715 case they'll be treated as an HTML string. 2716 2717 @example 2718 // empty NodeList 2719 var myNodeList = glow(); 2720 2721 @example 2722 // using glow to return a NodeList then chaining methods 2723 glow('p').addClass('eg').append('<div>Hello!</div>'); 2724 2725 @example 2726 // creating an element from a string 2727 glow('<div>Hello!</div>').appendTo('body'); 2728 2729 @see <a href="http://wiki.github.com/jeresig/sizzle/">Supported CSS selectors</a> 2730 */ 2731 function NodeList(contents) { 2732 // call push if we've been given stuff to add 2733 contents && this.push(contents); 2734 } 2735 NodeListProto = NodeList.prototype; 2736 2737 /** 2738 @name glow.NodeList#length 2739 @type Number 2740 @description Number of nodes in the NodeList 2741 @example 2742 // get the number of paragraphs on the page 2743 glow('p').length; 2744 */ 2745 NodeListProto.length = 0; 2746 2747 /** 2748 @name glow.NodeList._strToNodes 2749 @private 2750 @function 2751 @description Converts a string to an array of nodes 2752 2753 @param {string} str HTML string 2754 2755 @returns {Node[]} Array of nodes (including text / comment nodes) 2756 */ 2757 NodeList._strToNodes = (function() { 2758 var tmpDiv = document.createElement('div'), 2759 // these wraps are in the format [depth to children, opening html, closing html] 2760 tableWrap = [1, '<table>', '</table>'], 2761 emptyWrap = [0, '', ''], 2762 // Easlier Webkits won't accept <link> & <style> elms to be the only child of an element, 2763 // it steals them and hides them in the head for some reason. Using 2764 // broken html fixes it for some reason 2765 paddingWrap = glow.env.webkit < 526 ? [0, '', '</div>'] : [1, 'b<div>', '</div>'], 2766 trWrap = [3, '<table><tbody><tr>', '</tr></tbody></table>'], 2767 wraps = { 2768 caption: tableWrap, 2769 thead: tableWrap, 2770 th: trWrap, 2771 colgroup: tableWrap, 2772 tbody: tableWrap, 2773 tr: [2, '<table><tbody>', '</tbody></table>'], 2774 td: trWrap, 2775 tfoot: tableWrap, 2776 option: [1, '<select multiple="multiple">', '</select>'], 2777 legend: [1, '<fieldset>', '</fieldset>'], 2778 link: paddingWrap, 2779 script: paddingWrap, 2780 style: paddingWrap, 2781 '!': paddingWrap 2782 }; 2783 2784 function strToNodes(str) { 2785 var r = [], 2786 tagName = ( /^<([\w!]+)/.exec(str) || [] )[1], 2787 // This matches str content with potential elements that cannot 2788 // be a child of <div>. elmFilter declared at top of page. 2789 wrap = wraps[tagName] || emptyWrap, 2790 nodeDepth = wrap[0], 2791 childElm = tmpDiv, 2792 exceptTbody, 2793 rLen = 0, 2794 firstChild; 2795 2796 // Create the new element using the node tree contents available in filteredElm. 2797 childElm.innerHTML = (wrap[1] + str + wrap[2]); 2798 2799 // Strip newElement down to just the required elements' parent 2800 while(nodeDepth--) { 2801 childElm = childElm.lastChild; 2802 } 2803 2804 // pull nodes out of child 2805 if (wrap === tableWrap && str.indexOf('<tbody') === -1) { 2806 // IE7 (and earlier) sometimes gives us a <tbody> even though we didn't ask for one 2807 while (firstChild = childElm.firstChild) { 2808 if (firstChild.nodeName != 'TBODY') { 2809 r[rLen++] = firstChild; 2810 } 2811 childElm.removeChild(firstChild); 2812 } 2813 } 2814 else { 2815 while (firstChild = childElm.firstChild) { 2816 r[rLen++] = childElm.removeChild(firstChild); 2817 } 2818 } 2819 2820 return r; 2821 } 2822 2823 return strToNodes; 2824 })(); 2825 2826 // takes a collection and returns an array 2827 var collectionToArray = function(collection) { 2828 return arraySlice.call(collection, 0); 2829 }; 2830 2831 try { 2832 // look out for an IE bug 2833 arraySlice.call( document.documentElement.childNodes, 0 ); 2834 } 2835 catch(e) { 2836 collectionToArray = function(collection) { 2837 // We can't use this trick on IE collections that are com-based, like HTMLCollections 2838 // Thankfully they don't have a constructor, so that's how we detect those 2839 if (collection instanceof Object) { 2840 return arraySlice.call(collection, 0); 2841 } 2842 var i = collection.length, 2843 arr = []; 2844 2845 while (i--) { 2846 arr[i] = collection[i]; 2847 } 2848 return arr; 2849 } 2850 } 2851 2852 /** 2853 @name glow.NodeList#push 2854 @function 2855 @description Adds nodes to the NodeList 2856 2857 @param {string | Node | Node[] | glow.NodeList} nodes Node(s) to add to the NodeList 2858 Strings will be treated as CSS selectors or HTML strings. 2859 2860 @returns {glow.NodeList} 2861 2862 @example 2863 myNodeList.push('<div>Foo</div>').push('h1'); 2864 */ 2865 NodeListProto.push = function(nodes) { 2866 /*!debug*/ 2867 if (arguments.length !== 1) { 2868 glow.debug.warn('[wrong count] glow.NodeList#push expects 1 argument, not '+arguments.length+'.'); 2869 } 2870 /*gubed!*/ 2871 2872 if (nodes) { 2873 if (typeof nodes === 'string') { 2874 // if the string begins <, treat it as html, otherwise it's a selector 2875 if (nodes.charAt(0) === '<') { 2876 nodes = NodeList._strToNodes(nodes); 2877 } 2878 else { 2879 nodes = glow._sizzle(nodes) 2880 } 2881 arrayPush.apply(this, nodes); 2882 } 2883 2884 else if ( nodes.nodeType || nodes.window == nodes ) { 2885 if (this.length) { 2886 arrayPush.call(this, nodes); 2887 } 2888 else { 2889 this[0] = nodes; 2890 this.length = 1; 2891 } 2892 } 2893 else if (nodes.length !== undefined) { 2894 if (nodes.constructor != Array) { 2895 // convert array-like objects into an array 2896 nodes = collectionToArray(nodes); 2897 } 2898 arrayPush.apply(this, nodes); 2899 } 2900 /*!debug*/ 2901 else { 2902 glow.debug.warn('[wrong type] glow.NodeList#push: Ignoring unexpected argument type, failing silently'); 2903 } 2904 /*gubed!*/ 2905 } 2906 /*!debug*/ 2907 else { 2908 glow.debug.warn('[wrong type] glow.NodeList#push: Ignoring false argument type, failing silently'); 2909 } 2910 /*gubed!*/ 2911 return this; 2912 }; 2913 2914 /** 2915 @name glow.NodeList#eq 2916 @function 2917 @description Compares this NodeList to another 2918 Returns true if both NodeLists contain the same items in the same order 2919 2920 @param {Node | Node[] | glow.NodeList} nodeList The NodeList to compare to. 2921 2922 @returns {boolean} 2923 2924 @see {@link glow.NodeList#is} for testing if a NodeList item matches a selector 2925 2926 @example 2927 // the following returns true 2928 glow('#blah').eq( document.getElementById('blah') ); 2929 */ 2930 NodeListProto.eq = function(nodeList) { 2931 /*!debug*/ 2932 if (arguments.length !== 1) { 2933 glow.debug.warn('[wrong count] glow.NodeList#eq expects 1 argument, not ' + arguments.length + '.'); 2934 } 2935 if (typeof nodeList !== 'object') { 2936 glow.debug.warn('[wrong type] glow.NodeList#eq expects object argument, not ' + typeof nodeList + '.'); 2937 } 2938 /*gubed!*/ 2939 2940 var len = this.length, 2941 i = len; 2942 2943 // normalise param to NodeList 2944 if ( !(nodeList instanceof NodeList) ) { 2945 nodeList = new NodeList(nodeList); 2946 } 2947 2948 // quickly return false if lengths are different 2949 if (len != nodeList.length) { 2950 return false; 2951 } 2952 2953 // loop through and return false on inequality 2954 while (i--) { 2955 if (this[i] !== nodeList[i]) { 2956 return false; 2957 } 2958 } 2959 2960 return true; 2961 }; 2962 2963 /** 2964 @name glow.NodeList#slice 2965 @function 2966 @description Get a section of an NodeList 2967 Operates in the same way as an Array's slice method 2968 2969 @param {number} start Start index 2970 If negative, it specifies a position measured from the end of the list 2971 2972 @param {number} [end] End index 2973 By default, this is the end of the list. A negative end specifies 2974 a position measured from the end of the list. 2975 2976 @returns {glow.NodeList} A new sliced NodeList 2977 2978 @example 2979 var myNodeList = glow("<div></div><p></p>"); 2980 myNodeList.slice(1, 2); // selects the paragraph 2981 myNodeList.slice(-1); // same thing, selects the paragraph 2982 */ 2983 NodeListProto.slice = function(/*start, end*/) { 2984 return new NodeList( arraySlice.apply(this, arguments) ); 2985 }; 2986 2987 /** 2988 @name glow.NodeList#sort 2989 @function 2990 @description Sort the elements in the list. 2991 Items will already be in document order if a CSS selector 2992 was used to fetch them. 2993 2994 @param {Function} [func] Function to determine sort order 2995 This function will be passed 2 elements (elementA, elementB). The function 2996 should return a number less than 0 to sort elementA lower than elementB 2997 and greater than 0 to sort elementA higher than elementB. 2998 2999 If no function is provided, elements will be sorted in document order. 3000 3001 @returns {glow.NodeList} A new sorted NodeList 3002 3003 @example 3004 //get links in alphabetical (well, lexicographical) order 3005 var links = glow("a").sort(function(elementA, elementB) { 3006 return glow(elementA).text() < glow(elementB).text() ? -1 : 1; 3007 }) 3008 */ 3009 NodeListProto.sort = function(func) { 3010 var items = collectionToArray(this), 3011 sortedElms = func ? items.sort(func) : glow._sizzle.uniqueSort(items); 3012 3013 return new NodeList(sortedElms); 3014 }; 3015 3016 /** 3017 @name glow.NodeList#item 3018 @function 3019 @description Get a single item from the list as an NodeList 3020 Negative numbers can be used to get items from the end of the 3021 list. 3022 3023 @param {number} index The numeric index of the node to return. 3024 3025 @returns {glow.NodeList} A new NodeList containing a single item 3026 3027 @example 3028 // get the html from the fourth element 3029 myNodeList.item(3).html(); 3030 3031 @example 3032 // add a class name to the last item 3033 myNodeList.item(-1).addClass('last'); 3034 */ 3035 NodeListProto.item = function(index) { 3036 /*!debug*/ 3037 if ( arguments.length !== 1 ) { 3038 glow.debug.warn('[wrong count] glow.NodeList#item expects 1 argument, got ' + arguments.length); 3039 } 3040 /*gubed!*/ 3041 // TODO: test which of these methods is faster (use the current one unless significantly slower) 3042 return this.slice(index, (index + 1) || this.length); 3043 // return new NodeList( index < 0 ? this[this.length + index] : this[index] ); 3044 }; 3045 3046 /** 3047 @name glow.NodeList#each 3048 @function 3049 @description Calls a function for each node in the list. 3050 3051 @param {Function} callback The function to call for each node. 3052 The function will be passed 2 arguments, the index of the current item, 3053 and the NodeList being iterated over. 3054 3055 Inside the function 'this' refers to the Node. 3056 3057 Returning false from this function stops further iterations 3058 3059 @returns {glow.NodeList} 3060 3061 @example 3062 // add "link number: x" to each link, where x is the index of the link 3063 glow("a").each(function(i, nodeList) { 3064 glow(this).append(' link number: ' + i); 3065 }); 3066 @example 3067 // breaking out of an each loop 3068 glow("a").each(function(i, nodeList) { 3069 // do stuff 3070 if ( glow(this).hasClass('whatever') ) { 3071 // we don't want to process any more links 3072 return false; 3073 } 3074 }); 3075 */ 3076 NodeListProto.each = function(callback) { 3077 /*!debug*/ 3078 if ( arguments.length !== 1 ) { 3079 glow.debug.warn('[wrong count] glow.NodeList#each expects 1 argument, got ' + arguments.length); 3080 } 3081 if (typeof callback != 'function') { 3082 glow.debug.warn('[wrong type] glow.NodeList#each expects "function", got ' + typeof callback); 3083 } 3084 /*gubed!*/ 3085 for (var i = 0, len = this.length; i<len; i++) { 3086 if ( callback.call(this[i], i, this) === false ) { 3087 break; 3088 } 3089 } 3090 return this; 3091 }; 3092 3093 /** 3094 @name glow.NodeList#filter 3095 @function 3096 @description Filter the NodeList 3097 3098 @param {Function|string} test Filter test 3099 If a string is provided it's treated as a CSS selector. Elements 3100 which match the CSS selector are added to the new NodeList. 3101 3102 If 'test' is a function, it will be called per node in the NodeList. 3103 3104 The function is passed 2 arguments, the index of the current item, 3105 and the ElementList being itterated over. 3106 3107 Inside the function 'this' refers to the node. 3108 Return true to add the element to the new NodeList. 3109 3110 @returns {glow.NodeList} A new NodeList containing the filtered nodes 3111 3112 @example 3113 // return images with a width greater than 320 3114 glow("img").filter(function () { 3115 return glow(this).width() > 320; 3116 }); 3117 3118 @example 3119 // Get items that don't have an alt attribute 3120 myElementList.filter(':not([alt])'); 3121 */ 3122 NodeListProto.filter = function(test) { 3123 /*!debug*/ 3124 if ( arguments.length !== 1 ) { 3125 glow.debug.warn('[wrong count] glow.NodeList#filter expects 1 argument, got ' + arguments.length); 3126 } 3127 if ( !/^(function|string)$/.test(typeof test) ) { 3128 glow.debug.warn('[wrong type] glow.NodeList#each expects function/string, got ' + typeof test); 3129 } 3130 /*gubed!*/ 3131 var r = [], 3132 ri = 0; 3133 3134 if (typeof test === 'string') { 3135 r = glow._sizzle.matches(test, this); 3136 } 3137 else { 3138 for (var i = 0, len = this.length; i<len; i++) { 3139 if ( test.call(this[i], i, this) ) { 3140 r[ri++] = this[i]; 3141 } 3142 } 3143 } 3144 3145 return new NodeList(r); 3146 }; 3147 3148 3149 /** 3150 @name glow.NodeList#is 3151 @function 3152 @description Tests if the first element matches a CSS selector 3153 3154 @param {string} selector CSS selector 3155 3156 @returns {boolean} 3157 3158 @example 3159 if ( myNodeList.is(':visible') ) { 3160 // ... 3161 } 3162 */ 3163 NodeListProto.is = function(selector) { 3164 /*!debug*/ 3165 if ( arguments.length !== 1 ) { 3166 glow.debug.warn('[wrong count] glow.NodeList#is expects 1 argument, got ' + arguments.length); 3167 } 3168 if ( typeof selector !== 'string' ) { 3169 glow.debug.warn('[wrong type] glow.NodeList#is expects string, got ' + typeof selector); 3170 } 3171 /*gubed!*/ 3172 if ( !this[0] ) { 3173 return false; 3174 } 3175 return !!glow._sizzle.matches( selector, [ this[0] ] ).length; 3176 }; 3177 3178 // export 3179 glow.NodeList = NodeList; 3180 }); 3181 Glow.provide(function(glow) { 3182 var undef 3183 , NodeListProto = glow.NodeList.prototype 3184 3185 /** 3186 @private 3187 @name glow.NodeList-dom0PropertyMapping 3188 @description Mapping of HTML attribute names to DOM0 property names. 3189 */ 3190 , dom0PropertyMapping = { // keys must be lowercase 3191 'class' : 'className', 3192 'for' : 'htmlFor', 3193 'maxlength' : 'maxLength' 3194 } 3195 3196 /** 3197 @private 3198 @name glow.NodeList-dataPropName 3199 @type String 3200 @description The property name added to the DomElement by the NodeList#data method. 3201 */ 3202 , dataPropName = '_uniqueData' + glow.UID 3203 3204 /** 3205 @private 3206 @name glow.NodeList-dataIndex 3207 @type String 3208 @description The value of the dataPropName added by the NodeList#data method. 3209 */ 3210 , dataIndex = 1 // must be a truthy value 3211 3212 /** 3213 @private 3214 @name glow.NodeList-dataCache 3215 @type Object 3216 @description Holds the data used by the NodeList#data method. 3217 3218 The structure is like: 3219 [ 3220 { 3221 myKey: "my data" 3222 } 3223 ] 3224 */ 3225 , dataCache = []; 3226 3227 /** 3228 @name glow.NodeList#addClass 3229 @function 3230 @description Adds a class to each node. 3231 3232 @param {string} name The name of the class to add. 3233 3234 @returns {glow.NodeList} 3235 3236 @example 3237 glow("#login a").addClass("highlight"); 3238 */ 3239 NodeListProto.addClass = function(name) { 3240 var i = this.length; 3241 3242 /*!debug*/ 3243 if (arguments.length !== 1) { 3244 glow.debug.warn('[wrong count] glow.NodeList#addClass expects 1 argument, not '+arguments.length+'.'); 3245 } 3246 else if (typeof arguments[0] !== 'string') { 3247 glow.debug.warn('[wrong type] glow.NodeList#addClass expects argument 1 to be of type string, not '+typeof arguments[0]+'.'); 3248 } 3249 /*gubed!*/ 3250 3251 while (i--) { 3252 if (this[i].nodeType === 1) { 3253 _addClass(this[i], name); 3254 } 3255 } 3256 3257 return this; 3258 }; 3259 3260 function _addClass(node, name) { // TODO: handle classnames separated by non-space characters? 3261 if ( (' ' + node.className + ' ').indexOf(' ' + name + ' ') === -1 ) { 3262 node.className += (node.className? ' ' : '') + name; 3263 } 3264 } 3265 3266 /** 3267 @name glow.NodeList#attr 3268 @function 3269 @description Gets or sets attributes. 3270 3271 When getting an attribute, it is retrieved from the first 3272 node in this NodeList. Setting attributes applies the change 3273 to each element in this NodeList. 3274 3275 To set an attribute, pass in the name as the first 3276 parameter and the value as a second parameter. 3277 3278 To set multiple attributes in one call, pass in an object of 3279 name/value pairs as a single parameter. 3280 3281 For browsers that don't support manipulating attributes 3282 using the DOM, this method will try to do the right thing 3283 (i.e. don't expect the semantics of this method to be 3284 consistent across browsers as this is not possible with 3285 currently supported browsers). 3286 3287 @param {string | Object} name The name of the attribute, or an object of name/value pairs 3288 @param {string} [value] The value to set the attribute to. 3289 3290 @returns {string | undefined | glow.NodeList} 3291 3292 When setting attributes this method returns its own NodeList, otherwise 3293 returns the attribute value. The attribute name is always treated as 3294 case-insensitive. When getting, the returned value will be of type string unless 3295 that particular attribute was never set and there is no default value, in which 3296 case the returned value will be an empty string. 3297 3298 @example 3299 var myNodeList = glow(".myImgClass"); 3300 3301 // get an attribute 3302 myNodeList.attr("class"); 3303 3304 // set an attribute 3305 myNodeList.attr("class", "anotherImgClass"); 3306 3307 // set multiple attributes 3308 myNodeList.attr({ 3309 src: "a.png", 3310 alt: "Cat jumping through a field" 3311 }); 3312 */ 3313 // see: http://tobielangel.com/2007/1/11/attribute-nightmare-in-ie/ 3314 NodeListProto.attr = function(/*arguments*/) { 3315 var args = arguments, 3316 argsLen = args.length, 3317 thisLen = this.length, 3318 keyvals, 3319 name = keyvals = args[0], // using this API: attr(name) or attr({key: val}) ? 3320 dom0Property = '', 3321 node, 3322 attrNode; 3323 3324 /*!debug*/ 3325 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]+'.'); } 3326 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.'); } 3327 else if (arguments.length === 0 || arguments.length > 2) { glow.debug.warn('[wrong count] glow.NodeList#attr expects 1 or 2 arguments, not '+arguments.length+'.'); } 3328 /*gubed!*/ 3329 3330 if (this.length === 0) { // is this an empty nodelist? 3331 return (argsLen > 1)? this : undef; 3332 } 3333 3334 if (typeof keyvals === 'object') { // SETting value from {name: value} object 3335 for (name in keyvals) { 3336 if (!keyvals.hasOwnProperty(name)) { continue; } 3337 3338 // in IE6 and IE7 the attribute name needs to be translated into dom property name 3339 if (glow.env.ie < 8) { 3340 dom0Property = dom0PropertyMapping[name.toLowerCase()]; 3341 } 3342 3343 var i = thisLen; 3344 while (i--) { 3345 node = this[i]; 3346 3347 if (node.nodeType !== 1) { continue; } 3348 3349 if (dom0Property) { 3350 node[dom0Property] = keyvals[name]; 3351 } 3352 else { 3353 node.setAttribute(name, keyvals[name], 0); // IE flags, 0: case-insensitive 3354 } 3355 } 3356 } 3357 3358 return this; 3359 } 3360 else { 3361 node = this[0]; 3362 3363 if (node.nodeType !== 1) { 3364 return (argsLen > 1)? this : undef; 3365 } 3366 3367 if (argsLen === 1) { // GETting value from name. see http://reference.sitepoint.com/javascript/Element/getAttribute 3368 if ( glow.env.ie && (name === 'href' || name === 'src') ) { 3369 value = node.getAttribute(name, 2); // IE flags, 0: case-insensitive + 2: exactly as set 3370 return (value === null)? '' : value; 3371 } 3372 else if (node.attributes[name]) { // in IE node.getAttributeNode sometimes returns unspecified default values so we look for specified attributes if we can 3373 return (!node.attributes[name].specified)? '' : node.attributes[name].value; 3374 } 3375 else if (node.getAttributeNode) { // in IE getAttribute() does not always work so we use getAttributeNode if we can 3376 attrNode = node.getAttributeNode(name, 0); 3377 return (attrNode === null)? '' : attrNode.value; 3378 } 3379 else { 3380 value = node.getAttribute(name, 2); // IE flags, 0: case-insensitive + 2: exactly as set 3381 return (value === null)? '' : value; 3382 } 3383 } 3384 else { // SETting a single value like attr(name, value), normalize to an keyval object 3385 if (glow.env.ie < 8) { 3386 dom0Property = dom0PropertyMapping[name.toLowerCase()]; 3387 } 3388 3389 if (dom0Property) { 3390 node[dom0Property] = args[1]; 3391 } 3392 else { 3393 node.setAttribute(name, args[1], 0); // IE flags, 0: case-insensitive 3394 } 3395 return this; 3396 } 3397 } 3398 }; 3399 /** 3400 Copies the data from one nodelist to another 3401 @private 3402 @name glow.NodeList._copyData 3403 @see glow.NodeList#clone 3404 @function 3405 */ 3406 glow.NodeList._copyData = function(from, to){ 3407 var i = to.length, 3408 data; 3409 3410 while (i--) { 3411 data = dataCache[ from[i][dataPropName] ]; 3412 data && to.slice(i, i+1).data(data); 3413 } 3414 } 3415 3416 /** 3417 @name glow.NodeList#data 3418 @function 3419 @description Use this to safely attach arbitrary data to any DOM Element. 3420 3421 This method is useful when you wish to avoid memory leaks that are possible when adding your own data directly to DOM Elements. 3422 3423 When called with no arguments, will return glow's entire data store for the first node in this NodeList. 3424 3425 Otherwise, when given a name, will return the associated value from the first node in this NodeList. 3426 3427 When given both a name and a value, will store that data on every node in this NodeList. 3428 3429 Optionally you can pass in a single object composed of multiple name, value pairs. 3430 3431 @param {string|Object} [key] The name of the value in glow's data store. 3432 @param {Object} [val] The value you wish to associate with the given name. 3433 @see glow.NodeList#removeData 3434 @example 3435 3436 glow("p").data("tea", "milky"); 3437 var colour = glow("p").data("tea"); // milky 3438 @returns {Object} When setting a value this method can be chained, as in that case it will return itself. 3439 @see glow.NodeList#removeData 3440 */ 3441 NodeListProto.data = function (key, val) { /*debug*///console.log("data("+key+", "+val+")"); 3442 var args = arguments, 3443 argsLen = args.length, 3444 keyvals = key, // like: data({key: val}) or data(key, val) 3445 index, 3446 node; 3447 3448 /*!debug*/ 3449 if (arguments.length === 2 && typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#data expects name argument to be of type string.'); } 3450 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.'); } 3451 else if (arguments.length > 2) { glow.debug.warn('[wrong count] glow.NodeList#data expects 0, 1 or 2 arguments.'); } 3452 /*gubed!*/ 3453 3454 if (argsLen > 1) { // SET key, val on every node 3455 var i = this.length; 3456 while (i--) { 3457 node = this[i]; 3458 if (node.nodeType !== 1) { continue; } 3459 3460 index = node[''+dataPropName]; 3461 3462 if (!index) { // assumes index is always > 0 3463 index = dataIndex++; 3464 3465 node[dataPropName] = index; 3466 dataCache[index] = {}; 3467 } 3468 dataCache[index][key] = val; 3469 } 3470 3471 return this; // chainable with (key, val) signature 3472 } 3473 else if (typeof keyvals === 'object') { // SET keyvals on every node 3474 var i = this.length; 3475 while (i--) { 3476 node = this[i]; 3477 if (node.nodeType !== 1) { continue; } 3478 3479 index = node[dataPropName]; 3480 if (!index) { // assumes index is always > 0 3481 index = dataIndex++; 3482 3483 node[dataPropName] = index; 3484 dataCache[index] = {}; 3485 } 3486 for (key in keyvals) { 3487 dataCache[index][key] = keyvals[key]; 3488 } 3489 } 3490 3491 return this; // chainable with ({key, val}) signature 3492 } 3493 else { // GET from first node 3494 node = this[0]; 3495 if (node === undef || node.nodeType !== 1) { return undef; } 3496 3497 if ( !(index = node[dataPropName]) ) { 3498 return undef; 3499 } 3500 3501 if (key !== undef) { 3502 return dataCache[index][key]; 3503 } 3504 3505 // get the entire data cache object for this node 3506 return dataCache[index]; 3507 } 3508 }; 3509 3510 /** 3511 @name glow.NodeList#hasAttr 3512 @function 3513 @description Does the node have a particular attribute? 3514 3515 The first node in this NodeList is tested. 3516 3517 @param {string} name The name of the attribute to test for. 3518 3519 @returns {boolean|undefined} Returns undefined if the first node is not an element, 3520 or if the NodeList is empty, otherwise returns true/false to indicate if that attribute exists 3521 on the first element. 3522 3523 @example 3524 if ( glow("#myImg").hasAttr("alt") ){ 3525 // ... 3526 } 3527 */ 3528 NodeListProto.hasAttr = function(name) { 3529 var node; 3530 3531 /*!debug*/ 3532 if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.NodeList#hasAttr expects 1 argument.'); } 3533 else if (typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#hasAttr expects argument 1 to be of type string.'); } 3534 /*gubed!*/ 3535 3536 node = this[0]; 3537 3538 if (this.length && node.nodeType === 1) { 3539 if (node.attributes[name]) { // is an object in IE, or else: undefined in IE < 8, null in IE 8 3540 return !!node.attributes[name].specified; 3541 } 3542 3543 if (node.hasAttribute) { return node.hasAttribute(name); } // like FF, Safari, etc 3544 else { return node.attributes[name] !== undef; } // like IE7 3545 } 3546 }; 3547 3548 /** 3549 @name glow.NodeList#hasClass 3550 @function 3551 @description Does the node have a particular class? 3552 3553 The first node in this NodeList is tested. 3554 3555 @param {string} name The name of the class to test for. 3556 3557 @returns {boolean} 3558 3559 @example 3560 if ( glow("#myInput").hasClass("errored") ){ 3561 // ... 3562 } 3563 */ 3564 NodeListProto.hasClass = function (name) { 3565 /*!debug*/ 3566 if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.NodeList#hasClass expects 1 argument.'); } 3567 else if (typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#hasClass expects argument 1 to be of type string.'); } 3568 /*gubed!*/ 3569 3570 if (this.length && this[0].nodeType === 1) { 3571 return ( (' ' + this[0].className + ' ').indexOf(' ' + name + ' ') > -1 ); 3572 } 3573 }; 3574 3575 /** 3576 @name glow.NodeList#prop 3577 @function 3578 @description Gets or sets node properties. 3579 3580 This function gets / sets node properties, to get attributes, 3581 see {@link glow.NodeList#attr NodeList#attr}. 3582 3583 When getting a property, it is retrieved from the first 3584 node in this NodeList. Setting properties to each element in 3585 this NodeList. 3586 3587 To set multiple properties in one call, pass in an object of 3588 name/value pairs. 3589 3590 @param {string | Object} name The name of the property, or an object of name/value pairs 3591 @param {string} [value] The value to set the property to. 3592 3593 @returns {string | glow.NodeList} 3594 3595 When setting properties it returns the NodeList, otherwise 3596 returns the property value. 3597 3598 @example 3599 var myNodeList = glow("#formElement"); 3600 3601 // get the node name 3602 myNodeList.prop("nodeName"); 3603 3604 // set a property 3605 myNodeList.prop("_secretValue", 10); 3606 3607 // set multiple properties 3608 myNodeList.prop({ 3609 checked: true, 3610 _secretValue: 10 3611 }); 3612 */ 3613 NodeListProto.prop = function(name, val) { 3614 var hash = name, 3615 argsLen = arguments.length; 3616 3617 /*!debug*/ 3618 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.'); } 3619 else if (arguments.length === 2 && typeof name !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#prop expects name to be of type string.'); } 3620 else if (arguments.length === 0 || arguments.length > 2) { glow.debug.warn('[wrong count] glow.NodeList#prop expects 1 or 2 arguments.'); } 3621 /*gubed!*/ 3622 3623 if (this.length === 0) return; 3624 3625 if (argsLen === 2 && typeof name === 'string') { 3626 for (var i = 0, ilen = this.length; i < ilen; i++) { 3627 if (this[i].nodeType === 1) { this[i][name] = val; } 3628 } 3629 return this; 3630 } 3631 else if (argsLen === 1 && hash.constructor === Object) { 3632 for (var key in hash) { 3633 for (var i = 0, ilen = this.length; i < ilen; i++) { 3634 if (this[i].nodeType === 1) { this[i][key] = hash[key]; } 3635 } 3636 } 3637 return this; 3638 } 3639 else if (argsLen === 1 && typeof name === 'string') { 3640 if (this[0].nodeType === 1) { return this[0][name]; } 3641 } 3642 else { 3643 throw new Error('Invalid parameters.'); 3644 } 3645 }; 3646 3647 /** 3648 @name glow.NodeList#removeAttr 3649 @function 3650 @description Removes an attribute from each node. 3651 3652 @param {string} name The name of the attribute to remove. 3653 3654 @returns {glow.NodeList} 3655 3656 @example 3657 glow("a").removeAttr("target"); 3658 */ 3659 NodeListProto.removeAttr = function (name) { 3660 var dom0Property; 3661 3662 /*!debug*/ 3663 if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.NodeList#removeAttr expects 1 argument.'); } 3664 else if (typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#removeAttr expects argument 1 to be of type string.'); } 3665 /*gubed!*/ 3666 3667 for (var i = 0, leni = this.length; i < leni; i++) { 3668 if (this[i].nodeType === 1) { 3669 if (glow.env.ie < 8) { 3670 if ( (dom0Property = dom0PropertyMapping[name.toLowerCase()]) ) { 3671 this[i][dom0Property] = ''; 3672 } 3673 } 3674 3675 if (this[i].removeAttribute) this[i].removeAttribute(name); 3676 } 3677 } 3678 return this; 3679 }; 3680 3681 /** 3682 @name glow.NodeList#removeClass 3683 @function 3684 @description Removes a class from each node. 3685 3686 @param {string} name The name of the class to remove. 3687 3688 @returns {glow.NodeList} 3689 3690 @example 3691 glow("#footer #login a").removeClass("highlight"); 3692 */ 3693 NodeListProto.removeClass = function(name) { 3694 var node; 3695 3696 /*!debug*/ 3697 if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.NodeList#removeClass() expects 1 argument.'); } 3698 else if (typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#removeClass() expects argument 1 to be of type string.'); } 3699 /*gubed!*/ 3700 3701 var i = this.length; 3702 while (i--) { 3703 node = this[i]; 3704 if (node.className) { 3705 _removeClass(node, name); 3706 } 3707 } 3708 return this; 3709 }; 3710 3711 function _removeClass(node, name) { 3712 var oldClasses = node.className.split(' '), 3713 newClasses = []; 3714 3715 oldClasses = node.className.split(' '); 3716 newClasses = []; 3717 3718 var i = oldClasses.length; 3719 while (i--) { 3720 if (oldClasses[i] !== name) { 3721 oldClasses[i] && newClasses.unshift(oldClasses[i]); // unshift to maintain original order 3722 } 3723 } 3724 node.className = (newClasses.length)? newClasses.join(' ') : ''; 3725 } 3726 3727 /** 3728 @name glow.NodeList#removeData 3729 @function 3730 @description Removes data previously added by {@link glow.NodeList#data} from each node in this NodeList. 3731 3732 When called with no arguments, will delete glow's entire data store for each node in this NodeList. 3733 3734 Otherwise, when given a name, will delete the associated value from each node in this NodeList. 3735 3736 @param {string} [key] The name of the value in glow's data store. 3737 @see glow.NodeList#data 3738 */ 3739 NodeListProto.removeData = function(key) { 3740 var elm, 3741 i = this.length, 3742 index; 3743 // uses private scoped variables: dataCache, dataPropName 3744 3745 /*!debug*/ 3746 if (arguments.length > 1) { glow.debug.warn('[wrong count] glow.NodeList#removeData expects 0 or 1 arguments.'); } 3747 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.'); } 3748 /*gubed!*/ 3749 3750 while (i--) { 3751 elm = this[i]; 3752 index = elm[dataPropName]; 3753 3754 if (index !== undef) { 3755 switch (arguments.length) { 3756 case 0: 3757 dataCache[index] = undef; 3758 elm[dataPropName] = undef; 3759 try { 3760 delete elm[dataPropName]; // IE 6 goes wobbly here 3761 } 3762 catch(e) { // remove expando from IE 6 3763 elm.removeAttribute && elm.removeAttribute(dataPropName); 3764 } 3765 break; 3766 case 1: 3767 dataCache[index][key] = undef; 3768 delete dataCache[index][key]; 3769 break; 3770 } 3771 } 3772 } 3773 3774 return this; // chainable 3775 }; 3776 3777 /** 3778 @name glow.NodeList#toggleClass 3779 @function 3780 @description Toggles a class on each node. 3781 3782 @param {string} name The name of the class to toggle. 3783 3784 @returns {glow.NodeList} 3785 3786 @example 3787 glow(".onOffSwitch").toggleClass("on"); 3788 */ 3789 NodeListProto.toggleClass = function(name) { 3790 var node; 3791 3792 /*!debug*/ 3793 if (arguments.length !== 1) { glow.debug.warn('[wrong count] glow.NodeList#toggleClass() expects 1 argument.'); } 3794 else if (typeof arguments[0] !== 'string') {glow.debug.warn('[wrong type] glow.NodeList#toggleClass() expects argument 1 to be of type string.'); } 3795 /*gubed!*/ 3796 3797 for (var i = 0, leni = this.length; i < leni; i++) { 3798 node = this[i]; 3799 if (node.className) { 3800 if ( (' ' + node.className + ' ').indexOf(' ' + name + ' ') > -1 ) { 3801 _removeClass(node, name); 3802 } 3803 else { 3804 _addClass(node, name); 3805 } 3806 } 3807 } 3808 3809 return this; 3810 }; 3811 3812 /** 3813 @name glow.NodeList#val 3814 @function 3815 @description Gets or sets form values for the first node. 3816 The returned value depends on the type of element, see below: 3817 3818 <dl> 3819 <dt>Radio button or checkbox</dt> 3820 <dd>If checked, then the contents of the value property, otherwise an empty string.</dd> 3821 <dt>Select</dt> 3822 <dd>The contents of value property of the selected option</dd> 3823 <dt>Select (multiple)</dt> 3824 <dd>An array of selected option values.</dd> 3825 <dt>Other form elements</dt> 3826 <dd>The value of the input.</dd> 3827 </dl> 3828 3829 Getting values from a form: 3830 3831 If the first element in the NodeList is a form, then an 3832 object is returned containing the form data. Each item 3833 property of the object is a value as above, apart from when 3834 multiple elements of the same name exist, in which case the 3835 it will contain an array of values. 3836 3837 Setting values for form elements: 3838 3839 If a value is passed and the first element of the NodeList 3840 is a form element, then the form element is given that value. 3841 For select elements, this means that the first option that 3842 matches the value will be selected. For selects that allow 3843 multiple selection, the options which have a value that 3844 exists in the array of values/match the value will be 3845 selected and others will be deselected. 3846 3847 Checkboxes and radio buttons will be checked only if the value is the same 3848 as the one you provide. 3849 3850 Setting values for forms: 3851 3852 If the first element in the NodeList is a form and the 3853 value is an object, then each element of the form has its 3854 value set to the corresponding property of the object, using 3855 the method described above. 3856 3857 @param {string | Object} [value] The value to set the form element/elements to. 3858 3859 @returns {glow.NodeList | string | Object} 3860 3861 When used to set a value it returns the NodeList, otherwise 3862 returns the value as described above. 3863 3864 @example 3865 // get a value from an input with the id 'username' 3866 var username = glow("#username").val(); 3867 3868 @example 3869 // get values from a form 3870 var userDetails = glow("form").val(); 3871 3872 @example 3873 // set a value 3874 glow("#username").val("example username"); 3875 3876 @example 3877 // set values in a form 3878 glow("form").val({ 3879 username : "another", 3880 name : "A N Other" 3881 }); 3882 */ 3883 NodeListProto.val = function(){ 3884 var args = arguments, 3885 val = args[0], 3886 i = 0, 3887 length = this.length; 3888 3889 if (args.length === 0) { 3890 return this[0].nodeName == 'FORM' ? 3891 formValues(this[0]) : 3892 elementValue(this[0]); 3893 } 3894 if (this[0].nodeName == 'FORM') { 3895 if (! typeof val == 'object') { 3896 throw 'value for FORM must be object'; 3897 } 3898 setFormValues(this[0], val); 3899 } else { 3900 for (; i < length; i++) { 3901 setValue(this[i], val); 3902 } 3903 } 3904 return this; 3905 }; 3906 3907 /* 3908 @name elementValue 3909 @private 3910 @returns the value of the form element 3911 */ 3912 function elementValue (el) { 3913 var elType = el.type, 3914 elChecked = el.checked, 3915 elValue = el.value, 3916 vals = [], 3917 i = 0; 3918 3919 if (elType == 'radio') { 3920 return elChecked ? elValue : ''; 3921 } 3922 3923 else if (elType == 'checkbox') { 3924 return elChecked ? elValue : ''; 3925 } 3926 3927 else if (elType == 'select-one') { 3928 return el.selectedIndex > -1 ? el.options[el.selectedIndex].value : ''; 3929 } 3930 3931 else if (elType == 'select-multiple') { 3932 for (var length = el.options.length; i < length; i++) { 3933 if (el.options[i].selected) { 3934 vals[vals.length] = el.options[i].value; 3935 } 3936 } 3937 return vals; 3938 } 3939 3940 else { 3941 return elValue; 3942 } 3943 } 3944 3945 /* 3946 @name: setValue 3947 @description Set the value of a form element. Returns values that weren't able to set if array of vals passed (for multi select). Otherwise true if val set, false if not 3948 @returns val or bool 3949 @private 3950 */ 3951 function setValue (el, val) { 3952 var i = 0, 3953 length, 3954 n = 0, 3955 nlen, 3956 elOption, 3957 optionVal; 3958 3959 if (el.type == 'select-one') { 3960 for (length = el.options.length; i < length; i++) { 3961 if (el.options[i].value == val) { 3962 el.selectedIndex = i; 3963 return true; 3964 } 3965 } 3966 return false; 3967 } else if (el.type == 'select-multiple') { 3968 var isArray = !!val.push; 3969 for (i = 0, length = el.options.length; i < length; i++) { 3970 elOption = el.options[i]; 3971 optionVal = elOption.value; 3972 if (isArray) { 3973 elOption.selected = false; 3974 for (nlen = val.length; n < nlen; n++) { 3975 if (optionVal == val[n]) { 3976 elOption.selected = true; 3977 val.splice(n, 1); 3978 break; 3979 } 3980 } 3981 } else { 3982 return elOption.selected = val == optionVal; 3983 } 3984 } 3985 return false; 3986 } else if (el.type == 'radio' || el.type == 'checkbox') { 3987 el.checked = val == el.value; 3988 return val == el.value; 3989 } else { 3990 el.value = val; 3991 return true; 3992 } 3993 } 3994 3995 /* 3996 @name setFormValues 3997 @description Set values of a form to those in passed in object. 3998 @private 3999 */ 4000 function setFormValues (form, vals) { 4001 var prop, currentField, 4002 fields = {}, 4003 storeType, i = 0, n, len, foundOne, currentFieldType; 4004 4005 for (prop in vals) { 4006 currentField = form[prop]; 4007 if (currentField && currentField[0] && !currentField.options) { // is array of fields 4008 //normalise values to array of vals 4009 vals[prop] = vals[prop] && vals[prop].push ? vals[prop] : [vals[prop]]; 4010 //order the fields by types that matter 4011 fields.radios = []; 4012 fields.checkboxesSelects = []; 4013 fields.multiSelects = []; 4014 fields.other = []; 4015 4016 for (i = 0; currentField[i]; i++) { 4017 currentFieldType = currentField[i].type; 4018 if (currentFieldType == 'radio') { 4019 storeType = 'radios'; 4020 } else if (currentFieldType == 'select-one' || currentFieldType == 'checkbox') { 4021 storeType = 'checkboxesSelects'; 4022 } else if (currentFieldType == 'select-multiple') { 4023 storeType = 'multiSelects'; 4024 } else { 4025 storeType = 'other'; 4026 } 4027 //add it to the correct array 4028 fields[storeType][fields[storeType].length] = currentField[i]; 4029 } 4030 4031 for (i = 0; fields.multiSelects[i]; i++) { 4032 vals[prop] = setValue(fields.multiSelects[i], vals[prop]); 4033 } 4034 for (i = 0; fields.checkboxesSelects[i]; i++) { 4035 setValue(fields.checkboxesSelects[i], ''); 4036 for (n = 0, len = vals[prop].length; n < len; n++) { 4037 if (setValue(fields.checkboxesSelects[i], vals[prop][n])) { 4038 vals[prop].slice(n, 1); 4039 break; 4040 } 4041 } 4042 } 4043 for (i = 0; fields.radios[i]; i++) { 4044 fields.radios[i].checked = false; 4045 foundOne = false; 4046 for (n = 0, len = vals[prop].length; n < len; n++) { 4047 if (setValue(fields.radios[i], vals[prop][n])) { 4048 vals[prop].slice(n, 1); 4049 foundOne = true; 4050 break; 4051 } 4052 if (foundOne) { break; } 4053 } 4054 } 4055 for (i = 0; fields.other[i] && vals[prop][i] !== undefined; i++) { 4056 setValue(fields.other[i], vals[prop][i]); 4057 } 4058 } else if (currentField && currentField.nodeName) { // is single field, easy 4059 setValue(currentField, vals[prop]); 4060 } 4061 } 4062 } 4063 4064 /* 4065 @name formValues 4066 @description Get an object containing form data. 4067 @private 4068 */ 4069 function formValues (form) { 4070 var vals = {}, 4071 radios = {}, 4072 formElements = form.elements, 4073 i = formElements.length, 4074 name, 4075 formElement, 4076 j, 4077 radio, 4078 nodeName; 4079 while (i--) { 4080 formElement = formElements[i]; 4081 nodeName = formElement.nodeName.toLowerCase(); 4082 name = formElement.name; 4083 4084 // fieldsets & objects come back as form elements, but we don't care about these 4085 // we don't bother with fields that don't have a name 4086 // switch to whitelist? 4087 if ( 4088 nodeName == 'fieldset' || 4089 nodeName == 'object' || 4090 !name 4091 ) { continue; } 4092 if (formElement.type == 'checkbox' && ! formElement.checked) { 4093 if (! name in vals) { 4094 vals[name] = undefined; 4095 } 4096 } else if (formElement.type == 'radio') { 4097 4098 if (radios[name]) { 4099 radios[name][radios[name].length] = formElement; 4100 } else { 4101 radios[name] = [formElement]; 4102 } 4103 } else { 4104 var value = elementValue(formElement); 4105 if (name in vals) { 4106 if (vals[name].push) { 4107 vals[name][vals[name].length] = value; 4108 } else { 4109 vals[name] = [vals[name], value]; 4110 } 4111 } else { 4112 vals[name] = value; 4113 } 4114 } 4115 } 4116 for (i in radios) { 4117 var length, 4118 j = 0; 4119 for (length = radios[i].length; j < length; j++) { 4120 radio = radios[i][j]; 4121 name = radio.name; 4122 if (radio.checked) { 4123 vals[radio.name] = radio.value; 4124 break; 4125 } 4126 } 4127 if (! name in vals) { alert('15 if name in vals'); vals[name] = undefined; } 4128 } 4129 return vals; 4130 } 4131 }); 4132 /*! 4133 * Sizzle CSS Selector Engine - v1.0 4134 * Copyright 2009, The Dojo Foundation 4135 * Released under the MIT, BSD, and GPL Licenses. 4136 * More information: http://sizzlejs.com/ 4137 */ 4138 (function(){ 4139 4140 var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^\[\]]*\]|['"][^'"]*['"]|[^\[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, 4141 done = 0, 4142 toString = Object.prototype.toString, 4143 hasDuplicate = false, 4144 baseHasDuplicate = true; 4145 4146 // Here we check if the JavaScript engine is using some sort of 4147 // optimization where it does not always call our comparision 4148 // function. If that is the case, discard the hasDuplicate value. 4149 // Thus far that includes Google Chrome. 4150 [0, 0].sort(function(){ 4151 baseHasDuplicate = false; 4152 return 0; 4153 }); 4154 4155 var Sizzle = function(selector, context, results, seed) { 4156 results = results || []; 4157 context = context || document; 4158 4159 var origContext = context; 4160 4161 if ( context.nodeType !== 1 && context.nodeType !== 9 ) { 4162 return []; 4163 } 4164 4165 if ( !selector || typeof selector !== "string" ) { 4166 return results; 4167 } 4168 4169 var parts = [], m, set, checkSet, extra, prune = true, contextXML = Sizzle.isXML(context), 4170 soFar = selector, ret, cur, pop, i; 4171 4172 // Reset the position of the chunker regexp (start from head) 4173 do { 4174 chunker.exec(""); 4175 m = chunker.exec(soFar); 4176 4177 if ( m ) { 4178 soFar = m[3]; 4179 4180 parts.push( m[1] ); 4181 4182 if ( m[2] ) { 4183 extra = m[3]; 4184 break; 4185 } 4186 } 4187 } while ( m ); 4188 4189 if ( parts.length > 1 && origPOS.exec( selector ) ) { 4190 if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { 4191 set = posProcess( parts[0] + parts[1], context ); 4192 } else { 4193 set = Expr.relative[ parts[0] ] ? 4194 [ context ] : 4195 Sizzle( parts.shift(), context ); 4196 4197 while ( parts.length ) { 4198 selector = parts.shift(); 4199 4200 if ( Expr.relative[ selector ] ) { 4201 selector += parts.shift(); 4202 } 4203 4204 set = posProcess( selector, set ); 4205 } 4206 } 4207 } else { 4208 // Take a shortcut and set the context if the root selector is an ID 4209 // (but not if it'll be faster if the inner selector is an ID) 4210 if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && 4211 Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { 4212 ret = Sizzle.find( parts.shift(), context, contextXML ); 4213 context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; 4214 } 4215 4216 if ( context ) { 4217 ret = seed ? 4218 { expr: parts.pop(), set: makeArray(seed) } : 4219 Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); 4220 set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; 4221 4222 if ( parts.length > 0 ) { 4223 checkSet = makeArray(set); 4224 } else { 4225 prune = false; 4226 } 4227 4228 while ( parts.length ) { 4229 cur = parts.pop(); 4230 pop = cur; 4231 4232 if ( !Expr.relative[ cur ] ) { 4233 cur = ""; 4234 } else { 4235 pop = parts.pop(); 4236 } 4237 4238 if ( pop == null ) { 4239 pop = context; 4240 } 4241 4242 Expr.relative[ cur ]( checkSet, pop, contextXML ); 4243 } 4244 } else { 4245 checkSet = parts = []; 4246 } 4247 } 4248 4249 if ( !checkSet ) { 4250 checkSet = set; 4251 } 4252 4253 if ( !checkSet ) { 4254 Sizzle.error( cur || selector ); 4255 } 4256 4257 if ( toString.call(checkSet) === "[object Array]" ) { 4258 if ( !prune ) { 4259 results.push.apply( results, checkSet ); 4260 } else if ( context && context.nodeType === 1 ) { 4261 for ( i = 0; checkSet[i] != null; i++ ) { 4262 if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && Sizzle.contains(context, checkSet[i])) ) { 4263 results.push( set[i] ); 4264 } 4265 } 4266 } else { 4267 for ( i = 0; checkSet[i] != null; i++ ) { 4268 if ( checkSet[i] && checkSet[i].nodeType === 1 ) { 4269 results.push( set[i] ); 4270 } 4271 } 4272 } 4273 } else { 4274 makeArray( checkSet, results ); 4275 } 4276 4277 if ( extra ) { 4278 Sizzle( extra, origContext, results, seed ); 4279 Sizzle.uniqueSort( results ); 4280 } 4281 4282 return results; 4283 }; 4284 4285 Sizzle.uniqueSort = function(results){ 4286 if ( sortOrder ) { 4287 hasDuplicate = baseHasDuplicate; 4288 results.sort(sortOrder); 4289 4290 if ( hasDuplicate ) { 4291 for ( var i = 1; i < results.length; i++ ) { 4292 if ( results[i] === results[i-1] ) { 4293 results.splice(i--, 1); 4294 } 4295 } 4296 } 4297 } 4298 4299 return results; 4300 }; 4301 4302 Sizzle.matches = function(expr, set){ 4303 return Sizzle(expr, null, null, set); 4304 }; 4305 4306 Sizzle.find = function(expr, context, isXML){ 4307 var set; 4308 4309 if ( !expr ) { 4310 return []; 4311 } 4312 4313 for ( var i = 0, l = Expr.order.length; i < l; i++ ) { 4314 var type = Expr.order[i], match; 4315 4316 if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { 4317 var left = match[1]; 4318 match.splice(1,1); 4319 4320 if ( left.substr( left.length - 1 ) !== "\\" ) { 4321 match[1] = (match[1] || "").replace(/\\/g, ""); 4322 set = Expr.find[ type ]( match, context, isXML ); 4323 if ( set != null ) { 4324 expr = expr.replace( Expr.match[ type ], "" ); 4325 break; 4326 } 4327 } 4328 } 4329 } 4330 4331 if ( !set ) { 4332 set = context.getElementsByTagName("*"); 4333 } 4334 4335 return {set: set, expr: expr}; 4336 }; 4337 4338 Sizzle.filter = function(expr, set, inplace, not){ 4339 var old = expr, result = [], curLoop = set, match, anyFound, 4340 isXMLFilter = set && set[0] && Sizzle.isXML(set[0]); 4341 4342 while ( expr && set.length ) { 4343 for ( var type in Expr.filter ) { 4344 if ( (match = Expr.leftMatch[ type ].exec( expr )) != null && match[2] ) { 4345 var filter = Expr.filter[ type ], found, item, left = match[1]; 4346 anyFound = false; 4347 4348 match.splice(1,1); 4349 4350 if ( left.substr( left.length - 1 ) === "\\" ) { 4351 continue; 4352 } 4353 4354 if ( curLoop === result ) { 4355 result = []; 4356 } 4357 4358 if ( Expr.preFilter[ type ] ) { 4359 match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); 4360 4361 if ( !match ) { 4362 anyFound = found = true; 4363 } else if ( match === true ) { 4364 continue; 4365 } 4366 } 4367 4368 if ( match ) { 4369 for ( var i = 0; (item = curLoop[i]) != null; i++ ) { 4370 if ( item ) { 4371 found = filter( item, match, i, curLoop ); 4372 var pass = not ^ !!found; 4373 4374 if ( inplace && found != null ) { 4375 if ( pass ) { 4376 anyFound = true; 4377 } else { 4378 curLoop[i] = false; 4379 } 4380 } else if ( pass ) { 4381 result.push( item ); 4382 anyFound = true; 4383 } 4384 } 4385 } 4386 } 4387 4388 if ( found !== undefined ) { 4389 if ( !inplace ) { 4390 curLoop = result; 4391 } 4392 4393 expr = expr.replace( Expr.match[ type ], "" ); 4394 4395 if ( !anyFound ) { 4396 return []; 4397 } 4398 4399 break; 4400 } 4401 } 4402 } 4403 4404 // Improper expression 4405 if ( expr === old ) { 4406 if ( anyFound == null ) { 4407 Sizzle.error( expr ); 4408 } else { 4409 break; 4410 } 4411 } 4412 4413 old = expr; 4414 } 4415 4416 return curLoop; 4417 }; 4418 4419 Sizzle.error = function( msg ) { 4420 throw "Syntax error, unrecognized expression: " + msg; 4421 }; 4422 4423 var Expr = Sizzle.selectors = { 4424 order: [ "ID", "NAME", "TAG" ], 4425 match: { 4426 ID: /#((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, 4427 CLASS: /\.((?:[\w\u00c0-\uFFFF\-]|\\.)+)/, 4428 NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF\-]|\\.)+)['"]*\]/, 4429 ATTR: /\[\s*((?:[\w\u00c0-\uFFFF\-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, 4430 TAG: /^((?:[\w\u00c0-\uFFFF\*\-]|\\.)+)/, 4431 CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+\-]*)\))?/, 4432 POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^\-]|$)/, 4433 PSEUDO: /:((?:[\w\u00c0-\uFFFF\-]|\\.)+)(?:\((['"]?)((?:\([^\)]+\)|[^\(\)]*)+)\2\))?/ 4434 }, 4435 leftMatch: {}, 4436 attrMap: { 4437 "class": "className", 4438 "for": "htmlFor" 4439 }, 4440 attrHandle: { 4441 href: function(elem){ 4442 return elem.getAttribute("href"); 4443 } 4444 }, 4445 relative: { 4446 "+": function(checkSet, part){ 4447 var isPartStr = typeof part === "string", 4448 isTag = isPartStr && !/\W/.test(part), 4449 isPartStrNotTag = isPartStr && !isTag; 4450 4451 if ( isTag ) { 4452 part = part.toLowerCase(); 4453 } 4454 4455 for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { 4456 if ( (elem = checkSet[i]) ) { 4457 while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} 4458 4459 checkSet[i] = isPartStrNotTag || elem && elem.nodeName.toLowerCase() === part ? 4460 elem || false : 4461 elem === part; 4462 } 4463 } 4464 4465 if ( isPartStrNotTag ) { 4466 Sizzle.filter( part, checkSet, true ); 4467 } 4468 }, 4469 ">": function(checkSet, part){ 4470 var isPartStr = typeof part === "string", 4471 elem, i = 0, l = checkSet.length; 4472 4473 if ( isPartStr && !/\W/.test(part) ) { 4474 part = part.toLowerCase(); 4475 4476 for ( ; i < l; i++ ) { 4477 elem = checkSet[i]; 4478 if ( elem ) { 4479 var parent = elem.parentNode; 4480 checkSet[i] = parent.nodeName.toLowerCase() === part ? parent : false; 4481 } 4482 } 4483 } else { 4484 for ( ; i < l; i++ ) { 4485 elem = checkSet[i]; 4486 if ( elem ) { 4487 checkSet[i] = isPartStr ? 4488 elem.parentNode : 4489 elem.parentNode === part; 4490 } 4491 } 4492 4493 if ( isPartStr ) { 4494 Sizzle.filter( part, checkSet, true ); 4495 } 4496 } 4497 }, 4498 "": function(checkSet, part, isXML){ 4499 var doneName = done++, checkFn = dirCheck, nodeCheck; 4500 4501 if ( typeof part === "string" && !/\W/.test(part) ) { 4502 part = part.toLowerCase(); 4503 nodeCheck = part; 4504 checkFn = dirNodeCheck; 4505 } 4506 4507 checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); 4508 }, 4509 "~": function(checkSet, part, isXML){ 4510 var doneName = done++, checkFn = dirCheck, nodeCheck; 4511 4512 if ( typeof part === "string" && !/\W/.test(part) ) { 4513 part = part.toLowerCase(); 4514 nodeCheck = part; 4515 checkFn = dirNodeCheck; 4516 } 4517 4518 checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); 4519 } 4520 }, 4521 find: { 4522 ID: function(match, context, isXML){ 4523 if ( typeof context.getElementById !== "undefined" && !isXML ) { 4524 var m = context.getElementById(match[1]); 4525 return m ? [m] : []; 4526 } 4527 }, 4528 NAME: function(match, context){ 4529 if ( typeof context.getElementsByName !== "undefined" ) { 4530 var ret = [], results = context.getElementsByName(match[1]); 4531 4532 for ( var i = 0, l = results.length; i < l; i++ ) { 4533 if ( results[i].getAttribute("name") === match[1] ) { 4534 ret.push( results[i] ); 4535 } 4536 } 4537 4538 return ret.length === 0 ? null : ret; 4539 } 4540 }, 4541 TAG: function(match, context){ 4542 return context.getElementsByTagName(match[1]); 4543 } 4544 }, 4545 preFilter: { 4546 CLASS: function(match, curLoop, inplace, result, not, isXML){ 4547 match = " " + match[1].replace(/\\/g, "") + " "; 4548 4549 if ( isXML ) { 4550 return match; 4551 } 4552 4553 for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { 4554 if ( elem ) { 4555 if ( not ^ (elem.className && (" " + elem.className + " ").replace(/[\t\n]/g, " ").indexOf(match) >= 0) ) { 4556 if ( !inplace ) { 4557 result.push( elem ); 4558 } 4559 } else if ( inplace ) { 4560 curLoop[i] = false; 4561 } 4562 } 4563 } 4564 4565 return false; 4566 }, 4567 ID: function(match){ 4568 return match[1].replace(/\\/g, ""); 4569 }, 4570 TAG: function(match, curLoop){ 4571 return match[1].toLowerCase(); 4572 }, 4573 CHILD: function(match){ 4574 if ( match[1] === "nth" ) { 4575 // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' 4576 var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( 4577 match[2] === "even" && "2n" || match[2] === "odd" && "2n+1" || 4578 !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); 4579 4580 // calculate the numbers (first)n+(last) including if they are negative 4581 match[2] = (test[1] + (test[2] || 1)) - 0; 4582 match[3] = test[3] - 0; 4583 } 4584 4585 // TODO: Move to normal caching system 4586 match[0] = done++; 4587 4588 return match; 4589 }, 4590 ATTR: function(match, curLoop, inplace, result, not, isXML){ 4591 var name = match[1].replace(/\\/g, ""); 4592 4593 if ( !isXML && Expr.attrMap[name] ) { 4594 match[1] = Expr.attrMap[name]; 4595 } 4596 4597 if ( match[2] === "~=" ) { 4598 match[4] = " " + match[4] + " "; 4599 } 4600 4601 return match; 4602 }, 4603 PSEUDO: function(match, curLoop, inplace, result, not){ 4604 if ( match[1] === "not" ) { 4605 // If we're dealing with a complex expression, or a simple one 4606 if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { 4607 match[3] = Sizzle(match[3], null, null, curLoop); 4608 } else { 4609 var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); 4610 if ( !inplace ) { 4611 result.push.apply( result, ret ); 4612 } 4613 return false; 4614 } 4615 } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { 4616 return true; 4617 } 4618 4619 return match; 4620 }, 4621 POS: function(match){ 4622 match.unshift( true ); 4623 return match; 4624 } 4625 }, 4626 filters: { 4627 enabled: function(elem){ 4628 return elem.disabled === false && elem.type !== "hidden"; 4629 }, 4630 disabled: function(elem){ 4631 return elem.disabled === true; 4632 }, 4633 checked: function(elem){ 4634 return elem.checked === true; 4635 }, 4636 selected: function(elem){ 4637 // Accessing this property makes selected-by-default 4638 // options in Safari work properly 4639 elem.parentNode.selectedIndex; 4640 return elem.selected === true; 4641 }, 4642 parent: function(elem){ 4643 return !!elem.firstChild; 4644 }, 4645 empty: function(elem){ 4646 return !elem.firstChild; 4647 }, 4648 has: function(elem, i, match){ 4649 return !!Sizzle( match[3], elem ).length; 4650 }, 4651 header: function(elem){ 4652 return (/h\d/i).test( elem.nodeName ); 4653 }, 4654 text: function(elem){ 4655 return "text" === elem.type; 4656 }, 4657 radio: function(elem){ 4658 return "radio" === elem.type; 4659 }, 4660 checkbox: function(elem){ 4661 return "checkbox" === elem.type; 4662 }, 4663 file: function(elem){ 4664 return "file" === elem.type; 4665 }, 4666 password: function(elem){ 4667 return "password" === elem.type; 4668 }, 4669 submit: function(elem){ 4670 return "submit" === elem.type; 4671 }, 4672 image: function(elem){ 4673 return "image" === elem.type; 4674 }, 4675 reset: function(elem){ 4676 return "reset" === elem.type; 4677 }, 4678 button: function(elem){ 4679 return "button" === elem.type || elem.nodeName.toLowerCase() === "button"; 4680 }, 4681 input: function(elem){ 4682 return (/input|select|textarea|button/i).test(elem.nodeName); 4683 } 4684 }, 4685 setFilters: { 4686 first: function(elem, i){ 4687 return i === 0; 4688 }, 4689 last: function(elem, i, match, array){ 4690 return i === array.length - 1; 4691 }, 4692 even: function(elem, i){ 4693 return i % 2 === 0; 4694 }, 4695 odd: function(elem, i){ 4696 return i % 2 === 1; 4697 }, 4698 lt: function(elem, i, match){ 4699 return i < match[3] - 0; 4700 }, 4701 gt: function(elem, i, match){ 4702 return i > match[3] - 0; 4703 }, 4704 nth: function(elem, i, match){ 4705 return match[3] - 0 === i; 4706 }, 4707 eq: function(elem, i, match){ 4708 return match[3] - 0 === i; 4709 } 4710 }, 4711 filter: { 4712 PSEUDO: function(elem, match, i, array){ 4713 var name = match[1], filter = Expr.filters[ name ]; 4714 4715 if ( filter ) { 4716 return filter( elem, i, match, array ); 4717 } else if ( name === "contains" ) { 4718 return (elem.textContent || elem.innerText || Sizzle.getText([ elem ]) || "").indexOf(match[3]) >= 0; 4719 } else if ( name === "not" ) { 4720 var not = match[3]; 4721 4722 for ( var j = 0, l = not.length; j < l; j++ ) { 4723 if ( not[j] === elem ) { 4724 return false; 4725 } 4726 } 4727 4728 return true; 4729 } else { 4730 Sizzle.error( "Syntax error, unrecognized expression: " + name ); 4731 } 4732 }, 4733 CHILD: function(elem, match){ 4734 var type = match[1], node = elem; 4735 switch (type) { 4736 case 'only': 4737 case 'first': 4738 while ( (node = node.previousSibling) ) { 4739 if ( node.nodeType === 1 ) { 4740 return false; 4741 } 4742 } 4743 if ( type === "first" ) { 4744 return true; 4745 } 4746 node = elem; 4747 case 'last': 4748 while ( (node = node.nextSibling) ) { 4749 if ( node.nodeType === 1 ) { 4750 return false; 4751 } 4752 } 4753 return true; 4754 case 'nth': 4755 var first = match[2], last = match[3]; 4756 4757 if ( first === 1 && last === 0 ) { 4758 return true; 4759 } 4760 4761 var doneName = match[0], 4762 parent = elem.parentNode; 4763 4764 if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { 4765 var count = 0; 4766 for ( node = parent.firstChild; node; node = node.nextSibling ) { 4767 if ( node.nodeType === 1 ) { 4768 node.nodeIndex = ++count; 4769 } 4770 } 4771 parent.sizcache = doneName; 4772 } 4773 4774 var diff = elem.nodeIndex - last; 4775 if ( first === 0 ) { 4776 return diff === 0; 4777 } else { 4778 return ( diff % first === 0 && diff / first >= 0 ); 4779 } 4780 } 4781 }, 4782 ID: function(elem, match){ 4783 return elem.nodeType === 1 && elem.getAttribute("id") === match; 4784 }, 4785 TAG: function(elem, match){ 4786 return (match === "*" && elem.nodeType === 1) || elem.nodeName.toLowerCase() === match; 4787 }, 4788 CLASS: function(elem, match){ 4789 return (" " + (elem.className || elem.getAttribute("class")) + " ") 4790 .indexOf( match ) > -1; 4791 }, 4792 ATTR: function(elem, match){ 4793 var name = match[1], 4794 result = Expr.attrHandle[ name ] ? 4795 Expr.attrHandle[ name ]( elem ) : 4796 elem[ name ] != null ? 4797 elem[ name ] : 4798 elem.getAttribute( name ), 4799 value = result + "", 4800 type = match[2], 4801 check = match[4]; 4802 4803 return result == null ? 4804 type === "!=" : 4805 type === "=" ? 4806 value === check : 4807 type === "*=" ? 4808 value.indexOf(check) >= 0 : 4809 type === "~=" ? 4810 (" " + value + " ").indexOf(check) >= 0 : 4811 !check ? 4812 value && result !== false : 4813 type === "!=" ? 4814 value !== check : 4815 type === "^=" ? 4816 value.indexOf(check) === 0 : 4817 type === "$=" ? 4818 value.substr(value.length - check.length) === check : 4819 type === "|=" ? 4820 value === check || value.substr(0, check.length + 1) === check + "-" : 4821 false; 4822 }, 4823 POS: function(elem, match, i, array){ 4824 var name = match[2], filter = Expr.setFilters[ name ]; 4825 4826 if ( filter ) { 4827 return filter( elem, i, match, array ); 4828 } 4829 } 4830 } 4831 }; 4832 4833 var origPOS = Expr.match.POS, 4834 fescape = function(all, num){ 4835 return "\\" + (num - 0 + 1); 4836 }; 4837 4838 for ( var type in Expr.match ) { 4839 Expr.match[ type ] = new RegExp( Expr.match[ type ].source + (/(?![^\[]*\])(?![^\(]*\))/.source) ); 4840 Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source.replace(/\\(\d+)/g, fescape) ); 4841 } 4842 4843 var makeArray = function(array, results) { 4844 array = Array.prototype.slice.call( array, 0 ); 4845 4846 if ( results ) { 4847 results.push.apply( results, array ); 4848 return results; 4849 } 4850 4851 return array; 4852 }; 4853 4854 // Perform a simple check to determine if the browser is capable of 4855 // converting a NodeList to an array using builtin methods. 4856 // Also verifies that the returned array holds DOM nodes 4857 // (which is not the case in the Blackberry browser) 4858 try { 4859 Array.prototype.slice.call( document.documentElement.childNodes, 0 )[0].nodeType; 4860 4861 // Provide a fallback method if it does not work 4862 } catch(e){ 4863 makeArray = function(array, results) { 4864 var ret = results || [], i = 0; 4865 4866 if ( toString.call(array) === "[object Array]" ) { 4867 Array.prototype.push.apply( ret, array ); 4868 } else { 4869 if ( typeof array.length === "number" ) { 4870 for ( var l = array.length; i < l; i++ ) { 4871 ret.push( array[i] ); 4872 } 4873 } else { 4874 for ( ; array[i]; i++ ) { 4875 ret.push( array[i] ); 4876 } 4877 } 4878 } 4879 4880 return ret; 4881 }; 4882 } 4883 4884 var sortOrder; 4885 4886 if ( document.documentElement.compareDocumentPosition ) { 4887 sortOrder = function( a, b ) { 4888 if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { 4889 if ( a == b ) { 4890 hasDuplicate = true; 4891 } 4892 return a.compareDocumentPosition ? -1 : 1; 4893 } 4894 4895 var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; 4896 if ( ret === 0 ) { 4897 hasDuplicate = true; 4898 } 4899 return ret; 4900 }; 4901 } else if ( "sourceIndex" in document.documentElement ) { 4902 sortOrder = function( a, b ) { 4903 if ( !a.sourceIndex || !b.sourceIndex ) { 4904 if ( a == b ) { 4905 hasDuplicate = true; 4906 } 4907 return a.sourceIndex ? -1 : 1; 4908 } 4909 4910 var ret = a.sourceIndex - b.sourceIndex; 4911 if ( ret === 0 ) { 4912 hasDuplicate = true; 4913 } 4914 return ret; 4915 }; 4916 } else if ( document.createRange ) { 4917 sortOrder = function( a, b ) { 4918 if ( !a.ownerDocument || !b.ownerDocument ) { 4919 if ( a == b ) { 4920 hasDuplicate = true; 4921 } 4922 return a.ownerDocument ? -1 : 1; 4923 } 4924 4925 var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); 4926 aRange.setStart(a, 0); 4927 aRange.setEnd(a, 0); 4928 bRange.setStart(b, 0); 4929 bRange.setEnd(b, 0); 4930 var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); 4931 if ( ret === 0 ) { 4932 hasDuplicate = true; 4933 } 4934 return ret; 4935 }; 4936 } 4937 4938 // Utility function for retreiving the text value of an array of DOM nodes 4939 Sizzle.getText = function( elems ) { 4940 var ret = "", elem; 4941 4942 for ( var i = 0; elems[i]; i++ ) { 4943 elem = elems[i]; 4944 4945 // Get the text from text nodes and CDATA nodes 4946 if ( elem.nodeType === 3 || elem.nodeType === 4 ) { 4947 ret += elem.nodeValue; 4948 4949 // Traverse everything else, except comment nodes 4950 } else if ( elem.nodeType !== 8 ) { 4951 ret += Sizzle.getText( elem.childNodes ); 4952 } 4953 } 4954 4955 return ret; 4956 }; 4957 4958 // Check to see if the browser returns elements by name when 4959 // querying by getElementById (and provide a workaround) 4960 (function(){ 4961 // We're going to inject a fake input element with a specified name 4962 var form = document.createElement("div"), 4963 id = "script" + (new Date()).getTime(); 4964 form.innerHTML = "<a name='" + id + "'/>"; 4965 4966 // Inject it into the root element, check its status, and remove it quickly 4967 var root = document.documentElement; 4968 root.insertBefore( form, root.firstChild ); 4969 4970 // The workaround has to do additional checks after a getElementById 4971 // Which slows things down for other browsers (hence the branching) 4972 if ( document.getElementById( id ) ) { 4973 Expr.find.ID = function(match, context, isXML){ 4974 if ( typeof context.getElementById !== "undefined" && !isXML ) { 4975 var m = context.getElementById(match[1]); 4976 return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; 4977 } 4978 }; 4979 4980 Expr.filter.ID = function(elem, match){ 4981 var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); 4982 return elem.nodeType === 1 && node && node.nodeValue === match; 4983 }; 4984 } 4985 4986 root.removeChild( form ); 4987 root = form = null; // release memory in IE 4988 })(); 4989 4990 (function(){ 4991 // Check to see if the browser returns only elements 4992 // when doing getElementsByTagName("*") 4993 4994 // Create a fake element 4995 var div = document.createElement("div"); 4996 div.appendChild( document.createComment("") ); 4997 4998 // Make sure no comments are found 4999 if ( div.getElementsByTagName("*").length > 0 ) { 5000 Expr.find.TAG = function(match, context){ 5001 var results = context.getElementsByTagName(match[1]); 5002 5003 // Filter out possible comments 5004 if ( match[1] === "*" ) { 5005 var tmp = []; 5006 5007 for ( var i = 0; results[i]; i++ ) { 5008 if ( results[i].nodeType === 1 ) { 5009 tmp.push( results[i] ); 5010 } 5011 } 5012 5013 results = tmp; 5014 } 5015 5016 return results; 5017 }; 5018 } 5019 5020 // Check to see if an attribute returns normalized href attributes 5021 div.innerHTML = "<a href='#'></a>"; 5022 if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && 5023 div.firstChild.getAttribute("href") !== "#" ) { 5024 Expr.attrHandle.href = function(elem){ 5025 return elem.getAttribute("href", 2); 5026 }; 5027 } 5028 5029 div = null; // release memory in IE 5030 })(); 5031 5032 if ( document.querySelectorAll ) { 5033 (function(){ 5034 var oldSizzle = Sizzle, div = document.createElement("div"); 5035 div.innerHTML = "<p class='TEST'></p>"; 5036 5037 // Safari can't handle uppercase or unicode characters when 5038 // in quirks mode. 5039 if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { 5040 return; 5041 } 5042 5043 Sizzle = function(query, context, extra, seed){ 5044 context = context || document; 5045 5046 // Only use querySelectorAll on non-XML documents 5047 // (ID selectors don't work in non-HTML documents) 5048 if ( !seed && context.nodeType === 9 && !Sizzle.isXML(context) ) { 5049 try { 5050 return makeArray( context.querySelectorAll(query), extra ); 5051 } catch(e){} 5052 } 5053 5054 return oldSizzle(query, context, extra, seed); 5055 }; 5056 5057 for ( var prop in oldSizzle ) { 5058 Sizzle[ prop ] = oldSizzle[ prop ]; 5059 } 5060 5061 div = null; // release memory in IE 5062 })(); 5063 } 5064 5065 (function(){ 5066 var div = document.createElement("div"); 5067 5068 div.innerHTML = "<div class='test e'></div><div class='test'></div>"; 5069 5070 // Opera can't find a second classname (in 9.6) 5071 // Also, make sure that getElementsByClassName actually exists 5072 if ( !div.getElementsByClassName || div.getElementsByClassName("e").length === 0 ) { 5073 return; 5074 } 5075 5076 // Safari caches class attributes, doesn't catch changes (in 3.2) 5077 div.lastChild.className = "e"; 5078 5079 if ( div.getElementsByClassName("e").length === 1 ) { 5080 return; 5081 } 5082 5083 Expr.order.splice(1, 0, "CLASS"); 5084 Expr.find.CLASS = function(match, context, isXML) { 5085 if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { 5086 return context.getElementsByClassName(match[1]); 5087 } 5088 }; 5089 5090 div = null; // release memory in IE 5091 })(); 5092 5093 function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { 5094 for ( var i = 0, l = checkSet.length; i < l; i++ ) { 5095 var elem = checkSet[i]; 5096 if ( elem ) { 5097 elem = elem[dir]; 5098 var match = false; 5099 5100 while ( elem ) { 5101 if ( elem.sizcache === doneName ) { 5102 match = checkSet[elem.sizset]; 5103 break; 5104 } 5105 5106 if ( elem.nodeType === 1 && !isXML ){ 5107 elem.sizcache = doneName; 5108 elem.sizset = i; 5109 } 5110 5111 if ( elem.nodeName.toLowerCase() === cur ) { 5112 match = elem; 5113 break; 5114 } 5115 5116 elem = elem[dir]; 5117 } 5118 5119 checkSet[i] = match; 5120 } 5121 } 5122 } 5123 5124 function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { 5125 for ( var i = 0, l = checkSet.length; i < l; i++ ) { 5126 var elem = checkSet[i]; 5127 if ( elem ) { 5128 elem = elem[dir]; 5129 var match = false; 5130 5131 while ( elem ) { 5132 if ( elem.sizcache === doneName ) { 5133 match = checkSet[elem.sizset]; 5134 break; 5135 } 5136 5137 if ( elem.nodeType === 1 ) { 5138 if ( !isXML ) { 5139 elem.sizcache = doneName; 5140 elem.sizset = i; 5141 } 5142 if ( typeof cur !== "string" ) { 5143 if ( elem === cur ) { 5144 match = true; 5145 break; 5146 } 5147 5148 } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { 5149 match = elem; 5150 break; 5151 } 5152 } 5153 5154 elem = elem[dir]; 5155 } 5156 5157 checkSet[i] = match; 5158 } 5159 } 5160 } 5161 5162 Sizzle.contains = document.compareDocumentPosition ? function(a, b){ 5163 return !!(a.compareDocumentPosition(b) & 16); 5164 } : function(a, b){ 5165 return a !== b && (a.contains ? a.contains(b) : true); 5166 }; 5167 5168 Sizzle.isXML = function(elem){ 5169 // documentElement is verified for cases where it doesn't yet exist 5170 // (such as loading iframes in IE - #4833) 5171 var documentElement = (elem ? elem.ownerDocument || elem : 0).documentElement; 5172 return documentElement ? documentElement.nodeName !== "HTML" : false; 5173 }; 5174 5175 var posProcess = function(selector, context){ 5176 var tmpSet = [], later = "", match, 5177 root = context.nodeType ? [context] : context; 5178 5179 // Position selectors must be done after the filter 5180 // And so must :not(positional) so we move all PSEUDOs to the end 5181 while ( (match = Expr.match.PSEUDO.exec( selector )) ) { 5182 later += match[0]; 5183 selector = selector.replace( Expr.match.PSEUDO, "" ); 5184 } 5185 5186 selector = Expr.relative[selector] ? selector + "*" : selector; 5187 5188 for ( var i = 0, l = root.length; i < l; i++ ) { 5189 Sizzle( selector, root[i], tmpSet ); 5190 } 5191 5192 return Sizzle.filter( later, tmpSet ); 5193 }; 5194 5195 // Add Sizzle to Glow 5196 // This file is injected into sizzle.js by the ant "deps" target 5197 Glow.provide(function(glow) { 5198 glow._sizzle = Sizzle; 5199 }); 5200 5201 return; 5202 5203 5204 window.Sizzle = Sizzle; 5205 5206 })(); 5207 Glow.provide(function(glow) { 5208 var NodeListProto = glow.NodeList.prototype 5209 /* 5210 PrivateVar: ucheck 5211 Used by unique(), increased by 1 on each use 5212 */ 5213 , ucheck = 1 5214 /* 5215 PrivateVar: ucheckPropName 5216 This is the property name used by unique checks 5217 */ 5218 5219 , ucheckPropName = "_unique" + glow.UID; 5220 /* 5221 PrivateMethod: unique 5222 Get an array of nodes without duplicate nodes from an array of nodes. 5223 5224 Arguments: 5225 aNodes - (Array|<NodeList>) 5226 5227 Returns: 5228 An array of nodes without duplicates. 5229 */ 5230 //worth checking if it's an XML document? 5231 if (glow.env.ie) { 5232 var unique = function(aNodes) { 5233 if (aNodes.length == 1) { return aNodes; } 5234 5235 //remove duplicates 5236 var r = [], 5237 ri = 0, 5238 i = 0; 5239 5240 for (; aNodes[i]; i++) { 5241 if (aNodes[i].getAttribute(ucheckPropName) != ucheck && aNodes[i].nodeType == 1) { 5242 r[ri++] = aNodes[i]; 5243 } 5244 aNodes[i].setAttribute(ucheckPropName, ucheck); 5245 } 5246 for (i=0; aNodes[i]; i++) { 5247 aNodes[i].removeAttribute(ucheckPropName); 5248 } 5249 ucheck++; 5250 return r; 5251 } 5252 } else { 5253 var unique = function(aNodes) { 5254 if (aNodes.length == 1) { return aNodes; } 5255 5256 //remove duplicates 5257 var r = [], 5258 ri = 0, 5259 i = 0; 5260 5261 for (; aNodes[i]; i++) { 5262 if (aNodes[i][ucheckPropName] != ucheck && aNodes[i].nodeType == 1) { 5263 r[ri++] = aNodes[i]; 5264 } 5265 aNodes[i][ucheckPropName] = ucheck; 5266 } 5267 ucheck++; 5268 return r; 5269 } 5270 }; 5271 /** 5272 @name glow.NodeList#parent 5273 @function 5274 @description Gets the unique parent nodes of each node as a new NodeList. 5275 @param {string | HTMLElement | NodeList} [search] Search value 5276 If provided, will seek the next parent element until a match is found 5277 @returns {glow.NodeList} 5278 5279 Returns a new NodeList containing the parent nodes, with 5280 duplicates removed 5281 5282 @example 5283 // elements which contain links 5284 var parents = glow.dom.get("a").parent(); 5285 */ 5286 NodeListProto.parent = function(search) { 5287 var ret = [], 5288 ri = 0, 5289 i = this.length, 5290 node; 5291 5292 while (i--) { 5293 node = this[i]; 5294 if (node.nodeType == 1) { 5295 if(search){ 5296 while(node = node.parentNode){ 5297 if (glow._sizzle.filter(search, [node]).length) { 5298 ret[ri++] = node; 5299 break; 5300 } 5301 } 5302 } 5303 5304 else if(node = node.parentNode){ 5305 ret[ri++] = node; 5306 } 5307 5308 } 5309 5310 } 5311 5312 return new glow.NodeList(unique(ret)); 5313 }; 5314 5315 /* Private method for prev() and next() */ 5316 function getNextOrPrev(nodelist, dir, search) { 5317 var ret = [], 5318 ri = 0, 5319 node, 5320 i = 0, 5321 length = nodelist.length; 5322 5323 while (i < length) { 5324 node = nodelist[i]; 5325 if(search){ 5326 while (node = node[dir + 'Sibling']) { 5327 if (node.nodeType == 1 && node.nodeName != '!') { 5328 if (glow._sizzle.filter(search, [node]).length) { 5329 ret[ri++] = node; 5330 break; 5331 } 5332 } 5333 } 5334 } 5335 else{ 5336 while (node = node[dir + 'Sibling']) { 5337 if (node.nodeType == 1 && node.nodeName != '!') { 5338 ret[ri++] = node; 5339 break; 5340 } 5341 } 5342 } 5343 i++; 5344 } 5345 return new glow.NodeList(ret); 5346 } 5347 5348 /** 5349 @name glow.NodeList#prev 5350 @function 5351 @description Gets the previous sibling element for each node in the ElementList. 5352 If a filter is provided, the previous item that matches the filter is returned, or 5353 none if no match is found. 5354 @param {string | HTMLElement | NodeList} [search] Search value 5355 If provided, will seek the previous sibling element until a match is found 5356 @returns {glow.ElementList} 5357 A new ElementList containing the previous sibling elements that match the (optional) 5358 filter. 5359 @example 5360 // gets the element before #myLink (if there is one) 5361 var next = glow.get("#myLink").prev(); 5362 @example 5363 // get the previous sibling link element before #skipLink 5364 glow.get('#skipLink').prev('a') 5365 */ 5366 NodeListProto.prev = function(search) { 5367 return getNextOrPrev(this, 'previous', search); 5368 }; 5369 5370 /** 5371 @name glow.NodeList#next 5372 @function 5373 @description Gets the next sibling element for each node in the ElementList. 5374 If a filter is provided, the next item that matches the filter is returned, or 5375 none if no match is found. 5376 @param {string | HTMLElement | NodeList} [search] Search value 5377 If provided, will seek the next sibling element until a match is found 5378 @returns {glow.ElementList} 5379 A new ElementList containing the next sibling elements that match the (optional) 5380 filter. 5381 @example 5382 // gets the element following #myLink (if there is one) 5383 var next = glow.get("#myLink").next(); 5384 @example 5385 // get the next sibling link element after #skipLink 5386 glow.get('#skipLink').next('a') 5387 */ 5388 NodeListProto.next = function(search) { 5389 return getNextOrPrev(this, 'next', search); 5390 }; 5391 5392 5393 /** 5394 @name glow.NodeList#get 5395 @function 5396 @description Gets decendents of nodes that match a CSS selector. 5397 5398 @param {String} selector CSS selector 5399 5400 @returns {glow.NodeList} 5401 Returns a new NodeList containing matched elements 5402 5403 @example 5404 // create a new NodeList 5405 var myNodeList = glow.dom.create("<div><a href='s.html'>Link</a></div>"); 5406 5407 // get 'a' tags that are decendants of the NodeList nodes 5408 myNewNodeList = myNodeList.get("a"); 5409 */ 5410 NodeListProto.get = function(selector) { 5411 var ret = [], 5412 i = this.length; 5413 5414 while (i--) { 5415 glow._sizzle(selector, this[i], ret); 5416 5417 } 5418 // need to remove uniqueSorts because they're slow. Replace with own method for unique. 5419 return new glow.NodeList(unique(ret)); 5420 }; 5421 5422 5423 5424 /** 5425 @name glow.NodeList#ancestors 5426 @function 5427 @description Gets the unique ancestor nodes of each node as a new NodeList. 5428 @param {Function|string} [filter] Filter test 5429 If a string is provided, it is used in a call to {@link glow.ElementList#is ElementList#is}. 5430 If a function is provided it will be passed 2 arguments, the index of the current item, 5431 and the ElementList being itterated over. 5432 Inside the function 'this' refers to the HTMLElement. 5433 Return true to keep the node, or false to remove it. 5434 @returns {glow.dom.NodeList} 5435 Returns NodeList 5436 5437 @example 5438 // get ancestor elements for anchor elements 5439 var ancestors = glow.dom.get("a").ancestors(); 5440 */ 5441 NodeListProto.ancestors = function(filter) { 5442 var ret = [], 5443 ri = 0, 5444 i = 0, 5445 length = this.length, 5446 node; 5447 5448 while (i < length) { 5449 node = this[i].parentNode; 5450 5451 while (node && node.nodeType == 1) { 5452 ret[ri++] = node; 5453 node = node.parentNode; 5454 } 5455 i++; 5456 } 5457 if(filter){ 5458 ret = new glow.NodeList(ret); 5459 ret = ret.filter(filter); 5460 } 5461 return new glow.NodeList(unique(ret)); 5462 }; 5463 5464 /* 5465 Private method to get the child elements for an html node (used by children()) 5466 */ 5467 function getChildElms(node) { 5468 var r = [], 5469 childNodes = node.childNodes, 5470 i = 0, 5471 ri = 0; 5472 5473 for (; childNodes[i]; i++) { 5474 if (childNodes[i].nodeType == 1 && childNodes[i].nodeName != '!') { 5475 r[ri++] = childNodes[i]; 5476 } 5477 } 5478 return r; 5479 } 5480 5481 /** 5482 @name glow.NodeList#children 5483 @function 5484 @description Gets the child elements of each node as a new NodeList. 5485 5486 @returns {glow.dom.NodeList} 5487 5488 Returns a new NodeList containing all the child nodes 5489 5490 @example 5491 // get all list items 5492 var items = glow.dom.get("ul, ol").children(); 5493 */ 5494 NodeListProto.children = function() { 5495 var ret = [], 5496 i = this.length; 5497 5498 while(i--) { 5499 ret = ret.concat( getChildElms(this[i]) ); 5500 } 5501 return new glow.NodeList(ret); 5502 }; 5503 5504 /** 5505 @name glow.NodeList#contains 5506 @function 5507 @description Find if this NodeList contains the given element 5508 5509 @param {string | HTMLELement | NodeList} Single element to check for 5510 5511 @returns {boolean} 5512 myElementList.contains(elm) 5513 // Returns true if an element in myElementList contains elm, or IS elm. 5514 */ 5515 NodeListProto.contains = function(elm) { 5516 var i = 0, 5517 node = new glow.NodeList(elm)[0], 5518 length = this.length, 5519 newNodes, 5520 toTest; 5521 5522 // missing some nodes? Return false 5523 if ( !node || !this.length ) { 5524 return false; 5525 } 5526 5527 if (this[0].compareDocumentPosition) { //w3 method 5528 while (i < length) { 5529 //break out if the two are teh same 5530 if(this[i] == node){ 5531 break; 5532 } 5533 //check against bitwise to see if node is contained in this 5534 else if (!(this[i].compareDocumentPosition(node) & 16)) { 5535 return false; 5536 } 5537 i++; 5538 } 5539 } 5540 else if(node.contains){ 5541 for (; i < length; i++) { 5542 if ( !( this[i].contains( node ) ) ) { 5543 return false; 5544 } 5545 } 5546 } 5547 else { //manual method for last chance corale 5548 while (i < length) { 5549 toTest = node; 5550 while (toTest = toTest.parentNode) { 5551 if (this[i] == toTest) { break; } 5552 } 5553 if (!toTest) { 5554 return false; 5555 } 5556 i++; 5557 } 5558 } 5559 5560 return true; 5561 }; 5562 }); 5563 Glow.provide(function(glow) { 5564 var NodeListProto = glow.NodeList.prototype, 5565 document = window.document, 5566 undefined; 5567 5568 // create a fragment and insert a set of nodes into it 5569 function createFragment(nodes) { 5570 var fragment = document.createDocumentFragment(), 5571 i = 0, 5572 node; 5573 5574 while ( node = nodes[i++] ) { 5575 fragment.appendChild(node); 5576 } 5577 5578 return fragment; 5579 } 5580 5581 // generate the #before and #after methods 5582 // after: 1 for #(insert)after, 0 for #(insert)before 5583 // insert: 1 for #insert(After|Before), 0 for #(after|before) 5584 function afterAndBefore(after, insert) { 5585 return function(elements) { 5586 var toAddList, 5587 toAddToList, 5588 fragmentToAdd, 5589 nextFragmentToAdd, 5590 item, 5591 itemParent; 5592 5593 if (!this.length) { return this; } 5594 5595 // normalise 'elements' 5596 // if we're dealing with append/prepend then strings are always treated as HTML strings 5597 if (!insert && typeof elements === 'string') { 5598 elements = new glow.NodeList( glow.NodeList._strToNodes(elements) ); 5599 } 5600 else { 5601 elements = new glow.NodeList(elements); 5602 } 5603 5604 // set the element we're going to add to, and the elements we're going to add 5605 if (insert) { 5606 toAddToList = elements; 5607 toAddList = new glow.NodeList(this); 5608 } 5609 else { 5610 toAddToList = this; 5611 toAddList = elements; 5612 } 5613 5614 nextFragmentToAdd = createFragment(toAddList); 5615 5616 for (var i = 0, leni = toAddToList.length, lasti = leni - 1; i < leni; i++) { 5617 item = toAddToList[i]; 5618 fragmentToAdd = nextFragmentToAdd; 5619 5620 // we can only append after if the element has a parent right? 5621 if (itemParent = item.parentNode) { 5622 if (i !== lasti) { // if not the last item 5623 nextFragmentToAdd = fragmentToAdd.cloneNode(true); 5624 insert && toAddList.push(nextFragmentToAdd.childNodes); 5625 } 5626 itemParent.insertBefore(fragmentToAdd, after ? item.nextSibling : item); 5627 } 5628 } 5629 5630 return insert ? toAddList : toAddToList; 5631 } 5632 } 5633 5634 // generate the #append, #appendTo, #prepend and #prependTo methods 5635 // append: 1 for #append(To), 0 for #prepend(To) 5636 // to: 1 for #(append|prepend)To, 0 for #(append|prepend) 5637 function appendAndPrepend(append, to) { 5638 return function(elements) { 5639 var toAddList, 5640 toAddToList, 5641 fragmentToAdd, 5642 nextFragmentToAdd, 5643 item; 5644 5645 if (!this.length) { return this; } 5646 5647 // normalise 'elements' 5648 // if we're dealing with append/prepend then strings are always treated as HTML strings 5649 if (!to && typeof elements === 'string') { 5650 elements = new glow.NodeList( glow.NodeList._strToNodes(elements) ); 5651 } 5652 else { 5653 elements = new glow.NodeList(elements); 5654 } 5655 5656 // set the element we're going to add to, and the elements we're going to add 5657 if (to) { 5658 toAddToList = elements; 5659 toAddList = new glow.NodeList(this); 5660 } 5661 else { 5662 toAddToList = this; 5663 toAddList = elements; 5664 } 5665 5666 nextFragmentToAdd = createFragment(toAddList); 5667 5668 for (var i = 0, leni = toAddToList.length, lasti = leni - 1; i < leni; i++) { 5669 item = toAddToList[i]; 5670 fragmentToAdd = nextFragmentToAdd; 5671 5672 // avoid trying to append to non-elements 5673 if (item.nodeType === 1) { 5674 if (i !== lasti) { // if not the last item 5675 nextFragmentToAdd = fragmentToAdd.cloneNode(true); 5676 // add the clones to the return element for appendTo / prependTo 5677 to && toAddList.push(nextFragmentToAdd.childNodes); 5678 } 5679 item.insertBefore(fragmentToAdd, append ? null : item.firstChild); 5680 } 5681 } 5682 5683 return to ? toAddList : toAddToList; 5684 } 5685 } 5686 5687 /** 5688 @name glow.NodeList#after 5689 @function 5690 @description Insert node(s) after each node in this NodeList. 5691 If there is more than one node in this NodeList, 'nodes' 5692 will be inserted after the first element and clones will be 5693 inserted after each subsequent element. 5694 5695 @param {string | HTMLElement | HTMLElement[] | glow.NodeList} nodes Node(s) to insert 5696 Strings will be treated as HTML strings. 5697 5698 @returns {glow.NodeList} Original NodeList 5699 5700 @example 5701 // adds a paragraph after each heading 5702 glow('h1, h2, h3').after('<p>That was a nice heading.</p>'); 5703 */ 5704 NodeListProto.after = afterAndBefore(1); 5705 5706 /** 5707 @name glow.NodeList#before 5708 @function 5709 @description Insert node(s) before each node in this NodeList. 5710 If there is more than one node in this NodeList, 'nodes' 5711 will be inserted before the first element and clones will be 5712 inserted before each subsequent element. 5713 5714 @param {string | HTMLElement | HTMLElement[] | glow.NodeList} nodes Node(s) to insert 5715 Strings will be treated as HTML strings. 5716 5717 @returns {glow.NodeList} Original NodeList 5718 5719 @example 5720 // adds a div before each paragraph 5721 glow('p').before('<div>Here comes a paragraph!</div>'); 5722 */ 5723 NodeListProto.before = afterAndBefore(0); 5724 5725 /** 5726 @name glow.NodeList#append 5727 @function 5728 @description Appends node to each node in this NodeList. 5729 If there is more than one node in this NodeList, then the given nodes 5730 are appended to the first node and clones are appended to the other 5731 nodes. 5732 5733 @param {string | HTMLElement | HTMLElement[] | glow.NodeList} nodes Nodes(s) to append 5734 Strings will be treated as HTML strings. 5735 5736 @returns {glow.NodeList} Original NodeList 5737 5738 @example 5739 // ends every paragraph with '...' 5740 glow('p').append('<span>...</span>'); 5741 */ 5742 NodeListProto.append = appendAndPrepend(1); 5743 5744 /** 5745 @name glow.NodeList#prepend 5746 @function 5747 @description Prepends nodes to each node in this NodeList. 5748 If there is more than one node in this NodeList, then the given nodes 5749 are prepended to the first node and clones are prepended to the other 5750 nodes. 5751 5752 @param {string | HTMLElement | HTMLElement[] | glow.NodeList} nodes Nodes(s) to prepend 5753 Strings will be treated as HTML strings. 5754 5755 @returns {glow.NodeList} Original NodeList 5756 5757 @example 5758 // prepends every paragraph with 'Paragraph: ' 5759 glow('p').prepend('<span>Paragraph: </span>'); 5760 */ 5761 NodeListProto.prepend = appendAndPrepend(0); 5762 5763 /** 5764 @name glow.NodeList#appendTo 5765 @function 5766 @description Appends nodes in this NodeList to given node(s) 5767 If appending to more than one node, the NodeList is appended 5768 to the first node and clones are appended to the others. 5769 5770 @param {string | HTMLElement | HTMLElement[] | glow.NodeList} node Node(s) to append to. 5771 Strings will be treated as CSS selectors or HTML strings. 5772 5773 @returns {glow.NodeList} The appended nodes. 5774 5775 @example 5776 // appends '...' to every paragraph 5777 glow('<span>...</span>').appendTo('p'); 5778 */ 5779 NodeListProto.appendTo = appendAndPrepend(1, 1); 5780 5781 /** 5782 @name glow.NodeList#prependTo 5783 @function 5784 @description Prepends nodes in this NodeList to given node(s) 5785 If prepending to more than one node, the NodeList is prepended 5786 to the first node and clones are prepended to the others. 5787 5788 @param {string | HTMLElement | HTMLElement[] | glow.NodeList} node Node(s) to prepend to 5789 Strings will be treated as CSS selectors or HTML strings. 5790 5791 @returns {glow.NodeList} The prepended nodes. 5792 5793 @example 5794 // prepends 'Paragraph: ' to every paragraph 5795 glow('<span>Paragraph: </span>').prependTo('p'); 5796 */ 5797 NodeListProto.prependTo = appendAndPrepend(0, 1); 5798 5799 /** 5800 @name glow.NodeList#insertAfter 5801 @function 5802 @description Insert this NodeList after the given nodes 5803 If inserting after more than one node, the NodeList is inserted 5804 after the first node and clones are inserted after the others. 5805 5806 @param {string | HTMLElement | HTMLElement[] | glow.NodeList} nodes Node(s) to insert after 5807 Strings will be treated as CSS selectors. 5808 5809 @returns {glow.NodeList} Inserted nodes. 5810 5811 @example 5812 // adds a paragraph after each heading 5813 glow('<p>HAI!</p>').insertAfter('h1, h2, h3'); 5814 */ 5815 NodeListProto.insertAfter = afterAndBefore(1, 1); 5816 5817 /** 5818 @name glow.NodeList#insertBefore 5819 @function 5820 @description Insert this NodeList before the given nodes 5821 If inserting before more than one node, the NodeList is inserted 5822 before the first node and clones are inserted before the others. 5823 5824 @param {string | HTMLElement | HTMLElement[] | glow.NodeList} nodes Node(s) to insert before 5825 Strings will be treated as CSS selectors. 5826 5827 @returns {glow.NodeList} Inserted nodes. 5828 5829 @example 5830 // adds a div before each paragraph 5831 glow('<div>Here comes a paragraph!</div>').insertBefore('p'); 5832 */ 5833 NodeListProto.insertBefore = afterAndBefore(0, 1); 5834 5835 /** 5836 @name glow.NodeList#destroy 5837 @function 5838 @description Removes each element from the document 5839 The element, attached listeners & attached data will be 5840 destroyed to free up memory. 5841 5842 Detroyed elements may not be reused in some browsers. 5843 5844 @returns undefined 5845 5846 @example 5847 // destroy all links in the document 5848 glow("a").destroy(); 5849 */ 5850 var tmpDiv = document.createElement('div'); 5851 5852 NodeListProto.destroy = function() { 5853 var allElements = this.get('*').push(this); 5854 5855 // remove data and listeners 5856 glow.events.removeAllListeners( allElements.removeData() ); 5857 5858 this.appendTo(tmpDiv); 5859 tmpDiv.innerHTML = ''; 5860 }; 5861 5862 /** 5863 @name glow.NodeList#remove 5864 @function 5865 @description Removes each element from the document 5866 If you no longer need the elements, consider using 5867 {@link glow.NodeList#destroy destroy} 5868 5869 @returns {glow.NodeList} The removed elements 5870 5871 @example 5872 // take all the links out of a document 5873 glow("a").remove(); 5874 */ 5875 NodeListProto.remove = function() { 5876 var parent, 5877 node, 5878 i = this.length; 5879 5880 while (i--) { 5881 node = this[i]; 5882 if (parent = node.parentNode) { 5883 parent.removeChild(node); 5884 } 5885 } 5886 5887 return this; 5888 }; 5889 5890 /** 5891 @name glow.NodeList#empty 5892 @function 5893 @description Removes the nodes' contents 5894 5895 @returns {glow.NodeList} Original nodes 5896 5897 @example 5898 // remove the contents of all textareas 5899 glow("textarea").empty(); 5900 */ 5901 // TODO: is this shortcut worth doing? 5902 NodeListProto.empty = glow.env.ie ? 5903 // When you clean an element out using innerHTML it destroys its inner text nodes in IE8 and below 5904 // Here's an alternative method for IE: 5905 function() { 5906 var i = this.length, node, child; 5907 5908 while (i--) { 5909 node = this[i]; 5910 while (child = node.firstChild) { 5911 node.removeChild(child); 5912 } 5913 } 5914 5915 return this; 5916 } : 5917 // method for most browsers 5918 function() { 5919 var i = this.length; 5920 5921 while (i--) { 5922 this[i].innerHTML = ''; 5923 } 5924 5925 return this; 5926 } 5927 5928 /** 5929 @name glow.NodeList#replaceWith 5930 @function 5931 @description Replace elements with another 5932 5933 @param {string | HTMLElement | HTMLElement[] | glow.NodeList} elements Element(s) to insert into the document 5934 If there is more than one element in the NodeList, then the given elements 5935 replace the first element, clones are appended to the other elements. 5936 5937 @returns {glow.NodeList} The replaced elements 5938 Call {@link glow.NodeList#destroy destroy} on these if you 5939 no longer need them. 5940 */ 5941 NodeListProto.replaceWith = function(elements) { 5942 return this.after(elements).remove(); 5943 }; 5944 5945 /** 5946 @name glow.NodeList#wrap 5947 @function 5948 @description Wraps the given NodeList with the specified element(s). 5949 The given NodeList items will always be placed in the first 5950 child element that contains no further elements. 5951 5952 Each item in a given NodeList will be wrapped individually. 5953 5954 @param {string | HTMLElement | HTMLElement[] | glow.NodeList} wrapper Element to use as a wrapper 5955 Strings will be treated as HTML strings if they begin with <, else 5956 they'll be treated as a CSS selector. 5957 5958 @returns {glow.NodeList} The NodeList with new wrapper parents 5959 5960 @example 5961 // <span id="mySpan">Hello</span> 5962 glow("#mySpan").wrap("<div><p></p></div>"); 5963 // Makes: 5964 // <div> 5965 // <p> 5966 // <span id="mySpan">Hello</span> 5967 // </p> 5968 // </div> 5969 5970 */ 5971 // get first child element node of an element, otherwise undefined 5972 function getFirstChildElm(parent) { 5973 for (var child = parent.firstChild; child; child = child.nextSibling) { 5974 if (child.nodeType == 1) { 5975 return child; 5976 } 5977 } 5978 return undefined; 5979 } 5980 5981 NodeListProto.wrap = function(wrapper) { 5982 // normalise input 5983 wrapper = new glow.NodeList(wrapper); 5984 5985 // escape if the wraper is non-existant or not an element 5986 if (!wrapper[0] || wrapper[0].nodeType != 1) { 5987 return this; 5988 } 5989 5990 var toWrap, 5991 toWrapTarget, 5992 firstChildElm; 5993 5994 for (var i = 0, leni = this.length; i<leni; i++) { 5995 toWrap = this[i]; 5996 // get target element to insert toWrap in 5997 toWrapTarget = wrapper[0]; 5998 5999 while (toWrapTarget) { 6000 firstChildElm = getFirstChildElm(toWrapTarget); 6001 6002 if (!firstChildElm) { 6003 break; 6004 } 6005 toWrapTarget = firstChildElm; 6006 } 6007 6008 if (toWrap.parentNode) { 6009 wrapper.insertBefore(toWrap); 6010 } 6011 6012 // If wrapping multiple nodes, we need to take a clean copy of the wrapping nodes 6013 if (i != leni-1) { 6014 wrapper = wrapper.clone(); 6015 } 6016 6017 toWrapTarget.appendChild(toWrap); 6018 } 6019 6020 return this; 6021 }; 6022 6023 /** 6024 @name glow.NodeList#unwrap 6025 @function 6026 @description Removes the parent of each item in the list 6027 6028 @returns {glow.NodeList} The now unwrapped elements 6029 6030 @example 6031 // Before: <div><p><span id="mySpan">Hello</span></p></div> 6032 // unwrap the given element 6033 glow("#mySpan").unwrap(); 6034 // After: <div><span id="mySpan">Hello</span></div> 6035 */ 6036 NodeListProto.unwrap = function() { 6037 var parentToRemove, 6038 childNodes, 6039 // get unique parents 6040 parentsToRemove = this.parent(); 6041 6042 for (var i = 0, leni = parentsToRemove.length; i < leni; i++) { 6043 parentToRemove = parentsToRemove.slice(i, i+1); 6044 // make sure we get all children, including text nodes 6045 childNodes = new glow.NodeList( parentToRemove[0].childNodes ); 6046 6047 // 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 6048 if (!parentToRemove[0].parentNode){ 6049 childNodes.remove(); 6050 parentToRemove.destroy(); 6051 } 6052 else { 6053 childNodes.insertBefore(parentToRemove); 6054 parentToRemove.destroy(); 6055 } 6056 } 6057 return this; 6058 }; 6059 6060 /** 6061 @name glow.NodeList#clone 6062 @function 6063 @description Clones each node in the NodeList, along with data & event listeners 6064 6065 @returns {glow.NodeList} 6066 Returns a new NodeList containing clones of all the nodes in 6067 the NodeList 6068 6069 @example 6070 // get a copy of all heading elements 6071 var myClones = glow.get("h1, h2, h3, h4, h5, h6").clone(); 6072 */ 6073 6074 NodeListProto.clone = function() { 6075 var clonedNodeList = this.copy(), 6076 allCloneElms = clonedNodeList.get('*').push(clonedNodeList), 6077 allElms = this.get('*').push(this); 6078 6079 // now copy over the data and events for all cloned elements 6080 glow.events._copyDomEvents(allElms, allCloneElms); 6081 glow.NodeList._copyData(allElms, allCloneElms); 6082 6083 return clonedNodeList; 6084 }; 6085 6086 6087 /** 6088 @name glow.NodeList#copy 6089 @function 6090 @description Copies each node in the NodeList, excluding data & event listeners 6091 6092 @returns {glow.NodeList} 6093 Returns a new NodeList containing copies of all the nodes in 6094 the NodeList 6095 6096 @example 6097 // get a copy of all heading elements 6098 var myCopies = glow.get("h1, h2, h3, h4, h5, h6").copy(); 6099 */ 6100 NodeListProto.copy = function() { 6101 var nodes = [], 6102 i = this.length, 6103 clonedNodeList, 6104 allCloneElms, 6105 eventIdProp = '__eventId' + glow.UID, 6106 dataPropName = '_uniqueData' + glow.UID; 6107 6108 while (i--) { 6109 nodes[i] = this[i].cloneNode(true); 6110 } 6111 6112 clonedNodeList = new glow.NodeList(nodes); 6113 6114 // IE also clones node properties as attributes 6115 // we need to get rid of the eventId & dataId 6116 if (glow.env.ie) { 6117 allCloneElms = clonedNodeList.get('*').push(nodes); 6118 i = allCloneElms.length; 6119 while (i--) { 6120 allCloneElms[i][dataPropName] = allCloneElms[i][eventIdProp] = undefined; 6121 } 6122 } 6123 6124 return clonedNodeList; 6125 }; 6126 6127 /** 6128 @name glow.NodeList#html 6129 @function 6130 @description Gets / sets HTML content 6131 Either gets content of the first element, or sets the content 6132 for all elements in the list 6133 6134 @param {String} [htmlString] String to set as the HTML of elements 6135 If omitted, the html for the first element in the list is 6136 returned. 6137 6138 @returns {glow.NodeList | string} 6139 Returns the original NodeList when setting, 6140 or the HTML content when getting. 6141 6142 @example 6143 // get the html in #footer 6144 var footerContents = glow("#footer").html(); 6145 6146 @example 6147 // set a new footer 6148 glow("#footer").html("<strong>Hello World!</strong>"); 6149 */ 6150 NodeListProto.html = function(htmlString) { 6151 // getting 6152 if (!arguments.length) { 6153 return this[0] ? this[0].innerHTML : ''; 6154 } 6155 6156 // setting 6157 var i = this.length, 6158 node; 6159 6160 // normalise the string 6161 htmlString = htmlString === undefined? '' : String(htmlString); 6162 6163 while (i--) { 6164 node = this[i]; 6165 if (node.nodeType == 1) { 6166 try { 6167 // this has a habit of failing in IE for some elements 6168 node.innerHTML = htmlString; 6169 } 6170 catch (e) { 6171 new glow.NodeList(node).empty().append(htmlString); 6172 } 6173 } 6174 } 6175 6176 return this; 6177 }; 6178 6179 /** 6180 @name glow.NodeList#text 6181 @function 6182 @description Gets / set the text content 6183 Either gets content of the first element, or sets the content 6184 for all elements in the list 6185 6186 @param {String} [text] String to set as the text of elements 6187 If omitted, the test for the first element in the list is 6188 returned. 6189 6190 @returns {glow.NodeList | String} 6191 Returns the original NodeList when setting, 6192 or the text content when getting. 6193 6194 @example 6195 // set text 6196 var div = glow("<div></div>").text("Fun & games!"); 6197 // <div>Func & games!</div> 6198 6199 @example 6200 // get text 6201 var mainHeading = glow('#mainHeading').text(); 6202 */ 6203 NodeListProto.text = function(textString) { 6204 var firstNode = this[0], 6205 i = this.length, 6206 node; 6207 6208 // getting 6209 if (!arguments.length) { 6210 // get the text by checking a load of properties in priority order 6211 return firstNode ? 6212 firstNode.textContent || 6213 firstNode.innerText || 6214 firstNode.nodeValue || '' // nodeValue for comment & text nodes 6215 : ''; 6216 } 6217 6218 // setting 6219 // normalise the string 6220 textString = textString ? String(textString): ''; 6221 6222 this.empty(); 6223 while (i--) { 6224 node = this[i]; 6225 if (node.nodeType == 1) { 6226 node.appendChild( document.createTextNode(textString) ); 6227 } 6228 else { 6229 node.nodeValue = textString; 6230 } 6231 } 6232 6233 return this; 6234 }; 6235 }); 6236 Glow.provide(function(glow) { 6237 var NodeList = glow.NodeList, 6238 NodeListProto = NodeList.prototype, 6239 win = window, 6240 document = win.document, 6241 getComputedStyle = document.defaultView && document.defaultView.getComputedStyle, 6242 // regex for toStyleProp 6243 dashAlphaRe = /-(\w)/g, 6244 // regex for getCssValue 6245 isNumberButNotPx = /^-?[\d\.]+(?!px)[%a-z]+$/i, 6246 ieOpacityRe = /alpha\(opacity=([^\)]+)\)/, 6247 // regex for #css 6248 hasUnits = /width|height|top$|bottom$|left$|right$|spacing$|indent$|font-size/; 6249 6250 // replace function for toStyleProp 6251 function toStylePropReplace(match, p1) { 6252 return p1.toUpperCase(); 6253 } 6254 6255 /** 6256 @private 6257 @function 6258 @description Converts a css property name into its javascript name. 6259 Such as "background-color" to "backgroundColor". 6260 @param {string} prop CSS Property name. 6261 @returns {string} 6262 */ 6263 function toStyleProp(prop) { 6264 if (prop == 'float') { 6265 return glow.env.ie ? 'styleFloat' : 'cssFloat'; 6266 } 6267 return prop.replace(dashAlphaRe, toStylePropReplace); 6268 } 6269 6270 /** 6271 @private 6272 @function 6273 @description Get a total value of multiple CSS properties 6274 @param {HTMLElement} elm 6275 @param {string[]} props CSS properties to get the total value of 6276 @returns {number} 6277 */ 6278 function getTotalCssValue(elm, props) { 6279 var total = 0, 6280 i = props.length; 6281 6282 while (i--) { 6283 total += parseFloatFunc( 6284 getCssValue( elm, props[i] ) 6285 ) || 0; 6286 } 6287 6288 return total; 6289 } 6290 6291 /** 6292 @private 6293 @function 6294 @description Get a computed css property 6295 @param {HTMLElement} elm 6296 @param {string} prop CSS property to get the value of 6297 @returns {string} 6298 */ 6299 function getCssValue(elm, prop) { 6300 var defaultView = elm.ownerDocument.defaultView, 6301 computedStyle, 6302 r, 6303 currentStyle, 6304 oldDisplay, 6305 match; 6306 6307 if (getComputedStyle) { // the W3 way 6308 computedStyle = defaultView.getComputedStyle(elm, null); 6309 6310 // http://bugs.webkit.org/show_bug.cgi?id=13343 6311 // Webkit fails to get margin-right for rendered elements. 6312 // margin-right is measured from the right of the element to the right of the parent 6313 if (glow.env.webkit && prop === 'margin-right') { 6314 oldDisplay = elm.style.display; 6315 elm.style.display = 'none'; 6316 r = computedStyle[prop]; 6317 elm.style.display = oldDisplay; 6318 } 6319 else { 6320 r = computedStyle.getPropertyValue(prop); 6321 } 6322 } 6323 else if (currentStyle = elm.currentStyle) { // the IE<9 way 6324 if (prop === 'opacity') { // opacity, the IE way 6325 match = ieOpacityRe.exec(currentStyle.filter); 6326 return match ? String(parseInt(match[1], 10) / 100) || '1' : '1'; 6327 } 6328 // catch border-*-width. IE gets this wrong if the border style is none 6329 else if ( 6330 prop.indexOf('border') === 0 && 6331 prop.slice(-5) === 'width' && 6332 getCssValue(elm, 'border-style') === 'none') { 6333 6334 return '0px'; 6335 } 6336 6337 r = currentStyle[ toStyleProp(prop) ]; 6338 6339 // font-size gives us incorrect values when put through getPixelValue, avoid 6340 if (isNumberButNotPx.test(r) && prop != 'font-size') { 6341 r = getPixelValue( elm, r, prop.indexOf('height') >= 0 || prop.indexOf('top') >= 0 ) + 'px'; 6342 } 6343 } 6344 6345 // post-process return value 6346 if (prop === 'opacity') { 6347 r = r || '1'; 6348 } 6349 else if (prop.indexOf('color') != -1) { //deal with colour values 6350 r = NodeList._parseColor(r).toString(); 6351 } 6352 6353 return r; 6354 } 6355 6356 // vars used in _parseColor 6357 var mathRound = Math.round, 6358 parseIntFunc = parseInt, 6359 parseFloatFunc = parseFloat, 6360 htmlColorNames = { 6361 black: 0x000000, 6362 silver: 0xc0c0c0, 6363 gray: 0x808080, 6364 white: 0xffffff, 6365 maroon: 0x800000, 6366 red: 0xff0000, 6367 purple: 0x800080, 6368 fuchsia: 0xff00ff, 6369 green: 0x008000, 6370 lime: 0x00ff00, 6371 olive: 0x808000, 6372 yellow: 0xffff00, 6373 navy: 0x000080, 6374 blue: 0x0000ff, 6375 teal: 0x008080, 6376 aqua: 0x00ffff, 6377 orange: 0xffa500 6378 }, 6379 // match a string like rgba(10%, 10%, 10%, 0.5) where the % and alpha parts are optional 6380 colorRegex = /^rgba?\(([\d\.]+)(%?),\s*([\d\.]+)(%?),\s*([\d\.]+)(%?)(?:,\s*([\d\.]+))?/i, 6381 transColorRegex = /^(transparent|rgba\(0, ?0, ?0, ?0\))$/, 6382 wordCharRegex = /\w/g; 6383 6384 /** 6385 @name glow.NodeList._parseColor 6386 @private 6387 @function 6388 @description Convert a CSS colour string into a normalised format 6389 @returns {string} String in format rgb(0, 0, 0) 6390 Returned string also has r, g & b number properties 6391 */ 6392 NodeList._parseColor = function (val) { 6393 if ( transColorRegex.test(val) ) { 6394 return 'rgba(0, 0, 0, 0)'; 6395 } 6396 6397 var match, //tmp regex match holder 6398 r, g, b, a, //final colour vals 6399 hex; //tmp hex holder 6400 6401 if ( match = colorRegex.exec(val) ) { //rgb() format, cater for percentages 6402 r = match[2] ? mathRound( parseFloatFunc(match[1]) * 2.55 ) : parseIntFunc(match[1]); 6403 g = match[4] ? mathRound( parseFloatFunc(match[3]) * 2.55 ) : parseIntFunc(match[3]); 6404 b = match[6] ? mathRound( parseFloatFunc(match[5]) * 2.55 ) : parseIntFunc(match[5]); 6405 a = parseFloatFunc( match[7] || '1' ); 6406 } else { 6407 if (typeof val == 'number') { 6408 hex = val; 6409 } 6410 else if (val.charAt(0) == '#') { 6411 if (val.length === 4) { //deal with #fff shortcut 6412 val = val.replace(wordCharRegex, '$&$&'); 6413 } 6414 hex = parseIntFunc(val.slice(1), 16); 6415 } 6416 else { 6417 hex = htmlColorNames[val]; 6418 } 6419 6420 r = (hex) >> 16; 6421 g = (hex & 0x00ff00) >> 8; 6422 b = (hex & 0x0000ff); 6423 a = 1; 6424 } 6425 6426 val = new String('rgba(' + r + ', ' + g + ', ' + b + ', ' + a + ')'); 6427 val.r = r; 6428 val.g = g; 6429 val.b = b; 6430 val.a = a; 6431 return val; 6432 } 6433 6434 // vars for generateWidthAndHeight 6435 var horizontalBorderPadding = [ 6436 'border-left-width', 6437 'border-right-width', 6438 'padding-left', 6439 'padding-right' 6440 ], 6441 verticalBorderPadding = [ 6442 'border-top-width', 6443 'border-bottom-width', 6444 'padding-top', 6445 'padding-bottom' 6446 ]; 6447 6448 /** 6449 @private 6450 @function 6451 @description Get width or height of an element width/height. 6452 @param {HTMLElement} elm Element to measure. 6453 @param {string} 'Width' or 'Height'. 6454 */ 6455 function getDimension(elm, cssProp) { 6456 // exit if there's no element, or it isn't an Element, window or document 6457 if ( !elm || elm.nodeType === 3 || elm.nodeType === 8 ) { 6458 return 0; 6459 } 6460 6461 var r, 6462 document = elm.ownerDocument || elm.document || elm, 6463 docElm = document.documentElement, 6464 docBody = document.body, 6465 docElmOrBody = glow.env.standardsMode ? docElm : docBody, 6466 isWidth = (cssProp == 'Width'), 6467 cssBorderPadding; 6468 6469 if (elm.window) { // is window 6470 r = glow.env.webkit ? (isWidth ? docBody.clientWidth : elm.innerHeight) : 6471 /* else */ docElmOrBody['client' + cssProp]; 6472 } 6473 else if (elm.getElementById) { // is document 6474 // we previously checked offsetWidth & clientWidth here 6475 // but they returned values too large in IE6 6476 r = Math.max( 6477 docBody['scroll' + cssProp], 6478 docElm['scroll' + cssProp] 6479 ) 6480 } 6481 else { 6482 // get an array of css borders & padding 6483 cssBorderPadding = isWidth ? horizontalBorderPadding : verticalBorderPadding; 6484 r = elm['offset' + cssProp] - getTotalCssValue(elm, cssBorderPadding); 6485 } 6486 return r; 6487 } 6488 6489 /** 6490 @private 6491 @function 6492 @description Converts a relative value into an absolute pixel value. 6493 Only works in IE with Dimension value (not stuff like relative font-size). 6494 Based on some Dean Edwards' code 6495 6496 @param {HTMLElement} element Used to calculate relative values 6497 @param {string} value Relative value 6498 @param {boolean} useYAxis Calulate relative values to the y axis rather than x 6499 @returns number 6500 */ 6501 function getPixelValue(element, value, useYAxis) { 6502 // Remember the original values 6503 var axisPos = useYAxis ? 'top' : 'left', 6504 axisPosUpper = useYAxis ? 'Top' : 'Left', 6505 elmStyle = element.style, 6506 positionVal = elmStyle[axisPos], 6507 runtimePositionVal = element.runtimeStyle[axisPos], 6508 r; 6509 6510 // copy to the runtime type to prevent changes to the display 6511 element.runtimeStyle[axisPos] = element.currentStyle[axisPos]; 6512 // set value to left / top 6513 elmStyle[axisPos] = value; 6514 // get the pixel value 6515 r = elmStyle['pixel' + axisPosUpper]; 6516 6517 // revert values 6518 elmStyle[axisPos] = positionVal; 6519 element.runtimeStyle[axisPos] = runtimePositionVal; 6520 6521 return r; 6522 } 6523 6524 /** 6525 @name glow.NodeList#css 6526 @function 6527 @description Get / set a CSS property value 6528 6529 @param {string | Object} property The CSS property name, or object of property-value pairs to set 6530 6531 @param {string | number} [value] The value to apply 6532 Number values will be treated as 'px' unless the CSS property 6533 accepts a unitless value. 6534 6535 If value is omitted, the value for the given property will be returned 6536 6537 @returns {glow.NodeList | string} Returns the NodeList when setting value, or the CSS value when getting values. 6538 CSS values are strings. For instance, "height" will return 6539 "25px" for an element 25 pixels high. You can use 6540 parseInt to convert these values. 6541 6542 @example 6543 // get value from first node 6544 glow('#subNav').css('display'); 6545 6546 @example 6547 // set left padding to 10px on all nodes 6548 glow('#subNav li').css('padding-left', '2em'); 6549 6550 @example 6551 // where appropriate, px is assumed when no unit is passed 6552 glow('#mainPromo').css('margin-top', 300); 6553 6554 @example 6555 // set multiple CSS values at once 6556 // NOTE: Property names containing a hyphen such as font-weight must be quoted 6557 glow('#myDiv').css({ 6558 'font-weight': 'bold', 6559 'padding' : '10px', 6560 'color' : '#00cc99' 6561 }); 6562 */ 6563 NodeListProto.css = function(prop, val) { 6564 var thisStyle, 6565 i = this.length, 6566 styleProp, 6567 style, 6568 firstItem = this[0]; 6569 6570 if (prop.constructor === Object) { // set multiple values 6571 for (style in prop) { 6572 this.css( style, prop[style] ); 6573 } 6574 return this; 6575 } 6576 else if (val !== undefined) { //set one CSS value 6577 styleProp = toStyleProp(prop); 6578 while (i--) { 6579 if (this[i].nodeType === 1) { 6580 thisStyle = this[i].style; 6581 6582 if ( !isNaN(val) && hasUnits.test(prop) ) { 6583 val += 'px'; 6584 } 6585 6586 if (prop === 'opacity' && glow.env.ie) { 6587 val = parseFloatFunc(val); 6588 //in IE the element needs hasLayout for opacity to work 6589 thisStyle.zoom = '1'; 6590 thisStyle.filter = (val !== 1) ? 6591 'alpha(opacity=' + mathRound(val * 100) + ')' : 6592 ''; 6593 } 6594 else { 6595 thisStyle[styleProp] = val; 6596 } 6597 } 6598 } 6599 return this; 6600 } 6601 else { //getting stuff 6602 if (prop === 'width' || prop === 'height') { 6603 return this[prop]() + 'px'; 6604 } 6605 return (firstItem && firstItem.nodeType === 1) ? getCssValue(firstItem, prop) : ''; 6606 } 6607 }; 6608 6609 /** 6610 @name glow.NodeList#height 6611 @function 6612 @description Gets / set element height 6613 Return value does not include the padding or border of the element in 6614 browsers supporting the correct box model. 6615 6616 You can use this to easily get the height of the document or 6617 window, see example below. 6618 6619 @param {Number} [height] New height in pixels for each element in the list 6620 If ommited, the height of the first element is returned 6621 6622 @returns {glow.NodeList | number} 6623 Height of first element, or original NodeList when setting heights. 6624 6625 @example 6626 // get the height of #myDiv 6627 glow("#myDiv").height(); 6628 6629 @example 6630 // set the height of list items in #myList to 200 pixels 6631 glow("#myList > li").height(200); 6632 6633 @example 6634 // get the height of the document 6635 glow(document).height(); 6636 6637 @example 6638 // get the height of the window 6639 glow(win).height(); 6640 */ 6641 NodeListProto.height = function(height) { 6642 if (height === undefined) { 6643 return getDimension(this[0], 'Height'); 6644 } 6645 return this.css('height', height); 6646 }; 6647 6648 /** 6649 @name glow.NodeList#width 6650 @function 6651 @description Gets / set element width 6652 Return value does not include the padding or border of the element in 6653 browsers supporting the correct box model. 6654 6655 You can use this to easily get the width of the document or 6656 window, see example below. 6657 6658 @param {Number} [width] New width in pixels for each element in the list 6659 If ommited, the width of the first element is returned 6660 6661 @returns {glow.NodeList | number} 6662 width of first element, or original NodeList when setting widths. 6663 6664 @example 6665 // get the width of #myDiv 6666 glow("#myDiv").width(); 6667 6668 @example 6669 // set the width of list items in #myList to 200 pixels 6670 glow("#myList > li").width(200); 6671 6672 @example 6673 // get the width of the document 6674 glow(document).width(); 6675 6676 @example 6677 // get the width of the window 6678 glow(window).width(); 6679 */ 6680 NodeListProto.width = function(width) { 6681 if (width === undefined) { 6682 return getDimension(this[0], 'Width'); 6683 } 6684 return this.css('width', width); 6685 }; 6686 6687 /** 6688 @name glow.NodeList#scrollLeft 6689 @function 6690 @description Gets/sets the number of pixels the element has scrolled horizontally 6691 To get/set the scroll position of the window, use this method on 6692 a nodelist containing the window object. 6693 6694 @param {Number} [val] New left scroll position 6695 Omit this to get the current scroll position 6696 6697 @returns {glow.NodeList | number} 6698 Current scrollLeft value, or NodeList when setting scroll position. 6699 6700 @example 6701 // get the scroll left value of #myDiv 6702 var scrollPos = glow('#myDiv').scrollLeft(); 6703 // scrollPos is a number, eg: 45 6704 6705 @example 6706 // set the scroll left value of #myDiv to 20 6707 glow('#myDiv').scrollLeft(20); 6708 6709 @example 6710 // get the scrollLeft of the window 6711 glow(window).scrollLeft(); 6712 // scrollPos is a number, eg: 45 6713 */ 6714 NodeListProto.scrollLeft = function(val) { 6715 return scrollOffset(this, true, val); 6716 }; 6717 6718 /** 6719 @name glow.NodeList#scrollTop 6720 @function 6721 @description Gets/sets the number of pixels the element has scrolled vertically 6722 To get/set the scroll position of the window, use this method on 6723 a nodelist containing the window object. 6724 6725 @param {Number} [val] New top scroll position 6726 Omit this to get the current scroll position 6727 6728 @returns {glow.NodeList | number} 6729 Current scrollTop value, or NodeList when setting scroll position. 6730 6731 @example 6732 // get the scroll top value of #myDiv 6733 var scrollPos = glow("#myDiv").scrollTop(); 6734 // scrollPos is a number, eg: 45 6735 6736 @example 6737 // set the scroll top value of #myDiv to 20 6738 glow("#myDiv").scrollTop(20); 6739 6740 @example 6741 // get the scrollTop of the window 6742 glow(window).scrollTop(); 6743 // scrollPos is a number, eg: 45 6744 */ 6745 NodeListProto.scrollTop = function(val) { 6746 return scrollOffset(this, false, val); 6747 }; 6748 /** 6749 @name glow.dom-getScrollOffset 6750 @private 6751 @description Get the scrollTop / scrollLeft of a particular element 6752 @param {Element} elm Element (or window object) to get the scroll position of 6753 @param {Boolean} isLeft True if we're dealing with left scrolling, otherwise top 6754 */ 6755 function getScrollOffset(elm, isLeft) { 6756 var r, 6757 scrollProp = 'scroll' + (isLeft ? 'Left' : 'Top'); 6758 6759 // are we dealing with the window object or the document object? 6760 if (elm.window) { 6761 // get the scroll of the documentElement or the pageX/Yoffset 6762 // - some browsers use one but not the other 6763 r = elm.document.documentElement[scrollProp] 6764 || (isLeft ? elm.pageXOffset : elm.pageYOffset) 6765 || 0; 6766 } else { 6767 r = elm[scrollProp]; 6768 } 6769 return r; 6770 } 6771 6772 /** 6773 @name glow.dom-setScrollOffset 6774 @private 6775 @description Set the scrollTop / scrollLeft of a particular element 6776 @param {Element} elm Element (or window object) to get the scroll position of 6777 @param {Boolean} isLeft True if we're dealing with left scrolling, otherwise top 6778 @param {Number} newVal New scroll value 6779 */ 6780 function setScrollOffset(elm, isLeft, newVal) { 6781 // are we dealing with the window object or the document object? 6782 if (elm.window) { 6783 // we need to get whichever value we're not setting 6784 elm.scrollTo( 6785 isLeft ? newVal : getScrollOffset(elm, true), 6786 !isLeft ? newVal : getScrollOffset(elm, false) 6787 ); 6788 } else { 6789 elm['scroll' + (isLeft ? 'Left' : 'Top')] = newVal; 6790 } 6791 } 6792 6793 /** 6794 @name glow.dom-scrollOffset 6795 @private 6796 @description Set/get the scrollTop / scrollLeft of a NodeList 6797 @param {glow.dom.NodeList} nodeList Elements to get / set the position of 6798 @param {Boolean} isLeft True if we're dealing with left scrolling, otherwise top 6799 @param {Number} [val] Val to set (if not provided, we'll get the value) 6800 @returns NodeList for sets, Number for gets 6801 */ 6802 function scrollOffset(nodeList, isLeft, val) { 6803 var i = nodeList.length; 6804 6805 if (val !== undefined) { 6806 while (i--) { 6807 setScrollOffset(nodeList[i], isLeft, val); 6808 } 6809 return nodeList; 6810 } else { 6811 return getScrollOffset(nodeList[0], isLeft); 6812 } 6813 } 6814 /** 6815 @name glow.NodeList#hide 6816 @function 6817 @description Hides all items in the NodeList. 6818 6819 @returns {glow.NodeList} 6820 6821 @example 6822 // Hides all list items within #myList 6823 glow("#myList li").hide(); 6824 */ 6825 NodeListProto.hide = function() { 6826 return this.css('display', 'none').css('visibility', 'hidden'); 6827 }; 6828 6829 /** 6830 @name glow.NodeList#show 6831 @function 6832 @description Shows all hidden items in the NodeList. 6833 6834 @returns {glow.NodeList} 6835 6836 @example 6837 // Show element with ID myDiv 6838 glow("#myDiv").show(); 6839 6840 @example 6841 // Show all list items within #myList 6842 glow("#myList li").show(); 6843 */ 6844 NodeListProto.show = function() { 6845 var i = this.length, 6846 currItem, 6847 itemStyle; 6848 6849 while (i--) { 6850 /* Create a NodeList for the current item */ 6851 currItem = new glow.NodeList(this[i]); 6852 itemStyle = currItem[0].style; 6853 if (currItem.css('display') == 'none') { 6854 itemStyle.display = ''; 6855 itemStyle.visibility = 'visible'; 6856 /* If display is still none, set to block */ 6857 if (currItem.css('display') == 'none') { 6858 itemStyle.display = 'block'; 6859 } 6860 } 6861 } 6862 return this; 6863 }; 6864 6865 /** 6866 @name glow.NodeList#offset 6867 @function 6868 @description Gets the offset from the top left of the document. 6869 If the NodeList contains multiple items, the offset of the 6870 first item is returned. 6871 6872 @returns {Object} 6873 Returns an object with "top" & "left" properties in pixels 6874 6875 @example 6876 glow("#myDiv").offset().top 6877 */ 6878 NodeListProto.offset = function() { 6879 if ( !this[0] || this[0].nodeType !== 1) { 6880 return {top: 0, left: 0}; 6881 } 6882 6883 // 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) 6884 var elm = this[0], 6885 doc = elm.ownerDocument, 6886 docElm = doc.documentElement, 6887 window = doc.defaultView || doc.parentWindow, 6888 docScrollPos = { 6889 x: getScrollOffset(window, true), 6890 y: getScrollOffset(window, false) 6891 }; 6892 6893 //this is simple(r) if we can use 'getBoundingClientRect' 6894 // Sorry but the sooper dooper simple(r) way is not accurate in Safari 4 6895 if (!glow.env.webkit && elm.getBoundingClientRect) { 6896 var rect = elm.getBoundingClientRect(); 6897 6898 return { 6899 top: Math.floor(rect.top) 6900 /* 6901 getBoundingClientRect is realive to top left of 6902 the viewport, so we need to sort out scrolling offset 6903 */ 6904 + docScrollPos.y 6905 /* 6906 IE adds the html element's border to the value. We can 6907 deduct this value using client(Top|Left). However, if 6908 the user has done html{border:0} clientTop will still 6909 report a 2px border in IE quirksmode so offset will be off by 2. 6910 Hopefully this is an edge case but we may have to revisit this 6911 in future 6912 */ 6913 - docElm.clientTop, 6914 6915 left: Math.floor(rect.left) //see above for docs on all this stuff 6916 + docScrollPos.x 6917 - docElm.clientLeft 6918 }; 6919 } 6920 else { //damnit, let's go the long way around 6921 var top = elm.offsetTop, 6922 left = elm.offsetLeft, 6923 originalElm = elm, 6924 nodeNameLower, 6925 docBody = document.body, 6926 //does the parent chain contain a position:fixed element 6927 involvesFixedElement = false, 6928 offsetParentBeforeBody = elm; 6929 6930 //add up all the offset positions 6931 while (elm = elm.offsetParent) { 6932 left += elm.offsetLeft; 6933 top += elm.offsetTop; 6934 6935 //if css position is fixed, we need to add in the scroll offset too, catch it here 6936 if (getCssValue(elm, 'position') == 'fixed') { 6937 involvesFixedElement = true; 6938 } 6939 6940 //gecko & webkit (safari 3) don't add on the border for positioned items 6941 if (glow.env.gecko || glow.env.webkit > 500) { 6942 left += parseInt(getCssValue(elm, 'border-left-width')) || 0; 6943 top += parseInt(getCssValue(elm, 'border-top-width')) || 0; 6944 } 6945 6946 //we need the offset parent (before body) later 6947 if (elm.nodeName.toLowerCase() != 'body') { 6948 offsetParentBeforeBody = elm; 6949 } 6950 } 6951 6952 //deduct all the scroll offsets 6953 elm = originalElm; 6954 6955 while ((elm = elm.parentNode) && (elm != docBody) && (elm != docElm)) { 6956 left -= elm.scrollLeft; 6957 top -= elm.scrollTop; 6958 6959 //FIXES 6960 //gecko doesn't add the border of contained elements to the offset (overflow!=visible) 6961 if (glow.env.gecko && getCssValue(elm, 'overflow') != 'visible') { 6962 left += parseInt(getCssValue(elm, 'border-left-width')); 6963 top += parseInt(getCssValue(elm, 'border-top-width')); 6964 } 6965 } 6966 6967 //if we found a fixed position element we need to add the scroll offsets 6968 if (involvesFixedElement) { 6969 left += docScrollPos.x; 6970 top += docScrollPos.y; 6971 } 6972 6973 //FIXES 6974 // Gecko - non-absolutely positioned elements that are direct children of body get the body offset counted twice 6975 if ( 6976 (glow.env.gecko && getCssValue(offsetParentBeforeBody, 'position') != 'absolute') 6977 ) { 6978 left -= docBody.offsetLeft; 6979 top -= docBody.offsetTop; 6980 } 6981 6982 return {left:left, top:top}; 6983 } 6984 }; 6985 6986 /** 6987 @name glow.NodeList#position 6988 @function 6989 @description Get the top & left position of an element relative to its positioned parent 6990 This is useful if you want to make a position:static element position:absolute 6991 and retain the original position of the element 6992 6993 @returns {Object} 6994 An object with 'top' and 'left' number properties 6995 6996 @example 6997 // get the top distance from the positioned parent 6998 glow("#elm").position().top 6999 */ 7000 NodeListProto.position = function() { 7001 var positionedParent = new glow.NodeList( getPositionedParent(this[0]) ), 7002 hasPositionedParent = !!positionedParent[0], 7003 7004 // element margins to deduct 7005 marginLeft = parseInt( this.css('margin-left') ) || 0, 7006 marginTop = parseInt( this.css('margin-top') ) || 0, 7007 7008 // offset parent borders to deduct, set to zero if there's no positioned parent 7009 positionedParentBorderLeft = ( hasPositionedParent && parseInt( positionedParent.css('border-left-width') ) ) || 0, 7010 positionedParentBorderTop = ( hasPositionedParent && parseInt( positionedParent.css('border-top-width') ) ) || 0, 7011 7012 // element offsets 7013 elOffset = this.offset(), 7014 positionedParentOffset = hasPositionedParent ? positionedParent.offset() : {top: 0, left: 0}; 7015 7016 return { 7017 left: elOffset.left - positionedParentOffset.left - marginLeft - positionedParentBorderLeft, 7018 top: elOffset.top - positionedParentOffset.top - marginTop - positionedParentBorderTop 7019 } 7020 }; 7021 /* 7022 Get the 'real' positioned parent for an element, otherwise return null. 7023 */ 7024 function getPositionedParent(elm) { 7025 var offsetParent = elm.offsetParent, 7026 docElm = document.documentElement; 7027 7028 // get the real positioned parent 7029 // IE places elements with hasLayout in the offsetParent chain even if they're position:static 7030 // Also, <body> and <html> can appear in the offsetParent chain, but we don't want to return them if they're position:static 7031 while (offsetParent && new glow.NodeList(offsetParent).css('position') == 'static') { 7032 offsetParent = offsetParent.offsetParent; 7033 } 7034 7035 // sometimes the <html> element doesn't appear in the offsetParent chain, even if it has position:relative 7036 if (!offsetParent && new glow.NodeList(docElm).css('position') != 'static') { 7037 offsetParent = docElm; 7038 } 7039 7040 return offsetParent || null; 7041 } 7042 }); 7043 Glow.provide(function(glow) { 7044 var NodeListProto = glow.NodeList.prototype, 7045 document = window.document, 7046 undefined, 7047 keyEventNames = ' keypress keydown keyup '; 7048 7049 /** 7050 @name glow.NodeList#on 7051 @function 7052 @description Listen for an event. 7053 This will listen for a particular event on each dom node 7054 in the NodeList. 7055 7056 If you're listening to many children of a particular item, 7057 you may get better performance from {@link glow.NodeList#delegate}. 7058 7059 @param {String} eventName Name of the event to listen for. 7060 This can be any regular DOM event ('click', 'mouseover' etc) or 7061 a special event of NodeList. 7062 7063 @param {Function} callback Function to call when the event fires. 7064 The callback is passed a single event object. The type of this 7065 object is {@link glow.DomEvent} unless otherwise stated. 7066 7067 @param {Object} [thisVal] Value of 'this' within the callback. 7068 By default, this is the dom node being listened to. 7069 7070 @returns this 7071 7072 @example 7073 glow.get('#testLink').on('click', function(domEvent) { 7074 // do stuff 7075 7076 // if you want to cancel the default action (following the link)... 7077 return false; 7078 }); 7079 */ 7080 NodeListProto.on = function(eventName, callback, thisVal) { 7081 var isKeyEvent = (keyEventNames.indexOf(' ' + eventName + ' ') > -1); 7082 7083 // add standard glow listeners 7084 glow.events.addListeners(this, eventName, callback, thisVal); 7085 7086 // add the bridge functions if needed 7087 if (isKeyEvent) { 7088 glow.events._addKeyListener(this); 7089 } 7090 else { // assume it's a DOM event 7091 glow.events._addDomEventListener(this, eventName); 7092 } 7093 7094 return this; 7095 } 7096 7097 /** 7098 @name glow.NodeList#detach 7099 @function 7100 @description detach a listener from elements 7101 This will detach the listener from each dom node in the NodeList. 7102 7103 @param {String} eventName Name of the event to detach the listener from 7104 7105 @param {Function} callback Listener callback to detach 7106 7107 @returns this 7108 7109 @example 7110 function clickListener(domEvent) { 7111 // ... 7112 } 7113 7114 // adding listeners 7115 glow.get('a').on('click', clickListener); 7116 7117 // removing listeners 7118 glow.get('a').detach('click', clickListener); 7119 */ 7120 NodeListProto.detach = function(eventName, callback) { 7121 var isKeyEvent = (keyEventNames.indexOf(' ' + eventName + ' ') > -1); 7122 7123 // remove standard glow listeners 7124 glow.events.removeListeners(this, eventName, callback); 7125 7126 // remove the bridge functions if needed 7127 if (isKeyEvent) { 7128 glow.events._removeKeyListener(this); 7129 } 7130 else { // assume it's a DOM event 7131 glow.events._removeDomEventListener(this, eventName); 7132 } 7133 7134 return this; 7135 } 7136 7137 /** 7138 @name glow.NodeList#delegate 7139 @function 7140 @description Listen for an event occurring on child elements matching a selector. 7141 'delegate' will catch events which occur on matching items created after 7142 the listener was added. 7143 7144 @param {String} eventName Name of the event to listen for. 7145 This can be any regular DOM event ('click', 'mouseover' etc) or 7146 a special event of NodeList. 7147 7148 @param {String} selector CSS selector of child elements to listen for events on 7149 For example, if you were wanting to hear events from links, this 7150 would be 'a'. 7151 7152 @param {Function} callback Function to call when the event fires. 7153 The callback is passed a single event object. The type of this 7154 object is {@link glow.DomEvent} unless otherwise stated. 7155 7156 @param {Object} [thisVal] Value of 'this' within the callback. 7157 By default, this is the dom node matched by 'selector'. 7158 7159 @returns this 7160 7161 @example 7162 // Using 'on' to catch clicks on links in a list 7163 glow.get('#nav a').on('click', function() { 7164 // do stuff 7165 }); 7166 7167 // The above adds a listener to each link, any links created later 7168 // will not have this listener, so we won't hear about them. 7169 7170 // Using 'delegate' to catch clicks on links in a list 7171 glow.get('#nav').delegate('click', 'a', function() { 7172 // do stuff 7173 }); 7174 7175 // The above only adds one listener to #nav which tracks clicks 7176 // to any links within. This includes elements created after 'delegate' 7177 // was called. 7178 7179 @example 7180 // Using delegate to change class names on table rows so :hover 7181 // behaviour can be emulated in IE6 7182 glow.get('#contactData').delegate('mouseover', 'tr', function() { 7183 glow.get(this).addClass('hover'); 7184 }); 7185 7186 glow.get('#contactData').delegate('mouseout', 'tr', function() { 7187 glow.get(this).removeClass('hover'); 7188 }); 7189 */ 7190 NodeListProto.delegate = function(eventName, selector, callback, thisVal) { 7191 var isKeyEvent = (keyEventNames.indexOf(' ' + eventName + ' ') > -1); 7192 7193 // add standard glow listeners 7194 glow.events.addListeners(this, eventName + '/' + selector, callback, thisVal); 7195 7196 // register delegates 7197 glow.events._registerDelegate(this, eventName, selector); 7198 7199 // add the bridge functions if needed 7200 if (isKeyEvent) { 7201 glow.events._addKeyListener(this); 7202 } 7203 else { // assume it's a DOM event 7204 glow.events._addDomEventListener(this, eventName); 7205 } 7206 7207 return this; 7208 } 7209 7210 /** 7211 @name glow.NodeList#detachDelegate 7212 @function 7213 @description detach a delegated listener from elements 7214 This will detach the listener from each dom node in the NodeList. 7215 7216 @param {String} eventName Name of the event to detach the listener from 7217 7218 @param {String} selector CSS selector of child elements the listener is listening to 7219 7220 @param {Function} callback Listener callback to detach 7221 7222 @returns this 7223 7224 @example 7225 function clickListener(domEvent) { 7226 // ... 7227 } 7228 7229 // adding listeners 7230 glow.get('#nav').delegate('click', 'a', clickListener); 7231 7232 // removing listeners 7233 glow.get('#nav').detachDelegate('click', 'a', clickListener); 7234 */ 7235 NodeListProto.detachDelegate = function(eventName, selector, callback, thisVal) { 7236 var isKeyEvent = (keyEventNames.indexOf(' ' + eventName + ' ') > -1); 7237 7238 // remove standard glow listeners 7239 glow.events.removeListeners(this, eventName + '/' + selector, callback); 7240 7241 // unregister delegates 7242 glow.events._unregisterDelegate(this, eventName, selector); 7243 7244 // remove the bridge functions if needed 7245 if (isKeyEvent) { 7246 glow.events._removeKeyListener(this); 7247 } 7248 else { // assume it's a DOM event 7249 glow.events._removeDomEventListener(this, eventName); 7250 } 7251 7252 return this; 7253 } 7254 7255 /** 7256 @name glow.NodeList#fire 7257 @function 7258 @param {String} eventName Name of the event to fire 7259 @param {glow.events.Event} [event] Event object to pass into listeners. 7260 You can provide a simple object of key / value pairs which will 7261 be added as properties of a glow.events.Event instance. 7262 7263 @description Fire an event on dom nodes within the NodeList 7264 Note, this will only trigger event listeners to be called, it won't 7265 for example, move the mouse or click a link on the page. 7266 7267 @returns glow.events.Event 7268 7269 @example 7270 glow.get('#testLink').on('click', function() { 7271 alert('Link clicked!'); 7272 }); 7273 7274 // The following causes 'Link clicked!' to be alerted, but doesn't 7275 // cause the browser to follow the link 7276 glow.get('#testLink').fire('click'); 7277 */ 7278 NodeListProto.fire = function(eventName, event) { 7279 return glow.events.fire(this, eventName, event); 7280 } 7281 7282 /** 7283 @name glow.NodeList#event:mouseenter 7284 @event 7285 @description Fires when the mouse enters the element specifically, does not bubble 7286 7287 @param {glow.events.DomEvent} event Event Object 7288 */ 7289 7290 /** 7291 @name glow.NodeList#event:mouseleave 7292 @event 7293 @description Fires when the mouse leaves the element specifically, does not bubble 7294 7295 @param {glow.events.DomEvent} event Event Object 7296 */ 7297 7298 /** 7299 @name glow.NodeList#event:keydown 7300 @event 7301 @description Fires when the user presses a key 7302 Only fires if the element has focus, listen for this event on 7303 the document to catch all keydowns. 7304 7305 This event related to the user pressing a key on the keyboard, 7306 if you're more concerned about the character entered, see the 7307 {@link glow.NodeList#event:keypress keypress} event. 7308 7309 keydown will only fire once, when the user presses the key. 7310 7311 The order of events is keydown, keypress*, keyup. keypress may 7312 fire many times if the user holds the key down. 7313 7314 @param {glow.events.KeyboardEvent} event Event Object 7315 */ 7316 7317 /** 7318 @name glow.NodeList#event:keypress 7319 @event 7320 @description Fires when a key's command executes. 7321 For instance, if you hold down a key, it's action will occur many 7322 times. This event will fire on each action. 7323 7324 This event is useful when you want to react to keyboard repeating, or 7325 to detect when a character is entered into a field. 7326 7327 The order of events is keydown, keypress*, keyup. keypress may 7328 fire many times if the user holds the key down. 7329 7330 @param {glow.events.KeyboardEvent} event Event Object 7331 */ 7332 7333 /** 7334 @name glow.NodeList#event:keyup 7335 @event 7336 @description Fires when the user releases a key 7337 Only fires if the element has focus, listen for this event on 7338 the document to catch all keyups. 7339 7340 This event related to the user pressing a key on the keyboard, 7341 if you're more concerned about the character entered, see the 7342 {@link glow.NodeList#event:keypress keypress} event. 7343 7344 The order of events is keydown, keypress*, keyup. keypress may 7345 fire many times if the user holds the key down. 7346 7347 @param {glow.events.KeyboardEvent} event Event Object 7348 */ 7349 }); 7350 Glow.provide(function(glow) { 7351 var NodeList = glow.NodeList, 7352 NodeListProto = NodeList.prototype, 7353 undefined, 7354 parseFloat = window.parseFloat, 7355 // used to detect which CSS properties require units 7356 requiresUnitsRe = /width|height|top$|bottom$|left$|right$|spacing$|indent$|fontSize/i, 7357 // which simple CSS values cannot be negative 7358 noNegativeValsRe = /width|height|padding|opacity/, 7359 getUnit = /\D+$/, 7360 usesYAxis = /height|top/; 7361 7362 // TODO: get this from appearence.js 7363 function toStyleProp(prop) { 7364 if (prop == 'float') { 7365 return glow.env.ie ? 'styleFloat' : 'cssFloat'; 7366 } 7367 return prop.replace(/-(\w)/g, function(match, p1) { 7368 return p1.toUpperCase(); 7369 }); 7370 } 7371 7372 /** 7373 @private 7374 @function 7375 @param {nodelist} element 7376 @param {string} toUnit (em|%|pt...) 7377 @param {string} axis (x|y) 7378 @description Converts a css unit. 7379 We need to know the axis for calculating relative values, since they're 7380 relative to the width / height of the parent element depending 7381 on the situation. 7382 */ 7383 var testElement = glow('<div style="position:absolute;visibility:hidden;border:0;margin:0;padding:0"></div>'); 7384 7385 function convertCssUnit(element, value, toUnit, axis) { 7386 var elmStyle = testElement[0].style, 7387 axisProp = (axis === 'x') ? 'width' : 'height', 7388 startPixelValue, 7389 toUnitPixelValue; 7390 7391 startPixelValue = testElement.css(axisProp, value).insertAfter(element)[axisProp](); 7392 // using 10 of the unit then dividing by 10 to increase accuracy 7393 toUnitPixelValue = testElement.css(axisProp, 10 + toUnit)[axisProp]() / 10; 7394 testElement.remove(); 7395 return startPixelValue / toUnitPixelValue; 7396 } 7397 7398 /** 7399 @private 7400 @function 7401 @description Animate a colour value 7402 */ 7403 function animateColor(anim, stylePropName, from, to) { 7404 to = NodeList._parseColor(to); 7405 to = [to.r, to.g, to.b]; 7406 from = NodeList._parseColor(from); 7407 from = [from.r, from.g, from.b]; 7408 7409 anim.prop(stylePropName, { 7410 // we only need a template if we have units 7411 template: 'rgb(?,?,?)', 7412 from: from, 7413 to: to, 7414 round: true, 7415 min: 0, 7416 max: 255 7417 }); 7418 } 7419 7420 /** 7421 @private 7422 @function 7423 @description Animate opacity in IE's 'special' way 7424 */ 7425 function animateIeOpacity(elm, anim, from, to) { 7426 to = parseFloat(to) * 100; 7427 from = parseFloat(from) * 100; 7428 7429 // give the element 'hasLayout' 7430 elm.style.zoom = 1; 7431 7432 anim.prop('filter', { 7433 // we only need a template if we have units 7434 template: 'alpha(opacity=?)', 7435 from: from, 7436 to: to, 7437 allowNegative: false 7438 }); 7439 } 7440 7441 /** 7442 @private 7443 @function 7444 @description Scroll positions 7445 */ 7446 function animateScroll(elm, anim, from, to, scrollTopOrLeft) { 7447 var diff; 7448 7449 to = parseFloat(to); 7450 from = parseFloat(from); 7451 elm = glow(elm); 7452 7453 // auto-get start value if there isn't one 7454 if ( isNaN(from) ) { 7455 from = elm[scrollTopOrLeft](); 7456 } 7457 7458 diff = to - from; 7459 7460 anim.on('frame', function() { 7461 elm[scrollTopOrLeft]( diff * this.value + from ); 7462 }); 7463 } 7464 7465 /** 7466 @private 7467 @function 7468 @description Animate simple values 7469 This is a set of space-separated numbers (42) or numbers + unit (42em) 7470 7471 Units can be mixed 7472 */ 7473 function animateValues(element, anim, stylePropName, from, to) { 7474 var toUnit, 7475 fromUnit, 7476 round = [], 7477 template = '', 7478 requiresUnits = requiresUnitsRe.test(stylePropName), 7479 minZero = noNegativeValsRe.test(stylePropName); 7480 7481 from = String(from).split(' '); 7482 to = String(to).split(' '); 7483 7484 for (var i = 0, leni = to.length; i < leni; i++) { 7485 toUnit = ( getUnit.exec( to[i] ) || [''] )[0]; 7486 fromUnit = ( getUnit.exec( from[i] ) || [''] )[0]; 7487 7488 // create initial units if required 7489 if (requiresUnits) { 7490 toUnit = toUnit || 'px'; 7491 fromUnit = fromUnit || 'px'; 7492 } 7493 7494 round[i] = (toUnit === 'px'); 7495 7496 // make the 'from' unit the same as the 'to' unit 7497 if (toUnit !== fromUnit) { 7498 from = convertCssUnit( element, from, toUnit, usesYAxis.test(stylePropName) ? 'y' : 'x' ); 7499 } 7500 7501 template += ' ?' + toUnit; 7502 from[i] = parseFloat( from[i] ); 7503 to[i] = parseFloat( to[i] ); 7504 } 7505 7506 anim.prop(stylePropName, { 7507 template: template, 7508 from: from, 7509 to: to, 7510 round: round, 7511 min: minZero ? 0 : undefined 7512 }); 7513 } 7514 7515 /** 7516 @private 7517 @function 7518 @description Makes an animtion adjust CSS values over time 7519 */ 7520 function addCssAnim(nodeList, anim, properties) { 7521 var to, from, i, 7522 property, 7523 propertyIsArray, 7524 stylePropName; 7525 7526 for (var propName in properties) { 7527 property = properties[propName]; 7528 propertyIsArray = property.push; 7529 stylePropName = toStyleProp(propName); 7530 to = propertyIsArray ? property[1] : property; 7531 i = nodeList.length; 7532 7533 // do this for each nodelist item 7534 while (i--) { 7535 // deal with special values, scrollTop and scrollLeft which aren't really CSS 7536 // This is the only animation that can work on the window object too 7537 if ( propName.indexOf('scroll') === 0 && (nodeList[i].scrollTo || nodeList[i].scrollTop !== undefined) ) { 7538 animateScroll(nodeList[i], anim, propertyIsArray ? property[0] : undefined, to, propName); 7539 continue; 7540 } 7541 7542 // skip non-element nodes 7543 if ( nodeList[i].nodeType !== 1 ) { continue; } 7544 7545 // set new target 7546 anim.target( nodeList[i].style ); 7547 7548 from = propertyIsArray ? property[0] : nodeList.item(i).css(propName); 7549 7550 // deal with colour values 7551 if ( propName.indexOf('color') !== -1 ) { 7552 animateColor(anim, stylePropName, from, to); 7553 } 7554 // nice special case for IE 7555 else if (glow.env.ie && stylePropName === 'opacity') { 7556 animateIeOpacity(nodeList[i], anim, from, to); 7557 } 7558 // assume we're dealing with simple numbers, or numbers + unit 7559 // eg "5px", "5px 2em", "10px 5px 1em 4px" 7560 else { 7561 animateValues(nodeList[i], anim, stylePropName, from, to); 7562 } 7563 } 7564 } 7565 } 7566 7567 /** 7568 @name glow.NodeList#anim 7569 @function 7570 @description Animate properties of elements 7571 All elements in the NodeList are animated 7572 7573 All CSS values which are simple numbers (with optional unit) 7574 are supported. Eg: width, margin-top, left 7575 7576 All CSS values which are space-separated values are supported 7577 (eg background-position, margin, padding), although a 'from' 7578 value must be provided for short-hand properties like 'margin'. 7579 7580 All CSS colour values are supported. Eg: color, background-color. 7581 7582 'scrollLeft' and 'scrollTop' can be animated for elements and 7583 the window object. 7584 7585 Other properties, including CSS properties with limited support, can 7586 be animated using {@link glow.anim.Anim#prop}. 7587 7588 @param {number} duration Length of the animation in seconds. 7589 @param {Object} properties Properties to animate. 7590 This is an object where the key is the CSS property and the value 7591 is the value to animate to. 7592 7593 The value can also be an array, where the first item is the value to 7594 animate from, and the second is the value to animate to. 7595 7596 Numerical values will be treated as 'px' if the property requires units. 7597 7598 @param {Object} [opts] Options object 7599 @param {function|string} [opts.tween='easeBoth'] The motion of the animation. 7600 Strings are treated as properties of {@link glow.tweens}, although 7601 a tween function can be provided. 7602 @param {boolean} [opts.destroyOnComplete=true] Destroy the animation once it completes (unless it loops). 7603 This will free any DOM references the animation may have created. Once 7604 the animation is destroyed, it cannot be started again. 7605 @param {boolean} [opts.loop=true] Loop the animation. 7606 @param {boolean} [opts.startNow=true] Start the animation straight away? 7607 Animations can be started by calling {@link glow.anim.Anim#start} 7608 7609 @returns {glow.anim.Anim} 7610 7611 @example 7612 // change the nav's background colour to white and the top position 7613 // to 20px over a duration of 3 seconds 7614 glow('#nav').anim(3, { 7615 'background-color': '#fff', 7616 'top': 20 7617 }); 7618 7619 @example 7620 // Fade an element out and alert 'done' when complete 7621 glow('#nav').anim(3, { 7622 'opacity': 0 7623 }).on('complete', function() { 7624 alert('done!'); 7625 }); 7626 7627 @example 7628 // Scroll the window to the top 7629 glow(window).anim(2, { 7630 scrollTop: 0 7631 }); 7632 7633 @see {@link glow.NodeList#queueAnim} - Queue an animation to run after the current anim 7634 @see {@link glow.NodeList#fadeIn} - Shortcut to fade elements in 7635 @see {@link glow.NodeList#fadeOut} - Shortcut to fade elements out 7636 @see {@link glow.NodeList#fadeToggle} - Shortcut to toggle the fade of an element 7637 @see {@link glow.NodeList#slideOpen} - Shortcut to slide an element open 7638 @see {@link glow.NodeList#slideShut} - Shortcut to slide an element shut 7639 @see {@link glow.NodeList#slideToggle} - Shortcut to toggle an element open / shut 7640 7641 */ 7642 NodeListProto.anim = function(duration, properties, opts) { 7643 /*!debug*/ 7644 if (arguments.length < 2 || arguments.length > 3) { 7645 glow.debug.warn('[wrong count] glow.NodeList#anim expects 2 or 3 arguments, not ' + arguments.length + '.'); 7646 } 7647 if (typeof duration !== 'number') { 7648 glow.debug.warn('[wrong type] glow.NodeList#anim expects number as "duration" argument, not ' + typeof duration + '.'); 7649 } 7650 if (typeof properties !== 'object') { 7651 glow.debug.warn('[wrong type] glow.NodeList#anim expects object as "properties" argument, not ' + typeof properties + '.'); 7652 } 7653 if (opts !== undefined && typeof opts !== 'object') { 7654 glow.debug.warn('[wrong type] glow.NodeList#anim expects object as "opts" argument, not ' + typeof opts + '.'); 7655 } 7656 /*gubed!*/ 7657 7658 opts = opts || {}; 7659 7660 var anim = new glow.anim.Anim(duration, opts); 7661 7662 addCssAnim(this, anim, properties); 7663 7664 // auto start 7665 !(opts.startNow === false) && anim.start(); 7666 return anim; 7667 }; 7668 7669 /** 7670 @private 7671 @function 7672 @description Used as a listener for an animations's stop event. 7673 'this' is a nodelist of the animating item 7674 7675 Set in queueAnim 7676 */ 7677 function queueAnimStop() { 7678 this.removeData('glow_lastQueuedAnim').removeData('glow_currentAnim'); 7679 } 7680 7681 /** 7682 @name glow.NodeList#queueAnim 7683 @function 7684 @description Queue an animation to run after the current animation 7685 All elements in the NodeList are animated 7686 7687 This supports the same CSS properties as {@link glow.NodeList#anim}, 7688 but the animation is not started until the previous animation (added 7689 via {@link glow.NodeList#queueAnim queueAnim}) 7690 on that element ends. 7691 7692 If there are no queued animations on the element, the animation starts 7693 straight away. 7694 7695 @param {number} duration Length of the animation in seconds. 7696 @param {Object} Properties to animate. 7697 This is an object where the key is the CSS property and the value 7698 is the value to animate to. 7699 7700 The value can also be an array, where the first item is the value to 7701 animate from, and the second is the value to animate to. 7702 7703 Numerical values will be treated as 'px' if the property requires units. 7704 7705 @param {Object} [opts] Options object 7706 @param {function|string} [opts.tween='easeBoth'] The motion of the animation. 7707 Strings are treated as properties of {@link glow.tweens}, although 7708 a tween function can be provided. 7709 @param {boolean} [opts.destroyOnComplete=true] Destroy the animation once it completes (unless it loops). 7710 This will free any DOM references the animation may have created. Once 7711 the animation is destroyed, it cannot be started again. 7712 7713 @returns {glow.NodeList} 7714 7715 @example 7716 // change a nav item's background colour from white to yellow 7717 // when the mouse is over it, and back again when the mouse 7718 // exits. 7719 glow('#nav').delegate('mouseenter', 'li', function() { 7720 glow(this).queueAnim(0.5, { 7721 'background-color': 'yellow' 7722 }); 7723 }).delegate('mouseleave', 'li', function() { 7724 glow(this).queueAnim(0.5, { 7725 'background-color': 'white' 7726 }); 7727 }); 7728 7729 @example 7730 // adding listeners to a queued anim 7731 glow('#elementToAnimate').queueAnim(0.5, { 7732 height: 0 7733 }).lastQueuedAnim().on('complete', function() { 7734 alert('Animation complete!'); 7735 }); 7736 7737 @example 7738 // stopping and clearing current animation queue. 7739 // The next animation created via queueAnim will start 7740 // immediately 7741 glow('#elementToAnimate').curentAnim().stop(); 7742 7743 @see {@link glow.NodeList#fadeIn} - Shortcut to fade elements in 7744 @see {@link glow.NodeList#fadeOut} - Shortcut to fade elements out 7745 @see {@link glow.NodeList#fadeToggle} - Shortcut to toggle the fade of an element 7746 @see {@link glow.NodeList#slideOpen} - Shortcut to slide an element open 7747 @see {@link glow.NodeList#slideShut} - Shortcut to slide an element shut 7748 @see {@link glow.NodeList#slideToggle} - Shortcut to toggle an element open / shut 7749 7750 */ 7751 NodeListProto.queueAnim = function(duration, properties, opts) { 7752 /*!debug*/ 7753 if (arguments.length < 2 || arguments.length > 3) { 7754 glow.debug.warn('[wrong count] glow.NodeList#queueAnim expects 2 or 3 arguments, not ' + arguments.length + '.'); 7755 } 7756 if (typeof duration !== 'number') { 7757 glow.debug.warn('[wrong type] glow.NodeList#queueAnim expects number as "duration" argument, not ' + typeof duration + '.'); 7758 } 7759 if (typeof properties !== 'object') { 7760 glow.debug.warn('[wrong type] glow.NodeList#queueAnim expects object as "properties" argument, not ' + typeof properties + '.'); 7761 } 7762 if (opts !== undefined && typeof opts !== 'object') { 7763 glow.debug.warn('[wrong type] glow.NodeList#queueAnim expects object as "opts" argument, not ' + typeof opts + '.'); 7764 } 7765 /*gubed!*/ 7766 7767 opts = opts || {}; 7768 7769 var i = this.length, 7770 item, 7771 lastQueuedAnim, 7772 anim, 7773 startNextAnim; 7774 7775 // we don't want animations starting now 7776 opts.startNow = false; 7777 7778 while (i--) { 7779 item = this.item(i); 7780 if (item[0].nodeType !== 1) { continue; } 7781 lastQueuedAnim = item.data('glow_lastQueuedAnim'); 7782 // add a listener to 'stop', to clear the queue 7783 anim = new glow.anim.Anim(duration, opts).on('stop', queueAnimStop, item); 7784 item.data('glow_lastQueuedAnim', anim); 7785 7786 // closure some properties 7787 (function(item, properties, anim) { 7788 startNextAnim = function() { 7789 addCssAnim(item, anim, properties); 7790 anim.start(); 7791 item.data('glow_currentAnim', anim); 7792 } 7793 })(item, properties, anim); 7794 7795 // do we start the anim now, or after the next one? 7796 if (lastQueuedAnim) { 7797 lastQueuedAnim.on('complete', startNextAnim); 7798 } 7799 else { 7800 startNextAnim(); 7801 } 7802 } 7803 7804 return this; 7805 }; 7806 7807 /** 7808 @name glow.NodeList#currentAnim 7809 @function 7810 @description Get the currently playing animation added via {@link glow.NodeList#queueAnim queueAnim} for this element 7811 If no animation is currently playing, an empty animation is returned. 7812 This means you don't need to check to see if the item is defined before 7813 calling methods on it. 7814 7815 This method acts on the first item in the NodeList. 7816 7817 @returns {glow.anim.Anim} 7818 7819 @example 7820 // stopping and clearing current animation queue. 7821 // The next animation created via queueAnim will start 7822 // immediately 7823 glow('#elementToAnimate').curentAnim().stop(); 7824 7825 @example 7826 // Is the element animating as part of queueAnim? 7827 glow('#elementToAnimate').curentAnim().playing; // true/false 7828 */ 7829 NodeListProto.currentAnim = function() { 7830 /*!debug*/ 7831 if (arguments.length !== 0) { 7832 glow.debug.warn('[wrong count] glow.NodeList#currentAnim expects 0 arguments, not ' + arguments.length + '.'); 7833 } 7834 /*gubed!*/ 7835 return this.data('glow_currentAnim') || new glow.anim.Anim(0); 7836 } 7837 7838 /** 7839 @name glow.NodeList#lastQueuedAnim 7840 @function 7841 @description Get the last animation added via {@link glow.NodeList#queueAnim queueAnim} for this element 7842 If no animation has been added, an empty animation is returned. 7843 This means you don't need to check to see if the item is defined before 7844 calling methods on it. 7845 7846 This method acts on the first item in the NodeList. 7847 7848 @returns {glow.anim.Anim} 7849 */ 7850 NodeListProto.lastQueuedAnim = function() { 7851 /*!debug*/ 7852 if (arguments.length !== 0) { 7853 glow.debug.warn('[wrong count] glow.NodeList#lastQueuedAnim expects 0 arguments, not ' + arguments.length + '.'); 7854 } 7855 /*gubed!*/ 7856 return this.data('glow_lastQueuedAnim') || new glow.anim.Anim(0); 7857 } 7858 7859 /** 7860 @private 7861 @function 7862 @description This function generates the various anim shortcut functions 7863 */ 7864 function animShortcut(animName, animReverseName, animPropsFunc, defaultTween, onComplete, additionalFunc) { 7865 return function(duration, opts) { 7866 /*!debug*/ 7867 if (arguments.length > 2) { 7868 glow.debug.warn('[wrong count] glow.NodeList animation shortcuts expect 0, 1 or 2 arguments, not ' + arguments.length + '.'); 7869 } 7870 if (duration !== undefined && typeof duration !== 'number') { 7871 glow.debug.warn('[wrong type] glow.NodeList animation shortcuts expect number as "duration" argument, not ' + typeof duration + '.'); 7872 } 7873 if (opts !== undefined && typeof opts !== 'object') { 7874 glow.debug.warn('[wrong type] glow.NodeList animation shortcuts expect object as "opts" argument, not ' + typeof opts + '.'); 7875 } 7876 /*gubed!*/ 7877 7878 opts = opts || {}; 7879 7880 var item, 7881 reverseAnim, 7882 currentAnim, 7883 calcDuration, 7884 anim, 7885 i = this.length; 7886 7887 opts.tween = opts.tween || defaultTween; 7888 7889 if (duration === undefined) { 7890 duration = 1; 7891 } 7892 7893 calcDuration = duration; 7894 7895 while (i--) { 7896 item = this.item(i); 7897 currentAnim = item.data('glow_' + animName); 7898 // if this isn't an element ,or we're already animating it, skip 7899 if ( item[0].nodeType !== 1 || (currentAnim && currentAnim.playing) ) { continue; } 7900 7901 // if there's a reverse anim happening & it's playing, get rid 7902 reverseAnim = item.data('glow_' + animReverseName); 7903 if (reverseAnim && reverseAnim.playing) { 7904 // reduce the duration if we're not fading out as much 7905 calcDuration = duration * (reverseAnim.position / reverseAnim.duration); 7906 7907 reverseAnim.stop().destroy(); 7908 } 7909 7910 item.data('glow_' + animName, 7911 anim = item.anim( calcDuration, animPropsFunc(item), opts ).on('complete', onComplete, item) 7912 ); 7913 7914 additionalFunc && additionalFunc(anim, item, opts); 7915 } 7916 7917 return this; 7918 } 7919 }; 7920 7921 /** 7922 @name glow.NodeList#fadeIn 7923 @function 7924 @description Fade elements in 7925 If the element is currently fading out, the fadeOut animation will be automatically stopped. 7926 7927 @param {number} [duration=1] Duration in seconds 7928 @param {Object} [opts] Options object 7929 @param {function|string} [opts.tween='easeOut'] The motion of the animation. 7930 Strings are treated as properties of {@link glow.tweens}, although 7931 a tween function can be provided. 7932 7933 @returns {glow.NodeList} 7934 7935 @example 7936 // make a tooltip fade in & out 7937 var tooltip = glow('#emailTooltip'); 7938 7939 glow('#emailInput').on('focus', function() { 7940 tooltip.fadeIn(); 7941 }).on('blur', function() { 7942 tooltip.fadeOut(); 7943 }); 7944 */ 7945 NodeListProto.fadeIn = animShortcut('fadeIn', 'fadeOut', function(item) { 7946 item.css('display', 'block'); 7947 return {opacity: 1}; 7948 }, 'easeOut', function() { 7949 // on complete 7950 // we remove the filter from IE to bring back cleartype 7951 if (glow.env.ie) { 7952 this[0].style.filter = ''; 7953 } 7954 }); 7955 7956 /** 7957 @name glow.NodeList#fadeOut 7958 @function 7959 @description Fade elements out 7960 If the element is currently fading in, the fadeIn animation will be automatically stopped. 7961 7962 @param {number} [duration=1] Duration in seconds 7963 @param {Object} [opts] Options object 7964 @param {function|string} [opts.tween='easeIn'] The motion of the animation. 7965 Strings are treated as properties of {@link glow.tweens}, although 7966 a tween function can be provided. 7967 7968 @returns {glow.NodeList} 7969 7970 @example 7971 // make a tooltip fade in & out 7972 var tooltip = glow('#emailTooltip'); 7973 7974 glow('#emailInput').on('focus', function() { 7975 tooltip.fadeIn(); 7976 }).on('blur', function() { 7977 tooltip.fadeOut(); 7978 }); 7979 */ 7980 NodeListProto.fadeOut = animShortcut('fadeOut', 'fadeIn', function() { 7981 return {opacity:0} 7982 }, 'easeIn', function() { 7983 this.css('display', 'none'); 7984 }); 7985 7986 /** 7987 @name glow.NodeList#fadeToggle 7988 @function 7989 @description Fade elements in/out 7990 If the element is currently fading in/out, the fadeIn/fadeOut animation 7991 will be automatically stopped. 7992 7993 // Implementation note: (delete me later) 7994 If the element has an opactity of 0, then fade in, otherwise fade out. 7995 UNLESS there's fadeOut animation currently happening on this element, 7996 then fade in. 7997 7998 @param {number} [duration=1] Duration in seconds 7999 @param {Object} [opts] Options object 8000 @param {function|string} [opts.tween] The motion of the animation. 8001 Strings are treated as properties of {@link glow.tweens}, although 8002 a tween function can be provided. 8003 8004 By default, 'easeIn' is used for fading out, and 'easeOut' is 8005 used for fading in. 8006 8007 @returns {glow.NodeList} 8008 8009 @example 8010 // make a tooltip fade in & out 8011 var tooltip = glow('#emailTooltip'); 8012 8013 glow('#toggleTooltip').on('click', function() { 8014 tooltip.fadeToggle(); 8015 }); 8016 */ 8017 NodeListProto.fadeToggle = function(duration, opts) { 8018 var i = this.length, 8019 item, 8020 fadeOutAnim; 8021 8022 while (i--) { 8023 item = this.item(i); 8024 if (item[0].nodeType === 1) { 8025 // if the element has an opacity of 0, or is currently fading out 8026 if ( item.css('opacity') === '0' || ((fadeOutAnim = item.data('glow_fadeOut')) && fadeOutAnim.playing) ) { 8027 item.fadeIn(duration, opts); 8028 } 8029 else { 8030 item.fadeOut(duration, opts); 8031 } 8032 } 8033 } 8034 8035 return this; 8036 }; 8037 8038 /** 8039 @name glow.NodeList#slideOpen 8040 @function 8041 @description Slide elements open 8042 This animates an element's height from its current height to its 8043 full auto-height size. 8044 8045 If the element is currently sliding shut, the slideShut animation 8046 will be automatically stopped. 8047 8048 @param {number} [duration=1] Duration in seconds 8049 @param {Object} [opts] Options object 8050 @param {function|string} [opts.tween='easeBoth'] The motion of the animation. 8051 Strings are treated as properties of {@link glow.tweens}, although 8052 a tween function can be provided. 8053 @param {boolean} [opts.lockToBottom=false] Lock the bottom of the content to the bottom of the element. 8054 This means the bottom of the content is shown first, rather than the top. 8055 8056 @returns {glow.NodeList} 8057 8058 @example 8059 var menuContent = glow('#menu div.content'); 8060 8061 glow('#menu').on('mouseenter', function() { 8062 menuContent.slideOpen(); 8063 }).on('mouseleave', function() { 8064 menuContent.slideShut(); 8065 }); 8066 8067 @example 8068 glow('#furtherInfoHeading').on('click', function() { 8069 glow('#furtherInfoContent').slideOpen(); 8070 }); 8071 8072 @example 8073 // add content onto an element, and slide to reveal the new content 8074 glow('<div>' + newContent + '</div>').appendTo('#content').height(0).slideOpen(); 8075 8076 */ 8077 NodeListProto.slideOpen = animShortcut('slideOpen', 'slideShut', function(item) { 8078 var currentHeight = item.css('height'), 8079 fullHeight; 8080 8081 if ( item.css('overflow') === 'visible' ) { 8082 item.css('overflow', 'hidden'); 8083 } 8084 8085 item.css('height', 'auto'); 8086 fullHeight = item.height(); 8087 item.css('height', currentHeight); 8088 return {height: fullHeight} 8089 }, 'easeBoth', function() { 8090 this.css('height', 'auto').scrollTop(0); 8091 }, lockToBottom); 8092 8093 /** 8094 @name glow.NodeList#slideShut 8095 @function 8096 @description Slide elements shut 8097 This animates an element's height from its current height to zero. 8098 8099 If the element is currently sliding open, the slideOpen animation 8100 will be automatically stopped. 8101 8102 @param {number} [duration=1] Duration in seconds 8103 @param {Object} [opts] Options object 8104 @param {function|string} [opts.tween='easeBoth'] The motion of the animation. 8105 Strings are treated as properties of {@link glow.tweens}, although 8106 a tween function can be provided. 8107 @param {boolean} [opts.lockToBottom=false] Lock the bottom of the content to the bottom of the element. 8108 This means the top of the content is hidden first, rather than the bottom. 8109 8110 @returns {glow.NodeList} 8111 8112 @example 8113 var menuContent = glow('#menu div.content'); 8114 8115 glow('#menu').on('mouseenter', function() { 8116 menuContent.slideOpen(); 8117 }).on('mouseleave', function() { 8118 menuContent.slideShut(); 8119 }); 8120 */ 8121 NodeListProto.slideShut = animShortcut('slideShut', 'slideOpen', function(item) { 8122 if ( item.css('overflow') === 'visible' ) { 8123 item.css('overflow', 'hidden'); 8124 } 8125 return {height: 0} 8126 }, 'easeBoth', function() {}, lockToBottom); 8127 8128 /** 8129 @private 8130 @function 8131 @description Add frame listener to lock content to the bottom of an item. 8132 @param {glow.anim.Anim} anim Anim to alter 8133 @param {glow.NodeList} element Element being animated 8134 @param {Object} opts Options from slide[Open|Shut|Toggle] 8135 */ 8136 function lockToBottom(anim, element, opts) { 8137 var node = element[0], 8138 scrollHeight = node.scrollHeight; 8139 8140 if (opts.lockToBottom) { 8141 anim.on('frame', function() { 8142 element.scrollTop( scrollHeight - node.offsetHeight ); 8143 }); 8144 } 8145 } 8146 8147 /** 8148 @name glow.NodeList#slideToggle 8149 @function 8150 @description Slide elements open/shut 8151 If the element is currently sliding open/shut, the slideOpen/slideShut animation 8152 will be automatically stopped. 8153 8154 // Implementation note: (delete me later) 8155 If the element has a height of 0, then slide open, otherwise slide shut. 8156 UNLESS there's slideShut animation currently happening on this element, 8157 then slide open. 8158 8159 @param {number} [duration=1] Duration in seconds 8160 @param {Object} [opts] Options object 8161 @param {function|string} [opts.tween='easeBoth'] The motion of the animation. 8162 Strings are treated as properties of {@link glow.tweens}, although 8163 a tween function can be provided. 8164 @param {boolean} [opts.lockToBottom=false] Lock the bottom of the content to the bottom of the element. 8165 This means the top of the content is hidden first & shown last. 8166 8167 @returns {glow.NodeList} 8168 8169 @example 8170 var menuContent = glow('#menuContent'); 8171 8172 glow('#toggleMenu').on('click', function() { 8173 menuContent.slideToggle(); 8174 }); 8175 */ 8176 NodeListProto.slideToggle = function(duration, opts) { 8177 var i = this.length, 8178 item, 8179 slideShutAnim; 8180 8181 while (i--) { 8182 item = this.item(i); 8183 if (item[0].nodeType === 1) { 8184 // if the element has an height of 0, or is currently sliding shut 8185 if ( item.height() === 0 || ((slideShutAnim = item.data('glow_slideShut')) && slideShutAnim.playing) ) { 8186 item.slideOpen(duration, opts); 8187 } 8188 else { 8189 item.slideShut(duration, opts); 8190 } 8191 } 8192 } 8193 8194 return this; 8195 }; 8196 }); 8197 /** 8198 @name glow.net 8199 @namespace 8200 @description Methods for getting data & resources from other locations. Sometimes referred to as AJAX. 8201 */ 8202 Glow.provide(function(glow) { 8203 var net = {}, 8204 undefined, 8205 emptyFunc = function(){}; 8206 8207 /** 8208 @private 8209 @function 8210 @description Create XhrRequest factory methods 8211 8212 @param {string} method HTTP method 8213 @returns {function} Factory method 8214 */ 8215 function createXhrFactory(method) { 8216 return function(url, data, opts) { 8217 // only put & post use the data param 8218 if (method === 'POST' || method === 'PUT') { 8219 opts = opts || {}; 8220 opts.data = data; 8221 } 8222 else { 8223 opts = data; 8224 } 8225 8226 return new net.XhrRequest(method, url, opts); 8227 } 8228 } 8229 8230 /** 8231 @name glow.net.get 8232 @function 8233 @description Makes an HTTP GET request to a given url. 8234 This is a shortcut to creating an instance of {@link glow.net.XhrRequest}. 8235 8236 @param {string} url Url to make the request to. 8237 This can be a relative path. You cannot make requests for files on 8238 other domains (including sub-domains). For cross-domain requests, see 8239 {@link glow.dom.getJsonp} and {@link glow.dom.crossDomainGet}. 8240 @param {Object} [opts] Options. 8241 These options are the same as the constructor options for {@link glow.net.XhrRequest}. 8242 8243 @returns {glow.net.XhrRequest} 8244 8245 @example 8246 glow.net.get('myFile.html').on('load', function(response){ 8247 alert( 'Got file:' + response.text() ); 8248 }).on('error', function(response){ 8249 alert( 'Something went wrong:' + response.text() ); 8250 }); 8251 8252 */ 8253 net.get = createXhrFactory('GET'); 8254 8255 /** 8256 @name glow.net.post 8257 @function 8258 @description Makes an HTTP POST request to a given url 8259 This is a shortcut to creating an instance of {@link glow.net.XhrRequest}. 8260 8261 @param {string} url Url to make the request to. 8262 This can be a relative path. You cannot make requests for files on 8263 other domains (including sub-domains). For cross-domain requests, see 8264 {@link glow.dom.getJsonp} and {@link glow.dom.crossDomainGet}. 8265 @param {Object|String} data Data to send. 8266 This can be either a JSON-style object or a urlEncoded string. 8267 @param {Object} [opts] Options. 8268 These options are the same as the constructor options for {@link glow.net.XhrRequest}. 8269 8270 @returns {glow.net.XhrRequest} 8271 8272 @example 8273 glow.net.post('myFile.html', { 8274 key: 'value', 8275 otherkey: ['value1', 'value2'] 8276 }).on('load', function(response) { 8277 alert( 'Got file:' + response.text() ); 8278 }); 8279 */ 8280 net.post = createXhrFactory('POST'); 8281 8282 /** 8283 @name glow.net.put 8284 @function 8285 @description Makes an HTTP PUT request to a given url 8286 This is a shortcut to creating an instance of {@link glow.net.XhrRequest}. 8287 8288 @param {string} url Url to make the request to. 8289 This can be a relative path. You cannot make requests for files on 8290 other domains (including sub-domains). For cross-domain requests, see 8291 {@link glow.dom.getJsonp} and {@link glow.dom.crossDomainGet}. 8292 @param {Object|String} data Data to send. 8293 This can be either a JSON-style object or a urlEncoded string. 8294 @param {Object} [opts] Options. 8295 These options are the same as the constructor options for {@link glow.net.XhrRequest}. 8296 8297 @returns {glow.net.XhrRequest} 8298 8299 @example 8300 glow.net.put('myFile.html', { 8301 key: 'value', 8302 otherkey: ['value1', 'value2'] 8303 }).on('load', function(response) { 8304 // handle response 8305 }); 8306 */ 8307 net.put = createXhrFactory('PUT'); 8308 8309 /** 8310 @name glow.net.del 8311 @function 8312 @description Makes an HTTP DELETE request to a given url. 8313 This is a shortcut to creating an instance of {@link glow.net.XhrRequest}. 8314 8315 @param {string} url Url to make the request to. 8316 This can be a relative path. You cannot make requests for files on 8317 other domains (including sub-domains). For cross-domain requests, see 8318 {@link glow.dom.getJsonp} and {@link glow.dom.crossDomainGet}. 8319 @param {Object} [opts] Options. 8320 These options are the same as the constructor options for {@link glow.net.XhrRequest}. 8321 8322 @returns {glow.net.XhrRequest} 8323 8324 @example 8325 glow.net.del('myFile.html').on('load', function(response) { 8326 // handle response 8327 }); 8328 */ 8329 8330 net.del = createXhrFactory('DELETE'); 8331 8332 // export 8333 glow.net = net; 8334 }); 8335 Glow.provide(function(glow) { 8336 var undefined, 8337 XhrRequestProto, 8338 events = glow.events, 8339 removeAllListeners = events.removeAllListeners; 8340 8341 /** 8342 @private 8343 @function 8344 @description Creates an XMLHttpRequest transport 8345 @returns XMLHttpRequest 8346 */ 8347 var xmlHTTPRequest = window.ActiveXObject ? 8348 function() { 8349 return new ActiveXObject('Microsoft.XMLHTTP'); 8350 } : 8351 function() { 8352 return new XMLHttpRequest(); 8353 }; 8354 8355 /** 8356 @private 8357 @function 8358 @description Apply option object defaults. 8359 8360 @param {object} opts Options object to apply defaults to. 8361 @param {string} method HTTP method. 8362 8363 @returns {object} New opts object with defaults applied. 8364 */ 8365 function applyOptsDefaults(opts, method) { 8366 opts = glow.util.apply({ 8367 headers: {} 8368 }, opts); 8369 8370 var headers = opts.headers; 8371 8372 // convert data to string 8373 if (typeof opts.data === 'object') { 8374 opts.data = glow.util.encodeUrl(opts.data); 8375 } 8376 8377 // add requested with header if one hasn't been added 8378 if ( !headers['X-Requested-With'] ) { 8379 headers['X-Requested-With'] = 'XMLHttpRequest'; 8380 } 8381 8382 if (method !== 'GET' && !headers["Content-Type"]) { 8383 headers["Content-Type"] = 'application/x-www-form-urlencoded;'; 8384 } 8385 8386 return opts; 8387 } 8388 8389 /** 8390 @name glow.net.XhrRequest 8391 @class 8392 @param {string} method The HTTP method to use for the request. 8393 Methods are case sensitive in some browsers. 8394 @param {string} url Url to make the request to. 8395 This can be a relative path. You cannot make requests for files on 8396 other domains (including sub-domains). For cross-domain requests, see 8397 {@link glow.dom.getJsonp} and {@link glow.dom.crossDomainGet}. 8398 @param {Object} [opts] Options object 8399 @param {Object} [opts.headers] A hash of headers to send along with the request. 8400 eg `{'Accept-Language': 'en-gb'}` 8401 @param {boolean} [opts.cacheBust=false] Prevent the browser returning a cached response. 8402 If true, a value is added to the query string to ensure a fresh version of the 8403 file is being fetched. 8404 @param {number} [opts.timeout] Time to allow for the request in seconds. 8405 No timeout is set by default. Once the time is reached, the error 8406 event will fire with a '408' status code. 8407 @param {boolean} [opts.forceXml=false] Treat the response as XML. 8408 This will allow you to use {@link glow.net.XhrResponse#xml response.xml()} 8409 even if the response has a non-XML mime type. 8410 @param {Object|string} [opts.data] Data to send. 8411 This can be either a JSON-style object or a urlEncoded string. 8412 8413 @description Create an XHR request. 8414 Most common requests can be made using shortcuts methods in {@link glow.net}, 8415 such as {@link glow.net.get}. 8416 8417 @example 8418 new glow.net.XhrRequest('DELETE', 'whatever.php', { 8419 timeout: 10 8420 }).on('load', function(response) { 8421 alert( response.text() ); 8422 }); 8423 */ 8424 function XhrRequest(method, url, opts) { 8425 this._opts = opts = applyOptsDefaults(opts, method); 8426 8427 var request = this, 8428 nativeRequest = request.nativeRequest = xmlHTTPRequest(), //request object 8429 i; 8430 8431 // add the cacheBust to the url 8432 if (opts.cacheBust) { 8433 url = url + (url.indexOf('?') === -1 ? '?' : '&') + 'cachebuster=' + new Date().valueOf(); 8434 } 8435 8436 request.complete = false; 8437 8438 //open needs to go first to maintain cross-browser support for readystates 8439 nativeRequest.open(method, url, true); 8440 8441 //add custom headers 8442 for (i in opts.headers) { 8443 nativeRequest.setRequestHeader( i, opts.headers[i] ); 8444 } 8445 8446 // force the reponse to be treated as xml 8447 // IE doesn't support overrideMineType, we need to deal with that in {@link glow.net.XhrResponse#xml} 8448 if (opts.forceXml && nativeRequest.overrideMimeType) { 8449 nativeRequest.overrideMimeType('application/xml'); 8450 } 8451 8452 //sort out the timeout if there is one 8453 if (opts.timeout) { 8454 request._timeout = setTimeout(function() { 8455 var response = new glow.net.XhrResponse(request, true); 8456 request.abort().fire('error', response); 8457 }, opts.timeout * 1000); 8458 } 8459 8460 nativeRequest.onreadystatechange = function() { 8461 if (nativeRequest.readyState === 4) { 8462 var response = new glow.net.XhrResponse(request); 8463 8464 //clear the timeout 8465 clearTimeout(request._timeout); 8466 8467 //set as completed 8468 request.completed = true; 8469 request.fire(response.successful ? 'load' : 'error', response); 8470 8471 // prevent parent scopes leaking (cross-page) in IE 8472 nativeRequest.onreadystatechange = new Function(); 8473 removeAllListeners(request); 8474 } 8475 }; 8476 8477 // make sure it doesn't complete before listeners are attached 8478 setTimeout(function() { 8479 nativeRequest.send(opts.data || null); 8480 }, 0); 8481 } 8482 glow.util.extend(XhrRequest, events.Target); 8483 XhrRequestProto = XhrRequest.prototype; 8484 8485 /** 8486 @name glow.net.XhrRequest#_timeout 8487 @private 8488 @description setTimeout ID 8489 @type number 8490 */ 8491 8492 /** 8493 @name glow.net.XhrRequest#complete 8494 @description Boolean indicating whether the request has completed 8495 @example 8496 // request.complete with an asynchronous call 8497 var request = glow.net.get( 8498 "myFile.html").on('load', 8499 function(response){ 8500 alert(request.complete); // returns true 8501 }) 8502 8503 8504 @type boolean 8505 */ 8506 8507 /** 8508 @name glow.net.XhrRequest#nativeRequest 8509 @description The request object from the browser. 8510 This may not have the same properties and methods across user agents. 8511 Also, this will be undefined if the request originated from getJsonp. 8512 8513 @type Object 8514 */ 8515 8516 /** 8517 @name glow.net.XhrRequest#abort 8518 @function 8519 @description Aborts a request 8520 The load & error events will not fire. 8521 @example 8522 var request = glow.net.get('myFile.html').on('load', function(response) { 8523 //handle response 8524 }).on('abort', function() { 8525 alert('Something bad happened. The request was aborted.'); 8526 }); 8527 8528 request.abort(); // alerts "Something bad happened. The request was aborted" 8529 @returns this 8530 */ 8531 XhrRequestProto.abort = function() { 8532 if ( !this.completed && !this.fire('abort').defaultPrevented() ) { 8533 clearTimeout(this._timeout); 8534 this.nativeRequest.onreadystatechange = new Function(); 8535 removeAllListeners(this); 8536 } 8537 return this; 8538 }; 8539 8540 /** 8541 @name glow.net.XhrRequest#event:load 8542 @event 8543 @param {glow.net.XhrResponse} response 8544 @description Fired when the request is sucessful 8545 This will be fired when request returns with an HTTP code of 2xx. 8546 */ 8547 8548 /** 8549 @name glow.net.XhrRequest#event:abort 8550 @event 8551 @param {glow.events.Event} event Event Object 8552 @description Fired when the request is aborted 8553 If you cancel the default (eg, by returning false) the request 8554 will continue. 8555 */ 8556 8557 /** 8558 @name glow.net.XhrRequest#event:error 8559 @event 8560 @param {glow.net.XhrResponse} response 8561 @description Fired when the request is unsucessful 8562 This will be fired when request returns with an HTTP code which 8563 isn't 2xx or the request times out. 8564 */ 8565 8566 glow.net.XhrRequest = XhrRequest; 8567 }); 8568 Glow.provide(function(glow) { 8569 var XhrResponseProto, 8570 util = glow.util; 8571 8572 /** 8573 @name glow.net.XhrResponse 8574 @class 8575 @extends glow.events.Event 8576 @description The event object for {@link glow.net.XhrRequest}'s 'load' & 'error' events. 8577 @glowPrivateConstructor There is no direct constructor. 8578 */ 8579 8580 /* 8581 These params are hidden as we don't want users to try and create instances of this... 8582 8583 @param {glow.net.XhrRequest} [request] Original request object 8584 @param {Boolean} [timedOut=false] Set to true if the response timed out 8585 8586 */ 8587 function XhrResponse(request, timedOut) { 8588 var nativeResponse = this.nativeResponse = request.nativeRequest; 8589 this._request = request; 8590 8591 //IE reports status as 1223 rather than 204, for laffs 8592 this.status = timedOut ? 408 : 8593 nativeResponse.status == 1223 ? 204 : nativeResponse.status; 8594 8595 this.timedOut = !!timedOut; 8596 8597 this.successful = (this.status >= 200 && this.status < 300) || 8598 //from cache 8599 this.status == 304 || 8600 //watch our for requests from file:// 8601 (this.status == 0 && nativeResponse.responseText); 8602 } 8603 8604 util.extend(XhrResponse, glow.events.Event); 8605 XhrResponseProto = XhrResponse.prototype; 8606 8607 /** 8608 @name glow.net.XhrResponse#_request 8609 @private 8610 @description Original request object 8611 @type glow.net.XhrRequest 8612 */ 8613 8614 /** 8615 @name glow.net.XhrResponse#nativeResponse 8616 @description The response object from the browser. 8617 This may not have the same properties and methods across user agents. 8618 @type XMLHttpRequest 8619 */ 8620 8621 /** 8622 @name glow.net.XhrResponse#status 8623 @description HTTP status code of the response 8624 @type number 8625 */ 8626 8627 /** 8628 @name glow.net.XhrResponse#timedOut 8629 @description Boolean indicating if the requests time out was reached. 8630 @type boolean 8631 */ 8632 8633 /** 8634 @name glow.net.XhrResponse#successful 8635 @description Boolean indicating if the request returned successfully. 8636 @type boolean 8637 */ 8638 8639 /** 8640 @name glow.net.XhrResponse#text 8641 @function 8642 @description Gets the body of the response as plain text 8643 @returns {string} 8644 */ 8645 8646 XhrResponseProto.text = function() { 8647 return this.nativeResponse.responseText; 8648 }; 8649 8650 /** 8651 @name glow.net.XhrResponse#xml 8652 @function 8653 @description Gets the body of the response as xml 8654 @returns {XML} 8655 */ 8656 8657 XhrResponseProto.xml = function() { 8658 var nativeResponse = this.nativeResponse, 8659 contentType = this.header("Content-Type"); 8660 8661 if ( 8662 // IE 6 & 7 fail to recognise Content-Types ending +xml (eg application/rss+xml) 8663 // Files from the filesystem don't have a content type, but could be xml files, parse them to be safe 8664 glow.env.ie && ( 8665 contentType.slice(-4) === '+xml' || 8666 contentType === '' || 8667 this._request._opts.forceXml 8668 ) 8669 ) { 8670 var doc = new ActiveXObject("Microsoft.XMLDOM"); 8671 doc.loadXML( nativeResponse.responseText ); 8672 return doc; 8673 } 8674 else { 8675 return nativeResponse.responseXML; 8676 } 8677 }; 8678 8679 /** 8680 @name glow.net.XhrResponse#json 8681 @function 8682 @description Gets the body of the response as a JSON object. 8683 8684 @param {boolean} [safeMode=false] 8685 If true, the response will be parsed using a string parser which 8686 will filter out non-JSON javascript, this will be slower but 8687 recommended if you do not trust the data source. 8688 8689 @returns {object} 8690 */ 8691 XhrResponseProto.json = function(safe) { 8692 return util.decodeJson(this.text(), {safeMode:safe}); 8693 }; 8694 8695 /** 8696 @name glow.net.XhrResponse#nodeList 8697 @function 8698 @description Gets the body of the response as a {@link glow.NodeList}. 8699 8700 @returns {glow.NodeList} 8701 */ 8702 XhrResponseProto.nodeList = function(safe) { 8703 return glow( 8704 glow.NodeList._strToNodes( this.text() ) 8705 ); 8706 }; 8707 8708 /** 8709 @name glow.net.XhrResponse#header 8710 @function 8711 @description Gets a header from the response. 8712 8713 @param {string} name Header name 8714 @returns {string} Header value 8715 8716 @example var contentType = myResponse.header("Content-Type"); 8717 */ 8718 8719 XhrResponseProto.header = function(name) { 8720 return this.nativeResponse.getResponseHeader(name); 8721 }; 8722 8723 /** 8724 @name glow.net.XhrResponse#statusText 8725 @function 8726 @description Gets the meaning of {@link glow.net.XhrResponse#status status}. 8727 8728 @returns {string} 8729 */ 8730 XhrResponseProto.statusText = function() { 8731 return this.timedOut ? "Request Timeout" : this.nativeResponse.statusText; 8732 }; 8733 8734 glow.net.XhrResponse = XhrResponse; 8735 }); 8736 Glow.provide(function(glow) { 8737 var undefined, 8738 JsonpRequestProto, 8739 net = glow.net, 8740 emptyFunc = function(){}, 8741 events = glow.events, 8742 // Script elements that have been added via {@link glow.net.jsonp jsonp}, keyed by callback name 8743 scriptElements = {}, 8744 scriptElementsLen = 0, 8745 callbackPrefix = 'c', 8746 // Name of the global object used to store jsonp callbacks 8747 globalObjectName = '_' + glow.UID + 'jsonp', 8748 head = glow('head'), 8749 // a reference to the global object holding the callbacks 8750 globalObject; 8751 8752 /** 8753 @private 8754 @function 8755 @description Handle jsonp load. 8756 @param {glow.net.JsonpRequest} request 8757 @param {Object[]} args Arguments object passed to the callback from the jsonp source 8758 */ 8759 function jsonpLoad(request, args) { 8760 // we have to call listeners manually as we don't provide a real event object. A bit of a hack. 8761 var loadListeners = events._getListeners(request).load, 8762 i; 8763 8764 if (loadListeners) { 8765 loadListeners = loadListeners.slice(0); 8766 i = loadListeners.length; 8767 while (i--) { 8768 loadListeners[i][0].apply( loadListeners[i][1], args ); 8769 } 8770 } 8771 //set as completed 8772 request.completed = true; 8773 8774 cleanUp(request); 8775 } 8776 8777 /** 8778 @private 8779 @function 8780 @description Clean up to avoid memory leaks 8781 @param {glow.net.JsonpRequest} request 8782 @param {boolean} [leaveEmptyFunc] Replace global callback with blank function. 8783 If false, the global callback will be set to undefined, which is better for memory, 8784 but in some cases the callback may later be called (like a timed out request) so an 8785 empty function needs to be used to avoid errors. 8786 */ 8787 function cleanUp(request, leaveEmptyFunc) { 8788 var callbackName = request._callbackName; 8789 8790 clearTimeout(request._timeout); 8791 globalObject[callbackName] = leaveEmptyFunc ? emptyFunc : undefined; 8792 glow( scriptElements[callbackName] ).destroy(); 8793 scriptElements[callbackName] = undefined; 8794 } 8795 8796 /** 8797 @name glow.net.JsonpRequest 8798 @class 8799 @description A JSONP request. 8800 Although instance of this can be created manually, using 8801 {@link glow.net.jsonp} is preferred. 8802 */ 8803 // the params for this are the same as {@link glow.net.jsonp}. 8804 function JsonpRequest(url, opts) { 8805 opts = opts || {}; 8806 8807 var newIndex = scriptElements.length, 8808 //script element that gets inserted on the page 8809 //generated name of the callback 8810 callbackName = this._callbackName = callbackPrefix + (scriptElementsLen++), 8811 // script element to add to the page 8812 script = scriptElements[callbackName] = document.createElement('script'), 8813 request = this, 8814 timeout = opts.timeout, 8815 charset = opts.charset; 8816 8817 // add the callback name to the url 8818 url = glow.util.interpolate(url, { 8819 callback: globalObjectName + '.' + callbackName 8820 }); 8821 8822 // create the global object if it doesn't exist already 8823 globalObject || ( globalObject = window[globalObjectName] = {} ); 8824 8825 // create our callback 8826 globalObject[callbackName] = function() { 8827 jsonpLoad(request, arguments); 8828 }; 8829 8830 // set charset 8831 charset && (script.charset = charset); 8832 8833 if (opts.timeout) { 8834 request._timeout = setTimeout(function() { 8835 request.abort().fire('error'); 8836 }, timeout * 1000); 8837 } 8838 8839 script.src = url; 8840 8841 //add script to page 8842 head.prepend(script); 8843 8844 script = undefined; 8845 } 8846 8847 glow.util.extend(JsonpRequest, events.Target); 8848 JsonpRequestProto = JsonpRequest.prototype; 8849 8850 /** 8851 @name glow.net.JsonpRequest#_callbackName 8852 @private 8853 @description The name of the callback, used as a property name in globalObject and scriptElements 8854 */ 8855 8856 /** 8857 @name glow.net.JsonpRequest#_timeout 8858 @private 8859 @description timeout ID 8860 @type number 8861 */ 8862 8863 /** 8864 @name glow.net.JsonpRequest#complete 8865 @description Boolean indicating whether the request has completed 8866 @type boolean 8867 */ 8868 JsonpRequestProto.complete = false; 8869 8870 /** 8871 @name glow.net.JsonpRequest#abort 8872 @function 8873 @description Abort the request. 8874 The script file may still load, but the 'load' event will not fire. 8875 @returns this 8876 */ 8877 JsonpRequestProto.abort = function() { 8878 this.fire('abort'); 8879 cleanUp(this, true); 8880 return this; 8881 }; 8882 8883 /** 8884 @name glow.net.JsonpRequest#event:load 8885 @event 8886 @description Fired when the request is sucessful. 8887 The parameters to this event are whatever the datasource provides. 8888 8889 @example 8890 glow.net.jsonp('http://twitter.com/statuses/user_timeline/15390783.json?callback={callback}') 8891 .on('load', function(data) { 8892 alert(data); 8893 }); 8894 */ 8895 8896 /** 8897 @name glow.net.JsonpRequest#event:abort 8898 @event 8899 @param {glow.events.Event} event Event Object 8900 @description Fired when the request is aborted. 8901 */ 8902 8903 /** 8904 @name glow.net.JsonpRequest#event:error 8905 @event 8906 @param {glow.events.Event} event Event Object 8907 @description Fired when the request times out. 8908 */ 8909 8910 /** 8911 @name glow.net.jsonp 8912 @function 8913 @description Fetch JSON via JSONP. 8914 This can be used cross domain, but should only be used with trusted 8915 sources as any javascript included in the script will be executed. 8916 8917 This method only works if the server allows you to specify a callback 8918 name for JSON data. Not all JSON sources support this, check the API of the 8919 data source to ensure you're using the correct querystring parameter 8920 to set the callback name. 8921 8922 @param {string} url Url of the script. 8923 Set the callback name via the querystring to `{callback}`, Glow will 8924 replace this with another value and manage the callback internally. 8925 8926 Check the API of your data source for the correct parameter name. 8927 Eg, in Flickr it's `jsoncallback={callback}`, in Twitter it's 8928 `callback={callback}`. 8929 @param {object} [opts] 8930 @param {number} [opts.timeout] Time to allow for the request in seconds. 8931 @param {string} [opts.charset] Charset attribute value for the script. 8932 8933 @returns {glow.net.JsonpRequest} 8934 8935 @example 8936 glow.net.jsonp('http://twitter.com/statuses/user_timeline/15390783.json?callback={callback}', { 8937 timeout: 5 8938 }).on('load', function(data) { 8939 alert(data); 8940 }).on('error', function() { 8941 alert('Request timeout'); 8942 }); 8943 */ 8944 net.jsonp = function(url, opts) { 8945 return new glow.net.JsonpRequest(url, opts); 8946 }; 8947 8948 glow.net.JsonpRequest = JsonpRequest; 8949 }); 8950 Glow.provide(function(glow) { 8951 var undefined, 8952 ResourceRequestProto, 8953 ResourceResponseProto, 8954 net = glow.net; 8955 8956 /** 8957 @private 8958 @function 8959 @description Normalise urls param. 8960 Normalise ResourceRequest's urls parameter to an object with 'css', 'js' and 'img' properties. 8961 */ 8962 function normaliseUrlsParam(urls) { 8963 var r = { 8964 js: [], 8965 css: [], 8966 img: [] 8967 }, 8968 url; 8969 8970 if (typeof urls === 'object' && !urls.push) { 8971 r = glow.util.apply(r, urls); 8972 } 8973 else { 8974 // convert urls to an array if need be 8975 typeof urls === 'string' && ( urls = [urls] ); 8976 8977 // forwards loop, maintain order 8978 for (var i = 0, len = urls.length; i < len; i++) { 8979 url = urls[i]; 8980 if ( url.slice(-4) === '.css' ) { 8981 r.css[r.css.length] = url; 8982 } 8983 else if ( url.slice(-3) === '.js' ) { 8984 r.js[r.js.length] = url; 8985 } 8986 else { 8987 r.img[r.img.length] = url; 8988 } 8989 } 8990 } 8991 8992 return r; 8993 } 8994 8995 /** 8996 @name glow.net.ResourceRequest 8997 @class 8998 @description Request made via {@link glow.net.getResources} 8999 @glowPrivateConstructor There is no direct constructor. 9000 */ 9001 function ResourceRequest(urls) { 9002 urls = normaliseUrlsParam(urls); 9003 9004 var request = this, 9005 js = urls.js, 9006 css = urls.css, 9007 img = urls.img, 9008 jsLen = js.length, 9009 cssLen = css.length, 9010 imgLen = img.length, 9011 i; 9012 9013 request.totalResources = jsLen + cssLen + imgLen; 9014 9015 // ensure events don't fire until they're added 9016 setTimeout(function() { 9017 // guess it makes sense to load CSS, js then images (the browser will queue the requests) 9018 for (i = 0; i < cssLen; i++) { 9019 loadCss( request, css[i] ); 9020 } 9021 for (i = 0; i < jsLen; i++) { 9022 loadJs( request, js[i] ); 9023 } 9024 for (i = 0; i < imgLen; i++) { 9025 loadImg( request, img[i] ); 9026 } 9027 }, 0); 9028 9029 } 9030 9031 glow.util.extend(ResourceRequest, glow.events.Target); 9032 ResourceRequestProto = ResourceRequest.prototype; 9033 9034 /** 9035 @name glow.net.ResourceRequest#totalResources 9036 @type number 9037 @description Total number of resources requested. 9038 */ 9039 ResourceRequestProto.totalResources = 0; 9040 9041 /** 9042 @name glow.net.ResourceRequest#totalLoaded 9043 @type number 9044 @description Total number of resources successfully loaded. 9045 */ 9046 ResourceRequestProto.totalLoaded = 0; 9047 9048 /** 9049 @private 9050 @function 9051 @description Update a request after a resource loads. 9052 9053 @param {glow.net.ResourceRequest} request. 9054 @param {string} url Url of the requested resource. 9055 @param {glow.NodeList} resource The element used to load the resource. 9056 @param {string} type 'js', 'css' or 'img' 9057 */ 9058 function progress(request, url, resource, type) { 9059 9060 var totalLoaded = ++request.totalLoaded; 9061 9062 request.fire('progress', { 9063 resource: resource, 9064 url: url, 9065 type: type 9066 }); 9067 9068 if (totalLoaded === request.totalResources) { 9069 request.fire('load'); 9070 } 9071 } 9072 9073 /** 9074 @private 9075 @function 9076 @description Start loading an image 9077 9078 @param {glow.net.ResourceRequest} request 9079 @param {string} imgUrl 9080 */ 9081 function loadImg(request, imgUrl) { 9082 var img = new Image; 9083 // keep the url in its original format 9084 glow(img).data('srcUrl', imgUrl).on('load', imgLoaded, request); 9085 img.src = imgUrl; 9086 } 9087 9088 /** 9089 @private 9090 @function 9091 @description Process a loaded image. 9092 'this' is the ResourceRequest 9093 */ 9094 function imgLoaded(event) { 9095 var img = glow(event.attachedTo); 9096 progress( this, img.data('srcUrl'), img, 'img' ); 9097 } 9098 9099 /** 9100 @private 9101 @function 9102 @description Start loading a script 9103 9104 @param {glow.net.ResourceRequest} request 9105 @param {string} scriptUrl 9106 */ 9107 function loadJs(request, scriptUrl){ 9108 var script = glow( document.createElement('script') ) 9109 .data('srcUrl', scriptUrl) 9110 .prependTo('head'); 9111 9112 // two methods, one for IE (readystatechange) and the other for others 9113 script.on('readystatechange', jsLoaded, request).on('load', jsLoaded, request); 9114 9115 script[0].src = scriptUrl; 9116 } 9117 9118 /** 9119 @private 9120 @function 9121 @description Process a loaded script. 9122 'this' is the ResourceRequest 9123 */ 9124 function jsLoaded(event) { 9125 var script = glow(event.attachedTo), 9126 scriptElm = script[0], 9127 readyState = scriptElm.readyState; 9128 9129 if ( !readyState || readyState === 'loaded' || readyState === 'complete' ) { 9130 // remove events to prevent double-firing 9131 script.detach('readystatechange', jsLoaded).detach('load', jsLoaded); 9132 progress( this, script.data('srcUrl'), script, 'js' ); 9133 } 9134 } 9135 9136 /** 9137 @private 9138 @function 9139 @description Start loading a CSS file 9140 9141 @param {glow.net.ResourceRequest} request 9142 @param {string} cssUrl 9143 */ 9144 // This technique was found in http://code.google.com/p/ajaxsoft/source/browse/trunk/xLazyLoader 9145 function loadCss(request, cssUrl){ 9146 var currentHostname, 9147 urlHostname, 9148 link = glow('<link rel="stylesheet" type="text/css" media="all" href="' + cssUrl + '" />').data('srcUrl', cssUrl); 9149 9150 // we have to do something special for Gecko browsers when the css is from another domain 9151 if ( glow.env.gecko && /^(?:https?\:|\/\/)/.test(cssUrl) ) { 9152 currentHostname = location.hostname.replace('www.', ''); 9153 urlHostname = cssUrl.replace(/https?:\/\/|www\.|:.*/g, '').replace(/\/.*/g, ''); 9154 9155 if (currentHostname !== urlHostname) { 9156 // ack, we have to cheat 9157 setTimeout(function() { 9158 cssLoaded.call(request, { 9159 attachedTo: link 9160 }); 9161 }, 500); 9162 } 9163 } 9164 else { 9165 // two methods, one for IE (readystatechange), and one for opera 9166 link.on('readystatechange', cssLoaded, request).on('load', cssLoaded, request); 9167 // ...and one more for Moz & webkit 9168 (function pollCssRules() { 9169 try { 9170 link[0].sheet.cssRules; 9171 // we'll error before the next line if CSS hasn't loaded 9172 cssLoaded.call(request, { 9173 attachedTo: link 9174 }); 9175 } 9176 catch (e) { 9177 if ( !link.data('loaded') ) { 9178 setTimeout(pollCssRules, 20); 9179 } 9180 }; 9181 })(); 9182 } 9183 9184 //link[0].href = cssUrl; 9185 link.prependTo('head'); 9186 } 9187 9188 /** 9189 @private 9190 @function 9191 @description Process a loaded stylesheet. 9192 'this' is the ResourceRequest 9193 */ 9194 function cssLoaded(event) { 9195 var link = glow(event.attachedTo), 9196 linkElm = link[0], 9197 readyState = linkElm.readyState; 9198 9199 if ( !readyState || readyState === 'loaded' || readyState === 'complete' ) { 9200 // just incase there's a timeout still waiting 9201 if ( link.data('loaded') ) { 9202 return; 9203 } 9204 link.data('loaded', true); 9205 9206 // remove events to prevent double-firing 9207 link.detach('readystatechange', cssLoaded).detach('load', cssLoaded); 9208 progress( this, link.data('srcUrl'), link, 'css' ); 9209 } 9210 } 9211 9212 9213 /** 9214 @name glow.net.ResourceRequest#event:load 9215 @event 9216 @param {glow.events.Event} event Event Object 9217 @description Fired when all the requested items have completed. 9218 */ 9219 9220 /** 9221 @name glow.net.ResourceRequest#event:progress 9222 @event 9223 @description Fired when a single resource loads. 9224 9225 @param {glow.events.Event} event Event Object 9226 @param {string} event.url Url of the loaded resource. 9227 @param {glow.NodeList} event.resource The element used to load the resource. 9228 This will be a `<script>`, `<link>`, or `<img>` element. 9229 @param {string} event.type 'js', 'css' or 'img'. 9230 */ 9231 9232 /** 9233 @name glow.net.getResources 9234 @function 9235 @description Load scripts, images & CSS. 9236 Files can be loaded from other domains. 9237 9238 Note: Due to a cross-browser restriction, 'load' may fire before 9239 CSS files from another domain are fully loaded in Gecko browsers. 9240 9241 @param {string[]|string|Object} url 9242 Url(s) to load. Urls ending in ".css" are assumed to be CSS files, 9243 Urls ending in ".js" are assumed to be JavaScript. All other files 9244 will be treated as images. 9245 9246 You can provide an object in the form `{js: [], css: [], img: []}` to 9247 be explicit about how to treat each file. 9248 9249 @returns {glow.net.ResourceRequest} 9250 9251 @example 9252 // load a single CSS file with a callback specified 9253 glow.net.getResources('/styles/custom.css').on('load', function() { 9254 // CSS has now loaded 9255 }); 9256 9257 @example 9258 // load a single CSS file with a callback specified 9259 glow.net.getResources([ 9260 '/imgs/whatever.png', 9261 '/style/screen.css', 9262 ]).on('load', function() { 9263 // CSS & image now loaded 9264 }); 9265 9266 @example 9267 // load multiple files by specifying and array 9268 glow.net.getResources({ 9269 js: ['http://www.server.com/script', 'http://www.server.com/anotherScript'], 9270 img: ['http://www.server.com/product4/thumb'] 9271 }).on('progress', function(event) { 9272 // update a progress meter 9273 }).on('load', function(response){ 9274 // files now loaded 9275 }); 9276 */ 9277 net.getResources = function(urls, opts) { 9278 /*!debug*/ 9279 if (arguments.length < 1 && arguments.length > 2) { 9280 glow.debug.warn('[wrong count] glow.net.getResources expects 1 or 2 arguments, not ' + arguments.length + '.'); 9281 } 9282 /*gubed!*/ 9283 return new glow.net.ResourceRequest(urls, opts); 9284 }; 9285 9286 glow.net.ResourceRequest = ResourceRequest; 9287 }); 9288 Glow.provide(function(glow) { 9289 var undefined, 9290 CrossDomainRequestProto, 9291 CrossDomainResponseProto, 9292 net = glow.net, 9293 // We borrow some methods from XhrRequest later 9294 XhrResponseProto = net.XhrRequest.prototype, 9295 Target = glow.events.Target; 9296 9297 /** 9298 @name glow.net.CrossDomainRequest 9299 @class 9300 @description Cross-domain request via window.name 9301 A request made via a form submission in a hidden iframe, with the 9302 result being communicated via the name attribute of the iframe's window. 9303 9304 The URL that's requested should respond with a blank HTML page containing JavaScript 9305 that assigns the result to window.name as a string: 9306 9307 `<script type="text/javascript">window.name = 'result string';</script>` 9308 9309 Instances of this are returned by shortcut methods {@link glow.net.crossDomainGet} 9310 and {@link glow.net.crossDomainPost} 9311 9312 @param {string} method The HTTP method to use for the request. 9313 Only 'POST' and 'GET' are considered cross-browser. 9314 @param {string} url The URL to request. 9315 @param {Object} [opts] 9316 @param {Object|string} [opts.data] Data to send. 9317 This can be either a JSON-style object or a urlEncoded string. 9318 @param {number} [opts.timeout] Time to allow for the request in seconds. 9319 No timeout is set by default. 9320 @param {string} [opts.blankUrl='/favicon.ico'] 9321 The path of a page on same domain as the caller, ideally a page likely 9322 to be in the user's cache. 9323 */ 9324 function CrossDomainRequest(method, url, opts) { 9325 var request = this, 9326 timeout; 9327 9328 request._opts = opts = glow.util.apply({ 9329 data: {}, 9330 blankUrl: '/favicon.ico' 9331 }, opts); 9332 9333 // convert data to object 9334 if (typeof opts.data === 'string') { 9335 opts.data = glow.util.decodeUrl(opts.data); 9336 } 9337 9338 // set timeout for the request 9339 timeout = opts.timeout; 9340 if (timeout) { 9341 request._timeout = setTimeout(function () { 9342 request.fire('error'); 9343 cleanup(request); 9344 }, timeout * 1000); 9345 } 9346 9347 addIframe(request); 9348 buildAndSubmitForm(request, method, url); 9349 } 9350 glow.util.extend(CrossDomainRequest, Target); 9351 CrossDomainRequestProto = CrossDomainRequest.prototype; 9352 9353 /** 9354 @name glow.net.CrossDomainRequest#_opts 9355 @private 9356 @type Object 9357 @description Options object with defaults applied 9358 */ 9359 9360 /** 9361 @name glow.net.CrossDomainRequest#_iframe 9362 @private 9363 @type glow.NodeList 9364 @description Iframe used to send the data. 9365 */ 9366 9367 /** 9368 @name glow.net.CrossDomainRequest#_timeout 9369 @private 9370 @type number 9371 @description setTimeout id for request timeout 9372 */ 9373 9374 /** 9375 @private 9376 @function 9377 @description Add a hidden iframe for posting the request 9378 @param {glow.net.CrossDomainRequest} request 9379 */ 9380 function addIframe(request) { 9381 var iframe = request._iframe = glow( 9382 '<iframe style="visibility: hidden; position: absolute; height: 0;"></iframe>' 9383 ).appendTo(document.body); 9384 }; 9385 9386 /** 9387 @private 9388 @function 9389 @description Add a form to the iframe & submit it 9390 9391 @param {glow.net.CrossDomainRequest} request 9392 @param {string} method The HTTP method to use for the request. 9393 Only 'POST' and 'GET' are considered cross-browser. 9394 @param {string} url The URL to request. 9395 */ 9396 function buildAndSubmitForm(request, method, url) { 9397 var iframe = request._iframe, 9398 win = iframe[0].contentWindow, 9399 doc = win.document, 9400 form, 9401 data = request._opts.data; 9402 9403 // IE needs an empty document to be written to written to the iframe 9404 if (glow.env.ie) { 9405 doc.open(); 9406 doc.write('<html><body></body></html>'); 9407 doc.close(); 9408 } 9409 9410 // create form 9411 form = doc.createElement('form'); 9412 form.action = url; 9413 form.method = method; 9414 9415 doc.body.appendChild(form); 9416 9417 // build form elements 9418 for (var i in data) { 9419 if ( !data.hasOwnProperty(i) ) { continue; } 9420 9421 if (data[i] instanceof Array) { 9422 for (var j = 0, jLen = data[i].length; j < jLen; j++) { 9423 addHiddenInput( form, i, this.data[i][j] ); 9424 } 9425 } 9426 else { 9427 addHiddenInput( form, i, this.data[i] ); 9428 } 9429 } 9430 9431 // submit - the setTimeout makes the function run in the context of the form 9432 win.setTimeout(function () { 9433 form.submit(); 9434 }, 0); 9435 9436 // listen for form submitting 9437 iframe.on('load', handleResponse, request); 9438 } 9439 9440 /** 9441 @private 9442 @function 9443 @description Add a hidden input to a form for a piece of data. 9444 9445 @param {HTMLFormElement} form 9446 @param {string} name Input name 9447 @param {string} value Input value 9448 */ 9449 function addHiddenInput(form, name, value) { 9450 var input = form.ownerDocument.createElement('input'); 9451 input.type = 'hidden'; 9452 input.name = name; 9453 input.value = value; 9454 form.appendChild(input); 9455 } 9456 9457 /** 9458 @private 9459 @function 9460 @description Callback for load event in the hidden iframe. 9461 `this` is the request. 9462 */ 9463 function handleResponse() { 9464 var err, 9465 href, 9466 win = this._iframe[0].contentWindow; 9467 9468 try { 9469 href = win.location.href; 9470 } 9471 catch (e) { 9472 err = e; 9473 } 9474 9475 if (href !== 'about:blank' || err) { 9476 clearTimeout(this._timeout); 9477 this._iframe.detach('load', handleResponse).on('load', readHandler, this); 9478 9479 win.location = window.location.protocol + '//' + window.location.host + this._opts.blankUrl; 9480 } 9481 } 9482 9483 /** 9484 @private 9485 @function 9486 @description Callback for load event of blank page in same origin. 9487 `this` is the request. 9488 */ 9489 function readHandler() { 9490 this.fire( 'load', new CrossDomainResponse(this._iframe[0].contentWindow.name) ); 9491 cleanup(this); 9492 } 9493 9494 /** 9495 @private 9496 @function 9497 @description Removes the iframe and any event listeners. 9498 @param {glow.net.CrossDomainRequest} request 9499 */ 9500 function cleanup(request) { 9501 request._iframe.destroy(); 9502 glow.events.removeAllListeners(request); 9503 } 9504 9505 /** 9506 @name glow.net.CrossDomainRequest#event:load 9507 @event 9508 @param {glow.net.CrossDomainResponse} response 9509 @description Fired when the request is sucessful. 9510 */ 9511 9512 /** 9513 @name glow.net.CrossDomainRequest#event:error 9514 @event 9515 @param {glow.events.Event} event Event Object 9516 @description Fired when the request times out. 9517 */ 9518 9519 /** 9520 @name glow.net.CrossDomainResponse 9521 @class 9522 @description Response object for cross-domain requests. 9523 This is provided in {@link glow.net.CrossDomainRequest}'s 'load' event. 9524 @glowPrivateConstructor There is no direct constructor. 9525 */ 9526 function CrossDomainResponse(textResponse) { 9527 this._text = textResponse; 9528 } 9529 glow.util.extend(CrossDomainResponse, Target); 9530 CrossDomainResponseProto = CrossDomainResponse.prototype; 9531 9532 /** 9533 @name glow.net.CrossDomainResponse#_text 9534 @private 9535 @type string 9536 @description Text response from the server 9537 */ 9538 9539 /** 9540 @name glow.net.CrossDomainResponse#text 9541 @function 9542 @description Gets the body of the response as plain text. 9543 @returns {string} 9544 */ 9545 CrossDomainResponseProto.text = function() { 9546 return this._text; 9547 } 9548 9549 /** 9550 @name glow.net.CrossDomainResponse#json 9551 @function 9552 @description Gets the body of the response as a JSON object. 9553 9554 @param {boolean} [safeMode=false] 9555 If true, the response will be parsed using a string parser which 9556 will filter out non-JSON javascript, this will be slower but 9557 recommended if you do not trust the data source. 9558 9559 @returns {object} 9560 */ 9561 CrossDomainResponseProto.json = XhrResponseProto.json; 9562 9563 /** 9564 @name glow.net.CrossDomainResponse#nodeList 9565 @function 9566 @description Gets the body of the response as a {@link glow.NodeList}. 9567 9568 @returns {glow.NodeList} 9569 */ 9570 CrossDomainResponseProto.nodeList = XhrResponseProto.nodeList; 9571 9572 // ...and now, the factory methods! Yey! 9573 9574 /** 9575 @name glow.net.crossDomainPost 9576 @function 9577 @description Cross-domain post via window.name 9578 A request made via a form submission in a hidden iframe, with the 9579 result being communicated via the name attribute of the iframe's window. 9580 9581 The URL that's requested should respond with a blank HTML page containing JavaScript 9582 that assigns the result to window.name as a string: 9583 9584 `<script type="text/javascript">window.name = 'result string';</script>` 9585 9586 @param {string} url The URL to request. 9587 @param {Object|string} data Data to send. 9588 This can be either a JSON-style object or a urlEncoded string. 9589 @param {Object} [opts] 9590 @param {number} [opts.timeout] Time to allow for the request in seconds. 9591 No timeout is set by default. 9592 @param {string} [opts.blankUrl='/favicon.ico'] 9593 The path of a page on same domain as the caller, ideally a page likely 9594 to be in the user's cache. 9595 */ 9596 9597 net.crossDomainPost = function(url, data, opts) { 9598 opts = opts || {}; 9599 opts.data = data; 9600 return new CrossDomainRequest('POST', url, opts); 9601 }; 9602 9603 9604 /** 9605 @name glow.net.crossDomainGet 9606 @function 9607 @description Cross-domain get via window.name 9608 A request made via a form submission in a hidden iframe, with the 9609 result being communicated via the name attribute of the iframe's window. 9610 9611 The URL that's requested should respond with a blank HTML page containing JavaScript 9612 that assigns the result to window.name as a string: 9613 9614 `<script type="text/javascript">window.name = 'result string';</script>` 9615 9616 @param {string} url The URL to request. 9617 @param {Object} [opts] 9618 @param {number} [opts.timeout] Time to allow for the request in seconds. 9619 No timeout is set by default. 9620 @param {string} [opts.blankUrl='/favicon.ico'] 9621 The path of a page on same domain as the caller, ideally a page likely 9622 to be in the user's cache. 9623 */ 9624 9625 net.crossDomainGet = function(url, opts) { 9626 return new CrossDomainRequest('GET', url, opts); 9627 }; 9628 9629 // export 9630 glow.net.CrossDomainRequest = CrossDomainRequest; 9631 glow.net.CrossDomainResponse = CrossDomainResponse; 9632 }); 9633 Glow.provide(function(glow) { 9634 var tweens = glow.tweens = {}; 9635 /** 9636 @name glow.tweens 9637 @namespace 9638 @description Functions for controlling the motion of an animation 9639 */ 9640 9641 /* 9642 @name _reverse 9643 @private 9644 @description Takes a tween function and returns a function which does the reverse 9645 */ 9646 function _reverse(tween) { 9647 return function(t) { 9648 return 1 - tween(1 - t); 9649 } 9650 } 9651 9652 /** 9653 @name glow.tweens.linear 9654 @function 9655 @description Creates linear tween. 9656 Will transition values from start to finish with no 9657 acceleration or deceleration. 9658 9659 @returns {function} 9660 */ 9661 tweens.linear = function() { 9662 return function(t) { return t; }; 9663 }; 9664 9665 /** 9666 @name glow.tweens.easeIn 9667 @function 9668 @description Creates a tween which starts off slowly and accelerates. 9669 9670 @param {number} [strength=2] How strong the easing will be. 9671 9672 The higher the number the slower the animation starts and the quicker it ends. 9673 9674 @returns {function} 9675 */ 9676 tweens.easeIn = function(strength) { 9677 strength = strength || 2; 9678 return function(t) { 9679 return Math.pow(1, strength - 1) * Math.pow(t, strength); 9680 } 9681 }; 9682 9683 9684 /** 9685 @name glow.tweens.easeOut 9686 @function 9687 @description Creates a tween which starts off fast and decelerates. 9688 9689 @param {number} [strength=2] How strong the easing will be. 9690 9691 The higher the number the quicker the animation starts and the slower it ends. 9692 9693 @returns {function} 9694 */ 9695 tweens.easeOut = function(strength) { 9696 return _reverse(this.easeIn(strength)); 9697 }; 9698 9699 9700 /** 9701 @name glow.tweens.easeBoth 9702 @function 9703 @description Creates a tween which starts off slowly, accelerates then decelerates after the half way point. 9704 9705 This produces a smooth and natural looking transition. 9706 9707 @param {number} [strength=2] How strong the easing is. 9708 9709 A higher number produces a greater difference between 9710 start/end speed and the mid speed. 9711 9712 @returns {function} 9713 */ 9714 tweens.easeBoth = function(strength) { 9715 return this.combine(this.easeIn(strength), this.easeOut(strength)); 9716 }; 9717 9718 9719 /** 9720 @name glow.tweens.overshootIn 9721 @function 9722 @description Returns the reverse of {@link glow.tweens.overshootOut overshootOut} 9723 9724 @param {number} [amount=1.70158] How much to overshoot. 9725 9726 The default is 1.70158 which results in a 10% overshoot. 9727 9728 @returns {function} 9729 */ 9730 tweens.overshootIn = function(amount) { 9731 return _reverse(this.overshootOut(amount)); 9732 }; 9733 9734 9735 /** 9736 @name glow.tweens.overshootOut 9737 @function 9738 @description Creates a tween which overshoots its end point then returns to its end point. 9739 9740 @param {number} [amount=1.70158] How much to overshoot. 9741 9742 The default is 1.70158 which results in a 10% overshoot. 9743 9744 @returns {function} 9745 */ 9746 tweens.overshootOut = function(amount) { 9747 amount = amount || 1.70158; 9748 return function(t) { 9749 if (t == 0 || t == 1) { return t; } 9750 return ((t -= 1)* t * ((amount + 1) * t + amount) + 1); 9751 } 9752 }; 9753 9754 9755 /** 9756 @name glow.tweens.overshootBoth 9757 @function 9758 @description Returns a combination of {@link glow.tweens.overshootIn overshootIn} and {@link glow.tweens.overshootOut overshootOut} 9759 9760 @param {number} [amount=1.70158] How much to overshoot. 9761 9762 The default is 1.70158 which results in a 10% overshoot. 9763 9764 @returns {function} 9765 */ 9766 tweens.overshootBoth = function(amount) { 9767 return this.combine(this.overshootIn(amount), this.overshootOut(amount)); 9768 }; 9769 9770 9771 /** 9772 @name glow.tweens.bounceIn 9773 @function 9774 @description Returns the reverse of {@link glow.tweens.bounceOut bounceOut} 9775 9776 @returns {function} 9777 */ 9778 tweens.bounceIn = function() { 9779 return _reverse(this.bounceOut()); 9780 }; 9781 9782 9783 /** 9784 @name glow.tweens.bounceOut 9785 @function 9786 @description Returns a tween which bounces against the final value 3 times before stopping 9787 9788 @returns {function} 9789 */ 9790 tweens.bounceOut = function() { 9791 return function(t) { 9792 if (t < (1 / 2.75)) { 9793 return 7.5625 * t * t; 9794 } 9795 9796 else if (t < (2 / 2.75)) { 9797 return (7.5625 * (t -= (1.5 / 2.75)) * t + .75); 9798 } 9799 9800 else if (t < (2.5 / 2.75)) { 9801 return (7.5625 * (t -= (2.25 / 2.75)) * t + .9375); 9802 } 9803 9804 else { 9805 return (7.5625 * (t -= (2.625 / 2.75)) * t + .984375); 9806 } 9807 }; 9808 }; 9809 9810 9811 /** 9812 @name glow.tweens.elasticIn 9813 @function 9814 @description Returns the reverse of {@link glow.tweens.elasticOut elasticOut} 9815 9816 @param {number} [amplitude=1] How strong the elasticity will be. 9817 9818 @param {number} [frequency=3.33] The frequency. 9819 9820 @returns {function} 9821 */ 9822 tweens.elasticIn = function(amplitude, frequency) { 9823 return _reverse(this.elasticOut(amplitude, frequency)); 9824 }; 9825 9826 9827 /** 9828 @name glow.tweens.elasticOut 9829 @function 9830 @description Creates a tween which has an elastic movement. 9831 9832 You can tweak the tween using the parameters but you'll 9833 probably find the defaults sufficient. 9834 9835 @param {number} [amplitude=1] How strong the elasticity is. 9836 9837 @param {number} [frequency=3.33] The frequency. 9838 9839 @returns {function} 9840 */ 9841 tweens.elasticOut = function(amplitude, frequency) { 9842 var period = 1 / (frequency || 10 / 3); 9843 amplitude = amplitude || 1; 9844 return function (t) { 9845 var s; 9846 if (t == 0 || t == 1) { 9847 return t; 9848 } 9849 if (amplitude < 1) { 9850 s = period / 4; 9851 } 9852 else { 9853 s = period / (2 * Math.PI) * Math.asin(1 / amplitude); 9854 } 9855 return amplitude * Math.pow(2, -10 * t) * Math.sin( (t-s) * (2 * Math.PI) / period) + 1; 9856 } 9857 }; 9858 9859 9860 /** 9861 @name glow.tweens.combine 9862 @function 9863 @description Create a tween from two tweens. 9864 9865 This can be useful to make custom tweens which, for example, 9866 start with an easeIn and end with an overshootOut. To keep 9867 the motion natural, you should configure your tweens so the 9868 first ends and the same velocity that the second starts. 9869 9870 @param {function} tweenIn Tween to use for the first half 9871 9872 @param {function} tweenOut Tween to use for the second half 9873 9874 @example 9875 // 4.5 has been chosen for the easeIn strength so it 9876 // ends at the same velocity as overshootOut starts. 9877 var myTween = glow.tweens.combine( 9878 glow.tweens.easeIn(4.5), 9879 glow.tweens.overshootOut() 9880 ); 9881 9882 @returns {function} 9883 */ 9884 tweens.combine = function(tweenIn, tweenOut) { 9885 return function (t) { 9886 if (t < 0.5) { 9887 return tweenIn(t * 2) / 2; 9888 } 9889 else { 9890 return tweenOut((t - 0.5) * 2) / 2 + 0.5; 9891 } 9892 } 9893 } 9894 9895 }); 9896 9897 /** 9898 @name glow.anim 9899 @namespace 9900 @description Creating and synchronising animations 9901 */ 9902 Glow.provide(function(glow) { 9903 var undefined, 9904 AnimProto, 9905 activeAnims = [], 9906 activeAnimsLen = 0, 9907 animInterval; 9908 9909 /** 9910 @private 9911 @function 9912 @description This is called on each interval 9913 This set the properties of each animation per frame. 9914 9915 This is the drill sgt of the Anim world. 9916 */ 9917 function onInterval() { 9918 var dateNum = new Date().valueOf(), 9919 i = activeAnimsLen, 9920 anim; 9921 9922 while (i--) { 9923 // ideally, this processing would be a function of Anim, but it's quicker this way 9924 anim = activeAnims[i]; 9925 anim.position = (dateNum - anim._syncTime) / 1000; 9926 9927 // see if this animation is ready to complete 9928 if (anim.position >= anim.duration) { 9929 anim.position = anim.duration; 9930 anim.value = anim.tween(1); 9931 // render final frame 9932 anim.fire('frame'); 9933 // fire 'frame' and 'complete' and see if we're going to loop (preventing default) 9934 if ( anim.fire('complete').defaultPrevented() || anim.loop ) { 9935 // loop the animation 9936 anim._syncTime = dateNum; 9937 } 9938 // else deactivave the anim 9939 else { 9940 // reset the stop position so further starts start from the beginning 9941 anim._stopPos = 0; 9942 deactivateAnim(anim); 9943 // destroy the anim if needed 9944 anim.destroyOnComplete && anim.destroy(); 9945 } 9946 } 9947 else { 9948 // set up the value and render a frame 9949 anim.value = anim.tween( anim.position / anim.duration ); 9950 anim.fire('frame'); 9951 } 9952 } 9953 } 9954 9955 /** 9956 @private 9957 @function 9958 @description Calls 'frame' on an animation on an interval 9959 */ 9960 function activateAnim(anim) { 9961 // if this is the first anim, start the timer 9962 if (!activeAnimsLen) { 9963 animInterval = setInterval(onInterval, 13); 9964 } 9965 activeAnims[ activeAnimsLen++ ] = anim; 9966 anim.playing = true; 9967 } 9968 9969 /** 9970 @private 9971 @function 9972 @description Stops calling 'frame' on an animation on an interval 9973 */ 9974 function deactivateAnim(anim) { 9975 // decided to search forward, animations ending are more likely to be older & at the start of the array. 9976 // This mutates activeAnims 9977 for (var i = 0, leni = activeAnims.length; i < leni; i++) { 9978 if (activeAnims[i] === anim) { 9979 activeAnims.splice(i, 1); 9980 activeAnimsLen--; 9981 // if we're out of anims, stop the timer 9982 if (!activeAnimsLen) { 9983 clearInterval(animInterval); 9984 } 9985 anim.playing = false; 9986 return; 9987 } 9988 } 9989 } 9990 9991 /** 9992 @name glow.anim.Anim 9993 @extends glow.events.Target 9994 @class 9995 @description Animate an object. 9996 To animate CSS properties, see {@link glow.NodeList#anim}. 9997 9998 Once you have an Anim instance, the {@link glow.anim.Anim#prop} method 9999 can be used to easily animate object properties from one value to another. 10000 If this isn't suitable, listen for the 'frame' event to change values 10001 over time. 10002 10003 @param {number} duration Length of the animation in seconds. 10004 @param {Object} [opts] Object of options. 10005 @param {function|string} [opts.tween='easeBoth'] The way the value changes over time. 10006 Strings are treated as properties of {@link glow.tweens} (eg 'bounceOut'), although 10007 a tween function can be provided. 10008 10009 The default is an {@link glow.tweens.easeBoth easeBoth} tween. 10010 Looped animations will fire a 'complete' event on each loop. 10011 @param {boolean} [opts.destroyOnComplete=true] Destroy the animation once it completes (unless it loops). 10012 Shortcut for {@link glow.anim.Anim#destroyOnComplete}. 10013 @param {boolean} [opts.loop=false] Loop the animation. 10014 Shortcut for setting {@link glow.anim.Anim#loop}. 10015 10016 @example 10017 // Using glow.anim.Anim to animate an SVG blur over 5 seconds, with an easeOut tween 10018 // feGaussianBlurElm is a reference to an <feGaussianBlur /> element. 10019 new glow.anim.Anim(5, { 10020 tween: 'easeOut' 10021 }).target(feGaussianBlurElm).prop('stdDeviation', { 10022 from: 0, 10023 to: 8 10024 }).start(); 10025 10026 @example 10027 // Animate a CSS property we don't support in glow.NodeList#anim 10028 // This rotates a Mozilla CSS gradient 10029 var styleObject = glow('#nav').prop('style'); 10030 10031 new glow.anim.Anim(10).target(styleObject).prop('background', { 10032 // the question-mark in the template is replaced with the animated value 10033 template: '-moz-linear-gradient(?deg, red, blue)' 10034 from: 0, 10035 to: 360 10036 }).start(); 10037 10038 @example 10039 // Animate a CSS property we don't support in glow.NodeList#anim 10040 // This changes the colour of a webkit drop shadow from yellow to blue 10041 var styleObject = glow('#nav').prop('style'); 10042 10043 new glow.anim.Anim(3).target(styleObject).prop('WebkitBoxShadow', { 10044 // the ? in the template are replaced with the animate values 10045 template: 'rgb(?, ?, ?) 0px 4px 14px' 10046 // provide a 'from' and 'to' value for each question-mark 10047 from: [255, 255, 0], 10048 to: [0, 0, 255], 10049 // round the value, colours can't be fractional 10050 round: true 10051 }).start(); 10052 10053 @example 10054 // Make an ASCII progress bar animate from: 10055 // [--------------------] 0% 10056 // to 10057 // [++++++++++++++++++++] 100% 10058 var progressBar = glow('#progressBar'), 10059 // our progress bar is 20 chars 10060 barSize = 20; 10061 10062 new glow.anim.Anim(2).on('frame', function() { 10063 var onChars = Math.floor(this.value * barSize), 10064 offChars = barSize - onChars, 10065 // add the + and - chars 10066 barStr = new Array(onChars + 1).join('+') + new Array(offChars + 1).join('-'); 10067 10068 progressBar.text('[' + barStr + '] ' + Math.floor(this.value * 100) + '%'); 10069 }).start(); 10070 10071 @see {@link glow.NodeList#anim} - shortcut for animating CSS values on an element. 10072 */ 10073 10074 function Anim(duration, opts) { 10075 /*!debug*/ 10076 if (arguments.length < 1 || arguments.length > 2) { 10077 glow.debug.warn('[wrong count] glow.anim.Anim expects 1 or 2 arguments, not ' + arguments.length + '.'); 10078 } 10079 if ( isNaN(duration) ) { 10080 glow.debug.warn('[wrong type] glow.anim.Anim expects number as "duration" argument, not ' + typeof duration + '.'); 10081 } 10082 if (opts !== undefined && typeof opts !== 'object') { 10083 glow.debug.warn('[wrong type] glow.anim.Anim expects object as "opts" argument, not ' + typeof opts + '.'); 10084 } 10085 if ( opts && typeof opts.tween === 'string' && !glow.tweens[opts.tween] ) { 10086 glow.debug.warn('[unexpected value] glow.anim.Anim - tween ' + opts.tween + ' does not exist'); 10087 } 10088 /*gubed!*/ 10089 10090 opts = glow.util.apply({ 10091 destroyOnComplete: true 10092 // other options have falsey defaults 10093 }, opts || {}); 10094 10095 this.destroyOnComplete = opts.destroyOnComplete; 10096 10097 10098 if (typeof opts.tween === 'string') { 10099 this.tween = glow.tweens[opts.tween](); 10100 } 10101 else if (opts.tween) { 10102 this.tween = opts.tween; 10103 } 10104 10105 this.loop = !!opts.loop; 10106 this.duration = +duration; 10107 // defined & used in prop.js 10108 this._targets = []; 10109 }; 10110 10111 glow.util.extend(Anim, glow.events.Target); 10112 AnimProto = Anim.prototype; 10113 10114 /** 10115 @name glow.anim.Anim#_syncTime 10116 @private 10117 @type number 10118 @description Number used to work out where the animation should be against the current date 10119 If an animation starts at 0, this number will be new Date().valueOf(), it'll be 10120 lower for animations that start at a midpoint 10121 */ 10122 10123 /** 10124 @name glow.anim.Anim#_stopPos 10125 @private 10126 @type number 10127 @description The position the animation was stopped at 10128 This is set on `.stop()` and used to resume from 10129 the same place on `.start()` 10130 */ 10131 10132 /** 10133 @name glow.anim.Anim#duration 10134 @type number 10135 @description Length of the animation in seconds. 10136 */ 10137 10138 /** 10139 @name glow.anim.Anim#tween 10140 @type function 10141 @description The tween used by the animation. 10142 */ 10143 AnimProto.tween = glow.tweens.easeBoth(); 10144 10145 /** 10146 @name glow.anim.Anim#position 10147 @readOnly 10148 @type number 10149 @description Position of the animation in seconds. 10150 */ 10151 AnimProto.position = 0; 10152 10153 /** 10154 @name glow.anim.Anim#playing 10155 @readOnly 10156 @type boolean 10157 @description `true` if the animation is playing. 10158 */ 10159 AnimProto.playing = false; 10160 10161 /** 10162 @name glow.anim.Anim#loop 10163 @type boolean 10164 @description Loop the animation? 10165 This value can be changed while an animation is playing. 10166 10167 Looped animations will fire a 'complete' event at the end of each loop. 10168 */ 10169 10170 /** 10171 @name glow.anim.Anim#destroyOnComplete 10172 @type boolean 10173 @description Destroy the animation once it completes (unless it loops). 10174 This will free any DOM references the animation may have created. Once 10175 the animation is destroyed, it cannot be started again. 10176 */ 10177 10178 /** 10179 @name glow.anim.Anim#value 10180 @type number 10181 @readOnly 10182 @description Current tweened value of the animation, usually between 0 & 1. 10183 This can be used in frame events to change values between their start 10184 and end value. 10185 10186 The value may be greater than 1 or less than 0 if the tween 10187 overshoots the start or end position. {@link glow.tweens.elasticOut} 10188 for instance will result in values higher than 1, but will still end at 1. 10189 10190 @example 10191 // Work out a value between startValue & endValue for the current point in the animation 10192 var currentValue = (endValue - startValue / myAnim.value) + startValue; 10193 */ 10194 AnimProto.value = 0; 10195 10196 /** 10197 @name glow.anim.Anim#start 10198 @function 10199 @description Starts playing the animation 10200 If the animation is already playing, this has no effect. 10201 10202 @param {number} [position] Position to start the animation at, in seconds. 10203 By default, this will be the last position of the animation (if it was stopped) 10204 or 0. 10205 10206 @returns this 10207 */ 10208 AnimProto.start = function(position) { 10209 /*!debug*/ 10210 if (arguments.length > 1) { 10211 glow.debug.warn('[wrong count] glow.anim.Anim#start expects 0 or 1 argument, not ' + arguments.length + '.'); 10212 } 10213 if (position !== undefined && typeof position !== 'number') { 10214 glow.debug.warn('[wrong type] glow.anim.Anim#start expects number as "position" argument, not ' + typeof position + '.'); 10215 } 10216 /*gubed!*/ 10217 10218 if ( !this.playing && !this.fire('start').defaultPrevented() ) { 10219 // we set 'playing' here so goTo knows 10220 this.playing = true; 10221 this.goTo(position === undefined ? (this._stopPos || 0) : position); 10222 activateAnim(this); 10223 } 10224 return this; 10225 }; 10226 10227 /** 10228 @name glow.anim.Anim#stop 10229 @function 10230 @description Stops the animation playing. 10231 Stopped animations can be resumed by calling {@link glow.anim.Anim#start start}. 10232 10233 If the animation isn't playing, this has no effect. 10234 @returns this 10235 */ 10236 AnimProto.stop = function() { 10237 /*!debug*/ 10238 if (arguments.length !== 0) { 10239 glow.debug.warn('[wrong count] glow.anim.Anim#stop expects 0 arguments, not ' + arguments.length + '.'); 10240 } 10241 /*gubed!*/ 10242 if ( this.playing && !this.fire('stop').defaultPrevented() ) { 10243 this._stopPos = this.position; 10244 deactivateAnim(this); 10245 } 10246 return this; 10247 }; 10248 10249 /** 10250 @name glow.anim.Anim#destroy 10251 @function 10252 @description Destroys the animation & detaches references to objects 10253 This frees memory & is called automatically when an animation 10254 completes. 10255 @returns undefined 10256 */ 10257 AnimProto.destroy = function() { 10258 /*!debug*/ 10259 if (arguments.length !== 0) { 10260 glow.debug.warn('[wrong count] glow.anim.Anim#destroy expects 0 arguments, not ' + arguments.length + '.'); 10261 } 10262 /*gubed!*/ 10263 glow.events.removeAllListeners( [this] ); 10264 this._targets = undefined; 10265 }; 10266 10267 /** 10268 @name glow.anim.Anim#goTo 10269 @function 10270 @description Goes to a specific point in the animation. 10271 @param {number} pos Position in the animation to go to, in seconds 10272 10273 @example 10274 // move the animation to 2.5 seconds in 10275 // If the animation is playing, it will continue to play from the new position. 10276 // Otherwise, it will simply move to that position. 10277 myAnim.goTo(2.5); 10278 10279 @returns this 10280 */ 10281 AnimProto.goTo = function(position) { 10282 /*!debug*/ 10283 if (arguments.length !== 1) { 10284 glow.debug.warn('[wrong count] glow.anim.Anim#goTo expects 1 argument, not ' + arguments.length + '.'); 10285 } 10286 if (typeof position !== 'number') { 10287 glow.debug.warn('[wrong type] glow.anim.Anim#goTo expects number as "position" argument, not ' + typeof position + '.'); 10288 } 10289 /*gubed!*/ 10290 if (position > this.duration) { 10291 position = this.duration; 10292 } 10293 else if (position < 0) { 10294 position = 0; 10295 } 10296 // set stopPos to this so the next call to start() starts from here 10297 this._stopPos = this.position = position; 10298 // move the syncTime for this position if we're playing 10299 if (this.playing) { 10300 this._syncTime = new Date - (position * 1000); 10301 } 10302 this.value = this.tween(position / this.duration); 10303 this.fire('frame'); 10304 return this; 10305 }; 10306 10307 /** 10308 @name glow.anim.Anim#event:start 10309 @event 10310 @description Fires when an animation starts. 10311 Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault}) 10312 prevents this animation from starting. 10313 10314 @param {glow.events.Event} event Event Object 10315 */ 10316 10317 /** 10318 @name glow.anim.Anim#event:frame 10319 @event 10320 @description Fires on each frame of the animation 10321 Use a combination of this event and {@link glow.anim.Anim#value value} 10322 to create custom animations. 10323 10324 See the {@link glow.anim.Anim constructor} for usage examples. 10325 10326 @param {glow.events.Event} event Event Object 10327 */ 10328 10329 /** 10330 @name glow.anim.Anim#event:stop 10331 @event 10332 @description Fires when an animation is stopped before completing 10333 Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault}) 10334 prevents this animation from stopping. 10335 10336 @param {glow.events.Event} event Event Object 10337 */ 10338 10339 /** 10340 @name glow.anim.Anim#event:complete 10341 @event 10342 @description Fires when an animation completes 10343 Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault}) 10344 causes the animation to loop. 10345 10346 @param {glow.events.Event} event Event Object 10347 10348 @example 10349 // Make an animation loop 5 times 10350 var loopCount = 5; 10351 myAnim.on('complete', function() { 10352 return !loopCount--; 10353 }); 10354 */ 10355 10356 // export 10357 glow.anim = {}; 10358 glow.anim.Anim = Anim; 10359 }); 10360 Glow.provide(function(glow) { 10361 /** 10362 @name glow.anim.Anim#_evalFunc 10363 @function 10364 @private 10365 @description Evals a function to be used as a frame listener 10366 This function is isolated from the others to reduce the impact of 10367 eval() on compression and garbage collection 10368 10369 'targets' is used by the compiled function 10370 */ 10371 glow.anim.Anim.prototype._evalFunc = function evalFunc(s, targets) { 10372 eval('var f=function(){' + s + '}'); 10373 return f; 10374 } 10375 }); 10376 10377 Glow.provide(function(glow) { 10378 var undefined, 10379 AnimProto = glow.anim.Anim.prototype; 10380 10381 /** 10382 @name glow.anim.Anim#_targets 10383 @private 10384 @type Object[] 10385 @description An array of objects added via #target 10386 */ 10387 10388 /** 10389 @name glow.anim.Anim#target 10390 @function 10391 @description Set the object for subsequent calls to {@link glow.anim.Anim#prop prop} to act on. 10392 @param {Object} newTarget The target object 10393 10394 @returns this 10395 10396 @example 10397 // animate objToAnimate.value from 0 to 10 over 3 seconds 10398 // and anotherObjToAnimate.data from -100 to 20 over 3 seconds 10399 10400 var objToAnimate = {}, 10401 anotherObjToAnimate = {}; 10402 10403 new glow.anim.Anim(3).target(objToAnimate).prop('value', { 10404 from: 0, 10405 to: 10 10406 }).target(anotherObjToAnimate).prop('data', { 10407 from: 100, 10408 to: -20 10409 }) 10410 */ 10411 AnimProto.target = function(newTarget) { 10412 /*!debug*/ 10413 if (arguments.length !== 1) { 10414 glow.debug.warn('[wrong count] glow.anim.Anim#target expects 1 argument, not ' + arguments.length + '.'); 10415 } 10416 if (typeof newTarget !== 'object') { 10417 glow.debug.warn('[wrong type] glow.anim.Anim#target expects object as "newTarget" argument, not ' + typeof newTarget + '.'); 10418 } 10419 /*gubed!*/ 10420 this._targets[ this._targets.length ] = newTarget; 10421 return this; 10422 }; 10423 10424 /** 10425 @name glow.anim.Anim#_funcStr 10426 @private 10427 @type Object 10428 @description The string for the function _propFunc 10429 This is retained so it can be added to for further 10430 calls to prop 10431 */ 10432 AnimProto._funcStr = ''; 10433 10434 /** 10435 @private 10436 @description Returns a string that calculates the current value for a property 10437 */ 10438 function buildValueCalculator(from, to, max, min, round) { 10439 // start with (from + (from - to) * this.value) 10440 var str = '(' + Number(from) + '+' + (to - from) + '*this.value)'; 10441 10442 // wrap in functions to keep values within range / round values if needed 10443 if (min !== undefined) { 10444 str = 'Math.max(' + str + ', ' + min + ')'; 10445 } 10446 if (max !== undefined) { 10447 str = 'Math.min(' + str + ', ' + max + ')'; 10448 } 10449 if (round) { 10450 str = 'Math.round(' + str + ')'; 10451 } 10452 10453 return str; 10454 } 10455 10456 /** 10457 @private 10458 @description Turn a template into a script that outputs values in place of ? 10459 */ 10460 function compileTemplate(template, from, to, max, min, round) { 10461 // no template? That's easy. 10462 if (!template) { 10463 return buildValueCalculator(from, to, max, min, round); 10464 } 10465 10466 var templateParts = template.split('?'), 10467 templatePart, 10468 str = '"' + templateParts[0].replace(/"/g, '\\"') + '"', 10469 // discover which values are arrays 10470 Array = window.Array, 10471 fromIsArray = from.constructor === Array, 10472 toIsArray = to.constructor === Array, 10473 maxIsArray = max !== undefined && max.constructor === Array, 10474 minIsArray = min !== undefined && min.constructor === Array, 10475 roundIsArray = round.constructor === Array, 10476 iMinusOne = 0; 10477 10478 for (var i = 1, leni = templateParts.length; i < leni; i++, iMinusOne++) { 10479 templatePart = templateParts[i]; 10480 10481 if ( templateParts[iMinusOne].slice(-1) === '\\' ) { 10482 // the user wants a literal question mark, put it back 10483 str += '+"?"'; 10484 } 10485 else { 10486 // remove trailing slash, it's being used to escape a ? 10487 if ( templatePart.slice(-1) === '\\' ) { 10488 templatePart = templatePart.slice(0, -1); 10489 } 10490 str += '+' + 10491 buildValueCalculator( 10492 fromIsArray ? from[iMinusOne] : from, 10493 toIsArray ? to[iMinusOne] : to, 10494 maxIsArray ? max[iMinusOne] : max, 10495 minIsArray ? min[iMinusOne] : min, 10496 roundIsArray ? round[iMinusOne] : round 10497 ) + 10498 '+"' + templatePart.replace(/"/g, '\\"') + '"'; 10499 } 10500 } 10501 return str; 10502 } 10503 10504 /** 10505 @private 10506 @description Builds the function for an animation object's frame listener 10507 This function animatate object properties as instructed by #prop 10508 */ 10509 function buildFunction(anim, targetIndex, propName, conf) { 10510 var targets = anim._targets, 10511 // this is going to be our listener for the frame event 10512 functionStr = anim._funcStr, 10513 func; 10514 10515 functionStr += 'var target=targets[' + targetIndex + '];' + 10516 'target["' + propName.replace(/"/g, '\\"') + '"]=' + 10517 compileTemplate(conf.template, conf.from, conf.to, conf.max, conf.min, conf.round) + 10518 ';'; 10519 10520 // retain new function string 10521 anim._funcStr = functionStr; 10522 10523 // eval to create a single function to be called 10524 func = anim._evalFunc(functionStr, targets); 10525 10526 // remove old listener & add new one 10527 anim.detach('frame', anim._propFunc).on('frame', func); 10528 // retain new func so we can remove it later 10529 anim._propFunc = func; 10530 func = functionStr = undefined; 10531 } 10532 10533 /** 10534 @private 10535 @description Determines the value(s) to animate from 10536 */ 10537 function getFromVals(propValue, conf) { 10538 var results, 10539 template = conf.template, 10540 templateRegexStr; 10541 10542 // this is easy if from values are already specified 10543 // or there isn't a template to follow 10544 if (conf.from !== undefined || !template) { 10545 return conf.from || propValue; 10546 } 10547 10548 // turn the template into a regular expression, turning the ? into regex for detecting numbers 10549 templateRegexStr = glow.util.escapeRegex(template).replace(/([^\\]|^)\\\?/g, '$1(\\-?(?:\\d+)?(?:\\.\\d+)?)'); 10550 results = new RegExp(templateRegexStr).exec(propValue); 10551 if (!results) { 10552 throw new Error('glow.anim.Anim#prop: Could not detect start values using template: ' + template); 10553 } 10554 else { 10555 return Array.prototype.slice.call(results, 1); 10556 } 10557 } 10558 10559 /** 10560 @name glow.anim.Anim#prop 10561 @function 10562 @description Animate a property of an object. 10563 This shortcut adds a listener onto the animation's 'frame' event 10564 and changes a specific property from one value to another. 10565 10566 Values can be simple, such as '42', or more complex, such as 'rgba(255, 255, 0, 0.8)' 10567 10568 Before calling this, set the target object via {@link glow.anim.Anim#target}. 10569 10570 @param {string} propertyName Name of the property to animate. 10571 @param {Object} conf Animation configuration object. 10572 All configuration properties are optional with the exception of 10573 'to', and 'from' in some cases (conditions below). 10574 10575 @param {string} [conf.template] Template for complex values 10576 Templates can be used for values which are strings rather than numbers. 10577 10578 Question-marks are used within templates as placeholders for animated 10579 values. For instance, in the template '?em' the question-mark would be 10580 replaced with a number resulting in animated values like '1.5em'. 10581 10582 Multiple Question-marks can be used for properties with more than 10583 one animated value, eg 'rgba(?, ?, ?, ?)'. The values will be animated 10584 independently. 10585 10586 A literal question-mark can be placed in a template by preceeding it 10587 with a backslash. 10588 10589 @param {number|number[]} [conf.from] Value(s) to animate from. 10590 This can be a single number, or an array of numbers; one for each 10591 question-mark in the template. 10592 10593 If omitted, the from value(s) will be taken from the object. This 10594 will fail if the current value is undefined or is in a format 10595 different to the template. 10596 10597 @param {number|number[]} conf.to Value(s) to animate to. 10598 This can be a single number, or an array of numbers; one for each 10599 question-mark in the template. 10600 10601 @param {boolean|boolean[]} [conf.round=false] Round values to the nearest whole number. 10602 Use this to prevent the property being set to a fractional value. 10603 10604 This can be a single boolean, or an array of booleans; one for each 10605 question-mark in the template. This is useful for templates like 'rgba(?, ?, ?, ?)', 10606 where the rgb values need to be whole numbers, but the alpha value is 10607 between 0-1. 10608 10609 @param {number|number[]} [conf.min] Minimum value(s) 10610 Use this to stop values animating beneath certain values. 10611 10612 Eg, some tweens go beyond their end position, but heights cannot 10613 be negative. 10614 10615 This can be a single number, or an array of numbers; one for each 10616 question-mark in the template. 'undefined' means no restriction. 10617 10618 @param {number|number[]} [conf.max] Maximum value(s) 10619 Use this to stop values animating beyond certain values. 10620 10621 Eg, some tweens go beyond their end position, but colour values cannot 10622 be greater than 255. 10623 10624 This can be a single number, or an array of numbers; one for each 10625 question-mark in the template. 'undefined' means no restriction. 10626 10627 @returns this 10628 10629 @example 10630 // Using glow.anim.Anim to animate an SVG blur over 5 seconds, with an easeOut tween 10631 new glow.anim.Anim(5, { 10632 tween: 'easeOut' 10633 }).target(feGaussianBlurElm).prop('stdDeviation', { 10634 from: 0, 10635 to: 8 10636 }).start(); 10637 10638 @example 10639 // Animate a CSS property we don't support in glow.NodeList#anim 10640 // This rotates a Mozilla CSS gradient 10641 var styleObject = glow('#nav').prop('style'); 10642 10643 new glow.anim.Anim(10).target(styleObject).prop('background', { 10644 // the question-mark in the template is replaced with the animate value 10645 template: '-moz-linear-gradient(?deg, red, blue)' 10646 from: 0, 10647 to: 360 10648 }).start(); 10649 10650 @example 10651 // Animate a CSS property we don't support in glow.NodeList#anim 10652 // This changes the colour of a webkit drop shadow from yellow to blue 10653 var styleObject = glow('#nav').prop('style'); 10654 10655 new glow.anim.Anim(3).target(styleObject).prop('WebkitBoxShadow', { 10656 // the ? in the template are replaced with the animate values 10657 template: 'rgb(?, ?, ?) 0px 4px 14px' 10658 // provide a 'from' and 'to' value for each question-mark 10659 from: [255, 255, 0], 10660 to: [0, 0, 255], 10661 // round the value, colours can't be fractional 10662 round: true 10663 }).start(); 10664 */ 10665 AnimProto.prop = function(propName, conf) { 10666 /*!debug*/ 10667 if (arguments.length !== 2) { 10668 glow.debug.warn('[wrong count] glow.anim.Anim#prop expects 2 arguments, not ' + arguments.length + '.'); 10669 } 10670 if (typeof propName !== 'string') { 10671 glow.debug.warn('[wrong type] glow.anim.Anim#prop expects string as "propName" argument, not ' + typeof propName + '.'); 10672 } 10673 if (typeof conf !== 'object') { 10674 glow.debug.warn('[wrong type] glow.anim.Anim#prop expects object as "conf" argument, not ' + typeof conf + '.'); 10675 } 10676 if (conf.to === undefined || (!conf.to.push && typeof conf.to !== 'number') ) { 10677 glow.debug.warn('[wrong type] glow.anim.Anim#prop expects number/array as "conf.to" argument, not ' + typeof conf.to + '.'); 10678 } 10679 if (conf.from !== undefined && (!conf.from.push && typeof conf.from !== 'number') ) { 10680 glow.debug.warn('[wrong type] glow.anim.Anim#prop expects number/array as "conf.from" argument, not ' + typeof conf.from + '.'); 10681 } 10682 if (conf.template !== undefined && typeof conf.template !== 'string') { 10683 glow.debug.warn('[wrong type] glow.anim.Anim#prop expects string as "conf.template" argument, not ' + typeof conf.template + '.'); 10684 } 10685 if (this._targets.length === 0) { 10686 glow.debug.warn('[unmet prerequisite] glow.anim.Anim#target must be called before glow.anim.Anim#prop'); 10687 } 10688 /*gubed!*/ 10689 10690 var targetIndex = this._targets.length - 1, 10691 target = this._targets[targetIndex]; 10692 10693 // default conf 10694 conf = glow.util.apply({ 10695 from: getFromVals(target[propName], conf), 10696 round: false 10697 }, conf); 10698 10699 buildFunction(this, targetIndex, propName, conf); 10700 10701 return this; 10702 }; 10703 }); 10704 Glow.provide(function(glow) { 10705 var undefined, 10706 AnimProto = glow.anim.Anim.prototype; 10707 10708 /** 10709 @private 10710 @description Mirrors a tween 10711 */ 10712 function mirrorTween(tween) { 10713 return function(t) { 10714 return tween(1 - t); 10715 } 10716 } 10717 10718 /** 10719 @name glow.anim.Anim#_preReverseTween 10720 @private 10721 @type function 10722 @description This is the tween before it was reversed 10723 This means that anim.reverse().reverse() doesn't 10724 wrap the tween function twice, it stores it here 10725 so it can reinstate it. 10726 */ 10727 10728 /** 10729 @name glow.anim.Anim#reversed 10730 @private 10731 @type boolean 10732 @description Is the animation in a reversed state? 10733 This starts off as false, and is true if {@link glow.anim.Anim#reverse reverse} 10734 is called. If reverse is called again, this is false. 10735 10736 This is useful in 'complete' listeners to determine where the animation 10737 ended. 10738 */ 10739 AnimProto.reversed = false; 10740 10741 /** 10742 @name glow.anim.Anim#reverse 10743 @function 10744 @description Reverses this animation 10745 Adjusts the tween of this animation so it plays in reverse. If 10746 the animation is currently playing, it will continue to play. 10747 10748 The current position of the animation is also reversed, so if a 10749 3 second animation is currently 2 seconds in, it will be one 10750 second in when reversed. 10751 10752 This is handy for animations that do something on (for example) 10753 mouseenter, then need to animate back on mouseleave 10754 10755 @returns this 10756 10757 @example 10758 // change a nav item's background colour from white to yellow 10759 // when the mouse is over it, and back again when the mouse 10760 // exits. 10761 // 10762 // If the mouse leaves the item before the animation 10763 // completes, it animates back from whatever position it 10764 // ended on. 10765 glow('#nav').delegate('mouseenter', 'li', function() { 10766 var fadeAnim = glow(this).data('fadeAnim'); 10767 10768 if (fadeAnim) { 10769 // we've already created the animation, just reverse it and go! 10770 fadeAnim.reverse().start(); 10771 } 10772 else { 10773 // create our animation, this will only happen once per element 10774 glow(this).data('fadeAnim', 10775 glow(this).anim(0.5, { 10776 'background-color': 'yellow' 10777 }, { 10778 // don't destroy, we want to reuse this animation 10779 destroyOnComplete: false 10780 }); 10781 ); 10782 } 10783 10784 }).delegate('mouseleave', 'li', function() { 10785 // Get our animation, reverse it and go! 10786 glow(this).data('fadeAnim').reverse().start(); 10787 }); 10788 */ 10789 AnimProto.reverse = function() { 10790 /*!debug*/ 10791 if (arguments.length !== 0) { 10792 glow.debug.warn('[wrong count] glow.anim.Anim#reverse expects 0 arguments, not ' + arguments.length + '.'); 10793 } 10794 /*gubed!*/ 10795 var newPosition = this.position && (1 - this.position / this.duration) * this.duration, 10796 oldTween = this.tween; 10797 10798 // set reversed property 10799 this.reversed = !this.reversed; 10800 10801 // reverse the tween 10802 this.tween = this._preReverseTween || mirrorTween(this.tween); 10803 this._preReverseTween = oldTween; 10804 return this.goTo(newPosition); 10805 } 10806 10807 /** 10808 @name glow.anim.Anim#pingPong 10809 @function 10810 @description Alters the animation so it plays forward, then in reverse 10811 The duration of the animation is doubled. 10812 10813 @returns this 10814 10815 @example 10816 // Fades #myDiv to red then back to its original colour 10817 // The whole animation takes 2 seconds 10818 glow('#myDiv').anim(1, { 10819 'background-color': 'red' 10820 }).pingPong(); 10821 */ 10822 AnimProto.pingPong = function() { 10823 /*!debug*/ 10824 if (arguments.length !== 0) { 10825 glow.debug.warn('[wrong count] glow.anim.Anim#pingPong expects 0 arguments, not ' + arguments.length + '.'); 10826 } 10827 /*gubed!*/ 10828 var oldTween = this.tween, 10829 oldTweenReversed = mirrorTween(oldTween); 10830 // double the length of the animation 10831 this.duration *= 2; 10832 this.tween = function(t) { 10833 return (t < 0.5) ? oldTween(t * 2) : oldTweenReversed( (t - 0.5) * 2 ); 10834 } 10835 // invalidate the stored reversed tween 10836 this._preReverseTween = undefined; 10837 this.reversed = false; 10838 10839 return this.goTo(this.position / 2); 10840 } 10841 }); 10842 Glow.provide(function(glow) { 10843 var undefined, 10844 TimelineProto, 10845 Anim = glow.anim.Anim; 10846 10847 /** 10848 @private 10849 @description Listener for the start event on the sync anim the timeline uses 10850 'this' is the Timeline 10851 */ 10852 function animStart(e) { 10853 this.fire('start', e); 10854 this.playing = !e.defaultPrevented(); 10855 } 10856 10857 /** 10858 @private 10859 @description Listener for the stop event on the sync anim the timeline uses 10860 'this' is the Timeline 10861 */ 10862 function animStop(e) { 10863 this.fire('stop', e); 10864 this.playing = e.defaultPrevented(); 10865 } 10866 10867 /** 10868 @private 10869 @description Listener for the frame event on the sync anim the timeline uses 10870 'this' is the Timeline 10871 */ 10872 function animFrame(e) { 10873 this.goTo(this._anim.position); 10874 // if we're still playing, fire frame 10875 if (this._anim.playing) { 10876 this.fire('frame', e); 10877 } 10878 } 10879 10880 /** 10881 @private 10882 @description Listener for the complete event on the sync anim the timeline uses 10883 'this' is the Timeline 10884 */ 10885 function animComplete(e) { 10886 // mirror .loop 10887 this._anim.loop = this.loop; 10888 // fire complete with same event object so it can be cancelled by user 10889 this.fire('complete', e); 10890 // find out if we're going to loop, set .playing 10891 var loop = this.playing = ( this.loop || e.defaultPrevented() ); 10892 // if we're not looping, destroy 10893 if (!loop && this.destroyOnComplete) { 10894 this.destroy(); 10895 } 10896 } 10897 10898 /** 10899 @name glow.anim.Timeline 10900 @extends glow.events.Target 10901 @class 10902 @description Sequence and synchronise multiple animations 10903 This can be used to easily chain animations together 10904 and ensure that multiple animations stay in sync 10905 with each other. 10906 10907 @param {Object} [opts] Options object. 10908 10909 @param {boolean} [opts.loop=true] Loop the animation. 10910 Looped timelines will fire a 'complete' event on each loop. 10911 10912 @param {boolean} [opts.destroyOnComplete=true] Destroy animations in the timeline once it completes (unless it loops). 10913 This will free any DOM references the animations may have created. Once 10914 the animations are destroyed, the timeline cannot be started again. 10915 10916 @example 10917 // play 3 animations one after another 10918 new glow.anim.Timeline().track(anim1, anim2, anim3).start(); 10919 10920 @example 10921 // play 2 animations at the same time 10922 new glow.anim.Timeline() 10923 .track(anim1) 10924 .track(anim2) 10925 .start(); 10926 10927 @example 10928 // play 2 animations with a second pause in between 10929 new glow.anim.Timeline().track(anim1, 1, anim2).start(); 10930 10931 @example 10932 // Make a 'mexican wave' 10933 // #waveContainer contains 100 divs absolutely positioned next to each other 10934 10935 var animTimeline = new glow.anim.Timeline({ 10936 loop: true 10937 }); 10938 10939 //create a wave up & wave down anim for each div 10940 var wavingDivs = glow("#waveContainer div").each(function(i) { 10941 var div = glow(this); 10942 10943 animTimeline.track( 10944 // add a pause to the start of the anim, this creates the wave effect 10945 (i / 100), 10946 // move up & down 10947 div.anim(1, { 10948 top: [70, 0] 10949 }).pingPong() 10950 ); 10951 }); 10952 10953 animTimeline.start(); 10954 */ 10955 function Timeline(opts) { 10956 /*!debug*/ 10957 if (arguments.length > 1) { 10958 glow.debug.warn('[wrong count] glow.anim.Timeline expects 0 or 1 arguments, not ' + arguments.length + '.'); 10959 } 10960 if (opts !== undefined && typeof opts !== 'object') { 10961 glow.debug.warn('[wrong type] glow.anim.Iimeline expects object as "opts" argument, not ' + typeof opts + '.'); 10962 } 10963 /*gubed!*/ 10964 10965 opts = opts || {}; 10966 this.destroyOnComplete = (opts.destroyOnComplete !== false); 10967 this.loop = !!opts.loop; 10968 this._tracks = []; 10969 this._currentIndexes = []; 10970 this._startPos = []; 10971 10972 // create an animation to sync the timeline 10973 this._anim = new Anim(0, { 10974 destroyOnComplete: false, 10975 tween: 'linear' 10976 }) 10977 .on('start', animStart, this) 10978 .on('stop', animStop, this) 10979 .on('frame', animFrame, this) 10980 .on('complete', animComplete, this); 10981 } 10982 glow.util.extend(Timeline, glow.events.Target); 10983 TimelineProto = Timeline.prototype; 10984 10985 /** 10986 @name glow.anim.Timeline#duration 10987 @type number 10988 @description Length of the animation in seconds 10989 10990 // implementation note: (delete this later) 10991 This will need to be generated after each call to #track 10992 Won't be too expensive, just work out the length of the new 10993 track and Math.max(newTrack, this.duration) 10994 */ 10995 TimelineProto.duration = 0; 10996 10997 /** 10998 @name glow.anim.Timeline#position 10999 @type number 11000 @description Position of the animation in seconds 11001 */ 11002 TimelineProto.position = 0; 11003 11004 /** 11005 @name glow.anim.Timeline#playing 11006 @description true if the animation is playing. 11007 @returns {boolean} 11008 */ 11009 TimelineProto.playing = false; 11010 11011 /** 11012 @name glow.anim.Timeline#loop 11013 @description Loop the animation? 11014 This value can be changed while the animation is playing. 11015 11016 Looped animations will fire a 'complete' event on each loop. 11017 11018 @returns {boolean} 11019 */ 11020 11021 /** 11022 @name glow.anim.Timeline#destroyOnComplete 11023 @type boolean 11024 @description Destroy the animation once it completes (unless it loops)? 11025 This will free any DOM references the animation may have created. Once 11026 the animation is destroyed, it cannot be started again. 11027 */ 11028 11029 /** 11030 @name glow.anim.Timeline#_tracks 11031 @private 11032 @type Array[] 11033 @description An array of arrays. 11034 Each array represents a track, containing a combination of 11035 animations and functions 11036 */ 11037 11038 /** 11039 @name glow.anim.Timeline#_currentIndexes 11040 @private 11041 @type number[] 11042 @description Array of the current indexes within _tracks 11043 The indexes refer to which items that were last sent a .goTo90 11044 */ 11045 11046 /** 11047 @name glow.anim.Timeline#_startPos 11048 @private 11049 @type Array[] 11050 @description Mirrors _tracks 11051 Contains the start positions of the items in _tracks 11052 */ 11053 11054 /** 11055 @name glow.anim.Timeline#_anim 11056 @private 11057 @type glow.anim.Anim 11058 @description The single animation used to fire frames for this animation 11059 */ 11060 11061 /** 11062 @name glow.anim.Timeline#_lastPos 11063 @private 11064 @type number 11065 @description Last position rendered 11066 */ 11067 TimelineProto._lastPos = 0; 11068 11069 /** 11070 @name glow.anim.Timeline#start 11071 @function 11072 @description Starts playing the animation 11073 11074 @param {number} [start] Position to start the animation at, in seconds. 11075 By default, this will be the last position of the animation (if it was stopped) 11076 or 0. 11077 11078 @returns this 11079 */ 11080 TimelineProto.start = function() { 11081 this._anim.start(); 11082 return this; 11083 }; 11084 11085 /** 11086 @name glow.anim.Timeline#stop 11087 @function 11088 @description Stops the animation playing. 11089 Stopped animations can be resumed by calling {@link glow.anim.Timeline#start start}. 11090 @returns this 11091 */ 11092 TimelineProto.stop = function() { 11093 /*!debug*/ 11094 if (arguments.length !== 0) { 11095 glow.debug.warn('[wrong count] glow.anim.Timeline#stop expects 0 arguments, not ' + arguments.length + '.'); 11096 } 11097 /*gubed!*/ 11098 11099 var i = this._tracks.length, 11100 item; 11101 11102 this._anim.stop(); 11103 // check in case the event has been cancelled 11104 if (!this._anim.playing) { 11105 while (i--) { 11106 // get the current playing item for this track 11107 item = this._tracks[i][ this._currentIndexes[i] ]; 11108 // check there is an item playing 11109 if (item) { 11110 item.fire('stop'); 11111 item.playing = false; 11112 } 11113 } 11114 } 11115 return this; 11116 }; 11117 11118 /** 11119 @name glow.anim.Timeline#destroy 11120 @function 11121 @description Destroys all animations in the timeline & detaches references to DOM nodes 11122 This frees memory & is called automatically when the animation completes 11123 @returns undefined 11124 */ 11125 TimelineProto.destroy = function() { 11126 /*!debug*/ 11127 if (arguments.length !== 0) { 11128 glow.debug.warn('[wrong count] glow.anim.Timeline#destroy expects 0 arguments, not ' + arguments.length + '.'); 11129 } 11130 /*gubed!*/ 11131 11132 var i = this._tracks.length, 11133 j, 11134 item; 11135 11136 // destroy animations in tracks 11137 while (i--) { 11138 j = this._tracks[i].length; 11139 while (j--) { 11140 item = this._tracks[i][j]; 11141 item.destroy && item.destroy(); 11142 } 11143 } 11144 11145 // destroy syncing animation 11146 this._anim.destroy(); 11147 // remove listeners 11148 glow.events.removeAllListeners( [this] ); 11149 this._tracks = undefined; 11150 11151 }; 11152 11153 /** 11154 @private 11155 @function 11156 @description Moves a timeline forward onto timeline.position 11157 This deals with moving all the tracks forward from their 11158 current position to the new position. This is done on 11159 every frame, via timeline.goTo 11160 */ 11161 function moveForward(timeline) { 11162 var i = timeline._tracks.length, 11163 track, 11164 item, 11165 itemIndex, 11166 itemStart, 11167 timelinePosition = timeline.position; 11168 11169 while (i--) { 11170 track = timeline._tracks[i]; 11171 itemIndex = timeline._currentIndexes[i]; 11172 11173 while ( item = track[itemIndex] ) { 11174 itemStart = timeline._startPos[i][itemIndex]; 11175 // deal with functions in the timeline 11176 if (typeof item === 'function') { 11177 item(); 11178 itemIndex++; 11179 break; 11180 } 11181 // deal with animations in the timeline 11182 else if (timelinePosition - itemStart >= item.duration) { 11183 // the animation we're currently playing has come to 11184 // an end, play the last frame and move on to the next 11185 item.goTo(item.duration).fire('complete'); 11186 item.playing = false; 11187 } 11188 else { 11189 // the animation we're playing is somewhere in the middle 11190 if (!item.playing) { 11191 // ohh, we're just starting this animation 11192 item.fire('start'); 11193 item.playing = true; 11194 } 11195 item.goTo(timelinePosition - itemStart); 11196 // we're not done with this item, break 11197 break; 11198 } 11199 itemIndex++; 11200 } 11201 timeline._currentIndexes[i] = itemIndex; 11202 } 11203 } 11204 11205 /** 11206 @private 11207 @function 11208 @description 11209 This goes through all animations that start after the new position 11210 & before the previous position and calls their first frames. 11211 */ 11212 function moveBackward(timeline) { 11213 var i = timeline._tracks.length, 11214 j, 11215 track, 11216 item, 11217 itemStart, 11218 timelinePosition = timeline.position; 11219 11220 while (i--) { 11221 track = timeline._tracks[i]; 11222 j = timeline._currentIndexes[i] + 1; 11223 11224 while (j--) { 11225 item = track[j]; 11226 11227 if (!item) { 11228 continue; 11229 } 11230 // we don't need to reset items before the new position, 11231 // their frames are rendered by 'moveForward' 11232 if ( timeline._startPos[i][j] < timeline.position ) { 11233 break; 11234 } 11235 // we only want to deal with animations 11236 if (typeof item !== 'function') { 11237 item.goTo(0); 11238 } 11239 } 11240 11241 timeline._currentIndexes[i] = j; 11242 } 11243 11244 // as a shortcut, we use 'moveForward' to trigger the frame for the new position 11245 // on the current items 11246 moveForward(timeline); 11247 } 11248 11249 /** 11250 @name glow.anim.Timeline#goTo 11251 @function 11252 @description Goes to a specific point in the animation. 11253 @param {number} position Position in the animation to go to, in seconds 11254 11255 @example 11256 // move the animation to 2.5 seconds in 11257 // If the animation is playing, it will continue to play from the new position. 11258 // Otherwise, it will simply move to that position. 11259 myTimeline.goTo(2.5); 11260 11261 @returns {glow.anim.Timeline} 11262 */ 11263 TimelineProto.goTo = function(position) { 11264 /*!debug*/ 11265 if (arguments.length !== 1) { 11266 glow.debug.warn('[wrong count] glow.anim.Timeline#goTo expects 1 argument, not ' + arguments.length + '.'); 11267 } 11268 if (typeof position !== 'number') { 11269 glow.debug.warn('[wrong type] glow.anim.Timeline#goTo expects number as "position" argument, not ' + typeof position + '.'); 11270 } 11271 /*gubed!*/ 11272 11273 var resetAll; 11274 if (position > this.duration) { 11275 position = this.duration; 11276 } 11277 else if (position < 0) { 11278 position = 0; 11279 } 11280 11281 this.position = position; 11282 11283 (position < this._lastPos) ? moveBackward(this) : moveForward(this); 11284 11285 this._lastPos = position; 11286 return this; 11287 }; 11288 11289 /** 11290 @private 11291 @description This method is applied to animations / timeline when they're adopted 11292 */ 11293 function methodNotAllowed() { 11294 throw new Error('Cannot call this method on items contained in a timeline'); 11295 } 11296 11297 /** 11298 @private 11299 @description Overwrite methods on animations / timelines that no longer apply 11300 */ 11301 function adoptAnim(anim) { 11302 anim.stop(); 11303 anim.start = anim.stop = anim.reverse = anim.pingPong = methodNotAllowed; 11304 } 11305 11306 /** 11307 @name glow.anim.Timeline#track 11308 @function 11309 @description Add a track of animations to the timeline 11310 Animations in a track will run one after another. 11311 11312 Each track runs at the same time, always staying in sync. 11313 11314 @param {number|function|glow.anim.Anim|glow.anim.Timeline} item+ Item to add to the timelines 11315 Animation timelines can be placed within animation timelines 11316 11317 Numbers will be treated as number of seconds to pause before the next item. 11318 11319 Functions will be called. If the function takes 0.5 seconds to call, the next 11320 animation will start 0.5 seconds in, keeping everything in sync. 11321 11322 @returns this 11323 */ 11324 TimelineProto.track = function() { 11325 /*!debug*/ 11326 if (arguments.length < 1) { 11327 glow.debug.warn('[wrong count] glow.anim.Timeline#track expects at least 1 argument, not ' + arguments.length + '.'); 11328 } 11329 /*gubed!*/ 11330 11331 var args = arguments, 11332 tracksLen = this._tracks.length, 11333 track = this._tracks[tracksLen] = [], 11334 trackDuration = 0, 11335 trackDurations = this._startPos[tracksLen] = [], 11336 trackItem; 11337 11338 // loop through the added tracks 11339 for (var i = 0, leni = args.length; i < leni; i++) { 11340 trackItem = track[i] = args[i]; 11341 11342 if (trackItem instanceof Anim || trackItem instanceof Timeline) { 11343 adoptAnim(trackItem); 11344 } 11345 // convert numbers into empty animations 11346 else if (typeof trackItem === 'number') { 11347 trackItem = track[i] = new Anim(trackItem); 11348 } 11349 /*!debug*/ 11350 else if (typeof trackItem !== 'function') { 11351 glow.debug.warn('[wrong type] glow.anim.Timeline#track all arguments must be number/glow.anim.Anim/glow.anim.Timeline/function, arg ' + i + ' is ' + typeof trackItem + '.'); 11352 } 11353 /*gubed!*/ 11354 11355 // record the start time for this anim 11356 trackDurations[i] = trackDuration; 11357 trackDuration += trackItem.duration || 0; 11358 } 11359 11360 // update duration and anim duration 11361 this._anim.duration = this.duration = Math.max(this.duration, trackDuration); 11362 this._currentIndexes[tracksLen] = 0; 11363 11364 return this; 11365 }; 11366 11367 /** 11368 @name glow.anim.Timeline#event:start 11369 @event 11370 @description Fires when an animation starts. 11371 Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault}) 11372 prevents this animation from starting. 11373 11374 @param {glow.events.Event} event Event Object 11375 */ 11376 11377 /** 11378 @name glow.anim.Timeline#event:frame 11379 @event 11380 @description Fires on each frame of the animation 11381 11382 @param {glow.events.Event} event Event Object 11383 */ 11384 11385 /** 11386 @name glow.anim.Timeline#event:stop 11387 @event 11388 @description Fires when an animation is stopped before completing 11389 Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault}) 11390 prevents this animation from stopping. 11391 11392 @param {glow.events.Event} event Event Object 11393 */ 11394 11395 /** 11396 @name glow.anim.Timeline#event:complete 11397 @event 11398 @description Fires when an animation completes 11399 Preventing this event (by returning false or calling {@link glow.events.Event#preventDefault preventDefault}) 11400 causes the animation to loop. 11401 11402 @param {glow.events.Event} event Event Object 11403 11404 @example 11405 // Make an animation loop 5 times 11406 var loopCount = 5; 11407 myTimeline.on('complete', function() { 11408 return !!loopCount--; 11409 }); 11410 */ 11411 11412 // export 11413 glow.anim.Timeline = Timeline; 11414 }); 11415 Glow.complete('core', '2.0.0b1'); 11416