Skip to content
This repository
Browse code

API CHANGE: Upgrade jquery entwine

  • Loading branch information...
commit bbd1bb7495719f763ee4af77746f87b27c16f45c 1 parent 8177c93
Hamish Friedlander authored June 12, 2012 chillu committed June 15, 2012

Showing 25 changed files with 5,048 additions and 11,296 deletions. Show diff stats Hide diff stats

  1. 6  thirdparty/jquery-entwine/.piston.yml
  2. 5  thirdparty/jquery-entwine/build.sh
  3. 430  thirdparty/jquery-entwine/dist/jquery.concrete-dist.js
  4. 430  thirdparty/jquery-entwine/dist/jquery.entwine-dist.js
  5. 17  thirdparty/jquery-entwine/spec/SpecRunner.html
  6. 115  thirdparty/jquery-entwine/spec/spec.entwine.addrem.js
  7. 8  thirdparty/jquery-entwine/spec/spec.entwine.basics.js
  8. 141  thirdparty/jquery-entwine/spec/spec.entwine.eventcapture.js
  9. 133  thirdparty/jquery-entwine/src/domevents/jquery.entwine.domevents.addrem.js
  10. 46  ...y-entwine/src/{jquery.entwine.dommaybechanged.js → domevents/jquery.entwine.domevents.maybechanged.js}
  11. 63  thirdparty/jquery-entwine/src/jquery.entwine.addrem.js
  12. 11  thirdparty/jquery-entwine/src/jquery.entwine.ctors.js
  13. 111  thirdparty/jquery-entwine/src/jquery.entwine.eventcapture.js
  14. 14  thirdparty/jquery-entwine/src/jquery.entwine.events.js
  15. 2  thirdparty/jquery-entwine/src/jquery.entwine.inspector.js
  16. 6  thirdparty/jquery-entwine/src/jquery.entwine.js
  17. 188  thirdparty/jquery-entwine/vendor/jasmine-1.0.1/jasmine-html.js
  18. 166  thirdparty/jquery-entwine/vendor/jasmine-1.0.1/jasmine.css
  19. 2  thirdparty/jquery-entwine/vendor/{jasmine-1.0.1 → jasmine}/MIT.LICENSE
  20. 616  thirdparty/jquery-entwine/vendor/jasmine/jasmine-html.js
  21. 81  thirdparty/jquery-entwine/vendor/jasmine/jasmine.css
  22. 584  thirdparty/jquery-entwine/vendor/{jasmine-1.0.1 → jasmine}/jasmine.js
  23. 8,374  thirdparty/jquery-entwine/vendor/jquery-1.5.2.js
  24. 4,765  thirdparty/jquery-entwine/vendor/{jquery-1.6.js → jquery-1.7.2.js}
  25. 30  thirdparty/jquery-entwine/vendor/jquery.selector/jquery.selector.matches.js
6  thirdparty/jquery-entwine/.piston.yml
... ...
@@ -1,8 +1,8 @@
1 1
 --- 
2 2
 repository_url: https://github.com/hafriedlander/jquery.entwine.git
3  
-repository_class: Piston::Git::Repository
  3
+format: 1
4 4
 handler: 
5  
-  commit: 860ffe280044c3f88e64ba400f8f17b5d2616c39
6 5
   branch: master
  6
+  commit: ca22d2b88771cf3c65e65288c4991050a42883a3
  7
+repository_class: Piston::Git::Repository
7 8
 lock: false
8  
-format: 1
5  thirdparty/jquery-entwine/build.sh
@@ -16,9 +16,12 @@ for x in \
16 16
 	src/jquery.selector.affectedby.js \
17 17
 	src/jquery.focusinout.js \
18 18
 	src/jquery.entwine.js \
19  
-	src/jquery.entwine.dommaybechanged.js \
  19
+	src/domevents/jquery.entwine.domevents.addrem.js \
  20
+	src/domevents/jquery.entwine.domevents.maybechanged.js \
20 21
 	src/jquery.entwine.events.js \
  22
+	src/jquery.entwine.eventcapture.js \
21 23
 	src/jquery.entwine.ctors.js \
  24
+	src/jquery.entwine.addrem.js \
22 25
 	src/jquery.entwine.properties.js \
23 26
 	src/jquery.entwine.legacy.js
24 27
 do \
430  thirdparty/jquery-entwine/dist/jquery.concrete-dist.js
@@ -353,20 +353,6 @@ Sizzle is good for finding elements for a selector, but not so good for telling
353 353
 	// Does browser support Element.children
354 354
 	var hasChildren = div.children && div.children[0].tagName == 'FORM';
355 355
 
356  
-	var FUNC_IN  = /^\s*function\s*\([^)]*\)\s*\{/;
357  
-	var FUNC_OUT = /}\s*$/;
358  
-
359  
-	var funcToString = function(f) {
360  
-		return (''+f).replace(FUNC_IN,'').replace(FUNC_OUT,'');
361  
-	};
362  
-
363  
-	// Can we use Function#toString ?
364  
-	try {
365  
-		var testFunc = function(){ return 'good'; };
366  
-		if ((new Function('',funcToString(testFunc)))() != 'good') funcToString = false;
367  
-	}
368  
-	catch(e) { funcToString = false; console.log(e.message);/*pass*/ }
369  
-
370 356
 	/**** INTRO ****/
371 357
 	
372 358
 	var GOOD = /GOOD/g;
@@ -472,7 +458,13 @@ Sizzle is good for finding elements for a selector, but not so good for telling
472 458
 			this.wsattrs[attr] = true;
473 459
 			return join([this.uses_attr(attr), 'var _WS_'+varForAttr(attr)+' = " "+'+varForAttr(attr)+'+" ";']); 
474 460
 		},
475  
-		
  461
+
  462
+		uses_jqueryFilters: function() {
  463
+			if (this.jqueryFiltersAdded) return;
  464
+			this.jqueryFiltersAdded = true;
  465
+			return 'var _$filters = jQuery.find.selectors.filters;';
  466
+		},
  467
+
476 468
 		save: function(lbl) {
477 469
 			return 'var el'+lbl+' = el;';
478 470
 		},
