diff --git a/admin/code/LeftAndMain.php b/admin/code/LeftAndMain.php index 8a5895eee..1bddd76b5 100644 --- a/admin/code/LeftAndMain.php +++ b/admin/code/LeftAndMain.php @@ -291,6 +291,10 @@ class LeftAndMain extends Controller implements PermissionProvider { )) ); + if (Director::isDev()) { + Requirements::javascript(THIRDPARTY_DIR . '/jquery-entwine/src/jquery.entwine.inspector.js'); + } + Requirements::css(FRAMEWORK_ADMIN_DIR . '/thirdparty/jquery-notice/jquery.notice.css'); Requirements::css(THIRDPARTY_DIR . '/jquery-ui-themes/smoothness/jquery-ui.css'); Requirements::css(FRAMEWORK_ADMIN_DIR .'/thirdparty/chosen/chosen/chosen.css'); diff --git a/thirdparty/jquery-entwine/.piston.yml b/thirdparty/jquery-entwine/.piston.yml index 370568d3e..7b53fed1b 100644 --- a/thirdparty/jquery-entwine/.piston.yml +++ b/thirdparty/jquery-entwine/.piston.yml @@ -1,8 +1,8 @@ --- -format: 1 +repository_url: https://github.com/hafriedlander/jquery.entwine.git +repository_class: Piston::Git::Repository handler: - commit: dd1b1d3c0958aa12990cba80a9b9d429a7e89bee + commit: 860ffe280044c3f88e64ba400f8f17b5d2616c39 branch: master lock: false -repository_class: Piston::Git::Repository -repository_url: https://github.com/hafriedlander/jquery.entwine.git +format: 1 diff --git a/thirdparty/jquery-entwine/build.sh b/thirdparty/jquery-entwine/build.sh index 018d60532..eeb16ca5e 100644 --- a/thirdparty/jquery-entwine/build.sh +++ b/thirdparty/jquery-entwine/build.sh @@ -13,6 +13,7 @@ for x in \ vendor/jquery.selector/jquery.selector.js \ vendor/jquery.selector/jquery.selector.specifity.js \ vendor/jquery.selector/jquery.selector.matches.js \ + src/jquery.selector.affectedby.js \ src/jquery.focusinout.js \ src/jquery.entwine.js \ src/jquery.entwine.dommaybechanged.js \ diff --git a/thirdparty/jquery-entwine/dist/jquery.concrete-dist.js b/thirdparty/jquery-entwine/dist/jquery.concrete-dist.js new file mode 100644 index 000000000..fe040d42d --- /dev/null +++ b/thirdparty/jquery-entwine/dist/jquery.concrete-dist.js @@ -0,0 +1,1971 @@ +/* jQuery.Entwine - Copyright 2009-2011 Hamish Friedlander and SilverStripe. Version . */ + +/* vendor/jquery.selector/jquery.class.js */ + +/** + * Very basic Class utility. Based on base and jquery.class. + * + * Class definition: var Foo = Base.extend({ init: function(){ Constructor }; method_name: function(){ Method } }); + * + * Inheritance: var Bar = Foo.extend({ method_name: function(){ this._super(); } }); + * + * new-less Constructor: new Foo(arg) <-same as-> Foo(arg) + */ + +var Base; + +(function(){ + + var marker = {}, fnTest = /xyz/.test(function(){var xyz;}) ? /\b_super\b/ : /.*/; + + // The base Class implementation (does nothing) + Base = function(){}; + + Base.addMethod = function(name, func) { + var parent = this._super && this._super.prototype; + + if (parent && fnTest.test(func)) { + this.prototype[name] = function(){ + var tmp = this._super; + this._super = parent[name]; + try { + var ret = func.apply(this, arguments); + } + finally { + this._super = tmp; + } + return ret; + }; + } + else this.prototype[name] = func; + }; + + Base.addMethods = function(props) { + for (var name in props) { + if (typeof props[name] == 'function') this.addMethod(name, props[name]); + else this.prototype[name] = props[name]; + } + }; + + Base.subclassOf = function(parentkls) { + var kls = this; + while (kls) { + if (kls === parentkls) return true; + kls = kls._super; + } + }; + + // Create a new Class that inherits from this class + Base.extend = function(props) { + + // The dummy class constructor + var Kls = function() { + if (arguments[0] === marker) return; + + if (this instanceof Kls) { + if (this.init) this.init.apply(this, arguments); + } + else { + var ret = new Kls(marker); if (ret.init) ret.init.apply(ret, arguments); return ret; + } + }; + + // Add the common class variables and methods + Kls.constructor = Kls; + Kls.extend = Base.extend; + Kls.addMethod = Base.addMethod; + Kls.addMethods = Base.addMethods; + Kls.subclassOf = Base.subclassOf; + + Kls._super = this; + + // Attach the parent object to the inheritance chain + Kls.prototype = new this(marker); + Kls.prototype.constructor = Kls; + + // Copy the properties over onto the new prototype + Kls.addMethods(props); + + return Kls; + }; +})();; + + +/* vendor/jquery.selector/jquery.selector.js */ + +(function($){ + + var tokens = { + UNICODE: /\\[0-9a-f]{1,6}(?:\r\n|[ \n\r\t\f])?/, + ESCAPE: /(?:UNICODE)|\\[^\n\r\f0-9a-f]/, + NONASCII: /[^\x00-\x7F]/, + NMSTART: /[_a-z]|(?:NONASCII)|(?:ESCAPE)/, + NMCHAR: /[_a-z0-9-]|(?:NONASCII)|(?:ESCAPE)/, + IDENT: /-?(?:NMSTART)(?:NMCHAR)*/, + + NL: /\n|\r\n|\r|\f/, + + STRING: /(?:STRING1)|(?:STRING2)|(?:STRINGBARE)/, + STRING1: /"(?:(?:ESCAPE)|\\(?:NL)|[^\n\r\f\"])*"/, + STRING2: /'(?:(?:ESCAPE)|\\(?:NL)|[^\n\r\f\'])*'/, + STRINGBARE: /(?:(?:ESCAPE)|\\(?:NL)|[^\n\r\f\]])*/, + + FUNCTION: /(?:IDENT)\(\)/, + + INTEGER: /[0-9]+/, + + WITHN: /([-+])?(INTEGER)?(n)\s*(?:([-+])\s*(INTEGER))?/, + WITHOUTN: /([-+])?(INTEGER)/ + }; + + var rx = { + not: /:not\(/, + not_end: /\)/, + + tag: /((?:IDENT)|\*)/, + id: /#(IDENT)/, + cls: /\.(IDENT)/, + attr: /\[\s*(IDENT)\s*(?:([^=]?=)\s*(STRING)\s*)?\]/, + pseudo_el: /(?::(first-line|first-letter|before|after))|(?:::((?:FUNCTION)|(?:IDENT)))/, + pseudo_cls_nth: /:nth-child\(\s*(?:(?:WITHN)|(?:WITHOUTN)|(odd|even))\s*\)/, + pseudo_cls: /:(IDENT)/, + + comb: /\s*(\+|~|>)\s*|\s+/, + comma: /\s*,\s*/, + important: /\s+!important\s*$/ + }; + + /* Replace placeholders with actual regex, and mark all as case insensitive */ + var token = /[A-Z][A-Z0-9]+/; + for (var k in rx) { + var m, src = rx[k].source; + while (m = src.match(token)) src = src.replace(m[0], tokens[m[0]].source); + rx[k] = new RegExp(src, 'gi'); + } + + /** + * A string that matches itself against regexii, and keeps track of how much of itself has been matched + */ + var ConsumableString = Base.extend({ + init: function(str) { + this.str = str; + this.pos = 0; + }, + match: function(rx) { + var m; + rx.lastIndex = this.pos; + if ((m = rx.exec(this.str)) && m.index == this.pos ) { + this.pos = rx.lastIndex ? rx.lastIndex : this.str.length ; + return m; + } + return null; + }, + peek: function(rx) { + var m; + rx.lastIndex = this.pos; + if ((m = rx.exec(this.str)) && m.index == this.pos ) return m; + return null; + }, + showpos: function() { + return this.str.slice(0,this.pos)+'' + this.str.slice(this.pos); + }, + done: function() { + return this.pos == this.str.length; + } + }); + + /* A base class that all Selectors inherit off */ + var SelectorBase = Base.extend({}); + + /** + * A class representing a Simple Selector, as per the CSS3 selector spec + */ + var SimpleSelector = SelectorBase.extend({ + init: function() { + this.tag = null; + this.id = null; + this.classes = []; + this.attrs = []; + this.nots = []; + this.pseudo_classes = []; + this.pseudo_els = []; + }, + parse: function(selector) { + var m; + + /* Pull out the initial tag first, if there is one */ + if (m = selector.match(rx.tag)) this.tag = m[1]; + + /* Then for each selection type, try and find a match */ + do { + if (m = selector.match(rx.not)) { + this.nots[this.nots.length] = SelectorsGroup().parse(selector); + if (!(m = selector.match(rx.not_end))) { + throw 'Invalid :not term in selector'; + } + } + else if (m = selector.match(rx.id)) this.id = m[1]; + else if (m = selector.match(rx.cls)) this.classes[this.classes.length] = m[1]; + else if (m = selector.match(rx.attr)) this.attrs[this.attrs.length] = [ m[1], m[2], m[3] ]; + else if (m = selector.match(rx.pseudo_el)) this.pseudo_els[this.pseudo_els.length] = m[1] || m[2]; + else if (m = selector.match(rx.pseudo_cls_nth)) { + if (m[3]) { + var a = parseInt((m[1]||'')+(m[2]||'1')); + var b = parseInt((m[4]||'')+(m[5]||'0')); + } + else { + var a = m[8] ? 2 : 0; + var b = m[8] ? (4-m[8].length) : parseInt((m[6]||'')+m[7]); + } + this.pseudo_classes[this.pseudo_classes.length] = ['nth-child', [a, b]]; + } + else if (m = selector.match(rx.pseudo_cls)) this.pseudo_classes[this.pseudo_classes.length] = [m[1]]; + + } while(m && !selector.done()); + + return this; + } + }); + + /** + * A class representing a Selector, as per the CSS3 selector spec + */ + var Selector = SelectorBase.extend({ + init: function(){ + this.parts = []; + }, + parse: function(cons){ + this.parts[this.parts.length] = SimpleSelector().parse(cons); + + while (!cons.done() && !cons.peek(rx.comma) && (m = cons.match(rx.comb))) { + this.parts[this.parts.length] = m[1] || ' '; + this.parts[this.parts.length] = SimpleSelector().parse(cons); + } + + return this.parts.length == 1 ? this.parts[0] : this; + } + }); + + /** + * A class representing a sequence of selectors, as per the CSS3 selector spec + */ + var SelectorsGroup = SelectorBase.extend({ + init: function(){ + this.parts = []; + }, + parse: function(cons){ + this.parts[this.parts.length] = Selector().parse(cons); + + while (!cons.done() && (m = cons.match(rx.comma))) { + this.parts[this.parts.length] = Selector().parse(cons); + } + + return this.parts.length == 1 ? this.parts[0] : this; + } + }); + + + $.selector = function(s){ + var cons = ConsumableString(s); + var res = SelectorsGroup().parse(cons); + + res.selector = s; + + if (!cons.done()) throw 'Could not parse selector - ' + cons.showpos() ; + else return res; + }; + + $.selector.SelectorBase = SelectorBase; + $.selector.SimpleSelector = SimpleSelector; + $.selector.Selector = Selector; + $.selector.SelectorsGroup = SelectorsGroup; + +})(jQuery); +; + + +/* vendor/jquery.selector/jquery.selector.specifity.js */ + +(function($) { + + $.selector.SimpleSelector.addMethod('specifity', function() { + if (this.spec) return this.spec; + + var spec = [ + this.id ? 1 : 0, + this.classes.length + this.attrs.length + this.pseudo_classes.length, + ((this.tag && this.tag != '*') ? 1 : 0) + this.pseudo_els.length + ]; + $.each(this.nots, function(i,not){ + var ns = not.specifity(); spec[0] += ns[0]; spec[1] += ns[1]; spec[2] += ns[2]; + }); + + return this.spec = spec; + }); + + $.selector.Selector.addMethod('specifity', function(){ + if (this.spec) return this.spec; + + var spec = [0,0,0]; + $.each(this.parts, function(i,part){ + if (i%2) return; + var ps = part.specifity(); spec[0] += ps[0]; spec[1] += ps[1]; spec[2] += ps[2]; + }); + + return this.spec = spec; + }); + + $.selector.SelectorsGroup.addMethod('specifity', function(){ + if (this.spec) return this.spec; + + var spec = [0,0,0]; + $.each(this.parts, function(i,part){ + var ps = part.specifity(); spec[0] += ps[0]; spec[1] += ps[1]; spec[2] += ps[2]; + }); + + return this.spec = spec; + }); + + +})(jQuery); +; + + +/* vendor/jquery.selector/jquery.selector.matches.js */ + +/* +This attempts to do the opposite of Sizzle. +Sizzle is good for finding elements for a selector, but not so good for telling if an individual element matches a selector +*/ + +(function($) { + + /**** CAPABILITY TESTS ****/ + var div = document.createElement('div'); + div.innerHTML = '
'; + + // In IE 6-7, getAttribute often does the wrong thing (returns similar to el.attr), so we need to use getAttributeNode on that browser + var getAttributeDodgy = div.firstChild.getAttribute('id') !== 'test'; + + // Does browser support Element.firstElementChild, Element.previousElementSibling, etc. + var hasElementTraversal = div.firstElementChild && div.firstElementChild.tagName == 'FORM'; + + // Does browser support Element.children + var hasChildren = div.children && div.children[0].tagName == 'FORM'; + + var FUNC_IN = /^\s*function\s*\([^)]*\)\s*\{/; + var FUNC_OUT = /}\s*$/; + + var funcToString = function(f) { + return (''+f).replace(FUNC_IN,'').replace(FUNC_OUT,''); + }; + + // Can we use Function#toString ? + try { + var testFunc = function(){ return 'good'; }; + if ((new Function('',funcToString(testFunc)))() != 'good') funcToString = false; + } + catch(e) { funcToString = false; console.log(e.message);/*pass*/ } + + /**** INTRO ****/ + + var GOOD = /GOOD/g; + var BAD = /BAD/g; + + var STARTS_WITH_QUOTES = /^['"]/g; + + var join = function(js) { + return js.join('\n'); + }; + + var join_complex = function(js) { + var code = new String(js.join('\n')); // String objects can have properties set. strings can't + code.complex = true; + return code; + }; + + /**** ATTRIBUTE ACCESSORS ****/ + + // Not all attribute names can be used as identifiers, so we encode any non-acceptable characters as hex + var varForAttr = function(attr) { + return '_' + attr.replace(/^[^A-Za-z]|[^A-Za-z0-9]/g, function(m){ return '_0x' + m.charCodeAt(0).toString(16) + '_'; }); + }; + + var getAttr; + + // Good browsers + if (!getAttributeDodgy) { + getAttr = function(attr){ return 'var '+varForAttr(attr)+' = el.getAttribute("'+attr+'");' ; }; + } + // IE 6, 7 + else { + // On IE 6 + 7, getAttribute still has to be called with DOM property mirror name, not attribute name. Map attributes to those names + var getAttrIEMap = { 'class': 'className', 'for': 'htmlFor' }; + + getAttr = function(attr) { + var ieattr = getAttrIEMap[attr] || attr; + return 'var '+varForAttr(attr)+' = el.getAttribute("'+ieattr+'",2) || (el.getAttributeNode("'+attr+'")||{}).nodeValue;'; + }; + } + + /**** ATTRIBUTE COMPARITORS ****/ + + var attrchecks = { + '-': '!K', + '=': 'K != "V"', + '!=': 'K == "V"', + '~=': '_WS_K.indexOf(" V ") == -1', + '^=': '!K || K.indexOf("V") != 0', + '*=': '!K || K.indexOf("V") == -1', + '$=': '!K || K.substr(K.length-"V".length) != "V"' + }; + + /**** STATE TRACKER ****/ + + var State = $.selector.State = Base.extend({ + init: function(){ + this.reset(); + }, + reset: function() { + this.attrs = {}; this.wsattrs = {}; + }, + + prev: function(){ + this.reset(); + if (hasElementTraversal) return 'el = el.previousElementSibling'; + return 'while((el = el.previousSibling) && el.nodeType != 1) {}'; + }, + next: function() { + this.reset(); + if (hasElementTraversal) return 'el = el.nextElementSibling'; + return 'while((el = el.nextSibling) && el.nodeType != 1) {}'; + }, + prevLoop: function(body){ + this.reset(); + if (hasElementTraversal) return join([ 'while(el = el.previousElementSibling){', body]); + return join([ + 'while(el = el.previousSibling){', + 'if (el.nodeType != 1) continue;', + body + ]); + }, + parent: function() { + this.reset(); + return 'el = el.parentNode;'; + }, + parentLoop: function(body) { + this.reset(); + return join([ + 'while((el = el.parentNode) && el.nodeType == 1){', + body, + '}' + ]); + }, + + uses_attr: function(attr) { + if (this.attrs[attr]) return; + this.attrs[attr] = true; + return getAttr(attr); + }, + uses_wsattr: function(attr) { + if (this.wsattrs[attr]) return; + this.wsattrs[attr] = true; + return join([this.uses_attr(attr), 'var _WS_'+varForAttr(attr)+' = " "+'+varForAttr(attr)+'+" ";']); + }, + + save: function(lbl) { + return 'var el'+lbl+' = el;'; + }, + restore: function(lbl) { + this.reset(); + return 'el = el'+lbl+';'; + } + }); + + /**** PSEUDO-CLASS DETAILS ****/ + + var pseudoclschecks = { + 'first-child': join([ + 'var cel = el;', + 'while(cel = cel.previousSibling){ if (cel.nodeType === 1) BAD; }' + ]), + 'last-child': join([ + 'var cel = el;', + 'while(cel = cel.nextSibling){ if (cel.nodeType === 1) BAD; }' + ]), + 'nth-child': function(a,b) { + var get_i = join([ + 'var i = 1, cel = el;', + 'while(cel = cel.previousSibling){', + 'if (cel.nodeType === 1) i++;', + '}' + ]); + + if (a == 0) return join([ + get_i, + 'if (i- '+b+' != 0) BAD;' + ]); + else if (b == 0 && a >= 0) return join([ + get_i, + 'if (i%'+a+' != 0 || i/'+a+' < 0) BAD;' + ]); + else if (b == 0 && a < 0) return join([ + 'BAD;' + ]); + else return join([ + get_i, + 'if ((i- '+b+')%'+a+' != 0 || (i- '+b+')/'+a+' < 0) BAD;' + ]); + } + }; + + // Needs to refence contents of object, so must be injected after definition + pseudoclschecks['only-child'] = join([ + pseudoclschecks['first-child'], + pseudoclschecks['last-child'] + ]); + + /**** SimpleSelector ****/ + + $.selector.SimpleSelector.addMethod('compile', function(el) { + var js = []; + + /* Check against element name */ + if (this.tag && this.tag != '*') { + js[js.length] = 'if (el.tagName != "'+this.tag.toUpperCase()+'") BAD;'; + } + + /* Check against ID */ + if (this.id) { + js[js.length] = el.uses_attr('id'); + js[js.length] = 'if (_id !== "'+this.id+'") BAD;'; + } + + /* Build className checking variable */ + if (this.classes.length) { + js[js.length] = el.uses_wsattr('class'); + + /* Check against class names */ + $.each(this.classes, function(i, cls){ + js[js.length] = 'if (_WS__class.indexOf(" '+cls+' ") == -1) BAD;'; + }); + } + + /* Check against attributes */ + $.each(this.attrs, function(i, attr){ + js[js.length] = (attr[1] == '~=') ? el.uses_wsattr(attr[0]) : el.uses_attr(attr[0]); + var check = attrchecks[ attr[1] || '-' ]; + check = check.replace( /K/g, varForAttr(attr[0])).replace( /V/g, attr[2] && attr[2].match(STARTS_WITH_QUOTES) ? attr[2].slice(1,-1) : attr[2] ); + js[js.length] = 'if ('+check+') BAD;'; + }); + + /* Check against nots */ + $.each(this.nots, function(i, not){ + var lbl = ++lbl_id; + var func = join([ + 'l'+lbl+':{', + not.compile(el).replace(BAD, 'break l'+lbl).replace(GOOD, 'BAD'), + '}' + ]); + + if (!(not instanceof $.selector.SimpleSelector)) func = join([ + el.save(lbl), + func, + el.restore(lbl) + ]); + + js[js.length] = func; + }); + + /* Check against pseudo-classes */ + $.each(this.pseudo_classes, function(i, pscls){ + var check = pseudoclschecks[pscls[0]]; + if (check) { + js[js.length] = ( typeof check == 'function' ? check.apply(this, pscls[1]) : check ); + } + else if (check = $.find.selectors.filters[pscls[0]]) { + if (funcToString) { + js[js.length] = funcToString(check).replace(/elem/g,'el').replace(/return([^;]+);/,'if (!($1)) BAD;'); + } + else { + js[js.length] = 'if (!$.find.selectors.filters.'+pscls[0]+'(el)) BAD;'; + } + } + }); + + js[js.length] = 'GOOD'; + + /* Pass */ + return join(js); + }); + + var lbl_id = 0; + /** Turns an compiled fragment into the first part of a combination */ + function as_subexpr(f) { + if (f.complex) + return join([ + 'l'+(++lbl_id)+':{', + f.replace(GOOD, 'break l'+lbl_id), + '}' + ]); + else + return f.replace(GOOD, ''); + } + + var combines = { + ' ': function(el, f1, f2) { + return join_complex([ + f2, + 'while(true){', + el.parent(), + 'if (!el || el.nodeType !== 1) BAD;', + f1.compile(el).replace(BAD, 'continue'), + '}' + ]); + }, + + '>': function(el, f1, f2) { + return join([ + f2, + el.parent(), + 'if (!el || el.nodeType !== 1) BAD;', + f1.compile(el) + ]); + }, + + '~': function(el, f1, f2) { + return join_complex([ + f2, + el.prevLoop(), + f1.compile(el).replace(BAD, 'continue'), + '}', + 'BAD;' + ]); + }, + + '+': function(el, f1, f2) { + return join([ + f2, + el.prev(), + 'if (!el) BAD;', + f1.compile(el) + ]); + } + }; + + $.selector.Selector.addMethod('compile', function(el) { + var l = this.parts.length; + + var expr = this.parts[--l].compile(el); + while (l) { + var combinator = this.parts[--l]; + expr = combines[combinator](el, this.parts[--l], as_subexpr(expr)); + } + + return expr; + }); + + $.selector.SelectorsGroup.addMethod('compile', function(el) { + var expr = [], lbl = ++lbl_id; + + for (var i=0; i < this.parts.length; i++) { + expr[expr.length] = join([ + i == 0 ? el.save(lbl) : el.restore(lbl), + 'l'+lbl+'_'+i+':{', + this.parts[i].compile(el).replace(BAD, 'break l'+lbl+'_'+i), + '}' + ]); + } + + expr[expr.length] = 'BAD;'; + return join(expr); + }); + + $.selector.SelectorBase.addMethod('matches', function(el){ + this.matches = new Function('el', join([ + 'if (!el) return false;', + this.compile(new State()).replace(BAD, 'return false').replace(GOOD, 'return true') + ])); + return this.matches(el); + }); + +})(jQuery); + +; + + +/* src/jquery.selector.affectedby.js */ + +(function($) { + + // TODO: + // Make attributes & IDs work + + var DIRECT = /DIRECT/g; + var CONTEXT = /CONTEXT/g; + var EITHER = /DIRECT|CONTEXT/g; + + $.selector.SelectorBase.addMethod('affectedBy', function(props) { + this.affectedBy = new Function('props', ([ + 'var direct_classes, context_classes, direct_attrs, context_attrs, t;', + this.ABC_compile().replace(DIRECT, 'direct').replace(CONTEXT, 'context'), + 'return {classes: {context: context_classes, direct: direct_classes}, attrs: {context: context_attrs, direct: direct_attrs}};' + ]).join("\n")); + + // DEBUG: Print out the compiled funciton + // console.log(this.selector, ''+this.affectedBy); + + return this.affectedBy(props); + }); + + $.selector.SimpleSelector.addMethod('ABC_compile', function() { + var parts = []; + + $.each(this.classes, function(i, cls){ + parts[parts.length] = "if (t = props.classes['"+cls+"']) (DIRECT_classes || (DIRECT_classes = {}))['"+cls+"'] = t;"; + }); + + $.each(this.nots, function(i, not){ + parts[parts.length] = not.ABC_compile(); + }); + + return parts.join("\n"); + }); + + $.selector.Selector.addMethod('ABC_compile', function(arg){ + var parts = []; + var i = this.parts.length-1; + + parts[parts.length] = this.parts[i].ABC_compile(); + while ((i = i - 2) >= 0) parts[parts.length] = this.parts[i].ABC_compile().replace(EITHER, 'CONTEXT'); + + return parts.join("\n"); + }); + + $.selector.SelectorsGroup.addMethod('ABC_compile', function(){ + var parts = []; + + $.each(this.parts, function(i,part){ + parts[parts.length] = part.ABC_compile(); + }); + + return parts.join("\n"); + }); + + +})(jQuery); +; + + +/* src/jquery.focusinout.js */ + +(function($){ + + /** + * Add focusin and focusout support to bind and live for browers other than IE. Designed to be usable in a delegated fashion (like $.live) + * Copyright (c) 2007 Jörn Zaefferer + */ + $.support.focusInOut = !!($.browser.msie); + if (!$.support.focusInOut) { + // Emulate focusin and focusout by binding focus and blur in capturing mode + $.each({focus: 'focusin', blur: 'focusout'}, function(original, fix){ + $.event.special[fix] = { + setup: function(){ + if (!this.addEventListener) return false; + this.addEventListener(original, $.event.special[fix].handler, true); + }, + teardown: function(){ + if (!this.removeEventListener) return false; + this.removeEventListener(original, $.event.special[fix].handler, true); + }, + handler: function(e){ + arguments[0] = $.event.fix(e); + arguments[0].type = fix; + return $.event.handle.apply(this, arguments); + } + }; + }); + } + + (function(){ + //IE has some trouble with focusout with select and keyboard navigation + var activeFocus = null; + + $(document) + .bind('focusin', function(e){ + var target = e.realTarget || e.target; + if (activeFocus && activeFocus !== target) { + e.type = 'focusout'; + $(activeFocus).trigger(e); + e.type = 'focusin'; + e.target = target; + } + activeFocus = target; + }) + .bind('focusout', function(e){ + activeFocus = null; + }); + })(); + +})(jQuery);; + + +/* src/jquery.entwine.js */ + +try { + console.log; +} +catch (e) { + window.console = undefined; +} + +(function($) { + + var namespaces = {}; + + $.entwine = function() { + $.fn.entwine.apply(null, arguments); + }; + + /** + * A couple of utility functions for accessing the store outside of this closure, and for making things + * operate in a little more easy-to-test manner + */ + $.extend($.entwine, { + /** + * Get all the namespaces. Useful for introspection? Internal interface of Namespace not guaranteed consistant + */ + namespaces: namespaces, + + /** + * Remove all entwine rules + */ + clear_all_rules: function() { + // Remove proxy functions + for (var k in $.fn) { if ($.fn[k].isentwinemethod) delete $.fn[k]; } + // Remove bound events - TODO: Make this pluggable, so this code can be moved to jquery.entwine.events.js + $(document).unbind('.entwine'); + // Remove namespaces, and start over again + namespaces = $.entwine.namespaces = {}; + }, + + WARN_LEVEL_NONE: 0, + WARN_LEVEL_IMPORTANT: 1, + WARN_LEVEL_BESTPRACTISE: 2, + + /** + * Warning level. Set to a higher level to get warnings dumped to console. + */ + warningLevel: 0, + + /** Utility to optionally display warning messages depending on level */ + warn: function(message, level) { + if (level <= $.entwine.warningLevel && console && console.warn) { + console.warn(message); + if (console.trace) console.trace(); + } + }, + + warn_exception: function(where, /* optional: */ on, e) { + if ($.entwine.WARN_LEVEL_IMPORTANT <= $.entwine.warningLevel && console && console.warn) { + if (arguments.length == 2) { e = on; on = null; } + + if (on) console.warn('Uncaught exception',e,'in',where,'on',on); + else console.warn('Uncaught exception',e,'in',where); + + if (e.stack) console.warn("Stack Trace:\n" + e.stack); + } + } + }); + + + /** Stores a count of definitions, so that we can sort identical selectors by definition order */ + var rulecount = 0; + + var Rule = Base.extend({ + init: function(selector, name) { + this.selector = selector; + this.specifity = selector.specifity(); + this.important = 0; + this.name = name; + this.rulecount = rulecount++; + } + }); + + Rule.compare = function(a, b) { + var as = a.specifity, bs = b.specifity; + + return (a.important - b.important) || + (as[0] - bs[0]) || + (as[1] - bs[1]) || + (as[2] - bs[2]) || + (a.rulecount - b.rulecount) ; + }; + + $.entwine.RuleList = function() { + var list = []; + + list.addRule = function(selector, name){ + var rule = Rule(selector, name); + + list[list.length] = rule; + list.sort(Rule.compare); + + return rule; + }; + + return list; + }; + + var handlers = []; + + /** + * A Namespace holds all the information needed for adding entwine methods to a namespace (including the _null_ namespace) + */ + $.entwine.Namespace = Base.extend({ + init: function(name){ + if (name && !name.match(/^[A-Za-z0-9.]+$/)) $.entwine.warn('Entwine namespace '+name+' is not formatted as period seperated identifiers', $.entwine.WARN_LEVEL_BESTPRACTISE); + name = name || '__base'; + + this.name = name; + this.store = {}; + + namespaces[name] = this; + + if (name == "__base") { + this.injectee = $.fn; + this.$ = $; + } + else { + // We're in a namespace, so we build a Class that subclasses the jQuery Object Class to inject namespace functions into + + // jQuery 1.5 already provides a nice way to subclass, so use it + if ($.sub) { + this.$ = $.sub(); + this.injectee = this.$.prototype; + } + // For jQuery < 1.5 we have to do it ourselves + else { + var subfn = function(){}; + this.injectee = subfn.prototype = new $; + + // And then we provide an overriding $ that returns objects of our new Class, and an overriding pushStack to catch further selection building + var bound$ = this.$ = function(a) { + // Try the simple way first + var jq = $.fn.init.apply(new subfn(), arguments); + if (jq instanceof subfn) return jq; + + // That didn't return a bound object, so now we need to copy it + var rv = new subfn(); + rv.selector = jq.selector; rv.context = jq.context; var i = rv.length = jq.length; + while (i--) rv[i] = jq[i]; + return rv; + }; + + this.injectee.pushStack = function(elems, name, selector){ + var ret = bound$(elems); + + // Add the old object onto the stack (as a reference) + ret.prevObject = this; + ret.context = this.context; + + if ( name === "find" ) ret.selector = this.selector + (this.selector ? " " : "") + selector; + else if ( name ) ret.selector = this.selector + "." + name + "(" + selector + ")"; + + // Return the newly-formed element set + return ret; + }; + + // Copy static functions through from $ to this.$ so e.g. $.ajax still works + // @bug, @cantfix: Any class functions added to $ after this call won't get mirrored through + $.extend(this.$, $); + } + + // We override entwine to inject the name of this namespace when defining blocks inside this namespace + var entwine_wrapper = this.injectee.entwine = function(spacename) { + var args = arguments; + + if (!spacename || typeof spacename != 'string') { args = $.makeArray(args); args.unshift(name); } + else if (spacename.charAt(0) != '.') args[0] = name+'.'+spacename; + + return $.fn.entwine.apply(this, args); + }; + + this.$.entwine = function() { + entwine_wrapper.apply(null, arguments); + }; + + for (var i = 0; i < handlers.length; i++) { + var handler = handlers[i], builder; + + // Inject jQuery object method overrides + if (builder = handler.namespaceMethodOverrides) { + var overrides = builder(this); + for (var k in overrides) this.injectee[k] = overrides[k]; + } + + // Inject $.entwine function overrides + if (builder = handler.namespaceStaticOverrides) { + var overrides = builder(this); + for (var k in overrides) this.$.entwine[k] = overrides[k]; + } + } + } + }, + + /** + * Returns a function that does selector matching against the function list for a function name + * Used by proxy for all calls, and by ctorProxy to handle _super calls + * @param {String} name - name of the function as passed in the construction object + * @param {String} funcprop - the property on the Rule object that gives the actual function to call + * @param {function} basefunc - the non-entwine function to use as the catch-all function at the bottom of the stack + */ + one: function(name, funcprop, basefunc) { + var namespace = this; + var funcs = this.store[name]; + + var one = function(el, args, i){ + if (i === undefined) i = funcs.length; + while (i--) { + if (funcs[i].selector.matches(el)) { + var ret, tmp_i = el.i, tmp_f = el.f; + el.i = i; el.f = one; + try { ret = funcs[i][funcprop].apply(namespace.$(el), args); } + finally { el.i = tmp_i; el.f = tmp_f; } + return ret; + } + } + // If we didn't find a entwine-defined function, but there is a non-entwine function to use as a base, try that + if (basefunc) return basefunc.apply(namespace.$(el), args); + }; + + return one; + }, + + /** + * A proxy is a function attached to a callable object (either the base jQuery.fn or a subspace object) which handles + * finding and calling the correct function for each member of the current jQuery context + * @param {String} name - name of the function as passed in the construction object + * @param {function} basefunc - the non-entwine function to use as the catch-all function at the bottom of the stack + */ + build_proxy: function(name, basefunc) { + var one = this.one(name, 'func', basefunc); + + var prxy = function() { + var rv, ctx = $(this); + + var i = ctx.length; + while (i--) rv = one(ctx[i], arguments); + return rv; + }; + + return prxy; + }, + + bind_proxy: function(selector, name, func) { + var rulelist = this.store[name] || (this.store[name] = $.entwine.RuleList()); + + var rule = rulelist.addRule(selector, name); rule.func = func; + + if (!this.injectee.hasOwnProperty(name) || !this.injectee[name].isentwinemethod) { + this.injectee[name] = this.build_proxy(name, this.injectee.hasOwnProperty(name) ? this.injectee[name] : null); + this.injectee[name].isentwinemethod = true; + } + + if (!this.injectee[name].isentwinemethod) { + $.entwine.warn('Warning: Entwine function '+name+' clashes with regular jQuery function - entwine function will not be callable directly on jQuery object', $.entwine.WARN_LEVEL_IMPORTANT); + } + }, + + add: function(selector, data) { + // For every item in the hash, try ever method handler, until one returns true + for (var k in data) { + var v = data[k]; + + for (var i = 0; i < handlers.length; i++) { + if (handlers[i].bind && handlers[i].bind.call(this, selector, k, v)) break; + } + } + }, + + has: function(ctx, name) { + var rulelist = this.store[name]; + if (!rulelist) return false; + + /* We go forward this time, since low specifity is likely to knock out a bunch of elements quickly */ + for (var i = 0 ; i < rulelist.length; i++) { + ctx = ctx.not(rulelist[i].selector); + if (!ctx.length) return true; + } + return false; + } + }); + + /** + * A handler is some javascript code that adds support for some time of key / value pair passed in the hash to the Namespace add method. + * The default handlers provided (and included by default) are event, ctor and properties + */ + $.entwine.Namespace.addHandler = function(handler) { + for (var i = 0; i < handlers.length && handlers[i].order < handler.order; i++) { /* Pass */ } + handlers.splice(i, 0, handler); + }; + + $.entwine.Namespace.addHandler({ + order: 50, + + bind: function(selector, k, v){ + if ($.isFunction(v)) { + this.bind_proxy(selector, k, v); + return true; + } + } + }); + + $.extend($.fn, { + /** + * Main entwine function. Used for new definitions, calling into a namespace (or forcing the base namespace) and entering a using block + * + */ + entwine: function(spacename) { + var i = 0; + /* Don't actually work out selector until we try and define something on it - we might be opening a namespace on an function-traveresed object + which have non-standard selectors like .parents(.foo).slice(0,1) */ + var selector = null; + + /* By default we operator on the base namespace */ + var namespace = namespaces.__base || $.entwine.Namespace(); + + /* If the first argument is a string, then it's the name of a namespace. Look it up */ + if (typeof spacename == 'string') { + if (spacename.charAt('0') == '.') spacename = spacename.substr(1); + if (spacename) namespace = namespaces[spacename] || $.entwine.Namespace(spacename); + i=1; + } + + /* All remaining arguments should either be using blocks or definition hashs */ + while (i < arguments.length) { + var res = arguments[i++]; + + // If it's a function, call it - either it's a using block or it's a namespaced entwine definition + if ($.isFunction(res)) { + if (res.length != 1) $.entwine.warn('Function block inside entwine definition does not take $ argument properly', $.entwine.WARN_LEVEL_IMPORTANT); + res = res.call(namespace.$(this), namespace.$); + } + + // If we have a entwine definition hash, inject it into namespace + if (res) { + if (selector === null) selector = this.selector ? $.selector(this.selector) : false; + + if (selector) namespace.add(selector, res); + else $.entwine.warn('Entwine block given to entwine call without selector. Make sure you call $(selector).entwine when defining blocks', $.entwine.WARN_LEVEL_IMPORTANT); + } + } + + /* Finally, return the jQuery object 'this' refers to, wrapped in the new namespace */ + return namespace.$(this); + }, + + /** + * Calls the next most specific version of the current entwine method + */ + _super: function(){ + var rv, i = this.length; + while (i--) { + var el = this[0]; + rv = el.f(el, arguments, el.i); + } + return rv; + } + }); + +})(jQuery); +; + + +/* src/jquery.entwine.dommaybechanged.js */ + +(function($){ + + /** Utility function to monkey-patch a jQuery method */ + var monkey = function( /* method, method, ...., patch */){ + var methods = $.makeArray(arguments); + var patch = methods.pop(); + + $.each(methods, function(i, method){ + var old = $.fn[method]; + + $.fn[method] = function() { + var self = this, args = $.makeArray(arguments); + + var rv = old.apply(self, args); + patch.apply(self, args); + return rv; + } + }); + } + + /** What to call to run a function 'soon'. Normally setTimeout, but for syncronous mode we override so soon === now */ + var runSoon = window.setTimeout; + + /** The timer handle for the asyncronous matching call */ + var ChangeDetails = Base.extend({ + + init: function() { + this.global = false; + this.attrs = {}; + this.classes = {}; + }, + + /** Fire the change event. Only fires on the document node, so bind to that */ + triggerEvent: function() { + // If we're not the active changes instance any more, don't trigger + if (changes != this) return; + + // Cancel any pending timeout (if we're directly called in the mean time) + if (this.check_id) clearTimeout(this.check_id); + + // Create a new event object + var event = $.Event("DOMMaybeChanged"); + event.changes = this; + + // Reset the global changes object to be a new instance (do before trigger, in case trigger fires changes itself) + changes = new ChangeDetails(); + + // Fire event + $(document).triggerHandler(event); + }, + + changed: function() { + if (!this.check_id) { + var self = this; + this.check_id = runSoon(function(){ self.check_id = null; self.triggerEvent(); }, 10); + } + }, + + addAll: function() { + if (this.global) return this; // If we've already flagged as a global change, just skip + + this.global = true; + this.changed(); + return this; + }, + + addSubtree: function(node) { + return this.addAll(); + }, + + /* For now we don't do this. It's expensive, and jquery.entwine.ctors doesn't use this information anyway */ + addSubtreeFuture: function(node) { + if (this.global) return this; // If we've already flagged as a global change, just skip + + this.subtree = this.subtree ? this.subtree.add(node) : $(node); + this.changed(); + return this; + }, + + addAttr: function(attr, node) { + if (this.global) return this; + + this.attrs[attr] = (attr in this.attrs) ? this.attrs[attr].add(node) : $(node); + this.changed(); + return this; + }, + + addClass: function(klass, node) { + if (this.global) return this; + + this.classes[klass] = (klass in this.classes) ? this.classes[klass].add(node) : $(node); + this.changed(); + return this; + } + }); + + var changes = new ChangeDetails(); + + + monkey('append', 'prepend', 'empty', 'html', function(){ + changes.addSubtree(this); + }); + + monkey('after', 'before', 'remove', 'detach', function(){ + changes.addSubtree(this.parent()); + }) + + monkey('removeAttr', function(attr){ + changes.addAttr(attr, this); + }); + + monkey('addClass', 'removeClass', 'toggleClass', function(klass){ + if (typeof klass == 'string') changes.addClass(klass, this); + }); + + monkey('attr', function(a, b){ + if (b !== undefined && typeof a == 'string') changes.addAttr(a, this); + else if (typeof a != 'string') { for (var k in a) changes.addAttr(k, this); } + }); + + /* + These manipulation functions call one or more of the above to do the actual manipulation: + appendTo -> append + prependTo -> prepend + insertBefore -> before + insertAfter -> after + replaceWith -> before || append + replaceAll -> replaceWith + text -> empty, appendWith + wrapAll -> insertBefore, append + wrapInner -> wrapAll || append + wrap -> wrapAll + unwrap -> replaceWith + */ + + $.extend($.entwine, { + /** + * Make onmatch and onunmatch work in synchronous mode - that is, new elements will be detected immediately after + * the DOM manipulation that made them match. This is only really useful for during testing, since it's pretty slow + * (otherwise we'd make it the default). + */ + synchronous_mode: function() { + if (changes && changes.check_id) clearTimeout(changes.check_id); + changes = new ChangeDetails(); + + runSoon = function(func, delay){ func.call(this); return null; }; + }, + + /** + * Trigger onmatch and onunmatch now - usefull for after DOM manipulation by methods other than through jQuery. + * Called automatically on document.ready + */ + triggerMatching: function() { + changes.addAll(); //.triggerEvent(); + } + }); + + // And on DOM ready, trigger matching once + $(function(){ + $.entwine.triggerMatching(); + }); + +})(jQuery);; + + +/* src/jquery.entwine.events.js */ + +(function($) { + + /** Taken from jQuery 1.5.2 for backwards compatibility */ + if ($.support.changeBubbles == undefined) { + $.support.changeBubbles = true; + + var el = document.createElement("div"); + eventName = "onchange"; + + if (el.attachEvent) { + var isSupported = (eventName in el); + if (!isSupported) { + el.setAttribute(eventName, "return;"); + isSupported = typeof el[eventName] === "function"; + } + + $.support.changeBubbles = isSupported; + } + } + + /* Return true if node b is the same as, or is a descendant of, node a */ + if (document.compareDocumentPosition) { + var is_or_contains = function(a, b) { + return a && b && (a == b || !!(a.compareDocumentPosition(b) & 16)); + }; + } + else { + var is_or_contains = function(a, b) { + return a && b && (a == b || (a.contains ? a.contains(b) : true)); + }; + } + + /* Add the methods to handle event binding to the Namespace class */ + $.entwine.Namespace.addMethods({ + build_event_proxy: function(name) { + var one = this.one(name, 'func'); + + var prxy = function(e, data) { + // For events that do not bubble we manually trigger delegation (see delegate_submit below) + // If this event is a manual trigger, the event we actually want to bubble is attached as a property of the passed event + e = e.delegatedEvent || e; + + var el = e.target; + while (el && el.nodeType == 1 && !e.isPropagationStopped()) { + var ret = one(el, arguments); + if (ret !== undefined) e.result = ret; + if (ret === false) { e.preventDefault(); e.stopPropagation(); } + + el = el.parentNode; + } + }; + + return prxy; + }, + + build_mouseenterleave_proxy: function(name) { + var one = this.one(name, 'func'); + + var prxy = function(e) { + var el = e.target; + var rel = e.relatedTarget; + + while (el && el.nodeType == 1 && !e.isPropagationStopped()) { + /* We know el contained target. If it also contains relatedTarget then we didn't mouseenter / leave. What's more, every ancestor will also + contan el and rel, and so we can just stop bubbling */ + if (is_or_contains(el, rel)) break; + + var ret = one(el, arguments); + if (ret !== undefined) e.result = ret; + if (ret === false) { e.preventDefault(); e.stopPropagation(); } + + el = el.parentNode; + } + }; + + return prxy; + }, + + build_change_proxy: function(name) { + var one = this.one(name, 'func'); + + /* + This change bubble emulation code is taken mostly from jQuery 1.6 - unfortunately we can't easily reuse any of + it without duplication, so we'll have to re-migrate any bugfixes + */ + + // Get the value of an item. Isn't supposed to be interpretable, just stable for some value, and different + // once the value changes + var getVal = function( elem ) { + var type = elem.type, val = elem.value; + + if (type === "radio" || type === "checkbox") { + val = elem.checked; + } + else if (type === "select-multiple") { + val = ""; + if (elem.selectedIndex > -1) { + val = jQuery.map(elem.options, function(elem){ return elem.selected; }).join("-"); + } + } + else if (jQuery.nodeName(elem, "select")) { + val = elem.selectedIndex; + } + + return val; + }; + + // Test if a node name is a form input + var rformElems = /^(?:textarea|input|select)$/i; + + // Check if this event is a change, and bubble the change event if it is + var testChange = function(e) { + var elem = e.target, data, val; + + if (!rformElems.test(elem.nodeName) || elem.readOnly) return; + + data = jQuery.data(elem, "_entwine_change_data"); + val = getVal(elem); + + // the current data will be also retrieved by beforeactivate + if (e.type !== "focusout" || elem.type !== "radio") { + jQuery.data(elem, "_entwine_change_data", val); + } + + if (data === undefined || val === data) return; + + if (data != null || val) { + e.type = "change"; + + while (elem && elem.nodeType == 1 && !e.isPropagationStopped()) { + var ret = one(elem, arguments); + if (ret !== undefined) e.result = ret; + if (ret === false) { e.preventDefault(); e.stopPropagation(); } + + elem = elem.parentNode; + } + } + }; + + // The actual proxy - responds to several events, some of which triger a change check, some + // of which just store the value for future change checks + var prxy = function(e) { + var event = e.type, elem = e.target, type = jQuery.nodeName( elem, "input" ) ? elem.type : ""; + + switch (event) { + case 'focusout': + case 'beforedeactivate': + testChange.apply(this, arguments); + break; + + case 'click': + if ( type === "radio" || type === "checkbox" || jQuery.nodeName( elem, "select" ) ) { + testChange.apply(this, arguments); + } + break; + + // Change has to be called before submit + // Keydown will be called before keypress, which is used in submit-event delegation + case 'keydown': + if ( + (e.keyCode === 13 && !jQuery.nodeName( elem, "textarea" ) ) || + (e.keyCode === 32 && (type === "checkbox" || type === "radio")) || + type === "select-multiple" + ) { + testChange.apply(this, arguments); + } + break; + + // Beforeactivate happens also before the previous element is blurred + // with this event you can't trigger a change event, but you can store + // information + case 'focusin': + case 'beforeactivate': + jQuery.data( elem, "_entwine_change_data", getVal(elem) ); + break; + } + } + + return prxy; + }, + + bind_event: function(selector, name, func, event) { + var funcs = this.store[name] || (this.store[name] = $.entwine.RuleList()) ; + var proxies = funcs.proxies || (funcs.proxies = {}); + + var rule = funcs.addRule(selector, name); rule.func = func; + + if (!proxies[name]) { + switch (name) { + case 'onmouseenter': + proxies[name] = this.build_mouseenterleave_proxy(name); + event = 'mouseover'; + break; + case 'onmouseleave': + proxies[name] = this.build_mouseenterleave_proxy(name); + event = 'mouseout'; + break; + case 'onchange': + if (!$.support.changeBubbles) { + proxies[name] = this.build_change_proxy(name); + event = 'click keydown focusin focusout beforeactivate beforedeactivate'; + } + break; + case 'onsubmit': + event = 'delegatedSubmit'; + break; + case 'onfocus': + case 'onblur': + $.entwine.warn('Event '+event+' not supported - using focusin / focusout instead', $.entwine.WARN_LEVEL_IMPORTANT); + } + + // If none of the special handlers created a proxy, use the generic proxy + if (!proxies[name]) proxies[name] = this.build_event_proxy(name); + + $(document).bind(event.replace(/(\s+|$)/g, '.entwine$1'), proxies[name]); + } + } + }); + + $.entwine.Namespace.addHandler({ + order: 40, + + bind: function(selector, k, v){ + var match, event; + if ($.isFunction(v) && (match = k.match(/^on(.*)/))) { + event = match[1]; + this.bind_event(selector, k, v, event); + return true; + } + } + }); + + // Find all forms and bind onsubmit to trigger on the document too. + // This is the only event that can't be grabbed via delegation + + var form_binding_cache = $([]); // A cache for already-handled form elements + var delegate_submit = function(e, data){ + var delegationEvent = $.Event('delegatedSubmit'); delegationEvent.delegatedEvent = e; + return $(document).trigger(delegationEvent, data); + }; + + $(document).bind('DOMMaybeChanged', function(){ + var forms = $('form'); + // Only bind to forms we haven't processed yet + forms.not(form_binding_cache).bind('submit', delegate_submit); + // Then remember the current set of forms + form_binding_cache = forms; + }); + +})(jQuery); + ; + + +/* src/jquery.entwine.ctors.js */ + +(function($) { + + /* Add the methods to handle constructor & destructor binding to the Namespace class */ + $.entwine.Namespace.addMethods({ + bind_condesc: function(selector, name, func) { + var ctors = this.store.ctors || (this.store.ctors = $.entwine.RuleList()) ; + + var rule; + for (var i = 0 ; i < ctors.length; i++) { + if (ctors[i].selector.selector == selector.selector) { + rule = ctors[i]; break; + } + } + if (!rule) { + rule = ctors.addRule(selector, 'ctors'); + } + + rule[name] = func; + + if (!ctors[name+'proxy']) { + var one = this.one('ctors', name); + var namespace = this; + + var proxy = function(els, i, func) { + var j = els.length; + while (j--) { + var el = els[j]; + + var tmp_i = el.i, tmp_f = el.f; + el.i = i; el.f = one; + + try { func.call(namespace.$(el)); } + catch(e) { $.entwine.warn_exception(name, el, e); } + finally { el.i = tmp_i; el.f = tmp_f; } + } + }; + + ctors[name+'proxy'] = proxy; + } + } + }); + + $.entwine.Namespace.addHandler({ + order: 30, + + bind: function(selector, k, v) { + if ($.isFunction(v) && (k == 'onmatch' || k == 'onunmatch')) { + // When we add new matchers we need to trigger a full global recalc once, regardless of the DOM changes that triggered the event + this.matchersDirty = true; + + this.bind_condesc(selector, k, v); + return true; + } + } + }); + + /** + * Finds all the elements that now match a different rule (or have been removed) and call onmatch on onunmatch as appropriate + * + * Because this has to scan the DOM, and is therefore fairly slow, this is normally triggered off a short timeout, so that + * a series of DOM manipulations will only trigger this once. + * + * The downside of this is that things like: + * $('#foo').addClass('tabs'); $('#foo').tabFunctionBar(); + * won't work. + */ + $(document).bind('DOMMaybeChanged', function(e){ + // Get the change delta. Can help stop us from doing heavy lifting if none of the changes could actually trigger an onmatch or onunmatch function + var changes = e.changes; + + // var start = (new Date).getTime(); + + // For every namespace + for (var k in $.entwine.namespaces) { + var namespace = $.entwine.namespaces[k]; + + // That has constructors or destructors + var ctors = namespace.store.ctors; + if (ctors) { + + // Keep a record of elements that have matched some previous more specific rule. + // Not that we _don't_ actually do that until this is needed. If matched is null, it's not been calculated yet. + // We also keep track of any elements that have newly been taken or released by a specific rule + var matched = null, taken = $([]), released = $([]); + + // Updates matched to contain all the previously matched elements as if we'd been keeping track all along + var calcmatched = function(j){ + if (matched !== null) return; + matched = $([]); + + var cache, k = ctors.length; + while ((--k) > j) { + if (cache = ctors[k].cache) matched = matched.add(cache); + } + } + + // Some declared variables used in the loop + var add, rem, res, rule, sel, ctor, dtor, full; + + // Stepping through each selector from most to least specific + var j = ctors.length; + while (j--) { + // Build some quick-access variables + rule = ctors[j]; + sel = rule.selector.selector; + ctor = rule.onmatch; + dtor = rule.onunmatch; + + /* + Rule.cache might be stale or fresh. It'll be stale if + - some more specific selector now has some of rule.cache in it + - some change has happened that means new elements match this selector now + - some change has happened that means elements no longer match this selector + + The first we can just compare rules.cache with matched, removing anything that's there already. + */ + + // Reset the "elements that match this selector and no more specific selector with an onmatch rule" to null. + // Staying null means this selector is fresh. + res = null; + + // If this gets changed to true, it's too hard to do a delta update, so do a full update + full = false; + + if (namespace.matchersDirty || changes.global) { + // For now, just fall back to old version. We need to do something like changed.Subtree.find('*').andSelf().filter(sel), but that's _way_ slower on modern browsers than the below + full = true; + } + else { + // We don't deal with attributes yet, so any attribute change means we need to do a full recalc + for (var k in e.changes.attrs) { full = true; break; } + + /* + If a class changes, but it isn't listed in our selector, we don't care - the change couldn't affect whether or not any element matches + + If it is listed on our selector + - If it is on the direct match part, it could have added or removed the node it changed on + - If it is on the context part, it could have added or removed any node that were previously included or excluded because of a match or failure to match with the context required on that node + - NOTE: It might be on _both_ + */ + + var method = rule.selector.affectedBy(e.changes); + + if (method.classes.context) { + full = true; + } + else { + for (var k in method.classes.direct) { + calcmatched(j); + var recheck = e.changes.classes[k].not(matched); + + if (res === null) { + res = rule.cache ? rule.cache.not(taken).add(released.filter(sel)) : $([]); + } + + res = res.not(recheck).add(recheck.filter(sel)); + } + } + } + + if (full) { + calcmatched(j); + res = $(sel).not(matched); + } + else { + if (!res) { + // We weren't stale because of any changes to the DOM that affected this selector, but more specific + // onmatches might have caused stale-ness + + // Do any of the previous released elements match this selector? + add = released.length && released.filter(sel); + + if (add && add.length) { + // Yes, so we're stale as we need to include them. Filter for any possible taken value at the same time + res = rule.cache ? rule.cache.not(taken).add(add) : add; + } + else { + // Do we think we own any of the elements now taken by more specific rules? + rem = taken.length && rule.cache && rule.cache.filter(taken); + + if (rem && rem.length) { + // Yes, so we're stale as we need to exclude them. + res = rule.cache.not(rem); + } + } + } + } + + // Res will be null if we know we are fresh (no full needed, selector not affectedBy changes) + if (res === null) { + // If we are tracking matched, add ourselves + if (matched && rule.cache) matched = matched.add(rule.cache); + } + else { + // If this selector has a list of elements it matched against last time + if (rule.cache) { + // Find the ones that are extra this time + add = res.not(rule.cache); + rem = rule.cache.not(res); + } + else { + add = res; rem = null; + } + + if ((add && add.length) || (rem && rem.length)) { + if (rem && rem.length) { + released = released.add(rem); + + if (dtor && !rule.onunmatchRunning) { + rule.onunmatchRunning = true; + ctors.onunmatchproxy(rem, j, dtor); + rule.onunmatchRunning = false; + } + } + + // Call the constructor on the newly matched ones + if (add && add.length) { + taken = taken.add(add); + released = released.not(add); + + if (ctor && !rule.onmatchRunning) { + rule.onmatchRunning = true; + ctors.onmatchproxy(add, j, ctor); + rule.onmatchRunning = false; + } + } + } + + // If we are tracking matched, add ourselves + if (matched) matched = matched.add(res); + + // And remember this list of matching elements again this selector, so next matching we can find the unmatched ones + rule.cache = res; + } + } + + namespace.matchersDirty = false; + } + } + + // console.log((new Date).getTime() - start); + }); + + +})(jQuery); +; + + +/* src/jquery.entwine.properties.js */ + +(function($) { + + var entwine_prepend = '__entwine!'; + + var getEntwineData = function(el, namespace, property) { + return el.data(entwine_prepend + namespace + '!' + property); + }; + + var setEntwineData = function(el, namespace, property, value) { + return el.data(entwine_prepend + namespace + '!' + property, value); + }; + + var getEntwineDataAsHash = function(el, namespace) { + var hash = {}; + var id = jQuery.data(el[0]); + + var matchstr = entwine_prepend + namespace + '!'; + var matchlen = matchstr.length; + + var cache = jQuery.cache[id]; + for (var k in cache) { + if (k.substr(0,matchlen) == matchstr) hash[k.substr(matchlen)] = cache[k]; + } + + return hash; + }; + + var setEntwineDataFromHash = function(el, namespace, hash) { + for (var k in hash) setEntwineData(namespace, k, hash[k]); + }; + + var entwineData = function(el, namespace, args) { + switch (args.length) { + case 0: + return getEntwineDataAsHash(el, namespace); + case 1: + if (typeof args[0] == 'string') return getEntwineData(el, namespace, args[0]); + else return setEntwineDataFromHash(el, namespace, args[0]); + default: + return setEntwineData(el, namespace, args[0], args[1]); + } + }; + + $.extend($.fn, { + entwineData: function() { + return entwineData(this, '__base', arguments); + } + }); + + $.entwine.Namespace.addHandler({ + order: 60, + + bind: function(selector, k, v) { + if (k.charAt(0) != k.charAt(0).toUpperCase()) $.entwine.warn('Entwine property '+k+' does not start with a capital letter', $.entwine.WARN_LEVEL_BESTPRACTISE); + + // Create the getters and setters + + var getterName = 'get'+k; + var setterName = 'set'+k; + + this.bind_proxy(selector, getterName, function() { var r = this.entwineData(k); return r === undefined ? v : r; }); + this.bind_proxy(selector, setterName, function(v){ return this.entwineData(k, v); }); + + // Get the get and set proxies we just created + + var getter = this.injectee[getterName]; + var setter = this.injectee[setterName]; + + // And bind in the jQuery-style accessor + + this.bind_proxy(selector, k, function(v){ return (arguments.length == 1 ? setter : getter).call(this, v) ; }); + + return true; + }, + + namespaceMethodOverrides: function(namespace){ + return { + entwineData: function() { + return entwineData(this, namespace.name, arguments); + } + }; + } + }); + +})(jQuery); +; + + +/* src/jquery.entwine.legacy.js */ + +(function($) { + + // Adds back concrete methods for backwards compatibility + $.concrete = $.entwine; + $.fn.concrete = $.fn.entwine; + $.fn.concreteData = $.fn.entwineData; + + // Use addHandler to hack in the namespace.$.concrete equivilent to the namespace.$.entwine namespace-injection + $.entwine.Namespace.addHandler({ + order: 100, + bind: function(selector, k, v) { return false; }, + + namespaceMethodOverrides: function(namespace){ + namespace.$.concrete = namespace.$.entwine; + namespace.injectee.concrete = namespace.injectee.entwine; + namespace.injectee.concreteData = namespace.injectee.entwineData; + return {}; + } + }); + +})(jQuery); +; + diff --git a/thirdparty/jquery-entwine/dist/jquery.entwine-dist.js b/thirdparty/jquery-entwine/dist/jquery.entwine-dist.js index 6df3e75f1..fe040d42d 100644 --- a/thirdparty/jquery-entwine/dist/jquery.entwine-dist.js +++ b/thirdparty/jquery-entwine/dist/jquery.entwine-dist.js @@ -694,6 +694,69 @@ Sizzle is good for finding elements for a selector, but not so good for telling ; +/* src/jquery.selector.affectedby.js */ + +(function($) { + + // TODO: + // Make attributes & IDs work + + var DIRECT = /DIRECT/g; + var CONTEXT = /CONTEXT/g; + var EITHER = /DIRECT|CONTEXT/g; + + $.selector.SelectorBase.addMethod('affectedBy', function(props) { + this.affectedBy = new Function('props', ([ + 'var direct_classes, context_classes, direct_attrs, context_attrs, t;', + this.ABC_compile().replace(DIRECT, 'direct').replace(CONTEXT, 'context'), + 'return {classes: {context: context_classes, direct: direct_classes}, attrs: {context: context_attrs, direct: direct_attrs}};' + ]).join("\n")); + + // DEBUG: Print out the compiled funciton + // console.log(this.selector, ''+this.affectedBy); + + return this.affectedBy(props); + }); + + $.selector.SimpleSelector.addMethod('ABC_compile', function() { + var parts = []; + + $.each(this.classes, function(i, cls){ + parts[parts.length] = "if (t = props.classes['"+cls+"']) (DIRECT_classes || (DIRECT_classes = {}))['"+cls+"'] = t;"; + }); + + $.each(this.nots, function(i, not){ + parts[parts.length] = not.ABC_compile(); + }); + + return parts.join("\n"); + }); + + $.selector.Selector.addMethod('ABC_compile', function(arg){ + var parts = []; + var i = this.parts.length-1; + + parts[parts.length] = this.parts[i].ABC_compile(); + while ((i = i - 2) >= 0) parts[parts.length] = this.parts[i].ABC_compile().replace(EITHER, 'CONTEXT'); + + return parts.join("\n"); + }); + + $.selector.SelectorsGroup.addMethod('ABC_compile', function(){ + var parts = []; + + $.each(this.parts, function(i,part){ + parts[parts.length] = part.ABC_compile(); + }); + + return parts.join("\n"); + }); + + +})(jQuery); +; + + /* src/jquery.focusinout.js */ (function($){ @@ -1122,19 +1185,140 @@ catch (e) { /* src/jquery.entwine.dommaybechanged.js */ (function($){ - + + /** Utility function to monkey-patch a jQuery method */ + var monkey = function( /* method, method, ...., patch */){ + var methods = $.makeArray(arguments); + var patch = methods.pop(); + + $.each(methods, function(i, method){ + var old = $.fn[method]; + + $.fn[method] = function() { + var self = this, args = $.makeArray(arguments); + + var rv = old.apply(self, args); + patch.apply(self, args); + return rv; + } + }); + } + /** What to call to run a function 'soon'. Normally setTimeout, but for syncronous mode we override so soon === now */ var runSoon = window.setTimeout; /** The timer handle for the asyncronous matching call */ - var check_id = null; - - /** Fire the change event. Only fires on the document node, so bind to that */ - var triggerEvent = function() { - $(document).triggerHandler('DOMMaybeChanged'); - check_id = null; - }; - + var ChangeDetails = Base.extend({ + + init: function() { + this.global = false; + this.attrs = {}; + this.classes = {}; + }, + + /** Fire the change event. Only fires on the document node, so bind to that */ + triggerEvent: function() { + // If we're not the active changes instance any more, don't trigger + if (changes != this) return; + + // Cancel any pending timeout (if we're directly called in the mean time) + if (this.check_id) clearTimeout(this.check_id); + + // Create a new event object + var event = $.Event("DOMMaybeChanged"); + event.changes = this; + + // Reset the global changes object to be a new instance (do before trigger, in case trigger fires changes itself) + changes = new ChangeDetails(); + + // Fire event + $(document).triggerHandler(event); + }, + + changed: function() { + if (!this.check_id) { + var self = this; + this.check_id = runSoon(function(){ self.check_id = null; self.triggerEvent(); }, 10); + } + }, + + addAll: function() { + if (this.global) return this; // If we've already flagged as a global change, just skip + + this.global = true; + this.changed(); + return this; + }, + + addSubtree: function(node) { + return this.addAll(); + }, + + /* For now we don't do this. It's expensive, and jquery.entwine.ctors doesn't use this information anyway */ + addSubtreeFuture: function(node) { + if (this.global) return this; // If we've already flagged as a global change, just skip + + this.subtree = this.subtree ? this.subtree.add(node) : $(node); + this.changed(); + return this; + }, + + addAttr: function(attr, node) { + if (this.global) return this; + + this.attrs[attr] = (attr in this.attrs) ? this.attrs[attr].add(node) : $(node); + this.changed(); + return this; + }, + + addClass: function(klass, node) { + if (this.global) return this; + + this.classes[klass] = (klass in this.classes) ? this.classes[klass].add(node) : $(node); + this.changed(); + return this; + } + }); + + var changes = new ChangeDetails(); + + + monkey('append', 'prepend', 'empty', 'html', function(){ + changes.addSubtree(this); + }); + + monkey('after', 'before', 'remove', 'detach', function(){ + changes.addSubtree(this.parent()); + }) + + monkey('removeAttr', function(attr){ + changes.addAttr(attr, this); + }); + + monkey('addClass', 'removeClass', 'toggleClass', function(klass){ + if (typeof klass == 'string') changes.addClass(klass, this); + }); + + monkey('attr', function(a, b){ + if (b !== undefined && typeof a == 'string') changes.addAttr(a, this); + else if (typeof a != 'string') { for (var k in a) changes.addAttr(k, this); } + }); + + /* + These manipulation functions call one or more of the above to do the actual manipulation: + appendTo -> append + prependTo -> prepend + insertBefore -> before + insertAfter -> after + replaceWith -> before || append + replaceAll -> replaceWith + text -> empty, appendWith + wrapAll -> insertBefore, append + wrapInner -> wrapAll || append + wrap -> wrapAll + unwrap -> replaceWith + */ + $.extend($.entwine, { /** * Make onmatch and onunmatch work in synchronous mode - that is, new elements will be detected immediately after @@ -1142,48 +1326,26 @@ catch (e) { * (otherwise we'd make it the default). */ synchronous_mode: function() { - if (check_id) clearTimeout(check_id); check_id = null; + if (changes && changes.check_id) clearTimeout(changes.check_id); + changes = new ChangeDetails(); + runSoon = function(func, delay){ func.call(this); return null; }; }, - + /** * Trigger onmatch and onunmatch now - usefull for after DOM manipulation by methods other than through jQuery. * Called automatically on document.ready */ triggerMatching: function() { - matching(); + changes.addAll(); //.triggerEvent(); } }); - - function registerMutateFunction() { - $.each(arguments, function(i,func){ - var old = $.fn[func]; - $.fn[func] = function() { - var rv = old.apply(this, arguments); - if (!check_id) check_id = runSoon(triggerEvent, 100); - return rv; - }; - }); - } - - function registerSetterGetterFunction() { - $.each(arguments, function(i,func){ - var old = $.fn[func]; - $.fn[func] = function(a, b) { - var rv = old.apply(this, arguments); - if (!check_id && (b !== undefined || typeof a != 'string')) check_id = runSoon(triggerEvent, 100); - return rv; - }; - }); - } - // Register core DOM manipulation methods - registerMutateFunction('append', 'prepend', 'after', 'before', 'wrap', 'removeAttr', 'addClass', 'removeClass', 'toggleClass', 'empty', 'remove'); - registerSetterGetterFunction('attr'); - // And on DOM ready, trigger matching once - $(function(){ triggerEvent(); }); - + $(function(){ + $.entwine.triggerMatching(); + }); + })(jQuery);; @@ -1491,6 +1653,9 @@ catch (e) { bind: function(selector, k, v) { if ($.isFunction(v) && (k == 'onmatch' || k == 'onunmatch')) { + // When we add new matchers we need to trigger a full global recalc once, regardless of the DOM changes that triggered the event + this.matchersDirty = true; + this.bind_condesc(selector, k, v); return true; } @@ -1507,15 +1672,39 @@ catch (e) { * $('#foo').addClass('tabs'); $('#foo').tabFunctionBar(); * won't work. */ - $(document).bind('DOMMaybeChanged', function(){ + $(document).bind('DOMMaybeChanged', function(e){ + // Get the change delta. Can help stop us from doing heavy lifting if none of the changes could actually trigger an onmatch or onunmatch function + var changes = e.changes; + + // var start = (new Date).getTime(); + // For every namespace for (var k in $.entwine.namespaces) { + var namespace = $.entwine.namespaces[k]; + // That has constructors or destructors - var ctors = $.entwine.namespaces[k].store.ctors; + var ctors = namespace.store.ctors; if (ctors) { - // Keep a record of elements that have matched already - var matched = $([]), add, rem, res, rule, sel, ctor, dtor; + // Keep a record of elements that have matched some previous more specific rule. + // Not that we _don't_ actually do that until this is needed. If matched is null, it's not been calculated yet. + // We also keep track of any elements that have newly been taken or released by a specific rule + var matched = null, taken = $([]), released = $([]); + + // Updates matched to contain all the previously matched elements as if we'd been keeping track all along + var calcmatched = function(j){ + if (matched !== null) return; + matched = $([]); + + var cache, k = ctors.length; + while ((--k) > j) { + if (cache = ctors[k].cache) matched = matched.add(cache); + } + } + + // Some declared variables used in the loop + var add, rem, res, rule, sel, ctor, dtor, full; + // Stepping through each selector from most to least specific var j = ctors.length; while (j--) { @@ -1524,40 +1713,140 @@ catch (e) { sel = rule.selector.selector; ctor = rule.onmatch; dtor = rule.onunmatch; - - // Get the list of elements that match this selector, that haven't yet matched a more specific selector - res = add = $(sel).not(matched); - - // If this selector has a list of elements it matched against last time - if (rule.cache) { - // Find the ones that are extra this time - add = res.not(rule.cache); - if (dtor) { - // Find the ones that are gone this time - rem = rule.cache.not(res); - // And call the destructor on them - if (rem.length && !rule.onunmatchRunning) { - rule.onunmatchRunning = true; - ctors.onunmatchproxy(rem, j, dtor); - rule.onunmatchRunning = false; + + /* + Rule.cache might be stale or fresh. It'll be stale if + - some more specific selector now has some of rule.cache in it + - some change has happened that means new elements match this selector now + - some change has happened that means elements no longer match this selector + + The first we can just compare rules.cache with matched, removing anything that's there already. + */ + + // Reset the "elements that match this selector and no more specific selector with an onmatch rule" to null. + // Staying null means this selector is fresh. + res = null; + + // If this gets changed to true, it's too hard to do a delta update, so do a full update + full = false; + + if (namespace.matchersDirty || changes.global) { + // For now, just fall back to old version. We need to do something like changed.Subtree.find('*').andSelf().filter(sel), but that's _way_ slower on modern browsers than the below + full = true; + } + else { + // We don't deal with attributes yet, so any attribute change means we need to do a full recalc + for (var k in e.changes.attrs) { full = true; break; } + + /* + If a class changes, but it isn't listed in our selector, we don't care - the change couldn't affect whether or not any element matches + + If it is listed on our selector + - If it is on the direct match part, it could have added or removed the node it changed on + - If it is on the context part, it could have added or removed any node that were previously included or excluded because of a match or failure to match with the context required on that node + - NOTE: It might be on _both_ + */ + + var method = rule.selector.affectedBy(e.changes); + + if (method.classes.context) { + full = true; + } + else { + for (var k in method.classes.direct) { + calcmatched(j); + var recheck = e.changes.classes[k].not(matched); + + if (res === null) { + res = rule.cache ? rule.cache.not(taken).add(released.filter(sel)) : $([]); + } + + res = res.not(recheck).add(recheck.filter(sel)); } } } - - // Call the constructor on the newly matched ones - if (add.length && ctor && !rule.onmatchRunning) { - rule.onmatchRunning = true; - ctors.onmatchproxy(add, j, ctor); - rule.onmatchRunning = false; + + if (full) { + calcmatched(j); + res = $(sel).not(matched); + } + else { + if (!res) { + // We weren't stale because of any changes to the DOM that affected this selector, but more specific + // onmatches might have caused stale-ness + + // Do any of the previous released elements match this selector? + add = released.length && released.filter(sel); + + if (add && add.length) { + // Yes, so we're stale as we need to include them. Filter for any possible taken value at the same time + res = rule.cache ? rule.cache.not(taken).add(add) : add; + } + else { + // Do we think we own any of the elements now taken by more specific rules? + rem = taken.length && rule.cache && rule.cache.filter(taken); + + if (rem && rem.length) { + // Yes, so we're stale as we need to exclude them. + res = rule.cache.not(rem); + } + } + } + } + + // Res will be null if we know we are fresh (no full needed, selector not affectedBy changes) + if (res === null) { + // If we are tracking matched, add ourselves + if (matched && rule.cache) matched = matched.add(rule.cache); + } + else { + // If this selector has a list of elements it matched against last time + if (rule.cache) { + // Find the ones that are extra this time + add = res.not(rule.cache); + rem = rule.cache.not(res); + } + else { + add = res; rem = null; + } + + if ((add && add.length) || (rem && rem.length)) { + if (rem && rem.length) { + released = released.add(rem); + + if (dtor && !rule.onunmatchRunning) { + rule.onunmatchRunning = true; + ctors.onunmatchproxy(rem, j, dtor); + rule.onunmatchRunning = false; + } + } + + // Call the constructor on the newly matched ones + if (add && add.length) { + taken = taken.add(add); + released = released.not(add); + + if (ctor && !rule.onmatchRunning) { + rule.onmatchRunning = true; + ctors.onmatchproxy(add, j, ctor); + rule.onmatchRunning = false; + } + } + } + + // If we are tracking matched, add ourselves + if (matched) matched = matched.add(res); + + // And remember this list of matching elements again this selector, so next matching we can find the unmatched ones + rule.cache = res; } - - // Add these matched ones to the list tracking all elements matched so far - matched = matched.add(res); - // And remember this list of matching elements again this selector, so next matching we can find the unmatched ones - ctors[j].cache = res; } + + namespace.matchersDirty = false; } } + + // console.log((new Date).getTime() - start); }); diff --git a/thirdparty/jquery-entwine/spec/SpecRunner.html b/thirdparty/jquery-entwine/spec/SpecRunner.html index 7257037e8..baa0786c9 100644 --- a/thirdparty/jquery-entwine/spec/SpecRunner.html +++ b/thirdparty/jquery-entwine/spec/SpecRunner.html @@ -1,5 +1,5 @@ + "http://www.w3.org/TR/html4/loose.dtd"> Jasmine Test Runner @@ -10,14 +10,13 @@ - - + + + @@ -52,6 +53,7 @@ margin: 0; padding: 4px 15px; } + #jqver.error { padding: 4px 13px; border: 2px solid red; diff --git a/thirdparty/jquery-entwine/spec/spec.entwine.basics.js b/thirdparty/jquery-entwine/spec/spec.entwine.basics.js index f59b3bcd4..11bcc9450 100644 --- a/thirdparty/jquery-entwine/spec/spec.entwine.basics.js +++ b/thirdparty/jquery-entwine/spec/spec.entwine.basics.js @@ -1,86 +1,95 @@ -describe( 'Entwine', function() { - - beforeEach(function() { - $.entwine.warningLevel = $.entwine.WARN_LEVEL_BESTPRACTISE; - $('body').append('
'); - }); - - afterEach(function() { - $('#dom_test').remove(); - }); +describe('Entwine', function(){ - describe( 'Basics', function() { - - beforeEach(function() { - $.entwine.clear_all_rules(); - $('#dom_test').html('
'); - }); + beforeEach(function(){ + $.entwine.warningLevel = $.entwine.WARN_LEVEL_BESTPRACTISE; + $('body').append('
'); + }); - it( 'can attach and call a base function', function() { - $('#a').entwine({ - foo: function(){return this.attr('id');} - }); - expect($('.a').foo()).toEqual('a'); - }); - - it( 'can attach and call a base function on a selector using a data attribute selection', function() { - $('[data-fieldtype=foo]').entwine({ - foo: function(){return this.attr('id');} - }); - expect($('.a').foo()).toEqual('a') ; - }); - - it( 'can attach and call several base functions', function() { - $('#a').entwine({ - foo: function(){return 'foo_' + this.attr('id');}, - bar: function(){return 'bar_' + this.attr('id');} - }); - expect($('.a').foo()).toEqual( 'foo_a'); - expect($('.a').bar()).toEqual( 'bar_a'); - }); - - it( 'can attach and call a namespaced function', function() { - $.entwine('bar', function($){ - $('#a').entwine({ - foo: function(){return this.attr('id');} - }); - }); - expect($('.a').entwine('bar').foo()).toEqual( 'a'); - }); - - it( 'can attach and call a nested namespaced function', function() { - $.entwine('qux.baz.bar', function($){ - $('#a').entwine({ - foo: function(){return this.attr('id');} - }); - }); - expect($('.a').entwine('qux.baz.bar').foo()).toEqual( 'a'); - }); - - it( 'can call two functions on two elements', function() { - var res = [] - $('#a').entwine({ - foo: function(){res.push(this.attr('id'));} - }); - $('#b.c').entwine({ - foo: function(){res.push(this.attr('id'));} - }); - $('#dom_test div').foo(); - expect(res).toEqual( ['b', 'a']); - }); - - it( 'can call two namespaced functions on two elements', function() { - var res = [] - $.entwine('bar', function($){ - $('#a').entwine({ - foo: function(){res.push(this.attr('id'));} - }); - $('#b.c').entwine({ - foo: function(){res.push(this.attr('id'));} - }); - }); - $('#dom_test div').entwine('bar').foo(); - expect(res).toEqual( ['b', 'a']); - }); - }); + afterEach(function(){ + $('#dom_test').remove(); + }); + + describe('Basics', function(){ + + beforeEach(function(){ + $.entwine.clear_all_rules(); + $('#dom_test').html('
'); + }); + + it('can attach and call a base function', function(){ + $('#a').entwine({ + foo: function(){return this.attr('id');} + }); + + expect($('.a').foo()).toEqual('a'); + }); + + it('can attach and call a base function on a selector using a data attribute selection', function(){ + $('[data-fieldtype=foo]').entwine({ + foo: function(){return this.attr('id');} + }); + + expect($('.a').foo()).toEqual('a'); + }); + + it('can attach and call several base functions', function(){ + $('#a').entwine({ + foo: function(){return 'foo_' + this.attr('id');}, + bar: function(){return 'bar_' + this.attr('id');} + }); + + expect($('.a').foo()).toEqual('foo_a'); + expect($('.a').bar()).toEqual('bar_a'); + }); + + it('can attach and call a namespaced function', function(){ + $.entwine('bar', function($){ + $('#a').entwine({ + foo: function(){return this.attr('id');} + }); + }); + + expect($('.a').entwine('bar').foo()).toEqual('a'); + }); + + it('can attach and call a nested namespaced function', function(){ + $.entwine('qux.baz.bar', function($){ + $('#a').entwine({ + foo: function(){return this.attr('id');} + }); + }); + + expect($('.a').entwine('qux.baz.bar').foo()).toEqual('a'); + }); + + it('can call two functions on two elements', function(){ + var res = []; + + $('#a').entwine({ + foo: function(){res.push(this.attr('id'));} + }); + $('#b.c').entwine({ + foo: function(){res.push(this.attr('id'));} + }); + + $('#dom_test div').foo(); + expect(res).toEqual(['b', 'a']); + }); + + it('can call two namespaced functions on two elements', function(){ + var res = []; + + $.entwine('bar', function($){ + $('#a').entwine({ + foo: function(){res.push(this.attr('id'));} + }); + $('#b.c').entwine({ + foo: function(){res.push(this.attr('id'));} + }); + }); + + $('#dom_test div').entwine('bar').foo(); + expect(res).toEqual(['b', 'a']); + }); + }); }); \ No newline at end of file diff --git a/thirdparty/jquery-entwine/spec/spec.entwine.ctors.js b/thirdparty/jquery-entwine/spec/spec.entwine.ctors.js index 9128ddcee..f9df6dc9c 100644 --- a/thirdparty/jquery-entwine/spec/spec.entwine.ctors.js +++ b/thirdparty/jquery-entwine/spec/spec.entwine.ctors.js @@ -1,68 +1,147 @@ -describe( 'Entwine', function() { +describe('Entwine', function(){ - beforeEach(function() { - $('body').append('
') - }); - - afterEach(function() { - $('#dom_test').remove() - }); + beforeEach(function(){ + $('body').append('
'); + }); - describe( 'Ctors', function() { - - beforeEach(function() { - $.entwine.synchronous_mode(); - $.entwine.clear_all_rules() - $('#dom_test').html('
') - }); - - it( 'calls onmatch when new element created', function() { - var a = false; - $('#b').entwine({onmatch: function(){a = true;} }); - expect(a).toBeFalsy(); - $('#a').after('
'); - expect(a).toBeTruthy(); - }); - - it( 'calls onunmatch when new element deleted', function() { - var a = 0; - $('#b').entwine({onmatch: function(){a = 1;}, onunmatch: function(){a = 2;} }); - expect(a).toEqual( 0); - $('#a').after('
'); - expect(a).toEqual( 1); - $('#b').remove(); - expect(a).toEqual( 2); - }); - - it( 'calls onmatch when ruleset matches after class added', function() { - var a = 0; - $('#a.foo').entwine({onmatch: function(){a = 1;} }); - expect(a).toEqual(0); - $('#a').addClass('foo'); - expect(a).toEqual( 1); - }); + afterEach(function(){ + $('#dom_test').remove(); + }); - it( 'calls onmatch in both direct and namespaced onmatch, does not call less specific onmatch', function() { - var a = 0, b=0, c=0, d=0; - $('.foo').entwine({onmatch: function(){a = 1;}}) - $('.foo').entwine('bar', function($){return{onmatch: function(){b = 1;}}}) - $('#a.foo').entwine({onmatch: function(){c = 1;}}) - $('#a.foo').entwine('bar', function($){return{onmatch: function(){d = 1}}}) - expect([a, b, c, d]).toEqual( [0, 0, 0, 0]); - $('#a').addClass('foo'); - expect([a, b, c, d]).toEqual( [0, 0, 1, 1]); - }); - - it( 'calls onmatch in both direct and namespaced onmatch, super works as expected', function() { - var a = 0, b=0, c=0, d=0; - $('.foo').entwine({onmatch: function(){a += 1;}}) - $('.foo').entwine('bar', function($){return{onmatch: function(){b += 1;}}}) - $('#a.foo').entwine({onmatch: function(){this._super(); c = 1; this._super();}}) - $('#a.foo').entwine('bar', function($){return{onmatch: function(){this._super(); d = 1; this._super();}}}) - expect([a, b, c, d]).toEqual( [0, 0, 0, 0]); - $('#a').addClass('foo'); - expect([a, b, c, d]).toEqual( [2, 2, 1, 1]); - }); - - }); + describe('Ctors', function(){ + + beforeEach(function(){ + $.entwine.synchronous_mode(); + $.entwine.clear_all_rules(); + $('#dom_test').html('
'); + }); + + it('calls onmatch when new element created', function(){ + var a = false; + + $('#b').entwine({ + onmatch: function(){a = true;} + }); + + expect(a).toBeFalsy(); + + $('#a').after('
'); + expect(a).toBeTruthy(); + }); + + it('calls onunmatch when new element deleted', function(){ + var a = 0; + + $('#b').entwine({ + onmatch: function(){a = 1;}, + onunmatch: function(){a = 2;} + }); + + expect(a).toEqual(0); + + $('#a').after('
'); + expect(a).toEqual(1); + + $('#b').remove(); + expect(a).toEqual(2); + }); + + it('calls onmatch when ruleset matches after class added', function(){ + var a = 0; + + $('#a.foo').entwine({ + onmatch: function(){a = 1;} + }); + + expect(a).toEqual(0); + + $('#a').addClass('foo'); + expect(a).toEqual(1); + }); + + it('calls onmatch in both direct and namespaced onmatch, does not call less specific onmatch', function(){ + var a = 0, b = 0, c = 0, d = 0; + + $('.foo').entwine({ + onmatch: function(){a = 1;} + }); + $('.foo').entwine('bar', function($){return{ + onmatch: function(){b = 1;} + };}); + $('#a.foo').entwine({ + onmatch: function(){c = 1;} + }); + $('#a.foo').entwine('bar', function($){return{ + onmatch: function(){d = 1;} + };}); + + expect([a, b, c, d]).toEqual([0, 0, 0, 0]); + + $('#a').addClass('foo'); + expect([a, b, c, d]).toEqual([0, 0, 1, 1]); + }); + + it('calls onmatch in both direct and namespaced onmatch, super works as expected', function(){ + var a = 0, b = 0, c = 0, d = 0; + + $('.foo').entwine({ + onmatch: function(){a += 1;} + }); + $('.foo').entwine('bar', function($){return{ + onmatch: function(){b += 1;} + };}); + $('#a.foo').entwine({ + onmatch: function(){this._super(); c = 1; this._super();} + }); + $('#a.foo').entwine('bar', function($){return{ + onmatch: function(){this._super(); d = 1; this._super();} + };}); + + expect([a, b, c, d]).toEqual([0, 0, 0, 0]); + + $('#a').addClass('foo'); + expect([a, b, c, d]).toEqual([2, 2, 1, 1]); + }); + + it('handles onmatch rules being added post document.onready', function(){ + var a = 0, b = 0; + + $('#a').entwine({ + onmatch: function(){a += 1;} + }); + $('#a.a').entwine({ + onmatch: function(){b += 1;} + }); + + // Rules are new, and no DOM change, so no triggers yet + expect([a, b]).toEqual([0, 0]); + + // New #a.a rule thinks it matches no nodes, and so removing .a would normally not release it. Check we handle + $('#a').removeClass('a'); + expect([a, b]).toEqual([1, 0]); + }); + + it('calls onmatch in less specific rule when more specific rule no longer matches', function(){ + var a = 0, b = 0, c = 0; + + $('#a').entwine({ + onmatch: function(){a += 1;} + }); + $('#a.a').entwine({ + onmatch: function(){b += 1;} + }); + $('#a.a.b').entwine({ + onmatch: function(){c += 1;} + }); + + $.entwine.triggerMatching(); + expect([a, b, c]).toEqual([0, 0, 1]); + + $('#a').removeClass('b'); + expect([a, b, c]).toEqual([0, 1, 1]); + + $('#a').removeClass('a'); + expect([a, b, c]).toEqual([1, 1, 1]); + }); + }); }); \ No newline at end of file diff --git a/thirdparty/jquery-entwine/spec/spec.entwine.events.js b/thirdparty/jquery-entwine/spec/spec.entwine.events.js index 8ccb43496..58d5b72d4 100644 --- a/thirdparty/jquery-entwine/spec/spec.entwine.events.js +++ b/thirdparty/jquery-entwine/spec/spec.entwine.events.js @@ -1,162 +1,223 @@ -describe( 'Entwine', function() { - - beforeEach(function() { - $('body').append('
') - }); - - afterEach(function() { - $('#dom_test').remove() - }); - - describe( 'Events', function() { - - beforeEach(function() { - $.entwine.synchronous_mode(); - $.entwine.clear_all_rules() - $('#dom_test').html('
') - }); - - it( 'calls onfoo when foo triggered', function() { - var a = 0; - $('#a').entwine({onfoo: function(){a = 1;} }); - expect(a).toEqual(0); - $('#a').trigger('foo'); - expect(a).toEqual( 1); - }); - - it( 'only calls most specific onfoo when foo triggered', function() { - var a = 0, b = 0; - $('#a.a').entwine({onfoo: function(){a = 1;} }); - $('#a').entwine({onfoo: function(){b = 1;} }); - expect(a).toEqual( 0); - expect(b).toEqual( 0); - $('#a').trigger('foo'); - expect(a).toEqual( 1); - expect(b).toEqual( 0); - }); - - it( 'calls namespaced onfoo when foo triggered', function() { - var a = 0; - $('#a').entwine('bar', function($){return{onfoo: function(){a = 1;} }}); - expect(a).toEqual( 0); - $('#a').trigger('foo'); - expect(a).toEqual( 1); - }); - - it( 'calls most specific namespaced onfoo and most specific non-namespaced onfoo when foo triggered', function() { - var a = 0, b = 0, c = 0, d = 0; - $('#a.a').entwine({onfoo: function(){a = 1;} }); - $('#a').entwine({onfoo: function(){b = 1;} }); - $('#a.a').entwine('bar', function($){return{onfoo: function(){c = 1;} }}); - $('#a').entwine('bar', function($){return{onfoo: function(){d = 1;} }}); - expect([a, b, c, d]).toEqual( [0, 0, 0, 0] ); +describe('Entwine', function(){ - $('#a').trigger('foo'); - expect([a, b, c, d]).toEqual( [1, 0, 1, 0]); - }); - - it( 'calls up correctly on _super', function() { - var a = 0, b = 0; - $('#a').entwine({onfoo: function(){a += 1;} }); - $('#a.a').entwine({onfoo: function(){this._super(); b += 1; this._super();} }); - - expect([a, b]).toEqual( [0, 0]); - $('#a').trigger('foo') - expect([a, b]).toEqual( [2, 1]); - }); - - it( 'passes event object', function() { - var event; - $('#a').entwine({onfoo: function(e){event = e;} }); - $('#a').trigger('foo'); - expect(event.type).toBeDefined(); - expect(event.type).toEqual('foo'); - expect(event.target).toHaveAttr( 'id', 'a'); - }); - - it( 'delegates submit events to forms', function() { - var a = 0; - $('
').appendTo('#dom_test'); - - $('.foo').entwine({onsubmit: function(e, d){a = 1;} }); + beforeEach(function(){ + $('body').append('
'); + }); - expect(a).toEqual( 0); - $('.foo').trigger('submit'); - expect(a).toEqual( 1); - }); - - describe( 'can pass event data', function() { - it( 'on custom events', function() { - var data; - $('#a').entwine({onfoo: function(e, d){data = d;} }); - $('#a').trigger('foo', {cheese: 'burger'}); - expect(data.cheese).toEqual( 'burger'); - }); - - it( 'on normal events', function() { - var data; - $('#a').entwine({onclick: function(e, d){data = d;} }); - $('#a').trigger('click', {finger: 'left'}); - expect(data.finger).toEqual( 'left'); - }); - - it( 'on submit', function() { - var data; + afterEach(function(){ + $('#dom_test').remove(); + }); - $('').appendTo('#dom_test'); - $('.foo').entwine({onsubmit: function(e, d){data = d; return false;} }) + describe('Events', function(){ - $('.foo').trigger('submit', {cheese: 'burger'}); - expect(data.cheese).toEqual( 'burger'); - }); - }); + beforeEach(function(){ + $.entwine.synchronous_mode(); + $.entwine.clear_all_rules(); + $('#dom_test').html('
'); + }); - describe( 'calls onchange on checkboxes properly', function() { - beforeEach(function() { - $('#dom_test').html(''); - }); + it('calls onfoo when foo triggered', function(){ + var a = 0; - it( 'calls onchange', function() { - var a = 0; + $('#a').entwine({ + onfoo: function(){a = 1;} + }); - $('#i').entwine({onchange: function(){ a += 1; }}); + expect(a).toEqual(0); - // Can't just "click()" - it's not the same as an actual click event - $('#i').trigger('focusin'); $('#i')[0].click(); - expect(a).toEqual(1); - }); + $('#a').trigger('foo'); + expect(a).toEqual(1); + }); - it( 'calls onchange only once per change', function() { - var a = 0; + it('only calls most specific onfoo when foo triggered', function(){ + var a = 0, b = 0; - $('#i').entwine({onchange: function(){ a += 1; }}); + $('#a.a').entwine({ + onfoo: function(){a = 1;} + }); + $('#a').entwine({ + onfoo: function(){b = 1;} + }); - $('#i').trigger('focusin'); $('#i')[0].click(); - expect(a).toEqual(1); + expect(a).toEqual(0); + expect(b).toEqual(0); - $('#i').trigger('focusout'); $('#i').trigger('focusin'); $('#i').trigger('focusout'); - expect(a).toEqual(1); + $('#a').trigger('foo'); + expect(a).toEqual(1); + expect(b).toEqual(0); + }); - $('#i')[0].click(); - expect(a).toEqual(2); + it('calls namespaced onfoo when foo triggered', function(){ + var a = 0; - }); + $('#a').entwine('bar', function($){return{ + onfoo: function(){a = 1;} + };}); - it( 'calls onchange even if checked attribute altered in mean time', function() { - var a = 0; + expect(a).toEqual(0); - $('#i').entwine({onchange: function(){ a += 1; }}); + $('#a').trigger('foo'); + expect(a).toEqual(1); + }); - $('#i').trigger('focusin'); $('#i')[0].click(); - expect(a).toEqual(1); + it('calls most specific namespaced onfoo and most specific non-namespaced onfoo when foo triggered', function(){ + var a = 0, b = 0, c = 0, d = 0; - $('#i').removeAttr('checked'); + $('#a.a').entwine({ + onfoo: function(){a = 1;} + }); + $('#a').entwine({ + onfoo: function(){b = 1;} + }); + $('#a.a').entwine('bar', function($){return{ + onfoo: function(){c = 1;} + };}); + $('#a').entwine('bar', function($){return{ + onfoo: function(){d = 1;} + };}); - $('#i').trigger('focusin'); $('#i')[0].click(); - expect(a).toEqual(2); - }); - }); + expect([a, b, c, d]).toEqual([0, 0, 0, 0]); - }); - + $('#a').trigger('foo'); + expect([a, b, c, d]).toEqual([1, 0, 1, 0]); + }); + + it('calls up correctly on _super', function(){ + var a = 0, b = 0; + + $('#a').entwine({ + onfoo: function(){a += 1;} + }); + $('#a.a').entwine({ + onfoo: function(){this._super(); b += 1; this._super();} + }); + + expect([a, b]).toEqual([0, 0]); + + $('#a').trigger('foo') + expect([a, b]).toEqual([2, 1]); + }); + + it('passes event object', function(){ + var event; + + $('#a').entwine({ + onfoo: function(e){event = e;} + }); + + $('#a').trigger('foo'); + expect(event.type).toBeDefined(); + expect(event.type).toEqual('foo'); + expect(event.target).toHaveAttr('id', 'a'); + }); + + it('delegates submit events to forms', function(){ + var a = 0; + $('').appendTo('#dom_test'); + + $('.foo').entwine({ + onsubmit: function(e, d){a = 1;} + }); + + expect(a).toEqual(0); + + $('.foo').trigger('submit'); + expect(a).toEqual(1); + }); + + describe('can pass event data', function(){ + + it('on custom events', function(){ + var data; + + $('#a').entwine({ + onfoo: function(e, d){data = d;} + }); + + $('#a').trigger('foo', {cheese: 'burger'}); + expect(data.cheese).toEqual('burger'); + }); + + it('on normal events', function(){ + var data; + + $('#a').entwine({ + onclick: function(e, d){data = d;} + }); + + $('#a').trigger('click', {finger: 'left'}); + expect(data.finger).toEqual('left'); + }); + + it('on submit', function(){ + var data; + $('').appendTo('#dom_test'); + + $('.foo').entwine({ + onsubmit: function(e, d){data = d; return false;} + }); + + $('.foo').trigger('submit', {cheese: 'burger'}); + expect(data.cheese).toEqual('burger'); + }); + }); + + describe('calls onchange on checkboxes properly', function(){ + + beforeEach(function(){ + $('#dom_test').html(''); + }); + + it('calls onchange', function(){ + var a = 0; + + $('#i').entwine({ + onchange: function(){a += 1;} + }); + + // Can't just "click()" - it's not the same as an actual click event + $('#i').trigger('focusin'); + $('#i')[0].click(); + expect(a).toEqual(1); + }); + + it('calls onchange only once per change', function(){ + var a = 0; + + $('#i').entwine({ + onchange: function(){a += 1;} + }); + + $('#i').trigger('focusin'); + $('#i')[0].click(); + expect(a).toEqual(1); + + $('#i').trigger('focusout'); + $('#i').trigger('focusin'); + $('#i').trigger('focusout'); + expect(a).toEqual(1); + + $('#i')[0].click(); + expect(a).toEqual(2); + }); + + it('calls onchange even if checked attribute altered in mean time', function(){ + var a = 0; + + $('#i').entwine({ + onchange: function(){a += 1;} + }); + + $('#i').trigger('focusin'); + $('#i')[0].click(); + expect(a).toEqual(1); + + $('#i').removeAttr('checked'); + + $('#i').trigger('focusin'); + $('#i')[0].click(); + expect(a).toEqual(2); + }); + }); + }); }); \ No newline at end of file diff --git a/thirdparty/jquery-entwine/spec/spec.entwine.namespaces.js b/thirdparty/jquery-entwine/spec/spec.entwine.namespaces.js index 14be77e96..f2aac6983 100644 --- a/thirdparty/jquery-entwine/spec/spec.entwine.namespaces.js +++ b/thirdparty/jquery-entwine/spec/spec.entwine.namespaces.js @@ -1,249 +1,275 @@ -describe( 'Entwine', function() { - - beforeEach(function() { - $('body').append('
') - }); - - afterEach(function() { - $('#dom_test').remove() - }); - - describe( 'Namespaces', function() { +describe('Entwine', function(){ - beforeEach(function() { - $.entwine.synchronous_mode(); - $.entwine.clear_all_rules() - $('#dom_test').html('
') - }); - - it( 'namespaced functions work (single definition mode)', function() { - $('#a').entwine('bar', function($){return{ - bar: function(){return 'a';} - }}) - expect($('#a').entwine('bar').bar()).toEqual( 'a'); - }); - - it( 'namespaced functions work (block definition mode)', function() { - $.entwine('zap', function($){ - $('#a').entwine({ - bar: function(){return 'a';} - }) - }); - expect($('#a').entwine('zap').bar()).toEqual( 'a'); - }); - - it( 'double-namespaced functions work (block definition mode)', function() { - $.entwine('zap', function($){ - $.entwine('pow', function($){ - $('#a').entwine({ - bar: function(){return 'a';} - }) - }) - }) - expect($('#a').entwine('zap.pow').bar()).toEqual( 'a'); - }) + beforeEach(function(){ + $('body').append('
'); + }); - it( 'revert to base namespacing work (block definition mode)', function() { - $.entwine('zap', function($){ - $.entwine('.pow', function($){ - $('#a').entwine({ - bar: function(){return 'a';} - }) - }) - }) - expect($('#a').entwine('pow').bar()).toEqual( 'a'); - }); - - it( 'internal to namespace, will look up functions in namespace before in base', function() { - var res = [] - $('#a').entwine({ - foo: function(){res.push(1);}, - bar: function(){res.push(2); this.foo();} - }) - $('#a').entwine('bar', function($){return{ - foo: function(){res.push(3);}, - bar: function(){res.push(4); $(this).foo();} - }}) - - $('#dom_test div').bar(); - expect(res).toEqual( [2, 1]); - $('#dom_test div').entwine('bar').bar(); - expect(res).toEqual( [2, 1, 4, 3]); - }); - - it( 'internal to namespace, will look up functions in namespace before in base, even in closure', function() { - var res = [] - $('#a').entwine({ - foo: function(){res.push(1);}, - bar: function(){res.push(2); this.foo();} - }) - $('#a').entwine('bar', function($){return{ - foo: function(){res.push(3);}, - bar: function(){res.push(4); $('#a').each(function(){ $(this).foo(); })} - }}) - - $('#dom_test div').bar(); - expect(res).toEqual( [2, 1]); - $('#dom_test div').entwine('bar').bar(); - expect(res).toEqual( [2, 1, 4, 3]); - }); - - it( 'internal to namespace, will look up functions in namespace before in base, even in onmatch', function() { - var res = [] - $('#a').entwine({ - foo: function(){res.push(1);}, - bar: function(){res.push(2); this.foo();} - }) - $('#a').entwine('bar', function($){return{ - foo: function(){res.push(3);} - }}) - $('#a.d').entwine('bar', function($){return{ - onmatch: function(){res.push(4); this.foo();} - }}) - - $('#dom_test div').bar(); - expect(res).toEqual( [2, 1]); - - $('#a').addClass('d'); - expect(res).toEqual( [2, 1, 4, 3]); - }); - - it( 'internal to namespace, will look up functions in base when not present in namespace', function() { - var res = [] - $('#a').entwine({ - foo: function(){res.push(1);} - }) - $('#a').entwine('bar', function($){return{ - bar: function(){res.push(2); this.foo();} - }}) - $('#dom_test div').entwine('bar').bar(); - expect(res).toEqual( [2, 1]); - }); - - it( 'internal to namespace, will not look up functions in base if present in namespace, even when not applicable to selector', function() { - var res = [] - $('#a').entwine('bar', function($){return{ - foo: function(){this.bar();} - }}) - $('#a').entwine({ - bar: function(){res.push(1);} - }) - $('span').entwine('bar', function($){return{ - bar: function(){res.push(2);} - }}) - - $('#a').entwine('bar').foo() - expect(res).toEqual( []); - }); - - it( 'internal to namespace, can be directed to base namespace', function() { - var res = [] - $('#a').entwine({ - foo: function(){res.push(1);}, - bar: function(){res.push(2); this.foo();} - }) - $('#a').entwine('bar', function($){return{ - foo: function(){res.push(3);}, - bar: function(){res.push(4); this.foo(); this.entwine('.').foo();} - }}) - $('#dom_test div').bar(); - expect(res).toEqual( [2, 1]); - $('#dom_test div').entwine('bar').bar(); - expect(res).toEqual( [2, 1, 4, 3, 1]); - }); - - it( 'internal to namespace, will look up functions in namespace called the same as a regular jQuery base function', function() { - var res = [] - $('#a').entwine('bar', function($){return{ - load: function(){res.push(1);}, - bar: function(){res.push(2); this.load();} - }}) - $('#dom_test div').entwine('bar').bar(); - expect(res).toEqual( [2, 1]); - }); + afterEach(function(){ + $('#dom_test').remove(); + }); - it( 'internal to namespace, can be directed to regular jQuery base function', function() { - var res = [] - $.fn.testy = function(){ res.push(1); } - $('#a').entwine('bar', function($){return{ - testy: function(){res.push(3);}, - bar: function(){res.push(2); this.entwine('.').testy();} - }}) - $('#dom_test div').entwine('bar').bar(); - expect(res).toEqual( [2, 1]); - }); - - it( 'internal to namespace, can be directed to sub namespace', function() { - var res = [] - $.entwine('zap', function($){ - $('#a').entwine({ - foo: function(){ res.push(1); this.entwine('pow').bar(); } - }) - - $.entwine('pow', function($){ - $('#a').entwine({ - bar: function(){ res.push(2); } - }) - }) - }) - $('#dom_test div').entwine('zap').foo(); - expect(res).toEqual( [1, 2]); - }); + describe('Namespaces', function(){ - it( 'internal to namespace, can be directed to unrelated namespace', function() { - var res = [] - $.entwine('zap', function($){ - $('#a').entwine({ - foo: function(){ res.push(1); this.entwine('.pow').bar(); } - }) - - $.entwine('pow', function($){ - $('#a').entwine({ - bar: function(){ res.push(2); } - }) - }) - }) - $.entwine('pow', function($){ - $('#a').entwine({ - bar: function(){ res.push(3); } - }) - }) - - $('#dom_test div').entwine('zap').foo(); - expect(res).toEqual( [1, 3]); - }); + beforeEach(function() { + $.entwine.synchronous_mode(); + $.entwine.clear_all_rules(); + $('#dom_test').html('
'); + }); - it( 'a function passed out of a namespace will remember its namespace', function() { - var res = [] - var func = function(func) { - func.call($('#a, #b')); - } - $('#a, #b').entwine('bar', function($){return{ - zap: function(){res.push($(this).attr('id'));}, - bar: function(){res.push(2); func(this.zap);} - }}) - $('#dom_test #a').entwine('bar').bar(); - expect(res).toEqual( [2, 'b', 'a']); - }); + it('namespaced functions work (single definition mode)', function(){ + $('#a').entwine('bar', function($){return{ + bar: function(){return 'a';} + };}); + + expect($('#a').entwine('bar').bar()).toEqual('a'); + }); + + it('namespaced functions work (block definition mode)', function(){ + $.entwine('zap', function($){ + $('#a').entwine({ + bar: function(){return 'a';} + }); + }); + + expect($('#a').entwine('zap').bar()).toEqual('a'); + }); + + it('double-namespaced functions work (block definition mode)', function(){ + $.entwine('zap', function($){ + $.entwine('pow', function($){ + $('#a').entwine({ + bar: function(){return 'a';} + }); + }); + }); + + expect($('#a').entwine('zap.pow').bar()).toEqual('a'); + }) + + it('revert to base namespacing work (block definition mode)', function(){ + $.entwine('zap', function($){ + $.entwine('.pow', function($){ + $('#a').entwine({ + bar: function(){return 'a';} + }); + }); + }); + + expect($('#a').entwine('pow').bar()).toEqual('a'); + }); + + it('internal to namespace, will look up functions in namespace before in base', function(){ + var res = []; + + $('#a').entwine({ + foo: function(){res.push(1);}, + bar: function(){res.push(2); this.foo();} + }); + $('#a').entwine('bar', function($){return{ + foo: function(){res.push(3);}, + bar: function(){res.push(4); $(this).foo();} + };}); + + $('#dom_test div').bar(); + expect(res).toEqual([2, 1]); + + $('#dom_test div').entwine('bar').bar(); + expect(res).toEqual([2, 1, 4, 3]); + }); + + it('internal to namespace, will look up functions in namespace before in base, even in closure', function(){ + var res = []; + + $('#a').entwine({ + foo: function(){res.push(1);}, + bar: function(){res.push(2); this.foo();} + }); + + $('#a').entwine('bar', function($){return{ + foo: function(){res.push(3);}, + bar: function(){ + res.push(4); + $('#a').each(function(){$(this).foo();}); + } + };}); + + $('#dom_test div').bar(); + expect(res).toEqual([2, 1]); + + $('#dom_test div').entwine('bar').bar(); + expect(res).toEqual([2, 1, 4, 3]); + }); + + it('internal to namespace, will look up functions in namespace before in base, even in onmatch', function(){ + var res = []; + + $('#a').entwine({ + foo: function(){res.push(1);}, + bar: function(){res.push(2); this.foo();} + }); + $('#a').entwine('bar', function($){return{ + foo: function(){res.push(3);} + };}); + $('#a.d').entwine('bar', function($){return{ + onmatch: function(){res.push(4); this.foo();} + };}); + + $('#dom_test div').bar(); + expect(res).toEqual([2, 1]); + + $('#a').addClass('d'); + expect(res).toEqual([2, 1, 4, 3]); + }); + + it('internal to namespace, will look up functions in base when not present in namespace', function(){ + var res = []; + + $('#a').entwine({ + foo: function(){res.push(1);} + }); + $('#a').entwine('bar', function($){return{ + bar: function(){res.push(2); this.foo();} + };}); + + $('#dom_test div').entwine('bar').bar(); + expect(res).toEqual([2, 1]); + }); + + it('internal to namespace, will not look up functions in base if present in namespace, even when not applicable to selector', function(){ + var res = []; + + $('#a').entwine('bar', function($){return{ + foo: function(){this.bar();} + };}); + $('#a').entwine({ + bar: function(){res.push(1);} + }); + $('span').entwine('bar', function($){return{ + bar: function(){res.push(2);} + };}); + + $('#a').entwine('bar').foo() + expect(res).toEqual([]); + }); + + it('internal to namespace, can be directed to base namespace', function(){ + var res = []; + + $('#a').entwine({ + foo: function(){res.push(1);}, + bar: function(){res.push(2); this.foo();} + }); + $('#a').entwine('bar', function($){return{ + foo: function(){res.push(3);}, + bar: function(){res.push(4); this.foo(); this.entwine('.').foo();} + };}); + + $('#dom_test div').bar(); + expect(res).toEqual([2, 1]); + + $('#dom_test div').entwine('bar').bar(); + expect(res).toEqual([2, 1, 4, 3, 1]); + }); + + it('internal to namespace, will look up functions in namespace called the same as a regular jQuery base function', function(){ + var res = []; + + $('#a').entwine('bar', function($){return{ + load: function(){res.push(1);}, + bar: function(){res.push(2); this.load();} + };}); + + $('#dom_test div').entwine('bar').bar(); + expect(res).toEqual([2, 1]); + }); + + it('internal to namespace, can be directed to regular jQuery base function', function(){ + var res = []; + + $.fn.testy = function(){res.push(1);} + + $('#a').entwine('bar', function($){return{ + testy: function(){res.push(3);}, + bar: function(){res.push(2); this.entwine('.').testy();} + };}); + + $('#dom_test div').entwine('bar').bar(); + expect(res).toEqual([2, 1]); + }); + + it('internal to namespace, can be directed to sub namespace', function(){ + var res = []; + + $.entwine('zap', function($){ + $('#a').entwine({ + foo: function(){res.push(1); this.entwine('pow').bar();} + }); + $.entwine('pow', function($){ + $('#a').entwine({ + bar: function(){res.push(2);} + }); + }); + }); + + $('#dom_test div').entwine('zap').foo(); + expect(res).toEqual([1, 2]); + }); + + it('internal to namespace, can be directed to unrelated namespace', function(){ + var res = []; + + $.entwine('zap', function($){ + $('#a').entwine({ + foo: function(){res.push(1); this.entwine('.pow').bar();} + }); + $.entwine('pow', function($){ + $('#a').entwine({ + bar: function(){res.push(2);} + }); + }); + }); + $.entwine('pow', function($){ + $('#a').entwine({ + bar: function(){res.push(3);} + }); + }); + + $('#dom_test div').entwine('zap').foo(); + expect(res).toEqual([1, 3]); + }); + + it('a function passed out of a namespace will remember its namespace', function(){ + var res = []; + var func = function(func){ + func.call($('#a, #b')); + }; + + $('#a, #b').entwine('bar', function($){return{ + zap: function(){res.push($(this).attr('id'));}, + bar: function(){res.push(2); func(this.zap);} + };}); + + $('#dom_test #a').entwine('bar').bar(); + expect(res).toEqual([2, 'b', 'a']); + }); + + it('using block functions', function(){ + var res = []; + + $('#a').entwine({ + foo: function(){res.push(1);} + }); + $('#a').entwine('bar', function($){return{ + foo: function(){res.push(3);} + };}); + + $('#dom_test div').foo(); + expect(res).toEqual([1]); + + $('#dom_test div').entwine('bar', function($){$(this).foo();}); + expect(res).toEqual([1, 3]); + }); + + }); - it( 'using block functions', function() { - var res = [] - $('#a').entwine({ - foo: function(){res.push(1);} - }) - $('#a').entwine('bar', function($){return{ - foo: function(){res.push(3);} - }}) - - $('#dom_test div').foo(); - expect(res).toEqual( [1]); - - $('#dom_test div').entwine('bar', function($){ - $(this).foo(); - }) - expect(res).toEqual( [1, 3]); - }); - - }); - }); \ No newline at end of file diff --git a/thirdparty/jquery-entwine/spec/spec.entwine.properties.js b/thirdparty/jquery-entwine/spec/spec.entwine.properties.js index 312ae1788..e529f8df4 100644 --- a/thirdparty/jquery-entwine/spec/spec.entwine.properties.js +++ b/thirdparty/jquery-entwine/spec/spec.entwine.properties.js @@ -1,98 +1,126 @@ -describe('Entwine', function() { - - beforeEach(function() { - $('body').append('
') - }); - afterEach(function() { - $('#dom_test').remove() - }); - - describe('Properties', function() { - - beforeEach(function() { - $.entwine.clear_all_rules(); - $('#dom_test').html('
'); - }); - - it('can define and get a basic property', function() { - $('#a').entwine({ - Foo: null - }); - expect($('.a').getFoo()).toBeNull(); - }); - it('can define and set a basic property', function() { - $('#a').entwine({ - Foo: null - }); - $('.a').setFoo(1); - expect($('.a').getFoo()).toEqual(1) - }); - it('can define a default value', function() { - $('#a').entwine({ - Foo: 1 - }); - expect($('.a').getFoo()).toEqual(1); - }); - it('can override a default value with a true-ish value', function() { - $('#a').entwine({ Foo: 1 }); - $('#a').setFoo(2); - expect($('.a').getFoo()).toEqual(2); - }); - it('can override a default value with a false-ish value', function() { - $('#a').entwine({ Foo: 1 }); - $('#a').setFoo(0); - expect($('.a').getFoo()).toEqual(0); - }); - it('should manage proprties in namespaces without clashing', function() { - $('#a').entwine({ - Foo: 1 - }); - $.entwine('test', function($) { - $('#a').entwine({ - Foo: 2 - }); - }); - expect($('.a').getFoo()).toEqual(1); - expect($('.a').entwine('test').getFoo()).toEqual(2) - $('.a').setFoo(4); - $('.a').entwine('test').setFoo(8); - expect($('.a').getFoo()).toEqual(4) - expect($('.a').entwine('test').getFoo()).toEqual(8) - }); - it('should manage directly setting proprties in namespaces without clashing', function() { - $('#a').entwine({ - Foo: null - }); - $.entwine('test', function($) { - $('#a').entwine({ - Foo: null - }); - }); - $('.a').entwineData('Foo', 4); - $('.a').entwine('test').entwineData('Foo', 8); - expect($('.a').entwineData('Foo')).toEqual(4) ; - expect($('.a').entwine('test').entwineData('Foo')).toEqual(8); - }); - describe('jQuery style accessors', function() { - it('can define and get a basic property', function() { - $('#a').entwine({ - Foo: null - }); - expect($('.a').Foo()).toBeNull(); - }); - it('can define and set a basic property', function() { - $('#a').entwine({ - Foo: null - }); - $('.a').Foo(1); - expect($('.a').Foo()).toEqual(1) - }); - it('can define a default value', function() { - $('#a').entwine({ - Foo: 1 - }); - expect($('.a').Foo()).toEqual(1) - }); - }); - }); +describe('Entwine', function(){ + + beforeEach(function(){ + $('body').append('
'); + }); + + afterEach(function(){ + $('#dom_test').remove(); + }); + + describe('Properties', function(){ + + beforeEach(function(){ + $.entwine.clear_all_rules(); + $('#dom_test').html('
'); + }); + + it('can define and get a basic property', function(){ + $('#a').entwine({ + Foo: null + }); + + expect($('.a').getFoo()).toBeNull(); + }); + + it('can define and set a basic property', function(){ + $('#a').entwine({ + Foo: null + }); + + $('.a').setFoo(1); + expect($('.a').getFoo()).toEqual(1) + }); + + it('can define a default value', function(){ + $('#a').entwine({ + Foo: 1 + }); + + expect($('.a').getFoo()).toEqual(1); + }); + + it('can override a default value with a true-ish value', function(){ + $('#a').entwine({ + Foo: 1 + }); + + $('#a').setFoo(2); + expect($('.a').getFoo()).toEqual(2); + }); + + it('can override a default value with a false-ish value', function(){ + $('#a').entwine({ + Foo: 1 + }); + + $('#a').setFoo(0); + expect($('.a').getFoo()).toEqual(0); + }); + + it('should manage proprties in namespaces without clashing', function(){ + $('#a').entwine({ + Foo: 1 + }); + + $.entwine('test', function($){ + $('#a').entwine({ + Foo: 2 + }); + }); + + expect($('.a').getFoo()).toEqual(1); + expect($('.a').entwine('test').getFoo()).toEqual(2) + + $('.a').setFoo(4); + $('.a').entwine('test').setFoo(8); + expect($('.a').getFoo()).toEqual(4) + expect($('.a').entwine('test').getFoo()).toEqual(8) + }); + + it('should manage directly setting properties in namespaces without clashing', function(){ + $('#a').entwine({ + Foo: null + }); + + $.entwine('test', function($){ + $('#a').entwine({ + Foo: null + }); + }); + + $('.a').entwineData('Foo', 4); + $('.a').entwine('test').entwineData('Foo', 8); + expect($('.a').entwineData('Foo')).toEqual(4); + expect($('.a').entwine('test').entwineData('Foo')).toEqual(8); + }); + + describe('jQuery style accessors', function(){ + + it('can define and get a basic property', function(){ + $('#a').entwine({ + Foo: null + }); + + expect($('.a').Foo()).toBeNull(); + }); + + it('can define and set a basic property', function(){ + $('#a').entwine({ + Foo: null + }); + + $('.a').Foo(1); + expect($('.a').Foo()).toEqual(1) + }); + + it('can define a default value', function(){ + $('#a').entwine({ + Foo: 1 + }); + + expect($('.a').Foo()).toEqual(1) + }); + }); + }); }); \ No newline at end of file diff --git a/thirdparty/jquery-entwine/spec/spec.entwine.super.js b/thirdparty/jquery-entwine/spec/spec.entwine.super.js index e4f522a44..497e0d373 100644 --- a/thirdparty/jquery-entwine/spec/spec.entwine.super.js +++ b/thirdparty/jquery-entwine/spec/spec.entwine.super.js @@ -1,88 +1,75 @@ -describe('Entwine', function() { - - beforeEach(function() { - $('body').append('
') - }); - afterEach(function() { - $('#dom_test').remove() - }); - - describe('Super', function() { - - beforeEach(function() { - $.entwine.clear_all_rules() - $('#dom_test').html('
Foo
Bar
') - }); - - it('can call the super function', function() { - var a = 1; - $('#a').entwine({ - foo: function() { - a *= 2; - } - }); - $('#a.a').entwine({ - foo: function() { - a += 2; - this._super(); - } - }); - $('#a').foo(); - expect(a).toEqual(6) - }); - it('super to a non-existant class should be ignored', function() { - var a = 1; - $('#a').entwine({ - foo: function() { - a *= 2; - this._super(); - } - }); - $('#a.a').entwine({ - foo: function() { - a += 2; - this._super(); - } - }); - $('#a').foo(); - expect(a).toEqual(6) - }); - it('can call super from two different functions without screwing up what super points to', function() { - var list = []; - $('#a').entwine({ - foo: function() { - list.push('foo'); - this.bar(); - }, - bar: function() { - list.push('bar'); - } - }); - $('#a.a').entwine({ - foo: function() { - list.push('foo2'); - this._super(); - list.push('foo2'); - this._super(); - }, - bar: function() { - list.push('bar2'); - this._super(); - } - }); - $('#a').foo(); - expect(list).toEqual(['foo2', 'foo', 'bar2', 'bar', 'foo2', 'foo', 'bar2', 'bar']) - }); - it('can override (and call via super) a non-entwine jquery function', function() { - var a = 1 - $('#a').entwine({ - text: function() { - a = this._super(); - } - }); - $('#a').text(); - expect(a).toEqual('Foo') - expect($('#b').text()).toEqual('Bar') - }); - }); +describe('Entwine', function(){ + + beforeEach(function(){ + $('body').append('
'); + }); + + afterEach(function(){ + $('#dom_test').remove(); + }); + + describe('Super', function(){ + + beforeEach(function(){ + $.entwine.clear_all_rules(); + $('#dom_test').html('
Foo
Bar
'); + }); + + it('can call the super function', function(){ + var a = 1; + + $('#a').entwine({ + foo: function(){a *= 2;} + }); + $('#a.a').entwine({ + foo: function(){a += 2; this._super();} + }); + + $('#a').foo(); + expect(a).toEqual(6) + }); + + it('super to a non-existant class should be ignored', function(){ + var a = 1; + + $('#a').entwine({ + foo: function(){a *= 2; this._super();} + }); + $('#a.a').entwine({ + foo: function(){a += 2; this._super();} + }); + + $('#a').foo(); + expect(a).toEqual(6) + }); + + it('can call super from two different functions without screwing up what super points to', function(){ + var list = []; + + $('#a').entwine({ + foo: function(){list.push('foo'); this.bar();}, + bar: function(){list.push('bar');} + }); + $('#a.a').entwine({ + foo: function(){list.push('foo2'); this._super(); list.push('foo2'); this._super();}, + bar: function(){list.push('bar2'); this._super();} + }); + + $('#a').foo(); + expect(list).toEqual(['foo2', 'foo', 'bar2', 'bar', 'foo2', 'foo', 'bar2', 'bar']) + }); + + it('can override (and call via super) a non-entwine jquery function', function(){ + var a = 1; + + $('#a').entwine({ + text: function(){a = this._super();} + }); + + expect($('#a').text()).toBeUndefined(); + expect(a).toEqual('Foo') + + expect($('#b').text()).toEqual('Bar') + }); + }); }); \ No newline at end of file diff --git a/thirdparty/jquery-entwine/spec/spec.entwine.synchronous.js b/thirdparty/jquery-entwine/spec/spec.entwine.synchronous.js index 3ffd0b3cd..40e8014fd 100644 --- a/thirdparty/jquery-entwine/spec/spec.entwine.synchronous.js +++ b/thirdparty/jquery-entwine/spec/spec.entwine.synchronous.js @@ -1,34 +1,41 @@ -describe( 'Entwine', function() { - - beforeEach(function() { - $.entwine.warningLevel = $.entwine.WARN_LEVEL_BESTPRACTISE; - $.entwine.synchronous_mode(true); - $('body').append('
'); - }); - - afterEach(function() { - $('#dom_test').remove(); - $.entwine.synchronous_mode(false); - }); +describe('Entwine', function(){ - describe( 'Synchronous Mode', function() { - - beforeEach(function() { + beforeEach(function() { + $.entwine.warningLevel = $.entwine.WARN_LEVEL_BESTPRACTISE; + $.entwine.synchronous_mode(true); + $('body').append('
'); + }); + + afterEach(function(){ + $('#dom_test').remove(); + $.entwine.synchronous_mode(false); + }); + + describe('Synchronous Mode', function(){ + + beforeEach(function(){ // $.entwine.clear_all_rules(); }); - - it( 'can modify the DOM in onmatch', function() { - $('#a').entwine({onmatch: function() {this.append('
');}}); + + it('can modify the DOM in onmatch', function(){ + $('#a').entwine({ + onmatch: function(){this.append('
');} + }); + $('#dom_test').append('
'); - expect($('#a .appended').length).toEqual(1); + expect($('#a .appended').length).toEqual(1); }); - - it( 'can modify the DOM in onunmatch', function() { - $('#a').entwine({onunmatch: function() {$('#dom_test').append('
');}}); - $('#dom_test').append('
'); + + it('can modify the DOM in onunmatch', function(){ + $('#a').entwine({ + onmatch: function(){ /* NOP */ }, + onunmatch: function(){$('#dom_test').append('
');} + }); + + $('#dom_test').append('
'); $('#dom_test').find('#a').remove(); expect($('#dom_test .appended').length).toEqual(1); }); - - }); + + }); }); \ No newline at end of file diff --git a/thirdparty/jquery-entwine/src/jquery.entwine.ctors.js b/thirdparty/jquery-entwine/src/jquery.entwine.ctors.js index 17941030a..dc8b0176d 100644 --- a/thirdparty/jquery-entwine/src/jquery.entwine.ctors.js +++ b/thirdparty/jquery-entwine/src/jquery.entwine.ctors.js @@ -45,6 +45,9 @@ bind: function(selector, k, v) { if ($.isFunction(v) && (k == 'onmatch' || k == 'onunmatch')) { + // When we add new matchers we need to trigger a full global recalc once, regardless of the DOM changes that triggered the event + this.matchersDirty = true; + this.bind_condesc(selector, k, v); return true; } @@ -61,15 +64,39 @@ * $('#foo').addClass('tabs'); $('#foo').tabFunctionBar(); * won't work. */ - $(document).bind('DOMMaybeChanged', function(){ + $(document).bind('DOMMaybeChanged', function(e){ + // Get the change delta. Can help stop us from doing heavy lifting if none of the changes could actually trigger an onmatch or onunmatch function + var changes = e.changes; + + // var start = (new Date).getTime(); + // For every namespace for (var k in $.entwine.namespaces) { + var namespace = $.entwine.namespaces[k]; + // That has constructors or destructors - var ctors = $.entwine.namespaces[k].store.ctors; + var ctors = namespace.store.ctors; if (ctors) { - // Keep a record of elements that have matched already - var matched = $([]), add, rem, res, rule, sel, ctor, dtor; + // Keep a record of elements that have matched some previous more specific rule. + // Not that we _don't_ actually do that until this is needed. If matched is null, it's not been calculated yet. + // We also keep track of any elements that have newly been taken or released by a specific rule + var matched = null, taken = $([]), released = $([]); + + // Updates matched to contain all the previously matched elements as if we'd been keeping track all along + var calcmatched = function(j){ + if (matched !== null) return; + matched = $([]); + + var cache, k = ctors.length; + while ((--k) > j) { + if (cache = ctors[k].cache) matched = matched.add(cache); + } + } + + // Some declared variables used in the loop + var add, rem, res, rule, sel, ctor, dtor, full; + // Stepping through each selector from most to least specific var j = ctors.length; while (j--) { @@ -78,40 +105,140 @@ sel = rule.selector.selector; ctor = rule.onmatch; dtor = rule.onunmatch; - - // Get the list of elements that match this selector, that haven't yet matched a more specific selector - res = add = $(sel).not(matched); - - // If this selector has a list of elements it matched against last time - if (rule.cache) { - // Find the ones that are extra this time - add = res.not(rule.cache); - if (dtor) { - // Find the ones that are gone this time - rem = rule.cache.not(res); - // And call the destructor on them - if (rem.length && !rule.onunmatchRunning) { - rule.onunmatchRunning = true; - ctors.onunmatchproxy(rem, j, dtor); - rule.onunmatchRunning = false; + + /* + Rule.cache might be stale or fresh. It'll be stale if + - some more specific selector now has some of rule.cache in it + - some change has happened that means new elements match this selector now + - some change has happened that means elements no longer match this selector + + The first we can just compare rules.cache with matched, removing anything that's there already. + */ + + // Reset the "elements that match this selector and no more specific selector with an onmatch rule" to null. + // Staying null means this selector is fresh. + res = null; + + // If this gets changed to true, it's too hard to do a delta update, so do a full update + full = false; + + if (namespace.matchersDirty || changes.global) { + // For now, just fall back to old version. We need to do something like changed.Subtree.find('*').andSelf().filter(sel), but that's _way_ slower on modern browsers than the below + full = true; + } + else { + // We don't deal with attributes yet, so any attribute change means we need to do a full recalc + for (var k in e.changes.attrs) { full = true; break; } + + /* + If a class changes, but it isn't listed in our selector, we don't care - the change couldn't affect whether or not any element matches + + If it is listed on our selector + - If it is on the direct match part, it could have added or removed the node it changed on + - If it is on the context part, it could have added or removed any node that were previously included or excluded because of a match or failure to match with the context required on that node + - NOTE: It might be on _both_ + */ + + var method = rule.selector.affectedBy(e.changes); + + if (method.classes.context) { + full = true; + } + else { + for (var k in method.classes.direct) { + calcmatched(j); + var recheck = e.changes.classes[k].not(matched); + + if (res === null) { + res = rule.cache ? rule.cache.not(taken).add(released.filter(sel)) : $([]); + } + + res = res.not(recheck).add(recheck.filter(sel)); } } } - - // Call the constructor on the newly matched ones - if (add.length && ctor && !rule.onmatchRunning) { - rule.onmatchRunning = true; - ctors.onmatchproxy(add, j, ctor); - rule.onmatchRunning = false; + + if (full) { + calcmatched(j); + res = $(sel).not(matched); + } + else { + if (!res) { + // We weren't stale because of any changes to the DOM that affected this selector, but more specific + // onmatches might have caused stale-ness + + // Do any of the previous released elements match this selector? + add = released.length && released.filter(sel); + + if (add && add.length) { + // Yes, so we're stale as we need to include them. Filter for any possible taken value at the same time + res = rule.cache ? rule.cache.not(taken).add(add) : add; + } + else { + // Do we think we own any of the elements now taken by more specific rules? + rem = taken.length && rule.cache && rule.cache.filter(taken); + + if (rem && rem.length) { + // Yes, so we're stale as we need to exclude them. + res = rule.cache.not(rem); + } + } + } + } + + // Res will be null if we know we are fresh (no full needed, selector not affectedBy changes) + if (res === null) { + // If we are tracking matched, add ourselves + if (matched && rule.cache) matched = matched.add(rule.cache); + } + else { + // If this selector has a list of elements it matched against last time + if (rule.cache) { + // Find the ones that are extra this time + add = res.not(rule.cache); + rem = rule.cache.not(res); + } + else { + add = res; rem = null; + } + + if ((add && add.length) || (rem && rem.length)) { + if (rem && rem.length) { + released = released.add(rem); + + if (dtor && !rule.onunmatchRunning) { + rule.onunmatchRunning = true; + ctors.onunmatchproxy(rem, j, dtor); + rule.onunmatchRunning = false; + } + } + + // Call the constructor on the newly matched ones + if (add && add.length) { + taken = taken.add(add); + released = released.not(add); + + if (ctor && !rule.onmatchRunning) { + rule.onmatchRunning = true; + ctors.onmatchproxy(add, j, ctor); + rule.onmatchRunning = false; + } + } + } + + // If we are tracking matched, add ourselves + if (matched) matched = matched.add(res); + + // And remember this list of matching elements again this selector, so next matching we can find the unmatched ones + rule.cache = res; } - - // Add these matched ones to the list tracking all elements matched so far - matched = matched.add(res); - // And remember this list of matching elements again this selector, so next matching we can find the unmatched ones - ctors[j].cache = res; } + + namespace.matchersDirty = false; } } + + // console.log((new Date).getTime() - start); }); diff --git a/thirdparty/jquery-entwine/src/jquery.entwine.dommaybechanged.js b/thirdparty/jquery-entwine/src/jquery.entwine.dommaybechanged.js index faa844998..1963c6fc4 100644 --- a/thirdparty/jquery-entwine/src/jquery.entwine.dommaybechanged.js +++ b/thirdparty/jquery-entwine/src/jquery.entwine.dommaybechanged.js @@ -1,17 +1,138 @@ (function($){ - + + /** Utility function to monkey-patch a jQuery method */ + var monkey = function( /* method, method, ...., patch */){ + var methods = $.makeArray(arguments); + var patch = methods.pop(); + + $.each(methods, function(i, method){ + var old = $.fn[method]; + + $.fn[method] = function() { + var self = this, args = $.makeArray(arguments); + + var rv = old.apply(self, args); + patch.apply(self, args); + return rv; + } + }); + } + /** What to call to run a function 'soon'. Normally setTimeout, but for syncronous mode we override so soon === now */ var runSoon = window.setTimeout; /** The timer handle for the asyncronous matching call */ - var check_id = null; - - /** Fire the change event. Only fires on the document node, so bind to that */ - var triggerEvent = function() { - $(document).triggerHandler('DOMMaybeChanged'); - check_id = null; - }; - + var ChangeDetails = Base.extend({ + + init: function() { + this.global = false; + this.attrs = {}; + this.classes = {}; + }, + + /** Fire the change event. Only fires on the document node, so bind to that */ + triggerEvent: function() { + // If we're not the active changes instance any more, don't trigger + if (changes != this) return; + + // Cancel any pending timeout (if we're directly called in the mean time) + if (this.check_id) clearTimeout(this.check_id); + + // Create a new event object + var event = $.Event("DOMMaybeChanged"); + event.changes = this; + + // Reset the global changes object to be a new instance (do before trigger, in case trigger fires changes itself) + changes = new ChangeDetails(); + + // Fire event + $(document).triggerHandler(event); + }, + + changed: function() { + if (!this.check_id) { + var self = this; + this.check_id = runSoon(function(){ self.check_id = null; self.triggerEvent(); }, 10); + } + }, + + addAll: function() { + if (this.global) return this; // If we've already flagged as a global change, just skip + + this.global = true; + this.changed(); + return this; + }, + + addSubtree: function(node) { + return this.addAll(); + }, + + /* For now we don't do this. It's expensive, and jquery.entwine.ctors doesn't use this information anyway */ + addSubtreeFuture: function(node) { + if (this.global) return this; // If we've already flagged as a global change, just skip + + this.subtree = this.subtree ? this.subtree.add(node) : $(node); + this.changed(); + return this; + }, + + addAttr: function(attr, node) { + if (this.global) return this; + + this.attrs[attr] = (attr in this.attrs) ? this.attrs[attr].add(node) : $(node); + this.changed(); + return this; + }, + + addClass: function(klass, node) { + if (this.global) return this; + + this.classes[klass] = (klass in this.classes) ? this.classes[klass].add(node) : $(node); + this.changed(); + return this; + } + }); + + var changes = new ChangeDetails(); + + + monkey('append', 'prepend', 'empty', 'html', function(){ + changes.addSubtree(this); + }); + + monkey('after', 'before', 'remove', 'detach', function(){ + changes.addSubtree(this.parent()); + }) + + monkey('removeAttr', function(attr){ + changes.addAttr(attr, this); + }); + + monkey('addClass', 'removeClass', 'toggleClass', function(klass){ + if (typeof klass == 'string') changes.addClass(klass, this); + }); + + monkey('attr', function(a, b){ + if (b !== undefined && typeof a == 'string') changes.addAttr(a, this); + else if (typeof a != 'string') { for (var k in a) changes.addAttr(k, this); } + }); + + /* + These manipulation functions call one or more of the above to do the actual manipulation: + appendTo -> append + prependTo -> prepend + insertBefore -> before + insertAfter -> after + replaceWith -> before || append + replaceAll -> replaceWith + text -> empty, appendWith + wrapAll -> insertBefore, append + wrapInner -> wrapAll || append + wrap -> wrapAll + unwrap -> replaceWith + */ + $.extend($.entwine, { /** * Make onmatch and onunmatch work in synchronous mode - that is, new elements will be detected immediately after @@ -19,46 +140,24 @@ * (otherwise we'd make it the default). */ synchronous_mode: function() { - if (check_id) clearTimeout(check_id); check_id = null; + if (changes && changes.check_id) clearTimeout(changes.check_id); + changes = new ChangeDetails(); + runSoon = function(func, delay){ func.call(this); return null; }; }, - + /** * Trigger onmatch and onunmatch now - usefull for after DOM manipulation by methods other than through jQuery. * Called automatically on document.ready */ triggerMatching: function() { - matching(); + changes.addAll(); //.triggerEvent(); } }); - - function registerMutateFunction() { - $.each(arguments, function(i,func){ - var old = $.fn[func]; - $.fn[func] = function() { - var rv = old.apply(this, arguments); - if (!check_id) check_id = runSoon(triggerEvent, 100); - return rv; - }; - }); - } - - function registerSetterGetterFunction() { - $.each(arguments, function(i,func){ - var old = $.fn[func]; - $.fn[func] = function(a, b) { - var rv = old.apply(this, arguments); - if (!check_id && (b !== undefined || typeof a != 'string')) check_id = runSoon(triggerEvent, 100); - return rv; - }; - }); - } - // Register core DOM manipulation methods - registerMutateFunction('append', 'prepend', 'after', 'before', 'wrap', 'removeAttr', 'addClass', 'removeClass', 'toggleClass', 'empty', 'remove'); - registerSetterGetterFunction('attr'); - // And on DOM ready, trigger matching once - $(function(){ triggerEvent(); }); - + $(function(){ + $.entwine.triggerMatching(); + }); + })(jQuery); \ No newline at end of file diff --git a/thirdparty/jquery-entwine/src/jquery.entwine.inspector.js b/thirdparty/jquery-entwine/src/jquery.entwine.inspector.js new file mode 100644 index 000000000..8db16917c --- /dev/null +++ b/thirdparty/jquery-entwine/src/jquery.entwine.inspector.js @@ -0,0 +1,226 @@ + +document.write([ +'' +].join("\n")); + +jQuery(function($){ + + var inspectorPanel = $('
').appendTo('body'); + var columnHolder = $('
').appendTo(inspectorPanel); + var optionsHolder = $('
').appendTo(inspectorPanel); + + inspectorPanel.css({ + top: -400, + visibility: 'hidden' + }); + + $('body').bind('keypress', function(e){ + if ((e.ctrlKey || e.metaKey) && e.which == 96) { + if (inspectorPanel.css('visibility') != 'visible') { + inspectorPanel.css({top: 0, visibility: 'visible'}); + $('body').css({marginTop: 400}); + initialise(); + } + else { + inspectorPanel.css({top: -400, visibility: 'hidden'}); + $('body').css({marginTop: 0}); + reset(); + } + + return false; + } + }); + + var showUnmatching = $('').appendTo(optionsHolder); + var showUnmatchingLabel = $('').appendTo(optionsHolder); + + showUnmatching.bind('click', function(){ + inspectorPanel.toggleClass('show-unmatched', $(this).val()); + }); + + var hovernode; + + var reset = function() { + $('.ei-entwined').unbind('.entwine-inspector').removeClass('ei-entwined'); + if (hovernode) hovernode.remove(); + } + + var initialise = function(){ + reset(); + + $.each($.entwine.namespaces, function(name, namespace){ + $.each(namespace.store, function(name, list){ + $.each(list, function(i, rule){ + var match = $(rule.selector.selector); + match.addClass('ei-entwined').bind('click.entwine-inspector', displaydetails); + }) + }); + }); + }; + + var displaydetails = function(e){ + e.preventDefault(); e.stopPropagation(); + + columnHolder.empty(); + + var columns = {}; + $.each(['elements', 'namespaces', 'methods', 'selectors'], function(i, col){ + columns[col] = $('

