diff --git a/src/events/bind.js b/src/events/bind.js index da46023..f492023 100644 --- a/src/events/bind.js +++ b/src/events/bind.js @@ -19,7 +19,7 @@ define([ "shoestring", "dom/closest" ], function(){ var evts = evt.split( " " ), docEl = document.documentElement, - addToEventCache = function( el, evt, callback ) { + addToEventCache = function( el, evt, eventInfo ) { if ( !el.shoestringData ) { el.shoestringData = {}; } @@ -30,19 +30,25 @@ define([ "shoestring", "dom/closest" ], function(){ el.shoestringData.events[ evt ] = []; } var obj = {}; - if( callback.customCallfunc ) { + if( eventInfo.customCallfunc ) { obj.isCustomEvent = true; } - obj.callback = callback.customCallfunc || callback.callfunc; - obj.originalCallback = callback.originalCallback; + obj.callback = eventInfo.customCallfunc || eventInfo.callfunc; + obj.originalCallback = eventInfo.originalCallback; + obj.namespace = eventInfo.namespace; el.shoestringData.events[ evt ].push( obj ); }; - function encasedCallback( e ){ + function encasedCallback( e, namespace ){ var result; + if( e._namespace && e._namespace !== namespace ) { + return; + } + e.data = data; + e.namespace = e._namespace; var returnTrue = function(){ return true; @@ -85,11 +91,14 @@ define([ "shoestring", "dom/closest" ], function(){ } // This is exclusively for custom events on browsers without addEventListener (IE8) - function propChange( originalEvent, boundElement ) { - var triggeredElement = document.documentElement[ originalEvent.propertyName ].el; + function propChange( originalEvent, boundElement, namespace ) { + var lastEventInfo = document.documentElement[ originalEvent.propertyName ], + triggeredElement = lastEventInfo.el; if( triggeredElement !== undefined && shoestring( triggeredElement ).closest( boundElement ).length ) { - encasedCallback.call( triggeredElement, originalEvent ); + originalEvent._namespace = lastEventInfo._namespace; + originalEvent._args = lastEventInfo._args; + encasedCallback.call( triggeredElement, originalEvent, namespace ); } } @@ -97,7 +106,7 @@ define([ "shoestring", "dom/closest" ], function(){ // rebinds all callbacks on an element in the correct order. function reorderEvents( eventName ) { if( !this.attachEvent ) { - // do onthing + // do nothing return; } else if( this.shoestringData && this.shoestringData.events ) { var otherEvents = this.shoestringData.events[ eventName ]; @@ -117,24 +126,32 @@ define([ "shoestring", "dom/closest" ], function(){ var domEventCallback, customEventCallback, oEl = this; for( var i = 0, il = evts.length; i < il; i++ ){ - var evt = evts[ i ]; - domEventCallback = null; + var split = evts[ i ].split( "." ), + evt = split[ 0 ], + namespace = split.length > 0 ? split[ 1 ] : null; + + domEventCallback = function( originalEvent ) { + if( oEl.ssEventTrigger ) { + originalEvent._namespace = oEl.ssEventTrigger._namespace; + originalEvent._args = oEl.ssEventTrigger._args; + + oEl.ssEventTrigger = null; + } + return encasedCallback.call( oEl, originalEvent, namespace ); + }; customEventCallback = null; if( "addEventListener" in this ){ - this.addEventListener( evt, encasedCallback, false ); + this.addEventListener( evt, domEventCallback, false ); } else if( this.attachEvent ){ if( this[ "on" + evt ] !== undefined ) { - domEventCallback = function( originalEvent ) { - return encasedCallback.call( oEl, originalEvent ); - }; this.attachEvent( "on" + evt, domEventCallback ); } else { customEventCallback = (function() { var eventName = evt; return function( e ) { if( e.propertyName === eventName ) { - propChange.call( this, e, oEl ); + propChange.call( this, e, oEl, namespace ); } }; })(); @@ -142,10 +159,11 @@ define([ "shoestring", "dom/closest" ], function(){ } } - addToEventCache( this, evts[ i ], { + addToEventCache( this, evt, { callfunc: domEventCallback || encasedCallback, customCallfunc: customEventCallback, - originalCallback: originalCallback + originalCallback: originalCallback, + namespace: namespace }); reorderEvents.call( oEl, evt ); diff --git a/src/events/trigger.js b/src/events/trigger.js index ee911c7..58cfce0 100644 --- a/src/events/trigger.js +++ b/src/events/trigger.js @@ -6,19 +6,30 @@ define([ "shoestring" ], function(){ var evts = evt.split( " " ); return this.each(function(){ + var split, evt, namespace; for( var i = 0, il = evts.length; i < il; i++ ){ + split = evts[ i ].split( "." ), + evt = split[ 0 ], + namespace = split.length > 0 ? split[ 1 ] : null; + if( document.createEvent ){ var event = document.createEvent( "Event" ); - event.initEvent( evts[ i ], true, true ); + event.initEvent( evt, true, true ); event._args = args; + event._namespace = namespace; this.dispatchEvent( event ); } else if ( document.createEventObject ) { - if( ( "" + this[ evts[ i ] ] ).indexOf( "function" ) > -1 ) { - this[ evts[ i ] ](); + if( ( "" + this[ evt ] ).indexOf( "function" ) > -1 ) { + this.ssEventTrigger = { + _namespace: namespace, + _args: args + }; + this[ evt ](); } else { - document.documentElement[ evts[ i ] ] = { + document.documentElement[ evt ] = { "el": this, + _namespace: namespace, _args: args }; } diff --git a/src/events/unbind.js b/src/events/unbind.js index bcb5db6..a024524 100644 --- a/src/events/unbind.js +++ b/src/events/unbind.js @@ -2,41 +2,61 @@ define([ "shoestring" ], function(){ //>>excludeEnd("exclude"); - shoestring.fn.unbind = function( evt, callback ){ - var evts = evt.split( " " ), - docEl = document.documentElement; + function unbind( evt, namespace, callback ) { + var bound = this.shoestringData.events[ evt ]; + if( !bound.length ) { + return; + } + + for( var j = 0, jl = bound.length; j < jl; j++ ) { + if( !namespace || namespace === bound[ j ].namespace ) { + if( "removeEventListener" in window ){ + if( callback === undefined ) { + this.removeEventListener( evt, bound[ j ].callback, false ); + } else if( callback === bound[ j ].originalCallback ) { + this.removeEventListener( evt, bound[ j ].callback, false ); + } + } else if( this.detachEvent ){ + if( callback === undefined ) { + this.detachEvent( "on" + evt, bound[ j ].callback ); + // custom event + document.documentElement.detachEvent( "onpropertychange", bound[ j ].callback ); + } else if( callback === bound[ j ].originalCallback ) { + this.detachEvent( "on" + evt, bound[ j ].callback ); + // custom event + document.documentElement.detachEvent( "onpropertychange", bound[ j ].callback ); + } + } + } + } + } + + function unbindAll( namespace, callback ) { + for( var evtKey in this.shoestringData.events ) { + unbind.call( this, evtKey, namespace, callback ); + } + } + + shoestring.fn.unbind = function( str, callback ){ + var evts = str ? str.split( " " ) : []; return this.each(function(){ if( !this.shoestringData || !this.shoestringData.events ) { return; } - for( var i = 0, il = evts.length; i < il; i++ ){ - //>>includeStart("development", pragmas.development); - if( evts[ i ].indexOf( "." ) === 0 ) { - shoestring.error( 'event-namespaces' ); - } - //>>includeEnd("development"); + if( !evts.length ) { + unbindAll.call( this ); + } else { + var split, evt, namespace; + for( var i = 0, il = evts.length; i < il; i++ ){ + split = evts[ i ].split( "." ), + evt = split[ 0 ], + namespace = split.length > 0 ? split[ 1 ] : null; - var bound = this.shoestringData.events[ evts[ i ] ]; - if( bound ) { - for( var j = 0, jl = bound.length; j < jl; j++ ) { - if( "removeEventListener" in window ){ - if( callback === undefined ) { - this.removeEventListener( evts[ i ], bound[ j ].callback, false ); - } else if( callback === bound[ j ].originalCallback ) { - this.removeEventListener( evts[ i ], bound[ j ].callback, false ); - } - } else if( this.detachEvent ){ - if( callback === undefined ) { - this.detachEvent( "on" + evts[ i ], bound[ j ].callback ); - // custom event - docEl.detachEvent( "onpropertychange", bound[ j ].callback ); - } else if( callback === bound[ j ].originalCallback ) { - this.detachEvent( "on" + evts[ i ], bound[ j ].callback ); - // custom event - docEl.detachEvent( "onpropertychange", bound[ j ].callback ); - } - } + if( evt ) { + unbind.call( this, evt, namespace, callback ); + } else { + unbindAll.call( this, namespace, callback ); } } } diff --git a/src/util/errors.js b/src/util/errors.js index b186682..c102a9a 100644 --- a/src/util/errors.js +++ b/src/util/errors.js @@ -8,7 +8,6 @@ define([ "shoestring" ], function(){ "click": "the click method. Try using trigger( 'click' ) instead.", "css-get" : "getting computed attributes from the DOM.", - "event-namespaces": "event namespacing, especially on .unbind( '.myNamespace' ). An event namespace is treated as part of the event name.", "has-class" : "the hasClass method. Try using .is( '.klassname' ) instead.", "live-delegate" : "the .live or .delegate methods. Use .bind or .on instead.", "map": "the map method. Try using .each to make a new object.", diff --git a/test/unit/extensions.js b/test/unit/extensions.js index 4b27113..72fafd6 100644 --- a/test/unit/extensions.js +++ b/test/unit/extensions.js @@ -671,6 +671,28 @@ }).trigger( "click" ); }); + asyncTest( 'DOM Event `.bind()` and `.trigger()` with arguments', function() { + expect( 1 ); + + shoestring( '#qunit-fixture' ).html( '
' ); + + $( "#el" ).bind( "click", function( e, myArgument ) { + equal( myArgument, "Argument", 'a custom argument should exist.' ); + start(); + }).trigger( "click", [ "Argument" ] ); + }); + + asyncTest( 'Custom Event `.bind()` and `.trigger()` with arguments', function() { + expect( 1 ); + + shoestring( '#qunit-fixture' ).html( '' ); + + $( "#el" ).bind( "myCustomEvent", function( e, myArgument ) { + equal( myArgument, "Argument", 'a custom argument should exist.' ); + start(); + }).trigger( "myCustomEvent", [ "Argument" ] ); + }); + asyncTest( '`.bind()` and `.trigger()` with data', function() { expect( 2 ); @@ -895,7 +917,7 @@ }, 30); }); - asyncTest( '`.unbind()`', function() { + asyncTest( '`.unbind("click", function)`', function() { expect( 1 ); var counter = 0; @@ -915,7 +937,7 @@ }, 30); }); - asyncTest( '`.unbind() multiple dom events`', function() { + asyncTest( '`.unbind("mouseup mousedown", function) multiple dom events`', function() { expect( 1 ); var counter = 0; @@ -936,7 +958,7 @@ }, 30); }); - asyncTest( '`.unbind() multiple custom events`', function() { + asyncTest( '`.unbind("aCustomEvent anotherCustomEvent", function)`', function() { expect( 1 ); var counter = 0; @@ -957,7 +979,7 @@ }, 30); }); - asyncTest( '`.unbind()` without callback', function() { + asyncTest( '`.unbind("click")`', function() { expect( 1 ); var counter = 0; @@ -977,7 +999,7 @@ }, 30); }); - asyncTest( '`.unbind()` custom event', function() { + asyncTest( '`.unbind("aCustomEvent", function)`', function() { expect( 1 ); var counter = 0; @@ -997,7 +1019,7 @@ }, 30); }); - asyncTest( '`.unbind()` without callback, custom event', function() { + asyncTest( '`.unbind("aCustomEvent")`', function() { expect( 1 ); var counter = 0; @@ -1017,7 +1039,27 @@ }, 30); }); - asyncTest( '`.unbind()` in a `.bind()` callback', function() { + asyncTest( '`.unbind()` all', function() { + expect( 1 ); + var counter = 0; + + shoestring( '#qunit-fixture' ).html( '' ); + var f = function() { + counter++; + }; + + $( "#el" ).bind( "aCustomEvent", f ) + .trigger( "aCustomEvent" ) + .unbind() + .trigger( "aCustomEvent" ); + + setTimeout(function() { + equal( counter, 1, "callback should have fired once." ); + start(); + }, 30); + }); + + asyncTest( '`.unbind("aCustomEvent", function)` in a `.bind()` callback', function() { expect( 1 ); var counter = 0; @@ -1209,6 +1251,264 @@ $( "#child" ).trigger( "click" ); }); + asyncTest( 'Custom Events: namespaced bind, namespaced trigger', function() { + expect( 2 ); + + shoestring( '#qunit-fixture' ).html( '' ); + + $( "#el" ).bind( "customEvent.myNamespace", function( e ) { + ok( true, 'event callback should execute.' ); + ok( e.namespace, 'namespace property should exist.' ); + }) + .trigger( "customEvent.myNamespace" ); + + setTimeout(function() { + start(); + }, 15); + }); + + asyncTest( 'Custom Events: namespaced bind, unnamespaced trigger', function() { + expect( 2 ); + + shoestring( '#qunit-fixture' ).html( '' ); + + $( "#el" ).bind( "customEvent.myNamespace", function( e ) { + ok( true, 'event callback should execute.' ); + ok( !e.namespace, 'namespace property should not exist.' ); + }) + .trigger( "customEvent" ); + + setTimeout(function() { + start(); + }, 15); + }); + + asyncTest( 'DOM Events: namespaced bind, namespaced trigger', function() { + expect( 2 ); + + shoestring( '#qunit-fixture' ).html( '' ); + + $( "#el" ).bind( "click.myNamespace", function( e ) { + ok( true, 'event callback should execute.' ); + ok( e.namespace, 'namespace property should exist.' ); + }) + .trigger( "click.myNamespace" ); + + setTimeout(function() { + start(); + }, 15); + }); + + asyncTest( 'DOM Events: namespaced bind, unnamespaced trigger', function() { + expect( 2 ); + + shoestring( '#qunit-fixture' ).html( '' ); + + $( "#el2" ).bind( "click.myNamespace", function( e ) { + ok( true, 'event callback should execute.' ); + ok( !e.namespace, 'namespace property should not exist.' ); + }) + .trigger( "click" ); + + setTimeout(function() { + start(); + }, 15); + }); + + asyncTest( 'DOM Events: unnamespaced bind, namespaced trigger', function() { + expect( 0 ); + + shoestring( '#qunit-fixture' ).html( '' ); + + $( "#el" ).bind( "click", function( e ) { + ok( true, 'event callback should not execute.' ); + }).trigger( "click.myNamespace" ); + + setTimeout(function() { + start(); + }, 15); + }); + +asyncTest( '`Custom Events: .bind("myCustomEvent.myNamespace") .unbind("myCustomEvent.myNamespace")`', function() { + expect( 1 ); + var counter = 0; + + shoestring( '#qunit-fixture' ).html( '' ); + var f = function() { + counter++; + }; + + $( "#el" ).bind( "myCustomEvent.myNamespace", f ) + .trigger( "myCustomEvent.myNamespace" ) + .unbind( "myCustomEvent.myNamespace" ) + .trigger( "myCustomEvent.myNamespace" ); + + setTimeout(function() { + equal( counter, 1, "callback should have fired once." ); + start(); + }, 30); + }); + + asyncTest( '`Custom Events: .bind("myCustomEvent.myNamespace") .unbind("myCustomEvent.myNamespace", function)`', function() { + expect( 1 ); + var counter = 0; + + shoestring( '#qunit-fixture' ).html( '' ); + var f = function() { + counter++; + }; + + $( "#el" ).bind( "myCustomEvent.myNamespace", f ) + .trigger( "myCustomEvent.myNamespace" ) + .unbind( "myCustomEvent.myNamespace", f ) + .trigger( "myCustomEvent.myNamespace" ); + + setTimeout(function() { + equal( counter, 1, "callback should have fired once." ); + start(); + }, 30); + }); + + asyncTest( '`Custom Events: .bind("myCustomEvent.myNamespace") .unbind("myCustomEvent")`', function() { + expect( 1 ); + var counter = 0; + + shoestring( '#qunit-fixture' ).html( '' ); + var f = function() { + counter++; + }; + + $( "#el" ).bind( "myCustomEvent.myNamespace", f ) + .trigger( "myCustomEvent.myNamespace" ) + .unbind( "myCustomEvent" ) + .trigger( "myCustomEvent.myNamespace" ); + + setTimeout(function() { + equal( counter, 1, "callback should have fired once." ); + start(); + }, 30); + }); + + asyncTest( '`Custom Events: .bind("myCustomEvent") .unbind("myCustomEvent.myNamespace", function)`', function() { + expect( 1 ); + var counter = 0; + + shoestring( '#qunit-fixture' ).html( '' ); + var f = function() { + counter++; + }; + + $( "#el" ).bind( "myCustomEvent", f ) + .trigger( "myCustomEvent" ) + .unbind( "myCustomEvent.myNamespace", f ) + .trigger( "myCustomEvent" ); + + setTimeout(function() { + equal( counter, 2, "callback should fire twice. unbind should have not matched anything." ); + start(); + }, 30); + }); + + asyncTest( '`DOM Events: .bind("click.myNamespace") .unbind("click.myNamespace")`', function() { + expect( 1 ); + var counter = 0; + + shoestring( '#qunit-fixture' ).html( '' ); + var f = function() { + counter++; + }; + + $( "#el" ).bind( "click.myNamespace", f ) + .trigger( "click.myNamespace" ) + .unbind( "click.myNamespace" ) + .trigger( "click.myNamespace" ); + + setTimeout(function() { + equal( counter, 1, "callback should have fired once." ); + start(); + }, 30); + }); + + asyncTest( '`DOM Events: .bind("click.myNamespace") .unbind("click.myNamespace", function)`', function() { + expect( 1 ); + var counter = 0; + + shoestring( '#qunit-fixture' ).html( '' ); + var f = function() { + counter++; + }; + + $( "#el" ).bind( "click.myNamespace", f ) + .trigger( "click.myNamespace" ) + .unbind( "click.myNamespace", f ) + .trigger( "click.myNamespace" ); + + setTimeout(function() { + equal( counter, 1, "callback should have fired once." ); + start(); + }, 30); + }); + + asyncTest( '`DOM Events: .bind("click.myNamespace") .unbind("click")`', function() { + expect( 1 ); + var counter = 0; + + shoestring( '#qunit-fixture' ).html( '' ); + var f = function() { + counter++; + }; + + $( "#el" ).bind( "click.myNamespace", f ) + .trigger( "click.myNamespace" ) + .unbind( "click" ) + .trigger( "click.myNamespace" ); + + setTimeout(function() { + equal( counter, 1, "callback should have fired once." ); + start(); + }, 30); + }); + + asyncTest( '`DOM Events: .bind("click") .unbind("click.myNamespace", function)`', function() { + expect( 1 ); + var counter = 0; + + shoestring( '#qunit-fixture' ).html( '' ); + var f = function() { + counter++; + }; + + $( "#el" ).bind( "click", f ) + .trigger( "click" ) + .unbind( "click.myNamespace", f ) + .trigger( "click" ); + + setTimeout(function() { + equal( counter, 2, "callback should fire twice. unbind should have not matched anything." ); + start(); + }, 30); + }); + + asyncTest( '`DOM Events: .unbind(".myNamespace")`', function() { + expect( 1 ); + var counter = 0; + + shoestring( '#qunit-fixture' ).html( '' ); + var f = function() { + counter++; + }; + + $( "#el" ).bind( "click.myNamespace", f ) + .trigger( "click.myNamespace" ) + .unbind( ".myNamespace", f ) + .trigger( "click.myNamespace" ); + + setTimeout(function() { + equal( counter, 1, "callback should fire once." ); + start(); + }, 30); + }); + if( window.JSON && 'localStorage' in window ) { module( "util", config ); @@ -1226,7 +1526,7 @@ }); } - module( 'events', config ); + module( 'ajax', config ); test( "ajax doesn't override default options", function() { equal( shoestring.ajax.settings.method, "GET" ); @@ -1235,5 +1535,4 @@ }); // TODO test events + arguments on callbacks and trigger - // TODO unbind events by namespace only })();