[ Index ]

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

title

Body

[close]

/lib/yui/selector/ -> selector-beta-debug.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              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"});


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