diff --git a/.gitmodules b/.gitmodules index 26e3784..ee65a40 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,3 +11,10 @@ path = vendor/sprockets url = git://github.com/sstephenson/sprockets.git + +[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/CHANGELOG b/CHANGELOG index 2d4e595..326ba74 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,3 +1,15 @@ +* Avoid object creation and an unnecessary function call in `Class#addMethods`, when working around JScript DontEnum bug. Replace with feature test and a simple boolean check at runtime. (kangax) + +* Optimize Element#immediateDescendants. (kangax, Tobie Langel) + +* Remove unnecessary function object creation and `Number#times` in `Element._getContentFromAnonymousElement`. (kangax) + +* Eliminate runtime forking and long method lookup in `Element.hasAttribute`. (kangax) + +* Remove redundant ternary. (kangax) + +* Avoid repeating declaration statements where it makes sense, for slightly better runtime performance and minification. (kangax) + * Make `Event.stopObserving` return element in all cases. [#810 state:resolved] (Yaffle, Tobie Langel) * String#startsWith, String#endsWith performance optimization (Yaffle, Tobie Langel, kangax) diff --git a/Rakefile b/Rakefile index 60889c5..b6b3a37 100755 --- a/Rakefile +++ b/Rakefile @@ -13,24 +13,63 @@ 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 + + def self.has_git? + begin + `git --version` + return true + rescue Error => e + return false + end + end - def self.sprocketize(path, source, destination = nil, strip_comments = true) + 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(options = {}) + options = { + :destination => File.join(DIST_DIR, options[:source]), + :strip_comments => true + }.merge(options) + require_sprockets + load_path = [SRC_DIR] + + if selector_path = get_selector_engine(options[:selector_engine]) + load_path << selector_path + end + secretary = Sprockets::Secretary.new( - :root => File.join(ROOT_DIR, path), - :load_path => [SRC_DIR], - :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) + 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'] || DEFAULT_SELECTOR_ENGINE, + :strip_comments => false + ) rm_rf DOC_DIR PDoc::Runner.new(temp_path, { @@ -58,17 +97,44 @@ module PrototypeHelper require_submodule('CajaBuilder', 'caja_builder') end + def self.get_selector_engine(name) + return if name == DEFAULT_SELECTOR_ENGINE || !name + submodule_path = File.join(ROOT_DIR, "vendor", name) + return submodule_path if File.exist?(File.join(submodule_path, "repository", ".git")) + + get_submodule('the required selector engine', "#{name}/repository") + unless File.exist?(submodule_path) + puts "The selector engine you required isn't available at vendor/#{name}.\n\n" + exit + 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 vendor/#{path}" - 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" @@ -78,15 +144,15 @@ 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] desc "Builds the distribution." task :dist do - PrototypeHelper.sprocketize("src", "prototype.js") + PrototypeHelper.sprocketize( + :path => 'src', + :source => 'prototype.js', + :selector_engine => ENV['SELECTOR_ENGINE'] || PrototypeHelper::DEFAULT_SELECTOR_ENGINE + ) end namespace :doc do @@ -104,7 +170,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| diff --git a/ext/update_helper/prototype_update_helper.html b/ext/update_helper/prototype_update_helper.html index b2d355d..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() { @@ -280,6 +285,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..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([ @@ -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/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 ''; } } }, diff --git a/src/ajax/response.js b/src/ajax/response.js index 4fd9baa..658df87 100644 --- a/src/ajax/response.js +++ b/src/ajax/response.js @@ -64,14 +64,14 @@ Ajax.Response = Class.create({ var transport = this.transport = request.transport, readyState = this.readyState = transport.readyState; - if((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { + if ((readyState > 2 && !Prototype.Browser.IE) || readyState == 4) { this.status = this.getStatus(); this.statusText = this.getStatusText(); this.responseText = String.interpret(transport.responseText); this.headerJSON = this._getHeaderJSON(); } - if(readyState == 4) { + if (readyState == 4) { var xml = transport.responseXML; this.responseXML = Object.isUndefined(xml) ? null : xml; this.responseJSON = this._getResponseJSON(); 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.js b/src/dom.js index f9ff3ff..1fb0187 100644 --- a/src/dom.js +++ b/src/dom.js @@ -19,8 +19,15 @@ * **/ +/** section: DOM + * Prototype + * + * The Prototype namespace. + * +**/ //= require "dom/dom" +//= require //= require "dom/selector" //= require "dom/form" //= require "dom/event" diff --git a/src/dom/dom.js b/src/dom/dom.js index 0f0e247..6ab0241 100644 --- a/src/dom/dom.js +++ b/src/dom/dom.js @@ -115,9 +115,9 @@ if (!Node.ELEMENT_NODE) { // setAttribute is broken in IE (particularly when setting name attribute) // see: http://msdn.microsoft.com/en-us/library/ms536389.aspx var SETATTRIBUTE_IGNORES_NAME = (function(){ - var elForm = document.createElement("form"); - var elInput = document.createElement("input"); - var root = document.documentElement; + var elForm = document.createElement("form"), + elInput = document.createElement("input"), + root = document.documentElement; elInput.setAttribute("name", "test"); elForm.appendChild(elInput); root.appendChild(elForm); @@ -443,8 +443,9 @@ Element.Methods = { element = $(element); var result = '<' + element.tagName.toLowerCase(); $H({'id': 'id', 'className': 'class'}).each(function(pair) { - var property = pair.first(), attribute = pair.last(); - var value = (element[property] || '').toString(); + var property = pair.first(), + attribute = pair.last(), + value = (element[property] || '').toString(); if (value) result += ' ' + attribute + '=' + value.inspect(true); }); return result + '>'; @@ -458,12 +459,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; }, @@ -535,10 +542,14 @@ Element.Methods = { * **This method is deprecated, please see [[Element.childElements]]**. **/ immediateDescendants: function(element) { - if (!(element = $(element).firstChild)) return []; - while (element && element.nodeType != 1) element = element.nextSibling; - if (element) return [element].concat($(element).nextSiblings()); - return []; + var results = [], child = $(element).firstChild; + while (child) { + if (child.nodeType === 1) { + results.push(Element.extend(child)); + } + child = child.nextSibling; + } + return results; }, /** @@ -547,7 +558,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'); }, @@ -579,9 +590,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); }, /** @@ -599,7 +611,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.find(ancestors, expression, index); }, /** @@ -631,10 +643,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 Prototype.Selector.find(element.previousSiblings(), expression, index); + } else { + return element.recursivelyCollect("previousSibling", index + 1)[index]; + } }, /** @@ -649,23 +665,29 @@ 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 Prototype.Selector.find(element.nextSiblings(), expression, index); + } else { + var maximumLength = Object.isNumber(index) ? index + 1 : 1; + return element.recursivelyCollect("nextSibling", index + 1)[index]; + } }, /** - * 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); }, /** @@ -699,8 +721,9 @@ Element.Methods = { * // -> [li#lon, li#tok] **/ 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); }, /** @@ -1055,16 +1078,16 @@ Element.Methods = { // All *Width and *Height properties give 0 on elements with display none, // so enable the element temporarily - var els = element.style; - var originalVisibility = els.visibility; - var originalPosition = els.position; - var originalDisplay = els.display; + var els = element.style, + originalVisibility = els.visibility, + originalPosition = els.position, + originalDisplay = els.display; els.visibility = 'hidden'; if (originalPosition != 'fixed') // Switching fixed to absolute causes issues in Safari els.position = 'absolute'; els.display = 'block'; - var originalWidth = element.clientWidth; - var originalHeight = element.clientHeight; + var originalWidth = element.clientWidth, + originalHeight = element.clientHeight; els.display = originalDisplay; els.position = originalPosition; els.visibility = originalVisibility; @@ -1209,11 +1232,11 @@ Element.Methods = { element = $(element); if (Element.getStyle(element, 'position') == 'absolute') return element; - var offsets = Element.positionedOffset(element); - var top = offsets[1]; - var left = offsets[0]; - var width = element.clientWidth; - var height = element.clientHeight; + var offsets = Element.positionedOffset(element), + top = offsets[1], + left = offsets[0], + width = element.clientWidth, + height = element.clientHeight; element._originalLeft = left - parseFloat(element.style.left || 0); element._originalTop = top - parseFloat(element.style.top || 0); @@ -1241,8 +1264,8 @@ Element.Methods = { if (Element.getStyle(element, 'position') == 'relative') return element; element.style.position = 'relative'; - var top = parseFloat(element.style.top || 0) - (element._originalTop || 0); - var left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); + var top = parseFloat(element.style.top || 0) - (element._originalTop || 0), + left = parseFloat(element.style.left || 0) - (element._originalLeft || 0); element.style.top = top + 'px'; element.style.left = left + 'px'; @@ -1310,9 +1333,10 @@ Element.Methods = { * as properties: `{ left: leftValue, top: topValue }`. **/ viewportOffset: function(forElement) { - var valueT = 0, valueL = 0; - - var element = forElement; + var valueT = 0, + valueL = 0, + element = forElement; + do { valueT += element.offsetTop || 0; valueL += element.offsetLeft || 0; @@ -1403,12 +1427,11 @@ Element.Methods = { // find page position of source source = $(source); - var p = Element.viewportOffset(source); + var p = Element.viewportOffset(source), delta = [0, 0], parent = null; // find coordinate system to use element = $(element); - var delta = [0, 0]; - var parent = null; + // delta [0,0] will do fine with position: fixed elements, // position:absolute needs offsetParent deltas if (Element.getStyle(element, 'position') == 'absolute') { @@ -1618,10 +1641,9 @@ else if (Prototype.Browser.IE) { Element._attributeTranslations = (function(){ - var classProp = 'className'; - var forProp = 'for'; - - var el = document.createElement('div'); + var classProp = 'className', + forProp = 'for', + el = document.createElement('div'); // try "className" first (IE <8) el.setAttribute(classProp, 'x'); @@ -1666,10 +1688,9 @@ else if (Prototype.Browser.IE) { }, _getEv: (function(){ - var el = document.createElement('div'); + var el = document.createElement('div'), f; el.onclick = Prototype.emptyFunction; var value = el.getAttribute('onclick'); - var f; // IE<8 if (String(value).indexOf('{') > -1) { @@ -1803,7 +1824,7 @@ else if (Prototype.Browser.WebKit) { (value < 0.00001) ? 0 : value; if (value == 1) - if(element.tagName.toUpperCase() == 'IMG' && element.width) { + if (element.tagName.toUpperCase() == 'IMG' && element.width) { element.width++; element.width--; } else try { var n = document.createTextNode(' '); @@ -1846,8 +1867,8 @@ if ('outerHTML' in document.documentElement) { var parent = element.parentNode, tagName = parent.tagName.toUpperCase(); if (Element._insertionTranslations.tags[tagName]) { - var nextSibling = element.next(); - var fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); + var nextSibling = element.next(), + fragments = Element._getContentFromAnonymousElement(tagName, content.stripScripts()); parent.removeChild(element); if (nextSibling) fragments.each(function(node) { parent.insertBefore(node, nextSibling) }); @@ -1869,11 +1890,17 @@ Element._returnOffset = function(l, t) { }; Element._getContentFromAnonymousElement = function(tagName, html) { - var div = new Element('div'), t = Element._insertionTranslations.tags[tagName]; + var div = new Element('div'), + t = Element._insertionTranslations.tags[tagName]; if (t) { div.innerHTML = t[0] + html + t[1]; - t[2].times(function() { div = div.firstChild }); - } else div.innerHTML = html; + for (var i = t[2]; i--; ) { + div = div.firstChild; + } + } + else { + div.innerHTML = html; + } return $A(div.childNodes); }; @@ -1962,8 +1989,8 @@ Element.extend = (function() { if (typeof window.Element != 'undefined') { var proto = window.Element.prototype; if (proto) { - var id = '_' + (Math.random()+'').slice(2); - var el = document.createElement(tagName); + var id = '_' + (Math.random()+'').slice(2), + el = document.createElement(tagName); proto[id] = 'x'; var isBuggy = (el[id] !== 'x'); delete proto[id]; @@ -2037,10 +2064,14 @@ Element.extend = (function() { return extend; })(); -Element.hasAttribute = function(element, attribute) { - if (element.hasAttribute) return element.hasAttribute(attribute); - return Element.Methods.Simulated.hasAttribute(element, attribute); -}; +if (document.documentElement.hasAttribute) { + Element.hasAttribute = function(element, attribute) { + return element.hasAttribute(attribute); + }; +} +else { + Element.hasAttribute = Element.Methods.Simulated.hasAttribute; +} /** * Element.addMethods(methods) -> undefined @@ -2228,8 +2259,9 @@ Element.addMethods = function(methods) { klass = 'HTML' + tagName.capitalize() + 'Element'; if (window[klass]) return window[klass]; - var element = document.createElement(tagName); - var proto = element['__proto__'] || element.constructor.prototype; + var element = document.createElement(tagName), + proto = element['__proto__'] || element.constructor.prototype; + element = null; return proto; } diff --git a/src/dom/event.js b/src/dom/event.js index 0d86e11..8158eff 100644 --- a/src/dom/event.js +++ b/src/dom/event.js @@ -149,10 +149,14 @@ 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; + } } - + /** * Event.pointer(@event) -> Object * @@ -379,12 +383,12 @@ window.addEventListener('unload', Prototype.emptyFunction, false); - var _getDOMEventName = Prototype.K; + var _getDOMEventName = Prototype.K, + translations = { mouseenter: "mouseover", mouseleave: "mouseout" }; if (!MOUSEENTER_MOUSELEAVE_EVENTS_SUPPORTED) { _getDOMEventName = function(eventName) { - var translations = { mouseenter: "mouseover", mouseleave: "mouseout" }; - return eventName in translations ? translations[eventName] : eventName; + return (translations[eventName] || eventName); }; } diff --git a/src/dom/selector.js b/src/dom/selector.js index 7692a62..c327603 100644 --- a/src/dom/selector.js +++ b/src/dom/selector.js @@ -1,867 +1,66 @@ -/* 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. */ - -/** section: DOM - * class Selector - * - * 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(); - - 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 - * be descendants of this node. - * - * 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); - } - }, - - /** - * 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"; - } -}); - -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...] - * - * 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) { - 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; - }, - - /** - * 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.isNumber(expression)) { - index = expression; expression = false; - } - return Selector.matchElements(elements, expression || '*')[index || 0]; - }, - - /** - * Selector.findChildElements(element, expressions) -> [Element...] - * - * Searches beneath `element` for any elements that match the 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; - } -}); - -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 +/** related to: Prototype.Selector * $$(expression...) -> [Element...] * * Returns all elements in the document that match the provided CSS selectors. **/ -function $$() { - return Selector.findChildElements(document, $A(arguments)); + +window.$$ = function() { + var expression = $A(arguments).join(', '); + return Prototype.Selector.select(expression, document); +}; + +/** + * 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.find(elements, expression[, index]) -> Element + * - elements (Enumerable): a collection of DOM elements. + * - expression (String): A CSS selector. + # - index: Numeric index of the match to return, or 0 if omitted. + * + * Filters the given collection of elements with `expression` and returns the + * first matching element (or the `index`th matching element if `index` is + * specified). +**/ +if (!Prototype.Selector.find) { + Prototype.Selector.find = function(elements, expression, index) { + if (Object.isUndefined(index)) index = 0; + var match = Prototype.Selector.match, length = elements.length, matchIndex = 0, i; + + for (i = 0; i < length; i++) { + if (match(elements[i], expression) && index == matchIndex++) { + return Element.extend(elements[i]); + } + } + } } + diff --git a/src/lang/class.js b/src/lang/class.js index 68b1fe6..b277e2b 100644 --- a/src/lang/class.js +++ b/src/lang/class.js @@ -9,6 +9,17 @@ * inheritance](http://prototypejs.org/learn/class-inheritance). **/ var Class = (function() { + + // Some versions of JScript fail to enumerate over properties, names of which + // correspond to non-enumerable properties in the prototype chain + var IS_DONTENUM_BUGGY = (function(){ + for (var p in { toString: 1 }) { + // check actual property name, so that it works with augmented Object.prototype + if (p === 'toString') return false; + } + return true; + })(); + /** * Class.create([superclass][, methods...]) -> Class * - superclass (Class): The optional superclass to inherit methods from. @@ -59,7 +70,7 @@ var Class = (function() { parent.subclasses.push(klass); } - for (var i = 0; i < properties.length; i++) + for (var i = 0, length = properties.length; i < length; i++) klass.addMethods(properties[i]); if (!klass.prototype.initialize) @@ -135,12 +146,13 @@ var Class = (function() { * //-> alerts "You should probably run. He looks really mad." **/ function addMethods(source) { - var ancestor = this.superclass && this.superclass.prototype; - var properties = Object.keys(source); + var ancestor = this.superclass && this.superclass.prototype, + properties = Object.keys(source); - // IE6 doesn't enumerate toString and valueOf properties, - // Force copy if they're not coming from Object.prototype. - if (!Object.keys({ toString: true }).length) { + // IE6 doesn't enumerate `toString` and `valueOf` (among other built-in `Object.prototype`) properties, + // Force copy if they're not Object.prototype ones. + // Do not copy other Object.prototype.* for performance reasons + if (IS_DONTENUM_BUGGY) { if (source.toString != Object.prototype.toString) properties.push("toString"); if (source.valueOf != Object.prototype.valueOf) @@ -150,7 +162,7 @@ var Class = (function() { for (var i = 0, length = properties.length; i < length; i++) { var property = properties[i], value = source[property]; if (ancestor && Object.isFunction(value) && - value.argumentNames().first() == "$super") { + value.argumentNames()[0] == "$super") { var method = value; value = (function(m) { return function() { return ancestor[m].apply(this, arguments); }; diff --git a/src/lang/string.js b/src/lang/string.js index 979c7a7..a7c0334 100644 --- a/src/lang/string.js +++ b/src/lang/string.js @@ -167,8 +167,8 @@ Object.extend(String.prototype, (function() { * returns them as an array of strings. **/ function extractScripts() { - var matchAll = new RegExp(Prototype.ScriptFragment, 'img'); - var matchOne = new RegExp(Prototype.ScriptFragment, 'im'); + var matchAll = new RegExp(Prototype.ScriptFragment, 'img'), + matchOne = new RegExp(Prototype.ScriptFragment, 'im'); return (this.match(matchAll) || []).map(function(scriptTag) { return (scriptTag.match(matchOne) || ['', ''])[1]; }); @@ -251,8 +251,9 @@ Object.extend(String.prototype, (function() { return match[1].split(separator || '&').inject({ }, function(hash, pair) { if ((pair = pair.split('='))[0]) { - var key = decodeURIComponent(pair.shift()); - var value = pair.length > 1 ? pair.join('=') : pair[0]; + var key = decodeURIComponent(pair.shift()), + value = pair.length > 1 ? pair.join('=') : pair[0]; + if (value != undefined) value = decodeURIComponent(value); if (key in hash) { @@ -482,7 +483,7 @@ Object.extend(String.prototype, (function() { truncate: truncate, // Firefox 3.5+ supports String.prototype.trim // (`trim` is ~ 5x faster than `strip` in FF3.5) - strip: String.prototype.trim ? String.prototype.trim : strip, + strip: String.prototype.trim || strip, stripTags: stripTags, stripScripts: stripScripts, extractScripts: extractScripts, diff --git a/src/lang/template.js b/src/lang/template.js index cb9020d..8b396a8 100644 --- a/src/lang/template.js +++ b/src/lang/template.js @@ -133,8 +133,9 @@ var Template = Class.create({ var before = match[1] || ''; if (before == '\\') return match[2]; - var ctx = object, expr = match[3]; - var pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + var ctx = object, expr = match[3], + pattern = /^([^.[]+|\[((?:.*?[^\\])?)\])(\.|\[|$)/; + match = pattern.exec(expr); if (match == null) return before; diff --git a/src/prototype.js b/src/prototype.js index 21089a4..5f78f78 100644 --- a/src/prototype.js +++ b/src/prototype.js @@ -35,9 +35,9 @@ var Prototype = { if (typeof window.HTMLDivElement !== 'undefined') return true; - var div = document.createElement('div'); - var form = document.createElement('form'); - var isSupported = false; + var div = document.createElement('div'), + form = document.createElement('form'), + isSupported = false; if (div['__proto__'] && (div['__proto__'] !== form['__proto__'])) { isSupported = true; diff --git a/src/selector_engine.js b/src/selector_engine.js new file mode 100644 index 0000000..45ba30c --- /dev/null +++ b/src/selector_engine.js @@ -0,0 +1,29 @@ +Prototype._original_property = window.Sizzle; +//= require "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; + } + + return { + engine: engine, + select: select, + match: match + }; +})(Sizzle); + +// Restore globals. +window.Sizzle = Prototype._original_property; +delete Prototype._original_property; diff --git a/src/sizzle.js b/src/sizzle.js new file mode 100644 index 0000000..801b731 --- /dev/null +++ b/src/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/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/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..da428b0 --- /dev/null +++ b/test/unit/selector_engine_test.js @@ -0,0 +1,50 @@ +/* +
+
+
+
+*/ + +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')); + }, + + testFind: function() { + var elements = document.getElementsByTagName('*'), + expression = '.test_class'; + this.assertEqual('test_div_parent', Prototype.Selector.find(elements, expression).id); + this.assertEqual('test_div_child', Prototype.Selector.find(elements, expression, 1).id); + } +}); \ No newline at end of file diff --git a/test/unit/selector_test.js b/test/unit/selector_test.js index fc2eae2..633b82b 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'), @@ -412,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 diff --git a/vendor/legacy_selector/repository/legacy_selector.js b/vendor/legacy_selector/repository/legacy_selector.js new file mode 100644 index 0000000..0253d4c --- /dev/null +++ b/vendor/legacy_selector/repository/legacy_selector.js @@ -0,0 +1,803 @@ +/* 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.LegacySelector = 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.LegacySelector.CASE_INSENSITIVE_CLASS_NAMES) return false; + + 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.LegacySelector._div.querySelector(this.expression); + } catch(e) { + return false; + } + + return true; + }, + + compileMatcher: function() { + 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.LegacySelector._cache[e]) { + this.matcher = Prototype.LegacySelector._cache[e]; + return; + } + + this.matcher = ["this.matcher = function(root) {", + "var r = root, h = Prototype.LegacySelector.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.LegacySelector.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.LegacySelector, { + _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.LegacySelector.xpath.operators[m[2]]).evaluate(m); + }, + pseudo: function(m) { + var h = Prototype.LegacySelector.xpath.pseudos[m[1]]; + if (!h) return ''; + if (Object.isFunction(h)) return h(m); + return new Template(Prototype.LegacySelector.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.LegacySelector.patterns, + x = Prototype.LegacySelector.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.LegacySelector.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.LegacySelector.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.LegacySelector.handlers.unmark(results); + }, + + // COMBINATOR FUNCTIONS + descendant: function(nodes) { + 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.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); + } + 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.LegacySelector.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.LegacySelector.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.LegacySelector.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.LegacySelector.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.LegacySelector.handlers.byClassName(nodes, root, className); + }, + + byClassName: function(nodes, root, className) { + 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; + 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.LegacySelector.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.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.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.LegacySelector.handlers.nextElementSibling(node)) continue; + results.push(node); + } + return results; + }, + 'only-child': function(nodes, value, root) { + 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.LegacySelector.pseudos.nth(nodes, formula, root); + }, + 'nth-last-child': function(nodes, formula, root) { + return Prototype.LegacySelector.pseudos.nth(nodes, formula, root, true); + }, + 'nth-of-type': function(nodes, formula, root) { + return Prototype.LegacySelector.pseudos.nth(nodes, formula, root, false, true); + }, + 'nth-last-of-type': function(nodes, formula, root) { + return Prototype.LegacySelector.pseudos.nth(nodes, formula, root, true, true); + }, + 'first-of-type': function(nodes, formula, root) { + return Prototype.LegacySelector.pseudos.nth(nodes, "1", root, false, true); + }, + 'last-of-type': function(nodes, formula, root) { + return Prototype.LegacySelector.pseudos.nth(nodes, "1", root, true, true); + }, + 'only-of-type': function(nodes, formula, root) { + var p = Prototype.LegacySelector.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.LegacySelector.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.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); + } + } + 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.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); + 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.LegacySelector.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.LegacySelector.matchElements(elements, expression || '*')[index || 0]; + }, + + findChildElements: function(element, expressions) { + 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.LegacySelector(expressions[i].strip()); + h.concat(results, selector.findElements(element)); + } + return (l > 1) ? h.unique(results) : results; + } +}); + +if (Prototype.Browser.IE) { + Object.extend(Prototype.LegacySelector.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/selector_engine.js b/vendor/legacy_selector/selector_engine.js new file mode 100644 index 0000000..cf62875 --- /dev/null +++ b/vendor/legacy_selector/selector_engine.js @@ -0,0 +1,18 @@ +//= require "repository/legacy_selector" + +Prototype.Selector = (function(engine) { + function select(selector, scope) { + return engine.findChildElements(scope || document, [selector]); + } + + function match(element, selector) { + return !!engine.findElement([element], selector); + } + + return { + engine: engine, + select: select, + match: match, + filter: engine.matchElements + }; +})(Prototype.LegacySelector); diff --git a/vendor/nwmatcher/repository b/vendor/nwmatcher/repository new file mode 160000 index 0000000..c9f5d5d --- /dev/null +++ b/vendor/nwmatcher/repository @@ -0,0 +1 @@ +Subproject commit c9f5d5d4fc4ca294477f803bb8d688a8d45de664 diff --git a/vendor/nwmatcher/selector_engine.js b/vendor/nwmatcher/selector_engine.js new file mode 100644 index 0000000..2d55a14 --- /dev/null +++ b/vendor/nwmatcher/selector_engine.js @@ -0,0 +1,18 @@ +Prototype._original_property = window.NW; +//= require "repository/src/nwmatcher" + +Prototype.Selector = (function(engine) { + function select(selector, scope) { + return engine.select(selector, scope || document, null, Element.extend); + } + + return { + engine: engine, + select: select, + match: engine.match + }; +})(NW.Dom); + +// Restore globals. +window.NW = Prototype._original_property; +delete Prototype._original_property; \ No newline at end of file diff --git a/vendor/sizzle/repository b/vendor/sizzle/repository new file mode 160000 index 0000000..415e466 --- /dev/null +++ b/vendor/sizzle/repository @@ -0,0 +1 @@ +Subproject commit 415e466f70e5a53f589161b1f2944e5485007409 diff --git a/vendor/sizzle/selector_engine.js b/vendor/sizzle/selector_engine.js new file mode 100644 index 0000000..f460f33 --- /dev/null +++ b/vendor/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;