| [ Index ] |
PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008] |
[Summary view] [Print] [Text view]
1 /* 2 Copyright (c) 2008, Yahoo! Inc. All rights reserved. 3 Code licensed under the BSD License: 4 http://developer.yahoo.net/yui/license.txt 5 version: 2.6.0 6 */ 7 /** 8 * The selector module provides helper methods allowing CSS3 Selectors to be used with DOM elements. 9 * @module selector 10 * @title Selector Utility 11 * @namespace YAHOO.util 12 * @requires yahoo, dom 13 */ 14 15 (function() { 16 /** 17 * Provides helper methods for collecting and filtering DOM elements. 18 * @namespace YAHOO.util 19 * @class Selector 20 * @static 21 */ 22 var Selector = function() {}; 23 24 var Y = YAHOO.util; 25 26 var reNth = /^(?:([-]?\d*)(n){1}|(odd|even)$)*([-+]?\d*)$/; 27 28 Selector.prototype = { 29 /** 30 * Default document for use queries 31 * @property document 32 * @type object 33 * @default window.document 34 */ 35 document: window.document, 36 /** 37 * Mapping of attributes to aliases, normally to work around HTMLAttributes 38 * that conflict with JS reserved words. 39 * @property attrAliases 40 * @type object 41 */ 42 attrAliases: { 43 }, 44 45 /** 46 * Mapping of shorthand tokens to corresponding attribute selector 47 * @property shorthand 48 * @type object 49 */ 50 shorthand: { 51 //'(?:(?:[^\\)\\]\\s*>+~,]+)(?:-?[_a-z]+[-\\w]))+#(-?[_a-z]+[-\\w]*)': '[id=$1]', 52 '\\#(-?[_a-z]+[-\\w]*)': '[id=$1]', 53 '\\.(-?[_a-z]+[-\\w]*)': '[class~=$1]' 54 }, 55 56 /** 57 * List of operators and corresponding boolean functions. 58 * These functions are passed the attribute and the current node's value of the attribute. 59 * @property operators 60 * @type object 61 */ 62 operators: { 63 '=': function(attr, val) { return attr === val; }, // Equality 64 '!=': function(attr, val) { return attr !== val; }, // Inequality 65 '~=': function(attr, val) { // Match one of space seperated words 66 var s = ' '; 67 return (s + attr + s).indexOf((s + val + s)) > -1; 68 }, 69 '|=': function(attr, val) { return getRegExp('^' + val + '[-]?').test(attr); }, // Match start with value followed by optional hyphen 70 '^=': function(attr, val) { return attr.indexOf(val) === 0; }, // Match starts with value 71 '$=': function(attr, val) { return attr.lastIndexOf(val) === attr.length - val.length; }, // Match ends with value 72 '*=': function(attr, val) { return attr.indexOf(val) > -1; }, // Match contains value as substring 73 '': function(attr, val) { return attr; } // Just test for existence of attribute 74 }, 75 76 /** 77 * List of pseudo-classes and corresponding boolean functions. 78 * These functions are called with the current node, and any value that was parsed with the pseudo regex. 79 * @property pseudos 80 * @type object 81 */ 82 pseudos: { 83 'root': function(node) { 84 return node === node.ownerDocument.documentElement; 85 }, 86 87 'nth-child': function(node, val) { 88 return getNth(node, val); 89 }, 90 91 'nth-last-child': function(node, val) { 92 return getNth(node, val, null, true); 93 }, 94 95 'nth-of-type': function(node, val) { 96 return getNth(node, val, node.tagName); 97 }, 98 99 'nth-last-of-type': function(node, val) { 100 return getNth(node, val, node.tagName, true); 101 }, 102 103 'first-child': function(node) { 104 return getChildren(node.parentNode)[0] === node; 105 }, 106 107 'last-child': function(node) { 108 var children = getChildren(node.parentNode); 109 return children[children.length - 1] === node; 110 }, 111 112 'first-of-type': function(node, val) { 113 return getChildren(node.parentNode, node.tagName.toLowerCase())[0]; 114 }, 115 116 'last-of-type': function(node, val) { 117 var children = getChildren(node.parentNode, node.tagName.toLowerCase()); 118 return children[children.length - 1]; 119 }, 120 121 'only-child': function(node) { 122 var children = getChildren(node.parentNode); 123 return children.length === 1 && children[0] === node; 124 }, 125 126 'only-of-type': function(node) { 127 return getChildren(node.parentNode, node.tagName.toLowerCase()).length === 1; 128 }, 129 130 'empty': function(node) { 131 return node.childNodes.length === 0; 132 }, 133 134 'not': function(node, simple) { 135 return !Selector.test(node, simple); 136 }, 137 138 'contains': function(node, str) { 139 var text = node.innerText || node.textContent || ''; 140 return text.indexOf(str) > -1; 141 }, 142 'checked': function(node) { 143 return node.checked === true; 144 } 145 }, 146 147 /** 148 * Test if the supplied node matches the supplied selector. 149 * @method test 150 * 151 * @param {HTMLElement | String} node An id or node reference to the HTMLElement being tested. 152 * @param {string} selector The CSS Selector to test the node against. 153 * @return{boolean} Whether or not the node matches the selector. 154 * @static 155 156 */ 157 test: function(node, selector) { 158 node = Selector.document.getElementById(node) || node; 159 160 if (!node) { 161 return false; 162 } 163 164 var groups = selector ? selector.split(',') : []; 165 if (groups.length) { 166 for (var i = 0, len = groups.length; i < len; ++i) { 167 if ( rTestNode(node, groups[i]) ) { // passes if ANY group matches 168 return true; 169 } 170 } 171 return false; 172 } 173 return rTestNode(node, selector); 174 }, 175 176 /** 177 * Filters a set of nodes based on a given CSS selector. 178 * @method filter 179 * 180 * @param {array} nodes A set of nodes/ids to filter. 181 * @param {string} selector The selector used to test each node. 182 * @return{array} An array of nodes from the supplied array that match the given selector. 183 * @static 184 */ 185 filter: function(nodes, selector) { 186 nodes = nodes || []; 187 188 var node, 189 result = [], 190 tokens = tokenize(selector); 191 192 if (!nodes.item) { // if not HTMLCollection, handle arrays of ids and/or nodes 193 YAHOO.log('filter: scanning input for HTMLElements/IDs', 'info', 'Selector'); 194 for (var i = 0, len = nodes.length; i < len; ++i) { 195 if (!nodes[i].tagName) { // tagName limits to HTMLElements 196 node = Selector.document.getElementById(nodes[i]); 197 if (node) { // skip IDs that return null 198 nodes[i] = node; 199 } else { 200 YAHOO.log('filter: skipping invalid node', 'warn', 'Selector'); 201 } 202 } 203 } 204 } 205 result = rFilter(nodes, tokenize(selector)[0]); 206 clearParentCache(); 207 YAHOO.log('filter: returning:' + result.length, 'info', 'Selector'); 208 return result; 209 }, 210 211 /** 212 * Retrieves a set of nodes based on a given CSS selector. 213 * @method query 214 * 215 * @param {string} selector The CSS Selector to test the node against. 216 * @param {HTMLElement | String} root optional An id or HTMLElement to start the query from. Defaults to Selector.document. 217 * @param {Boolean} firstOnly optional Whether or not to return only the first match. 218 * @return {Array} An array of nodes that match the given selector. 219 * @static 220 */ 221 query: function(selector, root, firstOnly) { 222 var result = query(selector, root, firstOnly); 223 YAHOO.log('query: returning ' + result, 'info', 'Selector'); 224 return result; 225 } 226 }; 227 228 var query = function(selector, root, firstOnly, deDupe) { 229 var result = (firstOnly) ? null : []; 230 if (!selector) { 231 return result; 232 } 233 234 var groups = selector.split(','); // TODO: handle comma in attribute/pseudo 235 236 if (groups.length > 1) { 237 var found; 238 for (var i = 0, len = groups.length; i < len; ++i) { 239 found = arguments.callee(groups[i], root, firstOnly, true); 240 result = firstOnly ? found : result.concat(found); 241 } 242 clearFoundCache(); 243 return result; 244 } 245 246 if (root && !root.nodeName) { // assume ID 247 root = Selector.document.getElementById(root); 248 if (!root) { 249 YAHOO.log('invalid root node provided', 'warn', 'Selector'); 250 return result; 251 } 252 } 253 254 root = root || Selector.document; 255 var tokens = tokenize(selector); 256 var idToken = tokens[getIdTokenIndex(tokens)], 257 nodes = [], 258 node, 259 id, 260 token = tokens.pop() || {}; 261 262 if (idToken) { 263 id = getId(idToken.attributes); 264 } 265 266 // use id shortcut when possible 267 if (id) { 268 node = Selector.document.getElementById(id); 269 270 if (node && (root.nodeName == '#document' || contains(node, root))) { 271 if ( rTestNode(node, null, idToken) ) { 272 if (idToken === token) { 273 nodes = [node]; // simple selector 274 } else { 275 root = node; // start from here 276 } 277 } 278 } else { 279 return result; 280 } 281 } 282 283 if (root && !nodes.length) { 284 nodes = root.getElementsByTagName(token.tag); 285 } 286 287 if (nodes.length) { 288 result = rFilter(nodes, token, firstOnly, deDupe); 289 } 290 291 clearParentCache(); 292 return result; 293 }; 294 295 var contains = function() { 296 if (document.documentElement.contains && !YAHOO.env.ua.webkit < 422) { // IE & Opera, Safari < 3 contains is broken 297 return function(needle, haystack) { 298 return haystack.contains(needle); 299 }; 300 } else if ( document.documentElement.compareDocumentPosition ) { // gecko 301 return function(needle, haystack) { 302 return !!(haystack.compareDocumentPosition(needle) & 16); 303 }; 304 } else { // Safari < 3 305 return function(needle, haystack) { 306 var parent = needle.parentNode; 307 while (parent) { 308 if (needle === parent) { 309 return true; 310 } 311 parent = parent.parentNode; 312 } 313 return false; 314 }; 315 } 316 }(); 317 318 var rFilter = function(nodes, token, firstOnly, deDupe) { 319 var result = firstOnly ? null : []; 320 321 for (var i = 0, len = nodes.length; i < len; i++) { 322 if (! rTestNode(nodes[i], '', token, deDupe)) { 323 continue; 324 } 325 326 if (firstOnly) { 327 return nodes[i]; 328 } 329 if (deDupe) { 330 if (nodes[i]._found) { 331 continue; 332 } 333 nodes[i]._found = true; 334 foundCache[foundCache.length] = nodes[i]; 335 } 336 337 result[result.length] = nodes[i]; 338 } 339 340 return result; 341 }; 342 343 var rTestNode = function(node, selector, token, deDupe) { 344 token = token || tokenize(selector).pop() || {}; 345 346 if (!node.tagName || 347 (token.tag !== '*' && node.tagName.toUpperCase() !== token.tag) || 348 (deDupe && node._found) ) { 349 return false; 350 } 351 352 if (token.attributes.length) { 353 var attribute; 354 for (var i = 0, len = token.attributes.length; i < len; ++i) { 355 attribute = node.getAttribute(token.attributes[i][0], 2); 356 if (attribute === null || attribute === undefined) { 357 return false; 358 } 359 if ( Selector.operators[token.attributes[i][1]] && 360 !Selector.operators[token.attributes[i][1]](attribute, token.attributes[i][2])) { 361 return false; 362 } 363 } 364 } 365 366 if (token.pseudos.length) { 367 for (var i = 0, len = token.pseudos.length; i < len; ++i) { 368 if (Selector.pseudos[token.pseudos[i][0]] && 369 !Selector.pseudos[token.pseudos[i][0]](node, token.pseudos[i][1])) { 370 return false; 371 } 372 } 373 } 374 375 return (token.previous && token.previous.combinator !== ',') ? 376 combinators[token.previous.combinator](node, token) : 377 true; 378 }; 379 380 381 var foundCache = []; 382 var parentCache = []; 383 var regexCache = {}; 384 385 var clearFoundCache = function() { 386 YAHOO.log('getBySelector: clearing found cache of ' + foundCache.length + ' elements'); 387 for (var i = 0, len = foundCache.length; i < len; ++i) { 388 try { // IE no like delete 389 delete foundCache[i]._found; 390 } catch(e) { 391 foundCache[i].removeAttribute('_found'); 392 } 393 } 394 foundCache = []; 395 YAHOO.log('getBySelector: done clearing foundCache'); 396 }; 397 398 var clearParentCache = function() { 399 if (!document.documentElement.children) { // caching children lookups for gecko 400 return function() { 401 for (var i = 0, len = parentCache.length; i < len; ++i) { 402 delete parentCache[i]._children; 403 } 404 parentCache = []; 405 }; 406 } else return function() {}; // do nothing 407 }(); 408 409 var getRegExp = function(str, flags) { 410 flags = flags || ''; 411 if (!regexCache[str + flags]) { 412 regexCache[str + flags] = new RegExp(str, flags); 413 } 414 return regexCache[str + flags]; 415 }; 416 417 var combinators = { 418 ' ': function(node, token) { 419 while (node = node.parentNode) { 420 if (rTestNode(node, '', token.previous)) { 421 return true; 422 } 423 } 424 return false; 425 }, 426 427 '>': function(node, token) { 428 return rTestNode(node.parentNode, null, token.previous); 429 }, 430 431 '+': function(node, token) { 432 var sib = node.previousSibling; 433 while (sib && sib.nodeType !== 1) { 434 sib = sib.previousSibling; 435 } 436 437 if (sib && rTestNode(sib, null, token.previous)) { 438 return true; 439 } 440 return false; 441 }, 442 443 '~': function(node, token) { 444 var sib = node.previousSibling; 445 while (sib) { 446 if (sib.nodeType === 1 && rTestNode(sib, null, token.previous)) { 447 return true; 448 } 449 sib = sib.previousSibling; 450 } 451 452 return false; 453 } 454 }; 455 456 var getChildren = function() { 457 if (document.documentElement.children) { // document for capability test 458 return function(node, tag) { 459 return (tag) ? node.children.tags(tag) : node.children || []; 460 }; 461 } else { 462 return function(node, tag) { 463 if (node._children) { 464 return node._children; 465 } 466 var children = [], 467 childNodes = node.childNodes; 468 469 for (var i = 0, len = childNodes.length; i < len; ++i) { 470 if (childNodes[i].tagName) { 471 if (!tag || childNodes[i].tagName.toLowerCase() === tag) { 472 children[children.length] = childNodes[i]; 473 } 474 } 475 } 476 node._children = children; 477 parentCache[parentCache.length] = node; 478 return children; 479 }; 480 } 481 }(); 482 483 /* 484 an+b = get every _a_th node starting at the _b_th 485 0n+b = no repeat ("0" and "n" may both be omitted (together) , e.g. "0n+1" or "1", not "0+1"), return only the _b_th element 486 1n+b = get every element starting from b ("1" may may be omitted, e.g. "1n+0" or "n+0" or "n") 487 an+0 = get every _a_th element, "0" may be omitted 488 */ 489 var getNth = function(node, expr, tag, reverse) { 490 if (tag) tag = tag.toLowerCase(); 491 reNth.test(expr); 492 var a = parseInt(RegExp.$1, 10), // include every _a_ elements (zero means no repeat, just first _a_) 493 n = RegExp.$2, // "n" 494 oddeven = RegExp.$3, // "odd" or "even" 495 b = parseInt(RegExp.$4, 10) || 0, // start scan from element _b_ 496 result = []; 497 498 var siblings = getChildren(node.parentNode, tag); 499 500 if (oddeven) { 501 a = 2; // always every other 502 op = '+'; 503 n = 'n'; 504 b = (oddeven === 'odd') ? 1 : 0; 505 } else if ( isNaN(a) ) { 506 a = (n) ? 1 : 0; // start from the first or no repeat 507 } 508 509 if (a === 0) { // just the first 510 if (reverse) { 511 b = siblings.length - b + 1; 512 } 513 514 if (siblings[b - 1] === node) { 515 return true; 516 } else { 517 return false; 518 } 519 520 } else if (a < 0) { 521 reverse = !!reverse; 522 a = Math.abs(a); 523 } 524 525 if (!reverse) { 526 for (var i = b - 1, len = siblings.length; i < len; i += a) { 527 if ( i >= 0 && siblings[i] === node ) { 528 return true; 529 } 530 } 531 } else { 532 for (var i = siblings.length - b, len = siblings.length; i >= 0; i -= a) { 533 if ( i < len && siblings[i] === node ) { 534 return true; 535 } 536 } 537 } 538 return false; 539 }; 540 541 var getId = function(attr) { 542 for (var i = 0, len = attr.length; i < len; ++i) { 543 if (attr[i][0] == 'id' && attr[i][1] === '=') { 544 return attr[i][2]; 545 } 546 } 547 }; 548 549 var getIdTokenIndex = function(tokens) { 550 for (var i = 0, len = tokens.length; i < len; ++i) { 551 if (getId(tokens[i].attributes)) { 552 return i; 553 } 554 } 555 return -1; 556 }; 557 558 var patterns = { 559 tag: /^((?:-?[_a-z]+[\w-]*)|\*)/i, 560 attributes: /^\[([a-z]+\w*)+([~\|\^\$\*!=]=?)?['"]?([^\]]*?)['"]?\]/i, 561 //attributes: /^\[([a-z]+\w*)+([~\|\^\$\*!=]=?)?['"]?([^'"\]]*)['"]?\]*/i, 562 pseudos: /^:([-\w]+)(?:\(['"]?(.+)['"]?\))*/i, 563 combinator: /^\s*([>+~]|\s)\s*/ 564 }; 565 566 /** 567 Break selector into token units per simple selector. 568 Combinator is attached to left-hand selector. 569 */ 570 var tokenize = function(selector) { 571 var token = {}, // one token per simple selector (left selector holds combinator) 572 tokens = [], // array of tokens 573 id, // unique id for the simple selector (if found) 574 found = false, // whether or not any matches were found this pass 575 match; // the regex match 576 577 selector = replaceShorthand(selector); // convert ID and CLASS shortcuts to attributes 578 579 /* 580 Search for selector patterns, store, and strip them from the selector string 581 until no patterns match (invalid selector) or we run out of chars. 582 583 Multiple attributes and pseudos are allowed, in any order. 584 for example: 585 'form:first-child[type=button]:not(button)[lang|=en]' 586 */ 587 do { 588 found = false; // reset after full pass 589 for (var re in patterns) { 590 if (!YAHOO.lang.hasOwnProperty(patterns, re)) { 591 continue; 592 } 593 if (re != 'tag' && re != 'combinator') { // only one allowed 594 token[re] = token[re] || []; 595 } 596 if (match = patterns[re].exec(selector)) { // note assignment 597 found = true; 598 if (re != 'tag' && re != 'combinator') { // only one allowed 599 //token[re] = token[re] || []; 600 601 // capture ID for fast path to element 602 if (re === 'attributes' && match[1] === 'id') { 603 token.id = match[3]; 604 } 605 606 token[re].push(match.slice(1)); 607 } else { // single selector (tag, combinator) 608 token[re] = match[1]; 609 } 610 selector = selector.replace(match[0], ''); // strip current match from selector 611 if (re === 'combinator' || !selector.length) { // next token or done 612 token.attributes = fixAttributes(token.attributes); 613 token.pseudos = token.pseudos || []; 614 token.tag = token.tag ? token.tag.toUpperCase() : '*'; 615 tokens.push(token); 616 617 token = { // prep next token 618 previous: token 619 }; 620 } 621 } 622 } 623 } while (found); 624 625 return tokens; 626 }; 627 628 var fixAttributes = function(attr) { 629 var aliases = Selector.attrAliases; 630 attr = attr || []; 631 for (var i = 0, len = attr.length; i < len; ++i) { 632 if (aliases[attr[i][0]]) { // convert reserved words, etc 633 attr[i][0] = aliases[attr[i][0]]; 634 } 635 if (!attr[i][1]) { // use exists operator 636 attr[i][1] = ''; 637 } 638 } 639 return attr; 640 }; 641 642 var replaceShorthand = function(selector) { 643 var shorthand = Selector.shorthand; 644 var attrs = selector.match(patterns.attributes); // pull attributes to avoid false pos on "." and "#" 645 if (attrs) { 646 selector = selector.replace(patterns.attributes, 'REPLACED_ATTRIBUTE'); 647 } 648 for (var re in shorthand) { 649 if (!YAHOO.lang.hasOwnProperty(shorthand, re)) { 650 continue; 651 } 652 selector = selector.replace(getRegExp(re, 'gi'), shorthand[re]); 653 } 654 655 if (attrs) { 656 for (var i = 0, len = attrs.length; i < len; ++i) { 657 selector = selector.replace('REPLACED_ATTRIBUTE', attrs[i]); 658 } 659 } 660 return selector; 661 }; 662 663 Selector = new Selector(); 664 Selector.patterns = patterns; 665 Y.Selector = Selector; 666 667 if (YAHOO.env.ua.ie) { // rewrite class for IE (others use getAttribute('class') 668 Y.Selector.attrAliases['class'] = 'className'; 669 Y.Selector.attrAliases['for'] = 'htmlFor'; 670 } 671 672 })(); 673 YAHOO.register("selector", YAHOO.util.Selector, {version: "2.6.0", build: "1321"});
title
Description
Body
title
Description
Body
title
Description
Body
title
Body
| Generated: Wed Jan 14 11:33:29 2009 | Cross-referenced by PHPXref 0.7 |