diff --git a/.gitmodules b/.gitmodules index 00dca52..61fd316 100644 --- a/.gitmodules +++ b/.gitmodules @@ -11,6 +11,6 @@ path = vendor/sprockets url = git://github.com/sstephenson/sprockets.git -[submodule "vendor/sizzle"] - path = vendor/sizzle +[submodule "vendor/sizzle/sizzle"] + path = vendor/sizzle/sizzle url = git://github.com/jeresig/sizzle.git diff --git a/Rakefile b/Rakefile index cff99f8..2b36e08 100755 --- a/Rakefile +++ b/Rakefile @@ -42,7 +42,7 @@ module PrototypeHelper require_sizzle secretary = Sprockets::Secretary.new( :root => File.join(ROOT_DIR, path), - :load_path => [SRC_DIR, SIZZLE_DIR], + :load_path => self.load_path, :source_files => [source], :strip_comments => strip_comments ) @@ -51,6 +51,18 @@ module PrototypeHelper secretary.concatenation.save_to(destination) end + def self.load_path + selector = ENV['SELECTOR_ENGINE'] || 'sizzle' + selector_path = File.join(ROOT_DIR, 'vendor', selector) + if File.exists?(selector_path) + [SRC_DIR, selector_path] + else + puts "\nYou seem to be missing vendor/#{selector}." + puts "Built Prototype using Sizzle instead.\n\n" + [SRC_DIR, SIZZLE_DIR] + end + end + def self.build_doc_for(file) mkdir_p TMP_DIR temp_path = File.join(TMP_DIR, "prototype.temp.js") @@ -83,8 +95,8 @@ module PrototypeHelper end def self.require_sizzle - if !File.exists?(File.join(SIZZLE_DIR, 'sizzle.js')) - exit unless get_submodule("Sizzle", "sizzle") + if !File.exists?(File.join(SIZZLE_DIR, 'sizzle', 'sizzle.js')) + exit unless get_submodule("Sizzle", "sizzle/sizzle") end end diff --git a/src/dom.js b/src/dom.js index 595ce17..1d9fdf3 100644 --- a/src/dom.js +++ b/src/dom.js @@ -21,7 +21,7 @@ //= require "dom/dom" -//= require "dom/sizzle_adapter" +//= require //= require "dom/selector" //= require "dom/form" //= require "dom/event" diff --git a/vendor/nwmatcher/nwmatcher-1.1.1.js b/vendor/nwmatcher/nwmatcher-1.1.1.js new file mode 100644 index 0000000..82bc547 --- /dev/null +++ b/vendor/nwmatcher/nwmatcher-1.1.1.js @@ -0,0 +1,1441 @@ +/* + * Copyright (C) 2007-2009 Diego Perini + * All rights reserved. + * + * nwmatcher.js - A fast CSS selector engine and matcher + * + * Author: Diego Perini + * Version: 1.1.1 + * Created: 20070722 + * Release: 20090516 + * + * License: + * http://javascript.nwbox.com/NWMatcher/MIT-LICENSE + * Download: + * http://javascript.nwbox.com/NWMatcher/nwmatcher.js + */ + +window.NW || (window.NW = {}); + +NW.Dom = function(global) { + + var version = 'nwmatcher-1.1.1', + + // processing context + base = global.document, + + // script loading context + context = global.document, + + // context root element (HTML) + root = context.documentElement, + + // current DOM viewport/window, also used to + // detect Safari 2.0.x [object AbstractView] + view = base.defaultView || base.parentWindow, + + // cache access to native slice + slice = Array.prototype.slice, + + /* BEGIN FEATURE TESTING */ + + // detect native method in object + // not same scope of isHostObject + isNative = function(object, method) { + return object && method in object && + typeof object[method] != 'string' && + // IE/W3C browsers will return [native code] + // Safari 2.0.x and older will return [function] + (/\{\s*\[native code[^\]]*\]\s*\}|^\[function\]$/). + test(object[method]); + }, + + // NOTE: NATIVE_XXXXX check for existance of method only + // so through the code read it as "supported", maybe BUGGY + + // detect native getAttribute/hasAttribute methods, + // frameworks extend these to elements, but it seems + // this does not work for XML namespaced attributes, + // used to check both getAttribute/hasAttribute in IE + NATIVE_HAS_ATTRIBUTE = isNative(root, 'hasAttribute'), + + // detect if DOM methods are native in browsers + NATIVE_QSAPI = isNative(context, 'querySelector'), + NATIVE_GEBID = isNative(context, 'getElementById'), + NATIVE_GEBTN = isNative(root, 'getElementsByTagName'), + NATIVE_GEBCN = isNative(root, 'getElementsByClassName'), + + // get name of best children collection property available + // detect Safari 2.0.x different children implementation + NATIVE_CHILDREN = + 'children' in root ? + (view && global !== view ? + 'childNodes' : + 'children') : + 'childNodes', + + // nodeList can be converted by native .slice() + // Opera 9.27 and an id="length" will fold this + NATIVE_SLICE_PROTO = + (function() { + var isBuggy = false, div = context.createElement('div'); + try { + div.innerHTML = '
'; + root.insertBefore(div, root.firstChild); + isBuggy = !!slice.call(div.childNodes, 0)[0]; + } catch(e) { + } finally { + root.removeChild(div).innerHTML = ''; + } + return isBuggy; + })(), + + // check for Mutation Events, DOMAttrModified should be + // enough to ensure DOMNodeInserted/DOMNodeRemoved exist + NATIVE_MUTATION_EVENTS = root.addEventListener ? + (function() { + var isBuggy, id = root.id, + handler = function() { + root.removeEventListener('DOMAttrModified', handler, false); + NATIVE_MUTATION_EVENTS = true; + root.id = id; + }; + root.addEventListener('DOMAttrModified', handler, false); + // now modify an attribute + root.id = 'nw'; + isBuggy = root.id != 'nw'; + root.id = id; + handler = null; + return isBuggy; + })() : + false, + + // NOTE: BUGGY_XXXXX check both for existance and no known bugs, + // so through the code read it as "not supported", or "undefined" + + BUGGY_GEBID = NATIVE_GEBID ? + (function() { + var isBuggy, div = context.createElement('div'); + div.innerHTML = ''; + root.insertBefore(div, root.firstChild); + isBuggy = !!div.ownerDocument.getElementById('Z'); + root.removeChild(div); + div = null; + return isBuggy; + })() : + true, + + // detect IE gEBTN comment nodes bug + BUGGY_GEBTN = NATIVE_GEBTN ? + (function() { + var isBuggy, div = context.createElement('div'); + div.appendChild(context.createComment('')); + div = div.getElementsByTagName('*')[0]; + isBuggy = !!(div && div.nodeType == 8); + div = null; + return isBuggy; + })() : + true, + + // detect Opera gEBCN second class and/or UTF8 bugs + // test is taken from the jQuery selector test suite + BUGGY_GEBCN = NATIVE_GEBCN ? + (function() { + var isBuggy, div = context.createElement('div'); + div.innerHTML = ''; + isBuggy = !div.getElementsByClassName('台北')[0]; + div = null; + return isBuggy; + })() : + true, + + // check Seletor API implementations + BUGGY_QSAPI = NATIVE_QSAPI ? (function() { + var isBuggy, pattern = [], div = context.createElement('div'); + + // WebKit case sensitivity bug with className (when no DOCTYPE) + // https://bugs.webkit.org/show_bug.cgi?id=19047 + div.innerHTML = ''; + if (context.compatMode == 'BackCompat' && div.querySelector('.x') !== null) { + return { 'test': function() { return true; } }; + } + + // check :enabled :disabled bugs with hidden fields (Firefox 3.5 QSA bug) + // http://www.w3.org/TR/html5/interactive-elements.html#selector-enabled + div.innerHTML = ''; + // IE8 throws error with these pseudos + try { + isBuggy = div.querySelectorAll(':enabled').length === 1; + } catch(e) { } + isBuggy && pattern.push(':enabled', ':disabled'); + + // check :link bugs with hyperlinks matching (Firefox/Safari) + div.innerHTML = ''; + div.querySelectorAll(':link').length !== 1 && pattern.push(':link'); + + return pattern.length + ? new RegExp(pattern.join('|')) + : { 'test': function() { return false; } }; + })() : + true, + + /* END FEATURE TESTING */ + + // map of attribute names (in HTML and DOM namespaces) + // many are missing here, or maybe there are too many + // first two lines will cover most real cases anyway + /* + // we do not have to write attributes and + // we have a fixed internal getAttribute + // maybe we can skip this case juggling + Attributes = { + 'class': 'className', 'for': 'htmlFor', + 'classname': 'className', 'htmlfor': 'htmlFor', + 'tabindex': 'tabIndex', 'accesskey': 'accessKey', 'maxlength': 'maxLength', + 'readonly': 'readOnly', 'longdesc': 'longDesc', 'frameborder': 'frameBorder', + 'ismap': 'isMap', 'usemap': 'useMap', 'nohref': 'noHref', 'nowrap': 'noWrap', + 'colspan': 'colSpan', 'rowspan': 'rowSpan', + 'cellpadding': 'cellPadding', 'cellspacing': 'cellSpacing', + 'marginwidth': 'marginWidth', 'marginheight': 'marginHeight' + }, + */ + + // See Niels Leenheer blog http://rakaz.nl/item/css_selector_bugs_case_sensitivity + // + // Each attribute definition includes information about the case-sensitivity of its values. + // http://www.w3.org/TR/html4/types.html#h-6.1 + // + // HTML 4 and XHTML both have some attributes that have pre-defined and limited sets of values. + // http://www.w3.org/TR/xhtml1/#h-4.11 + + // Safari 2.0.x seems to always treat attributes as in Quirks mode + insensitiveMap = /^CSS/i.test(context.compatMode) || (view && global !== view) ? { + // must be trated case insensitive in both HTML and XHTML (Strict ?) + 'accept': 1, 'accept-charset': 1, 'alink': 1, 'axis': 1, + 'bgcolor': 1, 'charset': 1, 'codetype': 1, 'color': 1, + 'face': 1, 'enctype': 1, 'hreflang': 1, 'http-equiv': 1, + 'lang': 1, 'language': 1, 'link': 1, 'media': 1, 'rel': 1, + 'rev': 1, 'target': 1, 'text': 1, 'type': 1, 'vlink': 1 + } : { + // must be treated case insensitive in HTML (Quirks ?) + 'align': 1, 'checked': 1, 'clear': 1, 'compact': 1, 'declare': 1, + 'defer': 1, 'dir': 1, 'disabled': 1, 'frame': 1, 'method': 1, + 'multiple': 1, 'nohref': 1, 'noresize': 1, 'noshade': 1, 'nowrap': 1, + 'readonly': 1, 'rules': 1, 'scope': 1, 'scrolling': 1, 'selected': 1, + 'shape': 1, 'valign': 1, 'valuetype': 1 + }, + + // attribute referencing URI values need special treatment in IE + attributesURI = { + 'action': 2, 'cite': 2, 'codebase': 2, 'data': 2, 'href': 2, + 'longdesc': 2, 'lowsrc': 2, 'src': 2, 'usemap': 2 + }, + + // selection functions returning collections + compiledSelectors = { }, + + // matching functions returning booleans + compiledMatchers = { }, + + // place to add exotic functionalities + Selectors = { + // as a simple example this will check + // for chars not in standard ascii table + // + // 'mySpecialSelector': { + // 'Expression': /\u0080-\uffff/, + // 'Callback': mySelectorCallback + //} + // + // 'mySelectorCallback' will be invoked + // only after passing all other standard + // checks and only if none of them worked + }, + + // trim leading/trailing whitespaces + trim = /^\s+|\s+$/g, + + // nth pseudo selectors + position = /:(nth|of-type)/, + + // ascii extended + ascii = /\x00-\xff/, + + // http://www.w3.org/TR/css3-syntax/#characters + // unicode/ISO 10646 characters 161 and higher + // encoding = '|[\\u00a1-\\uffff]',// correct + // NOTE: Safari 2.0.x crashes with escaped (\\) + // Unicode ranges in regular expressions so we + // use a negated character range class instead + // NOTE: [^\\w\\W] tested as good replacement + encoding = '|[^\\x00-\\xa0]', + + // selector validator discard invalid chars + validator = new RegExp("([-_*\\w]" + encoding + ")"), + + // split comma separated selector groups, exclude commas inside () [] + // example: (#div a, ul > li a) group 1 is (#div a) group 2 is (ul > li a) + group = /(([^,\(\)\[\]]+|\([^\(\)]+\)|\(.*\)|\[[^\[\]]+\]|\[.*\]|\\.|\*)+)/g, + + // attribute operators + Operators = { + // ! is not really in the specs + // still unit tests have to pass + '!': "%p!=='%m'", + '=': "%p==='%m'", + '^': "%p.indexOf('%m')==0", + '*': "%p.indexOf('%m')>-1", + // sensitivity handled by compiler + // NOTE: working alternative + // '|': "/%m-/i.test(%p+'-')", + '|': "(%p+'-').indexOf('%m-')==0", + '~': "(' '+%p+' ').indexOf(' %m ')>-1", + // precompile in '%m' string length to optimize + // NOTE: working alternative + // '$': "%p.lastIndexOf('%m')==%p.length-'%m'.length" + '$': "%p.substr(%p.length - '%m'.length) === '%m'" + }, + + // optimization expressions + Optimize = { + ID: new RegExp("#((?:[-_\\w]" + encoding + "|\\\\.)+)*"), + TAG: new RegExp("((?:[-_\\w]" + encoding + "|\\\\.)+)*"), + CLASS: new RegExp("\\.((?:[-_\\w]" + encoding + "|\\\\.)+)*"), + // split last, right most, selector group token + TOKEN: /([^\ \>\+\~\,\(\)\[\]]+|\([^\(\)]+\)|\(.*\)|\[[^\[\]]+\]|\[.*\])+/g, + descendants: /[^> \w]/, + siblings: /[^+~\w]/ + }, + + // precompiled Regular Expressions + Patterns = { + // element attribute matcher + attribute: /^\[\s*([-\w]*:?(?:[-\w])+)\s*(?:([!^$*~|]*)?(\=)?\s*(["']*)?([^'"]*?)\4)\s*\](.*)/, + // structural pseudo-classes + spseudos: /^\:(root|empty|nth)?-?(first|last|only)?-?(child)?-?(of-type)?(\((?:even|odd|[^\)]*)\))?(.*)/, + // uistates + dynamic + negation pseudo-classes + dpseudos: /^\:((?:[-\w]|\\.)+)(\(([\x22\x27]*)?(.*?(\(.*?\))?[^(]*?)\3\))?(.*)/, + // E > F + children: /^\s*\>\s*(.*)/, + // E + F + adjacent: /^\s*\+\s*(.*)/, + // E ~ F + relative: /^\s*\~\s*(.*)/, + // E F + ancestor: /^(\s+)(.*)/, + // all + all: /^\*(.*)/, + // id + id: new RegExp("^#((?:[-_\\w]" + encoding + "|\\\\.)+)(.*)"), + // tag + tagName: new RegExp("^((?:[-_\\w]" + encoding + "]\\\\.)+)(.*)"), + // class + className: new RegExp("^\\.((?:[-_\\w]" + encoding + "|\\\\.)+)(.*)") + }, + + // current CSS3 grouping of Pseudo-Classes + // they allowed implementing extensions + // and improve error notifications + CSS3PseudoClasses = { + Structural: { + 'root': 0, 'empty': 0, + 'first-child': 0, 'last-child': 0, 'only-child': 0, + 'first-of-type': 0, 'last-of-type': 0, 'only-of-type': 0, + 'first-child-of-type': 0, 'last-child-of-type': 0, 'only-child-of-type': 0, + 'nth-child': 0, 'nth-last-child': 0, 'nth-of-type': 0, 'nth-last-of-type': 0 + // (the 4rd line is not in W3C CSS specs but is an accepted alias of 3nd line) + }, + // originally separated in different pseudo-classes + // we have grouped them to optimize a bit size+speed + // all are going through the same code path (switch) + // the assigned value represent current spec status: + // 0 = CSS3, 1 = CSS2, 2 = maybe implemented + Others: { + //UIElementStates: { + // we group them to optimize + 'checked': 0, 'disabled': 0, 'enabled': 0, 'selected': 1, 'indeterminate': 2, + //}, + //Dynamic: { + 'active': 0, 'focus': 0, 'hover': 0, 'link': 0, 'visited': 0, + //}, + // Target: { + 'target': 0, + //}, + // Language: { + 'lang': 0, + //}, + // Negation: { + 'not': 0, + //}, + // Content: { + // http://www.w3.org/TR/2001/CR-css3-selectors-20011113/#content-selectors + 'contains': 2 + //} + } + }, + + // conditionals optimizers for the compiler + + // do not change this, it is searched & replaced + ACCEPT_NODE = 'r[X++]=N;continue main;', + + // fix for IE gEBTN('*') returning collection with comment nodes + SKIP_COMMENTS = BUGGY_GEBTN ? 'if(e.nodeType!=1){continue;}' : '', + + // use the textContent or innerText property to check CSS3 :contains + // Safari 2 has a bug with innerText and hidden content, using an + // internal replace on the innerHTML property avoids trashing it + CONTAINS_TEXT = + 'textContent' in root ? + 'e.textContent' : + (function() { + var t = context.createElement('div'); + t.innerHTML = '

p

'; + t.style.display = 'none'; + return t.innerText.length > 0 ? + 'e.innerText' : + 'this.stripTags(e.innerHTML)'; + })(), + + // to check extensions have not yet been registered + // @return boolean + IS_EMPTY = + function(object) { + if (object && typeof object == 'object') { + for (var i in object) { return false; } + return true; + } + return false; + }, + + // compile a comma separated group of selector + // @mode boolean true for select, false for match + // @return function (compiled) + compileGroup = + function(selector, source, mode) { + var i = 0, seen = { }, parts, token; + if ((parts = selector.match(group))) { + // for each selector in the group + for ( ; i < parts.length; ++i) { + token = parts[i].replace(trim, ''); + // avoid repeating the same token + // in comma separated group (p, p) + if (!seen[token]) { + seen[token] = true; + // reset element reference after the + // first comma if using select() mode + if (i > 0) { + source += 'e=N;'; + } + // insert corresponding mode function + if (mode) { + source += compileSelector(token, ACCEPT_NODE); + } else { + source += compileSelector(token, 'return true;'); + } + } + } + } + if (mode) { + // for select method + return new Function('c,s,d,h', 'var k,e,r,n,C,N,T,X=0,x=0;main:for(k=0,r=[];e=N=c[k];k++){' + SKIP_COMMENTS + source + '}return r;'); + } else { + // for match method + return new Function('e,s,d,h', 'var n,C,N=e,T,X=0,x=0;' + source + 'return false;'); + } + }, + + // compile a CSS3 string selector into + // ad-hoc javascript matching function + // @return string (to be compiled) + compileSelector = + function(selector, source) { + + var i, a, b, n, k, expr, match, result, status, test, type; + + k = 0; + + while (selector) { + + // *** Universal selector + // * match all (empty block, do not remove) + if ((match = selector.match(Patterns.all))) { + // do nothing, handled in the compiler where + // BUGGY_GEBTN return comment nodes (ex: IE) + } + // *** ID selector + // #Foo Id case sensitive + else if ((match = selector.match(Patterns.id))) { + // document can contain conflicting elements (id/name) + source = 'if((n=e.getAttributeNode("id"))&&n.value=="' + match[1] + '"){' + source + '}'; + //source = 'if(e.getAttribute("id")=="' + match[1] + '"){' + source + '}'; + //source = 'if(e.id=="' + match[1] + '"){' + source + '}'; + } + // *** Type selector + // Foo Tag (case insensitive) + else if ((match = selector.match(Patterns.tagName))) { + // both tagName and nodeName properties may be upper or lower case + // depending on their creation NAMESPACE in createElementNS() + source = 'T=e.nodeName;if(T=="' + match[1].toUpperCase() + '"||T=="' + match[1].toLowerCase() + '"){' + source + '}'; + //source = 'if(e.nodeName=="' + match[1].toUpperCase() + '"){' + source + '}'; + //source = 'if(/' + match[1] + '/i.test(e.nodeName)){' + source + '}'; + } + // *** Class selector + // .Foo Class (case sensitive) + else if ((match = selector.match(Patterns.className))) { + // W3C CSS3 specs: element whose "class" attribute has been assigned a list of whitespace-separated values + // see section 6.4 Class selectors and notes at the bottom; explicitly non-normative in this specification. + //source = 'if((" "+e.className+" ").replace(/\\s+/g," ").indexOf(" ' + match[1] + ' ")>-1){' + source + '}'; + source = 'C=e.className;if(C&&(" "+C+" ").indexOf(" ' + match[1] + ' ")>-1){' + source + '}'; + } + // *** Attribute selector + // [attr] [attr=value] [attr="value"] [attr='value'] and !=, *=, ~=, |=, ^=, $= + // case sensitivity is treated differently depending on the document type (see map) + else if ((match = selector.match(Patterns.attribute))) { + // xml namespaced attribute ? + expr = match[1].split(':'); + expr = expr.length == 2 ? expr[1] : expr[0] + ''; + // check case treatment from insensitiveMap + if (insensitiveMap[expr.toLowerCase()]) { + match[5] = match[5].toLowerCase(); + } + source = 'if(' + + (Operators[(match[2] || match[3])] || 'this.hasAttribute(e,"' + match[1] + '")'). + replace(/\%p/g, 'this.getAttribute(e,"' + match[1] + '")' + + (expr ? '' : '.toLowerCase()')).replace(/\%m/g, match[5]) + + '){' + source + '}'; + } + // *** Adjacent sibling combinator + // E + F (F adiacent sibling of E) + else if ((match = selector.match(Patterns.adjacent))) { + source = 'while((e=e.previousSibling)){if(e.nodeType==1){' + source + 'break;}}'; + } + // *** General sibling combinator + // E ~ F (F relative sibling of E) + else if ((match = selector.match(Patterns.relative))) { + // previousSibling particularly slow on Gecko based browsers prior to FF3.1 + //source = 'while((e=e.previousSibling)){if(e.nodeType==1){' + source + '}}'; + k++; + source = + 'var N' + k + '=e;e=e.parentNode.firstChild;' + + 'while(e!=N' + k +'){if(e.nodeType==1){' + source + '}e=e.nextSibling;}'; + } + // *** Child combinator + // E > F (F children of E) + else if ((match = selector.match(Patterns.children))) { + source = 'if((e=e.parentNode)&&e.nodeType==1){' + source + '}'; + } + // *** Descendant combinator + // E F (E ancestor of F) + else if ((match = selector.match(Patterns.ancestor))) { + source = 'while((e=e.parentNode)&&e.nodeType==1){' + source + '}'; + } + // *** Structural pseudo-classes + // :root, :empty, + // :first-child, :last-child, :only-child, + // :first-of-type, :last-of-type, :only-of-type, + // :nth-child(), :nth-last-child(), :nth-of-type(), :nth-last-of-type() + else if ((match = selector.match(Patterns.spseudos)) && + selector.match(/([-\w]+)/)[0] in CSS3PseudoClasses.Structural) { + + switch (match[1]) { + case 'root': + // element root of the document + source = 'if(e===h){' + source + '}'; + break; + case 'empty': + // element that has no children + source = 'if(!e.firstChild){' + source + '}'; + break; + default: + type = match[4] == 'of-type' ? 'OfType' : 'Element'; + + if (match[5]) { + // remove the ( ) grabbed above + match[5] = match[5].replace(/\(|\)/g, ''); + if (match[5] == 'even') { + a = 2; + b = 0; + } else if (match[5] == 'odd') { + a = 2; + b = 1; + } else { + // assumes correct "an+b" format + a = match[5].match(/^-/) ? -1 : match[5].match(/^n/) ? 1 : 0; + a = a || ((n = match[5].match(/(-?\d{1,})n/)) ? parseInt(n[1], 10) : 0); + b = 0 || ((n = match[5].match(/(-?\d{1,})$/)) ? parseInt(n[1], 10) : 0); + } + + // executed after the count is computed + expr = match[2] == 'last' ? (match[4] ? + '(e==h?1:s.TwinsCount[e.parentNode._cssId][e.nodeName.toLowerCase()])' : + '(e==h?1:s.ChildCount[e.parentNode._cssId])') + '-' + (b - 1) : b; + + test = + b < 0 ? + a <= 1 ? + '<=' + Math.abs(b) : + '%' + a + '===' + (a + b) : + a > Math.abs(b) ? '%' + a + '===' + b : + a === Math.abs(b) ? '%' + a + '===' + 0 : + a === 0 ? '==' + expr : + a < 0 ? '<=' + b : + a > 0 ? '>=' + b : + ''; + + // 4 cases: 1 (nth) x 4 (child, of-type, last-child, last-of-type) + source = 'if(this.' + match[1] + type + '(e)' + test + '){' + source + '}'; + } else { + // 6 cases: 3 (first, last, only) x 1 (child) x 2 (-of-type) + // too much overhead calling functions out of the main loop ? + //source = 'if(this.' + match[2] + type + '(e)){' + source + '}'; + source = (match[4] ? 'T=e.nodeName;' : '') + + 'n=e;while((n=n.' + (match[2] == 'first' ? 'previous' : 'next') + 'Sibling)&&' + + 'n.node' + (match[4] ? 'Name!=T' : 'Type!=1') + ');' + + 'if(!n){' + (match[2] == 'first' || match[2] == 'last' ? source : + 'n=e;while((n=n.' + (match[2] == 'only' ? 'previous' : 'next') + 'Sibling)&&' + + 'n.node' + (match[4] ? 'Name!=T' : 'Type!=1') + ');' + + 'if(!n){' + source + '}') + + '}'; + } + break; + } + + } + // *** Dynamic pseudo-classes + // CSS3 :not, :contains, :enabled, :disabled, :checked, :target + // CSS2 :active, :focus, :hover (no way yet) + // CSS1 :link, :visited + else if ((match = selector.match(Patterns.dpseudos)) && + selector.match(/([-\w]+)/)[0] in CSS3PseudoClasses.Others) { + + if (match[2]) { + // if the pseudo-class is one with a parameter + // remove round brackets grabbed by expression + match[2] = match[2].replace(/^\((.*)\)$/, '$1'); + } + + switch (match[1]) { + // CSS3 part of structural pseudo-classes + case 'not': + // compile nested selectors, need to escape double quotes characters + // since the string we are inserting into already uses double quotes + source = 'if(!this.match(e, "' + match[2].replace(/\x22/g, '\\"') + '")){' + source +'}'; + break; + // maybe deprecated in latest proposals + case 'contains': + match[2] = match[2].replace(/^["']*|['"]*$/g, ''); + source = 'if(' + CONTAINS_TEXT + '.indexOf("' + match[2] + '")>-1){' + source + '}'; + break; + // CSS3 part of UI element states + case 'checked': + source = 'if("form" in e&&/radio|checkbox/i.test(e.type)&&e.checked===true){' + source + '}'; + break; + case 'enabled': + // does not consider hidden input fields + source = 'if((("form" in e&&e.type!=="hidden")||this.isLink(e))&&e.disabled===false){' + source + '}'; + break; + case 'disabled': + // does not consider hidden input fields + source = 'if((("form" in e&&e.type!=="hidden")||this.isLink(e))&&e.disabled===true){' + source + '}'; + break; + case 'selected': + // fix Safari selectedIndex property bug + n = base.getElementsByTagName('select'); + for (i = 0; n[i]; i++) { + n[i].selectedIndex; + } + source = 'if("form" in e&&e.selected===true){' + source + '}'; + break; + // CSS3 target element + case 'target': + n = base.location.hash; + source = 'if(e.id!=""&&e.id=="' + n + '"&&"href" in e){' + source + '}'; + break; + // CSS1 & CSS2 link + case 'link': + source = 'if(this.isLink(e)&&!e.visited){' + source + '}'; + break; + case 'visited': + source = 'if(this.isLink(e)&&!!e.visited){' + source + '}'; + break; + // CSS1 & CSS2 UI States IE & FF3 have native support + // these capabilities may be emulated by event managers + case 'active': + source = 'if("activeElement" in d&&e===d.activeElement){' + source + '}'; + break; + case 'hover': + source = 'if("hoverElement" in d&&e===d.hoverElement){' + source + '}'; + break; + case 'focus': + source = 'if("form" in e&&e===d.activeElement&&typeof d.hasFocus=="function"&&d.hasFocus()===true){' + source + '}'; + break; + default: + break; + } + } + else if (!IS_EMPTY(Selectors)) { + // this is where external extensions are + // invoked if expressions match selectors + status = true; + for (name in Selectors) { + if ((match = selector.match(Selectors[name].Expression))) { + result = Selectors[name].Callback(match, source); + source = result.source; + status |= result.status; + } + } + // if an extension fails to parse the selector + // it must return a false boolean in "status" + if (!status) { + // log error but continue execution, don't throw real exceptions + // because blocking following processes maybe is not a good idea + emit('DOMException: unknown pseudo selector "' + selector + '"'); + return source; + } + } + else { + // see above, log error but continue execution + emit('DOMException: unknown token in selector "' + selector + '"'); + return source; + } + + // ensure "match" is not null or empty since + // we do not throw real DOMExceptions above + selector = match && match[match.length - 1]; + } + + return source; + }, + + // enable/disable notifications + VERBOSE = false, + + // a way to control user notification + emit = + function(message) { + if (VERBOSE) { + if (global.console && global.console.log) { + global.console.log(message); + } else { + if (/exception/i.test(message)) { + global.status = message; + global.defaultStatus = message; + } else { + global.status += message; + } + } + } + }, + + // match element with selector + // @return boolean + match = + function(element, selector) { + // make sure an element node was passed + if (element && element.nodeType == 1) { + if (typeof selector == 'string' && selector.length) { + base = element.ownerDocument; + root = base.documentElement; + // save compiled matchers + if (!compiledMatchers[selector]) { + compiledMatchers[selector] = compileGroup(selector, '', false); + } + // result of compiled matcher + return compiledMatchers[selector].call(this, element, snap, base, root); + } + else { + emit('DOMException: "' + selector + '" is not a valid CSS selector.'); + } + } + return false; + }, + + // select elements matching selector + // version using new Selector API + // @return array + select_qsa = + function (selector, from) { + if (validator.test(selector)) { + if ((!from || from.nodeType == 9) && !BUGGY_QSAPI.test(selector)) { + try { + // use available Selectors API + return toArray((from || context).querySelectorAll(selector)); + } catch(e) { } + } + // fall back to NWMatcher select + return client_api.call(this, selector, from || context); + } + return [ ]; + }, + + lastSelector, + lastContext, + lastSlice, + + // select elements matching selector + // version using cross-browser client API + // @return array + client_api = + function client_api(selector, from) { + + var i = 0, done, elements, node, parts, token; + + // extract context if changed + if (!from || lastContext != from) { + // save passed context + lastContext = from; + // ensure from context is set + from || (from = context); + // reference context ownerDocument and document root (HTML) + root = (base = from.ownerDocument || from).documentElement; + } + + if (lastSelector != selector) { + // process valid selector strings + if (validator.test(selector)) { + // save passed selector + lastSelector = selector; + // get right most selector token + parts = selector.match(Optimize.TOKEN); + // only last slice before :not rules + lastSlice = parts[parts.length - 1].split(':not')[0]; + } else { + emit('DOMException: "' + selector + '" is not a valid CSS selector.'); + return [ ]; + } + } + + // caching enabled ? + if (cachingEnabled) { + snap = base.snapshot; + // valid base context storage + if (snap && !snap.isExpired) { + if (snap.Results[selector] && + snap.Roots[selector] == from) { + return snap.Results[selector]; + } + } else { + setCache(true, base); + snap = base.snapshot; + } + } else { + if (position.test(selector)) { + // need to clear storage + snap = new Snapshot(); + } + } + + // pre-filtering pass allow to scale proportionally with big DOM trees; + // this block can be safely removed, it is a speed booster on big pages + // and still maintain the mandatory "document ordered" result set + + // commas separators are treated + // sequentially to maintain order + if (selector.indexOf(',') < 0) { + + // CLASS optimization + if ((parts = lastSlice.match(Optimize.CLASS)) && + (token = parts[parts.length - 1])) { + elements = byClass(token, from); + if (selector == '.' + token) { + if (cachingEnabled && elements.length > 0) { + done = true; + } else { + return elements; + } + } + } else + + // MULTI TAG optimization + if (!Optimize.descendants.test(selector) && + (parts = selector.match(/([-_\w]+)|(>)/g)) && NATIVE_GEBTN) { + if (parts.length > 1) { + elements = byTags(parts, from); + } else { + elements = toArray(from.getElementsByTagName(parts[0])); + } + if (cachingEnabled && elements.length > 0) { + done = true; + } else { + return elements; + } + } else + + // TAG optimization + if ((parts = lastSlice.match(Optimize.TAG)) && + (token = parts[parts.length - 1]) && NATIVE_GEBTN) { + elements = from.getElementsByTagName(token); + if (selector == token) { + if (cachingEnabled && elements.length > 0) { + done = true; + } else { + return toArray(elements); + } + } + } else + + // ID optimization + if ((parts = lastSlice.match(Optimize.ID)) && + (token = parts[parts.length - 1]) && from.getElementById) { + elements = [byId(token, from)]; + if (elements[0]) { + if (selector == '#' + token) { + if (cachingEnabled && elements.length > 0) { + done = true; + } else { + return elements; + } + } else { + //if (selector.length != (selector.lastIndexOf('#' + token) + token.length + 1)) { + // optimize narrowing context + from = elements[0].parentNode; + elements = null; + } + } else { + return [ ]; + } + } + + } + + if (!elements || elements.length === 0) { + + var tag = lastSlice.match(/\b([-_\w]+)\b/); + elements = from.getElementsByTagName('*'); + + if ((parts = lastSlice.match(/\#([-_\w]+)$/)) && selector == '#' + parts[1]) { + while ((node = elements[i++])) { + if (node.id == parts[1]) { + return [node]; + } + } + return [ ]; + } else + + if ((parts = lastSlice.match(/\b([-_\w]+)?(?:(\.[-_\w]+)|(\#[-_\w]+))/))) { + while ((node = elements[i++])) { + if ( + (!parts[1] || node.nodeName == parts[1]) && ( + (!parts[3] || (parts[2] == '#' && node.id == parts[3])) || + (!parts[3] || (parts[2] == '.' && node.className == parts[3])) + )) { + return [node]; + } + } + } else + + elements = toArray(elements); + + } + // end of prefiltering pass + + // save compiled selectors + if (!done && !compiledSelectors[selector]) { + compiledSelectors[selector] = compileGroup(selector, '', true); + } + + if (cachingEnabled) { + // a cached result set for the requested selector + snap.Results[selector] = done ? elements : + compiledSelectors[selector].call(this, elements, snap, base, root); + snap.Roots[selector] = from; + return snap.Results[selector]; + } + + // a fresh result set for the requested selector + return done ? + elements : + compiledSelectors[selector].call(this, elements, snap, base, root); + }, + + // use the new native Selector API if available, + // if missing, use the cross-browser client api + // @return array + select = NATIVE_QSAPI ? + select_qsa : + client_api, + + // element by id + // @return element reference or null + byId = + function(id, from) { + var i = 0, element, names, result; + from || (from = context); + id = id.replace(/\\/g, ''); + if (from.getElementById) { + result = from.getElementById(id); + if (result && id != getAttribute(result, 'id') && from.getElementsByName) { + names = from.getElementsByName(id); + result = null; + while ((element = names[i++])) { + if (element.getAttributeNode('id').value == id) { + result = element; + break; + } + } + } + } else { + result = NW.Dom.select('[id="' + id + '"]', from)[0] || null; + } + return result; + }, + + // elements by tag + // @return nodeList (live) + byTag = + function(tag, from) { + return (from || context).getElementsByTagName(tag || '*'); + }, + + // elements by name + // @return array + byName = + function(name, from) { + return this.select('[name="' + name.replace(/\\/g, '') + '"]', from || context); + }, + + // elements by class + // @return nodeList (native GEBCN) + // @return array (non native GEBCN) + byClass = !BUGGY_GEBCN ? + function(name, from) { + return slice.call(from.getElementsByClassName(name.replace(/\\/g, '')), 0); + } : + function(name, from) { + // context is handled in byTag for non native gEBCN + var i = 0, j = 0, r = [ ], node, + nodes = from.getElementsByTagName('*'), + name = new RegExp("(^|\\s)" + name + "(\\s|$)"); + while ((node = nodes[i++])) { + if (name.test(node.className)) { + r[j++] = node; + } + } + return r; + }, + + // recursively get nested tagNames + // example: for "div" pass ["div"] + // "ul li a" pass ["ul", "li", "a"] + // @c array of tag names combinators + // @f from context or default + // @return array + byTags = + function(c, f) { + var h, i, j, k, n, o, p, + id, e = [f || context], + r = [ ], s = [ ], t = [ ]; + h = 0; + i = 0; + while ((n = c[i++])) { + if (n == '>') { + j = 0; + while ((o = e[j++])) { + k = 0; + r = o[NATIVE_CHILDREN]; + while ((p = r[k++])) { + if (p.nodeName.toLowerCase() == c[i].toLowerCase()) { + s[h++] = p; + } + } + } + i++; + h = 0; + e = s; + s = [ ]; + t = [ ]; + } else { + j= 0; + while ((o = e[j++])) { + k = 0; + r = o.getElementsByTagName(n.replace(trim, '')); + while ((p = r[k++])) { + id = (p._cssId || (p._cssId = ++cssId)); + // discard duplicates + if (!t[id]) { + t[id] = true; + s[h++] = p; + } + } + } + h = 0; + e = s; + s = [ ]; + t = [ ]; + } + } + return e; + }, + + // attribute value + // @type string + getAttribute = NATIVE_HAS_ATTRIBUTE ? + function(element, attribute) { + return element.getAttribute(attribute) + ''; + } : + function(element, attribute) { + var node; + // specific URI attributes (parameter 2 to fix IE bug) + if (attributesURI[attribute.toLowerCase()]) { + return element.getAttribute(attribute, 2) + ''; + } + node = element.getAttributeNode(attribute); + return (node && node.value) + ''; + }, + + // attribute presence + // @return boolean + hasAttribute = NATIVE_HAS_ATTRIBUTE ? + function(element, attribute) { + return element.hasAttribute(attribute); + } : + function(element, attribute) { + // need to get at AttributeNode first on IE + var node = element.getAttributeNode(attribute); + // use both "specified" & "nodeValue" properties + return !!(node && (node.specified || node.nodeValue)); + }, + + // check if element matches the :link pseudo + isLink = + function(element) { + var nodeName = element.nodeName.toLowerCase(); + return hasAttribute(element,'href') && nodeName == 'a' || nodeName == 'area' || nodeName == 'link'; + }, + + // get best children collection available + // Safari 2.0.x "children" implementation + // differs, taken care by feature testing + // @return nodeList (live) + getChildren = + function(element) { + // childNodes is slower to loop through because it contains text nodes + // empty text nodes could be removed at startup to compensate this a bit + return element[NATIVE_CHILDREN] || element.childNodes; + }, + + // test element to be the only element child in its parent + // @return boolean + firstElement = + function(element) { + while ((element = element.previousSibling) && element.nodeType != 1) { } + return !element; + }, + + // test element to be the only element child in its parent + // @return boolean + lastElement = + function(element) { + while ((element = element.nextSibling) && element.nodeType != 1) { } + return !element; + }, + + // test element to be the only element child in its parent + // @return boolean + onlyElement = + function(element) { + return firstElement(element) && lastElement(element); + }, + + // test element to be the first element of-type in its parent + // @return boolean + firstOfType = + function(element) { + var nodeName = element.nodeName.toLowerCase(); + while ((element = element.previousSibling) && element.nodeName.toLowerCase() != nodeName) { } + return !element; + }, + + // test element to be the last element of-type in its parent + // @return boolean + lastOfType = + function(element) { + var nodeName = element.nodeName.toLowerCase(); + while ((element = element.nextSibling) && element.nodeName.toLowerCase() != nodeName) { } + return !element; + }, + + // test element to be the only element of-type in its parent + // @return boolean + onlyOfType = + function(element) { + return firstOfType(element) && lastOfType(element); + }, + + // child position by nodeType + // @return number + nthElement = + function(element) { + var i, j, node, nodes, parent, cache = snap.ChildIndex; + if (!element._cssId || !cache[element._cssId]) { + if ((parent = element.parentNode).nodeType == 1) { + i = 0; + j = 0; + nodes = parent[NATIVE_CHILDREN]; + while ((node = nodes[i++])) { + if (node.nodeType == 1) { + cache[node._cssId || (node._cssId = ++cssId)] = ++j; + } + } + snap.ChildCount[parent._cssId || (parent._cssId = ++cssId)] = j; + } else { + // does not have a parent (ex.: document) + return 0; + } + } + return cache[element._cssId]; + }, + + // child position by nodeName + // @return number + nthOfType = + function(element) { + var i, j, name, node, nodes, pid, parent, cache = snap.TwinsIndex; + if (!element._cssId || !cache[element._cssId]) { + if ((parent = element.parentNode).nodeType == 1) { + i = 0; + j = 0; + nodes = parent[NATIVE_CHILDREN]; + name = element.nodeName.toLowerCase(); + while ((node = nodes[i++])) { + if (node.nodeName.toLowerCase() == name) { + cache[node._cssId || (node._cssId = ++cssId)] = ++j; + } + } + pid = (parent._cssId || (parent._cssId = ++cssId)); + snap.TwinsCount[pid] || (snap.TwinsCount[pid] = { }); + snap.TwinsCount[pid][name] = j; + } else { + // does not have a parent (ex.: document) + return 0; + } + } + return cache[element._cssId]; + }, + + // convert nodeList to array + // @return array + toArray = NATIVE_SLICE_PROTO ? + function(list) { + return slice.call(list); + } : + function(list) { + // avoid using the length property of nodeLists + // it may have been overwritten by bad HTML code + var i = 0, array = [ ]; + while ((array[i] = list[i++])) { } + array.length--; + return array; + }, + + // cssId expando on elements, + // used to keep child indexes + // during a selection session + cssId = 1, + + // BEGIN: local context caching system + + // ****************** CACHING ****************** + // keep caching states for each context document + // set manually by using setCache(true, context) + cachingEnabled = NATIVE_MUTATION_EVENTS, + + // indexes/count of elements contained in rootElement + // expired by Mutation Events on DOM tree changes + Snapshot = + function() { + return { + // validation flag, creating it already expired, + // code validation will set it valid first time + isExpired: false, + // count of siblings by nodeType or nodeName + ChildCount: [ ], + TwinsCount: [ ], + // ordinal position by nodeType or nodeName + ChildIndex: [ ], + TwinsIndex: [ ], + // result sets and related root contexts + Results: [ ], + Roots: [ ] + }; + }, + + // local indexes, cleared + // between selection calls + snap = new Snapshot(), + + // enable/disable context caching system + // @d optional document context (iframe, xml document) + // script loading context will be used as default context + setCache = + function(enable, d) { + d || (d = context); + if (!!enable) { + d.snapshot = new Snapshot(); + startMutation(d); + } else { + stopMutation(d); + } + cachingEnabled = !!enable; + }, + + // invoked by mutation events to expire cached parts + mutationWrapper = + function(event) { + var d = event.target.ownerDocument || event.target; + stopMutation(d); + switch (event.type) { + case 'DOMAttrModified': + expireCache(d); + break; + case 'DOMNodeInserted': + expireCache(d); + break; + case 'DOMNodeRemoved': + expireCache(d); + break; + default: + break; + } + }, + + // append mutation events + startMutation = + function(d) { + if (!d.isCaching) { + // FireFox/Opera/Safari/KHTML have support for Mutation Events + d.addEventListener('DOMAttrModified', mutationWrapper, false); + d.addEventListener('DOMNodeInserted', mutationWrapper, false); + d.addEventListener('DOMNodeRemoved', mutationWrapper, false); + d.isCaching = true; + } + }, + + // remove mutation events + stopMutation = + function(d) { + if (d.isCaching) { + d.removeEventListener('DOMAttrModified', mutationWrapper, false); + d.removeEventListener('DOMNodeInserted', mutationWrapper, false); + d.removeEventListener('DOMNodeRemoved', mutationWrapper, false); + d.isCaching = false; + } + }, + + // expire complete cache + // can be invoked by Mutation Events or + // programmatically by other code/scripts + // document context is mandatory no checks + expireCache = + function(d) { + if (d && d.snapshot) { + d.snapshot.isExpired = true; + } + }; + + // END: local context caching system + + return { + + // for testing purposes ! + compile: + function(selector, mode) { + return compileGroup(selector, '', mode || false).toString(); + }, + + // enable/disable cache + setCache: setCache, + + // forced expire of DOM tree cache + expireCache: expireCache, + + // element match selector, return boolean true/false + match: match, + + // elements matching selector, starting from element + select: select, + + // Safari 2 bug with innerText (gasp!) + // used to strip tags from innerHTML + // shouldn't be public, but needed + stripTags: + function(s) { + return s.replace(/<\/?("[^\"]*"|'[^\']*'|[^>])+>/gi, ''); + }, + + // add selector patterns for user defined callbacks + registerSelector: + function (name, rexp, func) { + if (!Selectors[name]) { + Selectors[name] = { }; + Selectors[name].Expression = rexp; + Selectors[name].Callback = func; + } + }, + + // add or overwrite user defined operators + // TODO: check when overwriting standard operators + registerOperator: + function (symbol, resolver) { + if (!Operators[symbol]) { + Operators[symbol] = resolver; + } + }, + + // retrieve element by id attr + byId: byId, + + // retrieve elements by tag name + byTag: byTag, + + // retrieve elements by name attr + byName: byName, + + // retrieve elements by class name + byClass: byClass, + + // check if element matches the :link pseudo + isLink: isLink, + + // retrieve all children elements + getChildren: getChildren, + + // read the value of the attribute + // as was in the original HTML code + getAttribute: getAttribute, + + // check for the attribute presence + // as was in the original HTML code + hasAttribute: hasAttribute, + + // first child element any type + firstElement: firstElement, + + // last child element any type + lastElement: lastElement, + + // only child element any type + onlyElement: onlyElement, + + // first child element of-type + firstOfType: firstOfType, + + // last child element of-type + lastOfType: lastOfType, + + // only child element of-type + onlyOfType: onlyOfType, + + // nth child element any type + nthElement: nthElement, + + // nth child element of-type + nthOfType: nthOfType, + + // convert nodeList to array + toArray: toArray + + }; + +}(this); diff --git a/vendor/nwmatcher/selector_engine.js b/vendor/nwmatcher/selector_engine.js new file mode 100644 index 0000000..899838d --- /dev/null +++ b/vendor/nwmatcher/selector_engine.js @@ -0,0 +1,37 @@ +Prototype._original_nw = window.NW; +//= require +Prototype.NW = window.NW; + +// Restore globals. +window.NW = Prototype._original_nw; +delete Prototype._original_nw; + +Prototype.Selector = (function(NW) { + function extend(elements) { + for (var i = 0, length = elements.length; i < length; i++) + elements[i] = Element.extend(elements[i]); + return elements; + } + + function select(selector, scope) { + return extend(NW.select(selector, scope || document)); + } + + function filter(elements, selector) { + var results = [], element; + for (var i = 0, length = elements.length; i < length; i++) { + element = elements[i]; + if (NW.match(element, selector)) { + results.push(Element.extend(element)) + } + } + return results; + } + + return { + select: select, + match: NW.match, + filter: filter + }; +})(Prototype.NW.Dom); + diff --git a/vendor/sizzle b/vendor/sizzle deleted file mode 160000 index e0f5cbc..0000000 --- a/vendor/sizzle +++ /dev/null @@ -1 +0,0 @@ -Subproject commit e0f5cbc75d12aa78f3ef30930414b2f88da7b2b8 diff --git a/src/dom/sizzle_adapter.js b/vendor/sizzle/selector_engine.js similarity index 96% rename from src/dom/sizzle_adapter.js rename to vendor/sizzle/selector_engine.js index 26249eb..efffb4d 100644 --- a/src/dom/sizzle_adapter.js +++ b/vendor/sizzle/selector_engine.js @@ -1,5 +1,5 @@ Prototype._original_sizzle = window.Sizzle; -//= require +//= require Prototype.Sizzle = window.Sizzle; // Restore globals. diff --git a/vendor/sizzle/sizzle b/vendor/sizzle/sizzle new file mode 160000 index 0000000..415e466 --- /dev/null +++ b/vendor/sizzle/sizzle @@ -0,0 +1 @@ +Subproject commit 415e466f70e5a53f589161b1f2944e5485007409