@@ -584,12 +576,8 @@ Sizzle is good for finding elements for a selector, but not so good for telling
584 576
 				js[js.length] = ( typeof check == 'function' ? check.apply(this, pscls[1]) : check );
585 577
 			}
586 578
 			else if (check = $.find.selectors.filters[pscls[0]]) {
587  
-				if (funcToString) {
588  
-					js[js.length] = funcToString(check).replace(/elem/g,'el').replace(/return([^;]+);/,'if (!($1)) BAD;');
589  
-				}
590  
-				else {
591  
-					js[js.length] = 'if (!$.find.selectors.filters.'+pscls[0]+'(el)) BAD;';
592  
-				}
  579
+				js[js.length] = el.uses_jqueryFilters();
  580
+				js[js.length] = 'if (!_$filters.'+pscls[0]+'(el)) BAD;';
593 581
 			}
594 582
 		});
595 583
 		
@@ -845,8 +833,10 @@ catch (e) {
845 833
 			for (var k in $.fn) { if ($.fn[k].isentwinemethod) delete $.fn[k]; }
846 834
 			// Remove bound events - TODO: Make this pluggable, so this code can be moved to jquery.entwine.events.js
847 835
 			$(document).unbind('.entwine');
  836
+			$(window).unbind('.entwine');
848 837
 			// Remove namespaces, and start over again
849  
-			namespaces = $.entwine.namespaces = {};
  838
+			for (var k in namespaces) delete namespaces[k];
  839
+			for (var k in $.entwine.capture_bindings) delete $.entwine.capture_bindings[k];
850 840
 		},
851 841
 		
852 842
 		WARN_LEVEL_NONE: 0,
