From 9e4a7ce8e8fcf9a12d89ae62c671f8a3c6c8c93e Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Sat, 19 Sep 2009 14:40:01 -0500 Subject: [PATCH 01/28] Don't call private Selector methods in Element#previous and Element#next --- src/dom/dom.js | 37 ++++++++++++++++++++++++++----------- 1 file changed, 26 insertions(+), 11 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 3fae168..1301bc7 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -418,12 +418,18 @@ Element.Methods = { * won't do!) of `element` that points to a single DOM node (e.g., * `nextSibling` or `parentNode`). **/ - recursivelyCollect: function(element, property) { + recursivelyCollect: function(element, property, maximumLength) { element = $(element); + maximumLength = maximumLength || -1; var elements = []; - while (element = element[property]) + + while (element = element[property]) { if (element.nodeType == 1) elements.push(Element.extend(element)); + if (elements.length == maximumLength) + break; + } + return elements; }, @@ -480,7 +486,7 @@ Element.Methods = { * Collects all of `element`'s previous siblings and returns them as an * array of elements. **/ - previousSiblings: function(element) { + previousSiblings: function(element, maximumLength) { return Element.recursivelyCollect(element, 'previousSibling'); }, @@ -564,10 +570,14 @@ Element.Methods = { **/ previous: function(element, expression, index) { element = $(element); - if (arguments.length == 1) return $(Selector.handlers.previousElementSibling(element)); - var previousSiblings = Element.previousSiblings(element); - return Object.isNumber(expression) ? previousSiblings[expression] : - Selector.findElement(previousSiblings, expression, index); + if (Object.isNumber(expression)) index = expression, expression = false; + if (!Object.isNumber(index)) index = 0; + + if (expression) { + return Selector.findElement(element.previousSiblings(), expression, index); + } else { + return element.recursivelyCollect("previousSibling", index + 1)[index]; + } }, /** @@ -582,10 +592,15 @@ Element.Methods = { **/ next: function(element, expression, index) { element = $(element); - if (arguments.length == 1) return $(Selector.handlers.nextElementSibling(element)); - var nextSiblings = Element.nextSiblings(element); - return Object.isNumber(expression) ? nextSiblings[expression] : - Selector.findElement(nextSiblings, expression, index); + if (Object.isNumber(expression)) index = expression, expression = false; + if (!Object.isNumber(index)) index = 0; + + if (expression) { + return Selector.findElement(element.nextSiblings(), expression, index); + } else { + var maximumLength = Object.isNumber(index) ? index + 1 : 1; + return element.recursivelyCollect("nextSibling", index + 1)[index]; + } }, From 4dd878f237e79db11a6532e3f74059c9b908069c Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Sat, 19 Sep 2009 14:42:59 -0500 Subject: [PATCH 02/28] Replace Prototype's Selector implementation with Sizzle --- .gitmodules | 3 + Rakefile | 3 +- src/dom/selector.js | 807 +------------------------------------ test/unit/selector_test.js | 8 - vendor/sizzle | 1 + 5 files changed, 24 insertions(+), 798 deletions(-) create mode 160000 vendor/sizzle diff --git a/.gitmodules b/.gitmodules index 26e3784..00dca52 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,3 +11,6 @@ path = vendor/sprockets url = git://github.com/sstephenson/sprockets.git +[submodule "vendor/sizzle"] + path = vendor/sizzle + url = git://github.com/jeresig/sizzle.git diff --git a/Rakefile b/Rakefile index 60889c5..3b08a0a 100755 --- a/Rakefile +++ b/Rakefile @@ -9,6 +9,7 @@ module PrototypeHelper DOC_DIR = File.join(ROOT_DIR, 'doc') TEMPLATES_DIR = File.join(ROOT_DIR, 'templates') PKG_DIR = File.join(ROOT_DIR, 'pkg') + SIZZLE_DIR = File.join(ROOT_DIR, 'vendor', 'sizzle') TEST_DIR = File.join(ROOT_DIR, 'test') TEST_UNIT_DIR = File.join(TEST_DIR, 'unit') TMP_DIR = File.join(TEST_UNIT_DIR, 'tmp') @@ -18,7 +19,7 @@ module PrototypeHelper require_sprockets secretary = Sprockets::Secretary.new( :root => File.join(ROOT_DIR, path), - :load_path => [SRC_DIR], + :load_path => [SRC_DIR, SIZZLE_DIR], :source_files => [source], :strip_comments => strip_comments ) diff --git a/src/dom/selector.js b/src/dom/selector.js index 947fd89..e3606a1 100644 --- a/src/dom/selector.js +++ b/src/dom/selector.js @@ -1,6 +1,4 @@ -/* Portions of the Selector class are derived from Jack Slocum's DomQuery, - * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style - * license. Please see http://www.yui-ext.com/ for more information. */ +//= require /** section: DOM * class Selector @@ -17,139 +15,8 @@ var Selector = Class.create({ **/ initialize: function(expression) { this.expression = expression.strip(); - - if (this.shouldUseSelectorsAPI()) { - this.mode = 'selectorsAPI'; - } else if (this.shouldUseXPath()) { - this.mode = 'xpath'; - this.compileXPathMatcher(); - } else { - this.mode = "normal"; - this.compileMatcher(); - } - }, - - shouldUseXPath: (function() { - - // Some versions of Opera 9.x produce incorrect results when using XPath - // with descendant combinators. - // see: http://opera.remcol.ath.cx/bugs/index.php?action=bug&id=652 - var IS_DESCENDANT_SELECTOR_BUGGY = (function(){ - var isBuggy = false; - if (document.evaluate && window.XPathResult) { - var el = document.createElement('div'); - el.innerHTML = '
'; - - var xpath = ".//*[local-name()='ul' or local-name()='UL']" + - "//*[local-name()='li' or local-name()='LI']"; - - var result = document.evaluate(xpath, el, null, - XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); - - isBuggy = (result.snapshotLength !== 2); - el = null; - } - return isBuggy; - })(); - - return function() { - if (!Prototype.BrowserFeatures.XPath) return false; - - var e = this.expression; - - // Safari 3 chokes on :*-of-type and :empty - if (Prototype.Browser.WebKit && - (e.include("-of-type") || e.include(":empty"))) - return false; - - // XPath can't do namespaced attributes, nor can it read - // the "checked" property from DOM nodes - if ((/(\[[\w-]*?:|:checked)/).test(e)) - return false; - - if (IS_DESCENDANT_SELECTOR_BUGGY) return false; - - return true; - } - - })(), - - shouldUseSelectorsAPI: function() { - if (!Prototype.BrowserFeatures.SelectorsAPI) return false; - - if (Selector.CASE_INSENSITIVE_CLASS_NAMES) return false; - - if (!Selector._div) Selector._div = new Element('div'); - - // Make sure the browser treats the selector as valid. Test on an - // isolated element to minimize cost of this check. - try { - Selector._div.querySelector(this.expression); - } catch(e) { - return false; - } - - return true; - }, - - compileMatcher: function() { - var e = this.expression, ps = Selector.patterns, h = Selector.handlers, - c = Selector.criteria, le, p, m, len = ps.length, name; - - if (Selector._cache[e]) { - this.matcher = Selector._cache[e]; - return; - } - - this.matcher = ["this.matcher = function(root) {", - "var r = root, h = Selector.handlers, c = false, n;"]; - - while (e && le != e && (/\S/).test(e)) { - le = e; - for (var i = 0; i [Element...] * - root (Element || document): A "scope" to search within. All results will @@ -158,644 +25,29 @@ var Selector = Class.create({ * Searches the document for elements that match the instance's CSS * selector. **/ - findElements: function(root) { - root = root || document; - var e = this.expression, results; - - switch (this.mode) { - case 'selectorsAPI': - // querySelectorAll queries document-wide, then filters to descendants - // of the context element. That's not what we want. - // Add an explicit context to the selector if necessary. - if (root !== document) { - var oldId = root.id, id = $(root).identify(); - // Escape special characters in the ID. - id = id.replace(/([\.:])/g, "\\$1"); - e = "#" + id + " " + e; - } - - results = $A(root.querySelectorAll(e)).map(Element.extend); - root.id = oldId; - - return results; - case 'xpath': - return document._getElementsByXPath(this.xpath, root); - default: - return this.matcher(root); - } + findElements: function(rootElement) { + return Sizzle(this.expression, rootElement || document); }, - + /** * Selector#match(element) -> Boolean * * Tests whether a `element` matches the instance's CSS selector. **/ match: function(element) { - this.tokens = []; - - var e = this.expression, ps = Selector.patterns, as = Selector.assertions; - var le, p, m, len = ps.length, name; - - while (e && le !== e && (/\S/).test(e)) { - le = e; - for (var i = 0; i"; + return "#"; } }); -if (Prototype.BrowserFeatures.SelectorsAPI && - document.compatMode === 'BackCompat') { - // Versions of Safari 3 before 3.1.2 treat class names case-insensitively in - // quirks mode. If we detect this behavior, we should use a different - // approach. - Selector.CASE_INSENSITIVE_CLASS_NAMES = (function(){ - var div = document.createElement('div'), - span = document.createElement('span'); - - div.id = "prototype_test_id"; - span.className = 'Test'; - div.appendChild(span); - var isIgnored = (div.querySelector('#prototype_test_id .test') !== null); - div = span = null; - return isIgnored; - })(); -} - Object.extend(Selector, { - _cache: { }, - - xpath: { - descendant: "//*", - child: "/*", - adjacent: "/following-sibling::*[1]", - laterSibling: '/following-sibling::*', - tagName: function(m) { - if (m[1] == '*') return ''; - return "[local-name()='" + m[1].toLowerCase() + - "' or local-name()='" + m[1].toUpperCase() + "']"; - }, - className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", - id: "[@id='#{1}']", - attrPresence: function(m) { - m[1] = m[1].toLowerCase(); - return new Template("[@#{1}]").evaluate(m); - }, - attr: function(m) { - m[1] = m[1].toLowerCase(); - m[3] = m[5] || m[6]; - return new Template(Selector.xpath.operators[m[2]]).evaluate(m); - }, - pseudo: function(m) { - var h = Selector.xpath.pseudos[m[1]]; - if (!h) return ''; - if (Object.isFunction(h)) return h(m); - return new Template(Selector.xpath.pseudos[m[1]]).evaluate(m); - }, - operators: { - '=': "[@#{1}='#{3}']", - '!=': "[@#{1}!='#{3}']", - '^=': "[starts-with(@#{1}, '#{3}')]", - '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", - '*=': "[contains(@#{1}, '#{3}')]", - '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", - '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" - }, - pseudos: { - 'first-child': '[not(preceding-sibling::*)]', - 'last-child': '[not(following-sibling::*)]', - 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', - 'empty': "[count(*) = 0 and (count(text()) = 0)]", - 'checked': "[@checked]", - 'disabled': "[(@disabled) and (@type!='hidden')]", - 'enabled': "[not(@disabled) and (@type!='hidden')]", - 'not': function(m) { - var e = m[6], p = Selector.patterns, - x = Selector.xpath, le, v, len = p.length, name; - - var exclusion = []; - while (e && le != e && (/\S/).test(e)) { - le = e; - for (var i = 0; i= 0)]"; - return new Template(predicate).evaluate({ - fragment: fragment, a: a, b: b }); - } - } - } - }, - - criteria: { - tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', - className: 'n = h.className(n, r, "#{1}", c); c = false;', - id: 'n = h.id(n, r, "#{1}", c); c = false;', - attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;', - attr: function(m) { - m[3] = (m[5] || m[6]); - return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m); - }, - pseudo: function(m) { - if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); - return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); - }, - descendant: 'c = "descendant";', - child: 'c = "child";', - adjacent: 'c = "adjacent";', - laterSibling: 'c = "laterSibling";' - }, - - patterns: [ - // combinators must be listed first - // (and descendant needs to be last combinator) - { name: 'laterSibling', re: /^\s*~\s*/ }, - { name: 'child', re: /^\s*>\s*/ }, - { name: 'adjacent', re: /^\s*\+\s*/ }, - { name: 'descendant', re: /^\s/ }, - - // selectors follow - { name: 'tagName', re: /^\s*(\*|[\w\-]+)(\b|$)?/ }, - { name: 'id', re: /^#([\w\-\*]+)(\b|$)/ }, - { name: 'className', re: /^\.([\w\-\*]+)(\b|$)/ }, - { name: 'pseudo', re: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/ }, - { name: 'attrPresence', re: /^\[((?:[\w-]+:)?[\w-]+)\]/ }, - { name: 'attr', re: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ } - ], - - // for Selector.match and Element#match - assertions: { - tagName: function(element, matches) { - return matches[1].toUpperCase() == element.tagName.toUpperCase(); - }, - - className: function(element, matches) { - return Element.hasClassName(element, matches[1]); - }, - - id: function(element, matches) { - return element.id === matches[1]; - }, - - attrPresence: function(element, matches) { - return Element.hasAttribute(element, matches[1]); - }, - - attr: function(element, matches) { - var nodeValue = Element.readAttribute(element, matches[1]); - return nodeValue && Selector.operators[matches[2]](nodeValue, matches[5] || matches[6]); - } - }, - - handlers: { - // UTILITY FUNCTIONS - // joins two collections - concat: function(a, b) { - for (var i = 0, node; node = b[i]; i++) - a.push(node); - return a; - }, - - // marks an array of nodes for counting - mark: function(nodes) { - var _true = Prototype.emptyFunction; - for (var i = 0, node; node = nodes[i]; i++) - node._countedByPrototype = _true; - return nodes; - }, - - unmark: (function(){ - - // IE improperly serializes _countedByPrototype in (inner|outer)HTML - // due to node properties being mapped directly to attributes - var PROPERTIES_ATTRIBUTES_MAP = (function(){ - var el = document.createElement('div'), - isBuggy = false, - propName = '_countedByPrototype', - value = 'x' - el[propName] = value; - isBuggy = (el.getAttribute(propName) === value); - el = null; - return isBuggy; - })(); - - return PROPERTIES_ATTRIBUTES_MAP ? - function(nodes) { - for (var i = 0, node; node = nodes[i]; i++) - node.removeAttribute('_countedByPrototype'); - return nodes; - } : - function(nodes) { - for (var i = 0, node; node = nodes[i]; i++) - node._countedByPrototype = void 0; - return nodes; - } - })(), - - // mark each child node with its position (for nth calls) - // "ofType" flag indicates whether we're indexing for nth-of-type - // rather than nth-child - index: function(parentNode, reverse, ofType) { - parentNode._countedByPrototype = Prototype.emptyFunction; - if (reverse) { - for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { - var node = nodes[i]; - if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; - } - } else { - for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) - if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; - } - }, - - // filters out duplicates and extends all nodes - unique: function(nodes) { - if (nodes.length == 0) return nodes; - var results = [], n; - for (var i = 0, l = nodes.length; i < l; i++) - // use `typeof` operator to prevent errors - if (typeof (n = nodes[i])._countedByPrototype == 'undefined') { - n._countedByPrototype = Prototype.emptyFunction; - results.push(Element.extend(n)); - } - return Selector.handlers.unmark(results); - }, - - // COMBINATOR FUNCTIONS - descendant: function(nodes) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) - h.concat(results, node.getElementsByTagName('*')); - return results; - }, - - child: function(nodes) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) { - for (var j = 0, child; child = node.childNodes[j]; j++) - if (child.nodeType == 1 && child.tagName != '!') results.push(child); - } - return results; - }, - - adjacent: function(nodes) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - var next = this.nextElementSibling(node); - if (next) results.push(next); - } - return results; - }, - - laterSibling: function(nodes) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) - h.concat(results, Element.nextSiblings(node)); - return results; - }, - - nextElementSibling: function(node) { - while (node = node.nextSibling) - if (node.nodeType == 1) return node; - return null; - }, - - previousElementSibling: function(node) { - while (node = node.previousSibling) - if (node.nodeType == 1) return node; - return null; - }, - - // TOKEN FUNCTIONS - tagName: function(nodes, root, tagName, combinator) { - var uTagName = tagName.toUpperCase(); - var results = [], h = Selector.handlers; - if (nodes) { - if (combinator) { - // fastlane for ordinary descendant combinators - if (combinator == "descendant") { - for (var i = 0, node; node = nodes[i]; i++) - h.concat(results, node.getElementsByTagName(tagName)); - return results; - } else nodes = this[combinator](nodes); - if (tagName == "*") return nodes; - } - for (var i = 0, node; node = nodes[i]; i++) - if (node.tagName.toUpperCase() === uTagName) results.push(node); - return results; - } else return root.getElementsByTagName(tagName); - }, - - id: function(nodes, root, id, combinator) { - var targetNode = $(id), h = Selector.handlers; - - if (root == document) { - // We don't have to deal with orphan nodes. Easy. - if (!targetNode) return []; - if (!nodes) return [targetNode]; - } else { - // In IE, we can check sourceIndex to see if root is attached - // to the document. If not (or if sourceIndex is not present), - // we do it the hard way. - if (!root.sourceIndex || root.sourceIndex < 1) { - var nodes = root.getElementsByTagName('*'); - for (var j = 0, node; node = nodes[j]; j++) { - if (node.id === id) return [node]; - } - } - } - - if (nodes) { - if (combinator) { - if (combinator == 'child') { - for (var i = 0, node; node = nodes[i]; i++) - if (targetNode.parentNode == node) return [targetNode]; - } else if (combinator == 'descendant') { - for (var i = 0, node; node = nodes[i]; i++) - if (Element.descendantOf(targetNode, node)) return [targetNode]; - } else if (combinator == 'adjacent') { - for (var i = 0, node; node = nodes[i]; i++) - if (Selector.handlers.previousElementSibling(targetNode) == node) - return [targetNode]; - } else nodes = h[combinator](nodes); - } - for (var i = 0, node; node = nodes[i]; i++) - if (node == targetNode) return [targetNode]; - return []; - } - return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; - }, - - className: function(nodes, root, className, combinator) { - if (nodes && combinator) nodes = this[combinator](nodes); - return Selector.handlers.byClassName(nodes, root, className); - }, - - byClassName: function(nodes, root, className) { - if (!nodes) nodes = Selector.handlers.descendant([root]); - var needle = ' ' + className + ' '; - for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { - nodeClassName = node.className; - if (nodeClassName.length == 0) continue; - if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) - results.push(node); - } - return results; - }, - - attrPresence: function(nodes, root, attr, combinator) { - if (!nodes) nodes = root.getElementsByTagName("*"); - if (nodes && combinator) nodes = this[combinator](nodes); - var results = []; - for (var i = 0, node; node = nodes[i]; i++) - if (Element.hasAttribute(node, attr)) results.push(node); - return results; - }, - - attr: function(nodes, root, attr, value, operator, combinator) { - if (!nodes) nodes = root.getElementsByTagName("*"); - if (nodes && combinator) nodes = this[combinator](nodes); - var handler = Selector.operators[operator], results = []; - for (var i = 0, node; node = nodes[i]; i++) { - var nodeValue = Element.readAttribute(node, attr); - if (nodeValue === null) continue; - if (handler(nodeValue, value)) results.push(node); - } - return results; - }, - - pseudo: function(nodes, name, value, root, combinator) { - if (nodes && combinator) nodes = this[combinator](nodes); - if (!nodes) nodes = root.getElementsByTagName("*"); - return Selector.pseudos[name](nodes, value, root); - } - }, - - pseudos: { - 'first-child': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - if (Selector.handlers.previousElementSibling(node)) continue; - results.push(node); - } - return results; - }, - 'last-child': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - if (Selector.handlers.nextElementSibling(node)) continue; - results.push(node); - } - return results; - }, - 'only-child': function(nodes, value, root) { - var h = Selector.handlers; - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) - results.push(node); - return results; - }, - 'nth-child': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root); - }, - 'nth-last-child': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root, true); - }, - 'nth-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root, false, true); - }, - 'nth-last-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, formula, root, true, true); - }, - 'first-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, "1", root, false, true); - }, - 'last-of-type': function(nodes, formula, root) { - return Selector.pseudos.nth(nodes, "1", root, true, true); - }, - 'only-of-type': function(nodes, formula, root) { - var p = Selector.pseudos; - return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); - }, - - // handles the an+b logic - getIndices: function(a, b, total) { - if (a == 0) return b > 0 ? [b] : []; - return $R(1, total).inject([], function(memo, i) { - if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); - return memo; - }); - }, - - // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type - nth: function(nodes, formula, root, reverse, ofType) { - if (nodes.length == 0) return []; - if (formula == 'even') formula = '2n+0'; - if (formula == 'odd') formula = '2n+1'; - var h = Selector.handlers, results = [], indexed = [], m; - h.mark(nodes); - for (var i = 0, node; node = nodes[i]; i++) { - if (!node.parentNode._countedByPrototype) { - h.index(node.parentNode, reverse, ofType); - indexed.push(node.parentNode); - } - } - if (formula.match(/^\d+$/)) { // just a number - formula = Number(formula); - for (var i = 0, node; node = nodes[i]; i++) - if (node.nodeIndex == formula) results.push(node); - } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b - if (m[1] == "-") m[1] = -1; - var a = m[1] ? Number(m[1]) : 1; - var b = m[2] ? Number(m[2]) : 0; - var indices = Selector.pseudos.getIndices(a, b, nodes.length); - for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { - for (var j = 0; j < l; j++) - if (node.nodeIndex == indices[j]) results.push(node); - } - } - h.unmark(nodes); - h.unmark(indexed); - return results; - }, - - 'empty': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) { - // IE treats comments as element nodes - if (node.tagName == '!' || node.firstChild) continue; - results.push(node); - } - return results; - }, - - 'not': function(nodes, selector, root) { - var h = Selector.handlers, selectorType, m; - var exclusions = new Selector(selector).findElements(root); - h.mark(exclusions); - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!node._countedByPrototype) results.push(node); - h.unmark(exclusions); - return results; - }, - - 'enabled': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (!node.disabled && (!node.type || node.type !== 'hidden')) - results.push(node); - return results; - }, - - 'disabled': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (node.disabled) results.push(node); - return results; - }, - - 'checked': function(nodes, value, root) { - for (var i = 0, results = [], node; node = nodes[i]; i++) - if (node.checked) results.push(node); - return results; - } - }, - - operators: { - '=': function(nv, v) { return nv == v; }, - '!=': function(nv, v) { return nv != v; }, - '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); }, - '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); }, - '*=': function(nv, v) { return nv == v || nv && nv.include(v); }, - '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, - '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() + - '-').include('-' + (v || "").toUpperCase() + '-'); } - }, - - /** - * Selector.split(expression) -> [String...] - * - * Takes a string of CSS selectors separated by commas; returns an array - * of individual selectors. - * - * Safer than doing a naive `Array#split`, since selectors can have commas - * in other places. - **/ - split: function(expression) { - var expressions = []; - expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { - expressions.push(m[1].strip()); - }); - return expressions; - }, - /** * Selector.matchElements(elements, expression) -> [Element...] * @@ -804,14 +56,9 @@ Object.extend(Selector, { * The only nodes returned will be those that match the given CSS selector. **/ matchElements: function(elements, expression) { - var matches = $$(expression), h = Selector.handlers; - h.mark(matches); - for (var i = 0, results = [], element; element = elements[i]; i++) - if (element._countedByPrototype) results.push(element); - h.unmark(matches); - return results; + return Sizzle.matches(expression, elements); }, - + /** * Selector.findElement(elements, expression[, index = 0]) -> Element * Selector.findElement(elements[, index = 0]) -> Element @@ -822,12 +69,10 @@ Object.extend(Selector, { * Returns the `index`th element overall if `expression` is not given. **/ findElement: function(elements, expression, index) { - if (Object.isNumber(expression)) { - index = expression; expression = false; - } - return Selector.matchElements(elements, expression || '*')[index || 0]; + if (Object.isUndefined(index)) index = 0; + return Sizzle.matches(expression, elements)[index]; }, - + /** * Selector.findChildElements(element, expressions) -> [Element...] * @@ -835,33 +80,17 @@ Object.extend(Selector, { * (or selectors) specified in `expressions`. **/ findChildElements: function(element, expressions) { - expressions = Selector.split(expressions.join(',')); - var results = [], h = Selector.handlers; - for (var i = 0, l = expressions.length, selector; i < l; i++) { - selector = new Selector(expressions[i].strip()); - h.concat(results, selector.findElements(element)); - } - return (l > 1) ? h.unique(results) : results; + var results = [], exprs = expressions.toArray(); + while (exprs.length) Sizzle(exprs.shift(), element || document, results); + return results; } }); -if (Prototype.Browser.IE) { - Object.extend(Selector.handlers, { - // IE returns comment nodes on getElementsByTagName("*"). - // Filter them out. - concat: function(a, b) { - for (var i = 0, node; node = b[i]; i++) - if (node.tagName !== "!") a.push(node); - return a; - } - }); -} - /** related to: Selector * $$(expression...) -> [Element...] * * Returns all elements in the document that match the provided CSS selectors. **/ function $$() { - return Selector.findChildElements(document, $A(arguments)); + return Selector.findChildElements(document, $A(arguments)).map(Element.extend); } diff --git a/test/unit/selector_test.js b/test/unit/selector_test.js index fc2eae2..6f11d2e 100644 --- a/test/unit/selector_test.js +++ b/test/unit/selector_test.js @@ -356,14 +356,6 @@ new Test.Unit.Runner({ this.assert(typeof results[2].show == 'function'); }, - testCountedIsNotAnAttribute: function() { - var el = $('list'); - Selector.handlers.mark([el]); - this.assert(!el.innerHTML.include("_counted")); - Selector.handlers.unmark([el]); - this.assert(!el.innerHTML.include("_counted")); - }, - testCopiedNodesGetIncluded: function() { this.assertElementsMatch( Selector.matchElements($('counted_container').descendants(), 'div'), diff --git a/vendor/sizzle b/vendor/sizzle new file mode 160000 index 0000000..e0f5cbc --- /dev/null +++ b/vendor/sizzle @@ -0,0 +1 @@ +Subproject commit e0f5cbc75d12aa78f3ef30930414b2f88da7b2b8 From 79cf30aab1ae1016c6e2666b30435bb7c118418a Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Sat, 19 Sep 2009 16:17:43 -0500 Subject: [PATCH 03/28] Extend matched elements --- src/dom/selector.js | 170 +++++++++++++++++++++++--------------------- 1 file changed, 89 insertions(+), 81 deletions(-) diff --git a/src/dom/selector.js b/src/dom/selector.js index e3606a1..60b201c 100644 --- a/src/dom/selector.js +++ b/src/dom/selector.js @@ -6,91 +6,99 @@ * A class that queries the document for elements that match a given CSS * selector. **/ -var Selector = Class.create({ - /** - * new Selector(expression) - * - expression (String): A CSS selector. - * - * Creates a `Selector` with the given CSS selector. - **/ - initialize: function(expression) { - this.expression = expression.strip(); - }, - - /** - * Selector#findElements(root) -> [Element...] - * - root (Element || document): A "scope" to search within. All results will - * be descendants of this node. - * - * Searches the document for elements that match the instance's CSS - * selector. - **/ - findElements: function(rootElement) { - return Sizzle(this.expression, rootElement || document); - }, - - /** - * Selector#match(element) -> Boolean - * - * Tests whether a `element` matches the instance's CSS selector. - **/ - match: function(element) { - return Sizzle.matches(this.expression, [element]).length == 1; - }, - - toString: function() { - return this.expression; - }, - - inspect: function() { - return "#"; +(function() { + function extend(elements) { + for (var i = 0, length = elements.length; i < length; i++) + elements[i] = Element.extend(elements[i]); + return elements; } -}); -Object.extend(Selector, { - /** - * Selector.matchElements(elements, expression) -> [Element...] - * - * Filters the given collection of elements with `expression`. - * - * The only nodes returned will be those that match the given CSS selector. - **/ - matchElements: function(elements, expression) { - return Sizzle.matches(expression, elements); - }, + window.Selector = Class.create({ + /** + * new Selector(expression) + * - expression (String): A CSS selector. + * + * Creates a `Selector` with the given CSS selector. + **/ + initialize: function(expression) { + this.expression = expression.strip(); + }, - /** - * Selector.findElement(elements, expression[, index = 0]) -> Element - * Selector.findElement(elements[, index = 0]) -> Element - * - * Returns the `index`th element in the collection that matches - * `expression`. - * - * Returns the `index`th element overall if `expression` is not given. - **/ - findElement: function(elements, expression, index) { - if (Object.isUndefined(index)) index = 0; - return Sizzle.matches(expression, elements)[index]; - }, + /** + * Selector#findElements(root) -> [Element...] + * - root (Element || document): A "scope" to search within. All results will + * be descendants of this node. + * + * Searches the document for elements that match the instance's CSS + * selector. + **/ + findElements: function(rootElement) { + return extend(Sizzle(this.expression, rootElement || document)); + }, - /** - * Selector.findChildElements(element, expressions) -> [Element...] + /** + * Selector#match(element) -> Boolean + * + * Tests whether a `element` matches the instance's CSS selector. + **/ + match: function(element) { + return Sizzle.matches(this.expression, [element]).length == 1; + }, + + toString: function() { + return this.expression; + }, + + inspect: function() { + return "#"; + } + }); + + Object.extend(Selector, { + /** + * Selector.matchElements(elements, expression) -> [Element...] + * + * Filters the given collection of elements with `expression`. + * + * The only nodes returned will be those that match the given CSS selector. + **/ + matchElements: function(elements, expression) { + return extend(Sizzle.matches(expression, elements)); + }, + + /** + * Selector.findElement(elements, expression[, index = 0]) -> Element + * Selector.findElement(elements[, index = 0]) -> Element + * + * Returns the `index`th element in the collection that matches + * `expression`. + * + * Returns the `index`th element overall if `expression` is not given. + **/ + findElement: function(elements, expression, index) { + if (Object.isUndefined(index)) index = 0; + return Element.extend(Sizzle.matches(expression, elements)[index]); + }, + + /** + * Selector.findChildElements(element, expressions) -> [Element...] + * + * Searches beneath `element` for any elements that match the selector + * (or selectors) specified in `expressions`. + **/ + findChildElements: function(element, expressions) { + var results = [], exprs = expressions.toArray(); + while (exprs.length) Sizzle(exprs.shift(), element || document, results); + return extend(results); + } + }); + + /** related to: Selector + * $$(expression...) -> [Element...] * - * Searches beneath `element` for any elements that match the selector - * (or selectors) specified in `expressions`. + * Returns all elements in the document that match the provided CSS selectors. **/ - findChildElements: function(element, expressions) { - var results = [], exprs = expressions.toArray(); - while (exprs.length) Sizzle(exprs.shift(), element || document, results); - return results; + window.$$ = function() { + return Selector.findChildElements(document, $A(arguments)); } -}); - -/** related to: Selector - * $$(expression...) -> [Element...] - * - * Returns all elements in the document that match the provided CSS selectors. -**/ -function $$() { - return Selector.findChildElements(document, $A(arguments)).map(Element.extend); -} +})(); From 4d042a9fa0d3eb2f8e7234e34192b2d0964dec9f Mon Sep 17 00:00:00 2001 From: Sam Stephenson Date: Sat, 10 Oct 2009 13:24:04 -0500 Subject: [PATCH 04/28] Match each element individually in Selector.findElement since Sizzle.matches does not preserve order --- src/dom/selector.js | 9 ++++++++- test/unit/fixtures/selector.html | 6 ++++++ test/unit/selector_test.js | 6 ++++++ 3 files changed, 20 insertions(+), 1 deletion(-) diff --git a/src/dom/selector.js b/src/dom/selector.js index 60b201c..ee7b308 100644 --- a/src/dom/selector.js +++ b/src/dom/selector.js @@ -77,7 +77,14 @@ **/ findElement: function(elements, expression, index) { if (Object.isUndefined(index)) index = 0; - return Element.extend(Sizzle.matches(expression, elements)[index]); + var selector = new Selector(expression), length = elements.length, matchIndex = 0, i; + + // Match each element individually, since Sizzle.matches does not preserve order + for (i = 0; i < length; i++) { + if (selector.match(elements[i]) && index == matchIndex++) { + return Element.extend(elements[i]); + } + } }, /** diff --git a/test/unit/fixtures/selector.html b/test/unit/fixtures/selector.html index 42abcd4..4cfa29f 100644 --- a/test/unit/fixtures/selector.html +++ b/test/unit/fixtures/selector.html @@ -79,4 +79,10 @@
blah
+ +
+
+ +
+
diff --git a/test/unit/selector_test.js b/test/unit/selector_test.js index 6f11d2e..633b82b 100644 --- a/test/unit/selector_test.js +++ b/test/unit/selector_test.js @@ -404,5 +404,11 @@ new Test.Unit.Runner({ document.body.appendChild(el); this.assertEqual(2, $(el).select('ul li').length); document.body.removeChild(el); + }, + + testFindElementWithIndexWhenElementsAreNotInDocumentOrder: function() { + var ancestors = $("target_1").ancestors(); + this.assertEqual($("container_2"), Selector.findElement(ancestors, "[container], .container", 0)); + this.assertEqual($("container_1"), Selector.findElement(ancestors, "[container], .container", 1)); } }); \ No newline at end of file From c5372d81f3a84554e3a0eb1bd56e5f411375947d Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sat, 10 Oct 2009 19:07:37 -0500 Subject: [PATCH 05/28] Detect if Sizzle hasn't been loaded via submodule. --- Rakefile | 21 +++++++++++++++++---- 1 file changed, 17 insertions(+), 4 deletions(-) diff --git a/Rakefile b/Rakefile index 3b08a0a..64f9b80 100755 --- a/Rakefile +++ b/Rakefile @@ -14,9 +14,14 @@ module PrototypeHelper TEST_UNIT_DIR = File.join(TEST_DIR, 'unit') TMP_DIR = File.join(TEST_UNIT_DIR, 'tmp') VERSION = YAML.load(IO.read(File.join(SRC_DIR, 'constants.yml')))['PROTOTYPE_VERSION'] + + %w[sprockets pdoc unittest_js caja_builder sizzle].each do |name| + $:.unshift File.join(PrototypeHelper::ROOT_DIR, 'vendor', name, 'lib') + end def self.sprocketize(path, source, destination = nil, strip_comments = true) require_sprockets + require_sizzle secretary = Sprockets::Secretary.new( :root => File.join(ROOT_DIR, path), :load_path => [SRC_DIR, SIZZLE_DIR], @@ -59,6 +64,16 @@ module PrototypeHelper require_submodule('CajaBuilder', 'caja_builder') end + def self.require_sizzle + if !File.exists?(File.join(SIZZLE_DIR, 'sizzle.js')) + puts "\nIt looks like you're missing Sizzle. Just run:\n\n" + puts " $ git submodule init" + puts " $ git submodule update" + puts "\nand you should be all set.\n\n" + exit + end + end + def self.require_submodule(name, path) begin require path @@ -67,7 +82,7 @@ module PrototypeHelper if missing_file == path puts "\nIt looks like you're missing #{name}. Just run:\n\n" puts " $ git submodule init" - puts " $ git submodule update vendor/#{path}" + puts " $ git submodule update" puts "\nand you should be all set.\n\n" else puts "\nIt looks like #{name} is missing the '#{missing_file}' gem. Just run:\n\n" @@ -79,9 +94,7 @@ module PrototypeHelper end end -%w[sprockets pdoc unittest_js caja_builder].each do |name| - $:.unshift File.join(PrototypeHelper::ROOT_DIR, 'vendor', name, 'lib') -end + task :default => [:dist, :dist_helper, :package, :clean_package_source] From d3df9ba400db220c9c7ed7b9961f99f24038a5f5 Mon Sep 17 00:00:00 2001 From: Andrew Dupont Date: Sun, 11 Oct 2009 18:49:14 -0500 Subject: [PATCH 06/28] Automate installation of Git submodules. --- Rakefile | 50 +++++++++++++++++++++++++++++++++++++++----------- 1 file changed, 39 insertions(+), 11 deletions(-) diff --git a/Rakefile b/Rakefile index 64f9b80..cff99f8 100755 --- a/Rakefile +++ b/Rakefile @@ -18,6 +18,24 @@ module PrototypeHelper %w[sprockets pdoc unittest_js caja_builder sizzle].each do |name| $:.unshift File.join(PrototypeHelper::ROOT_DIR, 'vendor', name, 'lib') end + + def self.has_git? + begin + `git --version` + return true + rescue Error => e + return false + end + end + + def self.require_git + return if has_git? + puts "\nPrototype requires Git in order to load its dependencies." + puts "\nMake sure you've got Git installed and in your path." + puts "\nFor more information, visit:\n\n" + puts " http://book.git-scm.com/2_installing_git.html" + exit + end def self.sprocketize(path, source, destination = nil, strip_comments = true) require_sprockets @@ -66,25 +84,37 @@ module PrototypeHelper def self.require_sizzle if !File.exists?(File.join(SIZZLE_DIR, 'sizzle.js')) - puts "\nIt looks like you're missing Sizzle. Just run:\n\n" - puts " $ git submodule init" - puts " $ git submodule update" - puts "\nand you should be all set.\n\n" - exit + exit unless get_submodule("Sizzle", "sizzle") end end + def self.get_submodule(name, path) + require_git + puts "\nYou seem to be missing #{name}. Obtaining it via git...\n\n" + + Kernel.system("git submodule init") + return true if Kernel.system("git submodule update vendor/#{path}") + + # If we got this far, something went wrong. + puts "\nLooks like it didn't work. Try it manually:\n\n" + puts " $ git submodule init" + puts " $ git submodule update vendor/#{path}" + false + end + def self.require_submodule(name, path) begin require path rescue LoadError => e + # Wait until we notice that a submodule is missing before we bother the + # user about installing git. (Maybe they brought all the files over + # from a different machine.) missing_file = e.message.sub('no such file to load -- ', '') if missing_file == path - puts "\nIt looks like you're missing #{name}. Just run:\n\n" - puts " $ git submodule init" - puts " $ git submodule update" - puts "\nand you should be all set.\n\n" + # Missing a git submodule. + retry if get_submodule(name, path) else + # Missing a gem. puts "\nIt looks like #{name} is missing the '#{missing_file}' gem. Just run:\n\n" puts " $ gem install #{missing_file}" puts "\nand you should be all set.\n\n" @@ -94,8 +124,6 @@ module PrototypeHelper end end - - task :default => [:dist, :dist_helper, :package, :clean_package_source] desc "Builds the distribution." From caf66395d5ee29885e7b0c10f95edd7211a31831 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 01:17:53 +0200 Subject: [PATCH 07/28] Add Prototype.Selector object with select, match and filter methods as a wraper around Sizzle.Redefine the whole of Selector API in terms of Prototype.Selector. --- src/dom.js | 1 + src/dom/selector.js | 42 ++++++++++----------------------------- src/dom/sizzle_adapter.js | 37 ++++++++++++++++++++++++++++++++++ 3 files changed, 49 insertions(+), 31 deletions(-) create mode 100644 src/dom/sizzle_adapter.js diff --git a/src/dom.js b/src/dom.js index f9ff3ff..595ce17 100644 --- a/src/dom.js +++ b/src/dom.js @@ -21,6 +21,7 @@ //= require "dom/dom" +//= require "dom/sizzle_adapter" //= require "dom/selector" //= require "dom/form" //= require "dom/event" diff --git a/src/dom/selector.js b/src/dom/selector.js index ee7b308..e011521 100644 --- a/src/dom/selector.js +++ b/src/dom/selector.js @@ -1,5 +1,3 @@ -//= require - /** section: DOM * class Selector * @@ -7,12 +5,6 @@ * selector. **/ (function() { - function extend(elements) { - for (var i = 0, length = elements.length; i < length; i++) - elements[i] = Element.extend(elements[i]); - return elements; - } - window.Selector = Class.create({ /** * new Selector(expression) @@ -33,7 +25,7 @@ * selector. **/ findElements: function(rootElement) { - return extend(Sizzle(this.expression, rootElement || document)); + return Prototype.Selector.select(this.expression); }, /** @@ -42,7 +34,7 @@ * Tests whether a `element` matches the instance's CSS selector. **/ match: function(element) { - return Sizzle.matches(this.expression, [element]).length == 1; + return Prototype.Selector.match(element, this.expression); }, toString: function() { @@ -62,9 +54,7 @@ * * The only nodes returned will be those that match the given CSS selector. **/ - matchElements: function(elements, expression) { - return extend(Sizzle.matches(expression, elements)); - }, + matchElements: Prototype.Selector.filter, /** * Selector.findElement(elements, expression[, index = 0]) -> Element @@ -76,13 +66,13 @@ * Returns the `index`th element overall if `expression` is not given. **/ findElement: function(elements, expression, index) { - if (Object.isUndefined(index)) index = 0; - var selector = new Selector(expression), length = elements.length, matchIndex = 0, i; - + index = index || 0; + var matchIndex = 0, element; // Match each element individually, since Sizzle.matches does not preserve order - for (i = 0; i < length; i++) { - if (selector.match(elements[i]) && index == matchIndex++) { - return Element.extend(elements[i]); + for (var i = 0, length = elements.length; i < length; i++) { + element = elements[i]; + if (Prototype.Selector.match(element, expression) && index === matchIndex++) { + return Element.extend(element); } } }, @@ -94,18 +84,8 @@ * (or selectors) specified in `expressions`. **/ findChildElements: function(element, expressions) { - var results = [], exprs = expressions.toArray(); - while (exprs.length) Sizzle(exprs.shift(), element || document, results); - return extend(results); + var selector = expressions.toArray().join(', '); + return Prototype.Selector.select(selector, element || document); } }); - - /** related to: Selector - * $$(expression...) -> [Element...] - * - * Returns all elements in the document that match the provided CSS selectors. - **/ - window.$$ = function() { - return Selector.findChildElements(document, $A(arguments)); - } })(); diff --git a/src/dom/sizzle_adapter.js b/src/dom/sizzle_adapter.js new file mode 100644 index 0000000..71ad3ee --- /dev/null +++ b/src/dom/sizzle_adapter.js @@ -0,0 +1,37 @@ +//= require + +Prototype.Selector = (function(Sizzle) { + function extend(elements) { + for (var i = 0, length = elements.length; i < length; i++) + elements[i] = Element.extend(elements[i]); + return elements; + } + + function select(selector, scope) { + return extend(Sizzle(selector, scope || document)); + } + + function match(element, selector) { + return Sizzle.matches(selector, [element]).length == 1; + } + + function filter(elements, selector) { + return extend(Sizzle.matches(selector, elements)); + } + + return { + select: select, + match: match, + filter: filter + }; +})(Sizzle); + +/** related to: Selector + * $$(expression...) -> [Element...] + * + * Returns all elements in the document that match the provided CSS selectors. +**/ +window.$$ = function() { + var expression = $A(arguments).join(', '); + return Prototype.Selector.select(expression, document); +}; \ No newline at end of file From 2d13d45dc8decc699e3d88ca832e3b56a2be1811 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 01:43:48 +0200 Subject: [PATCH 08/28] Remove dependencies to Selector in favor of Prototype.Selector throughout the library. --- src/dom/dom.js | 25 ++++++++++++++----------- src/dom/event.js | 8 ++++++-- 2 files changed, 20 insertions(+), 13 deletions(-) diff --git a/src/dom/dom.js b/src/dom/dom.js index 1301bc7..65182bf 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -518,9 +518,10 @@ Element.Methods = { * Checks if `element` matches the given CSS selector. **/ match: function(element, selector) { + element = $(element); if (Object.isString(selector)) - selector = new Selector(selector); - return selector.match($(element)); + return Prototype.Selector.match(element, selector); + return selector.match(element); }, /** @@ -538,7 +539,7 @@ Element.Methods = { if (arguments.length == 1) return $(element.parentNode); var ancestors = Element.ancestors(element); return Object.isNumber(expression) ? ancestors[expression] : - Selector.findElement(ancestors, expression, index); + Prototype.Selector.filter(ancestors, expression)[index || 0]; }, /** @@ -574,7 +575,7 @@ Element.Methods = { if (!Object.isNumber(index)) index = 0; if (expression) { - return Selector.findElement(element.previousSiblings(), expression, index); + return Prototype.Selector.filter(element.previousSiblings(), expression)[index]; } else { return element.recursivelyCollect("previousSibling", index + 1)[index]; } @@ -596,7 +597,7 @@ Element.Methods = { if (!Object.isNumber(index)) index = 0; if (expression) { - return Selector.findElement(element.nextSiblings(), expression, index); + return Prototype.Selector.filter(element.nextSiblings(), expression)[index]; } else { var maximumLength = Object.isNumber(index) ? index + 1 : 1; return element.recursivelyCollect("nextSibling", index + 1)[index]; @@ -605,15 +606,16 @@ Element.Methods = { /** - * Element.select(@element, selector...) -> [Element...] - * - selector (String): A CSS selector. + * Element.select(@element, expression...) -> [Element...] + * - expression (String): A CSS selector. * * Takes an arbitrary number of CSS selectors and returns an array of * descendants of `element` that match any of them. **/ select: function(element) { - var args = Array.prototype.slice.call(arguments, 1); - return Selector.findChildElements(element, args); + element = $(element); + var expressions = Array.prototype.slice.call(arguments, 1).join(', '); + return Prototype.Selector.select(expressions, element); }, /** @@ -624,8 +626,9 @@ Element.Methods = { * selector(s). **/ adjacent: function(element) { - var args = Array.prototype.slice.call(arguments, 1); - return Selector.findChildElements(element.parentNode, args).without(element); + element = $(element); + var expressions = Array.prototype.slice.call(arguments, 1).join(', '); + return Prototype.Selector.select(expressions, element.parentNode).without(element); }, /** diff --git a/src/dom/event.js b/src/dom/event.js index 7980c52..f94ac87 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -149,8 +149,12 @@ function findElement(event, expression) { var element = Event.element(event); if (!expression) return element; - var elements = [element].concat(element.ancestors()); - return Selector.findElement(elements, expression, 0); + while (element) { + if (Prototype.Selector.match(element, expression)) { + return Element.extend(element); + } + element = element.parentNode + } } /** From da3e1e361eea151c253f3028b4fd61bcc856a723 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 04:10:30 +0200 Subject: [PATCH 09/28] Clean-up. --- src/dom/selector.js | 10 ++++++++++ src/dom/sizzle_adapter.js | 17 +++++++---------- 2 files changed, 17 insertions(+), 10 deletions(-) diff --git a/src/dom/selector.js b/src/dom/selector.js index e011521..b8b0331 100644 --- a/src/dom/selector.js +++ b/src/dom/selector.js @@ -89,3 +89,13 @@ } }); })(); + +/** related to: Selector + * $$(expression...) -> [Element...] + * + * Returns all elements in the document that match the provided CSS selectors. +**/ +window.$$ = function() { + var expression = $A(arguments).join(', '); + return Prototype.Selector.select(expression, document); +}; \ No newline at end of file diff --git a/src/dom/sizzle_adapter.js b/src/dom/sizzle_adapter.js index 71ad3ee..26249eb 100644 --- a/src/dom/sizzle_adapter.js +++ b/src/dom/sizzle_adapter.js @@ -1,4 +1,10 @@ +Prototype._original_sizzle = window.Sizzle; //= require +Prototype.Sizzle = window.Sizzle; + +// Restore globals. +window.Sizzle = Prototype._original_sizzle; +delete Prototype._original_sizzle; Prototype.Selector = (function(Sizzle) { function extend(elements) { @@ -24,14 +30,5 @@ Prototype.Selector = (function(Sizzle) { match: match, filter: filter }; -})(Sizzle); +})(Prototype.Sizzle); -/** related to: Selector - * $$(expression...) -> [Element...] - * - * Returns all elements in the document that match the provided CSS selectors. -**/ -window.$$ = function() { - var expression = $A(arguments).join(', '); - return Prototype.Selector.select(expression, document); -}; \ No newline at end of file From 3e19f959a2c2a002ad6ab9103004682c431c6c28 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 04:42:05 +0200 Subject: [PATCH 10/28] Fix Selector#findElements. --- src/dom/selector.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/dom/selector.js b/src/dom/selector.js index b8b0331..4b03a25 100644 --- a/src/dom/selector.js +++ b/src/dom/selector.js @@ -25,7 +25,7 @@ * selector. **/ findElements: function(rootElement) { - return Prototype.Selector.select(this.expression); + return Prototype.Selector.select(this.expression, rootElement); }, /** From 7762e002cb0dd757b347494c896b6aa520b21c16 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 05:49:40 +0200 Subject: [PATCH 11/28] Reorder repository to allow for custom selector engines to be included instead of Sizzle (the current default). Selector engines belong in the vendor directory and must have a selector_engine.js file. To build a Prototype with your engine of choice just spepcify it at build time using the SELECTOR_ENGINE option. For example, to build a version with NSMatcher: rake dist SELECTOR_ENGINE=nwmatcher. --- .gitmodules | 4 +- Rakefile | 18 +- src/dom.js | 2 +- vendor/nwmatcher/nwmatcher-1.1.1.js | 1441 +++++++++++++++++ vendor/nwmatcher/selector_engine.js | 37 + vendor/sizzle | 1 - .../sizzle/selector_engine.js | 2 +- vendor/sizzle/sizzle | 1 + 8 files changed, 1498 insertions(+), 8 deletions(-) create mode 100644 vendor/nwmatcher/nwmatcher-1.1.1.js create mode 100644 vendor/nwmatcher/selector_engine.js delete mode 160000 vendor/sizzle rename src/dom/sizzle_adapter.js => vendor/sizzle/selector_engine.js (96%) create mode 160000 vendor/sizzle/sizzle diff --git a/.gitmodules b/.gitmodules index 00dca52..61fd316 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,6 +11,6 @@ path = vendor/sprockets url = git://github.com/sstephenson/sprockets.git -[submodule "vendor/sizzle"] - path = vendor/sizzle +[submodule "vendor/sizzle/sizzle"] + path = vendor/sizzle/sizzle url = git://github.com/jeresig/sizzle.git diff --git a/Rakefile b/Rakefile index cff99f8..2b36e08 100755 --- a/Rakefile +++ b/Rakefile @@ -42,7 +42,7 @@ module PrototypeHelper require_sizzle secretary = Sprockets::Secretary.new( :root => File.join(ROOT_DIR, path), - :load_path => [SRC_DIR, SIZZLE_DIR], + :load_path => self.load_path, :source_files => [source], :strip_comments => strip_comments ) @@ -51,6 +51,18 @@ module PrototypeHelper secretary.concatenation.save_to(destination) end + def self.load_path + selector = ENV['SELECTOR_ENGINE'] || 'sizzle' + selector_path = File.join(ROOT_DIR, 'vendor', selector) + if File.exists?(selector_path) + [SRC_DIR, selector_path] + else + puts "\nYou seem to be missing vendor/#{selector}." + puts "Built Prototype using Sizzle instead.\n\n" + [SRC_DIR, SIZZLE_DIR] + end + end + def self.build_doc_for(file) mkdir_p TMP_DIR temp_path = File.join(TMP_DIR, "prototype.temp.js") @@ -83,8 +95,8 @@ module PrototypeHelper end def self.require_sizzle - if !File.exists?(File.join(SIZZLE_DIR, 'sizzle.js')) - exit unless get_submodule("Sizzle", "sizzle") + if !File.exists?(File.join(SIZZLE_DIR, 'sizzle', 'sizzle.js')) + exit unless get_submodule("Sizzle", "sizzle/sizzle") end end diff --git a/src/dom.js b/src/dom.js index 595ce17..1d9fdf3 100644 --- a/src/dom.js +++ b/src/dom.js @@ -21,7 +21,7 @@ //= require "dom/dom" -//= require "dom/sizzle_adapter" +//= require //= require "dom/selector" //= require "dom/form" //= require "dom/event" diff --git a/vendor/nwmatcher/nwmatcher-1.1.1.js b/vendor/nwmatcher/nwmatcher-1.1.1.js new file mode 100644 index 0000000..82bc547 --- /dev/null +++ b/vendor/nwmatcher/nwmatcher-1.1.1.js @@ -0,0 +1,1441 @@ +/* + * Copyright (C) 2007-2009 Diego Perini + * All rights reserved. + * + * nwmatcher.js - A fast CSS selector engine and matcher + * + * Author: Diego Perini + * Version: 1.1.1 + * Created: 20070722 + * Release: 20090516 + * + * License: + * http://javascript.nwbox.com/NWMatcher/MIT-LICENSE + * Download: + * http://javascript.nwbox.com/NWMatcher/nwmatcher.js + */ + +window.NW || (window.NW = {}); + +NW.Dom = function(global) { + + var version = 'nwmatcher-1.1.1', + + // processing context + base = global.document, + + // script loading context + context = global.document, + + // context root element (HTML) + root = context.documentElement, + + // current DOM viewport/window, also used to + // detect Safari 2.0.x [object AbstractView] + view = base.defaultView || base.parentWindow, + + // cache access to native slice + slice = Array.prototype.slice, + + /* BEGIN FEATURE TESTING */ + + // detect native method in object + // not same scope of isHostObject + isNative = function(object, method) { + return object && method in object && + typeof object[method] != 'string' && + // IE/W3C browsers will return [native code] + // Safari 2.0.x and older will return [function] + (/\{\s*\[native code[^\]]*\]\s*\}|^\[function\]$/). + test(object[method]); + }, + + // NOTE: NATIVE_XXXXX check for existance of method only + // so through the code read it as "supported", maybe BUGGY + + // detect native getAttribute/hasAttribute methods, + // frameworks extend these to elements, but it seems + // this does not work for XML namespaced attributes, + // used to check both getAttribute/hasAttribute in IE + NATIVE_HAS_ATTRIBUTE = isNative(root, 'hasAttribute'), + + // detect if DOM methods are native in browsers + NATIVE_QSAPI = isNative(context, 'querySelector'), + NATIVE_GEBID = isNative(context, 'getElementById'), + NATIVE_GEBTN = isNative(root, 'getElementsByTagName'), + NATIVE_GEBCN = isNative(root, 'getElementsByClassName'), + + // get name of best children collection property available + // detect Safari 2.0.x different children implementation + NATIVE_CHILDREN = + 'children' in root ? + (view && global !== view ? + 'childNodes' : + 'children') : + 'childNodes', + + // nodeList can be converted by native .slice() + // Opera 9.27 and an id="length" will fold this + NATIVE_SLICE_PROTO = + (function() { + var isBuggy = false, div = context.createElement('div'); + try { + div.innerHTML = '
'; + root.insertBefore(div, root.firstChild); + isBuggy = !!slice.call(div.childNodes, 0)[0]; + } catch(e) { + } finally { + root.removeChild(div).innerHTML = ''; + } + return isBuggy; + })(), + + // check for Mutation Events, DOMAttrModified should be + // enough to ensure DOMNodeInserted/DOMNodeRemoved exist + NATIVE_MUTATION_EVENTS = root.addEventListener ? + (function() { + var isBuggy, id = root.id, + handler = function() { + root.removeEventListener('DOMAttrModified', handler, false); + NATIVE_MUTATION_EVENTS = true; + root.id = id; + }; + root.addEventListener('DOMAttrModified', handler, false); + // now modify an attribute + root.id = 'nw'; + isBuggy = root.id != 'nw'; + root.id = id; + handler = null; + return isBuggy; + })() : + false, + + // NOTE: BUGGY_XXXXX check both for existance and no known bugs, + // so through the code read it as "not supported", or "undefined" + + BUGGY_GEBID = NATIVE_GEBID ? + (function() { + var isBuggy, div = context.createElement('div'); + div.innerHTML = ''; + root.insertBefore(div, root.firstChild); + isBuggy = !!div.ownerDocument.getElementById('Z'); + root.removeChild(div); + div = null; + return isBuggy; + })() : + true, + + // detect IE gEBTN comment nodes bug + BUGGY_GEBTN = NATIVE_GEBTN ? + (function() { + var isBuggy, div = context.createElement('div'); + div.appendChild(context.createComment('')); + div = div.getElementsByTagName('*')[0]; + isBuggy = !!(div && div.nodeType == 8); + div = null; + return isBuggy; + })() : + true, + + // detect Opera gEBCN second class and/or UTF8 bugs + // test is taken from the jQuery selector test suite + BUGGY_GEBCN = NATIVE_GEBCN ? + (function() { + var isBuggy, div = context.createElement('div'); + div.innerHTML = ''; + isBuggy = !div.getElementsByClassName('台北')[0]; + div = null; + return isBuggy; + })() : + true, + + // check Seletor API implementations + BUGGY_QSAPI = NATIVE_QSAPI ? (function() { + var isBuggy, pattern = [], div = context.createElement('div'); + + // WebKit case sensitivity bug with className (when no DOCTYPE) + // https://bugs.webkit.org/show_bug.cgi?id=19047 + div.innerHTML = ''; + if (context.compatMode == 'BackCompat' && div.querySelector('.x') !== null) { + return { 'test': function() { return true; } }; + } + + // check :enabled :disabled bugs with hidden fields (Firefox 3.5 QSA bug) + // http://www.w3.org/TR/html5/interactive-elements.html#selector-enabled + div.innerHTML = ''; + // IE8 throws error with these pseudos + try { + isBuggy = div.querySelectorAll(':enabled').length === 1; + } catch(e) { } + isBuggy && pattern.push(':enabled', ':disabled'); + + // check :link bugs with hyperlinks matching (Firefox/Safari) + div.innerHTML = ''; + div.querySelectorAll(':link').length !== 1 && pattern.push(':link'); + + return pattern.length + ? new RegExp(pattern.join('|')) + : { 'test': function() { return false; } }; + })() : + true, + + /* END FEATURE TESTING */ + + // map of attribute names (in HTML and DOM namespaces) + // many are missing here, or maybe there are too many + // first two lines will cover most real cases anyway + /* + // we do not have to write attributes and + // we have a fixed internal getAttribute + // maybe we can skip this case juggling + Attributes = { + 'class': 'className', 'for': 'htmlFor', + 'classname': 'className', 'htmlfor': 'htmlFor', + 'tabindex': 'tabIndex', 'accesskey': 'accessKey', 'maxlength': 'maxLength', + 'readonly': 'readOnly', 'longdesc': 'longDesc', 'frameborder': 'frameBorder', + 'ismap': 'isMap', 'usemap': 'useMap', 'nohref': 'noHref', 'nowrap': 'noWrap', + 'colspan': 'colSpan', 'rowspan': 'rowSpan', + 'cellpadding': 'cellPadding', 'cellspacing': 'cellSpacing', + 'marginwidth': 'marginWidth', 'marginheight': 'marginHeight' + }, + */ + + // See Niels Leenheer blog http://rakaz.nl/item/css_selector_bugs_case_sensitivity + // + // Each attribute definition includes information about the case-sensitivity of its values. + // http://www.w3.org/TR/html4/types.html#h-6.1 + // + // HTML 4 and XHTML both have some attributes that have pre-defined and limited sets of values. + // http://www.w3.org/TR/xhtml1/#h-4.11 + + // Safari 2.0.x seems to always treat attributes as in Quirks mode + insensitiveMap = /^CSS/i.test(context.compatMode) || (view && global !== view) ? { + // must be trated case insensitive in both HTML and XHTML (Strict ?) + 'accept': 1, 'accept-charset': 1, 'alink': 1, 'axis': 1, + 'bgcolor': 1, 'charset': 1, 'codetype': 1, 'color': 1, + 'face': 1, 'enctype': 1, 'hreflang': 1, 'http-equiv': 1, + 'lang': 1, 'language': 1, 'link': 1, 'media': 1, 'rel': 1, + 'rev': 1, 'target': 1, 'text': 1, 'type': 1, 'vlink': 1 + } : { + // must be treated case insensitive in HTML (Quirks ?) + 'align': 1, 'checked': 1, 'clear': 1, 'compact': 1, 'declare': 1, + 'defer': 1, 'dir': 1, 'disabled': 1, 'frame': 1, 'method': 1, + 'multiple': 1, 'nohref': 1, 'noresize': 1, 'noshade': 1, 'nowrap': 1, + 'readonly': 1, 'rules': 1, 'scope': 1, 'scrolling': 1, 'selected': 1, + 'shape': 1, 'valign': 1, 'valuetype': 1 + }, + + // attribute referencing URI values need special treatment in IE + attributesURI = { + 'action': 2, 'cite': 2, 'codebase': 2, 'data': 2, 'href': 2, + 'longdesc': 2, 'lowsrc': 2, 'src': 2, 'usemap': 2 + }, + + // selection functions returning collections + compiledSelectors = { }, + + // matching functions returning booleans + compiledMatchers = { }, + + // place to add exotic functionalities + Selectors = { + // as a simple example this will check + // for chars not in standard ascii table + // + // 'mySpecialSelector': { + // 'Expression': /\u0080-\uffff/, + // 'Callback': mySelectorCallback + //} + // + // 'mySelectorCallback' will be invoked + // only after passing all other standard + // checks and only if none of them worked + }, + + // trim leading/trailing whitespaces + trim = /^\s+|\s+$/g, + + // nth pseudo selectors + position = /:(nth|of-type)/, + + // ascii extended + ascii = /\x00-\xff/, + + // http://www.w3.org/TR/css3-syntax/#characters + // unicode/ISO 10646 characters 161 and higher + // encoding = '|[\\u00a1-\\uffff]',// correct + // NOTE: Safari 2.0.x crashes with escaped (\\) + // Unicode ranges in regular expressions so we + // use a negated character range class instead + // NOTE: [^\\w\\W] tested as good replacement + encoding = '|[^\\x00-\\xa0]', + + // selector validator discard invalid chars + validator = new RegExp("([-_*\\w]" + encoding + ")"), + + // split comma separated selector groups, exclude commas inside () [] + // example: (#div a, ul > li a) group 1 is (#div a) group 2 is (ul > li a) + group = /(([^,\(\)\[\]]+|\([^\(\)]+\)|\(.*\)|\[[^\[\]]+\]|\[.*\]|\\.|\*)+)/g, + + // attribute operators + Operators = { + // ! is not really in the specs + // still unit tests have to pass + '!': "%p!=='%m'", + '=': "%p==='%m'", + '^': "%p.indexOf('%m')==0", + '*': "%p.indexOf('%m')>-1", + // sensitivity handled by compiler + // NOTE: working alternative + // '|': "/%m-/i.test(%p+'-')", + '|': "(%p+'-').indexOf('%m-')==0", + '~': "(' '+%p+' ').indexOf(' %m ')>-1", + // precompile in '%m' string length to optimize + // NOTE: working alternative + // '$': "%p.lastIndexOf('%m')==%p.length-'%m'.length" + '$': "%p.substr(%p.length - '%m'.length) === '%m'" + }, + + // optimization expressions + Optimize = { + ID: new RegExp("#((?:[-_\\w]" + encoding + "|\\\\.)+)*"), + TAG: new RegExp("((?:[-_\\w]" + encoding + "|\\\\.)+)*"), + CLASS: new RegExp("\\.((?:[-_\\w]" + encoding + "|\\\\.)+)*"), + // split last, right most, selector group token + TOKEN: /([^\ \>\+\~\,\(\)\[\]]+|\([^\(\)]+\)|\(.*\)|\[[^\[\]]+\]|\[.*\])+/g, + descendants: /[^> \w]/, + siblings: /[^+~\w]/ + }, + + // precompiled Regular Expressions + Patterns = { + // element attribute matcher + attribute: /^\[\s*([-\w]*:?(?:[-\w])+)\s*(?:([!^$*~|]*)?(\=)?\s*(["']*)?([^'"]*?)\4)\s*\](.*)/, + // structural pseudo-classes + spseudos: /^\:(root|empty|nth)?-?(first|last|only)?-?(child)?-?(of-type)?(\((?:even|odd|[^\)]*)\))?(.*)/, + // uistates + dynamic + negation pseudo-classes + dpseudos: /^\:((?:[-\w]|\\.)+)(\(([\x22\x27]*)?(.*?(\(.*?\))?[^(]*?)\3\))?(.*)/, + // E > F + children: /^\s*\>\s*(.*)/, + // E + F + adjacent: /^\s*\+\s*(.*)/, + // E ~ F + relative: /^\s*\~\s*(.*)/, + // E F + ancestor: /^(\s+)(.*)/, + // all + all: /^\*(.*)/, + // id + id: new RegExp("^#((?:[-_\\w]" + encoding + "|\\\\.)+)(.*)"), + // tag + tagName: new RegExp("^((?:[-_\\w]" + encoding + "]\\\\.)+)(.*)"), + // class + className: new RegExp("^\\.((?:[-_\\w]" + encoding + "|\\\\.)+)(.*)") + }, + + // current CSS3 grouping of Pseudo-Classes + // they allowed implementing extensions + // and improve error notifications + CSS3PseudoClasses = { + Structural: { + 'root': 0, 'empty': 0, + 'first-child': 0, 'last-child': 0, 'only-child': 0, + 'first-of-type': 0, 'last-of-type': 0, 'only-of-type': 0, + 'first-child-of-type': 0, 'last-child-of-type': 0, 'only-child-of-type': 0, + 'nth-child': 0, 'nth-last-child': 0, 'nth-of-type': 0, 'nth-last-of-type': 0 + // (the 4rd line is not in W3C CSS specs but is an accepted alias of 3nd line) + }, + // originally separated in different pseudo-classes + // we have grouped them to optimize a bit size+speed + // all are going through the same code path (switch) + // the assigned value represent current spec status: + // 0 = CSS3, 1 = CSS2, 2 = maybe implemented + Others: { + //UIElementStates: { + // we group them to optimize + 'checked': 0, 'disabled': 0, 'enabled': 0, 'selected': 1, 'indeterminate': 2, + //}, + //Dynamic: { + 'active': 0, 'focus': 0, 'hover': 0, 'link': 0, 'visited': 0, + //}, + // Target: { + 'target': 0, + //}, + // Language: { + 'lang': 0, + //}, + // Negation: { + 'not': 0, + //}, + // Content: { + // http://www.w3.org/TR/2001/CR-css3-selectors-20011113/#content-selectors + 'contains': 2 + //} + } + }, + + // conditionals optimizers for the compiler + + // do not change this, it is searched & replaced + ACCEPT_NODE = 'r[X++]=N;continue main;', + + // fix for IE gEBTN('*') returning collection with comment nodes + SKIP_COMMENTS = BUGGY_GEBTN ? 'if(e.nodeType!=1){continue;}' : '', + + // use the textContent or innerText property to check CSS3 :contains + // Safari 2 has a bug with innerText and hidden content, using an + // internal replace on the innerHTML property avoids trashing it + CONTAINS_TEXT = + 'textContent' in root ? + 'e.textContent' : + (function() { + var t = context.createElement('div'); + t.innerHTML = '

p

'; + t.style.display = 'none'; + return t.innerText.length > 0 ? + 'e.innerText' : + 'this.stripTags(e.innerHTML)'; + })(), + + // to check extensions have not yet been registered + // @return boolean + IS_EMPTY = + function(object) { + if (object && typeof object == 'object') { + for (var i in object) { return false; } + return true; + } + return false; + }, + + // compile a comma separated group of selector + // @mode boolean true for select, false for match + // @return function (compiled) + compileGroup = + function(selector, source, mode) { + var i = 0, seen = { }, parts, token; + if ((parts = selector.match(group))) { + // for each selector in the group + for ( ; i < parts.length; ++i) { + token = parts[i].replace(trim, ''); + // avoid repeating the same token + // in comma separated group (p, p) + if (!seen[token]) { + seen[token] = true; + // reset element reference after the + // first comma if using select() mode + if (i > 0) { + source += 'e=N;'; + } + // insert corresponding mode function + if (mode) { + source += compileSelector(token, ACCEPT_NODE); + } else { + source += compileSelector(token, 'return true;'); + } + } + } + } + if (mode) { + // for select method + return new Function('c,s,d,h', 'var k,e,r,n,C,N,T,X=0,x=0;main:for(k=0,r=[];e=N=c[k];k++){' + SKIP_COMMENTS + source + '}return r;'); + } else { + // for match method + return new Function('e,s,d,h', 'var n,C,N=e,T,X=0,x=0;' + source + 'return false;'); + } + }, + + // compile a CSS3 string selector into + // ad-hoc javascript matching function + // @return string (to be compiled) + compileSelector = + function(selector, source) { + + var i, a, b, n, k, expr, match, result, status, test, type; + + k = 0; + + while (selector) { + + // *** Universal selector + // * match all (empty block, do not remove) + if ((match = selector.match(Patterns.all))) { + // do nothing, handled in the compiler where + // BUGGY_GEBTN return comment nodes (ex: IE) + } + // *** ID selector + // #Foo Id case sensitive + else if ((match = selector.match(Patterns.id))) { + // document can contain conflicting elements (id/name) + source = 'if((n=e.getAttributeNode("id"))&&n.value=="' + match[1] + '"){' + source + '}'; + //source = 'if(e.getAttribute("id")=="' + match[1] + '"){' + source + '}'; + //source = 'if(e.id=="' + match[1] + '"){' + source + '}'; + } + // *** Type selector + // Foo Tag (case insensitive) + else if ((match = selector.match(Patterns.tagName))) { + // both tagName and nodeName properties may be upper or lower case + // depending on their creation NAMESPACE in createElementNS() + source = 'T=e.nodeName;if(T=="' + match[1].toUpperCase() + '"||T=="' + match[1].toLowerCase() + '"){' + source + '}'; + //source = 'if(e.nodeName=="' + match[1].toUpperCase() + '"){' + source + '}'; + //source = 'if(/' + match[1] + '/i.test(e.nodeName)){' + source + '}'; + } + // *** Class selector + // .Foo Class (case sensitive) + else if ((match = selector.match(Patterns.className))) { + // W3C CSS3 specs: element whose "class" attribute has been assigned a list of whitespace-separated values + // see section 6.4 Class selectors and notes at the bottom; explicitly non-normative in this specification. + //source = 'if((" "+e.className+" ").replace(/\\s+/g," ").indexOf(" ' + match[1] + ' ")>-1){' + source + '}'; + source = 'C=e.className;if(C&&(" "+C+" ").indexOf(" ' + match[1] + ' ")>-1){' + source + '}'; + } + // *** Attribute selector + // [attr] [attr=value] [attr="value"] [attr='value'] and !=, *=, ~=, |=, ^=, $= + // case sensitivity is treated differently depending on the document type (see map) + else if ((match = selector.match(Patterns.attribute))) { + // xml namespaced attribute ? + expr = match[1].split(':'); + expr = expr.length == 2 ? expr[1] : expr[0] + ''; + // check case treatment from insensitiveMap + if (insensitiveMap[expr.toLowerCase()]) { + match[5] = match[5].toLowerCase(); + } + source = 'if(' + + (Operators[(match[2] || match[3])] || 'this.hasAttribute(e,"' + match[1] + '")'). + replace(/\%p/g, 'this.getAttribute(e,"' + match[1] + '")' + + (expr ? '' : '.toLowerCase()')).replace(/\%m/g, match[5]) + + '){' + source + '}'; + } + // *** Adjacent sibling combinator + // E + F (F adiacent sibling of E) + else if ((match = selector.match(Patterns.adjacent))) { + source = 'while((e=e.previousSibling)){if(e.nodeType==1){' + source + 'break;}}'; + } + // *** General sibling combinator + // E ~ F (F relative sibling of E) + else if ((match = selector.match(Patterns.relative))) { + // previousSibling particularly slow on Gecko based browsers prior to FF3.1 + //source = 'while((e=e.previousSibling)){if(e.nodeType==1){' + source + '}}'; + k++; + source = + 'var N' + k + '=e;e=e.parentNode.firstChild;' + + 'while(e!=N' + k +'){if(e.nodeType==1){' + source + '}e=e.nextSibling;}'; + } + // *** Child combinator + // E > F (F children of E) + else if ((match = selector.match(Patterns.children))) { + source = 'if((e=e.parentNode)&&e.nodeType==1){' + source + '}'; + } + // *** Descendant combinator + // E F (E ancestor of F) + else if ((match = selector.match(Patterns.ancestor))) { + source = 'while((e=e.parentNode)&&e.nodeType==1){' + source + '}'; + } + // *** Structural pseudo-classes + // :root, :empty, + // :first-child, :last-child, :only-child, + // :first-of-type, :last-of-type, :only-of-type, + // :nth-child(), :nth-last-child(), :nth-of-type(), :nth-last-of-type() + else if ((match = selector.match(Patterns.spseudos)) && + selector.match(/([-\w]+)/)[0] in CSS3PseudoClasses.Structural) { + + switch (match[1]) { + case 'root': + // element root of the document + source = 'if(e===h){' + source + '}'; + break; + case 'empty': + // element that has no children + source = 'if(!e.firstChild){' + source + '}'; + break; + default: + type = match[4] == 'of-type' ? 'OfType' : 'Element'; + + if (match[5]) { + // remove the ( ) grabbed above + match[5] = match[5].replace(/\(|\)/g, ''); + if (match[5] == 'even') { + a = 2; + b = 0; + } else if (match[5] == 'odd') { + a = 2; + b = 1; + } else { + // assumes correct "an+b" format + a = match[5].match(/^-/) ? -1 : match[5].match(/^n/) ? 1 : 0; + a = a || ((n = match[5].match(/(-?\d{1,})n/)) ? parseInt(n[1], 10) : 0); + b = 0 || ((n = match[5].match(/(-?\d{1,})$/)) ? parseInt(n[1], 10) : 0); + } + + // executed after the count is computed + expr = match[2] == 'last' ? (match[4] ? + '(e==h?1:s.TwinsCount[e.parentNode._cssId][e.nodeName.toLowerCase()])' : + '(e==h?1:s.ChildCount[e.parentNode._cssId])') + '-' + (b - 1) : b; + + test = + b < 0 ? + a <= 1 ? + '<=' + Math.abs(b) : + '%' + a + '===' + (a + b) : + a > Math.abs(b) ? '%' + a + '===' + b : + a === Math.abs(b) ? '%' + a + '===' + 0 : + a === 0 ? '==' + expr : + a < 0 ? '<=' + b : + a > 0 ? '>=' + b : + ''; + + // 4 cases: 1 (nth) x 4 (child, of-type, last-child, last-of-type) + source = 'if(this.' + match[1] + type + '(e)' + test + '){' + source + '}'; + } else { + // 6 cases: 3 (first, last, only) x 1 (child) x 2 (-of-type) + // too much overhead calling functions out of the main loop ? + //source = 'if(this.' + match[2] + type + '(e)){' + source + '}'; + source = (match[4] ? 'T=e.nodeName;' : '') + + 'n=e;while((n=n.' + (match[2] == 'first' ? 'previous' : 'next') + 'Sibling)&&' + + 'n.node' + (match[4] ? 'Name!=T' : 'Type!=1') + ');' + + 'if(!n){' + (match[2] == 'first' || match[2] == 'last' ? source : + 'n=e;while((n=n.' + (match[2] == 'only' ? 'previous' : 'next') + 'Sibling)&&' + + 'n.node' + (match[4] ? 'Name!=T' : 'Type!=1') + ');' + + 'if(!n){' + source + '}') + + '}'; + } + break; + } + + } + // *** Dynamic pseudo-classes + // CSS3 :not, :contains, :enabled, :disabled, :checked, :target + // CSS2 :active, :focus, :hover (no way yet) + // CSS1 :link, :visited + else if ((match = selector.match(Patterns.dpseudos)) && + selector.match(/([-\w]+)/)[0] in CSS3PseudoClasses.Others) { + + if (match[2]) { + // if the pseudo-class is one with a parameter + // remove round brackets grabbed by expression + match[2] = match[2].replace(/^\((.*)\)$/, '$1'); + } + + switch (match[1]) { + // CSS3 part of structural pseudo-classes + case 'not': + // compile nested selectors, need to escape double quotes characters + // since the string we are inserting into already uses double quotes + source = 'if(!this.match(e, "' + match[2].replace(/\x22/g, '\\"') + '")){' + source +'}'; + break; + // maybe deprecated in latest proposals + case 'contains': + match[2] = match[2].replace(/^["']*|['"]*$/g, ''); + source = 'if(' + CONTAINS_TEXT + '.indexOf("' + match[2] + '")>-1){' + source + '}'; + break; + // CSS3 part of UI element states + case 'checked': + source = 'if("form" in e&&/radio|checkbox/i.test(e.type)&&e.checked===true){' + source + '}'; + break; + case 'enabled': + // does not consider hidden input fields + source = 'if((("form" in e&&e.type!=="hidden")||this.isLink(e))&&e.disabled===false){' + source + '}'; + break; + case 'disabled': + // does not consider hidden input fields + source = 'if((("form" in e&&e.type!=="hidden")||this.isLink(e))&&e.disabled===true){' + source + '}'; + break; + case 'selected': + // fix Safari selectedIndex property bug + n = base.getElementsByTagName('select'); + for (i = 0; n[i]; i++) { + n[i].selectedIndex; + } + source = 'if("form" in e&&e.selected===true){' + source + '}'; + break; + // CSS3 target element + case 'target': + n = base.location.hash; + source = 'if(e.id!=""&&e.id=="' + n + '"&&"href" in e){' + source + '}'; + break; + // CSS1 & CSS2 link + case 'link': + source = 'if(this.isLink(e)&&!e.visited){' + source + '}'; + break; + case 'visited': + source = 'if(this.isLink(e)&&!!e.visited){' + source + '}'; + break; + // CSS1 & CSS2 UI States IE & FF3 have native support + // these capabilities may be emulated by event managers + case 'active': + source = 'if("activeElement" in d&&e===d.activeElement){' + source + '}'; + break; + case 'hover': + source = 'if("hoverElement" in d&&e===d.hoverElement){' + source + '}'; + break; + case 'focus': + source = 'if("form" in e&&e===d.activeElement&&typeof d.hasFocus=="function"&&d.hasFocus()===true){' + source + '}'; + break; + default: + break; + } + } + else if (!IS_EMPTY(Selectors)) { + // this is where external extensions are + // invoked if expressions match selectors + status = true; + for (name in Selectors) { + if ((match = selector.match(Selectors[name].Expression))) { + result = Selectors[name].Callback(match, source); + source = result.source; + status |= result.status; + } + } + // if an extension fails to parse the selector + // it must return a false boolean in "status" + if (!status) { + // log error but continue execution, don't throw real exceptions + // because blocking following processes maybe is not a good idea + emit('DOMException: unknown pseudo selector "' + selector + '"'); + return source; + } + } + else { + // see above, log error but continue execution + emit('DOMException: unknown token in selector "' + selector + '"'); + return source; + } + + // ensure "match" is not null or empty since + // we do not throw real DOMExceptions above + selector = match && match[match.length - 1]; + } + + return source; + }, + + // enable/disable notifications + VERBOSE = false, + + // a way to control user notification + emit = + function(message) { + if (VERBOSE) { + if (global.console && global.console.log) { + global.console.log(message); + } else { + if (/exception/i.test(message)) { + global.status = message; + global.defaultStatus = message; + } else { + global.status += message; + } + } + } + }, + + // match element with selector + // @return boolean + match = + function(element, selector) { + // make sure an element node was passed + if (element && element.nodeType == 1) { + if (typeof selector == 'string' && selector.length) { + base = element.ownerDocument; + root = base.documentElement; + // save compiled matchers + if (!compiledMatchers[selector]) { + compiledMatchers[selector] = compileGroup(selector, '', false); + } + // result of compiled matcher + return compiledMatchers[selector].call(this, element, snap, base, root); + } + else { + emit('DOMException: "' + selector + '" is not a valid CSS selector.'); + } + } + return false; + }, + + // select elements matching selector + // version using new Selector API + // @return array + select_qsa = + function (selector, from) { + if (validator.test(selector)) { + if ((!from || from.nodeType == 9) && !BUGGY_QSAPI.test(selector)) { + try { + // use available Selectors API + return toArray((from || context).querySelectorAll(selector)); + } catch(e) { } + } + // fall back to NWMatcher select + return client_api.call(this, selector, from || context); + } + return [ ]; + }, + + lastSelector, + lastContext, + lastSlice, + + // select elements matching selector + // version using cross-browser client API + // @return array + client_api = + function client_api(selector, from) { + + var i = 0, done, elements, node, parts, token; + + // extract context if changed + if (!from || lastContext != from) { + // save passed context + lastContext = from; + // ensure from context is set + from || (from = context); + // reference context ownerDocument and document root (HTML) + root = (base = from.ownerDocument || from).documentElement; + } + + if (lastSelector != selector) { + // process valid selector strings + if (validator.test(selector)) { + // save passed selector + lastSelector = selector; + // get right most selector token + parts = selector.match(Optimize.TOKEN); + // only last slice before :not rules + lastSlice = parts[parts.length - 1].split(':not')[0]; + } else { + emit('DOMException: "' + selector + '" is not a valid CSS selector.'); + return [ ]; + } + } + + // caching enabled ? + if (cachingEnabled) { + snap = base.snapshot; + // valid base context storage + if (snap && !snap.isExpired) { + if (snap.Results[selector] && + snap.Roots[selector] == from) { + return snap.Results[selector]; + } + } else { + setCache(true, base); + snap = base.snapshot; + } + } else { + if (position.test(selector)) { + // need to clear storage + snap = new Snapshot(); + } + } + + // pre-filtering pass allow to scale proportionally with big DOM trees; + // this block can be safely removed, it is a speed booster on big pages + // and still maintain the mandatory "document ordered" result set + + // commas separators are treated + // sequentially to maintain order + if (selector.indexOf(',') < 0) { + + // CLASS optimization + if ((parts = lastSlice.match(Optimize.CLASS)) && + (token = parts[parts.length - 1])) { + elements = byClass(token, from); + if (selector == '.' + token) { + if (cachingEnabled && elements.length > 0) { + done = true; + } else { + return elements; + } + } + } else + + // MULTI TAG optimization + if (!Optimize.descendants.test(selector) && + (parts = selector.match(/([-_\w]+)|(>)/g)) && NATIVE_GEBTN) { + if (parts.length > 1) { + elements = byTags(parts, from); + } else { + elements = toArray(from.getElementsByTagName(parts[0])); + } + if (cachingEnabled && elements.length > 0) { + done = true; + } else { + return elements; + } + } else + + // TAG optimization + if ((parts = lastSlice.match(Optimize.TAG)) && + (token = parts[parts.length - 1]) && NATIVE_GEBTN) { + elements = from.getElementsByTagName(token); + if (selector == token) { + if (cachingEnabled && elements.length > 0) { + done = true; + } else { + return toArray(elements); + } + } + } else + + // ID optimization + if ((parts = lastSlice.match(Optimize.ID)) && + (token = parts[parts.length - 1]) && from.getElementById) { + elements = [byId(token, from)]; + if (elements[0]) { + if (selector == '#' + token) { + if (cachingEnabled && elements.length > 0) { + done = true; + } else { + return elements; + } + } else { + //if (selector.length != (selector.lastIndexOf('#' + token) + token.length + 1)) { + // optimize narrowing context + from = elements[0].parentNode; + elements = null; + } + } else { + return [ ]; + } + } + + } + + if (!elements || elements.length === 0) { + + var tag = lastSlice.match(/\b([-_\w]+)\b/); + elements = from.getElementsByTagName('*'); + + if ((parts = lastSlice.match(/\#([-_\w]+)$/)) && selector == '#' + parts[1]) { + while ((node = elements[i++])) { + if (node.id == parts[1]) { + return [node]; + } + } + return [ ]; + } else + + if ((parts = lastSlice.match(/\b([-_\w]+)?(?:(\.[-_\w]+)|(\#[-_\w]+))/))) { + while ((node = elements[i++])) { + if ( + (!parts[1] || node.nodeName == parts[1]) && ( + (!parts[3] || (parts[2] == '#' && node.id == parts[3])) || + (!parts[3] || (parts[2] == '.' && node.className == parts[3])) + )) { + return [node]; + } + } + } else + + elements = toArray(elements); + + } + // end of prefiltering pass + + // save compiled selectors + if (!done && !compiledSelectors[selector]) { + compiledSelectors[selector] = compileGroup(selector, '', true); + } + + if (cachingEnabled) { + // a cached result set for the requested selector + snap.Results[selector] = done ? elements : + compiledSelectors[selector].call(this, elements, snap, base, root); + snap.Roots[selector] = from; + return snap.Results[selector]; + } + + // a fresh result set for the requested selector + return done ? + elements : + compiledSelectors[selector].call(this, elements, snap, base, root); + }, + + // use the new native Selector API if available, + // if missing, use the cross-browser client api + // @return array + select = NATIVE_QSAPI ? + select_qsa : + client_api, + + // element by id + // @return element reference or null + byId = + function(id, from) { + var i = 0, element, names, result; + from || (from = context); + id = id.replace(/\\/g, ''); + if (from.getElementById) { + result = from.getElementById(id); + if (result && id != getAttribute(result, 'id') && from.getElementsByName) { + names = from.getElementsByName(id); + result = null; + while ((element = names[i++])) { + if (element.getAttributeNode('id').value == id) { + result = element; + break; + } + } + } + } else { + result = NW.Dom.select('[id="' + id + '"]', from)[0] || null; + } + return result; + }, + + // elements by tag + // @return nodeList (live) + byTag = + function(tag, from) { + return (from || context).getElementsByTagName(tag || '*'); + }, + + // elements by name + // @return array + byName = + function(name, from) { + return this.select('[name="' + name.replace(/\\/g, '') + '"]', from || context); + }, + + // elements by class + // @return nodeList (native GEBCN) + // @return array (non native GEBCN) + byClass = !BUGGY_GEBCN ? + function(name, from) { + return slice.call(from.getElementsByClassName(name.replace(/\\/g, '')), 0); + } : + function(name, from) { + // context is handled in byTag for non native gEBCN + var i = 0, j = 0, r = [ ], node, + nodes = from.getElementsByTagName('*'), + name = new RegExp("(^|\\s)" + name + "(\\s|$)"); + while ((node = nodes[i++])) { + if (name.test(node.className)) { + r[j++] = node; + } + } + return r; + }, + + // recursively get nested tagNames + // example: for "div" pass ["div"] + // "ul li a" pass ["ul", "li", "a"] + // @c array of tag names combinators + // @f from context or default + // @return array + byTags = + function(c, f) { + var h, i, j, k, n, o, p, + id, e = [f || context], + r = [ ], s = [ ], t = [ ]; + h = 0; + i = 0; + while ((n = c[i++])) { + if (n == '>') { + j = 0; + while ((o = e[j++])) { + k = 0; + r = o[NATIVE_CHILDREN]; + while ((p = r[k++])) { + if (p.nodeName.toLowerCase() == c[i].toLowerCase()) { + s[h++] = p; + } + } + } + i++; + h = 0; + e = s; + s = [ ]; + t = [ ]; + } else { + j= 0; + while ((o = e[j++])) { + k = 0; + r = o.getElementsByTagName(n.replace(trim, '')); + while ((p = r[k++])) { + id = (p._cssId || (p._cssId = ++cssId)); + // discard duplicates + if (!t[id]) { + t[id] = true; + s[h++] = p; + } + } + } + h = 0; + e = s; + s = [ ]; + t = [ ]; + } + } + return e; + }, + + // attribute value + // @type string + getAttribute = NATIVE_HAS_ATTRIBUTE ? + function(element, attribute) { + return element.getAttribute(attribute) + ''; + } : + function(element, attribute) { + var node; + // specific URI attributes (parameter 2 to fix IE bug) + if (attributesURI[attribute.toLowerCase()]) { + return element.getAttribute(attribute, 2) + ''; + } + node = element.getAttributeNode(attribute); + return (node && node.value) + ''; + }, + + // attribute presence + // @return boolean + hasAttribute = NATIVE_HAS_ATTRIBUTE ? + function(element, attribute) { + return element.hasAttribute(attribute); + } : + function(element, attribute) { + // need to get at AttributeNode first on IE + var node = element.getAttributeNode(attribute); + // use both "specified" & "nodeValue" properties + return !!(node && (node.specified || node.nodeValue)); + }, + + // check if element matches the :link pseudo + isLink = + function(element) { + var nodeName = element.nodeName.toLowerCase(); + return hasAttribute(element,'href') && nodeName == 'a' || nodeName == 'area' || nodeName == 'link'; + }, + + // get best children collection available + // Safari 2.0.x "children" implementation + // differs, taken care by feature testing + // @return nodeList (live) + getChildren = + function(element) { + // childNodes is slower to loop through because it contains text nodes + // empty text nodes could be removed at startup to compensate this a bit + return element[NATIVE_CHILDREN] || element.childNodes; + }, + + // test element to be the only element child in its parent + // @return boolean + firstElement = + function(element) { + while ((element = element.previousSibling) && element.nodeType != 1) { } + return !element; + }, + + // test element to be the only element child in its parent + // @return boolean + lastElement = + function(element) { + while ((element = element.nextSibling) && element.nodeType != 1) { } + return !element; + }, + + // test element to be the only element child in its parent + // @return boolean + onlyElement = + function(element) { + return firstElement(element) && lastElement(element); + }, + + // test element to be the first element of-type in its parent + // @return boolean + firstOfType = + function(element) { + var nodeName = element.nodeName.toLowerCase(); + while ((element = element.previousSibling) && element.nodeName.toLowerCase() != nodeName) { } + return !element; + }, + + // test element to be the last element of-type in its parent + // @return boolean + lastOfType = + function(element) { + var nodeName = element.nodeName.toLowerCase(); + while ((element = element.nextSibling) && element.nodeName.toLowerCase() != nodeName) { } + return !element; + }, + + // test element to be the only element of-type in its parent + // @return boolean + onlyOfType = + function(element) { + return firstOfType(element) && lastOfType(element); + }, + + // child position by nodeType + // @return number + nthElement = + function(element) { + var i, j, node, nodes, parent, cache = snap.ChildIndex; + if (!element._cssId || !cache[element._cssId]) { + if ((parent = element.parentNode).nodeType == 1) { + i = 0; + j = 0; + nodes = parent[NATIVE_CHILDREN]; + while ((node = nodes[i++])) { + if (node.nodeType == 1) { + cache[node._cssId || (node._cssId = ++cssId)] = ++j; + } + } + snap.ChildCount[parent._cssId || (parent._cssId = ++cssId)] = j; + } else { + // does not have a parent (ex.: document) + return 0; + } + } + return cache[element._cssId]; + }, + + // child position by nodeName + // @return number + nthOfType = + function(element) { + var i, j, name, node, nodes, pid, parent, cache = snap.TwinsIndex; + if (!element._cssId || !cache[element._cssId]) { + if ((parent = element.parentNode).nodeType == 1) { + i = 0; + j = 0; + nodes = parent[NATIVE_CHILDREN]; + name = element.nodeName.toLowerCase(); + while ((node = nodes[i++])) { + if (node.nodeName.toLowerCase() == name) { + cache[node._cssId || (node._cssId = ++cssId)] = ++j; + } + } + pid = (parent._cssId || (parent._cssId = ++cssId)); + snap.TwinsCount[pid] || (snap.TwinsCount[pid] = { }); + snap.TwinsCount[pid][name] = j; + } else { + // does not have a parent (ex.: document) + return 0; + } + } + return cache[element._cssId]; + }, + + // convert nodeList to array + // @return array + toArray = NATIVE_SLICE_PROTO ? + function(list) { + return slice.call(list); + } : + function(list) { + // avoid using the length property of nodeLists + // it may have been overwritten by bad HTML code + var i = 0, array = [ ]; + while ((array[i] = list[i++])) { } + array.length--; + return array; + }, + + // cssId expando on elements, + // used to keep child indexes + // during a selection session + cssId = 1, + + // BEGIN: local context caching system + + // ****************** CACHING ****************** + // keep caching states for each context document + // set manually by using setCache(true, context) + cachingEnabled = NATIVE_MUTATION_EVENTS, + + // indexes/count of elements contained in rootElement + // expired by Mutation Events on DOM tree changes + Snapshot = + function() { + return { + // validation flag, creating it already expired, + // code validation will set it valid first time + isExpired: false, + // count of siblings by nodeType or nodeName + ChildCount: [ ], + TwinsCount: [ ], + // ordinal position by nodeType or nodeName + ChildIndex: [ ], + TwinsIndex: [ ], + // result sets and related root contexts + Results: [ ], + Roots: [ ] + }; + }, + + // local indexes, cleared + // between selection calls + snap = new Snapshot(), + + // enable/disable context caching system + // @d optional document context (iframe, xml document) + // script loading context will be used as default context + setCache = + function(enable, d) { + d || (d = context); + if (!!enable) { + d.snapshot = new Snapshot(); + startMutation(d); + } else { + stopMutation(d); + } + cachingEnabled = !!enable; + }, + + // invoked by mutation events to expire cached parts + mutationWrapper = + function(event) { + var d = event.target.ownerDocument || event.target; + stopMutation(d); + switch (event.type) { + case 'DOMAttrModified': + expireCache(d); + break; + case 'DOMNodeInserted': + expireCache(d); + break; + case 'DOMNodeRemoved': + expireCache(d); + break; + default: + break; + } + }, + + // append mutation events + startMutation = + function(d) { + if (!d.isCaching) { + // FireFox/Opera/Safari/KHTML have support for Mutation Events + d.addEventListener('DOMAttrModified', mutationWrapper, false); + d.addEventListener('DOMNodeInserted', mutationWrapper, false); + d.addEventListener('DOMNodeRemoved', mutationWrapper, false); + d.isCaching = true; + } + }, + + // remove mutation events + stopMutation = + function(d) { + if (d.isCaching) { + d.removeEventListener('DOMAttrModified', mutationWrapper, false); + d.removeEventListener('DOMNodeInserted', mutationWrapper, false); + d.removeEventListener('DOMNodeRemoved', mutationWrapper, false); + d.isCaching = false; + } + }, + + // expire complete cache + // can be invoked by Mutation Events or + // programmatically by other code/scripts + // document context is mandatory no checks + expireCache = + function(d) { + if (d && d.snapshot) { + d.snapshot.isExpired = true; + } + }; + + // END: local context caching system + + return { + + // for testing purposes ! + compile: + function(selector, mode) { + return compileGroup(selector, '', mode || false).toString(); + }, + + // enable/disable cache + setCache: setCache, + + // forced expire of DOM tree cache + expireCache: expireCache, + + // element match selector, return boolean true/false + match: match, + + // elements matching selector, starting from element + select: select, + + // Safari 2 bug with innerText (gasp!) + // used to strip tags from innerHTML + // shouldn't be public, but needed + stripTags: + function(s) { + return s.replace(/<\/?("[^\"]*"|'[^\']*'|[^>])+>/gi, ''); + }, + + // add selector patterns for user defined callbacks + registerSelector: + function (name, rexp, func) { + if (!Selectors[name]) { + Selectors[name] = { }; + Selectors[name].Expression = rexp; + Selectors[name].Callback = func; + } + }, + + // add or overwrite user defined operators + // TODO: check when overwriting standard operators + registerOperator: + function (symbol, resolver) { + if (!Operators[symbol]) { + Operators[symbol] = resolver; + } + }, + + // retrieve element by id attr + byId: byId, + + // retrieve elements by tag name + byTag: byTag, + + // retrieve elements by name attr + byName: byName, + + // retrieve elements by class name + byClass: byClass, + + // check if element matches the :link pseudo + isLink: isLink, + + // retrieve all children elements + getChildren: getChildren, + + // read the value of the attribute + // as was in the original HTML code + getAttribute: getAttribute, + + // check for the attribute presence + // as was in the original HTML code + hasAttribute: hasAttribute, + + // first child element any type + firstElement: firstElement, + + // last child element any type + lastElement: lastElement, + + // only child element any type + onlyElement: onlyElement, + + // first child element of-type + firstOfType: firstOfType, + + // last child element of-type + lastOfType: lastOfType, + + // only child element of-type + onlyOfType: onlyOfType, + + // nth child element any type + nthElement: nthElement, + + // nth child element of-type + nthOfType: nthOfType, + + // convert nodeList to array + toArray: toArray + + }; + +}(this); diff --git a/vendor/nwmatcher/selector_engine.js b/vendor/nwmatcher/selector_engine.js new file mode 100644 index 0000000..899838d --- /dev/null +++ b/vendor/nwmatcher/selector_engine.js @@ -0,0 +1,37 @@ +Prototype._original_nw = window.NW; +//= require +Prototype.NW = window.NW; + +// Restore globals. +window.NW = Prototype._original_nw; +delete Prototype._original_nw; + +Prototype.Selector = (function(NW) { + function extend(elements) { + for (var i = 0, length = elements.length; i < length; i++) + elements[i] = Element.extend(elements[i]); + return elements; + } + + function select(selector, scope) { + return extend(NW.select(selector, scope || document)); + } + + function filter(elements, selector) { + var results = [], element; + for (var i = 0, length = elements.length; i < length; i++) { + element = elements[i]; + if (NW.match(element, selector)) { + results.push(Element.extend(element)) + } + } + return results; + } + + return { + select: select, + match: NW.match, + filter: filter + }; +})(Prototype.NW.Dom); + diff --git a/vendor/sizzle b/vendor/sizzle deleted file mode 160000 index e0f5cbc..0000000 --- a/vendor/sizzle +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e0f5cbc75d12aa78f3ef30930414b2f88da7b2b8 diff --git a/src/dom/sizzle_adapter.js b/vendor/sizzle/selector_engine.js similarity index 96% rename from src/dom/sizzle_adapter.js rename to vendor/sizzle/selector_engine.js index 26249eb..efffb4d 100644 --- a/src/dom/sizzle_adapter.js +++ b/vendor/sizzle/selector_engine.js @@ -1,5 +1,5 @@ Prototype._original_sizzle = window.Sizzle; -//= require +//= require Prototype.Sizzle = window.Sizzle; // Restore globals. diff --git a/vendor/sizzle/sizzle b/vendor/sizzle/sizzle new file mode 160000 index 0000000..415e466 --- /dev/null +++ b/vendor/sizzle/sizzle @@ -0,0 +1 @@ +Subproject commit 415e466f70e5a53f589161b1f2944e5485007409 From cba5468b09a911310bc150683f3af9d9e4078f13 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 06:38:27 +0200 Subject: [PATCH 12/28] Nitpicking. --- vendor/sizzle/selector_engine.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vendor/sizzle/selector_engine.js b/vendor/sizzle/selector_engine.js index efffb4d..d7aeeda 100644 --- a/vendor/sizzle/selector_engine.js +++ b/vendor/sizzle/selector_engine.js @@ -1,5 +1,5 @@ Prototype._original_sizzle = window.Sizzle; -//= require +//= require "sizzle/sizzle" Prototype.Sizzle = window.Sizzle; // Restore globals. From 17e8064d8a154b14b97a29d91fc7dd849dff8265 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 06:39:59 +0200 Subject: [PATCH 13/28] Add legacy Prototype selector engine. Acessible as Prototype.Legacy. Use SELECTOR_ENGINE=legacy to build. --- vendor/legacy/legacy.js | 803 +++++++++++++++++++++++++++++++ vendor/legacy/selector_engine.js | 17 + 2 files changed, 820 insertions(+) create mode 100644 vendor/legacy/legacy.js create mode 100644 vendor/legacy/selector_engine.js diff --git a/vendor/legacy/legacy.js b/vendor/legacy/legacy.js new file mode 100644 index 0000000..ddfd04e --- /dev/null +++ b/vendor/legacy/legacy.js @@ -0,0 +1,803 @@ +/* Portions of the Prototype.Legacy class are derived from Jack Slocum's DomQuery, + * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style + * license. Please see http://www.yui-ext.com/ for more information. */ + +Prototype.Legacy = Class.create({ + initialize: function(expression) { + this.expression = expression.strip(); + + if (this.shouldUseSelectorsAPI()) { + this.mode = 'selectorsAPI'; + } else if (this.shouldUseXPath()) { + this.mode = 'xpath'; + this.compileXPathMatcher(); + } else { + this.mode = "normal"; + this.compileMatcher(); + } + + }, + + shouldUseXPath: (function() { + + // Some versions of Opera 9.x produce incorrect results when using XPath + // with descendant combinators. + // see: http://opera.remcol.ath.cx/bugs/index.php?action=bug&id=652 + var IS_DESCENDANT_SELECTOR_BUGGY = (function(){ + var isBuggy = false; + if (document.evaluate && window.XPathResult) { + var el = document.createElement('div'); + el.innerHTML = '
'; + + var xpath = ".//*[local-name()='ul' or local-name()='UL']" + + "//*[local-name()='li' or local-name()='LI']"; + + var result = document.evaluate(xpath, el, null, + XPathResult.ORDERED_NODE_SNAPSHOT_TYPE, null); + + isBuggy = (result.snapshotLength !== 2); + el = null; + } + return isBuggy; + })(); + + return function() { + if (!Prototype.BrowserFeatures.XPath) return false; + + var e = this.expression; + + // Safari 3 chokes on :*-of-type and :empty + if (Prototype.Browser.WebKit && + (e.include("-of-type") || e.include(":empty"))) + return false; + + // XPath can't do namespaced attributes, nor can it read + // the "checked" property from DOM nodes + if ((/(\[[\w-]*?:|:checked)/).test(e)) + return false; + + if (IS_DESCENDANT_SELECTOR_BUGGY) return false; + + return true; + } + + })(), + + shouldUseSelectorsAPI: function() { + if (!Prototype.BrowserFeatures.SelectorsAPI) return false; + + if (Prototype.Legacy.CASE_INSENSITIVE_CLASS_NAMES) return false; + + if (!Prototype.Legacy._div) Prototype.Legacy._div = new Element('div'); + + // Make sure the browser treats the selector as valid. Test on an + // isolated element to minimize cost of this check. + try { + Prototype.Legacy._div.querySelector(this.expression); + } catch(e) { + return false; + } + + return true; + }, + + compileMatcher: function() { + var e = this.expression, ps = Prototype.Legacy.patterns, h = Prototype.Legacy.handlers, + c = Prototype.Legacy.criteria, le, p, m, len = ps.length, name; + + if (Prototype.Legacy._cache[e]) { + this.matcher = Prototype.Legacy._cache[e]; + return; + } + + this.matcher = ["this.matcher = function(root) {", + "var r = root, h = Prototype.Legacy.handlers, c = false, n;"]; + + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i = 0; i"; + } +}); + +if (Prototype.BrowserFeatures.SelectorsAPI && + document.compatMode === 'BackCompat') { + // Versions of Safari 3 before 3.1.2 treat class names case-insensitively in + // quirks mode. If we detect this behavior, we should use a different + // approach. + Prototype.Legacy.CASE_INSENSITIVE_CLASS_NAMES = (function(){ + var div = document.createElement('div'), + span = document.createElement('span'); + + div.id = "prototype_test_id"; + span.className = 'Test'; + div.appendChild(span); + var isIgnored = (div.querySelector('#prototype_test_id .test') !== null); + div = span = null; + return isIgnored; + })(); +} + +Object.extend(Prototype.Legacy, { + _cache: { }, + + xpath: { + descendant: "//*", + child: "/*", + adjacent: "/following-sibling::*[1]", + laterSibling: '/following-sibling::*', + tagName: function(m) { + if (m[1] == '*') return ''; + return "[local-name()='" + m[1].toLowerCase() + + "' or local-name()='" + m[1].toUpperCase() + "']"; + }, + className: "[contains(concat(' ', @class, ' '), ' #{1} ')]", + id: "[@id='#{1}']", + attrPresence: function(m) { + m[1] = m[1].toLowerCase(); + return new Template("[@#{1}]").evaluate(m); + }, + attr: function(m) { + m[1] = m[1].toLowerCase(); + m[3] = m[5] || m[6]; + return new Template(Prototype.Legacy.xpath.operators[m[2]]).evaluate(m); + }, + pseudo: function(m) { + var h = Prototype.Legacy.xpath.pseudos[m[1]]; + if (!h) return ''; + if (Object.isFunction(h)) return h(m); + return new Template(Prototype.Legacy.xpath.pseudos[m[1]]).evaluate(m); + }, + operators: { + '=': "[@#{1}='#{3}']", + '!=': "[@#{1}!='#{3}']", + '^=': "[starts-with(@#{1}, '#{3}')]", + '$=': "[substring(@#{1}, (string-length(@#{1}) - string-length('#{3}') + 1))='#{3}']", + '*=': "[contains(@#{1}, '#{3}')]", + '~=': "[contains(concat(' ', @#{1}, ' '), ' #{3} ')]", + '|=': "[contains(concat('-', @#{1}, '-'), '-#{3}-')]" + }, + pseudos: { + 'first-child': '[not(preceding-sibling::*)]', + 'last-child': '[not(following-sibling::*)]', + 'only-child': '[not(preceding-sibling::* or following-sibling::*)]', + 'empty': "[count(*) = 0 and (count(text()) = 0)]", + 'checked': "[@checked]", + 'disabled': "[(@disabled) and (@type!='hidden')]", + 'enabled': "[not(@disabled) and (@type!='hidden')]", + 'not': function(m) { + var e = m[6], p = Prototype.Legacy.patterns, + x = Prototype.Legacy.xpath, le, v, len = p.length, name; + + var exclusion = []; + while (e && le != e && (/\S/).test(e)) { + le = e; + for (var i = 0; i= 0)]"; + return new Template(predicate).evaluate({ + fragment: fragment, a: a, b: b }); + } + } + } + }, + + criteria: { + tagName: 'n = h.tagName(n, r, "#{1}", c); c = false;', + className: 'n = h.className(n, r, "#{1}", c); c = false;', + id: 'n = h.id(n, r, "#{1}", c); c = false;', + attrPresence: 'n = h.attrPresence(n, r, "#{1}", c); c = false;', + attr: function(m) { + m[3] = (m[5] || m[6]); + return new Template('n = h.attr(n, r, "#{1}", "#{3}", "#{2}", c); c = false;').evaluate(m); + }, + pseudo: function(m) { + if (m[6]) m[6] = m[6].replace(/"/g, '\\"'); + return new Template('n = h.pseudo(n, "#{1}", "#{6}", r, c); c = false;').evaluate(m); + }, + descendant: 'c = "descendant";', + child: 'c = "child";', + adjacent: 'c = "adjacent";', + laterSibling: 'c = "laterSibling";' + }, + + patterns: [ + // combinators must be listed first + // (and descendant needs to be last combinator) + { name: 'laterSibling', re: /^\s*~\s*/ }, + { name: 'child', re: /^\s*>\s*/ }, + { name: 'adjacent', re: /^\s*\+\s*/ }, + { name: 'descendant', re: /^\s/ }, + + // selectors follow + { name: 'tagName', re: /^\s*(\*|[\w\-]+)(\b|$)?/ }, + { name: 'id', re: /^#([\w\-\*]+)(\b|$)/ }, + { name: 'className', re: /^\.([\w\-\*]+)(\b|$)/ }, + { name: 'pseudo', re: /^:((first|last|nth|nth-last|only)(-child|-of-type)|empty|checked|(en|dis)abled|not)(\((.*?)\))?(\b|$|(?=\s|[:+~>]))/ }, + { name: 'attrPresence', re: /^\[((?:[\w-]+:)?[\w-]+)\]/ }, + { name: 'attr', re: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ } + ], + + // for Prototype.Legacy.match and Element#match + assertions: { + tagName: function(element, matches) { + return matches[1].toUpperCase() == element.tagName.toUpperCase(); + }, + + className: function(element, matches) { + return Element.hasClassName(element, matches[1]); + }, + + id: function(element, matches) { + return element.id === matches[1]; + }, + + attrPresence: function(element, matches) { + return Element.hasAttribute(element, matches[1]); + }, + + attr: function(element, matches) { + var nodeValue = Element.readAttribute(element, matches[1]); + return nodeValue && Prototype.Legacy.operators[matches[2]](nodeValue, matches[5] || matches[6]); + } + }, + + handlers: { + // UTILITY FUNCTIONS + // joins two collections + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + a.push(node); + return a; + }, + + // marks an array of nodes for counting + mark: function(nodes) { + var _true = Prototype.emptyFunction; + for (var i = 0, node; node = nodes[i]; i++) + node._countedByPrototype = _true; + return nodes; + }, + + unmark: (function(){ + + // IE improperly serializes _countedByPrototype in (inner|outer)HTML + // due to node properties being mapped directly to attributes + var PROPERTIES_ATTRIBUTES_MAP = (function(){ + var el = document.createElement('div'), + isBuggy = false, + propName = '_countedByPrototype', + value = 'x'; + el[propName] = value; + isBuggy = (el.getAttribute(propName) === value); + el = null; + return isBuggy; + })(); + + return PROPERTIES_ATTRIBUTES_MAP ? + function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node.removeAttribute('_countedByPrototype'); + return nodes; + } : + function(nodes) { + for (var i = 0, node; node = nodes[i]; i++) + node._countedByPrototype = void 0; + return nodes; + } + })(), + + // mark each child node with its position (for nth calls) + // "ofType" flag indicates whether we're indexing for nth-of-type + // rather than nth-child + index: function(parentNode, reverse, ofType) { + parentNode._countedByPrototype = Prototype.emptyFunction; + if (reverse) { + for (var nodes = parentNode.childNodes, i = nodes.length - 1, j = 1; i >= 0; i--) { + var node = nodes[i]; + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; + } + } else { + for (var i = 0, j = 1, nodes = parentNode.childNodes; node = nodes[i]; i++) + if (node.nodeType == 1 && (!ofType || node._countedByPrototype)) node.nodeIndex = j++; + } + }, + + // filters out duplicates and extends all nodes + unique: function(nodes) { + if (nodes.length == 0) return nodes; + var results = [], n; + for (var i = 0, l = nodes.length; i < l; i++) + // use `typeof` operator to prevent errors + if (typeof (n = nodes[i])._countedByPrototype == 'undefined') { + n._countedByPrototype = Prototype.emptyFunction; + results.push(Element.extend(n)); + } + return Prototype.Legacy.handlers.unmark(results); + }, + + // COMBINATOR FUNCTIONS + descendant: function(nodes) { + var h = Prototype.Legacy.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName('*')); + return results; + }, + + child: function(nodes) { + var h = Prototype.Legacy.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) { + for (var j = 0, child; child = node.childNodes[j]; j++) + if (child.nodeType == 1 && child.tagName != '!') results.push(child); + } + return results; + }, + + adjacent: function(nodes) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + var next = this.nextElementSibling(node); + if (next) results.push(next); + } + return results; + }, + + laterSibling: function(nodes) { + var h = Prototype.Legacy.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + h.concat(results, Element.nextSiblings(node)); + return results; + }, + + nextElementSibling: function(node) { + while (node = node.nextSibling) + if (node.nodeType == 1) return node; + return null; + }, + + previousElementSibling: function(node) { + while (node = node.previousSibling) + if (node.nodeType == 1) return node; + return null; + }, + + // TOKEN FUNCTIONS + tagName: function(nodes, root, tagName, combinator) { + var uTagName = tagName.toUpperCase(); + var results = [], h = Prototype.Legacy.handlers; + if (nodes) { + if (combinator) { + // fastlane for ordinary descendant combinators + if (combinator == "descendant") { + for (var i = 0, node; node = nodes[i]; i++) + h.concat(results, node.getElementsByTagName(tagName)); + return results; + } else nodes = this[combinator](nodes); + if (tagName == "*") return nodes; + } + for (var i = 0, node; node = nodes[i]; i++) + if (node.tagName.toUpperCase() === uTagName) results.push(node); + return results; + } else return root.getElementsByTagName(tagName); + }, + + id: function(nodes, root, id, combinator) { + var targetNode = $(id), h = Prototype.Legacy.handlers; + + if (root == document) { + // We don't have to deal with orphan nodes. Easy. + if (!targetNode) return []; + if (!nodes) return [targetNode]; + } else { + // In IE, we can check sourceIndex to see if root is attached + // to the document. If not (or if sourceIndex is not present), + // we do it the hard way. + if (!root.sourceIndex || root.sourceIndex < 1) { + var nodes = root.getElementsByTagName('*'); + for (var j = 0, node; node = nodes[j]; j++) { + if (node.id === id) return [node]; + } + } + } + + if (nodes) { + if (combinator) { + if (combinator == 'child') { + for (var i = 0, node; node = nodes[i]; i++) + if (targetNode.parentNode == node) return [targetNode]; + } else if (combinator == 'descendant') { + for (var i = 0, node; node = nodes[i]; i++) + if (Element.descendantOf(targetNode, node)) return [targetNode]; + } else if (combinator == 'adjacent') { + for (var i = 0, node; node = nodes[i]; i++) + if (Prototype.Legacy.handlers.previousElementSibling(targetNode) == node) + return [targetNode]; + } else nodes = h[combinator](nodes); + } + for (var i = 0, node; node = nodes[i]; i++) + if (node == targetNode) return [targetNode]; + return []; + } + return (targetNode && Element.descendantOf(targetNode, root)) ? [targetNode] : []; + }, + + className: function(nodes, root, className, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + return Prototype.Legacy.handlers.byClassName(nodes, root, className); + }, + + byClassName: function(nodes, root, className) { + if (!nodes) nodes = Prototype.Legacy.handlers.descendant([root]); + var needle = ' ' + className + ' '; + for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { + nodeClassName = node.className; + if (nodeClassName.length == 0) continue; + if (nodeClassName == className || (' ' + nodeClassName + ' ').include(needle)) + results.push(node); + } + return results; + }, + + attrPresence: function(nodes, root, attr, combinator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + if (nodes && combinator) nodes = this[combinator](nodes); + var results = []; + for (var i = 0, node; node = nodes[i]; i++) + if (Element.hasAttribute(node, attr)) results.push(node); + return results; + }, + + attr: function(nodes, root, attr, value, operator, combinator) { + if (!nodes) nodes = root.getElementsByTagName("*"); + if (nodes && combinator) nodes = this[combinator](nodes); + var handler = Prototype.Legacy.operators[operator], results = []; + for (var i = 0, node; node = nodes[i]; i++) { + var nodeValue = Element.readAttribute(node, attr); + if (nodeValue === null) continue; + if (handler(nodeValue, value)) results.push(node); + } + return results; + }, + + pseudo: function(nodes, name, value, root, combinator) { + if (nodes && combinator) nodes = this[combinator](nodes); + if (!nodes) nodes = root.getElementsByTagName("*"); + return Prototype.Legacy.pseudos[name](nodes, value, root); + } + }, + + pseudos: { + 'first-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Prototype.Legacy.handlers.previousElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'last-child': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + if (Prototype.Legacy.handlers.nextElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'only-child': function(nodes, value, root) { + var h = Prototype.Legacy.handlers; + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) + results.push(node); + return results; + }, + 'nth-child': function(nodes, formula, root) { + return Prototype.Legacy.pseudos.nth(nodes, formula, root); + }, + 'nth-last-child': function(nodes, formula, root) { + return Prototype.Legacy.pseudos.nth(nodes, formula, root, true); + }, + 'nth-of-type': function(nodes, formula, root) { + return Prototype.Legacy.pseudos.nth(nodes, formula, root, false, true); + }, + 'nth-last-of-type': function(nodes, formula, root) { + return Prototype.Legacy.pseudos.nth(nodes, formula, root, true, true); + }, + 'first-of-type': function(nodes, formula, root) { + return Prototype.Legacy.pseudos.nth(nodes, "1", root, false, true); + }, + 'last-of-type': function(nodes, formula, root) { + return Prototype.Legacy.pseudos.nth(nodes, "1", root, true, true); + }, + 'only-of-type': function(nodes, formula, root) { + var p = Prototype.Legacy.pseudos; + return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); + }, + + // handles the an+b logic + getIndices: function(a, b, total) { + if (a == 0) return b > 0 ? [b] : []; + return $R(1, total).inject([], function(memo, i) { + if (0 == (i - b) % a && (i - b) / a >= 0) memo.push(i); + return memo; + }); + }, + + // handles nth(-last)-child, nth(-last)-of-type, and (first|last)-of-type + nth: function(nodes, formula, root, reverse, ofType) { + if (nodes.length == 0) return []; + if (formula == 'even') formula = '2n+0'; + if (formula == 'odd') formula = '2n+1'; + var h = Prototype.Legacy.handlers, results = [], indexed = [], m; + h.mark(nodes); + for (var i = 0, node; node = nodes[i]; i++) { + if (!node.parentNode._countedByPrototype) { + h.index(node.parentNode, reverse, ofType); + indexed.push(node.parentNode); + } + } + if (formula.match(/^\d+$/)) { // just a number + formula = Number(formula); + for (var i = 0, node; node = nodes[i]; i++) + if (node.nodeIndex == formula) results.push(node); + } else if (m = formula.match(/^(-?\d*)?n(([+-])(\d+))?/)) { // an+b + if (m[1] == "-") m[1] = -1; + var a = m[1] ? Number(m[1]) : 1; + var b = m[2] ? Number(m[2]) : 0; + var indices = Prototype.Legacy.pseudos.getIndices(a, b, nodes.length); + for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { + for (var j = 0; j < l; j++) + if (node.nodeIndex == indices[j]) results.push(node); + } + } + h.unmark(nodes); + h.unmark(indexed); + return results; + }, + + 'empty': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) { + // IE treats comments as element nodes + if (node.tagName == '!' || node.firstChild) continue; + results.push(node); + } + return results; + }, + + 'not': function(nodes, selector, root) { + var h = Prototype.Legacy.handlers, selectorType, m; + var exclusions = new Prototype.Legacy(selector).findElements(root); + h.mark(exclusions); + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node._countedByPrototype) results.push(node); + h.unmark(exclusions); + return results; + }, + + 'enabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (!node.disabled && (!node.type || node.type !== 'hidden')) + results.push(node); + return results; + }, + + 'disabled': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.disabled) results.push(node); + return results; + }, + + 'checked': function(nodes, value, root) { + for (var i = 0, results = [], node; node = nodes[i]; i++) + if (node.checked) results.push(node); + return results; + } + }, + + operators: { + '=': function(nv, v) { return nv == v; }, + '!=': function(nv, v) { return nv != v; }, + '^=': function(nv, v) { return nv == v || nv && nv.startsWith(v); }, + '$=': function(nv, v) { return nv == v || nv && nv.endsWith(v); }, + '*=': function(nv, v) { return nv == v || nv && nv.include(v); }, + '~=': function(nv, v) { return (' ' + nv + ' ').include(' ' + v + ' '); }, + '|=': function(nv, v) { return ('-' + (nv || "").toUpperCase() + + '-').include('-' + (v || "").toUpperCase() + '-'); } + }, + + split: function(expression) { + var expressions = []; + expression.scan(/(([\w#:.~>+()\s-]+|\*|\[.*?\])+)\s*(,|$)/, function(m) { + expressions.push(m[1].strip()); + }); + return expressions; + }, + + matchElements: function(elements, expression) { + var matches = $$(expression), h = Prototype.Legacy.handlers; + h.mark(matches); + for (var i = 0, results = [], element; element = elements[i]; i++) + if (element._countedByPrototype) results.push(element); + h.unmark(matches); + return results; + }, + + findElement: function(elements, expression, index) { + if (Object.isNumber(expression)) { + index = expression; expression = false; + } + return Prototype.Legacy.matchElements(elements, expression || '*')[index || 0]; + }, + + findChildElements: function(element, expressions) { + expressions = Prototype.Legacy.split(expressions.join(',')); + var results = [], h = Prototype.Legacy.handlers; + for (var i = 0, l = expressions.length, selector; i < l; i++) { + selector = new Prototype.Legacy(expressions[i].strip()); + h.concat(results, selector.findElements(element)); + } + return (l > 1) ? h.unique(results) : results; + } +}); + +if (Prototype.Browser.IE) { + Object.extend(Prototype.Legacy.handlers, { + // IE returns comment nodes on getElementsByTagName("*"). + // Filter them out. + concat: function(a, b) { + for (var i = 0, node; node = b[i]; i++) + if (node.tagName !== "!") a.push(node); + return a; + } + }); +} + diff --git a/vendor/legacy/selector_engine.js b/vendor/legacy/selector_engine.js new file mode 100644 index 0000000..0ad6a86 --- /dev/null +++ b/vendor/legacy/selector_engine.js @@ -0,0 +1,17 @@ +//= require "legacy" + +Prototype.Selector = (function(Legacy) { + function select(selector, scope) { + return Legacy.findChildElements(scope || document, [selector]); + } + + function match(element, selector) { + return !!Legacy.findElement([element], selector); + } + + return { + select: select, + match: match, + filter: Legacy.matchElements + }; +})(Prototype.Legacy); From 15c323b9ac8fa22b41330fcc1c851297562b0cb3 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 07:18:52 +0200 Subject: [PATCH 14/28] Include NWMatcher as a submodule. --- .gitmodules | 3 + Rakefile | 7 + vendor/nwmatcher/nwmatcher | 1 + vendor/nwmatcher/nwmatcher-1.1.1.js | 1441 --------------------------- vendor/nwmatcher/selector_engine.js | 10 +- 5 files changed, 13 insertions(+), 1449 deletions(-) create mode 160000 vendor/nwmatcher/nwmatcher delete mode 100644 vendor/nwmatcher/nwmatcher-1.1.1.js diff --git a/.gitmodules b/.gitmodules index 61fd316..51c3adf 100644 --- a/.gitmodules +++ b/.gitmodules @@ -14,3 +14,6 @@ [submodule "vendor/sizzle/sizzle"] path = vendor/sizzle/sizzle url = git://github.com/jeresig/sizzle.git +[submodule "vendor/nwmatcher/nwmatcher"] + path = vendor/nwmatcher/nwmatcher + url = git://github.com/dperini/nwmatcher.git diff --git a/Rakefile b/Rakefile index 2b36e08..ff38e77 100755 --- a/Rakefile +++ b/Rakefile @@ -40,6 +40,7 @@ module PrototypeHelper def self.sprocketize(path, source, destination = nil, strip_comments = true) require_sprockets require_sizzle + require_nwmatcher secretary = Sprockets::Secretary.new( :root => File.join(ROOT_DIR, path), :load_path => self.load_path, @@ -100,6 +101,12 @@ module PrototypeHelper end end + def self.require_nwmatcher + if !File.exists?(File.join(ROOT_DIR, 'vendor', 'nwmatcher', 'src', 'nwmatcher.js')) + exit unless get_submodule("NWMmatcher", "nwmatcher/nwmatcher") + end + end + def self.get_submodule(name, path) require_git puts "\nYou seem to be missing #{name}. Obtaining it via git...\n\n" diff --git a/vendor/nwmatcher/nwmatcher b/vendor/nwmatcher/nwmatcher new file mode 160000 index 0000000..c9f5d5d --- /dev/null +++ b/vendor/nwmatcher/nwmatcher @@ -0,0 +1 @@ +Subproject commit c9f5d5d4fc4ca294477f803bb8d688a8d45de664 diff --git a/vendor/nwmatcher/nwmatcher-1.1.1.js b/vendor/nwmatcher/nwmatcher-1.1.1.js deleted file mode 100644 index 82bc547..0000000 --- a/vendor/nwmatcher/nwmatcher-1.1.1.js +++ /dev/null @@ -1,1441 +0,0 @@ -/* - * Copyright (C) 2007-2009 Diego Perini - * All rights reserved. - * - * nwmatcher.js - A fast CSS selector engine and matcher - * - * Author: Diego Perini - * Version: 1.1.1 - * Created: 20070722 - * Release: 20090516 - * - * License: - * http://javascript.nwbox.com/NWMatcher/MIT-LICENSE - * Download: - * http://javascript.nwbox.com/NWMatcher/nwmatcher.js - */ - -window.NW || (window.NW = {}); - -NW.Dom = function(global) { - - var version = 'nwmatcher-1.1.1', - - // processing context - base = global.document, - - // script loading context - context = global.document, - - // context root element (HTML) - root = context.documentElement, - - // current DOM viewport/window, also used to - // detect Safari 2.0.x [object AbstractView] - view = base.defaultView || base.parentWindow, - - // cache access to native slice - slice = Array.prototype.slice, - - /* BEGIN FEATURE TESTING */ - - // detect native method in object - // not same scope of isHostObject - isNative = function(object, method) { - return object && method in object && - typeof object[method] != 'string' && - // IE/W3C browsers will return [native code] - // Safari 2.0.x and older will return [function] - (/\{\s*\[native code[^\]]*\]\s*\}|^\[function\]$/). - test(object[method]); - }, - - // NOTE: NATIVE_XXXXX check for existance of method only - // so through the code read it as "supported", maybe BUGGY - - // detect native getAttribute/hasAttribute methods, - // frameworks extend these to elements, but it seems - // this does not work for XML namespaced attributes, - // used to check both getAttribute/hasAttribute in IE - NATIVE_HAS_ATTRIBUTE = isNative(root, 'hasAttribute'), - - // detect if DOM methods are native in browsers - NATIVE_QSAPI = isNative(context, 'querySelector'), - NATIVE_GEBID = isNative(context, 'getElementById'), - NATIVE_GEBTN = isNative(root, 'getElementsByTagName'), - NATIVE_GEBCN = isNative(root, 'getElementsByClassName'), - - // get name of best children collection property available - // detect Safari 2.0.x different children implementation - NATIVE_CHILDREN = - 'children' in root ? - (view && global !== view ? - 'childNodes' : - 'children') : - 'childNodes', - - // nodeList can be converted by native .slice() - // Opera 9.27 and an id="length" will fold this - NATIVE_SLICE_PROTO = - (function() { - var isBuggy = false, div = context.createElement('div'); - try { - div.innerHTML = '
'; - root.insertBefore(div, root.firstChild); - isBuggy = !!slice.call(div.childNodes, 0)[0]; - } catch(e) { - } finally { - root.removeChild(div).innerHTML = ''; - } - return isBuggy; - })(), - - // check for Mutation Events, DOMAttrModified should be - // enough to ensure DOMNodeInserted/DOMNodeRemoved exist - NATIVE_MUTATION_EVENTS = root.addEventListener ? - (function() { - var isBuggy, id = root.id, - handler = function() { - root.removeEventListener('DOMAttrModified', handler, false); - NATIVE_MUTATION_EVENTS = true; - root.id = id; - }; - root.addEventListener('DOMAttrModified', handler, false); - // now modify an attribute - root.id = 'nw'; - isBuggy = root.id != 'nw'; - root.id = id; - handler = null; - return isBuggy; - })() : - false, - - // NOTE: BUGGY_XXXXX check both for existance and no known bugs, - // so through the code read it as "not supported", or "undefined" - - BUGGY_GEBID = NATIVE_GEBID ? - (function() { - var isBuggy, div = context.createElement('div'); - div.innerHTML = ''; - root.insertBefore(div, root.firstChild); - isBuggy = !!div.ownerDocument.getElementById('Z'); - root.removeChild(div); - div = null; - return isBuggy; - })() : - true, - - // detect IE gEBTN comment nodes bug - BUGGY_GEBTN = NATIVE_GEBTN ? - (function() { - var isBuggy, div = context.createElement('div'); - div.appendChild(context.createComment('')); - div = div.getElementsByTagName('*')[0]; - isBuggy = !!(div && div.nodeType == 8); - div = null; - return isBuggy; - })() : - true, - - // detect Opera gEBCN second class and/or UTF8 bugs - // test is taken from the jQuery selector test suite - BUGGY_GEBCN = NATIVE_GEBCN ? - (function() { - var isBuggy, div = context.createElement('div'); - div.innerHTML = ''; - isBuggy = !div.getElementsByClassName('台北')[0]; - div = null; - return isBuggy; - })() : - true, - - // check Seletor API implementations - BUGGY_QSAPI = NATIVE_QSAPI ? (function() { - var isBuggy, pattern = [], div = context.createElement('div'); - - // WebKit case sensitivity bug with className (when no DOCTYPE) - // https://bugs.webkit.org/show_bug.cgi?id=19047 - div.innerHTML = ''; - if (context.compatMode == 'BackCompat' && div.querySelector('.x') !== null) { - return { 'test': function() { return true; } }; - } - - // check :enabled :disabled bugs with hidden fields (Firefox 3.5 QSA bug) - // http://www.w3.org/TR/html5/interactive-elements.html#selector-enabled - div.innerHTML = ''; - // IE8 throws error with these pseudos - try { - isBuggy = div.querySelectorAll(':enabled').length === 1; - } catch(e) { } - isBuggy && pattern.push(':enabled', ':disabled'); - - // check :link bugs with hyperlinks matching (Firefox/Safari) - div.innerHTML = ''; - div.querySelectorAll(':link').length !== 1 && pattern.push(':link'); - - return pattern.length - ? new RegExp(pattern.join('|')) - : { 'test': function() { return false; } }; - })() : - true, - - /* END FEATURE TESTING */ - - // map of attribute names (in HTML and DOM namespaces) - // many are missing here, or maybe there are too many - // first two lines will cover most real cases anyway - /* - // we do not have to write attributes and - // we have a fixed internal getAttribute - // maybe we can skip this case juggling - Attributes = { - 'class': 'className', 'for': 'htmlFor', - 'classname': 'className', 'htmlfor': 'htmlFor', - 'tabindex': 'tabIndex', 'accesskey': 'accessKey', 'maxlength': 'maxLength', - 'readonly': 'readOnly', 'longdesc': 'longDesc', 'frameborder': 'frameBorder', - 'ismap': 'isMap', 'usemap': 'useMap', 'nohref': 'noHref', 'nowrap': 'noWrap', - 'colspan': 'colSpan', 'rowspan': 'rowSpan', - 'cellpadding': 'cellPadding', 'cellspacing': 'cellSpacing', - 'marginwidth': 'marginWidth', 'marginheight': 'marginHeight' - }, - */ - - // See Niels Leenheer blog http://rakaz.nl/item/css_selector_bugs_case_sensitivity - // - // Each attribute definition includes information about the case-sensitivity of its values. - // http://www.w3.org/TR/html4/types.html#h-6.1 - // - // HTML 4 and XHTML both have some attributes that have pre-defined and limited sets of values. - // http://www.w3.org/TR/xhtml1/#h-4.11 - - // Safari 2.0.x seems to always treat attributes as in Quirks mode - insensitiveMap = /^CSS/i.test(context.compatMode) || (view && global !== view) ? { - // must be trated case insensitive in both HTML and XHTML (Strict ?) - 'accept': 1, 'accept-charset': 1, 'alink': 1, 'axis': 1, - 'bgcolor': 1, 'charset': 1, 'codetype': 1, 'color': 1, - 'face': 1, 'enctype': 1, 'hreflang': 1, 'http-equiv': 1, - 'lang': 1, 'language': 1, 'link': 1, 'media': 1, 'rel': 1, - 'rev': 1, 'target': 1, 'text': 1, 'type': 1, 'vlink': 1 - } : { - // must be treated case insensitive in HTML (Quirks ?) - 'align': 1, 'checked': 1, 'clear': 1, 'compact': 1, 'declare': 1, - 'defer': 1, 'dir': 1, 'disabled': 1, 'frame': 1, 'method': 1, - 'multiple': 1, 'nohref': 1, 'noresize': 1, 'noshade': 1, 'nowrap': 1, - 'readonly': 1, 'rules': 1, 'scope': 1, 'scrolling': 1, 'selected': 1, - 'shape': 1, 'valign': 1, 'valuetype': 1 - }, - - // attribute referencing URI values need special treatment in IE - attributesURI = { - 'action': 2, 'cite': 2, 'codebase': 2, 'data': 2, 'href': 2, - 'longdesc': 2, 'lowsrc': 2, 'src': 2, 'usemap': 2 - }, - - // selection functions returning collections - compiledSelectors = { }, - - // matching functions returning booleans - compiledMatchers = { }, - - // place to add exotic functionalities - Selectors = { - // as a simple example this will check - // for chars not in standard ascii table - // - // 'mySpecialSelector': { - // 'Expression': /\u0080-\uffff/, - // 'Callback': mySelectorCallback - //} - // - // 'mySelectorCallback' will be invoked - // only after passing all other standard - // checks and only if none of them worked - }, - - // trim leading/trailing whitespaces - trim = /^\s+|\s+$/g, - - // nth pseudo selectors - position = /:(nth|of-type)/, - - // ascii extended - ascii = /\x00-\xff/, - - // http://www.w3.org/TR/css3-syntax/#characters - // unicode/ISO 10646 characters 161 and higher - // encoding = '|[\\u00a1-\\uffff]',// correct - // NOTE: Safari 2.0.x crashes with escaped (\\) - // Unicode ranges in regular expressions so we - // use a negated character range class instead - // NOTE: [^\\w\\W] tested as good replacement - encoding = '|[^\\x00-\\xa0]', - - // selector validator discard invalid chars - validator = new RegExp("([-_*\\w]" + encoding + ")"), - - // split comma separated selector groups, exclude commas inside () [] - // example: (#div a, ul > li a) group 1 is (#div a) group 2 is (ul > li a) - group = /(([^,\(\)\[\]]+|\([^\(\)]+\)|\(.*\)|\[[^\[\]]+\]|\[.*\]|\\.|\*)+)/g, - - // attribute operators - Operators = { - // ! is not really in the specs - // still unit tests have to pass - '!': "%p!=='%m'", - '=': "%p==='%m'", - '^': "%p.indexOf('%m')==0", - '*': "%p.indexOf('%m')>-1", - // sensitivity handled by compiler - // NOTE: working alternative - // '|': "/%m-/i.test(%p+'-')", - '|': "(%p+'-').indexOf('%m-')==0", - '~': "(' '+%p+' ').indexOf(' %m ')>-1", - // precompile in '%m' string length to optimize - // NOTE: working alternative - // '$': "%p.lastIndexOf('%m')==%p.length-'%m'.length" - '$': "%p.substr(%p.length - '%m'.length) === '%m'" - }, - - // optimization expressions - Optimize = { - ID: new RegExp("#((?:[-_\\w]" + encoding + "|\\\\.)+)*"), - TAG: new RegExp("((?:[-_\\w]" + encoding + "|\\\\.)+)*"), - CLASS: new RegExp("\\.((?:[-_\\w]" + encoding + "|\\\\.)+)*"), - // split last, right most, selector group token - TOKEN: /([^\ \>\+\~\,\(\)\[\]]+|\([^\(\)]+\)|\(.*\)|\[[^\[\]]+\]|\[.*\])+/g, - descendants: /[^> \w]/, - siblings: /[^+~\w]/ - }, - - // precompiled Regular Expressions - Patterns = { - // element attribute matcher - attribute: /^\[\s*([-\w]*:?(?:[-\w])+)\s*(?:([!^$*~|]*)?(\=)?\s*(["']*)?([^'"]*?)\4)\s*\](.*)/, - // structural pseudo-classes - spseudos: /^\:(root|empty|nth)?-?(first|last|only)?-?(child)?-?(of-type)?(\((?:even|odd|[^\)]*)\))?(.*)/, - // uistates + dynamic + negation pseudo-classes - dpseudos: /^\:((?:[-\w]|\\.)+)(\(([\x22\x27]*)?(.*?(\(.*?\))?[^(]*?)\3\))?(.*)/, - // E > F - children: /^\s*\>\s*(.*)/, - // E + F - adjacent: /^\s*\+\s*(.*)/, - // E ~ F - relative: /^\s*\~\s*(.*)/, - // E F - ancestor: /^(\s+)(.*)/, - // all - all: /^\*(.*)/, - // id - id: new RegExp("^#((?:[-_\\w]" + encoding + "|\\\\.)+)(.*)"), - // tag - tagName: new RegExp("^((?:[-_\\w]" + encoding + "]\\\\.)+)(.*)"), - // class - className: new RegExp("^\\.((?:[-_\\w]" + encoding + "|\\\\.)+)(.*)") - }, - - // current CSS3 grouping of Pseudo-Classes - // they allowed implementing extensions - // and improve error notifications - CSS3PseudoClasses = { - Structural: { - 'root': 0, 'empty': 0, - 'first-child': 0, 'last-child': 0, 'only-child': 0, - 'first-of-type': 0, 'last-of-type': 0, 'only-of-type': 0, - 'first-child-of-type': 0, 'last-child-of-type': 0, 'only-child-of-type': 0, - 'nth-child': 0, 'nth-last-child': 0, 'nth-of-type': 0, 'nth-last-of-type': 0 - // (the 4rd line is not in W3C CSS specs but is an accepted alias of 3nd line) - }, - // originally separated in different pseudo-classes - // we have grouped them to optimize a bit size+speed - // all are going through the same code path (switch) - // the assigned value represent current spec status: - // 0 = CSS3, 1 = CSS2, 2 = maybe implemented - Others: { - //UIElementStates: { - // we group them to optimize - 'checked': 0, 'disabled': 0, 'enabled': 0, 'selected': 1, 'indeterminate': 2, - //}, - //Dynamic: { - 'active': 0, 'focus': 0, 'hover': 0, 'link': 0, 'visited': 0, - //}, - // Target: { - 'target': 0, - //}, - // Language: { - 'lang': 0, - //}, - // Negation: { - 'not': 0, - //}, - // Content: { - // http://www.w3.org/TR/2001/CR-css3-selectors-20011113/#content-selectors - 'contains': 2 - //} - } - }, - - // conditionals optimizers for the compiler - - // do not change this, it is searched & replaced - ACCEPT_NODE = 'r[X++]=N;continue main;', - - // fix for IE gEBTN('*') returning collection with comment nodes - SKIP_COMMENTS = BUGGY_GEBTN ? 'if(e.nodeType!=1){continue;}' : '', - - // use the textContent or innerText property to check CSS3 :contains - // Safari 2 has a bug with innerText and hidden content, using an - // internal replace on the innerHTML property avoids trashing it - CONTAINS_TEXT = - 'textContent' in root ? - 'e.textContent' : - (function() { - var t = context.createElement('div'); - t.innerHTML = '

p

'; - t.style.display = 'none'; - return t.innerText.length > 0 ? - 'e.innerText' : - 'this.stripTags(e.innerHTML)'; - })(), - - // to check extensions have not yet been registered - // @return boolean - IS_EMPTY = - function(object) { - if (object && typeof object == 'object') { - for (var i in object) { return false; } - return true; - } - return false; - }, - - // compile a comma separated group of selector - // @mode boolean true for select, false for match - // @return function (compiled) - compileGroup = - function(selector, source, mode) { - var i = 0, seen = { }, parts, token; - if ((parts = selector.match(group))) { - // for each selector in the group - for ( ; i < parts.length; ++i) { - token = parts[i].replace(trim, ''); - // avoid repeating the same token - // in comma separated group (p, p) - if (!seen[token]) { - seen[token] = true; - // reset element reference after the - // first comma if using select() mode - if (i > 0) { - source += 'e=N;'; - } - // insert corresponding mode function - if (mode) { - source += compileSelector(token, ACCEPT_NODE); - } else { - source += compileSelector(token, 'return true;'); - } - } - } - } - if (mode) { - // for select method - return new Function('c,s,d,h', 'var k,e,r,n,C,N,T,X=0,x=0;main:for(k=0,r=[];e=N=c[k];k++){' + SKIP_COMMENTS + source + '}return r;'); - } else { - // for match method - return new Function('e,s,d,h', 'var n,C,N=e,T,X=0,x=0;' + source + 'return false;'); - } - }, - - // compile a CSS3 string selector into - // ad-hoc javascript matching function - // @return string (to be compiled) - compileSelector = - function(selector, source) { - - var i, a, b, n, k, expr, match, result, status, test, type; - - k = 0; - - while (selector) { - - // *** Universal selector - // * match all (empty block, do not remove) - if ((match = selector.match(Patterns.all))) { - // do nothing, handled in the compiler where - // BUGGY_GEBTN return comment nodes (ex: IE) - } - // *** ID selector - // #Foo Id case sensitive - else if ((match = selector.match(Patterns.id))) { - // document can contain conflicting elements (id/name) - source = 'if((n=e.getAttributeNode("id"))&&n.value=="' + match[1] + '"){' + source + '}'; - //source = 'if(e.getAttribute("id")=="' + match[1] + '"){' + source + '}'; - //source = 'if(e.id=="' + match[1] + '"){' + source + '}'; - } - // *** Type selector - // Foo Tag (case insensitive) - else if ((match = selector.match(Patterns.tagName))) { - // both tagName and nodeName properties may be upper or lower case - // depending on their creation NAMESPACE in createElementNS() - source = 'T=e.nodeName;if(T=="' + match[1].toUpperCase() + '"||T=="' + match[1].toLowerCase() + '"){' + source + '}'; - //source = 'if(e.nodeName=="' + match[1].toUpperCase() + '"){' + source + '}'; - //source = 'if(/' + match[1] + '/i.test(e.nodeName)){' + source + '}'; - } - // *** Class selector - // .Foo Class (case sensitive) - else if ((match = selector.match(Patterns.className))) { - // W3C CSS3 specs: element whose "class" attribute has been assigned a list of whitespace-separated values - // see section 6.4 Class selectors and notes at the bottom; explicitly non-normative in this specification. - //source = 'if((" "+e.className+" ").replace(/\\s+/g," ").indexOf(" ' + match[1] + ' ")>-1){' + source + '}'; - source = 'C=e.className;if(C&&(" "+C+" ").indexOf(" ' + match[1] + ' ")>-1){' + source + '}'; - } - // *** Attribute selector - // [attr] [attr=value] [attr="value"] [attr='value'] and !=, *=, ~=, |=, ^=, $= - // case sensitivity is treated differently depending on the document type (see map) - else if ((match = selector.match(Patterns.attribute))) { - // xml namespaced attribute ? - expr = match[1].split(':'); - expr = expr.length == 2 ? expr[1] : expr[0] + ''; - // check case treatment from insensitiveMap - if (insensitiveMap[expr.toLowerCase()]) { - match[5] = match[5].toLowerCase(); - } - source = 'if(' + - (Operators[(match[2] || match[3])] || 'this.hasAttribute(e,"' + match[1] + '")'). - replace(/\%p/g, 'this.getAttribute(e,"' + match[1] + '")' + - (expr ? '' : '.toLowerCase()')).replace(/\%m/g, match[5]) + - '){' + source + '}'; - } - // *** Adjacent sibling combinator - // E + F (F adiacent sibling of E) - else if ((match = selector.match(Patterns.adjacent))) { - source = 'while((e=e.previousSibling)){if(e.nodeType==1){' + source + 'break;}}'; - } - // *** General sibling combinator - // E ~ F (F relative sibling of E) - else if ((match = selector.match(Patterns.relative))) { - // previousSibling particularly slow on Gecko based browsers prior to FF3.1 - //source = 'while((e=e.previousSibling)){if(e.nodeType==1){' + source + '}}'; - k++; - source = - 'var N' + k + '=e;e=e.parentNode.firstChild;' + - 'while(e!=N' + k +'){if(e.nodeType==1){' + source + '}e=e.nextSibling;}'; - } - // *** Child combinator - // E > F (F children of E) - else if ((match = selector.match(Patterns.children))) { - source = 'if((e=e.parentNode)&&e.nodeType==1){' + source + '}'; - } - // *** Descendant combinator - // E F (E ancestor of F) - else if ((match = selector.match(Patterns.ancestor))) { - source = 'while((e=e.parentNode)&&e.nodeType==1){' + source + '}'; - } - // *** Structural pseudo-classes - // :root, :empty, - // :first-child, :last-child, :only-child, - // :first-of-type, :last-of-type, :only-of-type, - // :nth-child(), :nth-last-child(), :nth-of-type(), :nth-last-of-type() - else if ((match = selector.match(Patterns.spseudos)) && - selector.match(/([-\w]+)/)[0] in CSS3PseudoClasses.Structural) { - - switch (match[1]) { - case 'root': - // element root of the document - source = 'if(e===h){' + source + '}'; - break; - case 'empty': - // element that has no children - source = 'if(!e.firstChild){' + source + '}'; - break; - default: - type = match[4] == 'of-type' ? 'OfType' : 'Element'; - - if (match[5]) { - // remove the ( ) grabbed above - match[5] = match[5].replace(/\(|\)/g, ''); - if (match[5] == 'even') { - a = 2; - b = 0; - } else if (match[5] == 'odd') { - a = 2; - b = 1; - } else { - // assumes correct "an+b" format - a = match[5].match(/^-/) ? -1 : match[5].match(/^n/) ? 1 : 0; - a = a || ((n = match[5].match(/(-?\d{1,})n/)) ? parseInt(n[1], 10) : 0); - b = 0 || ((n = match[5].match(/(-?\d{1,})$/)) ? parseInt(n[1], 10) : 0); - } - - // executed after the count is computed - expr = match[2] == 'last' ? (match[4] ? - '(e==h?1:s.TwinsCount[e.parentNode._cssId][e.nodeName.toLowerCase()])' : - '(e==h?1:s.ChildCount[e.parentNode._cssId])') + '-' + (b - 1) : b; - - test = - b < 0 ? - a <= 1 ? - '<=' + Math.abs(b) : - '%' + a + '===' + (a + b) : - a > Math.abs(b) ? '%' + a + '===' + b : - a === Math.abs(b) ? '%' + a + '===' + 0 : - a === 0 ? '==' + expr : - a < 0 ? '<=' + b : - a > 0 ? '>=' + b : - ''; - - // 4 cases: 1 (nth) x 4 (child, of-type, last-child, last-of-type) - source = 'if(this.' + match[1] + type + '(e)' + test + '){' + source + '}'; - } else { - // 6 cases: 3 (first, last, only) x 1 (child) x 2 (-of-type) - // too much overhead calling functions out of the main loop ? - //source = 'if(this.' + match[2] + type + '(e)){' + source + '}'; - source = (match[4] ? 'T=e.nodeName;' : '') + - 'n=e;while((n=n.' + (match[2] == 'first' ? 'previous' : 'next') + 'Sibling)&&' + - 'n.node' + (match[4] ? 'Name!=T' : 'Type!=1') + ');' + - 'if(!n){' + (match[2] == 'first' || match[2] == 'last' ? source : - 'n=e;while((n=n.' + (match[2] == 'only' ? 'previous' : 'next') + 'Sibling)&&' + - 'n.node' + (match[4] ? 'Name!=T' : 'Type!=1') + ');' + - 'if(!n){' + source + '}') + - '}'; - } - break; - } - - } - // *** Dynamic pseudo-classes - // CSS3 :not, :contains, :enabled, :disabled, :checked, :target - // CSS2 :active, :focus, :hover (no way yet) - // CSS1 :link, :visited - else if ((match = selector.match(Patterns.dpseudos)) && - selector.match(/([-\w]+)/)[0] in CSS3PseudoClasses.Others) { - - if (match[2]) { - // if the pseudo-class is one with a parameter - // remove round brackets grabbed by expression - match[2] = match[2].replace(/^\((.*)\)$/, '$1'); - } - - switch (match[1]) { - // CSS3 part of structural pseudo-classes - case 'not': - // compile nested selectors, need to escape double quotes characters - // since the string we are inserting into already uses double quotes - source = 'if(!this.match(e, "' + match[2].replace(/\x22/g, '\\"') + '")){' + source +'}'; - break; - // maybe deprecated in latest proposals - case 'contains': - match[2] = match[2].replace(/^["']*|['"]*$/g, ''); - source = 'if(' + CONTAINS_TEXT + '.indexOf("' + match[2] + '")>-1){' + source + '}'; - break; - // CSS3 part of UI element states - case 'checked': - source = 'if("form" in e&&/radio|checkbox/i.test(e.type)&&e.checked===true){' + source + '}'; - break; - case 'enabled': - // does not consider hidden input fields - source = 'if((("form" in e&&e.type!=="hidden")||this.isLink(e))&&e.disabled===false){' + source + '}'; - break; - case 'disabled': - // does not consider hidden input fields - source = 'if((("form" in e&&e.type!=="hidden")||this.isLink(e))&&e.disabled===true){' + source + '}'; - break; - case 'selected': - // fix Safari selectedIndex property bug - n = base.getElementsByTagName('select'); - for (i = 0; n[i]; i++) { - n[i].selectedIndex; - } - source = 'if("form" in e&&e.selected===true){' + source + '}'; - break; - // CSS3 target element - case 'target': - n = base.location.hash; - source = 'if(e.id!=""&&e.id=="' + n + '"&&"href" in e){' + source + '}'; - break; - // CSS1 & CSS2 link - case 'link': - source = 'if(this.isLink(e)&&!e.visited){' + source + '}'; - break; - case 'visited': - source = 'if(this.isLink(e)&&!!e.visited){' + source + '}'; - break; - // CSS1 & CSS2 UI States IE & FF3 have native support - // these capabilities may be emulated by event managers - case 'active': - source = 'if("activeElement" in d&&e===d.activeElement){' + source + '}'; - break; - case 'hover': - source = 'if("hoverElement" in d&&e===d.hoverElement){' + source + '}'; - break; - case 'focus': - source = 'if("form" in e&&e===d.activeElement&&typeof d.hasFocus=="function"&&d.hasFocus()===true){' + source + '}'; - break; - default: - break; - } - } - else if (!IS_EMPTY(Selectors)) { - // this is where external extensions are - // invoked if expressions match selectors - status = true; - for (name in Selectors) { - if ((match = selector.match(Selectors[name].Expression))) { - result = Selectors[name].Callback(match, source); - source = result.source; - status |= result.status; - } - } - // if an extension fails to parse the selector - // it must return a false boolean in "status" - if (!status) { - // log error but continue execution, don't throw real exceptions - // because blocking following processes maybe is not a good idea - emit('DOMException: unknown pseudo selector "' + selector + '"'); - return source; - } - } - else { - // see above, log error but continue execution - emit('DOMException: unknown token in selector "' + selector + '"'); - return source; - } - - // ensure "match" is not null or empty since - // we do not throw real DOMExceptions above - selector = match && match[match.length - 1]; - } - - return source; - }, - - // enable/disable notifications - VERBOSE = false, - - // a way to control user notification - emit = - function(message) { - if (VERBOSE) { - if (global.console && global.console.log) { - global.console.log(message); - } else { - if (/exception/i.test(message)) { - global.status = message; - global.defaultStatus = message; - } else { - global.status += message; - } - } - } - }, - - // match element with selector - // @return boolean - match = - function(element, selector) { - // make sure an element node was passed - if (element && element.nodeType == 1) { - if (typeof selector == 'string' && selector.length) { - base = element.ownerDocument; - root = base.documentElement; - // save compiled matchers - if (!compiledMatchers[selector]) { - compiledMatchers[selector] = compileGroup(selector, '', false); - } - // result of compiled matcher - return compiledMatchers[selector].call(this, element, snap, base, root); - } - else { - emit('DOMException: "' + selector + '" is not a valid CSS selector.'); - } - } - return false; - }, - - // select elements matching selector - // version using new Selector API - // @return array - select_qsa = - function (selector, from) { - if (validator.test(selector)) { - if ((!from || from.nodeType == 9) && !BUGGY_QSAPI.test(selector)) { - try { - // use available Selectors API - return toArray((from || context).querySelectorAll(selector)); - } catch(e) { } - } - // fall back to NWMatcher select - return client_api.call(this, selector, from || context); - } - return [ ]; - }, - - lastSelector, - lastContext, - lastSlice, - - // select elements matching selector - // version using cross-browser client API - // @return array - client_api = - function client_api(selector, from) { - - var i = 0, done, elements, node, parts, token; - - // extract context if changed - if (!from || lastContext != from) { - // save passed context - lastContext = from; - // ensure from context is set - from || (from = context); - // reference context ownerDocument and document root (HTML) - root = (base = from.ownerDocument || from).documentElement; - } - - if (lastSelector != selector) { - // process valid selector strings - if (validator.test(selector)) { - // save passed selector - lastSelector = selector; - // get right most selector token - parts = selector.match(Optimize.TOKEN); - // only last slice before :not rules - lastSlice = parts[parts.length - 1].split(':not')[0]; - } else { - emit('DOMException: "' + selector + '" is not a valid CSS selector.'); - return [ ]; - } - } - - // caching enabled ? - if (cachingEnabled) { - snap = base.snapshot; - // valid base context storage - if (snap && !snap.isExpired) { - if (snap.Results[selector] && - snap.Roots[selector] == from) { - return snap.Results[selector]; - } - } else { - setCache(true, base); - snap = base.snapshot; - } - } else { - if (position.test(selector)) { - // need to clear storage - snap = new Snapshot(); - } - } - - // pre-filtering pass allow to scale proportionally with big DOM trees; - // this block can be safely removed, it is a speed booster on big pages - // and still maintain the mandatory "document ordered" result set - - // commas separators are treated - // sequentially to maintain order - if (selector.indexOf(',') < 0) { - - // CLASS optimization - if ((parts = lastSlice.match(Optimize.CLASS)) && - (token = parts[parts.length - 1])) { - elements = byClass(token, from); - if (selector == '.' + token) { - if (cachingEnabled && elements.length > 0) { - done = true; - } else { - return elements; - } - } - } else - - // MULTI TAG optimization - if (!Optimize.descendants.test(selector) && - (parts = selector.match(/([-_\w]+)|(>)/g)) && NATIVE_GEBTN) { - if (parts.length > 1) { - elements = byTags(parts, from); - } else { - elements = toArray(from.getElementsByTagName(parts[0])); - } - if (cachingEnabled && elements.length > 0) { - done = true; - } else { - return elements; - } - } else - - // TAG optimization - if ((parts = lastSlice.match(Optimize.TAG)) && - (token = parts[parts.length - 1]) && NATIVE_GEBTN) { - elements = from.getElementsByTagName(token); - if (selector == token) { - if (cachingEnabled && elements.length > 0) { - done = true; - } else { - return toArray(elements); - } - } - } else - - // ID optimization - if ((parts = lastSlice.match(Optimize.ID)) && - (token = parts[parts.length - 1]) && from.getElementById) { - elements = [byId(token, from)]; - if (elements[0]) { - if (selector == '#' + token) { - if (cachingEnabled && elements.length > 0) { - done = true; - } else { - return elements; - } - } else { - //if (selector.length != (selector.lastIndexOf('#' + token) + token.length + 1)) { - // optimize narrowing context - from = elements[0].parentNode; - elements = null; - } - } else { - return [ ]; - } - } - - } - - if (!elements || elements.length === 0) { - - var tag = lastSlice.match(/\b([-_\w]+)\b/); - elements = from.getElementsByTagName('*'); - - if ((parts = lastSlice.match(/\#([-_\w]+)$/)) && selector == '#' + parts[1]) { - while ((node = elements[i++])) { - if (node.id == parts[1]) { - return [node]; - } - } - return [ ]; - } else - - if ((parts = lastSlice.match(/\b([-_\w]+)?(?:(\.[-_\w]+)|(\#[-_\w]+))/))) { - while ((node = elements[i++])) { - if ( - (!parts[1] || node.nodeName == parts[1]) && ( - (!parts[3] || (parts[2] == '#' && node.id == parts[3])) || - (!parts[3] || (parts[2] == '.' && node.className == parts[3])) - )) { - return [node]; - } - } - } else - - elements = toArray(elements); - - } - // end of prefiltering pass - - // save compiled selectors - if (!done && !compiledSelectors[selector]) { - compiledSelectors[selector] = compileGroup(selector, '', true); - } - - if (cachingEnabled) { - // a cached result set for the requested selector - snap.Results[selector] = done ? elements : - compiledSelectors[selector].call(this, elements, snap, base, root); - snap.Roots[selector] = from; - return snap.Results[selector]; - } - - // a fresh result set for the requested selector - return done ? - elements : - compiledSelectors[selector].call(this, elements, snap, base, root); - }, - - // use the new native Selector API if available, - // if missing, use the cross-browser client api - // @return array - select = NATIVE_QSAPI ? - select_qsa : - client_api, - - // element by id - // @return element reference or null - byId = - function(id, from) { - var i = 0, element, names, result; - from || (from = context); - id = id.replace(/\\/g, ''); - if (from.getElementById) { - result = from.getElementById(id); - if (result && id != getAttribute(result, 'id') && from.getElementsByName) { - names = from.getElementsByName(id); - result = null; - while ((element = names[i++])) { - if (element.getAttributeNode('id').value == id) { - result = element; - break; - } - } - } - } else { - result = NW.Dom.select('[id="' + id + '"]', from)[0] || null; - } - return result; - }, - - // elements by tag - // @return nodeList (live) - byTag = - function(tag, from) { - return (from || context).getElementsByTagName(tag || '*'); - }, - - // elements by name - // @return array - byName = - function(name, from) { - return this.select('[name="' + name.replace(/\\/g, '') + '"]', from || context); - }, - - // elements by class - // @return nodeList (native GEBCN) - // @return array (non native GEBCN) - byClass = !BUGGY_GEBCN ? - function(name, from) { - return slice.call(from.getElementsByClassName(name.replace(/\\/g, '')), 0); - } : - function(name, from) { - // context is handled in byTag for non native gEBCN - var i = 0, j = 0, r = [ ], node, - nodes = from.getElementsByTagName('*'), - name = new RegExp("(^|\\s)" + name + "(\\s|$)"); - while ((node = nodes[i++])) { - if (name.test(node.className)) { - r[j++] = node; - } - } - return r; - }, - - // recursively get nested tagNames - // example: for "div" pass ["div"] - // "ul li a" pass ["ul", "li", "a"] - // @c array of tag names combinators - // @f from context or default - // @return array - byTags = - function(c, f) { - var h, i, j, k, n, o, p, - id, e = [f || context], - r = [ ], s = [ ], t = [ ]; - h = 0; - i = 0; - while ((n = c[i++])) { - if (n == '>') { - j = 0; - while ((o = e[j++])) { - k = 0; - r = o[NATIVE_CHILDREN]; - while ((p = r[k++])) { - if (p.nodeName.toLowerCase() == c[i].toLowerCase()) { - s[h++] = p; - } - } - } - i++; - h = 0; - e = s; - s = [ ]; - t = [ ]; - } else { - j= 0; - while ((o = e[j++])) { - k = 0; - r = o.getElementsByTagName(n.replace(trim, '')); - while ((p = r[k++])) { - id = (p._cssId || (p._cssId = ++cssId)); - // discard duplicates - if (!t[id]) { - t[id] = true; - s[h++] = p; - } - } - } - h = 0; - e = s; - s = [ ]; - t = [ ]; - } - } - return e; - }, - - // attribute value - // @type string - getAttribute = NATIVE_HAS_ATTRIBUTE ? - function(element, attribute) { - return element.getAttribute(attribute) + ''; - } : - function(element, attribute) { - var node; - // specific URI attributes (parameter 2 to fix IE bug) - if (attributesURI[attribute.toLowerCase()]) { - return element.getAttribute(attribute, 2) + ''; - } - node = element.getAttributeNode(attribute); - return (node && node.value) + ''; - }, - - // attribute presence - // @return boolean - hasAttribute = NATIVE_HAS_ATTRIBUTE ? - function(element, attribute) { - return element.hasAttribute(attribute); - } : - function(element, attribute) { - // need to get at AttributeNode first on IE - var node = element.getAttributeNode(attribute); - // use both "specified" & "nodeValue" properties - return !!(node && (node.specified || node.nodeValue)); - }, - - // check if element matches the :link pseudo - isLink = - function(element) { - var nodeName = element.nodeName.toLowerCase(); - return hasAttribute(element,'href') && nodeName == 'a' || nodeName == 'area' || nodeName == 'link'; - }, - - // get best children collection available - // Safari 2.0.x "children" implementation - // differs, taken care by feature testing - // @return nodeList (live) - getChildren = - function(element) { - // childNodes is slower to loop through because it contains text nodes - // empty text nodes could be removed at startup to compensate this a bit - return element[NATIVE_CHILDREN] || element.childNodes; - }, - - // test element to be the only element child in its parent - // @return boolean - firstElement = - function(element) { - while ((element = element.previousSibling) && element.nodeType != 1) { } - return !element; - }, - - // test element to be the only element child in its parent - // @return boolean - lastElement = - function(element) { - while ((element = element.nextSibling) && element.nodeType != 1) { } - return !element; - }, - - // test element to be the only element child in its parent - // @return boolean - onlyElement = - function(element) { - return firstElement(element) && lastElement(element); - }, - - // test element to be the first element of-type in its parent - // @return boolean - firstOfType = - function(element) { - var nodeName = element.nodeName.toLowerCase(); - while ((element = element.previousSibling) && element.nodeName.toLowerCase() != nodeName) { } - return !element; - }, - - // test element to be the last element of-type in its parent - // @return boolean - lastOfType = - function(element) { - var nodeName = element.nodeName.toLowerCase(); - while ((element = element.nextSibling) && element.nodeName.toLowerCase() != nodeName) { } - return !element; - }, - - // test element to be the only element of-type in its parent - // @return boolean - onlyOfType = - function(element) { - return firstOfType(element) && lastOfType(element); - }, - - // child position by nodeType - // @return number - nthElement = - function(element) { - var i, j, node, nodes, parent, cache = snap.ChildIndex; - if (!element._cssId || !cache[element._cssId]) { - if ((parent = element.parentNode).nodeType == 1) { - i = 0; - j = 0; - nodes = parent[NATIVE_CHILDREN]; - while ((node = nodes[i++])) { - if (node.nodeType == 1) { - cache[node._cssId || (node._cssId = ++cssId)] = ++j; - } - } - snap.ChildCount[parent._cssId || (parent._cssId = ++cssId)] = j; - } else { - // does not have a parent (ex.: document) - return 0; - } - } - return cache[element._cssId]; - }, - - // child position by nodeName - // @return number - nthOfType = - function(element) { - var i, j, name, node, nodes, pid, parent, cache = snap.TwinsIndex; - if (!element._cssId || !cache[element._cssId]) { - if ((parent = element.parentNode).nodeType == 1) { - i = 0; - j = 0; - nodes = parent[NATIVE_CHILDREN]; - name = element.nodeName.toLowerCase(); - while ((node = nodes[i++])) { - if (node.nodeName.toLowerCase() == name) { - cache[node._cssId || (node._cssId = ++cssId)] = ++j; - } - } - pid = (parent._cssId || (parent._cssId = ++cssId)); - snap.TwinsCount[pid] || (snap.TwinsCount[pid] = { }); - snap.TwinsCount[pid][name] = j; - } else { - // does not have a parent (ex.: document) - return 0; - } - } - return cache[element._cssId]; - }, - - // convert nodeList to array - // @return array - toArray = NATIVE_SLICE_PROTO ? - function(list) { - return slice.call(list); - } : - function(list) { - // avoid using the length property of nodeLists - // it may have been overwritten by bad HTML code - var i = 0, array = [ ]; - while ((array[i] = list[i++])) { } - array.length--; - return array; - }, - - // cssId expando on elements, - // used to keep child indexes - // during a selection session - cssId = 1, - - // BEGIN: local context caching system - - // ****************** CACHING ****************** - // keep caching states for each context document - // set manually by using setCache(true, context) - cachingEnabled = NATIVE_MUTATION_EVENTS, - - // indexes/count of elements contained in rootElement - // expired by Mutation Events on DOM tree changes - Snapshot = - function() { - return { - // validation flag, creating it already expired, - // code validation will set it valid first time - isExpired: false, - // count of siblings by nodeType or nodeName - ChildCount: [ ], - TwinsCount: [ ], - // ordinal position by nodeType or nodeName - ChildIndex: [ ], - TwinsIndex: [ ], - // result sets and related root contexts - Results: [ ], - Roots: [ ] - }; - }, - - // local indexes, cleared - // between selection calls - snap = new Snapshot(), - - // enable/disable context caching system - // @d optional document context (iframe, xml document) - // script loading context will be used as default context - setCache = - function(enable, d) { - d || (d = context); - if (!!enable) { - d.snapshot = new Snapshot(); - startMutation(d); - } else { - stopMutation(d); - } - cachingEnabled = !!enable; - }, - - // invoked by mutation events to expire cached parts - mutationWrapper = - function(event) { - var d = event.target.ownerDocument || event.target; - stopMutation(d); - switch (event.type) { - case 'DOMAttrModified': - expireCache(d); - break; - case 'DOMNodeInserted': - expireCache(d); - break; - case 'DOMNodeRemoved': - expireCache(d); - break; - default: - break; - } - }, - - // append mutation events - startMutation = - function(d) { - if (!d.isCaching) { - // FireFox/Opera/Safari/KHTML have support for Mutation Events - d.addEventListener('DOMAttrModified', mutationWrapper, false); - d.addEventListener('DOMNodeInserted', mutationWrapper, false); - d.addEventListener('DOMNodeRemoved', mutationWrapper, false); - d.isCaching = true; - } - }, - - // remove mutation events - stopMutation = - function(d) { - if (d.isCaching) { - d.removeEventListener('DOMAttrModified', mutationWrapper, false); - d.removeEventListener('DOMNodeInserted', mutationWrapper, false); - d.removeEventListener('DOMNodeRemoved', mutationWrapper, false); - d.isCaching = false; - } - }, - - // expire complete cache - // can be invoked by Mutation Events or - // programmatically by other code/scripts - // document context is mandatory no checks - expireCache = - function(d) { - if (d && d.snapshot) { - d.snapshot.isExpired = true; - } - }; - - // END: local context caching system - - return { - - // for testing purposes ! - compile: - function(selector, mode) { - return compileGroup(selector, '', mode || false).toString(); - }, - - // enable/disable cache - setCache: setCache, - - // forced expire of DOM tree cache - expireCache: expireCache, - - // element match selector, return boolean true/false - match: match, - - // elements matching selector, starting from element - select: select, - - // Safari 2 bug with innerText (gasp!) - // used to strip tags from innerHTML - // shouldn't be public, but needed - stripTags: - function(s) { - return s.replace(/<\/?("[^\"]*"|'[^\']*'|[^>])+>/gi, ''); - }, - - // add selector patterns for user defined callbacks - registerSelector: - function (name, rexp, func) { - if (!Selectors[name]) { - Selectors[name] = { }; - Selectors[name].Expression = rexp; - Selectors[name].Callback = func; - } - }, - - // add or overwrite user defined operators - // TODO: check when overwriting standard operators - registerOperator: - function (symbol, resolver) { - if (!Operators[symbol]) { - Operators[symbol] = resolver; - } - }, - - // retrieve element by id attr - byId: byId, - - // retrieve elements by tag name - byTag: byTag, - - // retrieve elements by name attr - byName: byName, - - // retrieve elements by class name - byClass: byClass, - - // check if element matches the :link pseudo - isLink: isLink, - - // retrieve all children elements - getChildren: getChildren, - - // read the value of the attribute - // as was in the original HTML code - getAttribute: getAttribute, - - // check for the attribute presence - // as was in the original HTML code - hasAttribute: hasAttribute, - - // first child element any type - firstElement: firstElement, - - // last child element any type - lastElement: lastElement, - - // only child element any type - onlyElement: onlyElement, - - // first child element of-type - firstOfType: firstOfType, - - // last child element of-type - lastOfType: lastOfType, - - // only child element of-type - onlyOfType: onlyOfType, - - // nth child element any type - nthElement: nthElement, - - // nth child element of-type - nthOfType: nthOfType, - - // convert nodeList to array - toArray: toArray - - }; - -}(this); diff --git a/vendor/nwmatcher/selector_engine.js b/vendor/nwmatcher/selector_engine.js index 899838d..50830cf 100644 --- a/vendor/nwmatcher/selector_engine.js +++ b/vendor/nwmatcher/selector_engine.js @@ -1,5 +1,5 @@ Prototype._original_nw = window.NW; -//= require +//= require "nwmatcher/src/nwmatcher" Prototype.NW = window.NW; // Restore globals. @@ -7,14 +7,8 @@ window.NW = Prototype._original_nw; delete Prototype._original_nw; Prototype.Selector = (function(NW) { - function extend(elements) { - for (var i = 0, length = elements.length; i < length; i++) - elements[i] = Element.extend(elements[i]); - return elements; - } - function select(selector, scope) { - return extend(NW.select(selector, scope || document)); + return NW.select(selector, scope || document, null, Element.extend); } function filter(elements, selector) { From ed27b225a51e6f16029c90c20797b115e9d7fa4e Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 07:31:37 +0200 Subject: [PATCH 15/28] Fix Element extension when using the NWMatcher selector engine. (jddalton) --- Rakefile | 2 +- vendor/nwmatcher/selector_engine.js | 6 +++++- 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/Rakefile b/Rakefile index ff38e77..3aa2c18 100755 --- a/Rakefile +++ b/Rakefile @@ -102,7 +102,7 @@ module PrototypeHelper end def self.require_nwmatcher - if !File.exists?(File.join(ROOT_DIR, 'vendor', 'nwmatcher', 'src', 'nwmatcher.js')) + if !File.exists?(File.join(ROOT_DIR, 'vendor', 'nwmatcher', 'nwmatcher', 'src', 'nwmatcher.js')) exit unless get_submodule("NWMmatcher", "nwmatcher/nwmatcher") end end diff --git a/vendor/nwmatcher/selector_engine.js b/vendor/nwmatcher/selector_engine.js index 50830cf..4db802c 100644 --- a/vendor/nwmatcher/selector_engine.js +++ b/vendor/nwmatcher/selector_engine.js @@ -8,7 +8,11 @@ delete Prototype._original_nw; Prototype.Selector = (function(NW) { function select(selector, scope) { - return NW.select(selector, scope || document, null, Element.extend); + var results = []; + NW.select(selector, scope || document, null, function(element) { + results.push(Element.extend(element)); + }); + return results; } function filter(elements, selector) { From b0159bdba7b90f504bf225887cf03aedfcc00ee4 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 12:53:03 +0200 Subject: [PATCH 16/28] Document Prototype.Selector API. --- src/dom.js | 6 ++++++ src/dom/selector.js | 48 ++++++++++++++++++++++++++++++++++++++++++++- 2 files changed, 53 insertions(+), 1 deletion(-) diff --git a/src/dom.js b/src/dom.js index 1d9fdf3..1fb0187 100644 --- a/src/dom.js +++ b/src/dom.js @@ -19,6 +19,12 @@ * **/ +/** section: DOM + * Prototype + * + * The Prototype namespace. + * +**/ //= require "dom/dom" //= require diff --git a/src/dom/selector.js b/src/dom/selector.js index 4b03a25..94d8f4c 100644 --- a/src/dom/selector.js +++ b/src/dom/selector.js @@ -98,4 +98,50 @@ window.$$ = function() { var expression = $A(arguments).join(', '); return Prototype.Selector.select(expression, document); -}; \ No newline at end of file +}; + +/** + * Prototype.Selector + * + * A namespace that acts as a wrapper around + * the choosen selector engine (Sizzle by default). + * +**/ + +// Implementation provided by selector engine. + +/** + * Prototype.Selector.select(expression[, root = document]) -> [Element...] + * - expression (String): A CSS selector. + * - root (Element | document): A "scope" to search within. All results will + * be descendants of this node. + * + * Searches `root` for elements that match the provided CSS selector and returns an + * array of extended [[Element]] objects. +**/ + +// Implementation provided by selector engine. + +/** + * Prototype.Selector.match(element, expression) -> Boolean + * - element (Element): a DOM element. + * - expression (String): A CSS selector. + * + * Tests whether `element` matches the CSS selector. +**/ + +// Implementation provided by selector engine. + +/** + * Prototype.Selector.filter(elements, expression) -> [Element...] + * - elements (Enumerable): a collection of DOM elements. + * - expression (String): A CSS selector. + * + * Filters the given collection of elements with `expression` and returns an + * array of extended [[Element]] objects. + * + * The only nodes returned will be those that match the given CSS selector. +**/ + +// Implementation provided by selector engine. + From f6f6955a71d0e47955cefdd59cf69ad962355028 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 12:58:10 +0200 Subject: [PATCH 17/28] Marked old Selector API as deprecated. --- src/dom/selector.js | 14 +++++++------- 1 file changed, 7 insertions(+), 7 deletions(-) diff --git a/src/dom/selector.js b/src/dom/selector.js index 94d8f4c..d079f66 100644 --- a/src/dom/selector.js +++ b/src/dom/selector.js @@ -1,4 +1,4 @@ -/** section: DOM +/** deprecated, section: DOM * class Selector * * A class that queries the document for elements that match a given CSS @@ -6,7 +6,7 @@ **/ (function() { window.Selector = Class.create({ - /** + /** deprecated * new Selector(expression) * - expression (String): A CSS selector. * @@ -16,7 +16,7 @@ this.expression = expression.strip(); }, - /** + /** deprecated * Selector#findElements(root) -> [Element...] * - root (Element || document): A "scope" to search within. All results will * be descendants of this node. @@ -28,7 +28,7 @@ return Prototype.Selector.select(this.expression, rootElement); }, - /** + /** deprecated * Selector#match(element) -> Boolean * * Tests whether a `element` matches the instance's CSS selector. @@ -47,7 +47,7 @@ }); Object.extend(Selector, { - /** + /** deprecated * Selector.matchElements(elements, expression) -> [Element...] * * Filters the given collection of elements with `expression`. @@ -56,7 +56,7 @@ **/ matchElements: Prototype.Selector.filter, - /** + /** deprecated * Selector.findElement(elements, expression[, index = 0]) -> Element * Selector.findElement(elements[, index = 0]) -> Element * @@ -77,7 +77,7 @@ } }, - /** + /** deprecated * Selector.findChildElements(element, expressions) -> [Element...] * * Searches beneath `element` for any elements that match the selector From 7f5ce1e6c250d6dfa4a341c68212b3a372c21a00 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 13:17:53 +0200 Subject: [PATCH 18/28] Clean-up NWMatcher proxy. --- vendor/nwmatcher/selector_engine.js | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/vendor/nwmatcher/selector_engine.js b/vendor/nwmatcher/selector_engine.js index 4db802c..fea4a02 100644 --- a/vendor/nwmatcher/selector_engine.js +++ b/vendor/nwmatcher/selector_engine.js @@ -8,19 +8,19 @@ delete Prototype._original_nw; Prototype.Selector = (function(NW) { function select(selector, scope) { - var results = []; + var results = [], resultsIndex = 0; NW.select(selector, scope || document, null, function(element) { - results.push(Element.extend(element)); + results[resultsIndex++] = Element.extend(element); }); return results; } function filter(elements, selector) { - var results = [], element; + var results = [], resultsIndex = 0, element; for (var i = 0, length = elements.length; i < length; i++) { element = elements[i]; if (NW.match(element, selector)) { - results.push(Element.extend(element)) + results[resultsIndex++] = Element.extend(element); } } return results; From 75aab03ebabaa33a889751e69e8852e6107374fd Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 18:32:07 +0200 Subject: [PATCH 19/28] Repo and Rakefile refactoring. --- .gitmodules | 11 +++--- Rakefile | 45 +++++++++------------- vendor/legacy/{ => repository}/legacy.js | 0 vendor/legacy/selector_engine.js | 2 +- vendor/nwmatcher/{nwmatcher => repository} | 0 vendor/nwmatcher/selector_engine.js | 2 +- vendor/sizzle/{sizzle => repository} | 0 vendor/sizzle/selector_engine.js | 2 +- 8 files changed, 28 insertions(+), 34 deletions(-) rename vendor/legacy/{ => repository}/legacy.js (100%) rename vendor/nwmatcher/{nwmatcher => repository} (100%) rename vendor/sizzle/{sizzle => repository} (100%) diff --git a/.gitmodules b/.gitmodules index 51c3adf..ee65a40 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,9 +11,10 @@ path = vendor/sprockets url = git://github.com/sstephenson/sprockets.git -[submodule "vendor/sizzle/sizzle"] - path = vendor/sizzle/sizzle - url = git://github.com/jeresig/sizzle.git -[submodule "vendor/nwmatcher/nwmatcher"] - path = vendor/nwmatcher/nwmatcher + +[submodule "vendor/nwmatcher/repository"] + path = vendor/nwmatcher/repository url = git://github.com/dperini/nwmatcher.git +[submodule "vendor/sizzle/repository"] + path = vendor/sizzle/repository + url = git://github.com/jeresig/sizzle.git diff --git a/Rakefile b/Rakefile index 3aa2c18..31e772e 100755 --- a/Rakefile +++ b/Rakefile @@ -9,13 +9,13 @@ module PrototypeHelper DOC_DIR = File.join(ROOT_DIR, 'doc') TEMPLATES_DIR = File.join(ROOT_DIR, 'templates') PKG_DIR = File.join(ROOT_DIR, 'pkg') - SIZZLE_DIR = File.join(ROOT_DIR, 'vendor', 'sizzle') TEST_DIR = File.join(ROOT_DIR, 'test') TEST_UNIT_DIR = File.join(TEST_DIR, 'unit') TMP_DIR = File.join(TEST_UNIT_DIR, 'tmp') VERSION = YAML.load(IO.read(File.join(SRC_DIR, 'constants.yml')))['PROTOTYPE_VERSION'] - - %w[sprockets pdoc unittest_js caja_builder sizzle].each do |name| + DEFAULT_SELECTOR_ENGINE = 'sizzle' + + %w[sprockets pdoc unittest_js caja_builder].each do |name| $:.unshift File.join(PrototypeHelper::ROOT_DIR, 'vendor', name, 'lib') end @@ -39,11 +39,10 @@ module PrototypeHelper def self.sprocketize(path, source, destination = nil, strip_comments = true) require_sprockets - require_sizzle - require_nwmatcher + get_selector_engine(selector) secretary = Sprockets::Secretary.new( :root => File.join(ROOT_DIR, path), - :load_path => self.load_path, + :load_path => [SRC_DIR, selector_path], :source_files => [source], :strip_comments => strip_comments ) @@ -52,16 +51,12 @@ module PrototypeHelper secretary.concatenation.save_to(destination) end - def self.load_path - selector = ENV['SELECTOR_ENGINE'] || 'sizzle' - selector_path = File.join(ROOT_DIR, 'vendor', selector) - if File.exists?(selector_path) - [SRC_DIR, selector_path] - else - puts "\nYou seem to be missing vendor/#{selector}." - puts "Built Prototype using Sizzle instead.\n\n" - [SRC_DIR, SIZZLE_DIR] - end + def self.selector + ENV['SELECTOR_ENGINE'] || DEFAULT_SELECTOR_ENGINE + end + + def self.selector_path + File.join(ROOT_DIR, 'vendor', selector) end def self.build_doc_for(file) @@ -95,15 +90,14 @@ module PrototypeHelper require_submodule('CajaBuilder', 'caja_builder') end - def self.require_sizzle - if !File.exists?(File.join(SIZZLE_DIR, 'sizzle', 'sizzle.js')) - exit unless get_submodule("Sizzle", "sizzle/sizzle") - end - end - - def self.require_nwmatcher - if !File.exists?(File.join(ROOT_DIR, 'vendor', 'nwmatcher', 'nwmatcher', 'src', 'nwmatcher.js')) - exit unless get_submodule("NWMmatcher", "nwmatcher/nwmatcher") + def self.get_selector_engine(name) + file = File.join(ROOT_DIR, 'vendor', name, 'repository') + unless File.exists?(file) + get_submodule('the required selector engine', "#{name}/repository") + unless File.exists?(file) + puts "The selector engine you required isn't available at vendor/#{name}.\n\n" + exit + end end end @@ -113,7 +107,6 @@ module PrototypeHelper Kernel.system("git submodule init") return true if Kernel.system("git submodule update vendor/#{path}") - # If we got this far, something went wrong. puts "\nLooks like it didn't work. Try it manually:\n\n" puts " $ git submodule init" diff --git a/vendor/legacy/legacy.js b/vendor/legacy/repository/legacy.js similarity index 100% rename from vendor/legacy/legacy.js rename to vendor/legacy/repository/legacy.js diff --git a/vendor/legacy/selector_engine.js b/vendor/legacy/selector_engine.js index 0ad6a86..cff9dab 100644 --- a/vendor/legacy/selector_engine.js +++ b/vendor/legacy/selector_engine.js @@ -1,4 +1,4 @@ -//= require "legacy" +//= require "repository/legacy" Prototype.Selector = (function(Legacy) { function select(selector, scope) { diff --git a/vendor/nwmatcher/nwmatcher b/vendor/nwmatcher/repository similarity index 100% rename from vendor/nwmatcher/nwmatcher rename to vendor/nwmatcher/repository diff --git a/vendor/nwmatcher/selector_engine.js b/vendor/nwmatcher/selector_engine.js index fea4a02..9639ae6 100644 --- a/vendor/nwmatcher/selector_engine.js +++ b/vendor/nwmatcher/selector_engine.js @@ -1,5 +1,5 @@ Prototype._original_nw = window.NW; -//= require "nwmatcher/src/nwmatcher" +//= require "repository/src/nwmatcher" Prototype.NW = window.NW; // Restore globals. diff --git a/vendor/sizzle/sizzle b/vendor/sizzle/repository similarity index 100% rename from vendor/sizzle/sizzle rename to vendor/sizzle/repository diff --git a/vendor/sizzle/selector_engine.js b/vendor/sizzle/selector_engine.js index d7aeeda..bed246f 100644 --- a/vendor/sizzle/selector_engine.js +++ b/vendor/sizzle/selector_engine.js @@ -1,5 +1,5 @@ Prototype._original_sizzle = window.Sizzle; -//= require "sizzle/sizzle" +//= require "repository/sizzle" Prototype.Sizzle = window.Sizzle; // Restore globals. From 5f85799c3fc93d1131da6c71cc26830b18840385 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Fri, 23 Oct 2009 19:29:03 +0200 Subject: [PATCH 20/28] Refactor NWMatcher adapter. --- vendor/nwmatcher/selector_engine.js | 20 ++++++++------------ 1 file changed, 8 insertions(+), 12 deletions(-) diff --git a/vendor/nwmatcher/selector_engine.js b/vendor/nwmatcher/selector_engine.js index 9639ae6..2ebeda7 100644 --- a/vendor/nwmatcher/selector_engine.js +++ b/vendor/nwmatcher/selector_engine.js @@ -6,21 +6,17 @@ Prototype.NW = window.NW; window.NW = Prototype._original_nw; delete Prototype._original_nw; -Prototype.Selector = (function(NW) { +Prototype.Selector = (function(NWDom) { function select(selector, scope) { - var results = [], resultsIndex = 0; - NW.select(selector, scope || document, null, function(element) { - results[resultsIndex++] = Element.extend(element); - }); - return results; + return NWDom.select(selector, scope || document, null, Element.extend); } function filter(elements, selector) { - var results = [], resultsIndex = 0, element; - for (var i = 0, length = elements.length; i < length; i++) { - element = elements[i]; - if (NW.match(element, selector)) { - results[resultsIndex++] = Element.extend(element); + var results = [], element, i = 0; + while (element = elements[i++]) { + if (NWDom.match(element, selector)) { + Element.extend(element); + results.push(element); } } return results; @@ -28,7 +24,7 @@ Prototype.Selector = (function(NW) { return { select: select, - match: NW.match, + match: NWDom.match, filter: filter }; })(Prototype.NW.Dom); From 83826829a7732c710f47c77ac45d887a72044224 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Sat, 24 Oct 2009 16:12:38 +0200 Subject: [PATCH 21/28] Add Prototype.Selector.engine which simply holds a reference to the actual selector engine used. --- vendor/legacy/selector_engine.js | 13 +++++++------ vendor/nwmatcher/selector_engine.js | 25 ++++++++++++------------- vendor/sizzle/selector_engine.js | 27 +++++++++++++-------------- 3 files changed, 32 insertions(+), 33 deletions(-) diff --git a/vendor/legacy/selector_engine.js b/vendor/legacy/selector_engine.js index cff9dab..21d5c6b 100644 --- a/vendor/legacy/selector_engine.js +++ b/vendor/legacy/selector_engine.js @@ -1,17 +1,18 @@ //= require "repository/legacy" -Prototype.Selector = (function(Legacy) { +Prototype.Selector = (function(engine) { function select(selector, scope) { - return Legacy.findChildElements(scope || document, [selector]); + return engine.findChildElements(scope || document, [selector]); } function match(element, selector) { - return !!Legacy.findElement([element], selector); + return !!engine.findElement([element], selector); } return { - select: select, - match: match, - filter: Legacy.matchElements + engine: engine, + select: select, + match: match, + filter: engine.matchElements }; })(Prototype.Legacy); diff --git a/vendor/nwmatcher/selector_engine.js b/vendor/nwmatcher/selector_engine.js index 2ebeda7..5bc8ac4 100644 --- a/vendor/nwmatcher/selector_engine.js +++ b/vendor/nwmatcher/selector_engine.js @@ -1,20 +1,15 @@ -Prototype._original_nw = window.NW; +Prototype._original_property = window.NW; //= require "repository/src/nwmatcher" -Prototype.NW = window.NW; -// Restore globals. -window.NW = Prototype._original_nw; -delete Prototype._original_nw; - -Prototype.Selector = (function(NWDom) { +Prototype.Selector = (function(engine) { function select(selector, scope) { - return NWDom.select(selector, scope || document, null, Element.extend); + return engine.select(selector, scope || document, null, Element.extend); } function filter(elements, selector) { var results = [], element, i = 0; while (element = elements[i++]) { - if (NWDom.match(element, selector)) { + if (engine.match(element, selector)) { Element.extend(element); results.push(element); } @@ -23,9 +18,13 @@ Prototype.Selector = (function(NWDom) { } return { - select: select, - match: NWDom.match, - filter: filter + engine: engine, + select: select, + match: engine.match, + filter: filter }; -})(Prototype.NW.Dom); +})(NW.Dom); +// Restore globals. +window.NW = Prototype._original_property; +delete Prototype._original_property; \ No newline at end of file diff --git a/vendor/sizzle/selector_engine.js b/vendor/sizzle/selector_engine.js index bed246f..6980631 100644 --- a/vendor/sizzle/selector_engine.js +++ b/vendor/sizzle/selector_engine.js @@ -1,12 +1,7 @@ -Prototype._original_sizzle = window.Sizzle; +Prototype._original_property = window.Sizzle; //= require "repository/sizzle" -Prototype.Sizzle = window.Sizzle; -// Restore globals. -window.Sizzle = Prototype._original_sizzle; -delete Prototype._original_sizzle; - -Prototype.Selector = (function(Sizzle) { +Prototype.Selector = (function(engine) { function extend(elements) { for (var i = 0, length = elements.length; i < length; i++) elements[i] = Element.extend(elements[i]); @@ -14,21 +9,25 @@ Prototype.Selector = (function(Sizzle) { } function select(selector, scope) { - return extend(Sizzle(selector, scope || document)); + return extend(engine(selector, scope || document)); } function match(element, selector) { - return Sizzle.matches(selector, [element]).length == 1; + return engine.matches(selector, [element]).length == 1; } function filter(elements, selector) { - return extend(Sizzle.matches(selector, elements)); + return extend(engine.matches(selector, elements)); } return { - select: select, - match: match, - filter: filter + engine: engine, + select: select, + match: match, + filter: filter }; -})(Prototype.Sizzle); +})(Sizzle); +// Restore globals. +window.Sizzle = Prototype._original_property; +delete Prototype._original_property; From 107f8125254d52b50e3a7a7f5faa62145dc9c264 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Sat, 24 Oct 2009 22:17:37 +0200 Subject: [PATCH 22/28] Modify PrototypeHelper.sprocketize to take a hash of options rather than separate arguments. --- Rakefile | 53 ++++++++++++++++++++++++++++++++--------------------- 1 file changed, 32 insertions(+), 21 deletions(-) diff --git a/Rakefile b/Rakefile index 31e772e..ad8bea8 100755 --- a/Rakefile +++ b/Rakefile @@ -13,8 +13,7 @@ module PrototypeHelper TEST_UNIT_DIR = File.join(TEST_DIR, 'unit') TMP_DIR = File.join(TEST_UNIT_DIR, 'tmp') VERSION = YAML.load(IO.read(File.join(SRC_DIR, 'constants.yml')))['PROTOTYPE_VERSION'] - DEFAULT_SELECTOR_ENGINE = 'sizzle' - + %w[sprockets pdoc unittest_js caja_builder].each do |name| $:.unshift File.join(PrototypeHelper::ROOT_DIR, 'vendor', name, 'lib') end @@ -37,32 +36,40 @@ module PrototypeHelper exit end - def self.sprocketize(path, source, destination = nil, strip_comments = true) + def self.sprocketize(options = {}) + options = { + :destination => File.join(DIST_DIR, options[:source]), + :strip_comments => true + }.merge(options) + require_sprockets - get_selector_engine(selector) + load_path = [SRC_DIR] + + if selector = options[:selector_engine] + get_selector_engine(selector) + load_path << File.join(ROOT_DIR, 'vendor', selector) + end + secretary = Sprockets::Secretary.new( - :root => File.join(ROOT_DIR, path), - :load_path => [SRC_DIR, selector_path], - :source_files => [source], - :strip_comments => strip_comments + :root => File.join(ROOT_DIR, options[:path]), + :load_path => load_path, + :source_files => [options[:source]], + :strip_comments => options[:strip_comments] ) - destination = File.join(DIST_DIR, source) unless destination - secretary.concatenation.save_to(destination) - end - - def self.selector - ENV['SELECTOR_ENGINE'] || DEFAULT_SELECTOR_ENGINE - end - - def self.selector_path - File.join(ROOT_DIR, 'vendor', selector) + secretary.concatenation.save_to(options[:destination]) end def self.build_doc_for(file) mkdir_p TMP_DIR temp_path = File.join(TMP_DIR, "prototype.temp.js") - sprocketize('src', file, temp_path, false) + sprocketize( + :path => 'src', + :source => file, + :destination => temp_path, + :selector_engine => ENV['SELECTOR_ENGINE'] || 'sizzle', + :strip_comments => false + ) rm_rf DOC_DIR PDoc::Runner.new(temp_path, { @@ -140,7 +147,11 @@ task :default => [:dist, :dist_helper, :package, :clean_package_source] desc "Builds the distribution." task :dist do - PrototypeHelper.sprocketize("src", "prototype.js") + PrototypeHelper.sprocketize( + :path => 'src', + :source => 'prototype.js', + :selector_engine => ENV['SELECTOR_ENGINE'] || 'sizzle' + ) end namespace :doc do @@ -158,7 +169,7 @@ task :doc => ['doc:build'] desc "Builds the updating helper." task :dist_helper do - PrototypeHelper.sprocketize("ext/update_helper", "prototype_update_helper.js") + PrototypeHelper.sprocketize(:path => 'ext/update_helper', :source => 'prototype_update_helper.js') end Rake::PackageTask.new('prototype', PrototypeHelper::VERSION) do |package| From fdf3424f783f3a1bd1cf8a935204286c463d4886 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Sun, 25 Oct 2009 00:40:48 +0200 Subject: [PATCH 23/28] Add unit tests. --- test/unit/fixtures/selector_engine.html | 4 ++ test/unit/selector_engine_test.js | 53 +++++++++++++++++++++++++ 2 files changed, 57 insertions(+) create mode 100644 test/unit/fixtures/selector_engine.html create mode 100644 test/unit/selector_engine_test.js diff --git a/test/unit/fixtures/selector_engine.html b/test/unit/fixtures/selector_engine.html new file mode 100644 index 0000000..5b1acd2 --- /dev/null +++ b/test/unit/fixtures/selector_engine.html @@ -0,0 +1,4 @@ +
+
+
+
\ No newline at end of file diff --git a/test/unit/selector_engine_test.js b/test/unit/selector_engine_test.js new file mode 100644 index 0000000..dec966f --- /dev/null +++ b/test/unit/selector_engine_test.js @@ -0,0 +1,53 @@ +/* +
+
+
+
+*/ + +new Test.Unit.Runner({ + testEngine: function() { + this.assert(Prototype.Selector.engine); + }, + + testSelect: function() { + var elements = Prototype.Selector.select('.test_class'); + + this.assert(Object.isArray(elements)); + this.assertEqual(2, elements.length); + this.assertEqual('test_div_parent', elements[0].id); + this.assertEqual('test_div_child', elements[1].id); + }, + + testSelectWithContext: function() { + var elements = Prototype.Selector.select('.test_class', $('test_div_parent')); + + this.assert(Object.isArray(elements)); + this.assertEqual(1, elements.length); + this.assertEqual('test_div_child', elements[0].id); + }, + + testSelectWithEmptyResult: function() { + var elements = Prototype.Selector.select('.non_existent'); + + this.assert(Object.isArray(elements)); + this.assertEqual(0, elements.length); + }, + + testMatch: function() { + var element = $('test_div_parent'); + + this.assertEqual(true, Prototype.Selector.match(element, '.test_class')); + this.assertEqual(false, Prototype.Selector.match(element, '.non_existent')); + }, + + testFilter: function() { + var elements = document.getElementsByTagName('*'), + filtered = Prototype.Selector.filter(elements, '.test_class'); + + this.assert(Object.isArray(filtered)); + this.assertEqual(2, filtered.length); + this.assertEqual('test_div_parent', filtered[0].id); + this.assertEqual('test_div_child', filtered[1].id); + } +}); \ No newline at end of file From 24569d1d985f74d4d86d46e94d910e777ddfced4 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Sun, 25 Oct 2009 16:13:11 +0100 Subject: [PATCH 24/28] Deprecate the Selector API. --- .../prototype_update_helper.html | 32 +++++++ ext/update_helper/prototype_update_helper.js | 56 +++++++++++ src/deprecated.js | 92 ++++++++++++++++++ src/dom/selector.js | 94 +------------------ 4 files changed, 181 insertions(+), 93 deletions(-) diff --git a/ext/update_helper/prototype_update_helper.html b/ext/update_helper/prototype_update_helper.html index b2d355d..b5aa69f 100644 --- a/ext/update_helper/prototype_update_helper.html +++ b/ext/update_helper/prototype_update_helper.html @@ -280,6 +280,38 @@ this.assertNotNotified(); }, + testSelectorInstanceMethods: function() { + var selector = new Selector('div'); + this.assertWarnNotified('The Selector class has been deprecated. Please use the new Prototype.Selector API instead.'); + + selector.findElements(document); + this.assertWarnNotified('Selector#findElements has been deprecated. Please use the new Prototype.Selector API instead.'); + + selector.match(document.documentElement); + this.assertWarnNotified('Selector#match has been deprecated. Please use the new Prototype.Selector API instead.'); + + selector.toString(); + this.assertWarnNotified('Selector#toString has been deprecated. Please use the new Prototype.Selector API instead.'); + + selector.inspect(); + this.assertWarnNotified('Selector#inspect has been deprecated. Please use the new Prototype.Selector API instead.'); + }, + + testSelectorMatchElements: function() { + Selector.matchElements([], 'div'); + this.assertWarnNotified('Selector.matchElements has been deprecated. Please use the new Prototype.Selector API instead.'); + }, + + testSelectorFindElement: function() { + Selector.findElement([], 'div'); + this.assertWarnNotified('Selector.findElement has been deprecated. Please use the new Prototype.Selector API instead.'); + }, + + testSelectorFindChildElements: function() { + Selector.findChildElements(document, 'div'); + this.assertWarnNotified('Selector.findChildElements has been deprecated. Please use the new Prototype.Selector API instead.'); + }, + testLogDeprecationOption: function() { prototypeUpdateHelper.logLevel = UpdateHelper.Warn; var h = $H({ foo: 2 }); diff --git a/ext/update_helper/prototype_update_helper.js b/ext/update_helper/prototype_update_helper.js index e5107ca..353deee 100644 --- a/ext/update_helper/prototype_update_helper.js +++ b/ext/update_helper/prototype_update_helper.js @@ -275,6 +275,62 @@ var prototypeUpdateHelper = new UpdateHelper([ message: 'The class API has been fully revised and now allows for mixins and inheritance.\n' + 'You can find more about it here: http://prototypejs.org/learn/class-inheritance', condition: function() { return !arguments.length } + }, + + { + methodName: 'initialize', + namespace: Selector.prototype, + message: 'The Selector class has been deprecated. Please use the new Prototype.Selector API instead.', + type: 'warn' + }, + + { + methodName: 'findElements', + namespace: Selector.prototype, + message: 'Selector#findElements has been deprecated. Please use the new Prototype.Selector API instead.', + type: 'warn' + }, + + { + methodName: 'match', + namespace: Selector.prototype, + message: 'Selector#match has been deprecated. Please use the new Prototype.Selector API instead.', + type: 'warn' + }, + + { + methodName: 'toString', + namespace: Selector.prototype, + message: 'Selector#toString has been deprecated. Please use the new Prototype.Selector API instead.', + type: 'warn' + }, + + { + methodName: 'inspect', + namespace: Selector.prototype, + message: 'Selector#inspect has been deprecated. Please use the new Prototype.Selector API instead.', + type: 'warn' + }, + + { + methodName: 'matchElements', + namespace: Selector, + message: 'Selector.matchElements has been deprecated. Please use the new Prototype.Selector API instead.', + type: 'warn' + }, + + { + methodName: 'findElement', + namespace: Selector, + message: 'Selector.findElement has been deprecated. Please use the new Prototype.Selector API instead.', + type: 'warn' + }, + + { + methodName: 'findChildElements', + namespace: Selector, + message: 'Selector.findChildElements has been deprecated. Please use the new Prototype.Selector API instead.', + type: 'warn' } ]); diff --git a/src/deprecated.js b/src/deprecated.js index c5ea33e..c008d66 100644 --- a/src/deprecated.js +++ b/src/deprecated.js @@ -184,3 +184,95 @@ Element.ClassNames.prototype = { Object.extend(Element.ClassNames.prototype, Enumerable); /*--------------------------------------------------------------------------*/ + +/** deprecated, section: DOM + * class Selector + * + * A class that queries the document for elements that match a given CSS + * selector. +**/ +(function() { + window.Selector = Class.create({ + /** deprecated + * new Selector(expression) + * - expression (String): A CSS selector. + * + * Creates a `Selector` with the given CSS selector. + **/ + initialize: function(expression) { + this.expression = expression.strip(); + }, + + /** deprecated + * Selector#findElements(root) -> [Element...] + * - root (Element | document): A "scope" to search within. All results will + * be descendants of this node. + * + * Searches the document for elements that match the instance's CSS + * selector. + **/ + findElements: function(rootElement) { + return Prototype.Selector.select(this.expression, rootElement); + }, + + /** deprecated + * Selector#match(element) -> Boolean + * + * Tests whether a `element` matches the instance's CSS selector. + **/ + match: function(element) { + return Prototype.Selector.match(element, this.expression); + }, + + toString: function() { + return this.expression; + }, + + inspect: function() { + return "#"; + } + }); + + Object.extend(Selector, { + /** deprecated + * Selector.matchElements(elements, expression) -> [Element...] + * + * Filters the given collection of elements with `expression`. + * + * The only nodes returned will be those that match the given CSS selector. + **/ + matchElements: Prototype.Selector.filter, + + /** deprecated + * Selector.findElement(elements, expression[, index = 0]) -> Element + * Selector.findElement(elements[, index = 0]) -> Element + * + * Returns the `index`th element in the collection that matches + * `expression`. + * + * Returns the `index`th element overall if `expression` is not given. + **/ + findElement: function(elements, expression, index) { + index = index || 0; + var matchIndex = 0, element; + // Match each element individually, since Sizzle.matches does not preserve order + for (var i = 0, length = elements.length; i < length; i++) { + element = elements[i]; + if (Prototype.Selector.match(element, expression) && index === matchIndex++) { + return Element.extend(element); + } + } + }, + + /** deprecated + * Selector.findChildElements(element, expressions) -> [Element...] + * + * Searches beneath `element` for any elements that match the selector + * (or selectors) specified in `expressions`. + **/ + findChildElements: function(element, expressions) { + var selector = expressions.toArray().join(', '); + return Prototype.Selector.select(selector, element || document); + } + }); +})(); diff --git a/src/dom/selector.js b/src/dom/selector.js index d079f66..e0e5d40 100644 --- a/src/dom/selector.js +++ b/src/dom/selector.js @@ -1,96 +1,4 @@ -/** deprecated, section: DOM - * class Selector - * - * A class that queries the document for elements that match a given CSS - * selector. -**/ -(function() { - window.Selector = Class.create({ - /** deprecated - * new Selector(expression) - * - expression (String): A CSS selector. - * - * Creates a `Selector` with the given CSS selector. - **/ - initialize: function(expression) { - this.expression = expression.strip(); - }, - - /** deprecated - * Selector#findElements(root) -> [Element...] - * - root (Element || document): A "scope" to search within. All results will - * be descendants of this node. - * - * Searches the document for elements that match the instance's CSS - * selector. - **/ - findElements: function(rootElement) { - return Prototype.Selector.select(this.expression, rootElement); - }, - - /** deprecated - * Selector#match(element) -> Boolean - * - * Tests whether a `element` matches the instance's CSS selector. - **/ - match: function(element) { - return Prototype.Selector.match(element, this.expression); - }, - - toString: function() { - return this.expression; - }, - - inspect: function() { - return "#"; - } - }); - - Object.extend(Selector, { - /** deprecated - * Selector.matchElements(elements, expression) -> [Element...] - * - * Filters the given collection of elements with `expression`. - * - * The only nodes returned will be those that match the given CSS selector. - **/ - matchElements: Prototype.Selector.filter, - - /** deprecated - * Selector.findElement(elements, expression[, index = 0]) -> Element - * Selector.findElement(elements[, index = 0]) -> Element - * - * Returns the `index`th element in the collection that matches - * `expression`. - * - * Returns the `index`th element overall if `expression` is not given. - **/ - findElement: function(elements, expression, index) { - index = index || 0; - var matchIndex = 0, element; - // Match each element individually, since Sizzle.matches does not preserve order - for (var i = 0, length = elements.length; i < length; i++) { - element = elements[i]; - if (Prototype.Selector.match(element, expression) && index === matchIndex++) { - return Element.extend(element); - } - } - }, - - /** deprecated - * Selector.findChildElements(element, expressions) -> [Element...] - * - * Searches beneath `element` for any elements that match the selector - * (or selectors) specified in `expressions`. - **/ - findChildElements: function(element, expressions) { - var selector = expressions.toArray().join(', '); - return Prototype.Selector.select(selector, element || document); - } - }); -})(); - -/** related to: Selector +/** related to: Prototype.Selector * $$(expression...) -> [Element...] * * Returns all elements in the document that match the provided CSS selectors. From 70c5e98d44ecb7129501155811088a5b26cd1bf7 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Sun, 25 Oct 2009 16:24:51 +0100 Subject: [PATCH 25/28] Make the UpdaterHelper function on WebKit-based browsers. --- ext/update_helper/prototype_update_helper.html | 7 ++++++- ext/update_helper/prototype_update_helper.js | 4 ++-- ext/update_helper/update_helper.js | 11 +++++++---- 3 files changed, 15 insertions(+), 7 deletions(-) diff --git a/ext/update_helper/prototype_update_helper.html b/ext/update_helper/prototype_update_helper.html index b5aa69f..625911d 100644 --- a/ext/update_helper/prototype_update_helper.html +++ b/ext/update_helper/prototype_update_helper.html @@ -81,7 +81,12 @@ new Test.Unit.Runner({ testGetStack: function() { - this.assertMatch(/prototype_update_helper\.html:\d+\n$/, prototypeUpdateHelper.getStack()); + var stack = prototypeUpdateHelper.getStack(); + if (stack === '') { + this.info('UpdaterHelper#getStack is currently not supported on this browser.') + } else { + this.assertMatch(/prototype_update_helper\.html:\d+\n$/, prototypeUpdateHelper.getStack()); + } }, testDisplay: function() { diff --git a/ext/update_helper/prototype_update_helper.js b/ext/update_helper/prototype_update_helper.js index 353deee..b022966 100644 --- a/ext/update_helper/prototype_update_helper.js +++ b/ext/update_helper/prototype_update_helper.js @@ -1,6 +1,6 @@ //= require "update_helper" -/* UpdateHelper for Prototype <%= PROTOTYPE_VERSION %> (c) 2008 Tobie Langel +/* UpdateHelper for Prototype <%= PROTOTYPE_VERSION %> (c) 2008-2009 Tobie Langel * * UpdateHelper for Prototype is freely distributable under the same * terms as Prototype (MIT-style license). @@ -17,7 +17,7 @@ * * This, for example, will prevent deprecation messages from being logged. * - * THIS SCRIPT WORKS IN FIREFOX ONLY + * THIS SCRIPT DOES NOT WORK IN INTERNET EXPLORER *--------------------------------------------------------------------------*/ var prototypeUpdateHelper = new UpdateHelper([ diff --git a/ext/update_helper/update_helper.js b/ext/update_helper/update_helper.js index 81a0ad0..2a4b7b6 100644 --- a/ext/update_helper/update_helper.js +++ b/ext/update_helper/update_helper.js @@ -1,4 +1,4 @@ -/* Update Helper (c) 2008 Tobie Langel +/* Update Helper (c) 2008-2009 Tobie Langel * * Requires Prototype >= 1.6.0 * @@ -54,9 +54,12 @@ var UpdateHelper = Class.create({ try { throw new Error("stack"); } catch(e) { - return (e.stack || '').match(this.Regexp).reject(function(path) { - return /(prototype|unittest|update_helper)\.js/.test(path); - }).join("\n"); + var match = (e.stack || '').match(this.Regexp); + if (match) { + return match.reject(function(path) { + return (/(prototype|unittest|update_helper)\.js/).test(path); + }).join("\n"); + } else { return ''; } } }, From 74ae0a5537ad040faa972edafa6e0e9df2f9d10c Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Sun, 25 Oct 2009 16:35:59 +0100 Subject: [PATCH 26/28] Renamed Prototype.Legacy to Prototype.LegacySelector by popular request. --- .../repository/legacy_selector.js} | 134 +++++++++--------- .../selector_engine.js | 4 +- 2 files changed, 69 insertions(+), 69 deletions(-) rename vendor/{legacy/repository/legacy.js => legacy_selector/repository/legacy_selector.js} (82%) rename vendor/{legacy => legacy_selector}/selector_engine.js (83%) diff --git a/vendor/legacy/repository/legacy.js b/vendor/legacy_selector/repository/legacy_selector.js similarity index 82% rename from vendor/legacy/repository/legacy.js rename to vendor/legacy_selector/repository/legacy_selector.js index ddfd04e..0253d4c 100644 --- a/vendor/legacy/repository/legacy.js +++ b/vendor/legacy_selector/repository/legacy_selector.js @@ -1,8 +1,8 @@ -/* Portions of the Prototype.Legacy class are derived from Jack Slocum's DomQuery, +/* Portions of the Prototype.LegacySelector class are derived from Jack Slocum's DomQuery, * part of YUI-Ext version 0.40, distributed under the terms of an MIT-style * license. Please see http://www.yui-ext.com/ for more information. */ -Prototype.Legacy = Class.create({ +Prototype.LegacySelector = Class.create({ initialize: function(expression) { this.expression = expression.strip(); @@ -66,14 +66,14 @@ Prototype.Legacy = Class.create({ shouldUseSelectorsAPI: function() { if (!Prototype.BrowserFeatures.SelectorsAPI) return false; - if (Prototype.Legacy.CASE_INSENSITIVE_CLASS_NAMES) return false; + if (Prototype.LegacySelector.CASE_INSENSITIVE_CLASS_NAMES) return false; - if (!Prototype.Legacy._div) Prototype.Legacy._div = new Element('div'); + if (!Prototype.LegacySelector._div) Prototype.LegacySelector._div = new Element('div'); // Make sure the browser treats the selector as valid. Test on an // isolated element to minimize cost of this check. try { - Prototype.Legacy._div.querySelector(this.expression); + Prototype.LegacySelector._div.querySelector(this.expression); } catch(e) { return false; } @@ -82,16 +82,16 @@ Prototype.Legacy = Class.create({ }, compileMatcher: function() { - var e = this.expression, ps = Prototype.Legacy.patterns, h = Prototype.Legacy.handlers, - c = Prototype.Legacy.criteria, le, p, m, len = ps.length, name; + var e = this.expression, ps = Prototype.LegacySelector.patterns, h = Prototype.LegacySelector.handlers, + c = Prototype.LegacySelector.criteria, le, p, m, len = ps.length, name; - if (Prototype.Legacy._cache[e]) { - this.matcher = Prototype.Legacy._cache[e]; + if (Prototype.LegacySelector._cache[e]) { + this.matcher = Prototype.LegacySelector._cache[e]; return; } this.matcher = ["this.matcher = function(root) {", - "var r = root, h = Prototype.Legacy.handlers, c = false, n;"]; + "var r = root, h = Prototype.LegacySelector.handlers, c = false, n;"]; while (e && le != e && (/\S/).test(e)) { le = e; @@ -109,15 +109,15 @@ Prototype.Legacy = Class.create({ this.matcher.push("return h.unique(n);\n}"); eval(this.matcher.join('\n')); - Prototype.Legacy._cache[this.expression] = this.matcher; + Prototype.LegacySelector._cache[this.expression] = this.matcher; }, compileXPathMatcher: function() { - var e = this.expression, ps = Prototype.Legacy.patterns, - x = Prototype.Legacy.xpath, le, m, len = ps.length, name; + var e = this.expression, ps = Prototype.LegacySelector.patterns, + x = Prototype.LegacySelector.xpath, le, m, len = ps.length, name; - if (Prototype.Legacy._cache[e]) { - this.xpath = Prototype.Legacy._cache[e]; return; + if (Prototype.LegacySelector._cache[e]) { + this.xpath = Prototype.LegacySelector._cache[e]; return; } this.matcher = ['.//*']; @@ -135,7 +135,7 @@ Prototype.Legacy = Class.create({ } this.xpath = this.matcher.join(''); - Prototype.Legacy._cache[this.expression] = this.xpath; + Prototype.LegacySelector._cache[this.expression] = this.xpath; }, findElements: function(root) { @@ -168,7 +168,7 @@ Prototype.Legacy = Class.create({ match: function(element) { this.tokens = []; - var e = this.expression, ps = Prototype.Legacy.patterns, as = Prototype.Legacy.assertions; + var e = this.expression, ps = Prototype.LegacySelector.patterns, as = Prototype.LegacySelector.assertions; var le, p, m, len = ps.length, name; while (e && le !== e && (/\S/).test(e)) { @@ -177,7 +177,7 @@ Prototype.Legacy = Class.create({ p = ps[i].re; name = ps[i].name; if (m = e.match(p)) { - // use the Prototype.Legacy.assertions methods unless the selector + // use the Prototype.LegacySelector.assertions methods unless the selector // is too complex. if (as[name]) { this.tokens.push([name, Object.clone(m)]); @@ -194,7 +194,7 @@ Prototype.Legacy = Class.create({ var match = true, name, matches; for (var i = 0, token; token = this.tokens[i]; i++) { name = token[0], matches = token[1]; - if (!Prototype.Legacy.assertions[name](element, matches)) { + if (!Prototype.LegacySelector.assertions[name](element, matches)) { match = false; break; } } @@ -207,7 +207,7 @@ Prototype.Legacy = Class.create({ }, inspect: function() { - return "#"; + return "#"; } }); @@ -216,7 +216,7 @@ if (Prototype.BrowserFeatures.SelectorsAPI && // Versions of Safari 3 before 3.1.2 treat class names case-insensitively in // quirks mode. If we detect this behavior, we should use a different // approach. - Prototype.Legacy.CASE_INSENSITIVE_CLASS_NAMES = (function(){ + Prototype.LegacySelector.CASE_INSENSITIVE_CLASS_NAMES = (function(){ var div = document.createElement('div'), span = document.createElement('span'); @@ -229,7 +229,7 @@ if (Prototype.BrowserFeatures.SelectorsAPI && })(); } -Object.extend(Prototype.Legacy, { +Object.extend(Prototype.LegacySelector, { _cache: { }, xpath: { @@ -251,13 +251,13 @@ Object.extend(Prototype.Legacy, { attr: function(m) { m[1] = m[1].toLowerCase(); m[3] = m[5] || m[6]; - return new Template(Prototype.Legacy.xpath.operators[m[2]]).evaluate(m); + return new Template(Prototype.LegacySelector.xpath.operators[m[2]]).evaluate(m); }, pseudo: function(m) { - var h = Prototype.Legacy.xpath.pseudos[m[1]]; + var h = Prototype.LegacySelector.xpath.pseudos[m[1]]; if (!h) return ''; if (Object.isFunction(h)) return h(m); - return new Template(Prototype.Legacy.xpath.pseudos[m[1]]).evaluate(m); + return new Template(Prototype.LegacySelector.xpath.pseudos[m[1]]).evaluate(m); }, operators: { '=': "[@#{1}='#{3}']", @@ -277,8 +277,8 @@ Object.extend(Prototype.Legacy, { 'disabled': "[(@disabled) and (@type!='hidden')]", 'enabled': "[not(@disabled) and (@type!='hidden')]", 'not': function(m) { - var e = m[6], p = Prototype.Legacy.patterns, - x = Prototype.Legacy.xpath, le, v, len = p.length, name; + var e = m[6], p = Prototype.LegacySelector.patterns, + x = Prototype.LegacySelector.xpath, le, v, len = p.length, name; var exclusion = []; while (e && le != e && (/\S/).test(e)) { @@ -296,25 +296,25 @@ Object.extend(Prototype.Legacy, { return "[not(" + exclusion.join(" and ") + ")]"; }, 'nth-child': function(m) { - return Prototype.Legacy.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); + return Prototype.LegacySelector.xpath.pseudos.nth("(count(./preceding-sibling::*) + 1) ", m); }, 'nth-last-child': function(m) { - return Prototype.Legacy.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); + return Prototype.LegacySelector.xpath.pseudos.nth("(count(./following-sibling::*) + 1) ", m); }, 'nth-of-type': function(m) { - return Prototype.Legacy.xpath.pseudos.nth("position() ", m); + return Prototype.LegacySelector.xpath.pseudos.nth("position() ", m); }, 'nth-last-of-type': function(m) { - return Prototype.Legacy.xpath.pseudos.nth("(last() + 1 - position()) ", m); + return Prototype.LegacySelector.xpath.pseudos.nth("(last() + 1 - position()) ", m); }, 'first-of-type': function(m) { - m[6] = "1"; return Prototype.Legacy.xpath.pseudos['nth-of-type'](m); + m[6] = "1"; return Prototype.LegacySelector.xpath.pseudos['nth-of-type'](m); }, 'last-of-type': function(m) { - m[6] = "1"; return Prototype.Legacy.xpath.pseudos['nth-last-of-type'](m); + m[6] = "1"; return Prototype.LegacySelector.xpath.pseudos['nth-last-of-type'](m); }, 'only-of-type': function(m) { - var p = Prototype.Legacy.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); + var p = Prototype.LegacySelector.xpath.pseudos; return p['first-of-type'](m) + p['last-of-type'](m); }, nth: function(fragment, m) { var mm, formula = m[6], predicate; @@ -371,7 +371,7 @@ Object.extend(Prototype.Legacy, { { name: 'attr', re: /\[((?:[\w-]*:)?[\w-]+)\s*(?:([!^$*~|]?=)\s*((['"])([^\4]*?)\4|([^'"][^\]]*?)))?\]/ } ], - // for Prototype.Legacy.match and Element#match + // for Prototype.LegacySelector.match and Element#match assertions: { tagName: function(element, matches) { return matches[1].toUpperCase() == element.tagName.toUpperCase(); @@ -391,7 +391,7 @@ Object.extend(Prototype.Legacy, { attr: function(element, matches) { var nodeValue = Element.readAttribute(element, matches[1]); - return nodeValue && Prototype.Legacy.operators[matches[2]](nodeValue, matches[5] || matches[6]); + return nodeValue && Prototype.LegacySelector.operators[matches[2]](nodeValue, matches[5] || matches[6]); } }, @@ -466,19 +466,19 @@ Object.extend(Prototype.Legacy, { n._countedByPrototype = Prototype.emptyFunction; results.push(Element.extend(n)); } - return Prototype.Legacy.handlers.unmark(results); + return Prototype.LegacySelector.handlers.unmark(results); }, // COMBINATOR FUNCTIONS descendant: function(nodes) { - var h = Prototype.Legacy.handlers; + var h = Prototype.LegacySelector.handlers; for (var i = 0, results = [], node; node = nodes[i]; i++) h.concat(results, node.getElementsByTagName('*')); return results; }, child: function(nodes) { - var h = Prototype.Legacy.handlers; + var h = Prototype.LegacySelector.handlers; for (var i = 0, results = [], node; node = nodes[i]; i++) { for (var j = 0, child; child = node.childNodes[j]; j++) if (child.nodeType == 1 && child.tagName != '!') results.push(child); @@ -495,7 +495,7 @@ Object.extend(Prototype.Legacy, { }, laterSibling: function(nodes) { - var h = Prototype.Legacy.handlers; + var h = Prototype.LegacySelector.handlers; for (var i = 0, results = [], node; node = nodes[i]; i++) h.concat(results, Element.nextSiblings(node)); return results; @@ -516,7 +516,7 @@ Object.extend(Prototype.Legacy, { // TOKEN FUNCTIONS tagName: function(nodes, root, tagName, combinator) { var uTagName = tagName.toUpperCase(); - var results = [], h = Prototype.Legacy.handlers; + var results = [], h = Prototype.LegacySelector.handlers; if (nodes) { if (combinator) { // fastlane for ordinary descendant combinators @@ -534,7 +534,7 @@ Object.extend(Prototype.Legacy, { }, id: function(nodes, root, id, combinator) { - var targetNode = $(id), h = Prototype.Legacy.handlers; + var targetNode = $(id), h = Prototype.LegacySelector.handlers; if (root == document) { // We don't have to deal with orphan nodes. Easy. @@ -562,7 +562,7 @@ Object.extend(Prototype.Legacy, { if (Element.descendantOf(targetNode, node)) return [targetNode]; } else if (combinator == 'adjacent') { for (var i = 0, node; node = nodes[i]; i++) - if (Prototype.Legacy.handlers.previousElementSibling(targetNode) == node) + if (Prototype.LegacySelector.handlers.previousElementSibling(targetNode) == node) return [targetNode]; } else nodes = h[combinator](nodes); } @@ -575,11 +575,11 @@ Object.extend(Prototype.Legacy, { className: function(nodes, root, className, combinator) { if (nodes && combinator) nodes = this[combinator](nodes); - return Prototype.Legacy.handlers.byClassName(nodes, root, className); + return Prototype.LegacySelector.handlers.byClassName(nodes, root, className); }, byClassName: function(nodes, root, className) { - if (!nodes) nodes = Prototype.Legacy.handlers.descendant([root]); + if (!nodes) nodes = Prototype.LegacySelector.handlers.descendant([root]); var needle = ' ' + className + ' '; for (var i = 0, results = [], node, nodeClassName; node = nodes[i]; i++) { nodeClassName = node.className; @@ -602,7 +602,7 @@ Object.extend(Prototype.Legacy, { attr: function(nodes, root, attr, value, operator, combinator) { if (!nodes) nodes = root.getElementsByTagName("*"); if (nodes && combinator) nodes = this[combinator](nodes); - var handler = Prototype.Legacy.operators[operator], results = []; + var handler = Prototype.LegacySelector.operators[operator], results = []; for (var i = 0, node; node = nodes[i]; i++) { var nodeValue = Element.readAttribute(node, attr); if (nodeValue === null) continue; @@ -614,52 +614,52 @@ Object.extend(Prototype.Legacy, { pseudo: function(nodes, name, value, root, combinator) { if (nodes && combinator) nodes = this[combinator](nodes); if (!nodes) nodes = root.getElementsByTagName("*"); - return Prototype.Legacy.pseudos[name](nodes, value, root); + return Prototype.LegacySelector.pseudos[name](nodes, value, root); } }, pseudos: { 'first-child': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) { - if (Prototype.Legacy.handlers.previousElementSibling(node)) continue; + if (Prototype.LegacySelector.handlers.previousElementSibling(node)) continue; results.push(node); } return results; }, 'last-child': function(nodes, value, root) { for (var i = 0, results = [], node; node = nodes[i]; i++) { - if (Prototype.Legacy.handlers.nextElementSibling(node)) continue; + if (Prototype.LegacySelector.handlers.nextElementSibling(node)) continue; results.push(node); } return results; }, 'only-child': function(nodes, value, root) { - var h = Prototype.Legacy.handlers; + var h = Prototype.LegacySelector.handlers; for (var i = 0, results = [], node; node = nodes[i]; i++) if (!h.previousElementSibling(node) && !h.nextElementSibling(node)) results.push(node); return results; }, 'nth-child': function(nodes, formula, root) { - return Prototype.Legacy.pseudos.nth(nodes, formula, root); + return Prototype.LegacySelector.pseudos.nth(nodes, formula, root); }, 'nth-last-child': function(nodes, formula, root) { - return Prototype.Legacy.pseudos.nth(nodes, formula, root, true); + return Prototype.LegacySelector.pseudos.nth(nodes, formula, root, true); }, 'nth-of-type': function(nodes, formula, root) { - return Prototype.Legacy.pseudos.nth(nodes, formula, root, false, true); + return Prototype.LegacySelector.pseudos.nth(nodes, formula, root, false, true); }, 'nth-last-of-type': function(nodes, formula, root) { - return Prototype.Legacy.pseudos.nth(nodes, formula, root, true, true); + return Prototype.LegacySelector.pseudos.nth(nodes, formula, root, true, true); }, 'first-of-type': function(nodes, formula, root) { - return Prototype.Legacy.pseudos.nth(nodes, "1", root, false, true); + return Prototype.LegacySelector.pseudos.nth(nodes, "1", root, false, true); }, 'last-of-type': function(nodes, formula, root) { - return Prototype.Legacy.pseudos.nth(nodes, "1", root, true, true); + return Prototype.LegacySelector.pseudos.nth(nodes, "1", root, true, true); }, 'only-of-type': function(nodes, formula, root) { - var p = Prototype.Legacy.pseudos; + var p = Prototype.LegacySelector.pseudos; return p['last-of-type'](p['first-of-type'](nodes, formula, root), formula, root); }, @@ -677,7 +677,7 @@ Object.extend(Prototype.Legacy, { if (nodes.length == 0) return []; if (formula == 'even') formula = '2n+0'; if (formula == 'odd') formula = '2n+1'; - var h = Prototype.Legacy.handlers, results = [], indexed = [], m; + var h = Prototype.LegacySelector.handlers, results = [], indexed = [], m; h.mark(nodes); for (var i = 0, node; node = nodes[i]; i++) { if (!node.parentNode._countedByPrototype) { @@ -693,7 +693,7 @@ Object.extend(Prototype.Legacy, { if (m[1] == "-") m[1] = -1; var a = m[1] ? Number(m[1]) : 1; var b = m[2] ? Number(m[2]) : 0; - var indices = Prototype.Legacy.pseudos.getIndices(a, b, nodes.length); + var indices = Prototype.LegacySelector.pseudos.getIndices(a, b, nodes.length); for (var i = 0, node, l = indices.length; node = nodes[i]; i++) { for (var j = 0; j < l; j++) if (node.nodeIndex == indices[j]) results.push(node); @@ -714,8 +714,8 @@ Object.extend(Prototype.Legacy, { }, 'not': function(nodes, selector, root) { - var h = Prototype.Legacy.handlers, selectorType, m; - var exclusions = new Prototype.Legacy(selector).findElements(root); + var h = Prototype.LegacySelector.handlers, selectorType, m; + var exclusions = new Prototype.LegacySelector(selector).findElements(root); h.mark(exclusions); for (var i = 0, results = [], node; node = nodes[i]; i++) if (!node._countedByPrototype) results.push(node); @@ -763,7 +763,7 @@ Object.extend(Prototype.Legacy, { }, matchElements: function(elements, expression) { - var matches = $$(expression), h = Prototype.Legacy.handlers; + var matches = $$(expression), h = Prototype.LegacySelector.handlers; h.mark(matches); for (var i = 0, results = [], element; element = elements[i]; i++) if (element._countedByPrototype) results.push(element); @@ -775,14 +775,14 @@ Object.extend(Prototype.Legacy, { if (Object.isNumber(expression)) { index = expression; expression = false; } - return Prototype.Legacy.matchElements(elements, expression || '*')[index || 0]; + return Prototype.LegacySelector.matchElements(elements, expression || '*')[index || 0]; }, findChildElements: function(element, expressions) { - expressions = Prototype.Legacy.split(expressions.join(',')); - var results = [], h = Prototype.Legacy.handlers; + expressions = Prototype.LegacySelector.split(expressions.join(',')); + var results = [], h = Prototype.LegacySelector.handlers; for (var i = 0, l = expressions.length, selector; i < l; i++) { - selector = new Prototype.Legacy(expressions[i].strip()); + selector = new Prototype.LegacySelector(expressions[i].strip()); h.concat(results, selector.findElements(element)); } return (l > 1) ? h.unique(results) : results; @@ -790,7 +790,7 @@ Object.extend(Prototype.Legacy, { }); if (Prototype.Browser.IE) { - Object.extend(Prototype.Legacy.handlers, { + Object.extend(Prototype.LegacySelector.handlers, { // IE returns comment nodes on getElementsByTagName("*"). // Filter them out. concat: function(a, b) { diff --git a/vendor/legacy/selector_engine.js b/vendor/legacy_selector/selector_engine.js similarity index 83% rename from vendor/legacy/selector_engine.js rename to vendor/legacy_selector/selector_engine.js index 21d5c6b..cf62875 100644 --- a/vendor/legacy/selector_engine.js +++ b/vendor/legacy_selector/selector_engine.js @@ -1,4 +1,4 @@ -//= require "repository/legacy" +//= require "repository/legacy_selector" Prototype.Selector = (function(engine) { function select(selector, scope) { @@ -15,4 +15,4 @@ Prototype.Selector = (function(engine) { match: match, filter: engine.matchElements }; -})(Prototype.Legacy); +})(Prototype.LegacySelector); From 678774cbd6ad70d9ea398a0cc18e31cb01caae30 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Mon, 26 Oct 2009 02:00:55 +0100 Subject: [PATCH 27/28] Minor changes to the Sizzle adapter. --- vendor/sizzle/selector_engine.js | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/vendor/sizzle/selector_engine.js b/vendor/sizzle/selector_engine.js index 6980631..f460f33 100644 --- a/vendor/sizzle/selector_engine.js +++ b/vendor/sizzle/selector_engine.js @@ -3,8 +3,9 @@ Prototype._original_property = window.Sizzle; Prototype.Selector = (function(engine) { function extend(elements) { - for (var i = 0, length = elements.length; i < length; i++) - elements[i] = Element.extend(elements[i]); + for (var i = 0, length = elements.length; i < length; i++) { + Element.extend(elements[i]); + } return elements; } From cdb41a170fca3c491db68333b95f770ca5b11919 Mon Sep 17 00:00:00 2001 From: Tobie Langel Date: Mon, 30 Nov 2009 11:12:26 +0100 Subject: [PATCH 28/28] Add sizzle to ext/. --- Rakefile | 9 +- ext/sizzle/repository/sizzle.js | 1015 +++++++++++++++++++++++++++++++ ext/sizzle/selector_engine.js | 34 ++ 3 files changed, 1054 insertions(+), 4 deletions(-) create mode 100644 ext/sizzle/repository/sizzle.js create mode 100644 ext/sizzle/selector_engine.js diff --git a/Rakefile b/Rakefile index ad8bea8..1bb7c10 100755 --- a/Rakefile +++ b/Rakefile @@ -43,7 +43,7 @@ module PrototypeHelper }.merge(options) require_sprockets - load_path = [SRC_DIR] + load_path = [SRC_DIR, File.join(ROOT_DIR, 'ext', 'sizzle')] if selector = options[:selector_engine] get_selector_engine(selector) @@ -98,10 +98,11 @@ module PrototypeHelper end def self.get_selector_engine(name) - file = File.join(ROOT_DIR, 'vendor', name, 'repository') - unless File.exists?(file) + submodule = File.join(ROOT_DIR, 'vendor', name, 'repository') + ext = File.join(ROOT_DIR, 'ext', name) + unless File.exist?(submodule) || File.exist?(ext) get_submodule('the required selector engine', "#{name}/repository") - unless File.exists?(file) + unless File.exist?(submodule) puts "The selector engine you required isn't available at vendor/#{name}.\n\n" exit end diff --git a/ext/sizzle/repository/sizzle.js b/ext/sizzle/repository/sizzle.js new file mode 100644 index 0000000..801b731 --- /dev/null +++ b/ext/sizzle/repository/sizzle.js @@ -0,0 +1,1015 @@ +/*! + * Sizzle CSS Selector Engine - v1.0 + * Copyright 2009, The Dojo Foundation + * Released under the MIT, BSD, and GPL Licenses. + * More information: http://sizzlejs.com/ + */ +(function(){ + +var chunker = /((?:\((?:\([^()]+\)|[^()]+)+\)|\[(?:\[[^[\]]*\]|['"][^'"]*['"]|[^[\]'"]+)+\]|\\.|[^ >+~,(\[\\]+)+|[>+~])(\s*,\s*)?((?:.|\r|\n)*)/g, + done = 0, + toString = Object.prototype.toString, + hasDuplicate = false, + baseHasDuplicate = true; + +// Here we check if the JavaScript engine is using some sort of +// optimization where it does not always call our comparision +// function. If that is the case, discard the hasDuplicate value. +// Thus far that includes Google Chrome. +[0, 0].sort(function(){ + baseHasDuplicate = false; + return 0; +}); + +var Sizzle = function(selector, context, results, seed) { + results = results || []; + var origContext = context = context || document; + + if ( context.nodeType !== 1 && context.nodeType !== 9 ) { + return []; + } + + if ( !selector || typeof selector !== "string" ) { + return results; + } + + var parts = [], m, set, checkSet, check, mode, extra, prune = true, contextXML = isXML(context), + soFar = selector; + + // Reset the position of the chunker regexp (start from head) + while ( (chunker.exec(""), m = chunker.exec(soFar)) !== null ) { + soFar = m[3]; + + parts.push( m[1] ); + + if ( m[2] ) { + extra = m[3]; + break; + } + } + + if ( parts.length > 1 && origPOS.exec( selector ) ) { + if ( parts.length === 2 && Expr.relative[ parts[0] ] ) { + set = posProcess( parts[0] + parts[1], context ); + } else { + set = Expr.relative[ parts[0] ] ? + [ context ] : + Sizzle( parts.shift(), context ); + + while ( parts.length ) { + selector = parts.shift(); + + if ( Expr.relative[ selector ] ) + selector += parts.shift(); + + set = posProcess( selector, set ); + } + } + } else { + // Take a shortcut and set the context if the root selector is an ID + // (but not if it'll be faster if the inner selector is an ID) + if ( !seed && parts.length > 1 && context.nodeType === 9 && !contextXML && + Expr.match.ID.test(parts[0]) && !Expr.match.ID.test(parts[parts.length - 1]) ) { + var ret = Sizzle.find( parts.shift(), context, contextXML ); + context = ret.expr ? Sizzle.filter( ret.expr, ret.set )[0] : ret.set[0]; + } + + if ( context ) { + var ret = seed ? + { expr: parts.pop(), set: makeArray(seed) } : + Sizzle.find( parts.pop(), parts.length === 1 && (parts[0] === "~" || parts[0] === "+") && context.parentNode ? context.parentNode : context, contextXML ); + set = ret.expr ? Sizzle.filter( ret.expr, ret.set ) : ret.set; + + if ( parts.length > 0 ) { + checkSet = makeArray(set); + } else { + prune = false; + } + + while ( parts.length ) { + var cur = parts.pop(), pop = cur; + + if ( !Expr.relative[ cur ] ) { + cur = ""; + } else { + pop = parts.pop(); + } + + if ( pop == null ) { + pop = context; + } + + Expr.relative[ cur ]( checkSet, pop, contextXML ); + } + } else { + checkSet = parts = []; + } + } + + if ( !checkSet ) { + checkSet = set; + } + + if ( !checkSet ) { + throw "Syntax error, unrecognized expression: " + (cur || selector); + } + + if ( toString.call(checkSet) === "[object Array]" ) { + if ( !prune ) { + results.push.apply( results, checkSet ); + } else if ( context && context.nodeType === 1 ) { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && (checkSet[i] === true || checkSet[i].nodeType === 1 && contains(context, checkSet[i])) ) { + results.push( set[i] ); + } + } + } else { + for ( var i = 0; checkSet[i] != null; i++ ) { + if ( checkSet[i] && checkSet[i].nodeType === 1 ) { + results.push( set[i] ); + } + } + } + } else { + makeArray( checkSet, results ); + } + + if ( extra ) { + Sizzle( extra, origContext, results, seed ); + Sizzle.uniqueSort( results ); + } + + return results; +}; + +Sizzle.uniqueSort = function(results){ + if ( sortOrder ) { + hasDuplicate = baseHasDuplicate; + results.sort(sortOrder); + + if ( hasDuplicate ) { + for ( var i = 1; i < results.length; i++ ) { + if ( results[i] === results[i-1] ) { + results.splice(i--, 1); + } + } + } + } + + return results; +}; + +Sizzle.matches = function(expr, set){ + return Sizzle(expr, null, null, set); +}; + +Sizzle.find = function(expr, context, isXML){ + var set, match; + + if ( !expr ) { + return []; + } + + for ( var i = 0, l = Expr.order.length; i < l; i++ ) { + var type = Expr.order[i], match; + + if ( (match = Expr.leftMatch[ type ].exec( expr )) ) { + var left = match[1]; + match.splice(1,1); + + if ( left.substr( left.length - 1 ) !== "\\" ) { + match[1] = (match[1] || "").replace(/\\/g, ""); + set = Expr.find[ type ]( match, context, isXML ); + if ( set != null ) { + expr = expr.replace( Expr.match[ type ], "" ); + break; + } + } + } + } + + if ( !set ) { + set = context.getElementsByTagName("*"); + } + + return {set: set, expr: expr}; +}; + +Sizzle.filter = function(expr, set, inplace, not){ + var old = expr, result = [], curLoop = set, match, anyFound, + isXMLFilter = set && set[0] && isXML(set[0]); + + while ( expr && set.length ) { + for ( var type in Expr.filter ) { + if ( (match = Expr.match[ type ].exec( expr )) != null ) { + var filter = Expr.filter[ type ], found, item; + anyFound = false; + + if ( curLoop == result ) { + result = []; + } + + if ( Expr.preFilter[ type ] ) { + match = Expr.preFilter[ type ]( match, curLoop, inplace, result, not, isXMLFilter ); + + if ( !match ) { + anyFound = found = true; + } else if ( match === true ) { + continue; + } + } + + if ( match ) { + for ( var i = 0; (item = curLoop[i]) != null; i++ ) { + if ( item ) { + found = filter( item, match, i, curLoop ); + var pass = not ^ !!found; + + if ( inplace && found != null ) { + if ( pass ) { + anyFound = true; + } else { + curLoop[i] = false; + } + } else if ( pass ) { + result.push( item ); + anyFound = true; + } + } + } + } + + if ( found !== undefined ) { + if ( !inplace ) { + curLoop = result; + } + + expr = expr.replace( Expr.match[ type ], "" ); + + if ( !anyFound ) { + return []; + } + + break; + } + } + } + + // Improper expression + if ( expr == old ) { + if ( anyFound == null ) { + throw "Syntax error, unrecognized expression: " + expr; + } else { + break; + } + } + + old = expr; + } + + return curLoop; +}; + +var Expr = Sizzle.selectors = { + order: [ "ID", "NAME", "TAG" ], + match: { + ID: /#((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + CLASS: /\.((?:[\w\u00c0-\uFFFF-]|\\.)+)/, + NAME: /\[name=['"]*((?:[\w\u00c0-\uFFFF-]|\\.)+)['"]*\]/, + ATTR: /\[\s*((?:[\w\u00c0-\uFFFF-]|\\.)+)\s*(?:(\S?=)\s*(['"]*)(.*?)\3|)\s*\]/, + TAG: /^((?:[\w\u00c0-\uFFFF\*-]|\\.)+)/, + CHILD: /:(only|nth|last|first)-child(?:\((even|odd|[\dn+-]*)\))?/, + POS: /:(nth|eq|gt|lt|first|last|even|odd)(?:\((\d*)\))?(?=[^-]|$)/, + PSEUDO: /:((?:[\w\u00c0-\uFFFF-]|\\.)+)(?:\((['"]*)((?:\([^\)]+\)|[^\2\(\)]*)+)\2\))?/ + }, + leftMatch: {}, + attrMap: { + "class": "className", + "for": "htmlFor" + }, + attrHandle: { + href: function(elem){ + return elem.getAttribute("href"); + } + }, + relative: { + "+": function(checkSet, part, isXML){ + var isPartStr = typeof part === "string", + isTag = isPartStr && !/\W/.test(part), + isPartStrNotTag = isPartStr && !isTag; + + if ( isTag && !isXML ) { + part = part.toUpperCase(); + } + + for ( var i = 0, l = checkSet.length, elem; i < l; i++ ) { + if ( (elem = checkSet[i]) ) { + while ( (elem = elem.previousSibling) && elem.nodeType !== 1 ) {} + + checkSet[i] = isPartStrNotTag || elem && elem.nodeName === part ? + elem || false : + elem === part; + } + } + + if ( isPartStrNotTag ) { + Sizzle.filter( part, checkSet, true ); + } + }, + ">": function(checkSet, part, isXML){ + var isPartStr = typeof part === "string"; + + if ( isPartStr && !/\W/.test(part) ) { + part = isXML ? part : part.toUpperCase(); + + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + var parent = elem.parentNode; + checkSet[i] = parent.nodeName === part ? parent : false; + } + } + } else { + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + checkSet[i] = isPartStr ? + elem.parentNode : + elem.parentNode === part; + } + } + + if ( isPartStr ) { + Sizzle.filter( part, checkSet, true ); + } + } + }, + "": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( !/\W/.test(part) ) { + var nodeCheck = part = isXML ? part : part.toUpperCase(); + checkFn = dirNodeCheck; + } + + checkFn("parentNode", part, doneName, checkSet, nodeCheck, isXML); + }, + "~": function(checkSet, part, isXML){ + var doneName = done++, checkFn = dirCheck; + + if ( typeof part === "string" && !/\W/.test(part) ) { + var nodeCheck = part = isXML ? part : part.toUpperCase(); + checkFn = dirNodeCheck; + } + + checkFn("previousSibling", part, doneName, checkSet, nodeCheck, isXML); + } + }, + find: { + ID: function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? [m] : []; + } + }, + NAME: function(match, context, isXML){ + if ( typeof context.getElementsByName !== "undefined" ) { + var ret = [], results = context.getElementsByName(match[1]); + + for ( var i = 0, l = results.length; i < l; i++ ) { + if ( results[i].getAttribute("name") === match[1] ) { + ret.push( results[i] ); + } + } + + return ret.length === 0 ? null : ret; + } + }, + TAG: function(match, context){ + return context.getElementsByTagName(match[1]); + } + }, + preFilter: { + CLASS: function(match, curLoop, inplace, result, not, isXML){ + match = " " + match[1].replace(/\\/g, "") + " "; + + if ( isXML ) { + return match; + } + + for ( var i = 0, elem; (elem = curLoop[i]) != null; i++ ) { + if ( elem ) { + if ( not ^ (elem.className && (" " + elem.className + " ").indexOf(match) >= 0) ) { + if ( !inplace ) + result.push( elem ); + } else if ( inplace ) { + curLoop[i] = false; + } + } + } + + return false; + }, + ID: function(match){ + return match[1].replace(/\\/g, ""); + }, + TAG: function(match, curLoop){ + for ( var i = 0; curLoop[i] === false; i++ ){} + return curLoop[i] && isXML(curLoop[i]) ? match[1] : match[1].toUpperCase(); + }, + CHILD: function(match){ + if ( match[1] == "nth" ) { + // parse equations like 'even', 'odd', '5', '2n', '3n+2', '4n-1', '-n+6' + var test = /(-?)(\d*)n((?:\+|-)?\d*)/.exec( + match[2] == "even" && "2n" || match[2] == "odd" && "2n+1" || + !/\D/.test( match[2] ) && "0n+" + match[2] || match[2]); + + // calculate the numbers (first)n+(last) including if they are negative + match[2] = (test[1] + (test[2] || 1)) - 0; + match[3] = test[3] - 0; + } + + // TODO: Move to normal caching system + match[0] = done++; + + return match; + }, + ATTR: function(match, curLoop, inplace, result, not, isXML){ + var name = match[1].replace(/\\/g, ""); + + if ( !isXML && Expr.attrMap[name] ) { + match[1] = Expr.attrMap[name]; + } + + if ( match[2] === "~=" ) { + match[4] = " " + match[4] + " "; + } + + return match; + }, + PSEUDO: function(match, curLoop, inplace, result, not){ + if ( match[1] === "not" ) { + // If we're dealing with a complex expression, or a simple one + if ( ( chunker.exec(match[3]) || "" ).length > 1 || /^\w/.test(match[3]) ) { + match[3] = Sizzle(match[3], null, null, curLoop); + } else { + var ret = Sizzle.filter(match[3], curLoop, inplace, true ^ not); + if ( !inplace ) { + result.push.apply( result, ret ); + } + return false; + } + } else if ( Expr.match.POS.test( match[0] ) || Expr.match.CHILD.test( match[0] ) ) { + return true; + } + + return match; + }, + POS: function(match){ + match.unshift( true ); + return match; + } + }, + filters: { + enabled: function(elem){ + return elem.disabled === false && elem.type !== "hidden"; + }, + disabled: function(elem){ + return elem.disabled === true; + }, + checked: function(elem){ + return elem.checked === true; + }, + selected: function(elem){ + // Accessing this property makes selected-by-default + // options in Safari work properly + elem.parentNode.selectedIndex; + return elem.selected === true; + }, + parent: function(elem){ + return !!elem.firstChild; + }, + empty: function(elem){ + return !elem.firstChild; + }, + has: function(elem, i, match){ + return !!Sizzle( match[3], elem ).length; + }, + header: function(elem){ + return /h\d/i.test( elem.nodeName ); + }, + text: function(elem){ + return "text" === elem.type; + }, + radio: function(elem){ + return "radio" === elem.type; + }, + checkbox: function(elem){ + return "checkbox" === elem.type; + }, + file: function(elem){ + return "file" === elem.type; + }, + password: function(elem){ + return "password" === elem.type; + }, + submit: function(elem){ + return "submit" === elem.type; + }, + image: function(elem){ + return "image" === elem.type; + }, + reset: function(elem){ + return "reset" === elem.type; + }, + button: function(elem){ + return "button" === elem.type || elem.nodeName.toUpperCase() === "BUTTON"; + }, + input: function(elem){ + return /input|select|textarea|button/i.test(elem.nodeName); + } + }, + setFilters: { + first: function(elem, i){ + return i === 0; + }, + last: function(elem, i, match, array){ + return i === array.length - 1; + }, + even: function(elem, i){ + return i % 2 === 0; + }, + odd: function(elem, i){ + return i % 2 === 1; + }, + lt: function(elem, i, match){ + return i < match[3] - 0; + }, + gt: function(elem, i, match){ + return i > match[3] - 0; + }, + nth: function(elem, i, match){ + return match[3] - 0 == i; + }, + eq: function(elem, i, match){ + return match[3] - 0 == i; + } + }, + filter: { + PSEUDO: function(elem, match, i, array){ + var name = match[1], filter = Expr.filters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } else if ( name === "contains" ) { + return (elem.textContent || elem.innerText || "").indexOf(match[3]) >= 0; + } else if ( name === "not" ) { + var not = match[3]; + + for ( var i = 0, l = not.length; i < l; i++ ) { + if ( not[i] === elem ) { + return false; + } + } + + return true; + } + }, + CHILD: function(elem, match){ + var type = match[1], node = elem; + switch (type) { + case 'only': + case 'first': + while ( (node = node.previousSibling) ) { + if ( node.nodeType === 1 ) return false; + } + if ( type == 'first') return true; + node = elem; + case 'last': + while ( (node = node.nextSibling) ) { + if ( node.nodeType === 1 ) return false; + } + return true; + case 'nth': + var first = match[2], last = match[3]; + + if ( first == 1 && last == 0 ) { + return true; + } + + var doneName = match[0], + parent = elem.parentNode; + + if ( parent && (parent.sizcache !== doneName || !elem.nodeIndex) ) { + var count = 0; + for ( node = parent.firstChild; node; node = node.nextSibling ) { + if ( node.nodeType === 1 ) { + node.nodeIndex = ++count; + } + } + parent.sizcache = doneName; + } + + var diff = elem.nodeIndex - last; + if ( first == 0 ) { + return diff == 0; + } else { + return ( diff % first == 0 && diff / first >= 0 ); + } + } + }, + ID: function(elem, match){ + return elem.nodeType === 1 && elem.getAttribute("id") === match; + }, + TAG: function(elem, match){ + return (match === "*" && elem.nodeType === 1) || elem.nodeName === match; + }, + CLASS: function(elem, match){ + return (" " + (elem.className || elem.getAttribute("class")) + " ") + .indexOf( match ) > -1; + }, + ATTR: function(elem, match){ + var name = match[1], + result = Expr.attrHandle[ name ] ? + Expr.attrHandle[ name ]( elem ) : + elem[ name ] != null ? + elem[ name ] : + elem.getAttribute( name ), + value = result + "", + type = match[2], + check = match[4]; + + return result == null ? + type === "!=" : + type === "=" ? + value === check : + type === "*=" ? + value.indexOf(check) >= 0 : + type === "~=" ? + (" " + value + " ").indexOf(check) >= 0 : + !check ? + value && result !== false : + type === "!=" ? + value != check : + type === "^=" ? + value.indexOf(check) === 0 : + type === "$=" ? + value.substr(value.length - check.length) === check : + type === "|=" ? + value === check || value.substr(0, check.length + 1) === check + "-" : + false; + }, + POS: function(elem, match, i, array){ + var name = match[2], filter = Expr.setFilters[ name ]; + + if ( filter ) { + return filter( elem, i, match, array ); + } + } + } +}; + +var origPOS = Expr.match.POS; + +for ( var type in Expr.match ) { + Expr.match[ type ] = new RegExp( Expr.match[ type ].source + /(?![^\[]*\])(?![^\(]*\))/.source ); + Expr.leftMatch[ type ] = new RegExp( /(^(?:.|\r|\n)*?)/.source + Expr.match[ type ].source ); +} + +var makeArray = function(array, results) { + array = Array.prototype.slice.call( array, 0 ); + + if ( results ) { + results.push.apply( results, array ); + return results; + } + + return array; +}; + +// Perform a simple check to determine if the browser is capable of +// converting a NodeList to an array using builtin methods. +try { + Array.prototype.slice.call( document.documentElement.childNodes, 0 ); + +// Provide a fallback method if it does not work +} catch(e){ + makeArray = function(array, results) { + var ret = results || []; + + if ( toString.call(array) === "[object Array]" ) { + Array.prototype.push.apply( ret, array ); + } else { + if ( typeof array.length === "number" ) { + for ( var i = 0, l = array.length; i < l; i++ ) { + ret.push( array[i] ); + } + } else { + for ( var i = 0; array[i]; i++ ) { + ret.push( array[i] ); + } + } + } + + return ret; + }; +} + +var sortOrder; + +if ( document.documentElement.compareDocumentPosition ) { + sortOrder = function( a, b ) { + if ( !a.compareDocumentPosition || !b.compareDocumentPosition ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var ret = a.compareDocumentPosition(b) & 4 ? -1 : a === b ? 0 : 1; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( "sourceIndex" in document.documentElement ) { + sortOrder = function( a, b ) { + if ( !a.sourceIndex || !b.sourceIndex ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var ret = a.sourceIndex - b.sourceIndex; + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} else if ( document.createRange ) { + sortOrder = function( a, b ) { + if ( !a.ownerDocument || !b.ownerDocument ) { + if ( a == b ) { + hasDuplicate = true; + } + return 0; + } + + var aRange = a.ownerDocument.createRange(), bRange = b.ownerDocument.createRange(); + aRange.setStart(a, 0); + aRange.setEnd(a, 0); + bRange.setStart(b, 0); + bRange.setEnd(b, 0); + var ret = aRange.compareBoundaryPoints(Range.START_TO_END, bRange); + if ( ret === 0 ) { + hasDuplicate = true; + } + return ret; + }; +} + +// Check to see if the browser returns elements by name when +// querying by getElementById (and provide a workaround) +(function(){ + // We're going to inject a fake input element with a specified name + var form = document.createElement("div"), + id = "script" + (new Date).getTime(); + form.innerHTML = ""; + + // Inject it into the root element, check its status, and remove it quickly + var root = document.documentElement; + root.insertBefore( form, root.firstChild ); + + // The workaround has to do additional checks after a getElementById + // Which slows things down for other browsers (hence the branching) + if ( !!document.getElementById( id ) ) { + Expr.find.ID = function(match, context, isXML){ + if ( typeof context.getElementById !== "undefined" && !isXML ) { + var m = context.getElementById(match[1]); + return m ? m.id === match[1] || typeof m.getAttributeNode !== "undefined" && m.getAttributeNode("id").nodeValue === match[1] ? [m] : undefined : []; + } + }; + + Expr.filter.ID = function(elem, match){ + var node = typeof elem.getAttributeNode !== "undefined" && elem.getAttributeNode("id"); + return elem.nodeType === 1 && node && node.nodeValue === match; + }; + } + + root.removeChild( form ); + root = form = null; // release memory in IE +})(); + +(function(){ + // Check to see if the browser returns only elements + // when doing getElementsByTagName("*") + + // Create a fake element + var div = document.createElement("div"); + div.appendChild( document.createComment("") ); + + // Make sure no comments are found + if ( div.getElementsByTagName("*").length > 0 ) { + Expr.find.TAG = function(match, context){ + var results = context.getElementsByTagName(match[1]); + + // Filter out possible comments + if ( match[1] === "*" ) { + var tmp = []; + + for ( var i = 0; results[i]; i++ ) { + if ( results[i].nodeType === 1 ) { + tmp.push( results[i] ); + } + } + + results = tmp; + } + + return results; + }; + } + + // Check to see if an attribute returns normalized href attributes + div.innerHTML = ""; + if ( div.firstChild && typeof div.firstChild.getAttribute !== "undefined" && + div.firstChild.getAttribute("href") !== "#" ) { + Expr.attrHandle.href = function(elem){ + return elem.getAttribute("href", 2); + }; + } + + div = null; // release memory in IE +})(); + +if ( document.querySelectorAll ) (function(){ + var oldSizzle = Sizzle, div = document.createElement("div"); + div.innerHTML = "

"; + + // Safari can't handle uppercase or unicode characters when + // in quirks mode. + if ( div.querySelectorAll && div.querySelectorAll(".TEST").length === 0 ) { + return; + } + + Sizzle = function(query, context, extra, seed){ + context = context || document; + + // Only use querySelectorAll on non-XML documents + // (ID selectors don't work in non-HTML documents) + if ( !seed && context.nodeType === 9 && !isXML(context) ) { + try { + return makeArray( context.querySelectorAll(query), extra ); + } catch(e){} + } + + return oldSizzle(query, context, extra, seed); + }; + + for ( var prop in oldSizzle ) { + Sizzle[ prop ] = oldSizzle[ prop ]; + } + + div = null; // release memory in IE +})(); + +if ( document.getElementsByClassName && document.documentElement.getElementsByClassName ) (function(){ + var div = document.createElement("div"); + div.innerHTML = "
"; + + // Opera can't find a second classname (in 9.6) + if ( div.getElementsByClassName("e").length === 0 ) + return; + + // Safari caches class attributes, doesn't catch changes (in 3.2) + div.lastChild.className = "e"; + + if ( div.getElementsByClassName("e").length === 1 ) + return; + + Expr.order.splice(1, 0, "CLASS"); + Expr.find.CLASS = function(match, context, isXML) { + if ( typeof context.getElementsByClassName !== "undefined" && !isXML ) { + return context.getElementsByClassName(match[1]); + } + }; + + div = null; // release memory in IE +})(); + +function dirNodeCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + var sibDir = dir == "previousSibling" && !isXML; + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + if ( sibDir && elem.nodeType === 1 ){ + elem.sizcache = doneName; + elem.sizset = i; + } + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 && !isXML ){ + elem.sizcache = doneName; + elem.sizset = i; + } + + if ( elem.nodeName === cur ) { + match = elem; + break; + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +function dirCheck( dir, cur, doneName, checkSet, nodeCheck, isXML ) { + var sibDir = dir == "previousSibling" && !isXML; + for ( var i = 0, l = checkSet.length; i < l; i++ ) { + var elem = checkSet[i]; + if ( elem ) { + if ( sibDir && elem.nodeType === 1 ) { + elem.sizcache = doneName; + elem.sizset = i; + } + elem = elem[dir]; + var match = false; + + while ( elem ) { + if ( elem.sizcache === doneName ) { + match = checkSet[elem.sizset]; + break; + } + + if ( elem.nodeType === 1 ) { + if ( !isXML ) { + elem.sizcache = doneName; + elem.sizset = i; + } + if ( typeof cur !== "string" ) { + if ( elem === cur ) { + match = true; + break; + } + + } else if ( Sizzle.filter( cur, [elem] ).length > 0 ) { + match = elem; + break; + } + } + + elem = elem[dir]; + } + + checkSet[i] = match; + } + } +} + +var contains = document.compareDocumentPosition ? function(a, b){ + return a.compareDocumentPosition(b) & 16; +} : function(a, b){ + return a !== b && (a.contains ? a.contains(b) : true); +}; + +var isXML = function(elem){ + return elem.nodeType === 9 && elem.documentElement.nodeName !== "HTML" || + !!elem.ownerDocument && elem.ownerDocument.documentElement.nodeName !== "HTML"; +}; + +var posProcess = function(selector, context){ + var tmpSet = [], later = "", match, + root = context.nodeType ? [context] : context; + + // Position selectors must be done after the filter + // And so must :not(positional) so we move all PSEUDOs to the end + while ( (match = Expr.match.PSEUDO.exec( selector )) ) { + later += match[0]; + selector = selector.replace( Expr.match.PSEUDO, "" ); + } + + selector = Expr.relative[selector] ? selector + "*" : selector; + + for ( var i = 0, l = root.length; i < l; i++ ) { + Sizzle( selector, root[i], tmpSet ); + } + + return Sizzle.filter( later, tmpSet ); +}; + +// EXPOSE + +window.Sizzle = Sizzle; + +})(); diff --git a/ext/sizzle/selector_engine.js b/ext/sizzle/selector_engine.js new file mode 100644 index 0000000..f460f33 --- /dev/null +++ b/ext/sizzle/selector_engine.js @@ -0,0 +1,34 @@ +Prototype._original_property = window.Sizzle; +//= require "repository/sizzle" + +Prototype.Selector = (function(engine) { + function extend(elements) { + for (var i = 0, length = elements.length; i < length; i++) { + Element.extend(elements[i]); + } + return elements; + } + + function select(selector, scope) { + return extend(engine(selector, scope || document)); + } + + function match(element, selector) { + return engine.matches(selector, [element]).length == 1; + } + + function filter(elements, selector) { + return extend(engine.matches(selector, elements)); + } + + return { + engine: engine, + select: select, + match: match, + filter: filter + }; +})(Sizzle); + +// Restore globals. +window.Sizzle = Prototype._original_property; +delete Prototype._original_property;