1 (function(window, global) { 2 /** @exports self as puredom */ 3 4 // node: 5 if (typeof process==='object' && process.argv && process.argv[0]==='node') { 6 window = require('jsdom').jsdom().parentWindow; 7 } 8 9 var document = window.document, 10 navigator = window.navigator; 11 12 var previousSelf = window.puredom; 13 14 if (typeof Date.now!=='function') { 15 /** @ignore */ 16 Date.now = function() { 17 return new Date().getTime(); 18 }; 19 } 20 21 /** When called as a function, acts as an alias of {@link puredom.el}.<br /> 22 * If a <code>Function</code> is passed, it is registered as a DOMReady handler. <br /> 23 * Otherwise, all arguments are passed on to {@link puredom.el}. 24 * @version 1.9.1 25 * @namespace Core functionality 26 * @function 27 * @param {Function|Any} arg If a <code>Function</code> is passed, it is registered as a DOMReady handler. Otherwise, all arguments are passed on to {@link puredom.el} 28 * @name puredom 29 * @public 30 */ 31 var self = function(){ 32 return priv.puredom.apply(priv, arguments); 33 }, 34 /** @private */ 35 baseSelf = { 36 version : '1.9.1', 37 templateAttributeName : 'data-tpl-id', 38 baseAnimationInterval : 20, 39 allowCssTransitions : true, 40 easingMethods : { 41 ease : function(f) { 42 return (Math.sin(f*Math.PI - Math.PI/2) + 1) / 2; 43 }, 44 'ease-in-out' : function(f) { 45 return this.ease(f); 46 } 47 } 48 }, 49 initialized = false, 50 vendorCssPrefix, 51 vendorCssPrefixJS, 52 objConstructor = baseSelf.constructor, 53 textContentProperty, 54 getSupportedTextContentProperty, 55 56 /** @private */ 57 priv = { 58 oninit : [], 59 animationTimes : { 60 fast : 150, 61 medium : 450, 62 slow : 1000 63 }, 64 animationTimeScale : 1, 65 registeredEventCount : 0, 66 html5elements : 'abbr article aside audio canvas datalist details figcaption figure footer header hgroup mark meter nav output progress section summary time video'.split(' '), 67 support : { 68 html5 : true, 69 filters : false, 70 querySelectorAll : 'querySelectorAll' in document, 71 webkitMultitouch : 'ontouchstart' in window && navigator.maxTouchPoints!==0 && navigator.msMaxTouchPoints!==0 72 }, 73 regex : { 74 css3AutoPrefix : /([\s\;\/\*])(transform|transition|perspective|box\-sizing|box\-shadow|border\-radius)\:([^\;]*)(\;|$)/gim, // |text\-shadow 75 css3VendorPrefix : /\b\-(moz|webkit|ms|o|vendor)\-/gim, 76 templateFieldToken : /([^\\]?)\{([a-z0-9A-Z\$_\.]+)(\|[^\}]*?)?\}/gm, 77 parseObjectNameFromString : /^\[object ([^\s]+)\]$/gim, 78 autoDetectHTMLContent : /(<[a-z]|&[a-z#0-9]{1,10};)/gim, 79 whitespaceCharacters : /\s/, 80 getNumericCSSValue : /[^0-9\.\-]/gm, 81 getCSSValueUnits : /([a-z]+|%)$/, 82 getNonIntegerCharsSigned : /[^0-9\.\-]/gm, 83 getUpperCaseAlphaChars : /[A-Z]/gm 84 } 85 }; 86 87 /** @ignore */ 88 function noop(){} 89 90 /** @ignore */ 91 self.support = priv.support; 92 93 if (navigator.userAgent.match(/\b(webkit|applewebkit|chrome|chromium|khtml)\b/gim)) { 94 vendorCssPrefix = '-webkit'; 95 vendorCssPrefixJS = 'Webkit'; 96 } 97 else if (navigator.userAgent.match(/\bopera\b/gim)) { 98 vendorCssPrefix = '-o'; 99 vendorCssPrefixJS = 'O'; 100 } 101 else if (navigator.userAgent.match(/\bgecko\b/gim)) { 102 vendorCssPrefix = '-moz'; 103 vendorCssPrefixJS = 'Moz'; 104 } 105 else if (navigator.userAgent.match(/\bmsie\s*?(8|9|[1-9][0-9]+)\b/gim)) { 106 vendorCssPrefix = '-ms'; 107 vendorCssPrefixJS = 'Ms'; 108 } 109 else if (navigator.userAgent.match(/\bmsie\s*?[4-8]\b/gim)) { 110 priv.support.filters = true; 111 priv.support.filterProperty = 'filter'; 112 } 113 114 115 /** @ignore */ 116 (function(div, i) { 117 div = document.createElement('div'); 118 div.innerHTML = '<nav></nav>'; 119 priv.support.html5 = div.childNodes.length>0; 120 if (!priv.support.html5) { 121 priv.html5frag = document.createDocumentFragment(); 122 for (i=priv.html5elements.length; i--; ) { 123 priv.html5frag.createElement(priv.html5elements[i]); 124 } 125 priv.html5div = document.createElement('div'); 126 priv.html5frag.appendChild(priv.html5div); 127 } 128 }()); 129 130 131 /** Note: this function removes itself, and should only ever be run once. 132 * @ignore 133 */ 134 getSupportedTextContentProperty = function() { 135 var d = document.body; 136 textContentProperty = (d.textContent!==undefined && "textContent") || (d.innerText && 'innerText') || "innerHTML"; 137 getSupportedTextContentProperty = null; 138 return textContentProperty; 139 }; 140 141 142 143 /** Extend/augment a base object with the properties of one or more additional objects.<br /> 144 * <strong>Note:</strong> all additional arguments are treated as additional Objects to copy properties from. 145 * @param {Object} base The object to extend. For cloning, use an object literal. 146 * @param {Object} props An Object to copy properties from. 147 * @param {Object} [...] Additional arguments also get copied onto base. 148 * @returns {Object} base 149 * @example 150 * var clonedObj = puredom.extend({}, originalObj); 151 * puredom.extend(MyClass.prototype, prototypeAsHash); 152 */ 153 self.extend = function(base) { 154 var i, j, ext; 155 base = base || {}; 156 for (i=1; i<arguments.length; i++) { 157 ext = arguments[i]; 158 if (ext) { 159 for (j in ext) { 160 if (ext.hasOwnProperty(j)) { 161 base[j] = ext[j]; 162 } 163 } 164 // IE never reports toString as an "own property", so manually check if it was copied and fix if required: 165 if (typeof ext.toString==='function' && ext.toString!==Object.prototype.toString) { // ext.toString!==obj.toString && 166 base.toString = ext.toString; 167 } 168 } 169 } 170 return base; 171 }; 172 173 174 /** Mix functionality from one object into another. <br /> 175 * <strong>Note:</strong> all additional arguments are treated as additional Objects to copy properties from. <br /> 176 * <strong>Alternative Signature:</strong> <code>mixin(true, [props, ...], base)</code> 177 * @param {Object} base The object to extend. For cloning, use an object literal. 178 * @param {Object} props An Object to copy properties from, unless base already has a property of the same name. 179 * @returns {Object} base 180 * @example 181 * // standard: 182 * puredom.mixin(myObj, decorator1, decorator2); 183 * 184 * // alternative, decorator-first style: 185 * puredom.mixin(true, decorator1, decorator2, myObj); 186 */ 187 self.mixin = function(base) { 188 var i, j, ext, 189 mix = Array.prototype.slice.call(arguments, 1); 190 if (base===true) { 191 base = mix.pop(); 192 } 193 base = base || {}; 194 for (i=0; i<mix.length; i++) { 195 if ( (ext=mix[i]) ) { 196 for (j in ext) { 197 if (ext.hasOwnProperty(j) && !base.hasOwnProperty(j)) { 198 base[j] = ext[j]; 199 } 200 } 201 } 202 } 203 return base; 204 }; 205 206 207 /** Strip an object of all of its properties.<br /> 208 * <strong>Note:</strong> Sets property values to null, doesn't actually delete them. 209 * @param {Object} obj An object to strip all properties from 210 * @param {Boolean} [andProto=false] If <code>true</code>, nullifies entire prototype chain. 211 */ 212 self.strip = function(obj, andProto) { 213 for (var i in obj) { 214 if (andProto===true || obj.hasOwnProperty(i)) { 215 obj[i] = null; // faster than delete. 216 } 217 } 218 }; 219 220 221 /** Get a value from within a nested object. "Deep keys" use dot notation. 222 * @param {Object} obj The object to delve into. 223 * @param {String} path A dot-notated key to find within <code>obj</code> 224 * @param {Boolean} [discardFirst=false] If <code>true</code>, the first segment of <code>path</code> will be discarded. 225 * @param {Boolean} [allowIncompleteMatch=false] If <code>true</code>, returns the deepest reachable value, even if it is not a full path. 226 */ 227 self.delve = function(obj, path, discardFirst, allowIncompleteMatch) { 228 var i = 0; 229 if (path==='.' || (path==='this' && !obj.hasOwnProperty('this'))) { 230 return obj; 231 } 232 path = path.split('.'); 233 if (discardFirst===true) { 234 path.splice(0, 1); 235 } 236 while (i<path.length && obj && obj.hasOwnProperty(path[i])) { 237 obj = obj[path[i]]; 238 i += 1; 239 } 240 if (i>=path.length || (allowIncompleteMatch===true && i>0)) { 241 return obj; 242 } 243 }; 244 245 246 /** Flatten a nested Object using underscore-delimited keys. (<code>foo_bar_baz</code>) 247 * @param {Object} obj The nested/deep object to flatten 248 * @returns {Object} flat 249 */ 250 self.flattenObj = function(obj, prefix, depth, flat) { 251 var i, p; 252 prefix = prefix || ''; 253 depth = depth || 0; 254 flat = flat || {}; 255 for (i in obj) { 256 if (obj.hasOwnProperty(i)) { 257 p = prefix ? (prefix+'_'+i) : i; 258 if (self.isScalar(obj[i])) { 259 flat[p] = obj[i]; 260 } 261 else { 262 self.flattenObj(obj[i], p, depth+1, flat); 263 } 264 } 265 } 266 if (!depth) { 267 return flat; 268 } 269 }; 270 271 272 /** Inject arbitrarily nested template fields into a string of text. <br /> 273 * Fields are referenced like this: {foo.bar.baz|truncate:300,byWord}<br /> 274 * <em><strong>Note:</strong> keys are CaSe-SeNsItIvE.</em> 275 * @param {String} text The text to template 276 * @param {Object} fields An object containing (nested) keys for replacement 277 * @param {Boolean} [allowI18n=true] Allow Internationalization using the engine referenced by {@link puredom.l18n}? 278 * @returns {String} The templated text 279 */ 280 self.template = function(text, fields, allowI18n) { 281 var templated, 282 i18n; 283 if (allowI18n!==false && self.i18n) { 284 i18n = self.i18n; 285 } 286 templated = (text+'').replace(priv.regex.templateFieldToken, function(str, pre, id, filters) { 287 var val; 288 if (pre!=='\\' && id) { 289 val = self.delve(fields, id); 290 if (val) { 291 if (i18n) { 292 val = i18n(val) || val; 293 } 294 if (filters && filters.substring(0,1)==='|') { 295 val = self.text.filter(val, filters.substring(1)); 296 } 297 str = pre + val; 298 } 299 else { 300 str = pre; 301 } 302 } 303 else { 304 str = pre; 305 } 306 return str; 307 }); 308 return templated; 309 }; 310 311 312 /** Simple prototypal inheritance. 313 * @param {Function} baseClass The base (child) class. 314 * @param {Function} superClass A class to inherit from. 315 * @returns {Function} baseClass, for convenience 316 * @example 317 * puredom.inherits(puredom.ControllerManager, puredom.EventEmitter); 318 */ 319 self.inherits = function(base, superClass) { 320 function F(){} 321 F.prototype = superClass.prototype; 322 var proto = base.prototype; 323 base.prototype = new F(); 324 puredom.extend(base.prototype, proto); 325 base.prototype.constructor = base; 326 base.prototype.__super = superClass; 327 }; 328 329 330 /** Get the <strong>lowercase</strong> type (constructor name) of an object.<br /> 331 * <em><strong>Important Note:</strong> Unlike many other typeOf implementations, this method returns the name of an Object's constructor, rather than just "object".</em> 332 * @param {Any} what An object to analyze 333 * @returns {String} type 334 * @example 335 * puredom.typeOf({}) === 'object' 336 * puredom.typeOf([]) === 'array' 337 * puredom.typeOf(new Audio) === 'audio' 338 */ 339 self.typeOf = function(what) { 340 if (what===undefined) { 341 return 'undefined'; 342 } 343 else if (what===null) { 344 return 'null'; 345 } 346 else if (what) { 347 if (what.constructor===objConstructor) { 348 return 'object'; 349 } 350 else if (self.isArray(what)) { 351 return 'array'; 352 } 353 } 354 //return String(typeof what).toLowerCase(); 355 return Object.prototype.toString.call(what).replace(priv.regex.parseObjectNameFromString,'$1').toLowerCase(); 356 }; 357 358 359 /** Determines if the passed object is scalar. 360 * @param {Any} what An object to analyze 361 * @returns {Boolean} isScalar 362 */ 363 self.isScalar = function(what) { 364 var type = self.typeOf(what); 365 if (type==='undefined' || type==='null' || type==='number' || type==='string' || type==='boolean') { 366 return true; 367 } 368 return false; 369 }; 370 371 372 /* Index of an element within an array */ 373 if (!Array.prototype.indexOf || ([self]).indexOf(self)!==0) { 374 try { 375 /** @ignore */ 376 Array.prototype.indexOf = function(what) { 377 for (var x=0; x<this.length; x++) { 378 if (this[x]===what) { 379 return x; 380 } 381 } 382 return -1; 383 }; 384 }catch(arrayIndexErr){} 385 } 386 387 388 /** Convert an Array-like object (having a length and numeric properties) into an Array. 389 * @param {Any} obj An Array-like object to convert 390 * @returns {Array} array The converted <code>Array</code> on success, or the original object <code>obj</code> on failure. 391 */ 392 self.toArray = function(obj) { 393 var arr = [], 394 len = obj && obj.length, 395 x, p; 396 if (len || len===0) { 397 for (x=len; x--; ) { 398 arr[x] = obj[x]; 399 } 400 } 401 else { 402 x = 0; 403 while (true) { 404 if (obj.hasOwnProperty && obj.hasOwnProperty(x)) { 405 arr.push(obj[x]); 406 } 407 else if (obj.hasOwnProperty && obj.hasOwnProperty(x+'')) { 408 arr.push(obj[x+'']); 409 } 410 else { 411 break; 412 } 413 x += 1; 414 } 415 } 416 return arr; 417 }; 418 419 420 /** Determine if the argument is an Array 421 * @function 422 * @param {Any} what An object to analyze 423 * @returns {Boolean} isArray <code>true</code> if the object is an <code>Array</code>, otherwise <code>false</code>. 424 */ 425 self.isArray = Array.isArray ? function(what) { 426 return Array.isArray(what); 427 } : function(what) { 428 return Object.prototype.toString.call(what)==="[object Array]"; 429 }; 430 431 432 /** Determine if an object has a direct property with the given name. 433 * @param {Any} obj An object to test 434 * @param {String} prop A property name to test 435 * @returns {Boolean} hasOwnProperty 436 */ 437 self.hasOwnProp = function(obj, prop) { 438 return Object.prototype.hasOwnProperty.call(obj, prop); 439 }; 440 441 442 /** Iterate over an object, calling an <code>iterator</code> function on each value. 443 * @name puredom.forEach 444 * @function 445 * @param {Object|Array} obj An object to iterate over. 446 * @param {Function} iterator A function to call for each value. Gets passed <code>(value, key)</code>. 447 * @returns obj 448 */ 449 self.forEach = function(obj, iterator) { 450 var i, r; 451 if (self.isArray(obj)) { 452 for (i=0; i<obj.length; i++) { 453 r = iterator(obj[i], i); 454 if (r===false) { 455 break; 456 } 457 } 458 } 459 else { 460 for (i in obj) { 461 if (obj.hasOwnProperty(i)) { 462 r = iterator(obj[i], i); 463 if (r===false) { 464 break; 465 } 466 } 467 } 468 } 469 return obj; 470 }; 471 472 /** @ignore */ 473 self.foreach = self.forEach; 474 475 476 /** Set the innerHTML of an element, with fixes for various browser bugs 477 * @private 478 * @param {HTMLElement} el An element whose content should be set 479 * @param {String} html The content to set 480 */ 481 self.setInnerHTML = function(el, html) { 482 var frag, i; 483 if (priv.support.html5) { 484 el.innerHTML = html || ''; 485 } 486 else { 487 el.innerHTML = ''; 488 priv.html5div.innerHTML = html || ''; 489 frag = document.createDocumentFragment(); 490 for (i=priv.html5div.childNodes.length; i--; ) { 491 frag.appendChild(priv.html5div.firstChild); 492 } 493 el.appendChild(frag); 494 } 495 }; 496 497 498 /** Create a DOM node from an Object description 499 * @private 500 * @param {Object} options An object that describes how to construct the node 501 * @param {HTMLElement} [parent] Optional parent node to inject the newly constructed element into. 502 * @returns {HTMLElement} node Returns the created HTML element. 503 */ 504 self.createElement = function(options, parent) { 505 var el, x, i, childFrag, processProp, insertedBefore; 506 if (typeof options==='string') { 507 childFrag = document.createElement('div'); 508 childFrag.innerHTML = options; 509 for (i=0; i<childFrag.childNodes.length; i++) { 510 el = childFrag.childNodes[i]; 511 if (el.nodeType===1) { 512 if (parent) { 513 parent.appendChild(el); 514 } 515 } 516 } 517 return el; 518 } 519 options = options || {}; 520 el = document.createElement(options.type || "div"); 521 parent = parent || options.parent; 522 if (options.insertBefore && options.insertBefore.constructor===self.NodeSelection) { 523 options.insertBefore = options.insertBefore._nodes[0]; 524 } 525 if (!parent && options.insertBefore) { 526 parent = options.insertBefore.parentNode; 527 } 528 for (x in options) { 529 if (self.hasOwnProp(options, x)) { 530 if ((x+"").substring(0,2).toLowerCase()==="on") { 531 self.addEvent(el, x.substring(2), options[x]); 532 } 533 else if (x==="css" || x==="cssText") { 534 if (vendorCssPrefix) { 535 options[x] = options[x].replace(priv.regex.css3AutoPrefix, '$1'+vendorCssPrefix+'-$2:$3; $2:$3;'); 536 options[x] = options[x].replace(priv.regex.css3VendorPrefix, '-'+vendorCssPrefix+'-'); 537 el.style.cssText = options[x]; 538 } 539 else { 540 el.style.cssText = options[x]; 541 } 542 } 543 else if (x==='html' || x==='innerHTML') { 544 self.setInnerHTML(el, options[x]); 545 } 546 else if (x==="attributes") { 547 for (i in options[x]) { 548 if (self.hasOwnProp(options[x], i)) { 549 el.setAttribute(i, options[x][i]); 550 } 551 } 552 } 553 else if (x!=='parent' && x!=='children' && x!=='insertBefore' && x!=='type' && x!=='children' && x!=='html' && x!=='innerHTML') { 554 if (document.all) { 555 try { 556 el[x] = options[x]; 557 }catch(err){ 558 self.log(x); 559 } 560 } 561 else { 562 el[x] = options[x]; 563 } 564 } 565 else { 566 //self.log('Skipping "'+x+'" property in create()', options[x]); 567 } 568 } 569 } 570 if (parent) { 571 if (options.insertBefore) { 572 try { 573 parent.insertBefore(el, options.insertBefore); 574 insertedBefore = true; 575 }catch(err) { 576 insertedBefore = false; 577 } 578 } 579 if (!insertedBefore) { 580 parent.appendChild(el); 581 } 582 } 583 584 if (options.children && self.isArray(options.children)) { 585 childFrag = document.createDocumentFragment(); 586 for (x=0; x<options.children.length; x++) { 587 self.createElement(options.children[x], childFrag); 588 } 589 el.appendChild(childFrag); 590 } 591 return el; 592 }; 593 594 595 /** Creates a new selection containing the elements of <code>nodes</code>. <br /> 596 * This class is not generally instantiated directly - instead, use the puredom() 597 * function to query for elements or wrap an Array of elements with a selection. 598 * @class Represents a collection of DOM Elements. <br /> 599 * Puredom methods that work with DOM elements generally return an instance of this. 600 * @name puredom.NodeSelection 601 * @param {Array} nodes An array of raw DOM nodes to wrap in a selection. 602 */ 603 self.NodeSelection = function NodeSelection(nodes) { 604 var x; 605 this._results = []; 606 this._animations = []; 607 if (nodes) { 608 if (self.isArray(nodes)) { 609 this._nodes = nodes = nodes.slice(); 610 for (x=nodes.length; x--; ) { 611 if (!nodes[x]) { 612 nodes.splice(x, 1); 613 } 614 else if (nodes[x] instanceof NodeSelection) { 615 nodes = nodes.concat(nodes.splice(x, 1)[0]._nodes); 616 } 617 } 618 } 619 else { 620 this._nodes = [nodes]; 621 } 622 } 623 else { 624 this._nodes = []; 625 } 626 }; 627 628 self.extend(self.NodeSelection.prototype, /** @lends puredom.NodeSelection# */ { 629 630 /** @private */ 631 _results : [], 632 633 /** @private */ 634 _nodes : [], 635 636 /** @private */ 637 _animations : [], 638 639 640 /** Get an Array of String representations of each element in the selection. <br /> 641 * For a more logging-friendly option, see {@link puredom.NodeSelection#describe}. 642 */ 643 describe : function() { 644 var p = []; 645 this.each(function(node) { 646 var str = '<' + node.nodeName(), 647 id = node.prop('id'), 648 className = node.prop('className'), 649 cn, i, g; 650 if (id) { 651 str += ' id="' + id + '"'; 652 } 653 if (className) { 654 str += ' class="' + className + '"'; 655 } 656 str += '>'; 657 if (node._nodes[0].childNodes.length===1 && node._nodes[0].childNodes[0].nodeType===3) { 658 str += node.text().replace(/(\r|\n)/gim,decodeURIComponent("%E2%86%A9")).replace(/\t/gim,decodeURIComponent("%E2%86%92")); 659 } 660 else { 661 str += '[' + node.children().count() + ' children]'; 662 } 663 str += '</' + node.nodeName() + '>'; 664 p.push(str); 665 }); 666 return p; 667 }, 668 669 /** Get a String representation of the selection's current contents. <br /> 670 * For the raw Array description, see {@link puredom.NodeSelection#describe}. 671 */ 672 toString : function() { 673 return this.describe().join(', '); 674 }, 675 676 /** @private */ 677 toSource : function() { 678 return this._nodes; 679 }, 680 681 /** Get the result of the previous operation. <br /> 682 * Many puredom methods return the selection they were called on rather than a standard return value. 683 * This method gets the equivalent return value of the most recent selection method call. 684 * @param {Number} [reverseIndex=0] Optionally get an older return value. This value is a 0-based offset. 685 * @returns {Any} returnValue, or <code>undefined</code> if no value was returned. 686 */ 687 getResult : function(reverseIndex) { 688 reverseIndex = Math.round(reverseIndex) || 0; 689 return this._results[this._results.length - reverseIndex - 1]; 690 }, 691 692 /** @private */ 693 pushResult : function(result) { 694 this._results.push(result); 695 return this; 696 }, 697 698 /** Call an iterator function on each element in the selection, wrapping each in a new {@link puredom.NodeSelection}.<br /> 699 * <strong>Note:</strong> Return <code>false</code> from within <code>iterator</code> to break out of the loop. 700 * @param {Function} iterator Gets passed <code>(element, index)</code> for each element in the selection. The value of <code>this</code> is the selection itself. 701 * @returns {this} 702 */ 703 each : function(action) { 704 return this._each(action, true); 705 }, 706 707 /** Call an iterator function on each <strong>raw DOM node</strong> in the selection.<br /> 708 * <strong>Note:</strong> Return <code>false</code> from within <code>iterator</code> to break out of the loop. 709 * @param {Function} iterator Gets passed <code>(node, index)</code> for each element in the selection. The value of <code>this</code> is the selection itself. 710 * @returns {this} 711 */ 712 _each : function(action, asSelection, inReverse) { 713 var nodes = this._nodes.slice(0,this._nodes.length), 714 x, y, node, ret; 715 for (x=0; x<nodes.length; x++) { 716 y = x; 717 if (inReverse===true) { 718 y = nodes.length-y-1; 719 } 720 node = nodes[y]; 721 if (asSelection===true) { 722 node = new self.NodeSelection(node); 723 } 724 ret = action.call(this, node, y); 725 if (ret===false) { 726 break; 727 } 728 } 729 return this; 730 }, 731 732 /** Call a function on the selection in the future. 733 * @param {Number} millis The number of milliseconds to wait before calling <code>callback</code>. 734 * @param {Function} callback The function to call in <code>millis</code> milliseconds. Gets called on the selection, so the value of <code>this</code> is the selection itself. 735 * @returns {this} 736 */ 737 wait : function(millis, callback) { 738 var self = this; 739 if (callback) { 740 setTimeout(function() { 741 callback.apply(self); 742 self = callback = null; 743 }, Math.abs(millis)); 744 } 745 return this; 746 }, 747 748 /** Get the <strong>lower-case</strong> nodeName of an element.<br /> 749 * <em><strong>Note:</strong> Only returns a UUID for the first element in a selection.</em> <br /> 750 * <strong>Note:</strong> In puredom, the <code>window</code> Object is given a nodeName of "#window". 751 * @returns {String} nodeName 752 */ 753 nodeName : function() { 754 var node = this._nodes[0], 755 nodeName = node && node.nodeName && node.nodeName.toLowerCase(); 756 if (node===window) { 757 return '#window'; 758 } 759 else if (nodeName) { 760 return nodeName; 761 } 762 return null; 763 }, 764 765 /** Get a globally unique identifier for an element. <br /> 766 * <em><strong>Note:</strong> Only returns a UUID for the first element in a selection.</em> 767 * @returns {String} uuid 768 */ 769 uuid : function() { 770 return this._nodes[0] && priv.nodeToId(this._nodes[0]) || null; 771 }, 772 773 /** Get or set the textual content of elements. Omit <code>text</code> to retrieve the textual value instead of setting it. 774 * @param {String} [text] If set, replaces the textual content of elements. 775 * @returns {String} text The textual contents of the first element in the selection, or the selection itself if <code>text</code> was not set. 776 */ 777 text : function(text) { 778 if (arguments.length===0) { 779 return this._nodes[0] && this._nodes[0][textContentProperty || getSupportedTextContentProperty()] || ''; 780 } 781 text = text + ""; 782 this._each(function(el) { 783 el[textContentProperty || getSupportedTextContentProperty()] = text; 784 }); 785 return this; 786 }, 787 788 /** Set the HTML contents of elements. 789 * @param {String} [content] If set, updates the content of the elements. If not set, returns the HTML content of the first element. 790 * @param {Boolean} [asText=auto] If <code>true</code>, content will be treated as textual, if <code>false</code> content will be treated as HTML. Defaults to auto-detection of HTML. 791 * @returns {String} text The HTML or textual contents of the first element in the selection, or the selection itself if <code>html</code> was not set. 792 */ 793 html : function(content, asText) { 794 if (arguments.length===0) { 795 return this._nodes[0] && this._nodes[0].innerHTML || ''; 796 } 797 content = content + ""; 798 priv.regex.autoDetectHTMLContent.lastIndex = 0; 799 if ((priv.regex.autoDetectHTMLContent).test(content) && asText!==true) { 800 //self.log("Detected HTML in .html() insertion, using innerHTML...", {content:content}); 801 //alert('setting HTML: ' + content); 802 this._each(function(el) { 803 self.setInnerHTML(el, content); 804 }); 805 } 806 else { 807 //alert('setting PLAIN: ' + content); 808 //self.log("Detected plain text in .html() insertion, using textContent or equivalent...", {content:content}); 809 this._each(function(el) { 810 el[textContentProperty || getSupportedTextContentProperty()] = content; 811 }); 812 } 813 return this; 814 }, 815 816 /** Apply CSS to elements. 817 * @param {String|Object} css CSS to apply to the elements in the selection. Either a CSS-string, or an Object where the keys are CSS properties and the values are the corresponding values to apply. 818 * @param {Object} [options] Options 819 * @param {Number|String} [options.tween] Animate the application of the given CSS styles. Numeric values are treated as durations, String values must be a comma-separated value of the format: "duration,easing-method". 820 * @param {Function} [options.callback] A function to call once the styles have been applied. If tweening/animating, gets called once the animation has completed. 821 * @param {Function} callback Same as <code>options.callback</code>, takes precidence when used. 822 * @returns {this} 823 */ 824 css : function(css, options, callback) { 825 var type, 826 selection = this; 827 options = options || {}; 828 if (typeof options==='string' || typeof options==='number') { 829 options = {tween:options}; 830 } 831 if (!callback && options.callback) { 832 callback = options.callback; 833 } 834 if (!callback || !callback.call) { 835 callback = noop; 836 } 837 type = self.typeOf(options.tween); 838 if (typeof css==='string') { 839 css = priv.parseCSS(css); 840 } 841 if ((type==='string' && options.tween!=='none') || (type==='number' && options.tween>0)) { 842 var tween = (options.tween + '').replace(priv.regex.whitespaceCharacters,'').split(','), 843 x, 844 cb, 845 total = 0, 846 completed = 0; 847 cb = function(node) { 848 completed += 1; 849 if (completed>=total) { 850 cb = null; 851 if (callback) { 852 callback.call(selection, selection); // node 853 } 854 selection = callback = node = null; 855 } 856 }; 857 for (x in css) { 858 if (css.hasOwnProperty(x)) { 859 total += 1; 860 this.animateCss(x, css[x], tween[0], tween[1], cb); 861 } 862 } 863 } 864 else { 865 this._each(function(el) { 866 self.applyCss(el, css); 867 }); 868 869 if (callback) { 870 setTimeout(function(){ 871 callback.call(selection, selection); 872 selection = callback = null; 873 }, 1); 874 } 875 else { 876 selection = null; 877 } 878 } 879 return this; 880 }, 881 882 /** Show elements. 883 * @returns {this} 884 */ 885 show : function() { 886 this.css({ 887 display : '', 888 visibility : 'visible' 889 }); 890 // ensure the new active style is not display:none 891 this._each(function(node) { 892 if (node.style.display==='none' || self.nodeStyle(node, 'display')==='none') { 893 node.style.display = 'block'; 894 } 895 }); 896 return this; 897 }, 898 899 /** Hide elements. 900 * @param {Boolean} [andIgnore=true] If <code>false</code>, triggers "visibility:hidden" CSS, instead of "display:none". 901 * @returns {this} 902 */ 903 hide : function(andIgnore) { 904 /* 905 var disp = this.getStyle('display',true); 906 if (disp && disp!=='none') { 907 this._previousDisplayStyle = disp; 908 } 909 */ 910 return this.css( 911 andIgnore===false ? {visibility:'hidden'} : {display:'none'} 912 ); 913 }, 914 915 /** This function tries quite hard to guess what particular fade effect is needed. <br /> 916 * If the element that is already semi-transparent, it fades from the current opacity. <br /> 917 * If the element that is hidden but not explicitly transparent, it fades from opacity=0 (hidden). <br /> 918 * If the element is already 100% opaque (non-transparent), no animation is performed, and the callback is fired after a very small delay (to enforce async). <br /> 919 * Arguments are interchangeable for backward compatibility. 920 * @param {Number|String} tween A tween value. Can be a <code>{Number} duration</code>, or <code>{String} "duration,easing-method"</code>. 921 * @param {Function} [callback] A function to call once the fade has completed. Gets passed the selection. 922 * @returns {this} 923 */ 924 fadeIn : function(tween, callback) { 925 var originalOpacity = parseFloat(this.getStyle('opacity') || '0') || 0, 926 targetOpacity = 1; 927 if (this.getStyle('display')==='none' || this.getStyle('visibility')==='hidden') { 928 if (this.getStyle('opacity') && originalOpacity>0 && originalOpacity<1) { 929 targetOpacity = originalOpacity; 930 } 931 originalOpacity = 0; 932 this.css({ 933 opacity : 0 934 }); 935 } 936 // arguments can be in reverse order 937 if (self.typeOf(tween)==='function') { 938 callback = tween; 939 tween = arguments[1]; 940 } 941 if (originalOpacity>=1 || tween===0 || tween===false) { 942 this.css({ 943 opacity : targetOpacity 944 }).show(); 945 if (callback && callback.call) { 946 setTimeout(callback, 0); 947 } 948 return this; 949 } 950 this.css({ 951 opacity : targetOpacity 952 }, {tween:tween || 'medium', callback:function(selection) { 953 if (callback) { 954 callback(selection); 955 } 956 tween = callback = null; 957 }}).show(); 958 return this; 959 }, 960 961 /** The opposite of fadeIn(). Makes several guesses about the desired effect. */ 962 fadeOut : function(tween, callback, andIgnore) { 963 var originalOpacity = parseFloat(this.getStyle('opacity') || '1') || 1; 964 if (self.typeOf(tween)==='function') { 965 callback = tween; 966 tween = arguments[1]; 967 } 968 if (self.typeOf(callback)==='boolean') { 969 andIgnore = callback; 970 callback = null; 971 if (self.typeOf(arguments[2])==='function') { 972 callback = arguments[2]; 973 } 974 } 975 if (originalOpacity<=0 || this.getStyle('display')==='none' || this.getStyle('visibility')==='hidden' || tween===0 || tween===false) { 976 this.css({ 977 opacity : 0 978 }).hide(andIgnore); 979 setTimeout(callback, 0); 980 return this; 981 } 982 this.css({ 983 opacity : 0 984 }, {tween:tween || 'medium', callback:function(selection) { 985 selection.hide(andIgnore).css({ 986 opacity : originalOpacity 987 }); 988 if (callback) { 989 callback(selection); 990 } 991 tween = callback = null; 992 }}); 993 return this; 994 }, 995 996 /** Automatically detects and uses CSS3 transitions. 997 * @private 998 */ 999 animateCSS : (function() { 1000 var manual, cssTransition, supportsCssTransition, checkCssTransitionSupport; 1001 1002 /** @ignore */ 1003 manual = function(cssProp, targetValue, duration, easing, callback) { 1004 var startValues = [], 1005 perNodeProperties = [], 1006 numericTargetValue, units, s; 1007 1008 cssProp = cssProp.toLowerCase(); 1009 1010 if (targetValue!=='auto') { 1011 numericTargetValue = parseFloat((targetValue + '').replace(priv.regex.getNumericCSSValue,'')) || 0; 1012 s = self.typeOf(targetValue)==='string' && targetValue.match(priv.regex.getCSSValueUnits); 1013 units = (s && s[0]) || 'px'; 1014 if (cssProp==='opacity') { 1015 units = ''; 1016 } 1017 } 1018 else { 1019 units = cssProp==='opacity' ? '' : 'px'; 1020 } 1021 1022 this._each(function(node, i) { 1023 var ts, tss, testCssObj={}, iprop, vis; 1024 1025 startValues[i] = parseFloat((self.nodeStyle(node, cssProp) + '').replace(priv.regex.getNonIntegerCharsSigned,'')) || 0; 1026 1027 if (targetValue==='auto' || targetValue==='') { 1028 vis = node.style.visibility || ''; 1029 testCssObj[cssProp] = targetValue; 1030 testCssObj.visibility = 'hidden'; 1031 self.applyCss(node, testCssObj); 1032 ts = self.nodeStyle(node, cssProp); 1033 if (ts===targetValue || ts.indexOf('px')<ts.length-3) { 1034 iprop = cssProp.substring(0,1).toUpperCase() + cssProp.substring(1).toLowerCase(); 1035 ts = node['offset'+iprop] + 'px'; 1036 } 1037 tss = self.typeOf(ts)==='string' && ts.match(priv.regex.getCSSValueUnits); 1038 perNodeProperties[i] = { 1039 _actualTarget : targetValue, 1040 numericTargetValue : parseFloat((ts + '').replace(priv.regex.getNumericCSSValue,'')) || 0, 1041 units : cssProp==='opacity' ? '' : (tss && tss[0] || 'px') 1042 }; 1043 setTimeout(function() { 1044 node.style.visibility = vis; 1045 node = null; 1046 }, 51); 1047 testCssObj = ts = tss = null; 1048 } 1049 else { 1050 perNodeProperties[i] = { 1051 numericTargetValue : numericTargetValue, 1052 units : units 1053 }; 1054 } 1055 }); 1056 1057 return this.animate(function(fraction, anim) { 1058 this._each(function(node, i) { 1059 var cssObj = {}, 1060 value = (fraction * (perNodeProperties[i].numericTargetValue-startValues[i]) + startValues[i]), 1061 floatVal, 1062 units = perNodeProperties[i].units; 1063 if (units==='px') { 1064 value = Math.round(value); 1065 } 1066 else { 1067 floatVal = parseFloat(value); 1068 if (floatVal%1===0) { 1069 value = Math.round(floatVal); 1070 } 1071 else { 1072 value = floatVal.toFixed(2); 1073 } 1074 } 1075 cssObj[cssProp] = value + units; 1076 self.applyCss(node, cssObj); 1077 }); 1078 }, duration, easing, function(sel) { 1079 sel._each(function(node, i) { 1080 var cssObj = {}; 1081 if (perNodeProperties[i]._actualTarget) { 1082 cssObj[cssProp] = perNodeProperties[i]._actualTarget; 1083 self.applyCss(node, cssObj); 1084 } 1085 }); 1086 callback.apply(sel, arguments); 1087 }); 1088 }; 1089 1090 /** @ignore */ 1091 cssTransition = function(cssProp, targetValue, duration, easing, callback) { 1092 var anim = this._createAnimationObj(function(){}, duration, easing, callback), 1093 me = this, 1094 transition = {}, 1095 css = {}; 1096 1097 cssProp = self.getStyleAsProperty(cssProp); 1098 if (self.typeOf(targetValue)==='number' && (cssProp+'').toLowerCase()!=='opacity') { 1099 targetValue = targetValue + 'px'; 1100 } 1101 1102 transition[self.getStyleAsCSS(cssProp)] = { 1103 duration : anim.duration, 1104 timingFunction : anim.easing 1105 }; 1106 1107 css[cssProp] = targetValue; 1108 1109 setTimeout(function() { 1110 /** @ignore */ 1111 me._each(function(node) { 1112 self.updateCssTransitions(node, transition); 1113 self.applyCss(node, css); 1114 priv.incrementAnimationCount(node); 1115 }); 1116 /** @ignore */ 1117 anim._cb = function() { 1118 if (anim) { 1119 1120 /** remove CSS transition definitions from the generated CSS: 1121 * @ignore 1122 */ 1123 var nullTransition = {}; 1124 nullTransition[cssProp] = null; 1125 me._each(function(node) { 1126 self.updateCssTransitions(node, nullTransition); 1127 priv.decrementAnimationCount(node); 1128 }); 1129 1130 if (anim.callback) { 1131 anim.callback.call(me, me); 1132 } 1133 for (var x in anim) { 1134 if (anim.hasOwnProperty(x)) { 1135 try{ delete anim[x]; }catch(err){} 1136 } 1137 } 1138 } 1139 anim = css = callback = me = null; 1140 }; 1141 setTimeout(anim._cb, (parseInt(anim.duration,10) || 0)+20); 1142 }, 10); 1143 }; 1144 1145 /** @ignore */ 1146 checkCssTransitionSupport = function() { 1147 supportsCssTransition = document.body.style[vendorCssPrefixJS+'Transition']!==undefined || document.body.style.transition!==undefined; 1148 return supportsCssTransition; 1149 }; 1150 1151 return function(cssProp, targetValue, duration, easing, callback) { 1152 var iosCompat=false, x; 1153 if (self.typeOf(supportsCssTransition)!=='boolean') { 1154 checkCssTransitionSupport(); 1155 } 1156 if ((self.allowCssTransitions!==false || iosCompat===true) && supportsCssTransition) { 1157 cssTransition.apply(this, arguments); 1158 } 1159 else { 1160 manual.apply(this, arguments); 1161 } 1162 return this; 1163 }; 1164 }()), 1165 1166 animate : function(animator, duration, easing, callback) { 1167 if (animator) { 1168 var nodeSelection = this, 1169 anim = this._createAnimationObj.apply(this, arguments), 1170 frame; 1171 1172 this._each(function(node) { 1173 priv.incrementAnimationCount(node); 1174 }); 1175 1176 frame = function(now) { 1177 anim.frameTime = now; 1178 anim.position = anim.frameTime - anim.start; 1179 anim.fraction = anim.position / anim.duration; 1180 if (anim.position>=anim.duration) { 1181 anim.fraction = 1; 1182 anim.position = anim.duration; 1183 } 1184 else if (anim.easingMethod) { 1185 anim.fraction = anim.easingMethod.call(self.easingMethods, anim.fraction, anim); 1186 } 1187 1188 anim.animator.call(nodeSelection, anim.fraction, anim); 1189 1190 if (anim.fraction===1) { 1191 for (var x=nodeSelection._animations.length; x--; ) { 1192 if (nodeSelection._animations[x]===anim) { 1193 nodeSelection._animations.splice(x, 1); 1194 break; 1195 } 1196 } 1197 if (anim.callback) { 1198 setTimeout(function() { 1199 nodeSelection._each(function(node) { 1200 priv.decrementAnimationCount(node); 1201 }); 1202 anim.callback.call(nodeSelection, nodeSelection, anim); 1203 nodeSelection = anim = null; 1204 }, 10); 1205 } 1206 } 1207 else { 1208 anim.timer = self.animationFrame.getTimer(frame, self.baseAnimationInterval || 10); 1209 } 1210 }; 1211 1212 self.animationFrame.getTimer(frame, self.baseAnimationInterval || 10); 1213 1214 this._animations.push(anim); 1215 } 1216 return this; 1217 }, 1218 1219 /** @private */ 1220 _createAnimationObj : function(animator, duration, easing, callback) { 1221 var anim = { 1222 animator : animator, 1223 duration : duration, 1224 easing : self.typeOf(easing)==='string' ? easing : 'ease', 1225 callback : callback, 1226 start : self.animationFrame.getStartTime(), 1227 frameTime : null 1228 }; 1229 1230 if (self.typeOf(anim.duration)==='string') { 1231 switch (anim.duration.toLowerCase()) { 1232 case 'long': 1233 case 'slow': 1234 anim.duration = priv.animationTimes.slow; 1235 break; 1236 case 'short': 1237 case 'fast': 1238 anim.duration = priv.animationTimes.fast; 1239 break; 1240 default: 1241 anim.duration = parseInt(anim.duration, 10) || priv.animationTimes.medium; 1242 } 1243 } 1244 else { 1245 anim.duration = Math.round(anim.duration) || priv.animationTimes.medium; 1246 } 1247 1248 if (priv.animationTimeScale) { 1249 anim.duration *= priv.animationTimeScale; 1250 } 1251 1252 if (anim.easing && self.easingMethods.hasOwnProperty(anim.easing)) { 1253 anim.easingMethod = self.easingMethods[anim.easing]; 1254 } 1255 else { 1256 anim.easing = null; 1257 } 1258 return anim; 1259 }, 1260 1261 /** Add a CSS class to the selection. <br /> 1262 * Pass an Array and/or multiple arguments to add multiple classes. 1263 * @param {String} className A CSS class to add. 1264 * @returns {this} 1265 */ 1266 classify : function(className) { 1267 var classes = self.isArray(className) ? className : self.toArray(arguments); 1268 this._each(function(el) { 1269 self.addClass(el, classes); 1270 }); 1271 return this; 1272 }, 1273 1274 /** Remove a CSS class to the selection. <br /> 1275 * Pass an Array and/or multiple arguments to remove multiple classes. 1276 * @param {String} className A CSS class to remove. 1277 * @returns {this} 1278 */ 1279 declassify : function(className) { 1280 var classes = self.isArray(className) ? className : self.toArray(arguments); 1281 this._each(function(el) { 1282 self.removeClass(el, classes); 1283 }); 1284 return this; 1285 }, 1286 1287 /** Check if the selection contains only nodes with the given CSS class. 1288 * @param {String} className The CSS class to check for 1289 * @param {Boolean} [ifAny=false] If `true`, returns `true` only if *and* nodes have the given CSS class 1290 * @returns {Boolean} 1291 */ 1292 hasClass : function(className, ifAny) { 1293 var result = ifAny!==true; 1294 this._each(function(node) { 1295 var exists = node.classList ? node.classList.contains(className) : (' '+node.className+' ').indexOf(' '+className+' ')>-1; 1296 if (ifAny===true) { 1297 if (exists) { 1298 result = true; 1299 return false; 1300 } 1301 } 1302 else if (!exists) { 1303 result = false; 1304 } 1305 }); 1306 return result; 1307 }, 1308 1309 /** Set the opacity of each node in the selection. 1310 * @param {Number} opacity A value from 0 to 1 1311 * @returns {this} 1312 */ 1313 setOpacity : function(opacity) { 1314 this._each(function(el) { 1315 self.setOpacity(el, opacity); 1316 }); 1317 return this; 1318 }, 1319 1320 /** Call a method on each node in the selection and sum the results. 1321 * @returns {Number} 1322 */ 1323 sumOf : function(method) { 1324 var total = 0, 1325 args = Array.prototype.slice.call(arguments, 1); 1326 if (this.constructor.prototype.hasOwnProperty(method)) { 1327 this._each(function(node) { 1328 node = new self.NodeSelection(node); 1329 total += node[method].apply(node, args); 1330 }); 1331 } 1332 return total; 1333 }, 1334 1335 height : function(height, options) { 1336 var units, 1337 node, 1338 matches, 1339 offsetHeight = 0; 1340 if (self.typeOf(height)==='object' && !options) { 1341 options = height; 1342 height = null; 1343 } 1344 options = options || {}; 1345 if (height || height===0) { 1346 height = height + ''; 1347 if (height==='auto') { 1348 units = ''; 1349 } 1350 else { 1351 matches = (/^([\-0-9\.]+)(.*?)$/).exec(height); 1352 height = Math.round(matches && matches[1] || height) || 0; 1353 units = matches && matches[2] || 'px'; 1354 } 1355 this.css({ 1356 height : height + units 1357 }); 1358 return this; 1359 } 1360 this._each(function(node) { 1361 offsetHeight += parseInt(node.offsetHeight, 10) || 0; 1362 if (options.border!==true) { 1363 offsetHeight -= parseInt( (self.nodeStyle(node, 'border-top-width')+'').replace(priv.regex.getNonIntegerCharsSigned,'') ,10) || 0; 1364 offsetHeight -= parseInt( (self.nodeStyle(node, 'border-bottom-width')+'').replace(priv.regex.getNonIntegerCharsSigned,'') ,10) || 0; 1365 } 1366 if (options.margin===true) { 1367 offsetHeight += parseInt( (self.nodeStyle(node, 'margin-top')+'').replace(priv.regex.getNonIntegerCharsSigned,'') ,10) || 0; 1368 offsetHeight += parseInt( (self.nodeStyle(node, 'margin-bottom')+'').replace(priv.regex.getNonIntegerCharsSigned,'') ,10) || 0; 1369 } 1370 if (options.padding===false) { 1371 offsetHeight -= parseInt( (self.nodeStyle(node, 'padding-top')+'').replace(priv.regex.getNonIntegerCharsSigned,'') ,10) || 0; 1372 offsetHeight -= parseInt( (self.nodeStyle(node, 'padding-bottom')+'').replace(priv.regex.getNonIntegerCharsSigned,'') ,10) || 0; 1373 } 1374 }); 1375 return offsetHeight; 1376 }, 1377 width : function(width, options) { 1378 var units, 1379 node, 1380 matches, 1381 offsetWidth = 0; 1382 if (self.typeOf(width)==='object' && !options) { 1383 options = width; 1384 width = null; 1385 } 1386 options = options || {}; 1387 if (width || width===0) { 1388 width = width + ''; 1389 if (width==='auto') { 1390 units = ''; 1391 } 1392 else { 1393 matches = (/^([\-0-9\.]+)(.*?)$/).exec(width); 1394 width = matches && matches[1] || width; 1395 units = matches && matches[2] || 'px'; 1396 } 1397 this.css({ 1398 width : width + units 1399 }); 1400 return this; 1401 } 1402 this._each(function(node) { 1403 offsetWidth += parseInt(node.offsetWidth, 10) || 0; 1404 if (options.border!==true) { 1405 offsetWidth -= parseInt( (self.nodeStyle(node, 'border-left-width')+'').replace(priv.regex.getNonIntegerCharsSigned,'') ,10) || 0; 1406 offsetWidth -= parseInt( (self.nodeStyle(node, 'border-right-width')+'').replace(priv.regex.getNonIntegerCharsSigned,'') ,10) || 0; 1407 } 1408 if (options.margin===true) { 1409 offsetWidth += parseInt( (self.nodeStyle(node, 'margin-left')+'').replace(priv.regex.getNonIntegerCharsSigned,'') ,10) || 0; 1410 offsetWidth += parseInt( (self.nodeStyle(node, 'margin-right')+'').replace(priv.regex.getNonIntegerCharsSigned,'') ,10) || 0; 1411 } 1412 if (options.padding===false) { 1413 offsetWidth -= parseInt( (self.nodeStyle(node, 'padding-left')+'').replace(priv.regex.getNonIntegerCharsSigned,'') ,10) || 0; 1414 offsetWidth -= parseInt( (self.nodeStyle(node, 'padding-right')+'').replace(priv.regex.getNonIntegerCharsSigned,'') ,10) || 0; 1415 } 1416 }); 1417 return offsetWidth; 1418 }, 1419 x : function(absolute, mark) { 1420 var node = this._nodes[0], 1421 pos = null, 1422 posProp; 1423 if (node) { 1424 pos = node.offsetLeft; 1425 if (absolute===true) { 1426 while((node=node.parentNode) && node!==document) { 1427 pos += parseFloat(node.offsetLeft) || 0; 1428 } 1429 } 1430 } 1431 return pos; 1432 }, 1433 y : function(absolute) { 1434 var node = this._nodes[0], 1435 pos = null; 1436 if (node) { 1437 pos = node.offsetTop; 1438 if (absolute===true) { 1439 while((node=node.parentNode) && node!==document) { 1440 pos += parseFloat(node.offsetTop) || 0; 1441 } 1442 } 1443 } 1444 return pos; 1445 }, 1446 position : function(x, y, tween, units) { 1447 var css; 1448 units = units || 'px'; 1449 if (arguments.length<1 || (arguments.length===1 && arguments[0]===true)) { 1450 return { 1451 x : this.x(arguments[0]===true), 1452 y : this.y(arguments[0]===true) 1453 }; 1454 } 1455 else { 1456 css = {}; 1457 if (puredom.typeOf(x)==='number') { 1458 css.left = x + units; 1459 } 1460 else if (puredom.typeOf(x)==='string') { 1461 css.left = x; 1462 } 1463 if (puredom.typeOf(y)==='number') { 1464 css.top = y + units; 1465 } 1466 else if (puredom.typeOf(y)==='string') { 1467 css.top = y; 1468 } 1469 this.css(css, tween); 1470 return this; 1471 } 1472 }, 1473 scrollLeft : function(value) { 1474 if (value || value===0) { 1475 if (self.typeOf(value)!=='number') { 1476 value = Math.round((value+'').replace(priv.regex.getNonIntegerCharsSigned,'')); 1477 } 1478 this._each(function(node) { 1479 node.scrollLeft = value; 1480 }); 1481 } 1482 else { 1483 return this._nodes && this._nodes[0] && this._nodes[0].scrollLeft || 0; 1484 } 1485 }, 1486 scrollTop : function(value) { 1487 if (value || value===0) { 1488 if (self.typeOf(value)!=='number') { 1489 value = Math.round((value+'').replace(priv.regex.getNonIntegerCharsSigned,'')); 1490 } 1491 this._each(function(node) { 1492 node.scrollTop = value; 1493 }); 1494 } 1495 else { 1496 return this._nodes && this._nodes[0] && this._nodes[0].scrollTop || 0; 1497 } 1498 }, 1499 focus : function() { 1500 this._each(function(node) { 1501 if (node.focus) { 1502 node.focus(); 1503 } 1504 }); 1505 return this; 1506 }, 1507 blur : function() { 1508 this._each(function(node) { 1509 if (node.blur) { 1510 node.blur(); 1511 } 1512 }); 1513 return this; 1514 }, 1515 selectAll : function() { 1516 this._each(function(node) { 1517 if (node.SelectAll) { 1518 node.SelectAll(); 1519 } 1520 if (node.select) { 1521 node.select(); 1522 } 1523 if (node.selectionStart && node.hasOwnProperty('value')) { 1524 node.selectionStart = 0; 1525 node.selectionEnd = node.value.length; 1526 } 1527 }); 1528 }, 1529 getStyle : function(prop, returnValue) { 1530 var props = []; 1531 this._each(function(node) { 1532 props.push( self.nodeStyle(node, prop) || null ); 1533 }); 1534 if (returnValue===false) { 1535 this.pushResult(props); 1536 return this; 1537 } 1538 return props.length<=1 ? props[0] : props; 1539 }, 1540 value : function(newValue, options) { 1541 options = options || {}; 1542 if (newValue!==null && newValue!==undefined && arguments.length>0) { 1543 // set the value 1544 this._each(function(node) { 1545 var name = (node.nodeName+'').toLowerCase(), 1546 type = (node.getAttribute('type') || '').toLowerCase(); 1547 1548 if (name==='input' && (type==='checkbox' || type==='radio')) { 1549 node.checked = !!newValue; 1550 } 1551 else { 1552 node.value = newValue; 1553 } 1554 1555 if (options.fireChange!==false) { 1556 self.fireEvent({ 1557 type : 'change', 1558 target : node, 1559 value : newValue 1560 }); 1561 } 1562 }); 1563 1564 return this; 1565 } 1566 else { 1567 // get and return the value 1568 var values = []; 1569 this._each(function(node) { 1570 var name = (node.nodeName+'').toLowerCase(), 1571 type = (node.getAttribute('type') || '').toLowerCase(), 1572 value; 1573 if (name==='input' && (type==='checkbox' || type==='radio')) { 1574 values.push(!!node.checked); 1575 } 1576 else if (name==='select') { 1577 value = (node.multiselect || node.multiSelect) ? [] : null; 1578 self.el(node).query('option')._each(function(option) { 1579 if (option.selected || option.checked) { 1580 if (self.isArray(value)) { 1581 value.push(option.value); 1582 } 1583 else { 1584 value = option.value; 1585 } 1586 } 1587 }); 1588 values.push(value); 1589 } 1590 else { 1591 values.push(node.value); 1592 } 1593 }); 1594 return values.length<2 ? values[0] : values; 1595 } 1596 }, 1597 attr : function(key, value, returnValue) { 1598 var attrs = [], i, k; 1599 if (arguments.length===0) { 1600 attrs = {}; 1601 for (i=this._nodes[0].attributes.length; i--; ) { 1602 k = this._nodes[0].attributes[i]; 1603 // skip over non-user-specified attributes in IE 1604 if (k.specified) { 1605 attrs[k.name || k.nodeName] = k.value || k.nodeValue; 1606 } 1607 } 1608 return attrs; 1609 } 1610 else if (arguments.length>1) { 1611 if (self.typeOf(key)==='object') { 1612 for (i in key) { 1613 if (typeof i==='string' && key.hasOwnProperty(i)) { 1614 this.attr(attrs, key[i]); 1615 } 1616 } 1617 return this; 1618 } 1619 return this._each(function(node) { 1620 try { 1621 node.setAttribute(key, value); 1622 if (node.removeAttribute && (value===null || value===undefined)) { 1623 node.removeAttribute(key); 1624 } 1625 } catch(err) {} 1626 }); 1627 } 1628 else { 1629 this._each(function(node) { 1630 var a = node.getAttribute(key); 1631 if (typeof a!=='string') { 1632 a = null; 1633 } 1634 attrs.push( a ); 1635 }); 1636 if (returnValue===false) { 1637 this.pushResult(attrs); 1638 return this; 1639 } 1640 return attrs.length<=1 ? attrs[0] : attrs; 1641 } 1642 }, 1643 prop : function(key, value, returnValue) { 1644 var props = []; 1645 if (arguments.length>1) { 1646 return this._each(function(node) { 1647 node[key] = value; 1648 if (value===undefined) { 1649 try{ delete node[key]; }catch(err){} 1650 } 1651 }); 1652 } 1653 else { 1654 this._each(function(node) { 1655 var val; 1656 try { 1657 val = node[key]; 1658 }catch(err){ 1659 self.log('NodeSelection#prop('+key+') :: Access Error', err); 1660 } 1661 props.push( val || null ); 1662 }); 1663 if (returnValue===false) { 1664 this.pushResult(props); 1665 return this; 1666 } 1667 return props.length<=1 ? props[0] : props; 1668 } 1669 }, 1670 enable : function() { 1671 this.attr('disabled', null); 1672 return this; 1673 }, 1674 disable : function() { 1675 this.attr('disabled', 'disabled'); 1676 return this; 1677 }, 1678 enabled : function(newValue) { 1679 if (newValue===true || newValue===false) { 1680 this[newValue?'enable':'disable'](); 1681 return this; 1682 } 1683 else { 1684 return this.attr('disabled')!=='disabled' && this.prop('disabled')!==true; 1685 } 1686 }, 1687 1688 /** Register an event handler. <br /> 1689 * When an event of the given type is triggered, the handler function is called. 1690 * @param {String} type An event type to listen for 1691 * @param {String} [selector] Optionally fire only if the event target matches a CSS selector 1692 * @param {Function} handler A handler to call in response to the event 1693 * @example 1694 * function clickHandler(e){ alert(e.button); } 1695 * foo.addEvent("click", clickHandler); 1696 * @returns {this} 1697 */ 1698 on : function(type, selector, handler) { 1699 this._each(function(el) { 1700 self.addEvent(el, type, selector, handler); 1701 }); 1702 return this; 1703 }, 1704 1705 /** Un-register an event handler. 1706 * @param {String} type The event type 1707 * @param {String} [selector] Optionally fire only if the target matches a CSS selector 1708 * @param {Function} handler The handler to remove 1709 * @example 1710 * foo.removeEvent("click", clickHandler); 1711 * @returns {this} 1712 */ 1713 off : function(type, selector, handler) { 1714 this._each(function(el) { 1715 self.removeEvent(el, type, selector, handler); 1716 }); 1717 return this; 1718 }, 1719 1720 /** Fire an event on the selection. 1721 * @param {String} type An event type 1722 * @param {Object|Event} e The event data 1723 * @returns {this} 1724 */ 1725 trigger : function(type, e) { 1726 this._each(function(node) { 1727 self.fireEvent(self.extend({}, e || {}, { 1728 type : type, 1729 target : node 1730 })); 1731 }); 1732 return this; 1733 }, 1734 1735 /** @private */ 1736 _removeAllEvents : function(deep) { 1737 var children; 1738 this._each(function(node) { 1739 priv.wrappedEventListener.destroyObjHandlers(node, true); 1740 }); 1741 children = this.children(); 1742 if (deep===true && children.count()>0) { 1743 children._removeAllEvents(true); 1744 } 1745 children = deep = null; 1746 return this; 1747 }, 1748 1749 /** Append an element to the DOM. 1750 * @param {puredom.NodeSelection|HTMLElement} child An element or a Selection of elements to append 1751 * @returns {this} 1752 */ 1753 appendChild : function(child) { 1754 if (child && this._nodes.length>0) { 1755 if (child.constructor===this.constructor) { 1756 var parent = this._nodes[0]; 1757 child._each(function(node) { 1758 parent.appendChild(node); 1759 }); 1760 } 1761 else { 1762 this._nodes[0].appendChild(child); 1763 } 1764 } 1765 return this; 1766 }, 1767 1768 /** Insert an element into the DOM before a given reference element. 1769 * @param {puredom.NodeSelection|HTMLElement} child An element or a Selection of elements to insert 1770 * @param {puredom.NodeSelection|HTMLElement} before An element or a Selection to insert <code>child</code> before 1771 * @returns {this} 1772 */ 1773 insertBefore : function(child, before) { 1774 if (child && this._nodes.length>0) { 1775 if (before && before.constructor===this.constructor) { 1776 before = before._nodes[0]; 1777 } 1778 if (!before || before.parentNode!==this._nodes[0]) { 1779 return this.appendChild(child); 1780 } 1781 if (child.constructor===this.constructor) { 1782 var parent = this._nodes[0]; 1783 child._each(function(node) { 1784 parent.insertBefore(node, before); 1785 }, null, true); 1786 } 1787 else { 1788 this._nodes[0].insertBefore(child, before); 1789 } 1790 } 1791 return this; 1792 }, 1793 1794 /** Insert all elements in the Selection into a given parent. <br /> 1795 * Uses document fragments to improve performance when inserting a Selection containing multiple nodes. 1796 * @param {puredom.NodeSelection|HTMLElement} what A parent into which the selection should be inserted 1797 * @returns {this} 1798 */ 1799 insertInto : function(what) { 1800 var frag; 1801 if (what && this.count()>0) { 1802 if (what.constructor===this.constructor) { 1803 what = what._nodes[0]; 1804 } 1805 if (this.count()===1) { 1806 what.appendChild(this._nodes[0]); 1807 } 1808 else { 1809 frag = document.createDocumentFragment(); 1810 this._each(function(node) { 1811 frag.appendChild(node); 1812 }, null, true); 1813 what.appendChild(frag); 1814 } 1815 } 1816 return this; 1817 }, 1818 1819 /** Remove all elements in the Selection from the DOM. 1820 * @returns {this} 1821 */ 1822 remove : function() { 1823 this.fireEvent('remove'); 1824 this._each(function(node) { 1825 if (node.parentNode) { 1826 node.parentNode.removeChild(node); 1827 } 1828 }); 1829 return this; 1830 }, 1831 1832 /** Remove all elements in the Selection from the DOM, and <strong>destroy them</strong>. <br /> 1833 * <strong>Note:</strong> This also removes all elements from the Selection. 1834 * @returns {this} 1835 */ 1836 destroy : function() { 1837 this.remove(); 1838 this.fireEvent('destroy'); 1839 this._removeAllEvents(true); 1840 this._nodes.splice(0, this._nodes.length); 1841 return this; 1842 }, 1843 1844 /** Search for elements within the Selection's tree that match the given CSS selector. <br /> 1845 * <strong>Note:</strong> This is a scoped version of puredom(selector). 1846 * @param {String} selector A CSS selector 1847 * @param {Object} [options] Options, which get passed to puredom() 1848 * @returns {puredom.NodeSelection} results 1849 */ 1850 query : function(selector, options) { 1851 var results = []; 1852 if (this._nodes.length>0) { 1853 options = puredom.extend({}, options || {}); 1854 this._each(function(node) { 1855 var r; 1856 options.within = node; 1857 r = self.selectorEngine.query(selector, options); 1858 if (self.isArray(r) && r.length>0) { 1859 results = results.concat(r); 1860 } 1861 }); 1862 } 1863 return new self.NodeSelection(results); 1864 }, 1865 1866 /** Clone the selection, optionally into a new parent node. 1867 * @param {Boolean} [deep=true] Perform a deep clone, which clones all descendent nodes. 1868 * @param {Object} [newParent] Optionally inject into a new parentNode. Pass <code>true</code> to clone into the same parent. 1869 * @returns {puredom.NodeSelection} clonedSelection 1870 */ 1871 clone : function(deep, newParent) { 1872 var sel = []; 1873 if (newParent===true) { 1874 newParent = this.parent(); 1875 } 1876 this._each(function(node) { 1877 sel.push( node.cloneNode(deep!==false) ); 1878 }); 1879 sel = new self.NodeSelection(sel); 1880 if (newParent) { 1881 newParent.appendChild(sel); 1882 } 1883 sel._each(priv.removeNodeUID); 1884 if (deep!==false) { 1885 sel.query('*')._each(priv.removeNodeUID); 1886 } 1887 return sel; 1888 }, 1889 1890 /** Get the number of elements in the Selection. 1891 * @returns {Number} count 1892 */ 1893 count : function() { 1894 return this._nodes.length; 1895 }, 1896 1897 /** Check if the selection contains at least one element. 1898 * @returns {Boolean} exists 1899 */ 1900 exists : function() { 1901 return this.count()>0; 1902 }, 1903 1904 /** Get a new Selection containing both the next and previous sibling elements of each element in the current Selection. 1905 * @returns {puredom.NodeSelection} siblings 1906 */ 1907 siblings : function() { 1908 var newSelection = new self.NodeSelection(); 1909 this._each(function(node) { 1910 var n = node; 1911 while ((n=n.previousSibling)) { 1912 if (n.nodeName!=='#text' && n.nodeName!=='#comment' && n.nodeType!==3) { 1913 newSelection._nodes.push(n); 1914 } 1915 } 1916 n = node; 1917 while ((n=n.nextSibling)) { 1918 if (n.nodeName!=='#text' && n.nodeName!=='#comment' && n.nodeType!==3) { 1919 newSelection._nodes.push(n); 1920 } 1921 } 1922 }); 1923 return newSelection; 1924 }, 1925 1926 /** Get a new Selection containing the next sibling elements of each element in the current Selection. 1927 * @returns {puredom.NodeSelection} nextSiblings 1928 */ 1929 next : function() { 1930 var sib = this._nodes[0] && this._nodes[0].nextSibling; 1931 while (sib && sib.nodeType===3) { 1932 sib = sib.nextSibling; 1933 } 1934 return new self.NodeSelection(sib && [sib] || null); 1935 }, 1936 1937 /** Get a new Selection containing the previous sibling elements of each element in the current Selection. 1938 * @returns {puredom.NodeSelection} previousSiblings 1939 */ 1940 prev : function() { 1941 var sib = this._nodes[0] && this._nodes[0].previousSibling; 1942 while (sib && sib.nodeType===3) { 1943 sib = sib.previousSibling; 1944 } 1945 return new self.NodeSelection(sib && [sib] || null); 1946 }, 1947 1948 /** Alais of {@link puredom.NodeSelection#prev} */ 1949 previous : function() { 1950 return this.prev.apply(this,arguments); 1951 }, 1952 1953 /** Get a new Selection containing the first direct child element of the current Selection. 1954 * @returns {puredom.NodeSelection} firstChild 1955 */ 1956 firstChild : function() { 1957 return this.children().first(); 1958 }, 1959 1960 /** Get a new Selection containing the last direct child element of the current Selection. 1961 * @returns {puredom.NodeSelection} firstChild 1962 */ 1963 lastChild : function() { 1964 return this.children().last(); 1965 }, 1966 1967 /** Get a new Selection containing the direct child element of the current Selection at index <code>n</code>. 1968 * @param {Number} n A 0-based index. 1969 * @returns {puredom.NodeSelection} nthChild 1970 */ 1971 nthChild : function(n) { 1972 return this.children().index(n); 1973 }, 1974 1975 /** Get a new Selection containing only the first element in the current Selection. 1976 * @returns {puredom.NodeSelection} first 1977 */ 1978 first : function(n) { 1979 return new self.NodeSelection( this._nodes.slice(0, n || 1) ); 1980 }, 1981 1982 /** Get a new Selection containing only the last element in the current Selection. 1983 * @returns {puredom.NodeSelection} last 1984 */ 1985 last : function(n) { 1986 return new self.NodeSelection( this._nodes.slice(this._nodes.length-(n || 1)) ); 1987 }, 1988 1989 /** Get a new Selection containing only the <code>n</code> element(s) in the current Selection starting at index <code>i</code>. 1990 * @param {Number} i A 0-based index of the elements(s) to return 1991 * @param {Number} [n=1] The number of elements to return 1992 * @returns {puredom.NodeSelection} slice 1993 */ 1994 index : function(i, n) { 1995 return new self.NodeSelection(self.typeOf(i)==='number' && this._nodes.slice(i, i + (n || 1)) || null); 1996 }, 1997 1998 /** Get a new Selection containing the de-duped parent elements of the current Selection. 1999 * @returns {puredom.NodeSelection} parents 2000 */ 2001 parent : function() { 2002 var nodes=[], parent; 2003 this._each(function(node) { 2004 parent = node.parentNode; 2005 // Note: all newly created elements are placed into a document fragment in IE. 2006 // Unfortunately, this means parentNodes that are #document-fragment's can't be considered valid (lowest-common). 2007 if (parent && nodes.indexOf(parent)<0 && parent.nodeType!==11) { 2008 nodes.push(parent); 2009 } 2010 }); 2011 return new self.NodeSelection(nodes); 2012 }, 2013 2014 /** Get a new Selection containing all direct child elements of the current Selection. 2015 * @returns {puredom.NodeSelection} children 2016 */ 2017 children : function() { 2018 var children = [], 2019 x, y; 2020 if (this._nodes.length>0) { 2021 for (x=0; x<this._nodes.length; x++) { 2022 if (this._nodes[x].childNodes) { 2023 for (y=0; y<this._nodes[x].childNodes.length; y++) { 2024 if (this._nodes[x].childNodes[y].nodeType!==3 && this._nodes[x].childNodes[y].nodeName!=='#text' && this._nodes[x].childNodes[y].nodeName!=='#comment') { 2025 children.push(this._nodes[x].childNodes[y]); 2026 } 2027 } 2028 } 2029 } 2030 } 2031 return new self.NodeSelection(children); 2032 }, 2033 2034 /** Submit any forms in the Selection. 2035 * @returns {this} 2036 */ 2037 submit : function() { 2038 return this._each(function(node) { 2039 var evt = self.fireEvent({ 2040 type : 'submit', 2041 target : node 2042 }); 2043 if (evt.rval!==false && evt.returnValue!==false && evt.preventDefault!==true) { 2044 if (node.submit) { 2045 node.submit(); 2046 } 2047 } 2048 }); 2049 }, 2050 2051 /** Get or set the selected text of an input element. <br /> 2052 * <strong>Note:</strong> This only operates on the first element in the selection. 2053 * @param {Number} start A character index at which to start text selection 2054 * @param {Number} [end=start] A character index at which to end text selection 2055 * @returns {Object} An object with <code>start</code>, <code>end</code> and <code>text</code> properties correspdonding to the current selection state. 2056 */ 2057 selection : function(start, end) { 2058 var el = this._nodes[0], 2059 value, sel, before, endMax, range; 2060 if (start && typeof start!=='number' && start.start) { 2061 end = start.end; 2062 start = start.start; 2063 } 2064 if (typeof start==='number') { 2065 if (start<0) { 2066 start = 0; 2067 } 2068 endMax = el.value.length; 2069 if (end>endMax) { 2070 end = endMax; 2071 } 2072 if (start>end) { 2073 start = end; 2074 } 2075 else if (end<start) { 2076 end = start; 2077 } 2078 2079 if(window.getSelection) { 2080 el.selectionStart = start; 2081 el.selectionEnd = end; 2082 } 2083 else if (el.selectionEnd || el.selectionEnd===0) { 2084 el.selectionStart = start; 2085 el.selectionEnd = end; 2086 } 2087 else if (el.createTextRange) { 2088 range = el.createTextRange(); 2089 range.collapse(true); 2090 range.moveStart('character', start); 2091 range.moveEnd('character', end); 2092 range.select(); 2093 } 2094 } 2095 else { 2096 if (window.getSelection) { // Stanadards 2097 value = typeof el.value==='string' ? el.value : el.innerHTML; 2098 sel = window.getSelection(); 2099 return { 2100 start : el.selectionStart+0, 2101 end : el.selectionEnd+0, 2102 text : value.substring(sel.start, sel.end) 2103 }; 2104 } 2105 else if (document.selection) { // IE 2106 sel = document.selection.createRange(); 2107 before = document.body.createTextRange(); 2108 before.moveToElementText(el); 2109 before.setEndPoint("EndToStart", sel); 2110 return { 2111 start : before.text.length, 2112 end : before.text.length + sel.text.length, 2113 text : sel.text 2114 }; 2115 } 2116 else { 2117 self.log("Selection retrieval is not supported in this browser."); 2118 return { 2119 start : 0, 2120 end : 0, 2121 text : '' 2122 }; 2123 } 2124 } 2125 return this; 2126 }, 2127 2128 /** Template the Selection and all descendants based on <code>data-tpl-id</code> attributes. <br /> 2129 * Each element with a <code>data-tpl-id</code> attribute will have it's contents updated with the corresponding value of that attribute when interpreted as a dot-notated key path within <code>templateFields</code>. <br /> 2130 * <code>data-tpl-id</code> attribute values can optionally include a pipe-separated list of "filters". <br /> 2131 * <strong>Example:</strong> <br /> 2132 * <pre><span data-tpl-id="article.title|ucWords|truncate:byWord,300|htmlEntities"></span></pre> <br /> 2133 * <em><strong>Note:</strong> the "htmlEntities" filter is already added for you where needed.</em> 2134 * @param {Object} [templateFields={}] Template data fields. Nested Objects/Arrays are addressable via dot notation. 2135 * @returns {this} 2136 */ 2137 template : function(templateFields) { 2138 var attrName = self.templateAttributeName, 2139 getFilters; 2140 templateFields = templateFields || {}; 2141 2142 getFilters = function(filters, htmlEntities) { 2143 for (var i=filters.length; i--; ) { 2144 if (filters[i]==='htmlEntities') { 2145 filters.splice(i, 1); 2146 } 2147 } 2148 return filters; 2149 }; 2150 2151 this.query('['+attrName+']').each(function(node) { 2152 var nodeName = node.nodeName(), 2153 tpl = node.attr(attrName), 2154 key, tplField, tplValue, tplFilters, nType, keyMatches; 2155 2156 tpl = ~tpl.indexOf(':') ? priv.parseCSS(tpl, false) : { 'set':tpl }; 2157 2158 for (key in tpl) { 2159 if (tpl.hasOwnProperty(key)) { 2160 2161 tplField = tpl[key].split('|'); 2162 tplFilters = getFilters(tplField.slice(1)); 2163 tplField = tplField[0]; 2164 2165 tplValue = puredom.delve(templateFields, tplField); 2166 2167 if (tplValue!==null && tplValue!==undefined) { 2168 if ((tplValue instanceof Date || tplValue.constructor.name==='Date') && tplValue.toLocaleString) { 2169 tplValue = tplValue.toLocaleString(); 2170 } 2171 if (tplFilters && tplFilters.length) { 2172 tplValue = self.text.filter(tplValue, tplFilters.join('|')); 2173 } 2174 2175 keyMatches = key.match(/^([a-z]+)\-(.+)$/i); 2176 2177 if (keyMatches && typeof node[keyMatches[1]]==='function') { 2178 node[keyMatches[1]](keyMatches[2], tplValue); 2179 } 2180 else if (typeof node[key]==='function') { 2181 node[key](tplValue); 2182 } 2183 else if ( (nType = node.attr('data-tpl-prop')) ) { 2184 node.prop(nType, tplValue); 2185 } 2186 else { 2187 switch (nodeName) { 2188 case 'select': 2189 case 'input': 2190 case 'textarea': 2191 case 'meter': 2192 case 'progress': 2193 node.value(tplValue); 2194 break; 2195 2196 case 'img': 2197 case 'video': 2198 case 'audio': 2199 case 'iframe': 2200 node.attr('src', tplValue); 2201 break; 2202 2203 default: 2204 //node.html(self.text.htmlEntities(tplValue)); 2205 node.text(tplValue); 2206 break; 2207 } 2208 } 2209 } 2210 } 2211 } 2212 }); 2213 templateFields = null; 2214 return this; 2215 } 2216 }); 2217 2218 /** Alias of {@link puredom.NodeSelection#trigger} 2219 * @function 2220 */ 2221 self.NodeSelection.prototype.fireEvent = self.NodeSelection.prototype.trigger; 2222 2223 /** Alias of {@link puredom.NodeSelection#trigger} 2224 * @function 2225 */ 2226 self.NodeSelection.prototype.emit = self.NodeSelection.prototype.trigger; 2227 2228 /** Alias of {@link puredom.NodeSelection#on} 2229 * @function 2230 */ 2231 self.NodeSelection.prototype.addEvent = self.NodeSelection.prototype.on; 2232 2233 /** Alias of {@link puredom.NodeSelection#off} 2234 * @function 2235 */ 2236 self.NodeSelection.prototype.removeEvent = self.NodeSelection.prototype.off; 2237 2238 /** @ignore */ 2239 self.NodeSelection.prototype.animateCss = self.NodeSelection.prototype.animateCSS; 2240 2241 2242 2243 /** @private */ 2244 priv.incrementAnimationCount = function(node) { 2245 node._puredom_animationCount = priv.getAnimationCount(node) + 1; 2246 if (node._puredom_animationCount===1) { 2247 self.addClass(node, '_puredom_animating'); 2248 } 2249 }; 2250 /** @private */ 2251 priv.decrementAnimationCount = function(node) { 2252 var current = Math.max(0, priv.getAnimationCount(node)); 2253 if (current>1) { 2254 node._puredom_animationCount = current - 1; 2255 } 2256 else { 2257 node._puredom_animationCount = null; 2258 self.removeClass(node, '_puredom_animating'); 2259 } 2260 }; 2261 /** @private */ 2262 priv.getAnimationCount = function(node) { 2263 return parseInt(node._puredom_animationCount, 10) || 0; 2264 }; 2265 2266 2267 2268 /** Destroy and cleanup puredom. 2269 * @private 2270 */ 2271 priv.unload = function() { 2272 priv.wrappedEventListener.reset(); 2273 self.selectorEngine.clearCache(); 2274 priv._nodeToIdList = {}; 2275 setTimeout(function() { 2276 window.puredom = priv = objConstructor = getSupportedTextContentProperty = null; 2277 }, 10); 2278 }; 2279 2280 2281 /** Create or retrieve one or more elements based on a query. <br /> 2282 * If query begins with "<" or is an object, a new element is contructed based on that information. <br /> 2283 * If the query is a CSS selector, DOM nodes matching that selector are returned. 2284 * @param {String|Object} query A CSS selector (retrieval), or a DOM description (creation). 2285 * @param {Boolean} [log=false] If true, query process will be logged to the console. 2286 * @returns {puredom.NodeSelection} selection 2287 */ 2288 self.el = function(query, log) { 2289 var results, type; 2290 if (query) { 2291 type = typeof query; 2292 if (type==='string' && query.charAt(0)!=='<') { 2293 if (log===true) { 2294 self.log('query is a CSS selector', query, type); 2295 } 2296 if (query==='body') { 2297 results = document.body; 2298 } 2299 else if (query==='html') { 2300 results = document.documentElement || document; 2301 } 2302 else { 2303 results = self.selectorEngine.query(query, arguments[1]); 2304 } 2305 } 2306 else if (self.isArray(query)) { 2307 results = []; 2308 for (var x=0; x<query.length; x++) { 2309 Array.prototype.splice.apply(results, [0,0].concat(self.el(query[x])._nodes) ); 2310 } 2311 } 2312 else if (type==='string' || (type==='object' && !query.nodeName && query!==window)) { 2313 if (log===true) { 2314 self.log('query is an HTML fragment', query, type); 2315 } 2316 results = self.createElement.apply(self, arguments); 2317 } 2318 else if (query.constructor===self.NodeSelection) { 2319 if (log===true) { 2320 self.log('query is already a NodeSelection', query.constructor+'', query.constructor.name); 2321 } 2322 return query; 2323 } 2324 else if (query.nodeName || query===window) { 2325 if (log===true) { 2326 self.log('query is an HTML element', query, type); 2327 } 2328 if (query===window) { 2329 query = document.documentElement || document; 2330 } 2331 results = query; 2332 } 2333 } 2334 return new self.NodeSelection(results); 2335 }; 2336 2337 2338 2339 /** Get a selection ({@link puredom.NodeSelection}) containing the node with the given UUID. <br /> 2340 * UniqueIDs can be retrieved using {@link puredom.NodeSelection#uuid}. 2341 * @param {String} uuid Unique node ID, such as one derived from {@link puredom.NodeSelection#uuid}. 2342 * @returns {puredom.NodeSelection} selection A selection ({@link puredom.NodeSelection}) containing the referenced node. 2343 */ 2344 self.node = function(uuid) { 2345 return new self.NodeSelection(priv.idToNode(uuid)); 2346 }; 2347 2348 2349 // Events 2350 function cancelEvent(e) { 2351 if (e.stopPropagation) { 2352 e.stopPropagation(); 2353 } 2354 try { 2355 if (e.cancelBubble && e.cancelBubble.call) { 2356 e.cancelBubble(); 2357 } 2358 else { 2359 e.cancelBubble = true; 2360 } 2361 } catch(err) {} 2362 } 2363 2364 /** @class Represents a DOM event. 2365 * @name puredom.DOMEvent 2366 */ 2367 self.DOMEvent = function DOMEvent(type) { 2368 if (type) { 2369 this.type = type.replace(/^on/gi,''); 2370 } 2371 }; 2372 self.DOMEvent.displayName = 'puredom.DOMEvent'; 2373 2374 self.extend(self.DOMEvent.prototype, /** @lends puredom.DOMEvent# */ { 2375 2376 /** Which mouse button or key generated the action (if applicable) 2377 * @type Number 2378 */ 2379 which : null, 2380 2381 /** The triggered event type (with no "on"-prefix) 2382 * @type String 2383 */ 2384 type : '', 2385 2386 /** The DOM node that originated the event. 2387 * @type {Element} 2388 */ 2389 target : null, 2390 2391 /** When available, refers to a DOM node that aided in originating the event (such as the DOM node the mouse was *previously* overtop of). 2392 * @type {Element} 2393 */ 2394 relatedTarget : null, 2395 2396 /** Prevent the event's browser-default action from occurring. 2397 * @function 2398 */ 2399 preventDefault : function() { 2400 this.defaultPrevented = true; 2401 }, 2402 2403 /** Stop bubbling. 2404 * @function 2405 */ 2406 stopPropagation : function(){ 2407 this.propagationStopped = true; 2408 this.bubblingCancelled = true; 2409 }, 2410 2411 /** Stop bubbling, prevent the browser-default action and set the event's returned value to false. 2412 * @function 2413 */ 2414 cancel : function() { 2415 this.preventDefault(); 2416 this.stopPropagation(); 2417 this.returnValue = false; 2418 return false; 2419 }, 2420 2421 /** Represents the handler's return value. 2422 * @type Boolean 2423 */ 2424 returnValue : true, 2425 2426 /** The contained raw DOM Event. 2427 * @type DOMEvent 2428 */ 2429 originalEvent : null, 2430 2431 /** The timestamp when the event was triggered. 2432 * @type Number 2433 */ 2434 timeStamp : null 2435 }); 2436 2437 /** Alias of {@link puredom.DOMEvent#stopPropagation}, provided only for backward compatibility. 2438 * @function 2439 */ 2440 self.DOMEvent.prototype.cancelBubble = self.DOMEvent.prototype.stopPropagation; 2441 2442 /** Alias of {@link puredom.DOMEvent#cancel}, provided only for compatibility with other notable DOM libraries. 2443 * @function 2444 */ 2445 self.DOMEvent.prototype.stop = self.DOMEvent.prototype.cancel; 2446 2447 /** @deprecated 2448 * @private 2449 */ 2450 self.DOMEvent.prototype.prevent = self.DOMEvent.prototype.cancel; 2451 2452 2453 2454 2455 /** @private */ 2456 priv.wrappedEventListener = { 2457 list : [], 2458 none : {}, 2459 2460 /** @private */ 2461 summary : function() { 2462 for (var x=0; x<this.list.length; x++) { 2463 self.log( priv.idToNode(this.list[x].target), '.on', this.list[x].type, ' -> ', (this.list[x].handler.displayName || this.list[x].handler.name) ); 2464 } 2465 }, 2466 2467 /** @private */ 2468 reset : function(removeEvents) { 2469 var i, evt; 2470 if (removeEvents===true) { 2471 for (i=this.list.length; i--; ) { 2472 evt = this.list[i]; 2473 this.list[i] = this.none; 2474 self.removeEvent(priv.idToNode(evt.target), evt.type, evt.selector, evt.wrappedHandler); 2475 this.unsetRefs(evt); 2476 window.killCount = (window.killCount || 0) + 1; 2477 } 2478 } 2479 this.list.splice(0, this.list.length); 2480 }, 2481 2482 /** @private */ 2483 destroyObjHandlers : function(obj) { 2484 var i, evt, 2485 objId = priv.nodeToId(obj); 2486 for (i=this.list.length; i--; ) { 2487 evt = this.list[i]; 2488 if (evt.target===objId) { 2489 this.unsetRefs(evt); 2490 this.list.splice(i, 1); 2491 self.removeEvent(obj, evt.type, evt.selector, evt.wrappedHandler); 2492 window.killCount = (window.killCount || 0) + 1; 2493 } 2494 } 2495 }, 2496 2497 /** @private */ 2498 get : function(type, handler, obj, selector, andDestroy) { 2499 var i, evt; 2500 selector = selector || null; 2501 obj = priv.nodeToId(obj); 2502 for (i=this.list.length; i--; ) { 2503 evt = this.list[i]; 2504 if (evt.target===obj && evt.handler===handler && evt.selector===selector && evt.type===type) { 2505 handler = evt.wrappedHandler; 2506 if (andDestroy===true) { 2507 this.list.splice(i, 1); 2508 window.killCount = (window.killCount || 0) + 1; 2509 this.unsetRefs(evt); 2510 } 2511 break; 2512 } 2513 } 2514 // fall back to the original handler 2515 return handler; 2516 }, 2517 2518 /** @private */ 2519 unsetRefs : function(item) { 2520 item.wrappedHandler.type = null; 2521 item.wrappedHandler.handler = null; 2522 item.wrappedHandler.target = null; 2523 }, 2524 2525 /** @private */ 2526 internalFireEvent : function(event) { 2527 var target = priv.nodeToId(event.target), 2528 type = event.type.replace(/^on/gm,''), 2529 i, item, returnValue; 2530 for (i=this.list.length; i--; ) { 2531 item = this.list[i]; 2532 if (item.target===target && item.type===type) { 2533 returnValue = item.handler.call(event.target, event); 2534 if (returnValue===false) { 2535 break; 2536 } 2537 } 2538 } 2539 }, 2540 2541 /** @private */ 2542 create : function(type, handler, obj, selector) { 2543 selector = selector || null; 2544 var evt = { 2545 type : type, 2546 target : priv.nodeToId(obj), 2547 selector : selector, 2548 handler : handler, 2549 /** @ignore */ 2550 wrappedHandler : function wrappedHandler(e) { 2551 var handler = wrappedHandler.handler, 2552 type = (wrappedHandler.type || e.type).toLowerCase().replace(/^on/,''), 2553 originalTarget = this!==window ? this : (priv && priv.idToNode(wrappedHandler.target)), 2554 fireTarget, event, i, 2555 d = { 2556 isInSelf : false, 2557 doPreventDefault : false, 2558 doCancelBubble : false, 2559 doStopPropagation : false, 2560 e : e || window.event, 2561 ret : null, 2562 /** @ignore */ 2563 end : function() { 2564 d.end = null; 2565 e = event = d = handler = type = originalTarget = null; 2566 return this.ret; 2567 } 2568 }; 2569 if (!priv || !priv.idToNode) { 2570 self.log("target:<"+e.target.nodeName+' class="'+e.target.className+'" id="'+e.target.id+'"' + "> , type:"+type+"/"+e.type); 2571 } 2572 e = d.e; 2573 2574 2575 event = self.extend(new self.DOMEvent(type), { 2576 which : e.which, 2577 target : e.target || e.srcElement || originalTarget || document.body, 2578 relatedTarget : e.relatedTarget || (type==='mouseout' ? e.toElement : e.fromElement), 2579 returnValue : true, 2580 originalEvent : e, 2581 timeStamp : e.timeStamp || Date.now() 2582 }); 2583 2584 // NOTE: For convenience, copy extra properties from the original event. 2585 // This is mostly used for custom events to pass custom properties. 2586 for (i in e) { 2587 if (!event.hasOwnProperty(i) && typeof e[i]!=='function' && i!==i.toUpperCase() && i!=='layerX' && i!=='layerY') { 2588 event[i] = e[i]; 2589 } 2590 } 2591 2592 if (!event.target) { 2593 self.log('Event target doesn\'t exist for type "'+event.type+'": ',event.target,', srcElement=',e.srcElement); 2594 } 2595 2596 if (e.type==='touchend' && priv._lastTouchPos) { 2597 event.pageX = priv._lastTouchPos.pageX; 2598 event.pageY = priv._lastTouchPos.pageY; 2599 } 2600 else if (e.touches && e.touches[0]) { 2601 event.pageX = e.touches[0].pageX; 2602 event.pageY = e.touches[0].pageY; 2603 priv._lastTouchPos = { 2604 pageX : event.pageX, 2605 pageY : event.pageY 2606 }; 2607 } 2608 else if (e.pageX || e.pageX===0 || e.clientX || e.clientX===0) { 2609 event.pageX = e.pageX || (e.clientX+document.body.offsetLeft); 2610 event.pageY = e.pageY || (e.clientY+document.body.offsetTop); 2611 } 2612 if (type.indexOf('key')>-1 || e.keyCode || e.charCode) { 2613 event.keyCode = e.keyCode || e.which; 2614 event.charCode = e.keyCode || e.charCode || e.which; // this should never be used 2615 event.which = e.which; 2616 } 2617 if (type.indexOf('mouse')>-1 || type.indexOf('click')>-1 || (e.button!==null && e.button!==undefined)) { 2618 event.button = typeof e.button=='number' ? e.button : e.which; 2619 } 2620 2621 // fix safari #textnode target bug 2622 if (event.target && event.target.nodeType===3 && originalTarget.nodeType!==3) { 2623 event.target = event.target.parentNode; 2624 } 2625 2626 // allow filtering by CSS selector 2627 var sel = wrappedHandler.selector, 2628 selEls, isInSelector; 2629 if (sel && typeof sel==='string') { 2630 selEls = self.selectorEngine.query(sel, { 2631 within : originalTarget 2632 }); 2633 } 2634 2635 // is the capturing node within the original handler context? 2636 d.searchNode = !selEls && event.relatedTarget || event.target; 2637 do { 2638 if (selEls) { 2639 if (selEls.indexOf(d.searchNode) !== -1 ) { 2640 isInSelector = true; 2641 fireTarget = d.searchNode; 2642 break; 2643 } 2644 continue; 2645 } 2646 if (d.searchNode===originalTarget) { 2647 d.isInSelf = true; 2648 break; 2649 } 2650 } while(d.searchNode && (d.searchNode=d.searchNode.parentNode) && d.searchNode!==document); 2651 2652 if (selEls && !isInSelector) { 2653 return; 2654 } 2655 2656 // Don't fire mouseout events when the mouse is moving in/out a child node of the handler context element 2657 if ((type!=='mouseover' && type!=='mouseout') || !d.isInSelf) { 2658 if (handler && handler.call) { 2659 event.currentTarget = fireTarget || originalTarget; 2660 d.handlerResponse = handler.call(fireTarget || originalTarget, event); 2661 } 2662 else { 2663 // NOTE: Turn this on and fix the IE bug. 2664 //console.log('Handler not a function: ', self.typeOf(handler), ' handler=', handler, ' type=', type); 2665 } 2666 2667 event.returnValue = d.handlerResponse!==false && event.returnValue!==false; 2668 if (event.defaultPrevented) { 2669 event.returnValue = e.returnValue = false; 2670 if (e.preventDefault) { 2671 e.preventDefault(); 2672 } 2673 } 2674 if (event.bubblingCancelled===true || event.propagationStopped===true || event.cancelBubble===true) { 2675 cancelEvent(e); 2676 } 2677 d.ret = event.returnValue; 2678 } 2679 else { 2680 d.ret = true; 2681 } 2682 return d.end(); 2683 } 2684 }; 2685 evt.wrappedHandler.displayName = 'wrappedEventHandler_'+type; 2686 evt.wrappedHandler.handler = handler; 2687 evt.wrappedHandler.type = type; 2688 evt.wrappedHandler.target = evt.target; // an ID, not the node itself 2689 evt.wrappedHandler.selector = selector; 2690 this.list.push(evt); 2691 obj = type = handler = evt = null; 2692 return this.list[this.list.length-1].wrappedHandler; 2693 } 2694 2695 }; 2696 2697 2698 2699 2700 /** Get a String description of the subject for an event operation 2701 * @private 2702 * @param {Any} subject An object of any type. 2703 */ 2704 priv.getSubjectDescription = function(obj) { 2705 return (obj.nodeName ? (self.el(obj)+'') : (obj.constructor && obj.constructor.name || obj.name || obj)) + ''; 2706 }; 2707 2708 2709 /** Automatically translate DOM event types from [key] to [value] when registering or removing listeners. <br /> 2710 * Also falsifies corresponding puredom-wrapped events' type fields. 2711 * @object 2712 */ 2713 self.eventTypeMap = {}; 2714 if (priv.support.webkitMultitouch) { 2715 self.extend(self.eventTypeMap, { 2716 'mousedown' : 'touchstart', 2717 'mousemove' : 'touchmove', 2718 'mouseup' : 'touchend' 2719 }); 2720 } 2721 2722 /** Add an event listener to a DOM node for the given event type. 2723 * @private 2724 * @param {HTMLElement} obj An element to add the event listener to. 2725 * @param {String} type A type of event to register the listener for. 2726 * @param {String} [selector] Optionally call handler only if the target matches a CSS selector. 2727 * @param {Function} handler The listener function to register. Gets passed <code>({Event} event)</code>. 2728 */ 2729 self.addEvent = function(obj, type, selector, fn) { 2730 var x, origType; 2731 if (typeof selector==='function') { 2732 fn = selector; 2733 selector = null; 2734 } 2735 if (obj) { 2736 if (self.typeOf(type)==='string' && type.indexOf(',')>-1) { 2737 type = type.replace(/\s/gm,'').split(','); 2738 } 2739 if (self.isArray(type)) { 2740 for (x=0; x<type.length; x++) { 2741 self.addEvent(obj, type[x], selector, fn); 2742 } 2743 return true; 2744 } 2745 origType = type = (type+'').toLowerCase().replace(/^\s*(on)?(.*?)\s*$/gim,'$2'); 2746 2747 if (typeof type!=='string' || !fn || !fn.call) { 2748 self.log('Attempted to add event with invalid type or handler:', { 2749 type : type, 2750 handler : fn+'', 2751 subject : priv.getSubjectDescription(obj) 2752 }); 2753 return; 2754 } 2755 2756 if (self.eventTypeMap.hasOwnProperty(type)) { 2757 type = self.eventTypeMap[type]; 2758 } 2759 2760 fn = priv.wrappedEventListener.create(origType, fn, obj, selector); 2761 if (obj.attachEvent) { 2762 obj.attachEvent('on' + type, fn); 2763 } 2764 else if (obj.addEventListener) { 2765 obj.addEventListener(type, fn, false); 2766 self._eventCount = (self._eventCount || 0) + 1; 2767 } 2768 } 2769 }; 2770 2771 2772 /** Remove an event listener from a DOM node. 2773 * @private 2774 * @param {Element} obj An element to remove the event listener from. 2775 * @param {String} type The event type of the listener to be removed. 2776 * @param {String} [selector] Optionally call handler only if the target matches a CSS selector. 2777 * @param {Function} handler The listener function to remove. 2778 */ 2779 self.removeEvent = function(obj, type, selector, fn) { 2780 var x, origType; 2781 if (typeof selector==='function') { 2782 fn = selector; 2783 selector = null; 2784 } 2785 if (obj) { 2786 if (self.typeOf(type)==='string' && type.indexOf(',')>-1) { 2787 type = type.replace(/\s/gm,'').split(','); 2788 } 2789 if (self.isArray(type)) { 2790 for (x=0; x<type.length; x++) { 2791 self.removeEvent(obj, type[x], selector, fn); 2792 } 2793 return true; 2794 } 2795 origType = type = (type+'').toLowerCase().replace(/^\s*(on)?(.*?)\s*$/gim,'$2'); 2796 2797 if (typeof type!=='string' || !fn || !fn.call) { 2798 self.log('Attempted to remove event with invalid type or handler:', { 2799 type : type, 2800 handler : fn+'', 2801 subject : priv.getSubjectDescription(obj) 2802 }); 2803 return; 2804 } 2805 2806 if (self.eventTypeMap.hasOwnProperty(type)) { 2807 type = self.eventTypeMap[type]; 2808 } 2809 2810 fn = priv.wrappedEventListener.get(origType, fn, obj, selector, true); 2811 if (obj.detachEvent) { 2812 obj.detachEvent('on' + type, fn); 2813 } 2814 else if (obj.removeEventListener) { 2815 try { 2816 obj.removeEventListener(type, fn, false); 2817 self._eventCount = (self._eventCount || 0) - 1; 2818 } catch(err) {} 2819 } 2820 } 2821 }; 2822 2823 2824 /** When called from within an event handler and passed the DOM Event, cancels the event, prevents the event's default action, and returns false.<br /> 2825 * <strong>Note:</strong> puredom-wrapped Event objects have a cancel() method that does this for you. 2826 * @private 2827 * @param {Event} event A DOM Event. 2828 * @returns false 2829 */ 2830 self.cancelEvent = function(e) { 2831 e = e || window.event; 2832 if (e) { 2833 if (e.preventDefault) { 2834 e.preventDefault(); 2835 } 2836 if (e.stopPropagation) { 2837 e.stopPropagation(); 2838 } 2839 try { 2840 e.cancelBubble = true; 2841 } catch(err) {} 2842 e.returnValue = false; 2843 } 2844 return false; 2845 }; 2846 2847 2848 /* 2849 priv.checkEventTypeSupport = function(s, type) { 2850 var da = !!('ondataavailable' in s), 2851 support = false, 2852 handler; 2853 handler = function(e) { 2854 support = e && e.test_prop==='test_val'; 2855 }; 2856 self.addEvent(type, handler); 2857 self.fireEvent({ 2858 target : s, 2859 type : type, 2860 test_prop : 'test_val' 2861 }); 2862 self.removeEvent(type, handler); 2863 handler = s = type = null; 2864 return support; 2865 }; 2866 priv.supportsCustomEventTypes = priv.checkEventTypeSupport(document.createElement('span'), 'custom:event_typetest'); 2867 */ 2868 2869 2870 /** Fire an event on a DOM node. 2871 * @private 2872 * @param {Object} options An event options object, having at least a "type" and "target". 2873 * @param {String} [options.type] The event type to fire. "on"- prefixes get stripped. 2874 * @param {String} [options.target] A DOM node to fire the event on. 2875 * @returns {Object} result description, with {Boolean} "rval" (false if any handler returned false), and {Boolean} "preventDefault" (default was prevented). 2876 */ 2877 self.fireEvent = function (options) { 2878 var evt, rval, p, preventDefault, initError; 2879 options = options || {}; 2880 if (document.createEventObject) { // IE 2881 options = self.extend({}, options); 2882 options.type = 'on'+options.type.toLowerCase().replace(/^on/,''); 2883 //priv.checkEventTypeSupport(options.target, options.type); 2884 2885 try { 2886 evt = document.createEventObject(); 2887 }catch(err) { 2888 initError = true; 2889 } 2890 if (!initError) { 2891 for (p in options) { 2892 if (options.hasOwnProperty(p)) { 2893 try { 2894 evt[p] = options[p]; 2895 }catch(err){} 2896 } 2897 } 2898 try { 2899 rval = options.target.fireEvent(options.type, evt); 2900 preventDefault = evt.preventDefault===true; 2901 } catch(err) { 2902 initError = true; 2903 } 2904 } 2905 if (initError) { 2906 //self.log('Error: Could not fire "' + options.type + '" event in IE. Falling back to internal implementation.'); 2907 if (priv.wrappedEventListener.internalFireEvent) { 2908 priv.wrappedEventListener.internalFireEvent(options); 2909 } 2910 } 2911 2912 } 2913 else { // Everything else 2914 evt = document.createEvent("HTMLEvents"); 2915 evt.initEvent(options.type.toLowerCase().replace(/^on/,''), true, true); 2916 for (p in options) { 2917 if (options.hasOwnProperty(p)) { 2918 try { 2919 evt[p] = options[p]; 2920 } catch(err) {} 2921 } 2922 } 2923 rval = !options.target.dispatchEvent(evt); 2924 preventDefault = evt.preventDefault===true; 2925 } 2926 2927 return { 2928 evt : evt, 2929 preventDefault : preventDefault, 2930 rval : rval 2931 }; 2932 }; 2933 2934 2935 2936 2937 2938 /** @private */ 2939 priv._nodeToIdIndex = 0; 2940 2941 /** @private */ 2942 priv._nodeToIdList = {}; 2943 2944 /** Get the UUID value for a given node. If the node does not yet have a UUID, it is assigned one. 2945 * @private 2946 */ 2947 priv.nodeToId = function(el) { 2948 var search, id; 2949 if (el===window) { 2950 return '_td_autoid_window'; 2951 } 2952 else if (el===document.documentElement) { 2953 return '_td_autoid_html'; 2954 } 2955 else if (el===document.body) { 2956 return '_td_autoid_body'; 2957 } 2958 search = (/\s_td_autoid_([0-9]+)\s/gm).exec(' ' + el.className + ' '); 2959 if (search && search[1]) { 2960 id = search[1]; 2961 } 2962 else { 2963 priv._nodeToIdIndex += 1; 2964 id = priv._nodeToIdIndex + ''; 2965 self.addClass(el, '_td_autoid_'+id); 2966 } 2967 priv.ensureNodeIdListing(el, id); 2968 return id; 2969 }; 2970 2971 /** Get the node with the given UUID. 2972 * @private 2973 */ 2974 priv.idToNode = function(id) { 2975 var search, 2976 listed = priv._nodeToIdList[id+''], 2977 node; 2978 if (id==='_td_autoid_window') { 2979 return window; 2980 } 2981 else if (id==='_td_autoid_html') { 2982 return document.documentElement; 2983 } 2984 else if (id==='_td_autoid_body') { 2985 return document.body; 2986 } 2987 if (listed) { 2988 node = self.selectorEngine.query(listed); 2989 if (!(/\s_td_autoid_[0-9]+\s/gm).exec(' ' + node.className + ' ')) { 2990 node = null; 2991 } 2992 } 2993 if (!node) { 2994 search = self.selectorEngine.query('._td_autoid_'+id); 2995 node = search && search[0]; 2996 if (node) { 2997 priv.ensureNodeIdListing(node, id); 2998 } 2999 } 3000 return node || false; 3001 }; 3002 3003 /** @private */ 3004 priv.ensureNodeIdListing = function(node, id) { 3005 var idAttr; 3006 if (node.getAttribute) { 3007 idAttr = node.getAttribute('id'); 3008 if (!idAttr) { 3009 idAttr = '_td_autoid_'+id; 3010 node.setAttribute('id', idAttr); 3011 } 3012 priv._nodeToIdList[id] = '#'+idAttr; 3013 } 3014 }; 3015 3016 /** @private */ 3017 priv.removeNodeUID = function(node) { 3018 var id = node.getAttribute('id'); 3019 if (id && id.match(/^_td_autoid_[0-9]+$/g)) { 3020 if (node.removeAttribute) { 3021 node.removeAttribute('id'); 3022 } 3023 else { 3024 node.setAttribute('id', ''); 3025 } 3026 } 3027 if (node.className) { 3028 node.className = node.className.replace(/(^|\b)_td_autoid_[0-9]+(\b|$)/gim,''); 3029 } 3030 }; 3031 3032 3033 3034 /** @namespace Shim for HTML5's animationFrame feature. 3035 * @name puredom.animationFrame 3036 * @public 3037 */ 3038 self.animationFrame = (function() { 3039 /** @ignore */ 3040 var self = /** @lends puredom.animationFrame */ { 3041 nativeSupport : true, 3042 manualFramerate : 11 3043 }, 3044 perf = window.performance, 3045 prefix; 3046 3047 if (window.requestAnimationFrame) { 3048 prefix = ''; 3049 } 3050 else if (window.mozRequestAnimationFrame) { 3051 prefix = 'moz'; 3052 } 3053 else if (window.webkitRequestAnimationFrame) { 3054 prefix = 'webkit'; 3055 } 3056 else { 3057 self.nativeSupport = false; 3058 } 3059 3060 /** @ignore */ 3061 function now() { 3062 if (perf && perf.now) { 3063 return perf.now(); 3064 } 3065 return Date.now(); 3066 } 3067 3068 if (self.nativeSupport) { 3069 3070 /** Defer execution of an animation function so it occurs during the next rendering cycle. 3071 * @param {Function} f A function to call during the next animation frame. 3072 * @name puredom.animationFrame.getTimer 3073 * @function 3074 */ 3075 self.getTimer = function(f) { 3076 return window[ (prefix ? (prefix+'R') : 'r') + 'equestAnimationFrame'](f); 3077 }; 3078 3079 /** Unregister a deferred animation function. 3080 * @param {String} identifier A timer identifier, such as one obtained from {@link puredom.animationFrame.getTimer}. 3081 * @name puredom.animationFrame.cancelTimer 3082 * @function 3083 */ 3084 self.cancelTimer = function(t) { 3085 window[ (prefix ? (prefix+'C') : 'c') + 'ancelRequestAnimationFrame'](t); 3086 }; 3087 3088 /** Get the start time (timestamp, in milliseconds) of the current animation. 3089 * @param {String} identifier A timer identifier, such as one obtained from {@link puredom.animationFrame.getTimer}. 3090 * @name puredom.animationFrame.getStartTime 3091 * @function 3092 */ 3093 self.getStartTime = function(t) { 3094 return window[ (prefix ? (prefix+'A') : 'a') + 'nimationStartTime'] || now(); 3095 }; 3096 3097 } 3098 else { 3099 3100 /** @ignore */ 3101 self.getTimer = function(f) { 3102 return setTimeout(function() { 3103 f( now() ); 3104 f = null; 3105 }, self.manualFramerate); 3106 }; 3107 3108 /** @ignore */ 3109 self.cancelTimer = function(t) { 3110 clearTimeout(t); 3111 }; 3112 3113 /** @ignore */ 3114 self.getStartTime = function(t) { 3115 return now(); 3116 }; 3117 3118 } 3119 3120 return self; 3121 }()); 3122 3123 3124 3125 3126 3127 /** Set the opacity of an element. 3128 * @private 3129 * @param {HTMLElement} el A DOM node to which an opacity value should be applied. 3130 * @param {Number} opac An integer opacity between 0 and 100, or a decimal value between 0.01 and 1. 3131 */ 3132 self.setOpacity = function(el, opac) { 3133 if (opac<=1 && opac>0) { 3134 opac = opac * 100; 3135 } 3136 opac = Math.round(opac); 3137 if (opac<100) { 3138 el.style.opacity = parseFloat(opac/100).toFixed(2); 3139 el.style.filter = "alpha(opacity=" + opac + ")"; 3140 } 3141 else { 3142 el.style.opacity = null; 3143 el.style.filter = null; 3144 } 3145 }; 3146 3147 3148 3149 /** Apply a Microsoft filter value to an element, retaining existing applied filters. <br /> 3150 * See: {@link http://msdn.microsoft.com/en-us/library/ms532853(v=vs.85).aspx} 3151 * @private 3152 * @param {HTMLElement} el An element to apply a filter to. 3153 * @param {String} type A valid filter type. 3154 * @param {String} value The value to set for the given filter type. 3155 */ 3156 self.applyMsFilter = function(el, type, value) { 3157 var item, filters, 3158 valueStr = '', 3159 ch = priv.support.filterProperty==='MsFilter' ? '"' : '', 3160 p, a, i; 3161 type = type.charAt(0).toUpperCase() + type.substring(1); 3162 if (typeof value==='string') { 3163 valueStr = value; 3164 value = {}; 3165 a = valueStr.replace(/\s*(,|=)\s* /gm,'$1').split(','); 3166 for (p=0; p<a.length; p++) { 3167 i = a[p].indexOf('='); 3168 value[a[p].substring(0,i)] = a[p].substring(i+1); 3169 } 3170 } 3171 else { 3172 for (p in value) { 3173 if (value.hasOwnProperty(p)) { 3174 valueStr += ', '+p+'='+value[p]; 3175 } 3176 } 3177 valueStr = valueStr.replace(',',''); 3178 } 3179 try { 3180 item = el.filters && el.filters.item && (el.filters.item('DXImageTransform.Microsoft.'+type) || el.filters.item(type)); 3181 }catch(err){} 3182 if (item) { 3183 for (p in value) { 3184 if (value.hasOwnProperty(p) && p!=='enabled') { 3185 item[p] = value[p]; 3186 } 3187 } 3188 item.enabled = value.enabled?1:0; 3189 } 3190 else { 3191 filters = el.style.MsFilter || el.style.filter || ''; 3192 filters += ' ' + (self.nodeStyle(el, 'MsFilter') || self.nodeStyle(el, 'filter') || ''); 3193 if (filters) { 3194 filters = filters.replace(new RegExp('(^|\\s|\\))"?((progid\\:)?DXImageTransform\\.Microsoft\\.)?'+type+'\\s*?\\(.*?\\)"?\\s*?','gim'),'$1') + ' '; 3195 } 3196 el.style[priv.support.filterProperty] = filters + ch+'progid:DXImageTransform.Microsoft.'+type+'('+valueStr+')'+ch; 3197 } 3198 // hasLayout 3199 if (!el.style.zoom) { 3200 el.style.zoom = 1; 3201 } 3202 }; 3203 3204 3205 3206 3207 /** @private */ 3208 var cssPropCache = {}; 3209 /** @private */ 3210 function getPrefixedCssProperty(prop) { 3211 var ret = prop, 3212 p = cssPropCache[prop]; 3213 if (p) { 3214 return p; 3215 } 3216 if (vendorCssPrefixJS && prop.substring(0, vendorCssPrefixJS.length)!==vendorCssPrefixJS) { 3217 p = vendorCssPrefixJS + prop.charAt(0).toUpperCase() + prop.substring(1); 3218 if (p in document.body.style) { 3219 ret = p; 3220 } 3221 } 3222 cssPropCache[prop] = ret; 3223 return ret; 3224 } 3225 3226 3227 /** Apply key-value CSS styles to an element. 3228 * @param {HTMLElement} el An element whose style should be updated. 3229 * @param {Object} properties An Object where keys are CSS properties and values are the corresponding CSS values to apply. 3230 * @private 3231 */ 3232 self.applyCss = function(el, properties) { 3233 var x, cx, d, p, ieOpac, vp; 3234 properties = properties || {}; 3235 for (x in properties) { 3236 if (properties.hasOwnProperty(x)) { 3237 cx = self.getStyleAsCSS(x); 3238 cx = cx.replace(/^\-(moz|webkit|ms|o|vendor)\-/gim, vendorCssPrefix+'-'); 3239 cx = self.getStyleAsProperty(cx); 3240 cx = getPrefixedCssProperty(cx); 3241 if (!priv.support.filters) { 3242 el.style[cx] = properties[x]; 3243 } 3244 else { 3245 if (cx==='opacity') { 3246 ieOpac = Math.round( parseFloat(properties[x])*100 ); 3247 if (ieOpac<100) { 3248 self.applyMsFilter(el, 'alpha', { 3249 enabled : true, 3250 opacity : ieOpac 3251 }); 3252 } 3253 else { 3254 self.applyMsFilter(el, 'alpha', { 3255 enabled : false 3256 }); 3257 } 3258 } 3259 else if (cx==='--box-shadow') { 3260 d = properties[x].match(/\b(\#[0-9af]{3}[0-9af]{3}?|rgba?\([0-9\,\s]+\))\b/gim); 3261 d = d && d[0] || ''; 3262 p = (' '+properties[x]+' ').replace(d,'').replace(/\s+/m,' ').split(' ').slice(1,4); 3263 self.applyMsFilter(el, 'glow', { 3264 Color : d, 3265 Strength : Math.round(p[3].replace(/[^0-9\-\.]/gim,'')) 3266 }); 3267 } 3268 } 3269 } 3270 } 3271 }; 3272 3273 /** Convert a CSS property name to it's CamelCase equivalent. 3274 * @private 3275 */ 3276 self.getStyleAsProperty = function(style) { 3277 if (typeof style!=='string') { 3278 return null; 3279 } 3280 style = style.replace(/\-[a-z0-9]/gim, priv.styleAsPropReplacer); 3281 // fixes "webkitFoo" -> "WebkitFoo" etc 3282 style = style.replace(/^(webkit|moz|ms|o)[A-Z]/gm, priv.styleAsPropVendorPrefixReplacer); 3283 return style; 3284 }; 3285 /** @private */ 3286 priv.styleAsPropReplacer = function(s) { 3287 return s.charAt(1).toUpperCase(); 3288 }; 3289 /** @private */ 3290 priv.styleAsPropVendorPrefixReplacer = function(s) { 3291 return s.charAt(0).toUpperCase()+s.substring(1); 3292 }; 3293 3294 /** Convert a CSS property name to it's css-dash-separated equivalent. 3295 * @private 3296 */ 3297 self.getStyleAsCSS = function(style) { 3298 return typeof style==='string' && style.replace(/\-*([A-Z])/gm, '-$1').toLowerCase() || null; 3299 }; 3300 3301 /** Parse a CSS String and return an Object representation, converting `-css-keys` to `jsKeys`. 3302 * @param {String} css 3303 * @param {Boolean} [camelKeys=true] If `false`, keys will be left untouched. 3304 * @private 3305 */ 3306 priv.parseCSS = function(css, camelKeys) { 3307 var tokenizer = /\s*([a-z\-]+)\s*:\s*([^;]*?)\s*(?:;|$)/gi, 3308 obj, token, key; 3309 if (css) { 3310 obj = {}; 3311 tokenizer.lastIndex = 0; 3312 while ((token=tokenizer.exec(css))) { 3313 key = token[1]; 3314 if (camelKeys!==false) { 3315 key = self.getStyleAsProperty(key); 3316 } 3317 obj[key] = token[2]; 3318 } 3319 } 3320 return obj; 3321 }; 3322 3323 self._parseCss = priv.parseCSS; 3324 3325 /** Some intense CSS3 transitions wrapping, needed in order to support animating multiple 3326 * properties asynchronously with interjected transition modifications 3327 * @private 3328 */ 3329 self.getCssTransitions = function(el) { 3330 var transitions = {}, 3331 current = { 3332 properties : '-vendor-transition-property', 3333 durations : '-vendor-transition-duration', 3334 timingFunctions : '-vendor-transition-timing-function' 3335 }, 3336 p, x, durationStr, duration; 3337 for (p in current) { 3338 if (current.hasOwnProperty(p)) { 3339 current[p] = (self.nodeStyle(el, current[p]) || '').replace(/\s/,'').split(','); 3340 } 3341 } 3342 for (x=0; x<current.properties.length; x++) { 3343 if (current.properties[x] && current.properties[x]!=='null' && !(current.properties[x]==='all' && current.durations[x].match(/^[0\.ms]*$/))) { 3344 durationStr = current.durations[x] || current.durations[current.durations.length-1]; 3345 duration = parseFloat(durationStr.replace(/[^\-0-9\.]/gim,'')) || 0; 3346 if (!durationStr.match(/ms$/i)) { 3347 duration *= 1000; 3348 } 3349 transitions[self.getStyleAsProperty(current.properties[x])] = { 3350 duration : duration, 3351 timingFunction : current.timingFunctions[x] || current.timingFunctions[current.timingFunctions.length-1] || 'ease' 3352 }; 3353 } 3354 } 3355 3356 return transitions; 3357 }; 3358 3359 /** @private */ 3360 self.setCssTransitions = function(el, transitions) { 3361 var css = { 3362 '-vendor-transition-property' : [], 3363 '-vendor-transition-duration' : [], 3364 '-vendor-transition-timing-function' : [] 3365 }, 3366 p; 3367 3368 for (p in transitions) { 3369 if (transitions.hasOwnProperty(p) && transitions[p]) { 3370 css['-vendor-transition-property'].push(p.toLowerCase()); 3371 css['-vendor-transition-duration'].push((transitions[p].duration/1000).toFixed(3) + 's'); 3372 css['-vendor-transition-timing-function'].push(transitions[p].timingFunction || 'ease'); 3373 } 3374 } 3375 3376 for (p in css) { 3377 if (css.hasOwnProperty(p)) { 3378 css[p] = css[p].join(', '); 3379 } 3380 } 3381 3382 self.applyCss(el, css); 3383 }; 3384 3385 /** @private */ 3386 self.updateCssTransitions = function(el, transitionsToUpdate) { 3387 var transitions, p; 3388 if (transitionsToUpdate) { 3389 transitions = self.getCssTransitions(el); 3390 for (p in transitionsToUpdate) { 3391 if (transitionsToUpdate.hasOwnProperty(p)) { 3392 if (transitionsToUpdate[p]) { 3393 transitions[p] = transitionsToUpdate[p]; 3394 } 3395 else { 3396 delete transitions[p]; 3397 } 3398 } 3399 } 3400 self.setCssTransitions(el, transitions); 3401 } 3402 }; 3403 3404 3405 /** @private */ 3406 self.addClass = function(el, classes, remove) { 3407 var modified = false, 3408 list, index, i; 3409 if (classes) { 3410 if (classes.length===1) { 3411 classes = classes[0].split(' '); 3412 } 3413 else if (!self.isArray(classes)) { 3414 classes = classes.split(' '); 3415 } 3416 if (el.classList) { 3417 el.classList[remove ? 'remove' : 'add'].apply(el.classList, classes); 3418 return; 3419 } 3420 3421 list = (el.className || '').split(/\s+/); 3422 for (i=0; i<classes.length; i++) { 3423 index = list.indexOf(classes[i]); 3424 if (remove!==true && index===-1) { 3425 modified = true; 3426 list.push( classes[i] ); 3427 } 3428 else if (remove===true && index>-1) { 3429 modified = true; 3430 list.splice(index, 1); 3431 } 3432 } 3433 if (modified) { 3434 el.className = list.join(' '); 3435 } 3436 } 3437 }; 3438 3439 /** @private */ 3440 self.removeClass = function(el, classes) { 3441 return self.addClass(el, classes, true); 3442 }; 3443 3444 /** Get the current value of a CSS property from the given node. 3445 * @private 3446 */ 3447 self.nodeStyle = function(node, property) { 3448 var dashed = (property+'').replace(/[A-Z]/g, '-$0').toLowerCase(), 3449 camelized, 3450 filter, 3451 s, 3452 style; 3453 3454 dashed = dashed.replace(/^\-(moz|webkit|ms|o|vendor)\-/gim, vendorCssPrefix+'-'); 3455 camelized = dashed.replace(/\-[a-z]/gim, function (s) {return s.substring(1).toUpperCase();}); 3456 3457 if (dashed==='opacity' && priv.support.filters) { 3458 return node.filters.alpha && node.filters.alpha.enabled!==false && Math.round(node.filters.alpha.opacity)/100; 3459 } 3460 3461 if (node.style && node.style[camelized]) { 3462 style = node.style[camelized] || ''; 3463 } 3464 3465 else if (node.currentStyle && node.currentStyle[camelized]) { 3466 style = node.currentStyle[camelized] || node.currentStyle[dashed] || ''; 3467 } 3468 3469 else if (document.defaultView && document.defaultView.getComputedStyle) { 3470 s = document.defaultView.getComputedStyle(node, null); 3471 style = s && (s.getPropertyValue(dashed) || s.getPropertyValue(camelized)) || ''; 3472 } 3473 3474 else if (window.getComputedStyle) { 3475 s = window.getComputedStyle(node, null); 3476 style = s && (s.getPropertyValue(dashed) || s.getPropertyValue(camelized)) || ''; 3477 } 3478 3479 return style; 3480 }; 3481 3482 3483 /** Old alias of puredom.text.html() 3484 * @private 3485 */ 3486 self.htmlEntities = function(str, stripTags) { 3487 var filters = ['htmlEntities']; 3488 if (stripTags===true) { 3489 filters.push('stripTags'); 3490 } 3491 return self.text.filter(str, filters); 3492 }; 3493 3494 3495 /** Log to the browser console, if it exists. 3496 */ 3497 self.log = function() { 3498 var c = global.console; 3499 if (c && c.log) { 3500 c.log.apply(c, arguments); 3501 } 3502 }; 3503 3504 3505 3506 /** Add a new plugin method to {puredom.NodeSelection}. <br /> 3507 * When called, a plugin function gets passed the arguments supplied by the caller. <br /> 3508 * The value of <code>this</code> within the function is the selection ({@link puredom.NodeSelection}) it was called on. 3509 * @param {String} name A method name to define on {puredom.NodeSelection} 3510 * @param {Function} func The plugin method to define. 3511 */ 3512 self.addNodeSelectionPlugin = function(name, func) { 3513 if (!self.NodeSelection.prototype.hasOwnProperty(name)) { 3514 self.NodeSelection.prototype[name] = function() { 3515 var ret = func.apply(this, arguments); 3516 if (ret===null || ret===undefined) { 3517 return this; 3518 } 3519 return ret; 3520 }; 3521 } 3522 }; 3523 3524 3525 /** Called on DOM ready. 3526 * @private 3527 */ 3528 self.init = function() { 3529 if (!initialized) { 3530 initialized = true; 3531 3532 self.forEach(priv.oninit, function(i) { 3533 i.call(self, self); 3534 }); 3535 3536 self.fireEvent({ 3537 target : document.body, 3538 type : "puredomready", 3539 puredom : self 3540 }); 3541 } 3542 }; 3543 self.addEvent(document, "DOMContentLoaded", self.init); 3544 self.addEvent(window, "load", self.init); 3545 self.addEvent(window, "unload", priv.unload); 3546 3547 3548 /** Allows extensions to be included before the core. 3549 * @ignore 3550 */ 3551 (function() { 3552 if (previousSelf) { 3553 for (var x in previousSelf) { 3554 if (previousSelf.hasOwnProperty(x)) { 3555 self[x] = previousSelf[x]; 3556 } 3557 } 3558 previousSelf = null; 3559 } 3560 }()); 3561 3562 /** @private */ 3563 priv.puredom = function(i) { 3564 if (self.typeOf(i)==='function') { 3565 if (initialized===true) { 3566 i.call(self, self); 3567 } 3568 else { 3569 priv.oninit.push(i); 3570 } 3571 return self; 3572 } 3573 else { 3574 return self.el.apply(self, arguments); 3575 } 3576 }; 3577 3578 self.extend(self, baseSelf); 3579 self.toString = function(){return 'function puredom(){}';}; 3580 3581 this.puredom = global.puredom = self; 3582 3583 if (typeof define==='function' && define.amd) { 3584 define('puredom', function(){ return self; }); 3585 } 3586 if (typeof module==='object') { 3587 module.exports = self; 3588 } 3589 }(this, typeof global==='object' ? global : this)); 3590