Commit d42afd0f authored by Justin Meyer's avatar Justin Meyer Committed by jeresig

Adding in support for bubbling submit and change events, thanks to the patch...

Adding in support for bubbling submit and change events, thanks to the patch by Justin Meyer. Includes a delegation test suite for manually testing to see if the events work as intended.
parent c6a0211e
......@@ -63,13 +63,7 @@ jQuery.event = {
var handlers = events[ type ],
special = this.special[ type ] || {};
if ( special.add ) {
var modifiedHandler = special.add.call( elem, handler, data, namespaces );
if ( modifiedHandler && jQuery.isFunction( modifiedHandler ) ) {
modifiedHandler.guid = modifiedHandler.guid || handler.guid;
handler = modifiedHandler;
}
}
// Init the event handler queue
if ( !handlers ) {
......@@ -78,7 +72,7 @@ jQuery.event = {
// Check for a special event handler
// Only use addEventListener/attachEvent if the special
// events handler returns false
if ( !special.setup || special.setup.call( elem, data, namespaces ) === false ) {
if ( !special.setup || special.setup.call( elem, data, namespaces, handler) === false ) {
// Bind the global event handler to the element
if ( elem.addEventListener ) {
elem.addEventListener( type, handle, false );
......@@ -87,7 +81,15 @@ jQuery.event = {
}
}
}
if ( special.add ) {
var modifiedHandler = special.add.call( elem, handler, data, namespaces, handlers );
if ( modifiedHandler && jQuery.isFunction( modifiedHandler ) ) {
modifiedHandler.guid = modifiedHandler.guid || handler.guid;
handler = modifiedHandler;
}
}
// Add the function to the element's handler list
handlers[ handler.guid ] = handler;
......@@ -109,7 +111,7 @@ jQuery.event = {
return;
}
var events = jQuery.data( elem, "events" ), ret, type;
var events = jQuery.data( elem, "events" ), ret, type, fn;
if ( events ) {
// Unbind all events for the element
......@@ -133,12 +135,14 @@ jQuery.event = {
var namespaces = type.split(".");
type = namespaces.shift();
var all = !namespaces.length,
namespace = new RegExp("(^|\\.)" + namespaces.slice(0).sort().join("\\.(?:.*\\.)?") + "(\\.|$)"),
cleaned = jQuery.map( namespaces.slice(0).sort() , function(nm){ return nm.replace(/[^\w\s\.\|`]/g, function(ch){return "\\"+ch }) }),
namespace = new RegExp("(^|\\.)" + cleaned.join("\\.(?:.*\\.)?") + "(\\.|$)"),
special = this.special[ type ] || {};
if ( events[ type ] ) {
// remove the given handler for the given type
if ( handler ) {
fn = events[ type ][ handler.guid ];
delete events[ type ][ handler.guid ];
// remove all handlers for the given type
......@@ -152,7 +156,7 @@ jQuery.event = {
}
if ( special.remove ) {
special.remove.call( elem, namespaces );
special.remove.call( elem, namespaces, fn);
}
// remove generic event handler if no more handlers exist
......@@ -402,10 +406,12 @@ jQuery.event = {
},
live: {
add: function( proxy, data, namespaces ) {
add: function( proxy, data, namespaces, live ) {
jQuery.extend( proxy, data || {} );
proxy.guid += data.selector + data.live;
jQuery.event.add( this, data.live, liveHandler, data );
proxy.guid += data.selector + data.live;
jQuery.event.add( this, data.live, liveHandler, data );
},
remove: function( namespaces ) {
......@@ -422,7 +428,8 @@ jQuery.event = {
jQuery.event.remove( this, namespaces[0], liveHandler );
}
}
}
},
special: {}
}
}
};
......@@ -542,40 +549,110 @@ jQuery.each({
};
});
(function() {
var event = jQuery.event,
special = event.special,
handle = event.handle;
special.submit = {
setup: function(data, namespaces) {
if(data.selector) {
event.add(this, 'click.specialSubmit', function(e, eventData) {
if(jQuery(e.target).filter(":submit, :image").closest(data.selector).length) {
e.type = "submit";
return handle.call( this, e, eventData );
}
});
event.add(this, 'keypress.specialSubmit', function( e, eventData ) {
if(jQuery(e.target).filter(":text, :password").closest(data.selector).length) {
e.type = "submit";
return handle.call( this, e, eventData );
}
});
} else {
return false;
// submit delegation
jQuery.event.special.submit = {
setup: function( data, namespaces, fn ) {
if ( !jQuery.support.submitBubbles && this.nodeName.toLowerCase() !== "form" ) {
jQuery.event.add(this, "click.specialSubmit." + fn.guid, function( e ) {
var elem = e.target, type = elem.type;
if ( (type === "submit" || type === "image") && jQuery( elem ).closest("form").length ) {
return trigger( "submit", this, arguments );
}
});
jQuery.event.add(this, "keypress.specialSubmit." + fn.guid, function( e ) {
var elem = e.target, type = elem.type;
if ( (type === "text" || type === "password") && jQuery( elem ).closest("form").length && e.keyCode === 13 ) {
return trigger( "submit", this, arguments );
}
});
}
return false;
},
remove: function( namespaces, fn ) {
jQuery.event.remove( this, "click.specialSubmit" + (fn ? "."+fn.guid : "") );
jQuery.event.remove( this, "keypress.specialSubmit" + (fn ? "."+fn.guid : "") );
}
};
// change delegation, happens here so we have bind.
jQuery.event.special.change = {
filters: {
click: function( e ) {
var elem = e.target;
if ( elem.nodeName.toLowerCase() === "input" && elem.type === "checkbox" ) {
return trigger( "change", this, arguments );
}
return changeFilters.keyup.call( this, e );
},
keyup: function( e ) {
var elem = e.target, data, index = elem.selectedIndex + "";
if ( elem.nodeName.toLowerCase() === "select" ) {
data = jQuery.data( elem, "_change_data" );
jQuery.data( elem, "_change_data", index );
if ( (elem.type === "select-multiple" || data != null) && data !== index ) {
return trigger( "change", this, arguments );
}
}
},
beforeactivate: function( e ) {
var elem = e.target;
if ( elem.nodeName.toLowerCase() === "input" && elem.type === "radio" && !elem.checked ) {
return trigger( "change", this, arguments );
}
},
blur: function( e ) {
var elem = e.target, nodeName = elem.nodeName.toLowerCase();
if ( (nodeName === "textarea" || (nodeName === "input" && (elem.type === "text" || elem.type === "password")))
&& jQuery.data(elem, "_change_data") !== elem.value ) {
return trigger( "change", this, arguments );
}
},
focus: function( e ) {
var elem = e.target, nodeName = elem.nodeName.toLowerCase();
if ( nodeName === "textarea" || (nodeName === "input" && (elem.type === "text" || elem.type === "password" ) ) ) {
jQuery.data( elem, "_change_data", elem.value );
}
}
},
setup: function( data, namespaces, fn ) {
// return false if we bubble
if ( !jQuery.support.changeBubbles ) {
for ( var type in changeFilters ) {
jQuery.event.add( this, type + ".specialChange." + fn.guid, changeFilters[type] );
}
}
remove: function(namespaces) {
event.remove(this, 'click.specialSubmit');
event.remove(this, 'keypress.specialSubmit');
// always want to listen for change for trigger
return false;
},
remove: function( namespaces, fn ) {
if ( !jQuery.support.changeBubbles ) {
for ( var type in changeFilters ) {
jQuery.event.remove( this, type + ".specialChange" + (fn ? "."+fn.guid : ""), changeFilters[type] );
}
}
};
})();
}
};
var changeFilters = jQuery.event.special.change.filters;
function trigger( type, elem, args ) {
args[0].type = type;
return jQuery.event.handle.apply( elem, args );
}
// Create "bubbling" focus and blur events
jQuery.each({
......@@ -750,12 +827,19 @@ jQuery.fn.extend({
function liveHandler( event ) {
var stop = true, elems = [], selectors = [], args = arguments,
related, match, fn, elem, j, i,
related, match, fn, elem, j, i, data,
live = jQuery.extend({}, jQuery.data( this, "events" ).live);
for ( j in live ) {
if ( live[j].live === event.type ) {
selectors.push( live[j].selector );
fn = live[j];
if ( fn.live === event.type ||
fn.altLive && jQuery.inArray(event.type, fn.altLive) > -1 ) {
data = fn.data;
if ( !(data.beforeFilter && data.beforeFilter[event.type] &&
!data.beforeFilter[event.type](event)) ) {
selectors.push( fn.selector );
}
} else {
delete live[j];
}
......@@ -796,7 +880,9 @@ function liveHandler( event ) {
}
function liveConvert( type, selector ) {
return ["live", type, selector.replace(/\./g, "`").replace(/ /g, "|")].join(".");
return ["live", type, selector//.replace(/[^\w\s\.]/g, function(ch){ return "\\"+ch})
.replace(/\./g, "`")
.replace(/ /g, "|")].join(".");
}
jQuery.extend({
......
......@@ -91,6 +91,25 @@
div = null;
});
// Technique from Juriy Zaytsev
// http://thinkweb2.com/projects/prototype/detecting-event-support-without-browser-sniffing/
var eventSupported = function( eventName ) {
var el = document.createElement("div");
eventName = "on" + eventName;
var isSupported = (eventName in el);
if ( !isSupported ) {
el.setAttribute(eventName, "return;");
isSupported = typeof el[eventName] === "function";
}
el = null;
return isSupported;
};
jQuery.support.submitBubbles = eventSupported("submit");
jQuery.support.changeBubbles = eventSupported("change");
// release memory in IE
root = script = div = all = a = null;
})();
......
<html>
<head>
<script src='../dist/jquery.js' type='text/javascript'></script>
<style>
.red {
background-color: red;
border: solid 3px red;
}
</style>
</head>
<body>
<h2>Change Tests</h2>
<table>
<tr>
<td>
Change each:
</td>
<td>
<select class='select_test'>
<option value='one'>change me 1</option>
<option value='two'>change me 2</option>
<option value='three'>change me 3</option>
</select>
</td>
<td>
<select class='mselect_test' multiple="multiple">
<option value='one'>change me 1</option>
<option value='two'>change me 2</option>
<option value='three'>change me 3</option>
</select>
</td>
<td>
<input type="checkbox" class="checkbox_test" name="mycheckbox" id="checkbox1"/>
<label for="checkbox1">Checkbox 1 label</label><br/>
<input type="checkbox" class="checkbox_test" name="mycheckbox" id="checkbox2"/>
<label for="checkbox2">Checkbox 2 label</label>
</td>
<td>
<input type="radio" class="radio_test" name="myradio" id="radio1"/>
<label for="radio1">Radio 1 label</label><br/>
<input type="radio" class="radio_test" name="myradio" id="radio2"/>
<label for="radio2">Radio 2 label</label>
</td>
<td>
<input class='test' value='' id='input' size='10' />
</td>
<td>
<textarea rows='2'></textarea>
</td>
<td>$().bind('change')</td>
</tr>
<tr>
<td>Results:</td>
<td id='select' class="red">SELECT</td>
<td id='mselect' class="red">MULTI</td>
<td id='checkbox' class="red">CHECKBOX</td>
<td id='radio' class="red">RADIO</td>
<td id='text' class="red">TEXT</td>
<td id='textarea' class="red">TEXTAREA</td>
<td id='boundChange' class="red">DOCUMENT</td>
</tr>
</table>
<h2>Submit Tests</h2>
<table>
<tr>
<td>
Submit each:
</td>
<td>
<form action="" id="text_submit">
<input class='test' type='text' value='Key Return To Submit'/>
</form>
</td>
<td>
<form action="" id="password_submit">
<input class='test' type='password' value=''/>
</form>
</td>
<td>
<form action="" id="submit_submit">
<input type='submit' value="Click Me To Submit" />
</form>
</td>
<td>$().bind('submit')</td>
</tr>
<tr>
<td>Results:</td>
<td id='textSubmit' class="red">TEXT</td>
<td id='passwordSubmit' class="red">PASSWORD</td>
<td id='submitSubmit' class="red">BUTTON</td>
<td id='boundSubmit' class="red">DOCUMENT</td>
</tr>
</table>
<script type='text/javascript'>
makeChangeFunc = function(id, prevent){
return function(e){
if(prevent)
e.preventDefault();
$(id).css("backgroundColor","green").css("border","solid 3px green");
setTimeout(function(){
$(id).css("backgroundColor","");
}, 700)
}
}
$(".select_test").live("change",makeChangeFunc("#select"))
$(".mselect_test").live("change",makeChangeFunc("#mselect"))
$(".checkbox_test").live("change",makeChangeFunc("#checkbox"))
$(".radio_test").live("change",makeChangeFunc("#radio"))
$('textarea').live('change', makeChangeFunc("#textarea"))
$('#input').live('change', makeChangeFunc("#text"))
$().bind('change', makeChangeFunc("#boundChange"))
$("#text_submit").live("submit", makeChangeFunc("#textSubmit", true) )
$("#password_submit").live("submit", makeChangeFunc("#passwordSubmit", true) )
$("#submit_submit").live("submit", makeChangeFunc("#submitSubmit", true) )
$().bind('submit', makeChangeFunc("#boundSubmit"))
</script>
</body>
</html>
......@@ -820,20 +820,143 @@ test(".live()/.die()", function() {
jQuery('span#liveSpan1').die('click');
});
test("live with submit", function() {
var count = 0;
test("live with change", function(){
var selectChange = 0, checkboxChange = 0;
jQuery("#testForm").live("submit", function() {
count++;
return false;
var select = jQuery("select[name='S1']")
select.live("change", function() {
selectChange++;
});
var checkbox = jQuery("#check2"),
checkboxFunction = function(){
checkboxChange++;
}
checkbox.live("change", checkboxFunction);
// test click on select
// first click sets data
if ( !jQuery.support.changeBubbles ) {
select[0].selectedIndex = 1;
select.trigger("keyup");
}
// second click that changed it
selectChange = 0;
select[0].selectedIndex = select[0].selectedIndex ? 0 : 1;
select.trigger(jQuery.support.changeBubbles ? "change" : "click");
equals( selectChange, 1, "Change on click." );
// test keys on select
selectChange = 0;
select[0].selectedIndex = select[0].selectedIndex ? 0 : 1;
select.trigger(jQuery.support.changeBubbles ? "change" : "keyup");
equals( selectChange, 1, "Change on keyup." );
// test click on checkbox
checkbox.trigger(jQuery.support.changeBubbles ? "change" : "click");
equals( checkboxChange, 1, "Change on checkbox." );
// test before activate on radio
// test blur/focus on textarea
var textarea = jQuery("#area1"), textareaChange = 0, oldVal = textarea.val();
textarea.live("change", function() {
textareaChange++;
});
if ( !jQuery.support.changeBubbles ) {
textarea.trigger("focus");
}
textarea.val(oldVal + "foo");
textarea.trigger(jQuery.support.changeBubbles ? "change" : "blur");
equals( textareaChange, 1, "Change on textarea." );
textarea.val(oldVal);
textarea.die("change");
// test blur/focus on text
var text = jQuery("#name"), textChange = 0, oldTextVal = text.val();
text.live("change", function() {
textChange++;
});
if ( !jQuery.support.changeBubbles ) {
text.trigger("focus");
}
text.val(oldVal+"foo");
text.trigger(jQuery.support.changeBubbles ? "change" : "blur");
equals( textChange, 1, "Change on text input." );
text.val(oldTextVal);
text.die("change");
// test blur/focus on password
var password = jQuery("#name"), passwordChange = 0, oldPasswordVal = password.val();
password.live("change", function() {
passwordChange++;
});
if ( !jQuery.support.changeBubbles ) {
password.trigger("focus");
}
password.val(oldPasswordVal + "foo");
password.trigger(jQuery.support.changeBubbles ? "change" : "blur");
equals( passwordChange, 1, "Change on password input." );
password.val(oldPasswordVal);
password.die("change");
// make sure die works
jQuery("#testForm input[name=sub1]")[0].click();
jQuery("#testForm input[name=T1]").trigger({type: "keypress", keyCode: 13});
// die all changes
selectChange = 0;
select.die("change");
select[0].selectedIndex = select[0].selectedIndex ? 0 : 1;
select.trigger(jQuery.support.changeBubbles ? "change" : "click");
equals( selectChange, 0, "Die on click works." );
selectChange = 0;
select[0].selectedIndex = select[0].selectedIndex ? 0 : 1;
select.trigger(jQuery.support.changeBubbles ? "change" : "keyup");
equals( selectChange, 0, "Die on keyup works." );
// die specific checkbox
checkbox.die("change", checkboxFunction);
checkbox.trigger(jQuery.support.changeBubbles ? "change" : "click");
equals( checkboxChange, 1, "Die on checkbox." );
});
test("live with submit", function() {
var count1 = 0, count2 = 0;
equals(2, count);
jQuery("#testForm").live("submit", function(ev) {
count1++;
ev.preventDefault();
});
jQuery("body").live("submit", function(ev) {
count2++;
ev.preventDefault();
});
if ( jQuery.support.submitBubbles ) {
jQuery("#testForm input[name=sub1]")[0].click();
equals(count1,1 );
equals(count2,1);
} else {
jQuery("#testForm input[name=sub1]")[0].click();
jQuery("#testForm input[name=T1]").trigger({type: "keypress", keyCode: 13});
equals(count1,2);
equals(count2,2);
}
jQuery("#testForm").die("submit");
jQuery("body").die("submit");
});
test("live with focus/blur", function(){
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment