[ Index ]

PHP Cross Reference of Moodle 1.9.3 [Build 15-Oct-2008]

title

Body

[close]

/lib/yui/selector/ -> selector-beta.js (source)

   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"});


Generated: Wed Jan 14 11:33:29 2009 Cross-referenced by PHPXref 0.7