Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with HTTPS or Subversion.

Download ZIP
Browse files

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");
  • Loading branch information...
commit c03a2801556d42d60fed1a5b4ce5d25362ccd9f5 1 parent 67ded9a
John Resig jeresig authored
Showing with 141 additions and 21 deletions.
  1. +75 −21 src/event.js
  2. +66 −0 test/unit/event.js
96 src/event.js
View
@@ -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: [],
66 test/unit/event.js
View
@@ -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();
Please sign in to comment.
Something went wrong with that request. Please try again.