From: John Resig Date: Mon, 22 Dec 2008 04:59:34 +0000 (+0000) Subject: Added a new liveQuery/event delegation hybrid method: .live and .die. Easily adapts... X-Git-Url: http://git.asbjorn.biz/?p=jquery.git;a=commitdiff_plain;h=c03a2801556d42d60fed1a5b4ce5d25362ccd9f5 Added a new liveQuery/event delegation hybrid method: .live and .die. Easily adapts event delegation to the jQuery style. $("div").live("click", fn); $("div > #foo").live("submit", fn); $("div").die("click"); --- diff --git a/src/event.js b/src/event.js index 8fb77a9..73a6eac 100644 --- a/src/event.js +++ b/src/event.js @@ -53,12 +53,15 @@ jQuery.event = { // jQuery(...).bind("mouseover mouseout", fn); jQuery.each(types.split(/\s+/), function(index, type) { // Namespaced event handlers - var parts = type.split("."); - type = parts.shift(); - handler.type = parts.sort().join("."); + var namespaces = type.split("."); + type = namespaces.shift(); + handler.type = namespaces.slice().sort().join("."); // Get the current list of functions bound to this event var handlers = events[type]; + + if ( jQuery.event.specialAll[type] ) + jQuery.event.specialAll[type].setup.call(elem, data, namespaces); // Init the event handler queue if (!handlers) { @@ -67,7 +70,7 @@ jQuery.event = { // Check for a special event handler // Only use addEventListener/attachEvent if the special // events handler returns false - if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem,data) === false ) { + if ( !jQuery.event.special[type] || jQuery.event.special[type].setup.call(elem, data, namespaces) === false ) { // Bind the global event handler to the element if (elem.addEventListener) elem.addEventListener(type, handle, false); @@ -114,9 +117,9 @@ jQuery.event = { // jQuery(...).unbind("mouseover mouseout", fn); jQuery.each(types.split(/\s+/), function(index, type){ // Namespaced event handlers - var namespace = type.split("."); - type = namespace.shift(); - namespace = RegExp("(^|\\.)" + namespace.sort().join(".*\\.") + "(\\.|$)"); + var namespaces = type.split("."); + type = namespaces.shift(); + var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)"); if ( events[type] ) { // remove the given handler for the given type @@ -129,11 +132,14 @@ jQuery.event = { // Handle the removal of namespaced events if ( namespace.test(events[type][handler].type) ) delete events[type][handler]; + + if ( jQuery.event.specialAll[type] ) + jQuery.event.specialAll[type].teardown.call(elem, namespaces); // remove generic event handler if no more handlers exist for ( ret in events[type] ) break; if ( !ret ) { - if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem) === false ) { + if ( !jQuery.event.special[type] || jQuery.event.special[type].teardown.call(elem, namespaces) === false ) { if (elem.removeEventListener) elem.removeEventListener(type, jQuery.data(elem, "handle"), false); else if (elem.detachEvent) @@ -157,7 +163,7 @@ jQuery.event = { } }, - trigger: function(type, data, elem, donative, extra) { + trigger: function(type, data, elem, donative, extra, dohandlers) { // Clone the incoming data, if any data = jQuery.makeArray(data); @@ -203,14 +209,16 @@ jQuery.event = { if ( exclusive ) data[0].exclusive = true; - // Trigger the event, it is assumed that "handle" is a function - var handle = jQuery.data(elem, "handle"); - if ( handle ) - val = handle.apply( elem, data ); + if ( dohandlers !== false ) { + // Trigger the event, it is assumed that "handle" is a function + var handle = jQuery.data(elem, "handle"); + if ( handle ) + val = handle.apply( elem, data ); - // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links) - if ( (!fn || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false ) - val = false; + // Handle triggering native .onfoo handlers (and on links since we don't call .click() for links) + if ( (!fn || (jQuery.nodeName(elem, 'a') && type == "click")) && elem["on"+type] && elem["on"+type].apply( elem, data ) === false ) + val = false; + } if ( donative !== false && val !== false ) { var parent = elem.parentNode || elem.ownerDocument; @@ -248,18 +256,18 @@ jQuery.event = { handle: function(event) { // returned undefined or false - var val, ret, namespace, all, handlers; + var val, ret, all, handlers; event = arguments[0] = jQuery.event.fix( event || window.event ); // Namespaced event handlers - namespace = event.type.split("."); - event.type = namespace.shift(); + var namespaces = event.type.split("."); + event.type = namespaces.shift(); // Cache this now, all = true means, any handler - all = !namespace.length && !event.exclusive; + all = !namespaces.length && !event.exclusive; - namespace = RegExp("(^|\\.)" + namespace.sort().join(".*\\.") + "(\\.|$)"); + var namespace = RegExp("(^|\\.)" + namespaces.slice().sort().join(".*\\.") + "(\\.|$)"); handlers = ( jQuery.data(this, "events") || {} )[event.type]; @@ -381,6 +389,27 @@ jQuery.event = { setup: bindReady, teardown: function() {} } + }, + + specialAll: { + live: { + setup: function( selector, namespaces ){ + jQuery.event.add( this, namespaces[0], liveHandler ); + }, + teardown: function( namespaces ){ + if ( namespaces.length ) { + var remove = 0, name = RegExp("(^|\\.)" + namespaces[0] + "(\\.|$)"); + + jQuery.each( (jQuery.data(this, "events").live || {}), function(){ + if ( name.test(this.type) ) + remove++; + }); + + if ( remove <= 1 ) + jQuery.event.remove( this, namespaces[0], liveHandler ); + } + } + } } }; @@ -493,9 +522,34 @@ jQuery.fn.extend({ jQuery.readyList.push( function() { return fn.call(this, jQuery); } ); return this; + }, + + live: function( type, fn ){ + jQuery(document).bind( liveConvert(type, this.selector), this.selector, fn ); + return this; + }, + + die: function( type, fn ){ + jQuery(document).unbind( liveConvert(type, this.selector), fn ); + return this; } }); +function liveHandler( event ){ + var check = RegExp("(^|\\.)" + event.type + "(\\.|$)"); + jQuery.each(jQuery.data(this, "events").live || [], function(i, fn){ + if ( check.test(fn.type) ) { + var elem = jQuery(event.target).closest(fn.data)[0]; + if ( elem ) + jQuery.event.trigger( event.type, fn.data, elem, false, fn, false ); + } + }); +} + +function liveConvert(type, selector){ + return ["live", type, selector.replace(/\./g, "_")].join("."); +} + jQuery.extend({ isReady: false, readyList: [], diff --git a/test/unit/event.js b/test/unit/event.js index 4b96a4f..08b5913 100644 --- a/test/unit/event.js +++ b/test/unit/event.js @@ -418,6 +418,72 @@ test("toggle(Function, Function, ...)", function() { var data = jQuery.data( $div[0], 'events' ); ok( !data, "Unbinding one function from toggle unbinds them all"); }); + +test(".live()/.die()", function() { + expect(28); + + var submit = 0, div = 0, livea = 0, liveb = 0; + + jQuery("div").live("submit", function(){ submit++; return false; }); + jQuery("div").live("click", function(){ div++; }); + jQuery("div#nothiddendiv").live("click", function(){ livea++; }); + jQuery("div#nothiddendivchild").live("click", function(){ liveb++; }); + + // Nothing should trigger on the body + jQuery("body").trigger("click"); + equals( submit, 0, "Click on body" ); + equals( div, 0, "Click on body" ); + equals( livea, 0, "Click on body" ); + equals( liveb, 0, "Click on body" ); + + // This should trigger two events + jQuery("div#nothiddendiv").trigger("click"); + equals( submit, 0, "Click on div" ); + equals( div, 1, "Click on div" ); + equals( livea, 1, "Click on div" ); + equals( liveb, 0, "Click on div" ); + + // This should trigger three events (w/ bubbling) + jQuery("div#nothiddendivchild").trigger("click"); + equals( submit, 0, "Click on inner div" ); + equals( div, 2, "Click on inner div" ); + equals( livea, 2, "Click on inner div" ); + equals( liveb, 1, "Click on inner div" ); + + // This should trigger one submit + jQuery("div#nothiddendivchild").trigger("submit"); + equals( submit, 1, "Submit on div" ); + equals( div, 2, "Submit on div" ); + equals( livea, 2, "Submit on div" ); + equals( liveb, 1, "Submit on div" ); + + // Make sure no other events were removed in the process + jQuery("div#nothiddendivchild").trigger("click"); + equals( submit, 1, "die Click on inner div" ); + equals( div, 3, "die Click on inner div" ); + equals( livea, 3, "die Click on inner div" ); + equals( liveb, 2, "die Click on inner div" ); + + // Now make sure that the removal works + jQuery("div#nothiddendivchild").die("click"); + jQuery("div#nothiddendivchild").trigger("click"); + equals( submit, 1, "die Click on inner div" ); + equals( div, 4, "die Click on inner div" ); + equals( livea, 4, "die Click on inner div" ); + equals( liveb, 2, "die Click on inner div" ); + + // Make sure that the click wasn't removed too early + jQuery("div#nothiddendiv").trigger("click"); + equals( submit, 1, "die Click on inner div" ); + equals( div, 5, "die Click on inner div" ); + equals( livea, 5, "die Click on inner div" ); + equals( liveb, 2, "die Click on inner div" ); + + jQuery("div#nothiddendiv").die("click"); + jQuery("div").die("click"); + jQuery("div").die("submit"); +}); + /* test("jQuery(function($) {})", function() { stop();