Changeset 1155
- Timestamp:
- 10/08/06 09:18:22 (2 years ago)
- Files:
-
- mochikit/branches/selector/MochiKit/Selector.js (modified) (11 diffs)
Legend:
- Unmodified
- Added
- Removed
- Modified
- Copied
- Moved
mochikit/branches/selector/MochiKit/Selector.js
r1152 r1155 24 24 this.expression = expression.toString().replace(/(^\s+|\s+$)/g, ''); 25 25 this.parseExpression(); 26 this.compileMatcher(); 26 this.compileMatcher(); 27 27 }; 28 28 29 29 MochiKit.Selector.Selector.prototype = { 30 30 31 31 __class__: MochiKit.Selector.Selector, 32 33 parseExpression: function() { 34 function abort(message) { throw 'Parse error in selector: ' + message; } 35 36 if (this.expression == '') abort('empty expression'); 32 33 parseExpression: function () { 34 function abort(message) { 35 throw 'Parse error in selector: ' + message; 36 } 37 38 if (this.expression == '') { 39 abort('empty expression'); 40 } 37 41 38 42 var params = this.params, expr = this.expression, match, modifier, clause, rest; … … 43 47 } 44 48 45 if (expr == '*') return this.params.wildcard = true; 49 if (expr == '*') { 50 return this.params.wildcard = true; 51 } 46 52 47 53 while (match = expr.match(/^([^a-z0-9_-])?([a-z0-9_-]+(?:\([^)]*\))?)(.*)/i)) { 48 54 modifier = match[1], clause = match[2], rest = match[3]; 49 55 switch (modifier) { 50 case '#': params.id = clause; break; 51 case '.': params.classNames.push(clause); break; 52 case ':': params.pseudoClassNames.push(clause); break; 56 case '#': 57 params.id = clause; 58 break; 59 case '.': 60 params.classNames.push(clause); 61 break; 62 case ':': 63 params.pseudoClassNames.push(clause); 64 break; 53 65 case '': 54 case undefined: params.tagName = clause.toUpperCase(); break; 55 default: abort(repr(expr)); 66 case undefined: 67 params.tagName = clause.toUpperCase(); 68 break; 69 default: 70 abort(repr(expr)); 56 71 } 57 72 expr = rest; 58 73 } 59 74 60 if (expr.length > 0) abort(repr(expr)); 61 }, 62 63 buildMatchExpression: function() { 64 var params = this.params, conditions = [], clause; 65 75 if (expr.length > 0) { 76 abort(repr(expr)); 77 } 78 }, 79 80 buildMatchExpression: function () { 81 var params = this.params; 82 var conditions = []; 83 var clause; 84 66 85 function childElements(element) { 67 return "filter(function (node){ return node.nodeType == Node.ELEMENT_NODE; }, " + element + ".childNodes)";68 } 69 70 if (params.wildcard) 86 return "filter(function (node){ return node.nodeType == Node.ELEMENT_NODE; }, " + element + ".childNodes)"; 87 } 88 89 if (params.wildcard) { 71 90 conditions.push('true'); 72 if (clause = params.id) 91 } 92 if (clause = params.id) { 73 93 conditions.push('element.id == ' + repr(clause)); 74 if (clause = params.tagName) 94 } 95 if (clause = params.tagName) { 75 96 conditions.push('element.tagName.toUpperCase() == ' + repr(clause)); 76 if ((clause = params.classNames).length > 0) 77 for (var i = 0; i < clause.length; i++) 97 } 98 if ((clause = params.classNames).length > 0) { 99 for (var i = 0; i < clause.length; i++) { 78 100 conditions.push('hasElementClass(element, ' + repr(clause[i]) + ')'); 79 if ((clause = params.pseudoClassNames).length > 0) 101 } 102 } 103 if ((clause = params.pseudoClassNames).length > 0) { 80 104 for (var i = 0; i < clause.length; i++) { 81 105 var match = clause[i].match(/^([^(]+)(?:\((.*)\))?$/); 82 var pseudoClass = match[1], pseudoClassArgument = match[2] 106 var pseudoClass = match[1]; 107 var pseudoClassArgument = match[2]; 83 108 switch (pseudoClass) { 84 109 case 'root': … … 89 114 case 'nth-last-of-type': 90 115 match = pseudoClassArgument.match(/^((?:(\d+)n\+)?(\d+)|odd|even)$/); 91 if (!match) throw "Invalid argument to pseudo element nth-child: " + pseudoClassArgument; 92 var a,b; 93 if (match[0] == 'odd') a = 2, b = 1; 94 else if (match[0] == 'even') a = 2, b = 0; 95 else a = match[2] && parseInt(match), b = parseInt(match[3]); 96 conditions.push('this.nthChild(element,' + a + ',' + b 97 + ',' + !!pseudoClass.match('^nth-last') // Reverse 98 + ',' + !!pseudoClass.match('of-type$') // Restrict to same tagName 99 + ')'); 116 if (!match) { 117 throw "Invalid argument to pseudo element nth-child: " + pseudoClassArgument; 118 } 119 var a, b; 120 if (match[0] == 'odd') { 121 a = 2; 122 b = 1; 123 } else if (match[0] == 'even') { 124 a = 2; 125 b = 0; 126 } else { 127 a = match[2] && parseInt(match); 128 b = parseInt(match[3]); 129 } 130 conditions.push('this.nthChild(element,' + a + ',' + b 131 + ',' + !!pseudoClass.match('^nth-last') // Reverse 132 + ',' + !!pseudoClass.match('of-type$') // Restrict to same tagName 133 + ')'); 100 134 break; 101 135 case 'first-child': … … 135 169 } 136 170 } 171 } 137 172 if (clause = params.attributes) { 138 map(function (attribute) {173 map(function (attribute) { 139 174 var value = 'element.getAttribute(' + repr(attribute.name) + ')'; 140 var splitValueBy = function (delimiter) {175 var splitValueBy = function (delimiter) { 141 176 return value + ' && ' + value + '.split(' + repr(delimiter) + ')'; 142 177 } 143 178 144 179 switch (attribute.operator) { 145 case '=': conditions.push(value + ' == ' + repr(attribute.value)); break; 146 case '~=': conditions.push('findValue(' + splitValueBy(' ') + ', ' + repr(attribute.value) + ') > -1'); break; 147 case '^=': conditions.push(value + '.substring(0, ' + attribute.value.length + ') == ' + repr(attribute.value)); break; 148 case '$=': conditions.push(value + '.substring(' + value + '.length - ' + attribute.value.length + ') == ' + repr(attribute.value)); break; 149 case '*=': conditions.push(value + '.match(' + repr(attribute.value) + ')'); break; 150 case '|=': conditions.push( 151 splitValueBy('-') + '[0].toUpperCase() == ' + repr(attribute.value.toUpperCase()) 152 ); break; 153 case '!=': conditions.push(value + ' != ' + repr(attribute.value)); break; 180 case '=': 181 conditions.push(value + ' == ' + repr(attribute.value)); 182 break; 183 case '~=': 184 conditions.push('findValue(' + splitValueBy(' ') + ', ' + repr(attribute.value) + ') > -1'); 185 break; 186 case '^=': 187 conditions.push(value + '.substring(0, ' + attribute.value.length + ') == ' + repr(attribute.value)); 188 break; 189 case '$=': 190 conditions.push(value + '.substring(' + value + '.length - ' + attribute.value.length + ') == ' + repr(attribute.value)); 191 break; 192 case '*=': 193 conditions.push(value + '.match(' + repr(attribute.value) + ')'); 194 break; 195 case '|=': 196 conditions.push( 197 splitValueBy('-') + '[0].toUpperCase() == ' + repr(attribute.value.toUpperCase()) 198 ); 199 break; 200 case '!=': 201 conditions.push(value + ' != ' + repr(attribute.value)); 202 break; 154 203 case '': 155 case undefined: conditions.push(value + ' != null'); break; 156 default: throw 'Unknown operator ' + attribute.operator + ' in selector'; 204 case undefined: 205 conditions.push(value + ' != null'); 206 break; 207 default: 208 throw 'Unknown operator ' + attribute.operator + ' in selector'; 157 209 } 158 210 }, clause); … … 161 213 return conditions.join(' && '); 162 214 }, 163 164 compileMatcher: function () {215 216 compileMatcher: function () { 165 217 this.match = new Function('element', 'if (!element.tagName) return false; \ 166 218 return ' + this.buildMatchExpression()); 167 219 }, 168 169 nthChild: function(element, a, b, reverse, sametag){ 170 var siblings = filter(function(node){ return node.nodeType == Node.ELEMENT_NODE; }, element.parentNode.childNodes); 171 if (sametag) siblings = filter(function(node){ return node.tagName == element.tagName}, siblings); 172 if (reverse) siblings = MochiKit.Iter.reversed(siblings); 220 221 nthChild: function (element, a, b, reverse, sametag){ 222 var siblings = filter(function (node) { 223 return node.nodeType == Node.ELEMENT_NODE; 224 }, element.parentNode.childNodes); 225 if (sametag) { 226 siblings = filter(function (node) { 227 return node.tagName == element.tagName; 228 }, siblings); 229 } 230 if (reverse) { 231 siblings = MochiKit.Iter.reversed(siblings); 232 } 173 233 if (a) { 174 var actualIndex = findIdentical(siblings, element);234 var actualIndex = MochiKit.Base.findIdentical(siblings, element); 175 235 return ((actualIndex + 1 - b) / a) % 1 == 0; 176 236 } else { 177 return b == findIdentical(siblings, element) + 1;178 } 179 }, 180 181 findElements: function (scope, axis) {237 return b == MochiKit.Base.findIdentical(siblings, element) + 1; 238 } 239 }, 240 241 findElements: function (scope, axis) { 182 242 var element; 183 184 if (axis == undefined) axis = ""; 185 243 244 if (axis == undefined) { 245 axis = ""; 246 } 247 186 248 function inScope(element, scope) { 187 if (axis == "") 249 if (axis == "") { 188 250 return MochiKit.DOM.isChildNode(element, scope); 189 else if (axis == ">")251 } else if (axis == ">") { 190 252 return element.parentNode == scope; 191 else if (axis == "+")253 } else if (axis == "+") { 192 254 return element.previousSibling == scope; 193 else if (axis == "~") {255 } else if (axis == "~") { 194 256 while (element.previousSibling) { 195 257 if (element.previousSibling == scope) return true; … … 202 264 } 203 265 204 if (element = $(this.params.id)) 205 if (this.match(element)) 206 if (!scope || inScope(element, scope)) 266 if (element = $(this.params.id)) { 267 if (this.match(element)) { 268 if (!scope || inScope(element, scope)) { 207 269 return [element]; 270 } 271 } 272 } 208 273 209 274 function nextSiblingElement(node) { 210 275 node = node.nextSibling; 211 while (node && node.nodeType != Node.ELEMENT_NODE) 276 while (node && node.nodeType != Node.ELEMENT_NODE) { 212 277 node = node.nextSibling; 278 } 213 279 return node; 214 280 } … … 217 283 scope = (scope || document).getElementsByTagName(this.params.tagName || '*'); 218 284 } else if (axis == ">") { 219 if (!scope) throw "> combinator not allowed without preceeding expression"; 220 scope = filter(function(node){ return node.nodeType == Node.ELEMENT_NODE; }, scope.childNodes); 285 if (!scope) { 286 throw "> combinator not allowed without preceeding expression"; 287 } 288 scope = filter(function (node) { 289 return node.nodeType == Node.ELEMENT_NODE; 290 }, scope.childNodes); 221 291 } else if (axis == "+") { 222 if (!scope) throw "+ combinator not allowed without preceeding expression"; 292 if (!scope) { 293 throw "+ combinator not allowed without preceeding expression"; 294 } 223 295 scope = nextSiblingElement(scope) && [nextSiblingElement(scope)]; 224 296 } else if (axis == "~") { 225 if (!scope) throw "~ combinator not allowed without preceeding expression"; 297 if (!scope) { 298 throw "~ combinator not allowed without preceeding expression"; 299 } 226 300 var newscope = new Array(); 227 301 while (nextSiblingElement(scope)) { … … 231 305 scope = newscope; 232 306 } 233 234 if (!scope) return []; 307 308 if (!scope) { 309 return []; 310 } 235 311 236 312 var results = []; 237 for (var i = 0; i < scope.length; i++) 238 if (this.match(element = scope[i])) 313 for (var i = 0; i < scope.length; i++) { 314 if (this.match(element = scope[i])) { 239 315 results.push(element); 316 } 317 } 240 318 241 319 return results; 242 320 }, 243 321 244 toString: function () {322 toString: function () { 245 323 return this.expression; 246 324 } 247 325 248 326 }; 249 327 250 328 MochiKit.Base.update(MochiKit.Selector, { 251 matchElements: function (elements, expression) {329 matchElements: function (elements, expression) { 252 330 var selector = new Selector(expression); 253 331 return elements.select(selector.match.bind(selector)).collect(Element.extend); 254 332 }, 255 256 findElement: function(elements, expression, index) { 257 if (typeof expression == 'number') index = expression, expression = false; 333 334 findElement: function (elements, expression, index) { 335 if (typeof expression == 'number') { 336 index = expression; 337 expression = false; 338 } 258 339 return Selector.matchElements(elements, expression || '*')[index || 0]; 259 340 }, 260 261 findChildElements: function (element, expressions) {262 return flattenArray(map(function (expression){341 342 findChildElements: function (element, expressions) { 343 return flattenArray(map(function (expression) { 263 344 var nextScope = ""; 264 return reduce(function (results, expr){345 return reduce(function (results, expr) { 265 346 if (match = expr.match(/^[>+~]$/)) { 266 347 nextScope = match[0]; … … 268 349 } else { 269 350 var selector = new MochiKit.Selector.Selector(expr); 270 var elements = reduce(function (elements, result){351 var elements = reduce(function (elements, result) { 271 352 return extend(elements, selector.findElements(result || element, nextScope)); 272 353 }, results, []); … … 277 358 }, expressions)); 278 359 } 279 360 280 361 }); 281 362 … … 283 364 return MochiKit.Selector.findChildElements(document, arguments); 284 365 } 366
