| [ 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 for (var i = 0, len = nodes.length; i < len; ++i) { 194 if (!nodes[i].tagName) { // tagName limits to HTMLElements 195 node = Selector.document.getElementById(nodes[i]); 196 if (node) { // skip IDs that return null 197 nodes[i] = node; 198 } else { 199 } 200 } 201 } 202 } 203 result = rFilter(nodes, tokenize(selector)[0]); 204 clearParentCache(); 205 return result; 206 }, 207 208 /** 209 * Retrieves a set of nodes based on a given CSS selector. 210 * @method query 211 * 212 * @param {string} selector The CSS Selector to test the node against. 213 * @param {HTMLElement | String} root optional An id or HTMLElement to start the query from. Defaults to Selector.document. 214 * @param {Boolean} firstOnly optional Whether or not to return only the first match. 215 * @return {Array} An array of nodes that match the given selector. 216 * @static 217 */ 218 query: function(selector, root, firstOnly) { 219 var result = query(selector, root, firstOnly); 220 return result; 221 } 222 }; 223 224 var query = function(selector, root, firstOnly, deDupe) { 225 var result = (firstOnly) ? null : []; 226 if (!selector) { 227 return result; 228 } 229 230 var groups = selector.split(','); // TODO: handle comma in attribute/pseudo 231 232 if (groups.length > 1) { 233 var found; 234 for (var i = 0, len = groups.length; i < len; ++i) { 235 found = arguments.callee(groups[i], root, firstOnly, true); 236 result = firstOnly ? found : result.concat(found); 237 } 238 clearFoundCache(); 239 return result; 240 } 241 242 if (root && !root.nodeName) { // assume ID 243 root = Selector.document.getElementById(root); 244 if (!root) { 245 return result; 246 } 247 } 248 249 root = root || Selector.document; 250 var tokens = tokenize(selector); 251 var idToken = tokens[getIdTokenIndex(tokens)], 252 nodes = [], 253 node, 254 id, 255 token = tokens.pop() || {}; 256 257 if (idToken) { 258 id = getId(idToken.attributes); 259 } 260 261 // use id shortcut when possible 262 if (id) { 263 node = Selector.document.getElementById(id); 264 265 if (node && (root.nodeName == '#document' || contains(node, root))) { 266 if ( rTestNode(node, null, idToken) ) { 267 if (idToken === token) { 268 nodes = [node]; // simple selector 269 } else { 270 root = node; // start from here 271 } 272 } 273 } else { 274 return result; 275 } 276 } 277 278 if (root && !nodes.length) { 279 nodes = root.getElementsByTagName(token.tag); 280 } 281 282 if (nodes.length) { 283 result = rFilter(nodes, token, firstOnly, deDupe); 284 } 285 286 clearParentCache(); 287 return result; 288 }; 289 290 var contains = function() { 291 if (document.documentElement.contains && !YAHOO.env.ua.webkit < 422) { // IE & Opera, Safari < 3 contains is broken 292 return function(needle, haystack) { 293 return haystack.contains(needle); 294 }; 295 } else if ( document.documentElement.compareDocumentPosition ) { // gecko 296 return function(needle, haystack) { 297 return !!(haystack.compareDocumentPosition(needle) & 16); 298 }; 299 } else { // Safari < 3 300 return function(needle, haystack) { 301 var parent = needle.parentNode; 302 while (parent) { 303 if (needle === parent) { 304 return true; 305 } 306 parent = parent.parentNode; 307 } 308 return false; 309 }; 310 } 311 }(); 312 313 var rFilter = function(nodes, token, firstOnly, deDupe) { 314 var result = firstOnly ? null : []; 315 316 for (var i = 0, len = nodes.length; i < len; i++) { 317 if (! rTestNode(nodes[i], '', token, deDupe)) { 318 continue; 319 } 320 321 if (firstOnly) { 322 return nodes[i]; 323 } 324 if (deDupe) { 325 if (nodes[i]._found) { 326 continue; 327 } 328 nodes[i]._found = true; 329 foundCache[foundCache.length] = nodes[i]; 330 } 331 332 result[result.length] = nodes[i]; 333 } 334 335 return result; 336 }; 337 338 var rTestNode = function(node, selector, token, deDupe) { 339 token = token || tokenize(selector).pop() || {}; 340 341 if (!node.tagName || 342 (token.tag !== '*' && node.tagName.toUpperCase() !== token.tag) || 343 (deDupe && node._found) ) { 344 return false; 345 } 346 347 if (token.attributes.length) { 348 var attribute; 349 for (var i = 0, len = token.attributes.length; i < len; ++i) { 350 attribute = node.getAttribute(token.attributes[i][0], 2); 351 if (attribute === null || attribute === undefined) { 352 return false; 353 } 354 if ( Selector.operators[token.attributes[i][1]] && 355 !Selector.operators[token.attributes[i][1]](attribute, token.attributes[i][2])) { 356 return false; 357 } 358 } 359 } 360 361 if (token.pseudos.length) { 362 for (var i = 0, len = token.pseudos.length; i < len; ++i) { 363 if (Selector.pseudos[token.pseudos[i][0]] && 364 !Selector.pseudos[token.pseudos[i][0]](node, token.pseudos[i][1])) { 365 return false; 366 } 367 } 368 } 369 370 return (token.previous && token.previous.combinator !== ',') ? 371 combinators[token.previous.combinator](node, token) : 372 true; 373 }; 374 375 376 var foundCache = []; 377 var parentCache = []; 378 var regexCache = {}; 379 380 var clearFoundCache = function() { 381 for (var i = 0, len = foundCache.length; i < len; ++i) { 382 try { // IE no like delete 383 delete foundCache[i]._found; 384 } catch(e) { 385 foundCache[i].removeAttribute('_found'); 386 } 387 } 388 foundCache = []; 389 }; 390 391 var clearParentCache = function() { 392 if (!document.documentElement.children) { // caching children lookups for gecko 393 return function() { 394 for (var i = 0, len = parentCache.length; i < len; ++i) { 395 delete parentCache[i]._children; 396 } 397 parentCache = []; 398 }; 399 } else return function() {}; // do nothing 400 }(); 401 402 var getRegExp = function(str, flags) { 403 flags = flags || ''; 404 if (!regexCache[str + flags]) { 405 regexCache[str + flags] = new RegExp(str, flags); 406 } 407 return regexCache[str + flags]; 408 }; 409 410 var combinators = { 411 ' ': function(node, token) { 412 while (node = node.parentNode) { 413 if (rTestNode(node, '', token.previous)) { 414 return true; 415 } 416 } 417 return false; 418 }, 419 420 '>': function(node, token) { 421 return rTestNode(node.parentNode, null, token.previous); 422 }, 423 424 '+': function(node, token) { 425 var sib = node.previousSibling; 426 while (sib && sib.nodeType !== 1) { 427 sib = sib.previousSibling; 428 } 429 430 if (sib && rTestNode(sib, null, token.previous)) { 431 return true; 432 } 433 return false; 434 }, 435 436 '~': function(node, token) { 437 var sib = node.previousSibling; 438 while (sib) { 439 if (sib.nodeType === 1 && rTestNode(sib, null, token.previous)) { 440 return true; 441 } 442 sib = sib.previousSibling; 443 } 444 445 return false; 446 } 447 }; 448 449 var getChildren = function() { 450 if (document.documentElement.children) { // document for capability test 451 return function(node, tag) { 452 return (tag) ? node.children.tags(tag) : node.children || []; 453 }; 454 } else { 455 return function(node, tag) { 456 if (node._children) { 457 return node._children; 458 } 459 var children = [], 460 childNodes = node.childNodes; 461 462 for (var i = 0, len = childNodes.length; i < len; ++i) { 463 if (childNodes[i].tagName) { 464 if (!tag || childNodes[i].tagName.toLowerCase() === tag) { 465 children[children.length] = childNodes[i]; 466 } 467 } 468 } 469 node._children = children; 470 parentCache[parentCache.length] = node; 471 return children; 472 }; 473 } 474 }(); 475 476 /* 477 an+b = get every _a_th node starting at the _b_th 478 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 479 1n+b = get every element starting from b ("1" may may be omitted, e.g. "1n+0" or "n+0" or "n") 480 an+0 = get every _a_th element, "0" may be omitted 481 */ 482 var getNth = function(node, expr, tag, reverse) { 483 if (tag) tag = tag.toLowerCase(); 484 reNth.test(expr); 485 var a = parseInt(RegExp.$1, 10), // include every _a_ elements (zero means no repeat, just first _a_) 486 n = RegExp.$2, // "n" 487 oddeven = RegExp.$3, // "odd" or "even" 488 b = parseInt(RegExp.$4, 10) || 0, // start scan from element _b_ 489 result = []; 490 491 var siblings = getChildren(node.parentNode, tag); 492 493 if (oddeven) { 494 a = 2; // always every other 495 op = '+'; 496 n = 'n'; 497 b = (oddeven === 'odd') ? 1 : 0; 498 } else if ( isNaN(a) ) { 499 a = (n) ? 1 : 0; // start from the first or no repeat 500 } 501 502 if (a === 0) { // just the first 503 if (reverse) { 504 b = siblings.length - b + 1; 505 } 506 507 if (siblings[b - 1] === node) { 508 return true; 509 } else { 510 return false; 511 } 512 513 } else if (a < 0) { 514 reverse = !!reverse; 515 a = Math.abs(a); 516 } 517 518 if (!reverse) { 519 for (var i = b - 1, len = siblings.length; i < len; i += a) { 520 if ( i >= 0 && siblings[i] === node ) { 521 return true; 522 } 523 } 524 } else { 525 for (var i = siblings.length - b, len = siblings.length; i >= 0; i -= a) { 526 if ( i < len && siblings[i] === node ) { 527 return true; 528 } 529 } 530 } 531 return false; 532 }; 533 534 var getId = function(attr) { 535 for (var i = 0, len = attr.length; i < len; ++i) { 536 if (attr[i][0] == 'id' && attr[i][1] === '=') { 537 return attr[i][2]; 538 } 539 } 540 }; 541 542 var getIdTokenIndex = function(tokens) { 543 for (var i = 0, len = tokens.length; i < len; ++i) { 544 if (getId(tokens[i].attributes)) { 545 return i; 546 } 547 } 548 return -1; 549 }; 550 551 var patterns = { 552 tag: /^((?:-?[_a-z]+[\w-]*)|\*)/i, 553 attributes: /^\[([a-z]+\w*)+([~\|\^\$\*!=]=?)?['"]?([^\]]*?)['"]?\]/i, 554 //attributes: /^\[([a-z]+\w*)+([~\|\^\$\*!=]=?)?['"]?([^'"\]]*)['"]?\]*/i, 555 pseudos: /^:([-\w]+)(?:\(['"]?(.+)['"]?\))*/i, 556 combinator: /^\s*([>+~]|\s)\s*/ 557 }; 558 559 /** 560 Break selector into token units per simple selector. 561 Combinator is attached to left-hand selector. 562 */ 563 var tokenize = function(selector) { 564 var token = {}, // one token per simple selector (left selector holds combinator) 565 tokens = [], // array of tokens 566 id, // unique id for the simple selector (if found) 567 found = false, // whether or not any matches were found this pass 568 match; // the regex match 569 570 selector = replaceShorthand(selector); // convert ID and CLASS shortcuts to attributes 571 572 /* 573 Search for selector patterns, store, and strip them from the selector string 574 until no patterns match (invalid selector) or we run out of chars. 575 576 Multiple attributes and pseudos are allowed, in any order. 577 for example: 578 'form:first-child[type=button]:not(button)[lang|=en]' 579 */ 580 do { 581 found = false; // reset after full pass 582 for (var re in patterns) { 583 if (!YAHOO.lang.hasOwnProperty(patterns, re)) { 584 continue; 585 } 586 if (re != 'tag' && re != 'combinator') { // only one allowed 587 token[re] = token[re] || []; 588 } 589 if (match = patterns[re].exec(selector)) { // note assignment 590 found = true; 591 if (re != 'tag' && re != 'combinator') { // only one allowed 592 //token[re] = token[re] || []; 593 594 // capture ID for fast path to element 595 if (re === 'attributes' && match[1] === 'id') { 596 token.id = match[3]; 597 } 598 599 token[re].push(match.slice(1)); 600 } else { // single selector (tag, combinator) 601 token[re] = match[1]; 602 } 603 selector = selector.replace(match[0], ''); // strip current match from selector 604 if (re === 'combinator' || !selector.length) { // next token or done 605 token.attributes = fixAttributes(token.attributes); 606 token.pseudos = token.pseudos || []; 607 token.tag = token.tag ? token.tag.toUpperCase() : '*'; 608 tokens.push(token); 609 610 token = { // prep next token 611 previous: token 612 }; 613 } 614 } 615 } 616 } while (found); 617 618 return tokens; 619 }; 620 621 var fixAttributes = function(attr) { 622 var aliases = Selector.attrAliases; 623 attr = attr || []; 624 for (var i = 0, len = attr.length; i < len; ++i) { 625 if (aliases[attr[i][0]]) { // convert reserved words, etc 626 attr[i][0] = aliases[attr[i][0]]; 627 } 628 if (!attr[i][1]) { // use exists operator 629 attr[i][1] = ''; 630 } 631 } 632 return attr; 633 }; 634 635 var replaceShorthand = function(selector) { 636 var shorthand = Selector.shorthand; 637 var attrs = selector.match(patterns.attributes); // pull attributes to avoid false pos on "." and "#" 638 if (attrs) { 639 selector = selector.replace(patterns.attributes, 'REPLACED_ATTRIBUTE'); 640 } 641 for (var re in shorthand) { 642 if (!YAHOO.lang.hasOwnProperty(shorthand, re)) { 643 continue; 644 } 645 selector = selector.replace(getRegExp(re, 'gi'), shorthand[re]); 646 } 647 648 if (attrs) { 649 for (var i = 0, len = attrs.length; i < len; ++i) { 650 selector = selector.replace('REPLACED_ATTRIBUTE', attrs[i]); 651 } 652 } 653 return selector; 654 }; 655 656 Selector = new Selector(); 657 Selector.patterns = patterns; 658 Y.Selector = Selector; 659 660 if (YAHOO.env.ua.ie) { // rewrite class for IE (others use getAttribute('class') 661 Y.Selector.attrAliases['class'] = 'className'; 662 Y.Selector.attrAliases['for'] = 'htmlFor'; 663 } 664 665 })(); 666 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 |