'+col+'

').appendTo(columnHolder); + }) + + var lists = {}; + + var ctr = 0; + + lists.elements = $('').appendTo(columns.elements); + + var displayelement = function(){ + var target = $(this); + + var li = $('
  • '); + var clone = $(this.cloneNode(false)); clone.removeClass('ei-entwined'); clone.removeAttr('style'); + + + var i = clone[0].attributes.length; + while (i--) { + var attr = clone[0].attributes.item(i); + if (attr.name != 'class' && attr.name != 'id' && attr.value.length > 20) attr.value = attr.value.substr(0, 18)+'..'+attr.value.substr(-2); + } + + li.append(clone); + + li.text(li.html()).attr('data-id', ++ctr).data('el', target).prependTo(lists.elements); + + var namespaces = $('').appendTo(columns.namespaces); + + $.each($.entwine.namespaces, function(name, namespace){ + var methods = $(''); + + $.each(namespace.store, function(method, list){ + + if (method == 'ctors') { + var matchselectors = $(''); + var unmatchselectors = $(''); + + $.each(list, function(i, rule){ + var matchitem = $('
  • '+rule.selector.selector+'
  • ').prependTo(matchselectors); + var unmatchitem = rule.onunmatch ? $('
  • '+rule.selector.selector+'
  • ').prependTo(unmatchselectors) : null; + + if (target.is(rule.selector.selector)) { + matchitem.addClass('matching'); unmatchitem && unmatchitem.addClass('matching'); + + if (!methods.parent().length) { + $('
  • '+name+'
  • ').prependTo(namespaces); + methods.appendTo(columns.methods); + } + + if (!matchselectors.parent().length) { + $('
  • onmatch
  • ').prependTo(methods); + matchselectors.appendTo(columns.selectors); + } + + if (rule.onunmatch && !unmatchselectors.parent().length) { + $('
  • onunmatch
  • ').prependTo(methods); + unmatchselectors.appendTo(columns.selectors); + } + } + }); + } + else { + var selectors = $(''); + + $.each(list, function(i, rule){ + var ruleitem = $('
  • '+rule.selector.selector+'
  • ').prependTo(selectors); + + if (target.is(rule.selector.selector)){ + ruleitem.addClass('matching'); + + if (!methods.parent().length) { + $('
  • '+name+'
  • ').prependTo(namespaces); + methods.appendTo(columns.methods); + } + + if (!selectors.parent().length) { + $('
  • '+method+'
  • ').prependTo(methods); + selectors.appendTo(columns.selectors); + } + } + }) + } + }); + }); + }; + + $.each($(e.target).parents().andSelf().filter('.ei-entwined'), displayelement); + $('#ei-elements > ul:first > li:first').click(); + } + + var activatelist = function(list) { + list = $(list); + + list.siblings('ul').css('display', 'none'); + + list.css('display', 'block'); + list.children().first().click(); + } + + $('#entwine-inspector').live('mouseleave', function(){ + if (hovernode) hovernode.hide(); + }) + + $('#entwine-inspector').live('mouseenter', function(){ + if (hovernode) hovernode.show(); + }) + + $('#ei-elements > ul > li').live('click', function(e){ + var target = $(e.target), id = target.attr('data-id'); + target.addClass('selected').siblings().removeClass('selected'); + + if (!hovernode) { + hovernode = $('
    ').appendTo('body'); + } + + var hover = target.data('el'); + hovernode.css({width: hover.outerWidth()-2, height: hover.outerHeight()-2, top: hover.offset().top, left: hover.offset().left}); + + $('.ei-selected').removeClass('ei-selected'); + + activatelist('#ei-namespaces ul[data-element='+id+']'); + }); + + $('#ei-namespaces > ul > li').live('click', function(e){ + var target = $(e.target), namespace = target.attr('data-namespace'); + target.addClass('selected').siblings().removeClass('selected'); + + activatelist('#ei-methods ul[data-namespace='+namespace+']'); + }); + + $('#ei-methods > ul > li').live('click', function(e){ + var target = $(e.target), method = target.attr('data-method'); + target.addClass('selected').siblings().removeClass('selected'); + + activatelist('#ei-selectors ul[data-method='+method+']'); + }); + +}); \ No newline at end of file diff --git a/thirdparty/jquery-entwine/src/jquery.selector.affectedby.js b/thirdparty/jquery-entwine/src/jquery.selector.affectedby.js new file mode 100644 index 000000000..0744faef7 --- /dev/null +++ b/thirdparty/jquery-entwine/src/jquery.selector.affectedby.js @@ -0,0 +1,58 @@ +(function($) { + + // TODO: + // Make attributes & IDs work + + var DIRECT = /DIRECT/g; + var CONTEXT = /CONTEXT/g; + var EITHER = /DIRECT|CONTEXT/g; + + $.selector.SelectorBase.addMethod('affectedBy', function(props) { + this.affectedBy = new Function('props', ([ + 'var direct_classes, context_classes, direct_attrs, context_attrs, t;', + this.ABC_compile().replace(DIRECT, 'direct').replace(CONTEXT, 'context'), + 'return {classes: {context: context_classes, direct: direct_classes}, attrs: {context: context_attrs, direct: direct_attrs}};' + ]).join("\n")); + + // DEBUG: Print out the compiled funciton + // console.log(this.selector, ''+this.affectedBy); + + return this.affectedBy(props); + }); + + $.selector.SimpleSelector.addMethod('ABC_compile', function() { + var parts = []; + + $.each(this.classes, function(i, cls){ + parts[parts.length] = "if (t = props.classes['"+cls+"']) (DIRECT_classes || (DIRECT_classes = {}))['"+cls+"'] = t;"; + }); + + $.each(this.nots, function(i, not){ + parts[parts.length] = not.ABC_compile(); + }); + + return parts.join("\n"); + }); + + $.selector.Selector.addMethod('ABC_compile', function(arg){ + var parts = []; + var i = this.parts.length-1; + + parts[parts.length] = this.parts[i].ABC_compile(); + while ((i = i - 2) >= 0) parts[parts.length] = this.parts[i].ABC_compile().replace(EITHER, 'CONTEXT'); + + return parts.join("\n"); + }); + + $.selector.SelectorsGroup.addMethod('ABC_compile', function(){ + var parts = []; + + $.each(this.parts, function(i,part){ + parts[parts.length] = part.ABC_compile(); + }); + + return parts.join("\n"); + }); + + +})(jQuery);