Changeset 6366
- Timestamp:
- 03/09/07 04:12:13 (1 year ago)
- Files:
-
- spinoffs/prototype/trunk/CHANGELOG (modified) (1 diff)
- spinoffs/prototype/trunk/src/dom.js (modified) (5 diffs)
- spinoffs/prototype/trunk/src/selector.js (modified) (1 diff)
- spinoffs/prototype/trunk/test/lib/unittest.js (modified) (1 diff)
- spinoffs/prototype/trunk/test/unit/selector.html (modified) (2 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
spinoffs/prototype/trunk/CHANGELOG
r6363 r6366 1 1 *SVN* 2 3 * Merge the selector branch into trunk, bringing vast performance improvements, bug fixes, and near-complete CSS3 compliance to $$ and Selector. Closes #7568. [Andrew Dupont] 4 Selector speed test: http://andrewdupont.net/test/double-dollar/ 2 5 3 6 * Add support for JSON encoding and decoding. Closes #7427. [Tobie Langel] spinoffs/prototype/trunk/src/dom.js
r6358 r6366 19 19 return results; 20 20 }; 21 21 22 document.getElementsByClassName = function(className, parentElement) { 22 23 var q = ".//*[contains(concat(' ', @class, ' '), ' " + className + " ')]"; 23 24 return document._getElementsByXPath(q, parentElement); 24 25 } 26 25 27 } else document.getElementsByClassName = function(className, parentElement) { 26 28 var children = ($(parentElement) || document.body).getElementsByTagName('*'); … … 178 180 179 181 up: function(element, expression, index) { 180 return Selector.findElement($(element).ancestors(), expression, index); 182 var ancestors = $(element).ancestors(); 183 return expression ? Selector.findElement(ancestors, expression, index) : 184 ancestors[index || 0]; 181 185 }, 182 186 183 187 down: function(element, expression, index) { 184 return Selector.findElement($(element).descendants(), expression, index); 188 var descendants = $(element).descendants(); 189 return expression ? Selector.findElement(descendants, expression, index) : 190 descendants[index || 0]; 185 191 }, 186 192 187 193 previous: function(element, expression, index) { 188 return Selector.findElement($(element).previousSiblings(), expression, index); 194 var previousSiblings = $(element).previousSiblings(); 195 return expression ? Selector.findElement(previousSiblings, expression, index) : 196 previousSiblings[index || 0]; 189 197 }, 190 198 191 199 next: function(element, expression, index) { 192 return Selector.findElement($(element).nextSiblings(), expression, index); 200 var nextSiblings = $(element).nextSiblings(); 201 return expression ? Selector.findElement(nextSiblings, expression, index) : 202 nextSiblings[index || 0]; 193 203 }, 194 204 … … 204 214 readAttribute: function(element, name) { 205 215 element = $(element); 206 if ( document.all && !window.opera) {216 if (Prototype.Browser.IE) { 207 217 var t = Element._attributeTranslations; 208 218 if (t.values[name]) return t.values[name](element, name); 209 219 if (t.names[name]) name = t.names[name]; 210 220 var attribute = element.attributes[name]; 211 if(attribute) return attribute.nodeValue;221 return attribute ? attribute.nodeValue : null; 212 222 } 213 223 return element.getAttribute(name); … … 399 409 element._overflow = null; 400 410 return element; 401 } 411 } 402 412 }; 403 413 … … 554 564 Prototype.BrowserFeatures.ElementExtensions = true; 555 565 } 566 567 Element.hasAttribute = function(element, attribute) { 568 if (element.hasAttribute) return element.hasAttribute(attribute); 569 return Element.Methods.Simulated.hasAttribute(element, attribute); 570 }; 556 571 557 572 Element.addMethods = function(methods) { spinoffs/prototype/trunk/src/selector.js
r5957 r6366 1 1 var Selector = Class.create(); 2 2 3 Selector.prototype = { 3 4 initialize: function(expression) { 4 this.params = {classNames: []}; 5 this.expression = expression.toString().strip(); 6 this.parseExpression(); 7 this.compileMatcher(); 8 }, 9 10 parseExpression: function() { 11 function abort(message) { throw 'Parse error in selector: ' + message; } 12 13 if (this.expression == '') abort('empty expression'); 14 15 var params = this.params, expr = this.expression, match, modifier, clause, rest; 16 while (match = expr.match(/^(.*)\[([a-z0-9_:-]+?)(?:([~\|!]?=)(?:"([^"]*)"|([^\]\s]*)))?\]$/i)) { 17 params.attributes = params.attributes || []; 18 params.attributes.push({name: match[2], operator: match[3], value: match[4] || match[5] || ''}); 19 expr = match[1]; 20 } 21 22 if (expr == '*') return this.params.wildcard = true; 23 24 while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+)(.*)/i)) { 25 modifier = match[1], clause = match[2], rest = match[3]; 26 switch (modifier) { 27 case '#': params.id = clause; break; 28 case '.': params.classNames.push(clause); break; 29 case '': 30 case undefined: params.tagName = clause.toUpperCase(); break; 31 default: abort(expr.inspect()); 32 } 33 expr = rest; 34 } 35 36 if (expr.length > 0) abort(expr.inspect()); 37 }, 38 39 buildMatchExpression: function() { 40 var params = this.params, conditions = [], clause; 41 42 if (params.wildcard) 43 conditions.push('true'); 44 if (clause = params.id) 45 conditions.push('element.readAttribute("id") == ' + clause.inspect()); 46 if (clause = params.tagName) 47 conditions.push('element.tagName.toUpperCase() == ' + clause.inspect()); 48 if ((clause = params.classNames).length > 0) 49 for (var i = 0, length = clause.length; i < length; i++) 50 conditions.push('element.hasClassName(' + clause[i].inspect() + ')'); 51 if (clause = params.attributes) { 52 clause.each(function(attribute) { 53 var value = 'element.readAttribute(' + attribute.name.inspect() + ')'; 54 var splitValueBy = function(delimiter) { 55 return value + ' && ' + value + '.split(' + delimiter.inspect() + ')'; 56 } 57 58 switch (attribute.operator) { 59 case '=': conditions.push(value + ' == ' + attribute.value.inspect()); break; 60 case '~=': conditions.push(splitValueBy(' ') + '.include(' + attribute.value.inspect() + ')'); break; 61 case '|=': conditions.push( 62 splitValueBy('-') + '.first().toUpperCase() == ' + attribute.value.toUpperCase().inspect() 63 ); break; 64 case '!=': conditions.push(value + ' != ' + attribute.value.inspect()); break; 65 case '': 66 case undefined: conditions.push('element.hasAttribute(' + attribute.name.inspect() + ')'); break; 67 default: throw 'Unknown operator ' + attribute.operator + ' in selector'; 68 } 69 }); 70 } 71 72 return conditions.join(' && '); 73 }, 74 5 this.expression = expression.strip(); 6 this.compileMatcher(); 7 }, 8 75 9 compileMatcher: function() { 76 this.match = new Function('element', 'if (!element.tagName) return false; \ 77 element = $(element); \ 78 return ' + this.buildMatchExpression()); 79 }, 80 81 findElements: function(scope) { 82 var element; 83 84 if (element = $(this.params.id)) 85 if (this.match(element)) 86 if (!scope || Element.childOf(element, scope)) 87 return [element]; 88 89 scope = (scope || document).getElementsByTagName(this.params.tagName || '*'); 90 91 var results = []; 92 for (var i = 0, length = scope.length; i < length; i++) 93 if (this.match(element = scope[i])) 94 results.push(Element.extend(element)); 95 96 return results; 97 }, 98 10 // Selectors with namespaced attributes can't use the XPath version 11 if (Prototype.BrowserFeatures.XPath && !(/\[[\w-]*?:/).test(this.expression)) 12 return this.compileXPathMatcher(); 13 14 var e = this.expression, ps = Selector.patterns, h = Selector.handlers, 15 c = Selector.criteria, le, p, m; 16 17 if (Selector._cache[e]) { 18 this.matcher = Selector._cache[e]; return; 19 } 20 this.matcher = ["this.matcher = function(root) {", 21 "var r = root, h = Selector.handlers, c = false, n;"]; 22 23 while (e && le != e && (/\S/).test(e)) { 24 le = e; 25 for (var i in ps) { 26 p = ps[i]; 27 if (m = e.match(p)) { 28 this.matcher.push(typeof c[i] == 'function' ? c[i](m) : 29 new Template(c[i]).evaluate(m)); 30 e = e.replace(m[0], ''); 31 break; 32 } 33 } 34 } 35 36 this.matcher.push("return h.unique(n);\n}"); 37 eval(this.matcher.join('\n')); 38 Selector._cache[this.expression] = this.matcher; 39 }, 40 41 compileXPathMatcher: function() { 42 var e = this.expression, ps = Selector.patterns, 43 x = Selector.xpath, le, p, m; 44 45 if (Selector._cache[e]) { 46 this.xpath = Selector._cache[e]; return; 47 } 48 49 this.matcher = ['.//*']; 50 while (e && le != e && (/\S/).test(e)) { 51 le = e; 52 for (var i in ps) { 53 if (m = e.match(ps[i])) { 54 this.matcher.push(typeof x[i] == 'function' ? x[i](m) : 55 new Template(x[i]).evaluate(m)); 56 e = e.replace(m[0], ''); 57 break; 58 } 59 } 60 } 61 62 this.xpath = this.matcher.join(''); 63 Selector._cache[this.expression] = this.xpath; 64 }, 65 66 findElements: function(root) { 67 root = root || document; 68 if (this.xpath) return document._getElementsByXPath(this.xpath, root); 69 return this.matcher(root); 70 }, 71 72 match: function(element) { 73 return this.findElements(document).include(element); 74 }, 75 99 76 toString: function() { 100 77 return this.expression; 78 }, 79 80 inspect: function() { 81 return "#<Selector:" + this.expression.inspect() + ">"; 101 82 } 102 } 83 }; 103 84 104 85 Object.extend(Selector, { 86 _cache: {}, 87 88 xpath: { 89 descendant: "//*", 90 child: "/*", 91 adjacent: "/following-sibling::*[1]", 92 laterSibling: '/following-sibling::*', 93 tagName: function(m) { 94 if (m[1] == '*') return ''; 95 return "[local-name()='" + m[1].toLowerCase() + 96 "' or local-name()='" + m[1].toUpperCase() + "']"; 97 }, 98 className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", 99 id: "[@id='#{1}']", 100 attrPresence: "[@#{1}]", 101 attr: function(m) { 102 m[3] = m[5] || m[6]; 103 return new Template(Selector.xpath.operators[m[2]]).evaluate(m); 104 }, 105 pseudo: function(m) { 106 var h = Selector.xpath.pseudos[m[1]]; 107 if (!h) return ''; 108 if (typeof h === 'function') return h(m); 109 return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); 110 }, 111 operators: { 112 '=': "[@#{1}='#{3}']", 113 '!=': "[@#{1}!='#{3}']", 114 '^=': "[starts-with(@#{1}, '#{3}')]", 115 '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", 116 '*=': "[contains(@#{1}, '#{3}')]", 117 '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", 118 '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" 119 }, 120 pseudos: { 121 'first-child': '[not(preceding-sibling::*)]', 122 'last-child': '[not(following-sibling::*)]', 123 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', 124 'empty': "[count(*) = 0 and (count(text()) = 0 or translate(text(), ' \t\r\n', '') = '')]", 125 'checked': "[@checked]", 126 'disabled': "[@disabled]", 127 'enabled': "[not(@disabled)]", 128 'not': function(m) { 129 if (!m[6]) return ''; 130 var p = Selector.patterns, x = Selector.xpath; 131 for (var i in p) { 132 if (mm = m[6].match(p[i])) { 133 var ss = typeof x[i] == 'function' ? x[i](mm) : new Template(x[i]).evaluate(mm); 134 m[6] = ss.substring(1, ss.length - 1); 135 break; 136 } 137 } 138 return "[not(" + m[6] + ")]"; 139 }, 140 'nth-child': function(m) { 141 return Selector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); 142 }, 143 'nth-last-child': function(m) { 144 return Selector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); 145 }, 146 'nth-of-type': function(m) { 147 return Selector.xpath.pseudos.nth("position() ", m); 148 }, 149 'nth-last-of-type': function(m) { 150 return Selector.xpath.pseudos.nth("(last() + 1 - position()) ", m); 151 }, 152 'first-of-type': function(m) { 153 m[6] = "1"; return Selector.xpath.pseudos['nth-of-type'](m); 154 }, 155 'last-of-type': function(m) { 156 m[6] = "1"; return Selector.xpath.pseudos['nth-last-of-type'](m); 157 }, 158 'only-of-type': function(m) { 159 var p = Selector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); 160 }, 161 nth: function(predicate, m) { 162 var mm, formula = m[6]; 163 if (formula == 'even') formula = '2n+0'; 164 if (formula == 'odd') formula = '2n+1'; 165 if (mm = formula.match(/^(\d+)$/)) // digit only 166 predicate += "= " + mm[1]; 167 if (mm = formula.match(/^(\d+)?n(\+(\d+))?/)) { // an+b 168 var a = mm[1] ? Number(mm[1]) : 1; 169 var b = mm[3] ? Number(mm[3]) : 0; 170 predicate += "mod " + a + " = " + b; 171 } 172 return "[" + predicate + "]"; 173 } 174 } 175 }, 176 177 criteria: { 178 tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', 179 className: 'n = h.className(n, r, "#{1}", c); c = false;', 180 id: 'n = h.id(n, r, "#{1}", c); c = false;', 181 attrPresence: 'n = h.attrPresence(n, r, "#{1}"); c = false;', 182 attr: function(m) { 183 m[3] = m[5] || m[6]; 184 return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}"); c = false;').evaluate(m); 185 }, 186 pseudo: 'n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;', 187 descendant: 'c = "descendant";', 188 child: 'c = "child";', 189 adjacent: 'c = "adjacent";', 190 laterSibling: 'c = "laterSibling";' 191 }, 192 193 patterns: { 194 // combinators must be listed first 195 // (and descendant needs to be last combinator) 196 laterSibling: /^\s*~\s*/, 197 child: /^\s*>\s*/, 198 adjacent: /^\s*\+\s*/, 199 descendant: /^\s/, 200 201 // selectors follow 202 tagName: /^\s*(\*|[\w\-]+)(\b|$)?/, 203 id: /^#([\w\-\*]+)(\b|$)/, 204 className: /^\.([\w\-\*]+)(\b|$)/, 205 pseudo: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$)/, 206 attrPresence: /^\[([\w]+)\]/, 207 attr: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\]]*?)\4|([^'"][^\]]*?)))?\]/ 208 }, 209 210 handlers: { 211 // UTILITY FUNCTIONS 212 // joins two collections 213 concat: function(a, b) { 214 for (var i = 0, node; node = b[i]; i++) 215 a.push(node); 216 return a; 217 }, 218 219 // marks an array of nodes for counting 220 mark: function(nodes) { 221 for (var i = 0, node; node = nodes[i]; i++) 222 node._counted = true; 223 return nodes; 224 }, 225 226 unmark: function(nodes) { 227 for (var i = 0, node; node = nodes[i]; i++) 228 node._counted = undefined; 229 return nodes; 230 }, 231 232 // mark each child node with its position (for nth calls) 233 // "ofType" flag indicates whether we're indexing for nth-of-type 234 // rather than nth-child 235 index: function(parentNode, reverse, ofType) { 236 parentNode._counted = true; 237 if (reverse) { 238 for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { 239 node = nodes[i]; 240 if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; 241 } 242 } else { 243 for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) 244 if (node.nodeType == 1 && (!ofType || node._counted)) node.nodeIndex = j++; 245 } 246 }, 247 248 // filters out duplicates and extends all nodes 249 unique: function(nodes) { 250 if (nodes.length == 0) return nodes; 251 var results = [nodes[0]], n; 252 nodes[0]._counted = true; 253 for (var i = 0, l = nodes.length; i < l; i++) { 254 n = nodes[i]; 255 if (!n._counted) { 256 n._counted = true; 257 results.push(Element.extend(n)); 258 } 259 } 260 return Selector.handlers.unmark(results); 261 }, 262 263 // COMBINATOR FUNCTIONS 264 descendant: function(nodes) { 265 var h = Selector.handlers; 266 for (var i = 0, results = [], node; node = nodes[i]; i++) 267 h.concat(results, Element.descendants(node)); 268 return results; 269 }, 270 271 child: function(nodes) { 272 var h = Selector.handlers; 273 for (var i = 0, results = [], node; node = nodes[i]; i++) 274 h.concat(results, Element.immediateDescendants(node)); 275 return results; 276 }, 277 278 adjacent: function(nodes) { 279 for (var i = 0, results = [], node; node = nodes[i]; i++) { 280 var next = this.nextElementSibling(node); 281 if (next) results.push(next); 282 } 283 return results; 284 }, 285 286 laterSibling: function(nodes) { 287 var h = Selector.handlers; 288 for (var i = 0, results = [], node; node = nodes[i]; i++) 289 h.concat(results, Element.nextSiblings(node)); 290 return results; 291 }, 292 293 nextElementSibling: function(node) { 294 while (node = node.nextSibling) 295 if (node.nodeType == 1) return node; 296 return null; 297 }, 298 299 previousElementSibling: function(node) { 300 while (node = node.previousSibling) 301 if (node.nodeType == 1) return node; 302 return null; 303 }, 304 305 // TOKEN FUNCTIONS 306 tagName: function(nodes, root, tagName, combinator) { 307 tagName = tagName.toUpperCase(); 308 var results = [], h = Selector.handlers; 309 if (nodes) { 310 if (combinator) { 311 // fastlane for ordinary descendant combinators 312 if (combinator == "descendant") { 313 for (var i = 0, node; node = nodes[i]; i++) 314 h.concat(results, node.getElementsByTagName(tagName)); 315 return results; 316 } else nodes = this[combinator](nodes); 317 if (tagName == "*") return nodes; 318 } 319 for (var i = 0, node; node = nodes[i]; i++) 320 if (node.tagName.toUpperCase() == tagName) results.push(node); 321 return results; 322 } else return root.getElementsByTagName(tagName); 323 }, 324 325 id: function(nodes, root, id, combinator) { 326 var targetNode = $(id), h = Selector.handlers; 327 if (!nodes && root == document) return targetNode ? [targetNode] : []; 328 if (nodes) { 329 if (combinator) { 330 if (combinator == 'child') { 331 for (var i = 0, node; node = nodes[i]; i++) 332 if (targetNode.parentNode == node) return [targetNode]; 333 } else if (combinator == 'descendant') { 334 for (var i = 0, node; node = nodes[i]; i++) 335 if (Element.descendantOf(targetNode, node)) return [targetNode]; 336 } else if (combinator == 'adjacent') { 337 for (var i = 0, node; node = nodes[i]; i++) 338 if (Selector.handlers.previousElementSibling(targetNode) == node) 339 return [targetNode]; 340 } else nodes = h[combinator](nodes); 341 } 342 for (var i = 0, node; node = nodes[i]; i++) 343 if (node == targetNode) return [targetNode]; 344 return []; 345 } 346 return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; 347 }, 348 349 className: function(nodes, root, className, combinator) { 350 if (nodes && combinator) nodes = this[combinator](nodes); 351 return Selector.handlers.byClassName(nodes, root, className); 352 }, 353 354 byClassName: function(nodes, root, className) { 355 if (!nodes) nodes = Selector.handlers.descendant([root]); 356 var needle = ' ' + className + ' '; 357 for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { 358 nodeClassName = node.className; 359 if (nodeClassName.length == 0) continue; 360 if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) 361 results.push(node); 362 } 363 return results; 364 }, 365 366 attrPresence: function(nodes, root, attr) { 367 var results = []; 368 for (var i = 0, node; node = nodes[i]; i++) 369 if (Element.hasAttribute(node, attr)) results.push(node); 370 return results; 371 }, 372 373 attr: function(nodes, root, attr, value, operator) { 374 var handler = Selector.operators[operator], results = []; 375 for (var i = 0, node; node = nodes[i]; i++) { 376 var nodeValue = Element.readAttribute(node, attr); 377 if (nodeValue === null) continue; 378 if (handler(nodeValue, value)) results.push(node); 379 } 380 return results; 381 }, 382 383 pseudo: function(nodes, name, value, root, combinator) { 384 if (combinator) nodes = this[combinator](nodes); 385 return Selector.pseudos[name](nodes, value, root); 386 } 387 }, 388 389 pseudos: { 390 'first-child': function(nodes, value, root) { 391 for (var i = 0, results = [], node; node = nodes[i]; i++) { 392 if (Selector.handlers.previousElementSibling(node)) continue; 393 results.push(node); 394 } 395 return results; 396 }, 397 'last-child': function(nodes, value, root) { 398 for (var i = 0, results = [], node; node = nodes[i]; i++) { 399 if (Selector.handlers.nextElementSibling(node)) continue; 400 results.push(node); 401 } 402 return results; 403 }, 404 'only-child': function(nodes, value, root) { 405 var h = Selector.handlers; 406 for (var i = 0, results = [], node; node = nodes[i]; i++) 407 if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) 408 results.push(node); 409 return results; 410 }, 411 'nth-child': function(nodes, formula, root) { 412 return Selector.pseudos.nth(nodes, formula, root); 413 }, 414 'nth-last-child': function(nodes, formula, root) { 415 return Selector.pseudos.nth(nodes, formula, root, true); 416 }, 417 'nth-of-type': function(nodes, formula, root) { 418 return Selector.pseudos.nth(nodes, formula, root, false, true); 419 }, 420 'nth-last-of-type': function(nodes, formula, root) { 421 return Selector.pseudos.nth(nodes, formula, root, true, true); 422 }, 423 'first-of-type': function(nodes, formula, root) { 424 return Selector.pseudos.nth(nodes, "1", root, false, true); 425 }, 426 'last-of-type': function(nodes, formula, root) { 427 return Selector.pseudos.nth(nodes, "1", root, true, true); 428 }, 429 'only-of-type': function(nodes, formula, root) { 430 var p = Selector.pseudos; 431 return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); 432 }, 433 434 // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type 435 nth: function(nodes, formula, root, reverse, ofType) { 436 if (formula == 'even') formula = '2n+0'; 437 if (formula == 'odd') formula = '2n+1'; 438 var h = Selector.handlers, results = [], indexed = [], m; 439 h.mark(nodes); 440 for (var i = 0, node; node = nodes[i]; i++) { 441 if (!node.parentNode._counted) { 442 h.index(node.parentNode, reverse, ofType); 443 indexed.push(node.parentNode); 444 } 445 } 446 if (formula.match(/^\d+$/)) { // just a number 447 formula = Number(formula); 448 for (var i = 0, node; node = nodes[i]; i++) 449 if (node.nodeIndex == formula) results.push(node); 450 } else if (m = formula.match(/^(\d+)?n(\+(\d+))?$/)) { // an+b 451 var a = m[1] ? Number(m[1]) : 1; 452 var b = m[3] ? Number(m[3]) : 0; 453 for (var i = 0, node; node = nodes[i]; i++) 454 if (node.nodeIndex % a == b) results.push(node); 455 } 456 h.unmark(nodes); 457 h.unmark(indexed); 458 return results; 459 }, 460 461 'empty': function(nodes, value, root) { 462 for (var i = 0, results = [], node; node = nodes[i]; i++) { 463 // IE treats comments as element nodes 464 if (node.tagName == '!' || (node.firstChild && !node.innerHTML.match(/^\s*$/))) continue; 465 results.push(node); 466 } 467 return results; 468 }, 469 470 'not': function(nodes, selector, root) { 471 var h = Selector.handlers, exclusions = $A(nodes), selectorType, m; 472 for (var i in Selector.patterns) { 473 if (m = selector.match(Selector.patterns[i])) { 474 selectorType = i; break; 475 } 476 } 477 switch(selectorType) { 478 case 'className': case 'tagName': case 'id': // fallthroughs 479 case 'attrPresence': exclusions = h[selectorType](exclusions, root, m[1], false); break; 480 case 'attr': m[3] = m[5] || m[6]; exclusions = h.attr(exclusions, root, m[1], m[3], m[2]); break; 481 case 'pseudo': exclusions = h.pseudo(exclusions, m[1], m[6], root, false); break; 482 // only 'simple selectors' (one token) allowed in a :not clause 483 default: throw 'Illegal selector in :not clause.'; 484 } 485 h.mark(exclusions); 486 for (var i = 0, results = [], node; node = nodes[i]; i++) 487 if (!node._counted) results.push(node); 488 h.unmark(exclusions); 489 return results; 490 }, 491 492 'enabled': function(nodes, value, root) { 493 for (var i = 0, results = [], node; node = nodes[i]; i++) 494 if (!node.disabled) results.push(node); 495 return results; 496 }, 497 498 'disabled': function(nodes, value, root) { 499 for (var i = 0, results = [], node; node = nodes[i]; i++) 500 if (node.disabled) results.push(node); 501 return results; 502 }, 503 504 'checked': function(nodes, value, root) { 505 for (var i = 0, results = [], node; node = nodes[i]; i++) 506 if (node.checked) results.push(node); 507 return results; 508 } 509 }, 510 511 operators: { 512 '=': function(nv, v) { return nv == v; }, 513 '!=': function(nv, v) { return nv != v; }, 514 '^=': function(nv, v) { return nv.startsWith(v); }, 515 '$=': function(nv, v) { return nv.endsWith(v); }, 516 '*=': function(nv, v) { return nv.include(v); }, 517 '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, 518 '|=': function(nv, v) { return ('-' + nv.toUpperCase() + '-').include('-' + v.toUpperCase() + '-'); } 519 }, 520 105 521 matchElements: function(elements, expression) { 106 var selector = new Selector(expression); 107 return elements.select(selector.match.bind(selector)).map(Element.extend); 522 var matches = new Selector(expression).findElements(), h = Selector.handlers; 523 h.mark(matches); 524 for (var i = 0, results = [], element; element = elements[i]; i++) 525 if (element._counted) results.push(element); 526 h.unmark(matches); 527 return results; 108 528 }, 109 529 110 530 findElement: function(elements, expression, index) { 111 if (typeof expression == 'number') index = expression, expression = false; 531 if (typeof expression == 'number') { 532 index = expression; expression = false; 533 } 112 534 return Selector.matchElements(elements, expression || '*')[index || 0]; 113 535 }, 114 536 115 537 findChildElements: function(element, expressions) { 116 return expressions.map(function(expression) { 117 return expression.match(/[^\s"]+(?:"[^"]*"[^\s"]+)*/g).inject([null], function(results, expr) { 118 var selector = new Selector(expr); 119 return results.inject([], function(elements, result) { 120 return elements.concat(selector.findElements(result || element)); 121 }); 122 }); 123 }).flatten(); 538 var exprs = expressions.join(','), expressions = []; 539 exprs.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { 540 expressions.push(m[1].strip()); 541 }); 542 var results = [], h = Selector.handlers; 543 for (var i = 0, l = expressions.length, selector; i < l; i++) { 544 selector = new Selector(expressions[i].strip()); 545 h.concat(results, selector.findElements(element)); 546 } 547 return (l > 1) ? h.unique(results) : results; 124 548 } 125 549 }); spinoffs/prototype/trunk/test/lib/unittest.js
r5649 r6366 306 306 assertEnumEqual: function(expected, actual) { 307 307 var message = arguments[2] || "assertEnumEqual"; 308 try { $A(expected).length == $A(actual).length && 308 expected = $A(expected); 309 actual = $A(actual); 310 try { expected.length == actual.length && 309 311 expected.zip(actual).all(function(pair) { return pair[0] == pair[1] }) ? 310 312 this.pass() : this.fail(message + ': expected ' + Test.Unit.inspect(expected) + spinoffs/prototype/trunk/test/unit/selector.html
r5957 r6366 1 1 <!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Transitional//EN" 2 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 3 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en"> 2 "http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"> 3 <html xmlns="http://www.w3.org/1999/xhtml" xml:lang="en" lang="en" 4 xmlns:html="http://www.w3.org/1999/xhtml"> 4 5 <head> 5 <title>Prototype Unit test file</title>6 <meta http-equiv="content-type" content="text/html; charset=utf-8" />7 <script src="../../dist/prototype.js" type="text/javascript"></script>8 <script src="../lib/unittest.js" type="text/javascript"></script>9 <link rel="stylesheet" href="../test.css" type="text/css" />10 <style type="text/css" media="screen">11 /* <![CDATA[ */12 #testcss1 { font-size:11px; color: #f00; }13 #testcss2 { font-size:12px; color: #0f0; display: none; }14 /* ]]> */15 </style>6 <title>Prototype Unit test file</title> 7 <meta http-equiv="content-type" content="text/html; charset=utf-8" /> 8 <script src="../../dist/prototype.js" type="text/javascript"></script> 9 <script src="../lib/unittest.js" type="text/javascript"></script> 10 <link rel="stylesheet" href="../test.css" type="text/css" /> 11 <style type="text/css" media="screen"> 12 /* <![CDATA[ */ 13 #testcss1 { font-size:11px; color: #f00; } 14 #testcss2 { font-size:12px; color: #0f0; display: none; } 15 /* ]]> */ 16 </style> 16 17 </head> 17 18 <body> 18 19 <h1>Prototype Unit test file</h1> 19 20 <p> 20 Test of utility functions in selector.js21 Test of utility functions in selector.js 21 22 </p> 22 23 23 24 <div id="fixtures" style="display: none"> 24 <h1 class="title">Some title <span>here</span></h1>25 <p id="p" class="first summary">26 <strong id="strong">This</strong> is a short blurb27 <a id="link_1" class="first internal" href="#">with a link</a> or28 <a id="link_2" class="internal highlight" href="#"><em id="em">two</em></a>.29 Or <cite id="with_title" title="hello world!">three</cite>.30 </p>31 <ul id="list">32 <li id="item_1" class="first"><a id="link_3" href="#" class="external"><span id="span">Another link</span></a></li>33 <li id="item_2">Some text</li>25 <h1 class="title">Some title <span>here</span></h1> 26 <p id="p" class="first summary"> 27 <strong id="strong">This</strong> is a short blurb 28 <a id="link_1" class="first internal" rel="external nofollow" href="#">with a link</a> or 29 <a id="link_2" class="internal highlight" href="#"><em id="em">two</em></a>. 30 Or <cite id="with_title" title="hello world!">a citation</cite>. 31 </p> 32 <ul id="list"> 33 <li id="item_1" class="first"><a id="link_3" href="#" class="external"><span id="span">Another link</span></a></li> 34 <li id="item_2">Some text</li> 34 35 <li id="item_3" xml:lang="es-us" class="">Otra cosa</li> 35 </ul> 36 37 <!-- this form has a field with the name 'id', 38 therefore its ID property won't be 'troubleForm': --> 39 <form id="troubleForm"><input type="hidden" name="id" /></form> 40 </div> 36 </ul> 37 38 <!-- this form has a field with the name 'id', 39 therefore its ID property won't be 'troubleForm': --> 40 <form id="troubleForm"> 41 <input type="hidden" name="id" id="hidden" /> 42 <input type="text" name="disabled_text_field" id="disabled_text_field" disabled="disabled" /> 43 <input type="text" name="enabled_text_field" id="enabled_text_field" /> 44 <input type="checkbox" name="checkboxes" id="checked_box" checked="checked" value="Checked" /> 45 <input type="checkbox" name="checkboxes" id="unchecked_box" value="Unchecked"/> 46 <input type="radio" name="radiobuttons" id="checked_radio" checked="checked" value="Checked" /> 47 <input type="radio" name="radiobuttons" id="unchecked_radio" value="Unchecked" /> 48 </form> 49 50 <div id="level1"> 51 <span id="level2_1"> 52 <span id="level3_1"></span> 53 <!-- This comment should be ignored by the adjacent selector --> 54 <span id="level3_2"></span> 55 </span> 56 <span id="level2_2"> 57 <em id="level_only_child"> 58 </em> 59 </span> 60 <div id="level2_3"></div> 61 </div> <!-- #level1 --> 62 63 <div id="dupContainer"> 64 <span id="dupL1"> 65 <span id="dupL2"> 66 <span id="dupL3"> 67 <span id="dupL4"> 68 <span id="dupL5"></span> 69 </span> 70 </span> 71 </span> 72 </span> 73 </div> <!-- #dupContainer --> 74 75 <div id="grandfather"> grandfather 76 <div id="father" class="brothers men"> father 77 <div id="son"> son </div> 78 </div> 79 <div id="uncle" class="brothers men"> uncle </div> 80 </div> 81 82 <form id="commaParent" title="commas,are,good"> 83 <input type="hidden" id="commaChild" name="foo" value="#commaOne,#commaTwo" /> 84 <input type="hidden" id="commaTwo" name="foo2" value="oops" /> 85 </form> 86 87 </div> <!-- #fixtures --> 41 88 42 89 <!-- Log output --> … … 46 93 <script type="text/javascript" language="javascript" charset="utf-8"> 47 94 // <![CDATA[ 48 new Test.Unit.Runner({ 49 testSelectorWithTagName: function() {with(this) { 50 assertEnumEqual($A(document.getElementsByTagName('li')), $$('li')); 51 assertEnumEqual([$('strong')], $$('strong')); 52 assertEnumEqual([], $$('nonexistent')); 53 assertEnumEqual($A(document.getElementsByTagName('*')), $$('*')); 54 }}, 55 56 testSelectorWithId: function() {with(this) { 57 assertEnumEqual([$('fixtures')], $$('#fixtures')); 58 assertEnumEqual([], $$('#nonexistent')); 59 assertEnumEqual([$('troubleForm')], $$('#troubleForm')); 60 }}, 61 62 testSelectorWithClassName: function() {with(this) { 63 assertEnumEqual($('p', 'link_1', 'item_1'), $$('.first')); 64 assertEnumEqual([], $$('.second')); 65 }}, 66 67 testSelectorWithTagNameAndId: function() {with(this) { 68 assertEnumEqual([$('strong')], $$('strong#strong')); 69 assertEnumEqual([], $$('p#strong')); 70 }}, 71 72 testSelectorWithTagNameAndClassName: function() {with(this) { 73 assertEnumEqual($('link_1', 'link_2'), $$('a.internal')); 74 assertEnumEqual([$('link_2')], $$('a.internal.highlight')); 75 assertEnumEqual([$('link_2')], $$('a.highlight.internal')); 76 assertEnumEqual([], $$('a.highlight.internal.nonexistent')); 77 }}, 78 79 testSelectorWithIdAndClassName: function() {with(this) { 80 assertEnumEqual([$('link_2')], $$('#link_2.internal')); 81 assertEnumEqual([$('link_2')], $$('.internal#link_2')); 82 assertEnumEqual([$('link_2')], $$('#link_2.internal.highlight')); 83 assertEnumEqual([], $$('#link_2.internal.nonexistent')); 84 }}, 85 86 testSelectorWithTagNameAndIdAndClassName: function() {with(this) { 87 assertEnumEqual([$('link_2')], $$('a#link_2.internal')); 88 assertEnumEqual([$('link_2')], $$('a.internal#link_2')); 89 assertEnumEqual([$('item_1')], $$('li#item_1.first')); 90 assertEnumEqual([], $$('li#item_1.nonexistent')); 91 assertEnumEqual([], $$('li#item_1.first.nonexistent')); 92 }}, 93 94 test$$MatchesAncestryWithTokensSeparatedByWhitespace: function() {with(this) { 95 assertEnumEqual($('em', 'span'), $$('#fixtures a *')); 96 assertEnumEqual([$('p')], $$('div#fixtures p')); 97 }}, 98 99 test$$CombinesResultsWhenMultipleExpressionsArePassed: function() {with(this) { 100 assertEnumEqual($('link_1', 'link_2', 'item_1', 'item_2', 'item_3'), $$('#p a', ' ul#list li ')); 101 }}, 102 103 testSelectorWithTagNameAndAttributeExistence: function() {with(this) { 104 assertEnumEqual($$('#fixtures h1'), $$('h1[class]')); 105 assertEnumEqual($$('#fixtures h1'), $$('h1[CLASS]')); 106 assertEnumEqual([$('item_3')], $$('li#item_3[class]')); 107 }}, 108 109 testSelectorWithTagNameAndSpecificAttributeValue: function() {with(this) { 110 assertEnumEqual($('link_1', 'link_2', 'link_3'), $$('a[href="#"]')); 111 assertEnumEqual($('link_1', 'link_2', 'link_3'), $$('a[href=#]')); 112 }}, 113 114 testSelectorWithTagNameAndWhitespaceTokenizedAttributeValue: function() {with(this) { 115 assertEnumEqual($('link_1', 'link_2'), $$('a[class~="internal"]')); 116 assertEnumEqual($('link_1', 'link_2'), $$('a[class~=internal]')); 117 }}, 118 119 testSelectorWithUniversalAndHyphenTokenizedAttributeValue: function() {with(this) { 120 assertEnumEqual([$('item_3')], $$('*[xml:lang|="es"]')); 121 assertEnumEqual([$('item_3')], $$('*[xml:lang|="ES"]')); 122 }}, 123 124 testSelectorWithTagNameAndNegatedAttributeValue: function() {with(this) { 125 assertEnumEqual([], $$('a[href!=#]')); 126 }}, 127 128 test$$WithNestedAttributeSelectors: function() {with(this) { 129 assertEnumEqual([$('strong')], $$('div[style] p[id] strong')); 130 }}, 131 132 testSelectorWithMultipleConditions: function() {with(this) { 133 assertEnumEqual([$('link_3')], $$('a[class~=external][href="#"]')); 134 assertEnumEqual([], $$('a[class~=external][href!="#"]')); 135 }}, 136 137 testSelectorMatchElements: function() {with(this) { 138 assertElementsMatch(Selector.matchElements($('list').descendants(), 'li'), '#item_1', '#item_2', '#item_3'); 139 assertElementsMatch(Selector.matchElements($('fixtures').descendants(), 'a.internal'), '#link_1', '#link_2'); 140 assertEnumEqual([], Selector.matchElements($('fixtures').descendants(), 'p.last')); 141 }}, 142 143 testSelectorFindElement: function() {with(this) { 144 assertElementMatches(Selector.findElement($('list').descendants(), 'li'), 'li#item_1.first'); 145 assertElementMatches(Selector.findElement($('list').descendants(), 'li', 1), 'li#item_2'); 146 assertElementMatches(Selector.findElement($('list').descendants(), 'li#item_3'), 'li'); 147 assertEqual(undefined, Selector.findElement($('list').descendants(), 'em')); 148 }}, 149 150 testSelectorWithSpaceInAttributeValue: function() {with(this) { 151 assertEnumEqual([$('with_title')], $$('cite[title="hello world!"]')); 152 }} 153 }, 'testlog'); 95 96