@@ -997,7 +987,7 @@ catch (e) {
997 987
 				
998 988
 				for (var i = 0; i < handlers.length; i++) {
999 989
 					var handler = handlers[i], builder;
1000  
-					
  990
+
1001 991
 					// Inject jQuery object method overrides
1002 992
 					if (builder = handler.namespaceMethodOverrides) {
1003 993
 						var overrides = builder(this);
@@ -1182,7 +1172,144 @@ catch (e) {
1182 1172
 ;
1183 1173
 
1184 1174
 
1185  
-/* src/jquery.entwine.dommaybechanged.js */
  1175
+/* src/domevents/jquery.entwine.domevents.addrem.js */
  1176
+
  1177
+(function($){
  1178
+
  1179
+	// Gets all the child elements of a particular elements, stores it in an array
  1180
+	function getElements(store, original) {
  1181
+		var node, i = store.length, next = original.firstChild;
  1182
+
  1183
+		while ((node = next)) {
  1184
+			if (node.nodeType === 1) store[i++] = node;
  1185
+			next = node.firstChild || node.nextSibling;
  1186
+			while (!next && (node = node.parentNode) && node !== original) next = node.nextSibling;
  1187
+		}
  1188
+	}
  1189
+
  1190
+	// This might be faster? Or slower? @todo: benchmark.
  1191
+	function getElementsAlt(store, node) {
  1192
+		if (node.getElementsByTagName) {
  1193
+			var els = node.getElementsByTagName('*'), len = els.length, i = 0, j = store.length;
  1194
+			for(; i < len; i++, j++) {
  1195
+				store[j] = els[i];
  1196
+			}
  1197
+		}
  1198
+		else if (node.childNodes) {
  1199
+			var els = node.childNodes, len = els.length, i = 0;
  1200
+			for(; i < len; i++) {
  1201
+				getElements(store, els[i]);
  1202
+			}
  1203
+		}
  1204
+	}
  1205
+
  1206
+	var dontTrigger = false;
  1207
+
  1208
+	// Monkey patch $.fn.domManip to catch all regular jQuery add element calls
  1209
+	var _domManip = $.prototype.domManip;
  1210
+	$.prototype.domManip = function(args, table, callback) {
  1211
+		if (!callback.patched) {
  1212
+			var original = callback;
  1213
+			arguments[2] = function(elem){
  1214
+				var added = [];
  1215
+
  1216
+				if (!dontTrigger) {
  1217
+					if (elem.nodeType == 1) added[added.length] = elem;
  1218
+					getElements(added, elem);
  1219
+				}
  1220
+
  1221
+				var rv = original.apply(this, arguments);
  1222
+
  1223
+				if (!dontTrigger && added.length) {
  1224
+					var event = $.Event('EntwineElementsAdded');
  1225
+					event.targets = added;
  1226
+					$(document).triggerHandler(event);
  1227
+				}
  1228
+
  1229
+				return rv;
  1230
+			}
  1231
+			arguments[2].patched = true;
  1232
+		}
  1233
+
  1234
+		return _domManip.apply(this, arguments);
  1235
+	}
  1236
+
  1237
+	// Monkey patch $.fn.html to catch when jQuery sets innerHTML directly
  1238
+	var _html = $.prototype.html;
  1239
+	$.prototype.html = function(value) {
  1240
+		if (value === undefined) return _html.apply(this, arguments);
  1241
+
  1242
+		dontTrigger = true;
  1243
+		var res = _html.apply(this, arguments);
  1244
+		dontTrigger = false;
  1245
+
  1246
+		var added = [];
  1247
+
  1248
+		var i = 0, length = this.length;
  1249
+		for (; i < length; i++ ) getElements(added, this[i]);
  1250
+
  1251
+		var event = $.Event('EntwineElementsAdded');
  1252
+		event.targets = added;
  1253
+		$(document).triggerHandler(event);
  1254
+
  1255
+		return res;
  1256
+	}
  1257
+
  1258
+	// If this is true, we've changed something to call cleanData so that we can catch the elements, but we don't
  1259
+	// want to call the underlying original $.cleanData
  1260
+	var supressActualClean = false;
  1261
+
  1262
+	// Monkey patch $.cleanData to catch element removal
  1263
+	var _cleanData = $.cleanData;
  1264
+	$.cleanData = function( elems ) {
  1265
+		// By default we can assume all elements passed are legitimately being removeed
  1266
+		var removed = elems;
  1267
+
  1268
+		// Except if we're supressing actual clean - we might be being called by jQuery "being careful" about detaching nodes
  1269
+		// before attaching them. So we need to check to make sure these nodes currently are in a document
  1270
+		if (supressActualClean) {
  1271
+			var i = 0, len = elems.length, removed = [], ri = 0;
  1272
+			for(; i < len; i++) {
  1273
+				var node = elems[i], current = node;
  1274
+				while (current = current.parentNode) {
  1275
+					if (current.nodeType == 9) { removed[ri++] = node; break; }
  1276
+				}
  1277
+			}
  1278
+		}
  1279
+
  1280
+		if (removed.length) {
  1281
+			var event = $.Event('EntwineElementsRemoved');
  1282
+			event.targets = removed;
  1283
+			$(document).triggerHandler(event);
  1284
+		}
  1285
+
  1286
+		if (!supressActualClean) _cleanData.apply(this, arguments);
  1287
+	}
  1288
+
  1289
+	// Monkey patch $.fn.remove to catch when we're just detaching (keepdata == 1) -
  1290
+	// this doesn't call cleanData but still needs to trigger event
  1291
+	var _remove = $.prototype.remove;
  1292
+	$.prototype.remove = function(selector, keepdata) {
  1293
+		supressActualClean = keepdata;
  1294
+		var rv = _remove.call(this, selector);
  1295
+		supressActualClean = false;
  1296
+		return rv;
  1297
+	}
  1298
+
  1299
+	// And on DOM ready, trigger adding once
  1300
+	$(function(){
  1301
+		var added = []; getElements(added, document);
  1302
+
  1303
+		var event = $.Event('EntwineElementsAdded');
  1304
+		event.targets = added;
  1305
+		$(document).triggerHandler(event);
  1306
+	});
  1307
+
  1308
+
  1309
+})(jQuery);;
  1310
+
  1311
+
  1312
+/* src/domevents/jquery.entwine.domevents.maybechanged.js */
1186 1313
 
1187 1314
 (function($){
1188 1315
 
@@ -1224,15 +1351,11 @@ catch (e) {
1224 1351
 			// Cancel any pending timeout (if we're directly called in the mean time)
1225 1352
 			if (this.check_id) clearTimeout(this.check_id);
1226 1353
 
1227  
-			// Create a new event object
1228  
-			var event = $.Event("DOMMaybeChanged");
1229  
-			event.changes = this;
1230  
-
1231 1354
 			// Reset the global changes object to be a new instance (do before trigger, in case trigger fires changes itself)
1232 1355
 			changes = new ChangeDetails();
1233 1356
 
1234 1357
 			// Fire event
1235  
-			$(document).triggerHandler(event);
  1358
+			$(document).triggerHandler("EntwineSubtreeMaybeChanged", [this]);
1236 1359
 		},
1237 1360
 
1238 1361
 		changed: function() {
@@ -1282,14 +1405,22 @@ catch (e) {
1282 1405
 
1283 1406
 	var changes = new ChangeDetails();
1284 1407
 
  1408
+	// Element add events trigger maybechanged events
  1409
+
  1410
+	$(document).bind('EntwineElementsAdded', function(e){ changes.addSubtree(e.targets); });
1285 1411
 
1286  
-	monkey('append', 'prepend', 'empty', 'html', function(){
1287  
-		changes.addSubtree(this);
  1412
+	// Element remove events trigger maybechanged events, but we have to wait until after the nodes are actually removed
  1413
+	// (EntwineElementsRemoved fires _just before_ the elements are removed so the data still exists), especially in syncronous mode
  1414
+
  1415
+	var removed = null;
  1416
+	$(document).bind('EntwineElementsRemoved', function(e){ removed = e.targets; });
  1417
+
  1418
+	monkey('remove', 'html', 'empty', function(){
  1419
+		var subtree = removed; removed = null;
  1420
+		if (subtree) changes.addSubtree(subtree);
1288 1421
 	});
1289 1422
 
1290  
-	monkey('after', 'before', 'remove', 'detach', function(){
1291  
-		changes.addSubtree(this.parent());
1292  
-	})
  1423
+	// We also need to know when an attribute, class, etc changes. Patch the relevant jQuery methods here
1293 1424
 
1294 1425
 	monkey('removeAttr', function(attr){
1295 1426
 		changes.addAttr(attr, this);
@@ -1304,20 +1435,7 @@ catch (e) {
1304 1435
 		else if (typeof a != 'string') { for (var k in a) changes.addAttr(k, this); }
1305 1436
 	});
1306 1437
 
1307  
-	/*
1308  
-	These manipulation functions call one or more of the above to do the actual manipulation:
1309  
-	appendTo -> append
1310  
-	prependTo -> prepend
1311  
-	insertBefore -> before
1312  
-	insertAfter -> after
1313  
-	replaceWith -> before || append
1314  
-	replaceAll -> replaceWith
1315  
-	text -> empty, appendWith
1316  
-	wrapAll -> insertBefore, append
1317  
-	wrapInner -> wrapAll || append
1318  
-	wrap -> wrapAll
1319  
-	unwrap -> replaceWith
1320  
-	*/
  1438
+	// Add some usefull accessors to $.entwine
1321 1439
 
1322 1440
 	$.extend($.entwine, {
1323 1441
 		/**
@@ -1337,15 +1455,10 @@ catch (e) {
1337 1455
 		 * Called automatically on document.ready
1338 1456
 		 */
1339 1457
 		triggerMatching: function() {
1340  
-			changes.addAll(); //.triggerEvent();
  1458
+			changes.addAll();
1341 1459
 		}
1342 1460
 	});
1343 1461
 
1344  
-	// And on DOM ready, trigger matching once
1345  
-	$(function(){
1346  
-		$.entwine.triggerMatching();
1347  
-	});
1348  
-
1349 1462
 })(jQuery);;
1350 1463
 
1351 1464
 
@@ -1586,24 +1699,138 @@ catch (e) {
1586 1699
 	// Find all forms and bind onsubmit to trigger on the document too. 
1587 1700
 	// This is the only event that can't be grabbed via delegation
1588 1701
 	
1589  
-	var form_binding_cache = $([]); // A cache for already-handled form elements
1590  
-	var delegate_submit = function(e, data){ 
  1702
+	var delegate_submit = function(e, data){
1591 1703
 		var delegationEvent = $.Event('delegatedSubmit'); delegationEvent.delegatedEvent = e;
1592 1704
 		return $(document).trigger(delegationEvent, data); 
1593 1705
 	};
1594 1706
 
1595  
-	$(document).bind('DOMMaybeChanged', function(){
1596  
-		var forms = $('form');
1597  
-		// Only bind to forms we haven't processed yet
1598  
-		forms.not(form_binding_cache).bind('submit', delegate_submit);
1599  
-		// Then remember the current set of forms
1600  
-		form_binding_cache = forms;
  1707
+	$(document).bind('EntwineElementsAdded', function(e){
  1708
+		var forms = $(e.targets).filter('form');
  1709
+		if (!forms.length) return;
  1710
+
  1711
+		forms.bind('submit.entwine_delegate_submit', delegate_submit);
1601 1712
 	});
1602 1713
 
1603 1714
 })(jQuery);
1604 1715
 	;
1605 1716
 
1606 1717
 
  1718
+/* src/jquery.entwine.eventcapture.js */
  1719
+
  1720
+(function($) {
  1721
+
  1722
+	$.entwine.Namespace.addMethods({
  1723
+		bind_capture: function(selector, event, name, capture) {
  1724
+			var store  = this.captures || (this.captures = {});
  1725
+			var rulelists = store[event] || (store[event] = {});
  1726
+			var rulelist = rulelists[name] || (rulelists[name] = $.entwine.RuleList());
  1727
+
  1728
+			rule = rulelist.addRule(selector, event);
  1729
+			rule.handler = name;
  1730
+
  1731
+			this.bind_proxy(selector, name, capture);
  1732
+		}
  1733
+	});
  1734
+
  1735
+	var bindings = $.entwine.capture_bindings = {};
  1736
+
  1737
+	var event_proxy = function(event) {
  1738
+		return function(e) {
  1739
+			var namespace, capturelists, forevent, capturelist, rule, handler, sel;
  1740
+
  1741
+			for (var k in $.entwine.namespaces) {
  1742
+				namespace = $.entwine.namespaces[k];
  1743
+				capturelists = namespace.captures;
  1744
+
  1745
+				if (capturelists && (forevent = capturelists[event])) {
  1746
+					for (var k in forevent) {
  1747
+						var capturelist = forevent[k];
  1748
+						var triggered = namespace.$([]);
  1749
+
  1750
+						// Stepping through each selector from most to least specific
  1751
+						var j = capturelist.length;
  1752
+						while (j--) {
  1753
+							rule = capturelist[j];
  1754
+							handler = rule.handler;
  1755
+							sel = rule.selector.selector;
  1756
+
  1757
+							var matching = namespace.$(sel).not(triggered);
  1758
+							matching[handler].apply(matching, arguments);
  1759
+
  1760
+							triggered = triggered.add(matching);
  1761
+						}
  1762
+					}
  1763
+				}
  1764
+			}
  1765
+		}
  1766
+	};
  1767
+
  1768
+	var selector_proxy = function(selector, handler, includechildren) {
  1769
+		var matcher = $.selector(selector);
  1770
+		return function(e){
  1771
+			if (matcher.matches(e.target)) return handler.apply(this, arguments);
  1772
+		}
  1773
+	};
  1774
+
  1775
+	var window_proxy = function(selector, handler, includechildren) {
  1776
+		return function(e){
  1777
+			if (e.target === window) return handler.apply(this, arguments);
  1778
+		}
  1779
+	};
  1780
+
  1781
+	var property_proxy = function(property, handler, includechildren) {
  1782
+		var matcher;
  1783
+
  1784
+		return function(e){
  1785
+			var match = this['get'+property]();
  1786
+
  1787
+			if (typeof(match) == 'string') {
  1788
+				var matcher = (matcher && match == matcher.selector) ? matcher : $.selector(match);
  1789
+				if (matcher.matches(e.target)) return handler.apply(this, arguments);
  1790
+			}
  1791
+			else {
  1792
+				if ($.inArray(e.target, match) !== -1) return handler.apply(this, arguments);
  1793
+			}
  1794
+		}
  1795
+	};
  1796
+
  1797
+	$.entwine.Namespace.addHandler({
  1798
+		order: 10,
  1799
+
  1800
+		bind: function(selector, k, v) {
  1801
+			var match;
  1802
+			if ($.isPlainObject(v) && (match = k.match(/^from\s*(.*)/))) {
  1803
+				var from = match[1];
  1804
+				var proxyGen;
  1805
+
  1806
+				if (from.match(/[^\w]/)) proxyGen = selector_proxy;
  1807
+				else if (from == 'Window' || from == 'window') proxyGen = window_proxy;
  1808
+				else proxyGen = property_proxy;
  1809
+
  1810
+				for (var onevent in v) {
  1811
+					var handler = v[onevent];
  1812
+					match = onevent.match(/^on(.*)/);
  1813
+					var event = match[1];
  1814
+
  1815
+					this.bind_capture(selector, event, k + '_' + event, proxyGen(from, handler));
  1816
+
  1817
+					if (!bindings[event]) {
  1818
+						var namespaced = event.replace(/(\s+|$)/g, '.entwine$1');
  1819
+						bindings[event] = event_proxy(event);
  1820
+
  1821
+						$(proxyGen == window_proxy ? window : document).bind(namespaced, bindings[event]);
  1822
+					}
  1823
+				}
  1824
+
  1825
+				return true;
  1826
+			}
  1827
+		}
  1828
+	});
  1829
+
  1830
+})(jQuery);
  1831
+;
  1832
+
  1833
+
1607 1834
 /* src/jquery.entwine.ctors.js */
1608 1835
 
1609 1836
 (function($) {	
@@ -1672,10 +1899,7 @@ catch (e) {
1672 1899
 	 *   $('#foo').addClass('tabs'); $('#foo').tabFunctionBar();
1673 1900
 	 * won't work.
1674 1901
 	 */
1675  
-	$(document).bind('DOMMaybeChanged', function(e){
1676  
-		// 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
1677  
-		var changes = e.changes;
1678  
-
  1902
+	$(document).bind('EntwineSubtreeMaybeChanged', function(e, changes){
1679 1903
 		// var start = (new Date).getTime();
1680 1904
 
1681 1905
 		// For every namespace
@@ -1736,7 +1960,7 @@ catch (e) {
1736 1960
 					}
1737 1961
 					else {
1738 1962
 						// We don't deal with attributes yet, so any attribute change means we need to do a full recalc
1739  
-						for (var k in e.changes.attrs) {	full = true; break; }
  1963
+						for (var k in changes.attrs) {	full = true; break; }
1740 1964
 
1741 1965
 						/*
1742 1966
 						 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
@@ -1747,7 +1971,7 @@ catch (e) {
1747 1971
 							- NOTE: It might be on _both_
1748 1972
 						 */
1749 1973
 
1750  
-						var method = rule.selector.affectedBy(e.changes);
  1974
+						var method = rule.selector.affectedBy(changes);
1751 1975
 
1752 1976
 						if (method.classes.context) {
1753 1977
 							full = true;
@@ -1755,7 +1979,7 @@ catch (e) {
1755 1979
 						else {
1756 1980
 							for (var k in method.classes.direct) {
1757 1981
 								calcmatched(j);
1758  
-								var recheck = e.changes.classes[k].not(matched);
  1982
+								var recheck = changes.classes[k].not(matched);
1759 1983
 
1760 1984
 								if (res === null) {
1761 1985
 									res = rule.cache ? rule.cache.not(taken).add(released.filter(sel)) : $([]);
@@ -1854,6 +2078,74 @@ catch (e) {
1854 2078
 ;
1855 2079
 
1856 2080
 
  2081
+/* src/jquery.entwine.addrem.js */
  2082
+
  2083
+(function($) {
  2084
+
  2085
+	$.entwine.Namespace.addMethods({
  2086
+		build_addrem_proxy: function(name) {
  2087
+			var one = this.one(name, 'func');
  2088
+
  2089
+			return function() {
  2090
+				if (this.length === 0){
  2091
+					return;
  2092
+				}
  2093
+				else if (this.length) {
  2094
+					var rv, i = this.length;
  2095
+					while (i--) rv = one(this[i], arguments);
  2096
+					return rv;
  2097
+				}
  2098
+				else {
  2099
+					return one(this, arguments);
  2100
+				}
  2101
+			};
  2102
+		},
  2103
+
  2104
+		bind_addrem_proxy: function(selector, name, func) {
  2105
+			var rulelist = this.store[name] || (this.store[name] = $.entwine.RuleList());
  2106
+
  2107
+			var rule = rulelist.addRule(selector, name); rule.func = func;
  2108
+
  2109
+			if (!this.injectee.hasOwnProperty(name)) {
  2110
+				this.injectee[name] = this.build_addrem_proxy(name);
  2111
+				this.injectee[name].isentwinemethod = true;
  2112
+			}
  2113
+		}
  2114
+	});
  2115
+
  2116
+	$.entwine.Namespace.addHandler({
  2117
+		order: 30,
  2118
+
  2119
+		bind: function(selector, k, v) {
  2120
+			if ($.isFunction(v) && (k == 'onadd' || k == 'onremove')) {
  2121
+				this.bind_addrem_proxy(selector, k, v);
  2122
+				return true;
  2123
+			}
  2124
+		}
  2125
+	});
  2126
+
  2127
+	$(document).bind('EntwineElementsAdded', function(e){
  2128
+		// For every namespace
  2129
+		for (var k in $.entwine.namespaces) {
  2130
+			var namespace = $.entwine.namespaces[k];
  2131
+			if (namespace.injectee.onadd) namespace.injectee.onadd.call(e.targets);
  2132
+		}
  2133
+	});
  2134
+
  2135
+	$(document).bind('EntwineElementsRemoved', function(e){
  2136
+		for (var k in $.entwine.namespaces) {
  2137
+			var namespace = $.entwine.namespaces[k];
  2138
+			if (namespace.injectee.onremove) namespace.injectee.onremove.call(e.targets);
  2139
+		}
  2140
+	});
  2141
+
  2142
+
  2143
+
  2144
+
  2145
+})(jQuery);
  2146
+;
  2147
+
  2148
+
1857 2149
 /* src/jquery.entwine.properties.js */
1858 2150
 
1859 2151
 (function($) {	
430  thirdparty/jquery-entwine/dist/jquery.entwine-dist.js
@@ -353,20 +353,6 @@ Sizzle is good for finding elements for a selector, but not so good for telling
353 353
 	// Does browser support Element.children
354 354
 	var hasChildren = div.children && div.children[0].tagName == 'FORM';
355 355
 
356  
-	var FUNC_IN  = /^\s*function\s*\([^)]*\)\s*\{/;
357  
-	var FUNC_OUT = /}\s*$/;
358  
-
359  
-	var funcToString = function(f) {
360  
-		return (''+f).replace(FUNC_IN,'').replace(FUNC_OUT,'');
361  
-	};
362  
-
363  
-	// Can we use Function#toString ?
364  
-	try {
365  
-		var testFunc = function(){ return 'good'; };
366  
-		if ((new Function('',funcToString(testFunc)))() != 'good') funcToString = false;
367  
-	}
368  
-	catch(e) { funcToString = false; console.log(e.message);/*pass*/ }
369  
-
370 356
 	/**** INTRO ****/
371 357
 	
372 358
 	var GOOD = /GOOD/g;
@@ -472,7 +458,13 @@ Sizzle is good for finding elements for a selector, but not so good for telling
472 458
 			this.wsattrs[attr] = true;
473 459
 			return join([this.uses_attr(attr), 'var _WS_'+varForAttr(attr)+' = " "+'+varForAttr(attr)+'+" ";']); 
474 460
 		},
475  
-		
  461
+
  462
+		uses_jqueryFilters: function() {
  463
+			if (this.jqueryFiltersAdded) return;
  464
+			this.jqueryFiltersAdded = true;
  465
+			return 'var _$filters = jQuery.find.selectors.filters;';
  466
+		},
  467
+
476 468
 		save: function(lbl) {
477 469
 			return 'var el'+lbl+' = el;';
478 470
 		},
@@ -584,12 +576,8 @@ Sizzle is good for finding elements for a selector, but not so good for telling
584 576
 				js[js.length] = ( typeof check == 'function' ? check.apply(this, pscls[1]) : check );
585 577
 			}
586 578
 			else if (check = $.find.selectors.filters[pscls[0]]) {
587  
-				if (funcToString) {
588  
-					js[js.length] = funcToString(check).replace(/elem/g,'el').replace(/return([^;]+);/,'if (!($1)) BAD;');
589  
-				}
590  
-				else {
591  
-					js[js.length] = 'if (!$.find.selectors.filters.'+pscls[0]+'(el)) BAD;';
592  
-				}
  579
+				js[js.length] = el.uses_jqueryFilters();
  580
+				js[js.length] = 'if (!_$filters.'+pscls[0]+'(el)) BAD;';
593 581
 			}
594 582
 		});
595 583
 		
@@ -845,8 +833,10 @@ catch (e) {
845 833
 			for (var k in $.fn) { if ($.fn[k].isentwinemethod) delete $.fn[k]; }
846 834
 			// Remove bound events - TODO: Make this pluggable, so this code can be moved to jquery.entwine.events.js
847 835
 			$(document).unbind('.entwine');
  836
+			$(window).unbind('.entwine');
848 837
 			// Remove namespaces, and start over again
849  
-			namespaces = $.entwine.namespaces = {};
  838
+			for (var k in namespaces) delete namespaces[k];
  839
+			for (var k in $.entwine.capture_bindings) delete $.entwine.capture_bindings[k];
850 840
 		},
851 841
 		
852 842
 		WARN_LEVEL_NONE: 0,
@@ -997,7 +987,7 @@ catch (e) {
997 987
 				
998 988
 				for (var i = 0; i < handlers.length; i++) {
999 989
 					var handler = handlers[i], builder;
1000  
-					
  990
+
1001 991
 					// Inject jQuery object method overrides
1002 992
 					if (builder = handler.namespaceMethodOverrides) {
1003 993
 						var overrides = builder(this);
@@ -1182,7 +1172,144 @@ catch (e) {
1182 1172
 ;
1183 1173
 
1184 1174
 
1185  
-/* src/jquery.entwine.dommaybechanged.js */
  1175
+/* src/domevents/jquery.entwine.domevents.addrem.js */
  1176
+
  1177
+(function($){
  1178
+
  1179
+	// Gets all the child elements of a particular elements, stores it in an array
  1180
+	function getElements(store, original) {
  1181
+		var node, i = store.length, next = original.firstChild;
  1182
+
  1183
+		while ((node = next)) {
  1184
+			if (node.nodeType === 1) store[i++] = node;
  1185
+			next = node.firstChild || node.nextSibling;
  1186
+			while (!next && (node = node.parentNode) && node !== original) next = node.nextSibling;
  1187
+		}
  1188
+	}
  1189
+
  1190
+	// This might be faster? Or slower? @todo: benchmark.
  1191
+	function getElementsAlt(store, node) {
  1192
+		if (node.getElementsByTagName) {
  1193
+			var els = node.getElementsByTagName('*'), len = els.length, i = 0, j = store.length;
  1194
+			for(; i < len; i++, j++) {
  1195
+				store[j] = els[i];
  1196
+			}
  1197
+		}
  1198
+		else if (node.childNodes) {
  1199
+			var els = node.childNodes, len = els.length, i = 0;
  1200
+			for(; i < len; i++) {
  1201
+				getElements(store, els[i]);
  1202
+			}
  1203
+		}
  1204
+	}
  1205
+
  1206
+	var dontTrigger = false;
  1207
+
  1208
+	// Monkey patch $.fn.domManip to catch all regular jQuery add element calls
  1209
+	var _domManip = $.prototype.domManip;
  1210
+	$.prototype.domManip = function(args, table, callback) {
  1211
+		if (!callback.patched) {
  1212
+			var original = callback;
  1213
+			arguments[2] = function(elem){
  1214
+				var added = [];
  1215
+
  1216
+				if (!dontTrigger) {
  1217
+					if (elem.nodeType == 1) added[added.length] = elem;
  1218
+					getElements(added, elem);
  1219
+				}
  1220
+
  1221
+				var rv = original.apply(this, arguments);
  1222
+
  1223
+				if (!dontTrigger && added.length) {
  1224
+					var event = $.Event('EntwineElementsAdded');
  1225
+					event.targets = added;
  1226
+					$(document).triggerHandler(event);
  1227
+				}
  1228
+
  1229
+				return rv;
  1230
+			}
  1231
+			arguments[2].patched = true;
  1232
+		}
  1233
+
  1234
+		return _domManip.apply(this, arguments);
  1235
+	}
  1236
+
  1237
+	// Monkey patch $.fn.html to catch when jQuery sets innerHTML directly
  1238
+	var _html = $.prototype.html;
  1239
+	$.prototype.html = function(value) {
  1240
+		if (value === undefined) return _html.apply(this, arguments);
  1241
+
  1242
+		dontTrigger = true;
  1243
+		var res = _html.apply(this, arguments);
  1244
+		dontTrigger = false;
  1245
+
  1246
+		var added = [];
  1247
+
  1248
+		var i = 0, length = this.length;
  1249
+		for (; i < length; i++ ) getElements(added, this[i]);
  1250
+
  1251
+		var event = $.Event('EntwineElementsAdded');
  1252
+		event.targets = added;
  1253
+		$(document).triggerHandler(event);
  1254
+
  1255
+		return res;
  1256
+	}
  1257
+
  1258
+	// If this is true, we've changed something to call cleanData so that we can catch the elements, but we don't
  1259
+	// want to call the underlying original $.cleanData
  1260
+	var supressActualClean = false;
  1261
+
  1262
+	// Monkey patch $.cleanData to catch element removal
  1263
+	var _cleanData = $.cleanData;
  1264
+	$.cleanData = function( elems ) {
  1265
+		// By default we can assume all elements passed are legitimately being removeed
  1266
+		var removed = elems;
  1267
+
  1268
+		// Except if we're supressing actual clean - we might be being called by jQuery "being careful" about detaching nodes
  1269
+		// before attaching them. So we need to check to make sure these nodes currently are in a document
  1270
+		if (supressActualClean) {
  1271
+			var i = 0, len = elems.length, removed = [], ri = 0;
  1272
+			for(; i < len; i++) {
  1273
+				var node = elems[i], current = node;
  1274
+				while (current = current.parentNode) {
  1275
+					if (current.nodeType == 9) { removed[ri++] = node; break; }
  1276
+				}
  1277
+			}
  1278
+		}
  1279
+
  1280
+		if (removed.length) {
  1281
+			var event = $.Event('EntwineElementsRemoved');
  1282
+			event.targets = removed;
  1283
+			$(document).triggerHandler(event);
  1284
+		}
  1285
+
  1286
+		if (!supressActualClean) _cleanData.apply(this, arguments);
  1287
+	}
  1288
+
  1289
+	// Monkey patch $.fn.remove to catch when we're just detaching (keepdata == 1) -
  1290
+	// this doesn't call cleanData but still needs to trigger event
  1291
+	var _remove = $.prototype.remove;
  1292
+	$.prototype.remove = function(selector, keepdata) {
  1293
+		supressActualClean = keepdata;
  1294
+		var rv = _remove.call(this, selector);
  1295
+		supressActualClean = false;
  1296
+		return rv;
  1297
+	}
  1298
+
  1299
+	// And on DOM ready, trigger adding once
  1300
+	$(function(){
  1301
+		var added = []; getElements(added, document);
  1302
+
  1303
+		var event = $.Event('EntwineElementsAdded');
  1304
+		event.targets = added;
  1305
+		$(document).triggerHandler(event);
  1306
+	});
  1307
+
  1308
+
  1309
+})(jQuery);;
  1310
+
  1311
+
  1312
+/* src/domevents/jquery.entwine.domevents.maybechanged.js */
1186 1313
 
1187 1314
 (function($){
1188 1315
 
@@ -1224,15 +1351,11 @@ catch (e) {
1224 1351
 			// Cancel any pending timeout (if we're directly called in the mean time)
1225 1352
 			if (this.check_id) clearTimeout(this.check_id);
1226 1353
 
1227  
-			// Create a new event object
1228  
-			var event = $.Event("DOMMaybeChanged");
1229  
-			event.changes = this;
1230  
-
1231 1354
 			// Reset the global changes object to be a new instance (do before trigger, in case trigger fires changes itself)
1232 1355
 			changes = new ChangeDetails();
1233 1356
 
1234 1357
 			// Fire event
1235  
-			$(document).triggerHandler(event);
  1358
+			$(document).triggerHandler("EntwineSubtreeMaybeChanged", [this]);
1236 1359
 		},
1237 1360
 
1238 1361
 		changed: function() {
@@ -1282,14 +1405,22 @@ catch (e) {
1282 1405
 
1283 1406
 	var changes = new ChangeDetails();
1284 1407
 
  1408
+	// Element add events trigger maybechanged events
  1409
+
  1410
+	$(document).bind('EntwineElementsAdded', function(e){ changes.addSubtree(e.targets); });
1285 1411
 
1286  
-	monkey('append', 'prepend', 'empty', 'html', function(){
1287  
-		changes.addSubtree(this);
  1412
+	// Element remove events trigger maybechanged events, but we have to wait until after the nodes are actually removed
  1413
+	// (EntwineElementsRemoved fires _just before_ the elements are removed so the data still exists), especially in syncronous mode
  1414
+
  1415
+	var removed = null;
  1416
+	$(document).bind('EntwineElementsRemoved', function(e){ removed = e.targets; });
  1417
+
  1418
+	monkey('remove', 'html', 'empty', function(){
  1419
+		var subtree = removed; removed = null;
  1420
+		if (subtree) changes.addSubtree(subtree);
1288 1421
 	});
1289 1422
 
1290  
-	monkey('after', 'before', 'remove', 'detach', function(){
1291  
-		changes.addSubtree(this.parent());
1292  
-	})
  1423
+	// We also need to know when an attribute, class, etc changes. Patch the relevant jQuery methods here
1293 1424
 
1294 1425
 	monkey('removeAttr', function(attr){
1295 1426
 		changes.addAttr(attr, this);
@@ -1304,20 +1435,7 @@ catch (e) {
1304 1435
 		else if (typeof a != 'string') { for (var k in a) changes.addAttr(k, this); }
1305 1436
 	});
1306 1437
 
1307  
-	/*
1308  
-	These manipulation functions call one or more of the above to do the actual manipulation:
1309  
-	appendTo -> append
1310  
-	prependTo -> prepend
1311  
-	insertBefore -> before
1312  
-	insertAfter -> after
1313  
-	replaceWith -> before || append
1314  
-	replaceAll -> replaceWith
1315  
-	text -> empty, appendWith
1316  
-	wrapAll -> insertBefore, append
1317  
-	wrapInner -> wrapAll || append
1318  
-	wrap -> wrapAll
1319  
-	unwrap -> replaceWith
1320  
-	*/
  1438
+	// Add some usefull accessors to $.entwine
1321 1439
 
1322 1440
 	$.extend($.entwine, {
1323 1441
 		/**
@@ -1337,15 +1455,10 @@ catch (e) {
1337 1455
 		 * Called automatically on document.ready
1338 1456
 		 */
1339 1457
 		triggerMatching: function() {
1340  
-			changes.addAll(); //.triggerEvent();
  1458
+			changes.addAll();
1341 1459
 		}
1342 1460
 	});
1343 1461
 
1344  
-	// And on DOM ready, trigger matching once
1345  
-	$(function(){
1346  
-		$.entwine.triggerMatching();
1347  
-	});
1348  
-
1349 1462
 })(jQuery);;
1350 1463
 
1351 1464
 
@@ -1586,24 +1699,138 @@ catch (e) {
1586 1699
 	// Find all forms and bind onsubmit to trigger on the document too. 
1587 1700
 	// This is the only event that can't be grabbed via delegation
1588 1701
 	
1589  
-	var form_binding_cache = $([]); // A cache for already-handled form elements
1590  
-	var delegate_submit = function(e, data){ 
  1702
+	var delegate_submit = function(e, data){
1591 1703
 		var delegationEvent = $.Event('delegatedSubmit'); delegationEvent.delegatedEvent = e;
1592 1704
 		return $(document).trigger(delegationEvent, data); 
1593 1705
 	};
1594 1706
 
1595  
-	$(document).bind('DOMMaybeChanged', function(){
1596  
-		var forms = $('form');
1597  
-		// Only bind to forms we haven't processed yet
1598  
-		forms.not(form_binding_cache).bind('submit', delegate_submit);
1599  
-		// Then remember the current set of forms
1600  
-		form_binding_cache = forms;
  1707
+	$(document).bind('EntwineElementsAdded', function(e){
  1708
+		var forms = $(e.targets).filter('form');
  1709
+		if (!forms.length) return;
  1710
+
  1711
+		forms.bind('submit.entwine_delegate_submit', delegate_submit);
1601 1712
 	});
1602 1713
 
1603 1714
 })(jQuery);
1604 1715
 	;
1605 1716
 
1606 1717
 
  1718
+/* src/jquery.entwine.eventcapture.js */
  1719
+
  1720
+(function($) {
  1721
+
  1722
+	$.entwine.Namespace.addMethods({
  1723
+		bind_capture: function(selector, event, name, capture) {
  1724
+			var store  = this.captures || (this.captures = {});
  1725
+			var rulelists = store[event] || (store[event] = {});
  1726
+			var rulelist = rulelists[name] || (rulelists[name] = $.entwine.RuleList());
  1727
+
  1728
+			rule = rulelist.addRule(selector, event);
  1729
+			rule.handler = name;
  1730
+
  1731
+			this.bind_proxy(selector, name, capture);
  1732
+		}
  1733
+	});
  1734
+
  1735
+	var bindings = $.entwine.capture_bindings = {};
  1736
+
  1737
+	var event_proxy = function(event) {
  1738
+		return function(e) {
  1739
+			var namespace, capturelists, forevent, capturelist, rule, handler, sel;
  1740
+
  1741
+			for (var k in $.entwine.namespaces) {
  1742
+				namespace = $.entwine.namespaces[k];
  1743
+				capturelists = namespace.captures;
  1744
+
  1745
+				if (capturelists && (forevent = capturelists[event])) {
  1746
+					for (var k in forevent) {
  1747
+						var capturelist = forevent[k];
  1748
+						var triggered = namespace.$([]);
  1749
+
  1750
+						// Stepping through each selector from most to least specific
  1751
+						var j = capturelist.length;
  1752
+						while (j--) {
  1753
+							rule = capturelist[j];
  1754
+							handler = rule.handler;
  1755
+							sel = rule.selector.selector;
  1756
+
  1757
+							var matching = namespace.$(sel).not(triggered);
  1758
+							matching[handler].apply(matching, arguments);
  1759
+
  1760
+							triggered = triggered.add(matching);
  1761
+						}
  1762
+					}
  1763
+				}
  1764
+			}
  1765
+		}
  1766
+	};
  1767
+
  1768
+	var selector_proxy = function(selector, handler, includechildren) {
  1769
+		var matcher = $.selector(selector);
  1770
+		return function(e){
  1771
+			if (matcher.matches(e.target)) return handler.apply(this, arguments);
  1772
+		}
  1773
+	};
  1774
+
  1775
+	var window_proxy = function(selector, handler, includechildren) {
  1776
+		return function(e){
  1777
+			if (e.target === window) return handler.apply(this, arguments);
  1778
+		}
  1779
+	};
  1780
+
  1781
+	var property_proxy = function(property, handler, includechildren) {
  1782
+		var matcher;
  1783
+
  1784
+		return function(e){
  1785
+			var match = this['get'+property]();
  1786
+
  1787
+			if (typeof(match) == 'string') {
  1788
+				var matcher = (matcher && match == matcher.selector) ? matcher : $.selector(match);
  1789
+				if (matcher.matches(e.target)) return handler.apply(this, arguments);
  1790
+			}
  1791
+			else {
  1792
+				if ($.inArray(e.target, match) !== -1) return handler.apply(this, arguments);
  1793
+			}
  1794
+		}
  1795
+	};
  1796
+
  1797
+	$.entwine.Namespace.addHandler({
  1798
+		order: 10,
  1799
+
  1800
+		bind: function(selector, k, v) {
  1801
+			var match;
  1802
+			if ($.isPlainObject(v) && (match = k.match(/^from\s*(.*)/))) {
  1803
+				var from = match[1];
  1804
+				var proxyGen;
  1805
+
  1806
+				if (from.match(/[^\w]/)) proxyGen = selector_proxy;
  1807
+				else if (from == 'Window' || from == 'window') proxyGen = window_proxy;
  1808
+				else proxyGen = property_proxy;
  1809
+
  1810
+				for (var onevent in v) {
  1811
+					var handler = v[onevent];
  1812
+					match = onevent.match(/^on(.*)/);
  1813
+					var event = match[1];
  1814
+
  1815
+					this.bind_capture(selector, event, k + '_' + event, proxyGen(from, handler));
  1816
+
  1817
+					if (!bindings[event]) {
  1818
+						var namespaced = event.replace(/(\s+|$)/g, '.entwine$1');
  1819
+						bindings[event] = event_proxy(event);
  1820
+
  1821
+						$(proxyGen == window_proxy ? window : document).bind(namespaced, bindings[event]);
  1822
+					}
  1823
+				}
  1824
+
  1825
+				return true;