diff --git a/README.md b/README.md index eb303ea..a24e3de 100644 --- a/README.md +++ b/README.md @@ -67,6 +67,20 @@ Or alternatively, you can pass an array of elements (this actually cuts down on *(note: the focus, blur, and submit events will not delegate)* +Bean also now supports namespacing your events! This makes it much easier to target them down the line with things like remove or fire. To name space an event just add a dot followed by your unique name identifier: + + bean.add(element, 'click.fat', fn); + bean.add(element, 'click.ded', fn); + bean.add(element, 'click', fn); + + //later... + bean.fire(element, 'click.ded'); + bean.remove(element, 'click.fat'); + + //alternatively you can specify mutliple remove handlers at once + bean.fire(element, 'click.ded.fat'); + bean.remove(element, 'click.fat.ded'); + remove() ------ bean.remove() is how you get rid of listeners once you no longer want them. It's also a good idea to call remove on elements before you remove elements from your dom (this gives bean a chance to clean up some things and prevents memory leaks) diff --git a/bean.js b/bean.js index ce1ea05..c1ffc36 100644 --- a/bean.js +++ b/bean.js @@ -33,7 +33,7 @@ } function retrieveUid(obj, uid) { - return (obj._uid = obj._uid || uid || _uid++); + return (obj._uid = uid || obj._uid || _uid++); } function listener(element, type, fn, add, custom) { @@ -60,8 +60,10 @@ }; } - function addListener(element, type, fn, args) { - var events = retrieveEvents(element), handlers = events[type] || (events[type] = {}), uid = retrieveUid(fn, type.replace(namespace, '')); + function addListener(element, orgType, fn, args) { + var type = orgType.replace(/\..*/, ''), events = retrieveEvents(element), + handlers = events[type] || (events[type] = {}), + uid = retrieveUid(fn, orgType.replace(namespace, '')); if (handlers[uid]) { return element; } @@ -150,16 +152,19 @@ function fire(element, type) { var evt, k, i, types = type.split(' '); for (i = types.length; i--;) { - type = types[i]; - var isNative = nativeEvents.indexOf(type) > -1; - if (element[addEvent]) { + type = types[i].replace(/\..*/, ''); + var isNative = nativeEvents.indexOf(type) > -1, + namespace = types[i].replace(namespace, ''), + handlers = retrieveEvents(element)[type]; + if (namespace) { + handlers[namespace] && handlers[namespace](); + } else if (element[addEvent]) { evt = document.createEvent(isNative ? "HTMLEvents" : "UIEvents"); evt[isNative ? 'initEvent' : 'initUIEvent'](type, true, true, context, 1); element.dispatchEvent(evt); } else if (element[attachEvent]) { isNative ? element.fireEvent('on' + type, document.createEventObject()) : element['_on' + type]++; } else { - var handlers = retrieveEvents(element)[type]; for (k in handlers) { handlers.hasOwnProperty(k) && handlers[k](); } diff --git a/bean.min.js b/bean.min.js index 65a79e6..1a1a994 100644 --- a/bean.min.js +++ b/bean.min.js @@ -8,4 +8,4 @@ * dperini: https://github.com/dperini/nwevents * the entire mootools team: github.com/mootools/mootools-core */ -!function(a){function y(a){var b=a.relatedTarget;if(!b)return b==null;return b!=this&&b.prefix!="xul"&&!/document/.test(this.toString())&&!j(this,b)}function w(a){if(!a)return{};var b=a.type,c=a.target||a.srcElement;a.preventDefault=a.preventDefault||w.preventDefault,a.stopPropagation=a.stopPropagation||w.stopPropagation,a.target=c&&c.nodeType==3?c.parentNode:c;if(b.indexOf("key")!=-1)a.keyCode=a.which||a.keyCode;else if(/click|mouse|menu/i.test(b)){a.rightClick=a.which==3||a.button==2,a.pos={x:0,y:0};if(a.pageX||a.pageY)a.pos.x=a.pageX,a.pos.y=a.pageY;else if(a.clientX||a.clientY)a.pos.x=a.clientX+document.body.scrollLeft+document.documentElement.scrollLeft,a.pos.y=a.clientY+document.body.scrollTop+document.documentElement.scrollTop;e.test(b)&&(a.relatedTarget=a.relatedTarget||a[(b=="mouseover"?"from":"to")+"Element"])}return a}function v(a,b,c){var d=k(b),e,f;e=c?d[c]:d;for(f in e)e.hasOwnProperty(f)&&(c?s:v)(a,c||b,c?e[f]:f);return a}function u(b,c){var d,e,h,i=c.split(" ");for(h=i.length;h--;){c=i[h];var j=x.indexOf(c)>-1;if(b[f])d=document.createEvent(j?"HTMLEvents":"UIEvents"),d[j?"initEvent":"initUIEvent"](c,!0,!0,a,1),b.dispatchEvent(d);else if(b[g])j?b.fireEvent("on"+c,document.createEventObject()):b["_on"+c]++;else{var l=k(b)[c];for(e in l)l.hasOwnProperty(e)&&l[e]()}}return b}function t(a,b,c){var d,e,f=typeof b=="string",g=q,h=k(a);if(f&&/\s/.test(b)){b=b.split(" ");var i=b.length-1;while(t(a,b[i])&&i--);return a}if(!h||f&&!h[b])return a;if(typeof c=="function")g(a,b,c);else{g=b?g:t,e=f&&b,b=b?c||h[b]||b:h;for(d in b)b.hasOwnProperty(d)&&g(a,e||d,b[d])}return a}function s(a,b,c,d,e){if(typeof b=="object"&&!c)for(var f in b)b.hasOwnProperty(f)&&s(a,f,b[f]);else{var g=typeof c=="string",h=(g?c:b).split(" ");c=g?r(b,d,e):c;for(var i=h.length;i--;)p(a,h[i],c,Array.prototype.slice.call(arguments,g?4:3))}return a}function r(a,b,c){return function(d){var e=typeof a=="string"?c(a,this):a;for(var f=d.target;f&&f!=this;f=f.parentNode)for(var g=e.length;g--;)if(e[g]==f)return b.apply(f,arguments)}}function q(a,b,c){var d=k(a);if(!d||!d[b])return a;c=d[b][c._uid],delete d[b][c._uid],b=z[b]?z[b].base:b;var e=a[f]||x.indexOf(b)>-1;m(a,e?b:"propertychange",c,!1,!e&&b);return a}function p(b,c,e,g){var h=k(b),i=h[c]||(h[c]={}),j=l(e);if(i[j])return b;var p=z[c];e=p&&p.condition?o(b,e,c,p.condition):e,c=p&&p.base||c;var r=a[f]||x.indexOf(c)>-1;e=r?n(b,e,g):o(b,e,c,!1,g);if(c=="unload"){var s=e;e=function(){q(b,c,e)&&s()}}m(b,r?c:"propertychange",e,!0,!r&&!0),i[j]=e,e._uid=j;return c=="unload"?b:d[l(b)]=b}function o(a,b,c,d,e){return function(f){(d?d.call(this,f):f&&f.propertyName=="_on"+c||!f)&&b.apply(a,[f].concat(e))}}function n(b,c,d){return function(e){e=w(e||((this.ownerDocument||this.document||this).parentWindow||a).event);return c.apply(b,[e].concat(d))}}function m(a,b,c,d,e){a[f]?a[d?f:h](b,c,!1):a[g]&&(e&&d&&(a["_on"+e]=a["_on"+e]||0),a[d?g:i]("on"+b,c))}function l(a){return a._uid=a._uid||b++}function k(a){var b=l(a);return c[b]=c[b]||{}}function j(a,b){var c=b.parentNode;while(c!=null){if(c==a)return!0;c=c.parentNode}}var b=1,c={},d={},e=/over|out/,f="addEventListener",g="attachEvent",h="removeEventListener",i="detachEvent";w.preventDefault=function(){this.returnValue=!1},w.stopPropagation=function(){this.cancelBubble=!0};var x="click,dblclick,mouseup,mousedown,contextmenu,mousewheel,DOMMouseScroll,mouseover,mouseout,mousemove,selectstart,selectend,keydown,keypress,keyup,orientationchange,touchstart,touchmove,touchend,touchcancel,gesturestart,gesturechange,gestureend,focus,blur,change,reset,select,submit,load,unload,beforeunload,resize,move,DOMContentLoaded,readystatechange,"+"error,abort,scroll".split(","),z={mouseenter:{base:"mouseover",condition:y},mouseleave:{base:"mouseout",condition:y},mousewheel:{base:/Firefox/.test(navigator.userAgent)?"DOMMouseScroll":"mousewheel"}},A={add:s,remove:t,clone:v,fire:u},B=function(a){var b=t(a)._uid;b&&(delete d[b],delete c[b])};a[g]&&s(a,"unload",function(){for(var b in d)d.hasOwnProperty(b)&&B(d[b]);a.CollectGarbage&&CollectGarbage()});var C=a.bean;A.noConflict=function(){a.bean=C;return this},typeof module!="undefined"&&module.exports?module.exports=A:a.bean=A}(this) \ No newline at end of file +!function(a){function z(a){var b=a.relatedTarget;if(!b)return b==null;return b!=this&&b.prefix!="xul"&&!/document/.test(this.toString())&&!k(this,b)}function x(a){if(!a)return{};var b=a.type,c=a.target||a.srcElement;a.preventDefault=a.preventDefault||x.preventDefault,a.stopPropagation=a.stopPropagation||x.stopPropagation,a.target=c&&c.nodeType==3?c.parentNode:c;if(b.indexOf("key")!=-1)a.keyCode=a.which||a.keyCode;else if(/click|mouse|menu/i.test(b)){a.rightClick=a.which==3||a.button==2,a.pos={x:0,y:0};if(a.pageX||a.pageY)a.pos.x=a.pageX,a.pos.y=a.pageY;else if(a.clientX||a.clientY)a.pos.x=a.clientX+document.body.scrollLeft+document.documentElement.scrollLeft,a.pos.y=a.clientY+document.body.scrollTop+document.documentElement.scrollTop;e.test(b)&&(a.relatedTarget=a.relatedTarget||a[(b=="mouseover"?"from":"to")+"Element"])}return a}function w(a,b,c){var d=l(b),e,f;e=c?d[c]:d;for(f in e)e.hasOwnProperty(f)&&(c?t:w)(a,c||b,c?e[f]:f);return a}function v(b,c){var d,e,f,i=c.split(" ");for(f=i.length;f--;){c=i[f].replace(/\..*/,"");var j=y.indexOf(c)>-1,k=i[f].replace(k,""),m=l(b)[c];if(k)m[k]&&m[k]();else if(b[g])d=document.createEvent(j?"HTMLEvents":"UIEvents"),d[j?"initEvent":"initUIEvent"](c,!0,!0,a,1),b.dispatchEvent(d);else if(b[h])j?b.fireEvent("on"+c,document.createEventObject()):b["_on"+c]++;else for(e in m)m.hasOwnProperty(e)&&m[e]()}return b}function u(a,b,c){var d,e,f=typeof b=="string",g=r,h=l(a);if(f&&/\s/.test(b)){b=b.split(" ");var i=b.length-1;while(u(a,b[i])&&i--);return a}if(!h||f&&!h[b])return a;if(typeof c=="function")g(a,b,c);else{g=b?g:u,e=f&&b,b=b?c||h[b]||b:h;for(d in b)b.hasOwnProperty(d)&&g(a,e||d,b[d])}return a}function t(a,b,c,d,e){if(typeof b=="object"&&!c)for(var f in b)b.hasOwnProperty(f)&&t(a,f,b[f]);else{var g=typeof c=="string",h=(g?c:b).split(" ");c=g?s(b,d,e):c;for(var i=h.length;i--;)q(a,h[i],c,Array.prototype.slice.call(arguments,g?4:3))}return a}function s(a,b,c){return function(d){var e=typeof a=="string"?c(a,this):a;for(var f=d.target;f&&f!=this;f=f.parentNode)for(var g=e.length;g--;)if(e[g]==f)return b.apply(f,arguments)}}function r(a,b,c){var d=l(a);if(!d||!d[b])return a;c=d[b][c._uid],delete d[b][c._uid],b=A[b]?A[b].base:b;var e=a[g]||y.indexOf(b)>-1;n(a,e?b:"propertychange",c,!1,!e&&b);return a}function q(b,c,e,h){var i=c.replace(/\..*/,""),j=l(b),k=j[i]||(j[i]={}),q=m(e,c.replace(f,""));if(k[q])return b;var s=A[i];e=s&&s.condition?p(b,e,i,s.condition):e,i=s&&s.base||i;var t=a[g]||y.indexOf(i)>-1;e=t?o(b,e,h):p(b,e,i,!1,h);if(i=="unload"){var u=e;e=function(){r(b,i,e)&&u()}}n(b,t?i:"propertychange",e,!0,!t&&!0),k[q]=e,e._uid=q;return i=="unload"?b:d[m(b)]=b}function p(a,b,c,d,e){return function(f){(d?d.call(this,f):f&&f.propertyName=="_on"+c||!f)&&b.apply(a,[f].concat(e))}}function o(b,c,d){return function(e){e=x(e||((this.ownerDocument||this.document||this).parentWindow||a).event);return c.apply(b,[e].concat(d))}}function n(a,b,c,d,e){a[g]?a[d?g:i](b,c,!1):a[h]&&(e&&d&&(a["_on"+e]=a["_on"+e]||0),a[d?h:j]("on"+b,c))}function m(a,c){return a._uid=c||a._uid||b++}function l(a){var b=m(a);return c[b]=c[b]||{}}function k(a,b){var c=b.parentNode;while(c!=null){if(c==a)return!0;c=c.parentNode}}var b=1,c={},d={},e=/over|out/,f=/.*(?=\..*)\.|.*/,g="addEventListener",h="attachEvent",i="removeEventListener",j="detachEvent";x.preventDefault=function(){this.returnValue=!1},x.stopPropagation=function(){this.cancelBubble=!0};var y="click,dblclick,mouseup,mousedown,contextmenu,mousewheel,DOMMouseScroll,mouseover,mouseout,mousemove,selectstart,selectend,keydown,keypress,keyup,orientationchange,touchstart,touchmove,touchend,touchcancel,gesturestart,gesturechange,gestureend,focus,blur,change,reset,select,submit,load,unload,beforeunload,resize,move,DOMContentLoaded,readystatechange,"+"error,abort,scroll".split(","),A={mouseenter:{base:"mouseover",condition:z},mouseleave:{base:"mouseout",condition:z},mousewheel:{base:/Firefox/.test(navigator.userAgent)?"DOMMouseScroll":"mousewheel"}},B={add:t,remove:u,clone:w,fire:v},C=function(a){var b=u(a)._uid;b&&(delete d[b],delete c[b])};a[h]&&t(a,"unload",function(){for(var b in d)d.hasOwnProperty(b)&&C(d[b]);a.CollectGarbage&&CollectGarbage()});var D=a.bean;B.noConflict=function(){a.bean=D;return this},typeof module!="undefined"&&module.exports?module.exports=B:a.bean=B}(this) \ No newline at end of file diff --git a/src/bean.js b/src/bean.js index aa71446..72561c4 100644 --- a/src/bean.js +++ b/src/bean.js @@ -1,7 +1,8 @@ !function (context) { - var _uid = 1, registry = {}, collected = {}, + var __uid = 1, registry = {}, collected = {}, overOut = /over|out/, - namespace = /.*(?=\..*)\.|.*/, + namespace = /[^\.]*(?=\..*)\.|.*/, + stripName = /\..*/, addEvent = 'addEventListener', attachEvent = 'attachEvent', removeEvent = 'removeEventListener', @@ -23,7 +24,7 @@ } function retrieveUid(obj, uid) { - return (obj._uid = uid || obj._uid || _uid++); + return (obj.__uid = uid || obj.__uid || __uid++); } function listener(element, type, fn, add, custom) { @@ -51,7 +52,7 @@ } function addListener(element, orgType, fn, args) { - var type = orgType.replace(/\..*/, ''), events = retrieveEvents(element), + var type = orgType.replace(stripName, ''), events = retrieveEvents(element), handlers = events[type] || (events[type] = {}), uid = retrieveUid(fn, orgType.replace(namespace, '')); if (handlers[uid]) { @@ -70,20 +71,25 @@ } listener(element, isNative ? type : 'propertychange', fn, true, !isNative && true); handlers[uid] = fn; - fn._uid = uid; + fn.__uid = uid; return type == 'unload' ? element : (collected[retrieveUid(element)] = element); } - function removeListener(element, type, handler) { - var events = retrieveEvents(element); + function removeListener(element, orgType, handler) { + var uid, names, uids, i, events = retrieveEvents(element), type = orgType.replace(stripName, ''); if (!events || !events[type]) { return element; } - handler = events[type][handler._uid]; - delete events[type][handler._uid]; - type = customEvents[type] ? customEvents[type].base : type; - var isNative = element[addEvent] || nativeEvents.indexOf(type) > -1; - listener(element, isNative ? type : 'propertychange', handler, false, !isNative && type); + names = orgType.replace(namespace, ''); + uids = names ? names.split('.') : [handler.__uid]; + for (i = uids.length; i--;) { + uid = uids[i]; + handler = events[type][uid]; + delete events[type][uid]; + type = customEvents[type] ? customEvents[type].base : type; + var isNative = element[addEvent] || nativeEvents.indexOf(type) > -1; + listener(element, isNative ? type : 'propertychange', handler, false, !isNative && type); + } return element; } @@ -115,19 +121,26 @@ return element; } - function remove(element, events, fn) { - var k, type, isString = typeof(events) == 'string', rm = removeListener, attached = retrieveEvents(element); - if (isString && /\s/.test(events)) { - events = events.split(' '); - var i = events.length - 1; - while (remove(element, events[i]) && i--) {} + function remove(element, orgEvents, fn) { + var k, type, events, + isString = typeof(orgEvents) == 'string', + names = isString && orgEvents.replace(namespace, ''), + rm = removeListener, + attached = retrieveEvents(element); + if (isString && /\s/.test(orgEvents)) { + orgEvents = orgEvents.split(' '); + var i = orgEvents.length - 1; + while (remove(element, orgEvents[i]) && i--) {} return element; } + events = isString ? orgEvents.replace(stripName, '') : orgEvents; if (!attached || (isString && !attached[events])) { return element; } if (typeof fn == 'function') { rm(element, events, fn); + } else if (names) { + rm(element, orgEvents); } else { rm = events ? rm : remove; type = isString && events; @@ -140,19 +153,24 @@ } function fire(element, type) { - - //todo make fire work!! var evt, k, i, types = type.split(' '); for (i = types.length; i--;) { - var isNative = nativeEvents.indexOf(type) > -1, orgType = type[i], isNamespace = orgType.test(/./); - if (element[addEvent] && !isNamespace) { + type = types[i].replace(/\..*/, ''); + var isNative = nativeEvents.indexOf(type) > -1, + isNamespace = types[i].replace(namespace, ''), + handlers = retrieveEvents(element)[type]; + if (isNamespace) { + isNamespace = isNamespace.split('.'); + for (k = isNamespace.length; k--;) { + handlers[isNamespace[k]] && handlers[isNamespace[k]](); + } + } else if (element[addEvent]) { evt = document.createEvent(isNative ? "HTMLEvents" : "UIEvents"); evt[isNative ? 'initEvent' : 'initUIEvent'](type, true, true, context, 1); element.dispatchEvent(evt); - } else if (element[attachEvent] && !isNamespace){ + } else if (element[attachEvent]) { isNative ? element.fireEvent('on' + type, document.createEventObject()) : element['_on' + type]++; } else { - var handlers = retrieveEvents(element)[type]; for (k in handlers) { handlers.hasOwnProperty(k) && handlers[k](); } @@ -229,7 +247,7 @@ var bean = { add: add, remove: remove, clone: clone, fire: fire }; var clean = function (el) { - var uid = remove(el)._uid; + var uid = remove(el).__uid; if (uid) { delete collected[uid]; delete registry[uid]; diff --git a/tests/tests.js b/tests/tests.js index a1a200b..7eee621 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -340,19 +340,33 @@ sink('add', function (test, ok) { bean.fire(el1, 'click.fat'); }); - // test('namespace: should be able to remove handlers based on name', 1, function () { - // var el1 = document.getElementById('foo'); - // var el2 = document.getElementById('bar'); - // bean.add(el1, 'click.ded', function () {ok(true, 'bubbles up dom')}); - // Syn.click(el2); - // }); - // - // test('namespace: should be able to remove multiple handlers based on name', 1, function () { - // var el1 = document.getElementById('foo'); - // var el2 = document.getElementById('bar'); - // bean.add(el1, 'click.fat', function () {ok(true, 'bubbles up dom')}); - // Syn.click(el2); - // }); + test('namespace: should be able to target multiple namespaced event handlers with fire', 2, function () { + var el1 = document.getElementById('foo'); + bean.remove(el1); + bean.add(el1, 'click.fat', function () {ok(true, 'target multiple namespaced event handlers with fire')}); + bean.add(el1, 'click.ded', function () {ok(true, 'targets multiple namespaced event handlers with fire')}); + bean.add(el1, 'click', function () {ok(true, 'targets multiple namespaced event handlers with fire')}); + bean.fire(el1, 'click.fat.ded'); + }); + + test('namespace: should be able to remove handlers based on name', 1, function () { + var el1 = document.getElementById('foo'); + bean.remove(el1); + bean.add(el1, 'click.ded', function () {ok(true, 'removes handlers based on name')}); + bean.add(el1, 'click', function () {ok(true, 'removes handlers based on name')}); + bean.remove(el1, 'click.ded'); + Syn.click(el1); + }); + + test('namespace: should be able to remove multiple handlers based on name', 1, function () { + var el1 = document.getElementById('foo'); + bean.remove(el1); + bean.add(el1, 'click.fat', function () {ok(true, 'removes multiple handlers based on name')}); + bean.add(el1, 'click.ded', function () {ok(true, 'removes multiple handlers based on name')}); + bean.add(el1, 'click', function () {ok(true, 'removes multiple handlers based on name')}); + bean.remove(el1, 'click.ded.fat'); + Syn.click(el1); + }); });