From 8a7c569d8c17cf74b2934234468f0edd9bae13f4 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Fri, 25 Nov 2011 14:55:53 +1100 Subject: [PATCH 01/19] registry rewrite, closes #19, closes #34 --- src/bean.js | 325 +++++++++++++++++++++++-------------------------- tests/tests.js | 14 +++ 2 files changed, 163 insertions(+), 176 deletions(-) diff --git a/src/bean.js b/src/bean.js index b8620fa..3a954be 100644 --- a/src/bean.js +++ b/src/bean.js @@ -4,12 +4,10 @@ else this[name] = definition(); }('bean', function () { var win = window, - __uid = 1, - registry = {}, - collected = {}, overOut = /over|out/, namespace = /[^\.]*(?=\..*)\.|.*/, stripName = /\..*/, + own = 'hasOwnProperty', addEvent = 'addEventListener', attachEvent = 'attachEvent', removeEvent = 'removeEventListener', @@ -19,31 +17,92 @@ W3C_MODEL = root[addEvent], eventSupport = W3C_MODEL ? addEvent : attachEvent, - isDescendant = function (parent, child) { - var node = child.parentNode; - while (node !== null) { - if (node == parent) { - return true; - } - node = node.parentNode; - } + customEvents = { + mouseenter: { base: 'mouseover', condition: check }, + mouseleave: { base: 'mouseout', condition: check }, + mousewheel: { base: /Firefox/.test(navigator.userAgent) ? 'DOMMouseScroll' : 'mousewheel' } }, - retrieveUid = function (obj, uid) { - return (obj.__uid = uid && (uid + '::' + __uid++) || obj.__uid || __uid++); - }, + RegEntry = function () { + function entry(element, type, handler, original, namespaces) { + this.element = element; + this.type = type; + this.handler = handler; + this.original = original; + this.namespaces = namespaces; + this.isNative = !!nativeEvents[type] && !!element[eventSupport]; + this.custom = customEvents[type]; + this.eventType = W3C_MODEL || this.isNative ? type : 'propertychange'; + this.customType = !W3C_MODEL && !this.isNative && type; + this.targetElement = targetElement(element, this.isNative); + this.eventSupport = !!this.targetElement[eventSupport]; + } + entry.prototype.inNamespaces = function (namespaces) { + var i, j; + if (!namespaces) return true; + if (!this.namespaces) return false; + for (i = namespaces.length; i--;) { + for (j = this.namespaces.length; j--;) { + if (namespaces[i] === this.namespaces[j]) return true; + } + } + return false; + } + entry.prototype.matches = function (element, original, handler) { + return this.element === element && + (!original || this.original === original) && + (!handler || this.handler === handler); + } + return entry; + }(), + + registry = function () { + var map = {}, + forAll = function (element, type, original, handler, fn) { + if (!type) { + for (var t in map) map[own](t) && forAll(element, t, original, handler, fn); + } else { + var i, list = map[type]; + for (i in list) { + if (list[own](i) && (element === '*' || list[i].matches(element, original, handler))) + if (!fn(list[i], list, i, type)) return; + } + } + }, + get = function (element, type, original) { + var handlers = []; + forAll(element, type, original, null, function(handler) { return handlers.push(handler); }); + return handlers; + }, + put = function (entry) { + (map[entry.type] || (map[entry.type] = [])).push(entry); + return entry; + }, + del = function (entry) { + forAll(entry.element, entry.type, null, entry.handler, function(handler, list, i) { + list.splice(i, 1); + return false; + }); + }, + entries = function () { + var t, entries = []; + for (t in map) map[own](t) && (entries = entries.concat(map[t])); + return entries; + } + return { forAll: forAll, get: get, put: put, del: del, entries: entries }; + }(), - retrieveEvents = function (element) { - var uid = retrieveUid(element); - return (registry[uid] = registry[uid] || {}); + isDescendant = function (parent, node) { + while ((node = node.parentNode) !== null) { + if (node === parent) return true; + } + return false; }, listener = W3C_MODEL ? function (element, type, fn, add) { element[add ? addEvent : removeEvent](type, fn, false); } : function (element, type, fn, add, custom) { - if (custom && add && element['_on' + custom] === null) { - element['_on' + custom] = 0; - } + if (custom && add && element['_on' + custom] === null) element['_on' + custom] = 0; element[add ? attachEvent : detachEvent]('on' + type, fn); }, @@ -67,82 +126,46 @@ return !W3C_MODEL && !isNative && (element === doc || element === win) ? root : element; }, - addListener = function (element, orgType, fn, args) { - var type = orgType.replace(stripName, ''), - events = retrieveEvents(element), - handlers = events[type] || (events[type] = {}), - originalFn = fn, - uid = retrieveUid(fn, orgType.replace(namespace, '')); - if (handlers[uid]) { - return element; - } - var custom = customEvents[type]; - if (custom) { - fn = custom.condition ? customHandler(element, fn, type, custom.condition) : fn; - type = custom.base || type; - } - var isNative = nativeEvents[type]; - fn = isNative ? nativeHandler(element, fn, args) : customHandler(element, fn, type, false, args); - isNative = W3C_MODEL || isNative; - if (type == 'unload') { - var org = fn; - fn = function () { - removeListener(element, type, fn) && org(); - }; + addListener = function (element, orgType, fn, originalFn, args) { + var entry, type = orgType.replace(stripName, ''), + namespaces = orgType.replace(namespace, '').split('.'); + + if (registry.get(element, type, fn).length) return element; + if (type === 'unload') fn = once(removeListener, element, type, fn, originalFn); + if (customEvents[type]) { + fn = customEvents[type].condition ? customHandler(element, fn, type, customEvents[type].condition) : fn; + type = customEvents[type].base || type; } - element = targetElement(element, isNative); - element[eventSupport] && listener(element, isNative ? type : 'propertychange', fn, true, !isNative && type); - handlers[uid] = fn; - fn.__uid = uid; - fn.__originalFn = originalFn; - return type == 'unload' ? element : (collected[retrieveUid(element)] = element); + entry = registry.put(new RegEntry(element, type, fn, originalFn, !!namespaces[0] && namespaces)); + entry.handler = entry.isNative ? + nativeHandler(element, entry.handler, args) : + customHandler(element, entry.handler, type, false, args); + if (entry.eventSupport) + listener(entry.targetElement, entry.eventType, entry.handler, true, entry.customType); + return element }, - removeListener = function (element, orgType, handler) { - var uid = element.__uid, names, uids, i, events = retrieveEvents(element), type = orgType.replace(stripName, ''); + removeListener = function (element, orgType, handler, names) { + var i, entry, type = (orgType && orgType.replace(stripName, '')), + handlers = registry.get(element, type, handler); - if (!events || !events[type]) { - return element; - } + if (!handlers.length) return element; - handler && handler.__one && (handler = handler.__one) - names = orgType.replace(namespace, ''); - uids = names ? names.split('.') : [handler.__uid]; - - function destroyHandler(uid) { - handler = events[type][uid]; - if (!handler) { - return; - } - delete events[type][uid]; - if (element[eventSupport]) { - type = customEvents[type] ? customEvents[type].base : type; - var isNative = W3C_MODEL || nativeEvents[type]; - element = targetElement(element, isNative); - listener(element, isNative ? type : 'propertychange', handler, false, !isNative && type); + for (i in handlers) { + if (handlers[own](i) && handlers[i].inNamespaces(names)) { + entry = handlers[i]; + entry.eventSupport && listener(entry.targetElement, entry.eventType, entry.handler, false, entry.type); + registry.del(entry) } } - - destroyHandler(names); //get combos - for (i = uids.length; i--; destroyHandler(uids[i])) {} //get singles - - if (isEmpty(events[type])) { - delete events[type]; - } - - if (isEmpty(registry[uid])) { - delete registry[uid]; - delete collected[uid]; - } - return element; }, del = function (selector, fn, $) { return function (e) { - var array = typeof selector == 'string' ? $(selector, this) : selector; - for (var target = e.target; target && target != this; target = target.parentNode) { - for (var i = array.length; i--;) { + var target, i, array = typeof selector == 'string' ? $(selector, this) : selector; + for (target = e.target; target && target != this; target = target.parentNode) { + for (i = array.length; i--;) { if (array[i] == target) { return fn.apply(target, arguments); } @@ -152,27 +175,29 @@ }, _add = function (meth, element, events, fn, delfn, $) { + var type, types, i, isDel = typeof fn === 'string', originalFn = fn, + args = Array.prototype.slice.call(arguments, 4); + if (typeof events == 'object' && !fn) { - for (var type in events) { - events.hasOwnProperty(type) && _add(meth, element, type, events[type]); + for (type in events) { + events[own](type) && _add(meth, element, type, events[type]); } } else { - var isDel = typeof fn == 'string', types = (isDel ? fn : events).split(' '); - fn = isDel ? del(events, delfn, $) : meth == 'one' ? - function(fn) { - var one = function() { - remove(element, events, one) - fn.apply(this, arguments) - } - return (fn.__one = one) - }(fn) : fn - for (var i = types.length; i--;) { - addListener(element, types[i], fn, Array.prototype.slice.call(arguments, isDel ? 5 : 4)); + types = (isDel ? fn : events).split(' '); + if (isDel) { + fn = del(events, (originalFn = delfn), $); + args = args.slice(1); } + if (meth === 'one') fn = once(remove, element, events, fn, originalFn); + for (i = types.length; i--;) addListener(element, types[i], fn, originalFn, args); } return element; }, + once = function (rm, element, type, fn, originalFn) { + return function () { rm(element, type, originalFn) && fn.apply(this, arguments); }; + }, + add = function () { return _add.apply(this, ['add'].concat(Array.prototype.slice.call(arguments, 0))) }, @@ -182,70 +207,37 @@ }, remove = function (element, orgEvents, fn) { - var k, m, type, events, i, + var k, m, type, events, i, names, isString = typeof(orgEvents) == 'string', - names = isString && orgEvents.replace(namespace, ''), - rm = removeListener, - attached = retrieveEvents(element); - names = names && names.split('.'); + rm = removeListener; + if (isString && /\s/.test(orgEvents)) { orgEvents = orgEvents.split(' '); - i = orgEvents.length - 1; - while (remove(element, orgEvents[i]) && i--) {} + for (i = orgEvents.length; remove(element, orgEvents[i], fn) && i--;) {} return element; } events = isString ? orgEvents.replace(stripName, '') : orgEvents; - if (!attached || names || (isString && !attached[events])) { - for (k in attached) { - if (attached.hasOwnProperty(k)) { - for (i in attached[k]) { - for (m = names.length; m--;) { - attached[k].hasOwnProperty(i) && - new RegExp('^' + names[m] + '::\\d*(\\..*)?$').test(i) && - rm(element, [k, i].join('.')); - } - } - } - } - return element; - } if (typeof fn == 'function') { rm(element, events, fn); - } else if (names) { - rm(element, orgEvents); } else { - rm = events ? rm : remove; - type = isString && events; - events = events ? (fn || attached[events] || events) : attached; - for (k in events) { - if (events.hasOwnProperty(k)) { - rm(element, type || k, events[k]); - delete events[k]; // remove unused leaf keys - } - } + if (names = isString && orgEvents.replace(namespace, '')) names = names.split('.'); + rm(element, type, null, names) } return element; }, fire = function (element, type, args) { - var evt, k, i, m, types = type.split(' '); + var evt, k, i, names, handlers, types = type.split(' '); for (i = types.length; i--;) { type = types[i].replace(stripName, ''); - var isNative = nativeEvents[type], - isNamespace = types[i].replace(namespace, ''), - handlers = retrieveEvents(element)[type]; - if (isNamespace) { - isNamespace = isNamespace.split('.'); - for (k = isNamespace.length; k--;) { - for (m in handlers) { - handlers.hasOwnProperty(m) && new RegExp('^' + isNamespace[k] + '::\\d*(\\..*)?$').test(m) && handlers[m].apply(element, [false].concat(args)); - } - } - } else if (!args && element[eventSupport]) { - fireListener(isNative, type, element); + if (names = types[i].replace(namespace, '')) names = names.split('.'); + if (!names && !args && element[eventSupport]) { + fireListener(nativeEvents[type], type, element); } else { + handlers = registry.get(element, type); for (k in handlers) { - handlers.hasOwnProperty(k) && handlers[k].apply(element, [false].concat(args)); + if (handlers[own](k) && handlers[k].inNamespaces(names)) + handlers[k].handler.apply(element, [false].concat(args)); } } } @@ -253,20 +245,18 @@ }, fireListener = W3C_MODEL ? function (isNative, type, element) { - evt = document.createEvent(isNative ? "HTMLEvents" : "UIEvents"); + evt = doc.createEvent(isNative ? "HTMLEvents" : "UIEvents"); evt[isNative ? 'initEvent' : 'initUIEvent'](type, true, true, win, 1); element.dispatchEvent(evt); } : function (isNative, type, element) { element = targetElement(element, isNative); - isNative ? element.fireEvent('on' + type, document.createEventObject()) : element['_on' + type]++; + isNative ? element.fireEvent('on' + type, doc.createEventObject()) : element['_on' + type]++; }, clone = function (element, from, type) { - var events = retrieveEvents(from), obj, k; - var uid = retrieveUid(element); - obj = type ? events[type] : events; - for (k in obj) { - obj.hasOwnProperty(k) && (type ? add : clone)(element, type || from, type ? obj[k].__originalFn : k); + var i, handlers = registry.get(from, type) + for (i in handlers) { + handlers[own](i) && add(element, handlers[i].type, handlers[i].original) } return element; }, @@ -300,14 +290,7 @@ } } return result; - }, - - isEmpty = function (obj) { - for (var prop in obj) { - if (obj.hasOwnProperty(prop)) return false; - } - return true; - } + }; fixEvent.preventDefault = function (e) { return function () { @@ -349,29 +332,19 @@ return (related != this && related.prefix != 'xul' && !/document/.test(this.toString()) && !isDescendant(this, related)); } - var customEvents = { - mouseenter: { base: 'mouseover', condition: check }, - mouseleave: { base: 'mouseout', condition: check }, - mousewheel: { base: /Firefox/.test(navigator.userAgent) ? 'DOMMouseScroll' : 'mousewheel' } - }; - var bean = { add: add, one: one, remove: remove, clone: clone, fire: fire }; - var clean = function (el) { - var uid = remove(el).__uid; - if (uid) { - delete collected[uid]; - delete registry[uid]; - } - }; - if (win[attachEvent]) { - add(win, 'unload', function () { - for (var k in collected) { - collected.hasOwnProperty(k) && clean(collected[k]); + var cleanup = function () { + var i, entries = registry.entries(); + for (i in entries) { + if (entries[own](i) && entries[i].type !== 'unload') + remove(entries[i].element, entries[i].type); } + win[detachEvent]('onunload', cleanup); win.CollectGarbage && CollectGarbage(); - }); + }; + win[attachEvent]('onunload', cleanup); } bean.noConflict = function () { diff --git a/tests/tests.js b/tests/tests.js index 6856c96..66e6a06 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -442,6 +442,20 @@ sink('delegation', function (test, ok) { Syn.click(el3); Syn.click(el4); }); + + test('delegate: should be able to remove delegated handler', 1, function() { + var el1 = document.getElementById('foo'); + var el2 = document.getElementById('bar'); + bean.remove(el1); + bean.remove(el2); + var fn = function() { + ok(true, 'degegated event triggered once'); + bean.remove(el1, 'click', fn); + } + bean.add(el1, '.bar', 'click', fn, qwery); + Syn.click(el2); + Syn.click(el2); + }); }) sink('namespaces', function (test, ok) { From 04df8dc0605649042528d71caf3dcafca3cf98cf Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Fri, 25 Nov 2011 14:59:16 +1100 Subject: [PATCH 02/19] move customEvents back down --- src/bean.js | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/bean.js b/src/bean.js index 3a954be..2dd408c 100644 --- a/src/bean.js +++ b/src/bean.js @@ -17,12 +17,6 @@ W3C_MODEL = root[addEvent], eventSupport = W3C_MODEL ? addEvent : attachEvent, - customEvents = { - mouseenter: { base: 'mouseover', condition: check }, - mouseleave: { base: 'mouseout', condition: check }, - mousewheel: { base: /Firefox/.test(navigator.userAgent) ? 'DOMMouseScroll' : 'mousewheel' } - }, - RegEntry = function () { function entry(element, type, handler, original, namespaces) { this.element = element; @@ -332,6 +326,12 @@ return (related != this && related.prefix != 'xul' && !/document/.test(this.toString()) && !isDescendant(this, related)); } + var customEvents = { + mouseenter: { base: 'mouseover', condition: check }, + mouseleave: { base: 'mouseout', condition: check }, + mousewheel: { base: /Firefox/.test(navigator.userAgent) ? 'DOMMouseScroll' : 'mousewheel' } + }; + var bean = { add: add, one: one, remove: remove, clone: clone, fire: fire }; if (win[attachEvent]) { From e3ebc4b47314ef89c7e2a8ad5d3793821bb5d252 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Fri, 25 Nov 2011 18:29:48 +1100 Subject: [PATCH 03/19] broken merge --- src/bean.js | 1 + 1 file changed, 1 insertion(+) diff --git a/src/bean.js b/src/bean.js index 7a0a789..f9ca8a1 100644 --- a/src/bean.js +++ b/src/bean.js @@ -6,6 +6,7 @@ var context = this, old = this.bean, win = window, + overOut = /over|out/, namespace = /[^\.]*(?=\..*)\.|.*/, stripName = /\..*/, own = 'hasOwnProperty', From 8caf6cde388934ea95ff6f761f28c54d023647cb Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Mon, 28 Nov 2011 18:01:54 +1100 Subject: [PATCH 04/19] cleanup and speedup A bunch of minor little code cleanups and also a heap of optimisations to find extra speed; such as replacing for-in loops with proper indexed for loops on arrays. Also changed the way one() is called to make add() much faster by removing the intermediary call and _add(). one() now does a custom `this`. --- src/bean.js | 156 ++++++++++++++++++++++++---------------------------- 1 file changed, 73 insertions(+), 83 deletions(-) diff --git a/src/bean.js b/src/bean.js index f9ca8a1..2739621 100644 --- a/src/bean.js +++ b/src/bean.js @@ -1,6 +1,6 @@ !function (name, definition) { if (typeof module != 'undefined') module.exports = definition(); - else if (typeof define == 'function' && typeof define.amd == 'object') define(definition); + else if (typeof define == 'function' && typeof define.amd == 'object') define(definition); else this[name] = definition(); }('bean', function () { var context = this, @@ -18,6 +18,8 @@ root = doc.documentElement || {}, W3C_MODEL = root[addEvent], eventSupport = W3C_MODEL ? addEvent : attachEvent, + slice = Array.prototype.slice, + ONE = {one:1}, // singleton for quick matching making add() do one() RegEntry = function () { function entry(element, type, handler, original, namespaces) { @@ -26,12 +28,12 @@ this.handler = handler; this.original = original; this.namespaces = namespaces; - this.isNative = !!nativeEvents[type] && !!element[eventSupport]; + this.isNative = nativeEvents[type] && element[eventSupport]; this.custom = customEvents[type]; this.eventType = W3C_MODEL || this.isNative ? type : 'propertychange'; this.customType = !W3C_MODEL && !this.isNative && type; this.targetElement = targetElement(element, this.isNative); - this.eventSupport = !!this.targetElement[eventSupport]; + this.eventSupport = this.targetElement[eventSupport]; } entry.prototype.inNamespaces = function (namespaces) { var i, j; @@ -58,34 +60,40 @@ if (!type) { for (var t in map) map[own](t) && forAll(element, t, original, handler, fn); } else { - var i, list = map[type]; - for (i in list) { - if (list[own](i) && (element === '*' || list[i].matches(element, original, handler))) + var i = 0, l, list = map[type], all = element === '*'; + if (!list) return + for (l = list.length; i < l; i++) { + if (all || list[i].matches(element, original, handler)) if (!fn(list[i], list, i, type)) return; } } }, + has = function (element, type, original) { + var b = false; + forAll(element, type, original, null, function(entry) { return !(b = true); }); + return b; + }, get = function (element, type, original) { - var handlers = []; - forAll(element, type, original, null, function(handler) { return handlers.push(handler); }); - return handlers; + var entries = []; + forAll(element, type, original, null, function(entry) { return entries.push(entry); }); + return entries; }, put = function (entry) { (map[entry.type] || (map[entry.type] = [])).push(entry); return entry; }, del = function (entry) { - forAll(entry.element, entry.type, null, entry.handler, function(handler, list, i) { + forAll(entry.element, entry.type, null, entry.handler, function(entry, list, i) { list.splice(i, 1); return false; }); }, entries = function () { var t, entries = []; - for (t in map) map[own](t) && (entries = entries.concat(map[t])); + for (t in map) map(t) && (entries = entries.concat(map[t])); return entries; } - return { forAll: forAll, get: get, put: put, del: del, entries: entries }; + return { forAll: forAll, has: has, get: get, put: put, del: del, entries: entries }; }(), isDescendant = function (parent, node) { @@ -111,9 +119,9 @@ customHandler = function (element, fn, type, condition, args) { return function (event) { - if (condition ? condition.apply(this, arguments) : W3C_MODEL ? true : event && event.propertyName == '_on' + type || !event) { + if (condition ? condition.apply(this, arguments) : W3C_MODEL ? true : event && event.propertyName === '_on' + type || !event) { event = event ? fixEvent(event || ((this.ownerDocument || this.document || this).parentWindow || win).event) : null; - fn.apply(element, Array.prototype.slice.call(arguments, event ? 0 : 1).concat(args)); + fn.apply(element, event && (!args || args.length === 0) ? arguments : slice.call(arguments, event ? 0 : 1).concat(args)); } }; }, @@ -126,13 +134,13 @@ var entry, type = orgType.replace(stripName, ''), namespaces = orgType.replace(namespace, '').split('.'); - if (registry.get(element, type, fn).length) return element; + if (registry.has(element, type, fn)) { return element; } if (type === 'unload') fn = once(removeListener, element, type, fn, originalFn); if (customEvents[type]) { fn = customEvents[type].condition ? customHandler(element, fn, type, customEvents[type].condition) : fn; type = customEvents[type].base || type; } - entry = registry.put(new RegEntry(element, type, fn, originalFn, !!namespaces[0] && namespaces)); + entry = registry.put(new RegEntry(element, type, fn, originalFn, namespaces[0] && namespaces)); entry.handler = entry.isNative ? nativeHandler(element, entry.handler, args) : customHandler(element, entry.handler, type, false, args); @@ -142,15 +150,13 @@ }, removeListener = function (element, orgType, handler, names) { - var i, entry, type = (orgType && orgType.replace(stripName, '')), + var i, l, entry, type = (orgType && orgType.replace(stripName, '')), handlers = registry.get(element, type, handler); - if (!handlers.length) return element; - - for (i in handlers) { - if (handlers[own](i) && handlers[i].inNamespaces(names)) { - entry = handlers[i]; - entry.eventSupport && listener(entry.targetElement, entry.eventType, entry.handler, false, entry.type); + for (i = 0, l = handlers.length; i < l; i++) { + if (handlers[i].inNamespaces(names)) { + if ((entry = handlers[i]).eventSupport) + listener(entry.targetElement, entry.eventType, entry.handler, false, entry.type); registry.del(entry) } } @@ -159,10 +165,10 @@ del = function (selector, fn, $) { return function (e) { - var target, i, array = typeof selector == 'string' ? $(selector, this) : selector; - for (target = e.target; target && target != this; target = target.parentNode) { + var target, i, array = typeof selector === 'string' ? $(selector, this) : selector; + for (target = e.target; target && target !== this; target = target.parentNode) { for (i = array.length; i--;) { - if (array[i] == target) { + if (array[i] === target) { return fn.apply(target, arguments); } } @@ -170,21 +176,17 @@ }; }, - _add = function (meth, element, events, fn, delfn, $) { - var type, types, i, isDel = typeof fn === 'string', originalFn = fn, - args = Array.prototype.slice.call(arguments, 4); - - if (typeof events == 'object' && !fn) { + add = function (element, events, fn, delfn, $) { + var type, types, i, args, originalFn = fn, isDel = fn && typeof fn === 'string'; + if (events && !fn && typeof events === 'object') { for (type in events) { - events[own](type) && _add(meth, element, type, events[type]); + events[own](type) && add.apply(this, [ element, type, events[type] ]); } } else { + args = arguments.length > 3 ? slice.call(arguments, 3) : []; types = (isDel ? fn : events).split(' '); - if (isDel) { - fn = del(events, (originalFn = delfn), $); - args = args.slice(1); - } - if (meth === 'one') fn = once(remove, element, events, fn, originalFn); + isDel && (fn = del(events, (originalFn = delfn), $)) && (args = slice.call(args, 1)); + this === ONE && (fn = once(remove, element, events, fn, originalFn)); for (i = types.length; i--;) addListener(element, types[i], fn, originalFn, args); } return element; @@ -194,26 +196,20 @@ return function () { rm(element, type, originalFn) && fn.apply(this, arguments); }; }, - add = function () { - return _add.apply(this, ['add'].concat(Array.prototype.slice.call(arguments, 0))) - }, - one = function () { - return _add.apply(this, ['one'].concat(Array.prototype.slice.call(arguments, 0))) + return add.apply(ONE, arguments) }, remove = function (element, orgEvents, fn) { - var k, m, type, events, i, names, - isString = typeof(orgEvents) == 'string', - rm = removeListener; - - if (isString && /\s/.test(orgEvents)) { + var k, m, type, events, i, names, rm = removeListener, + isString = orgEvents && typeof orgEvents === 'string'; + if (isString && orgEvents.indexOf(' ') > 0) { orgEvents = orgEvents.split(' '); - for (i = orgEvents.length; remove(element, orgEvents[i], fn) && i--;) {} + for (i = orgEvents.length; i-- && remove(element, orgEvents[i], fn);) {} return element; } events = isString ? orgEvents.replace(stripName, '') : orgEvents; - if (typeof fn == 'function') { + if (fn && typeof fn === 'function') { rm(element, events, fn); } else { if (names = isString && orgEvents.replace(namespace, '')) names = names.split('.'); @@ -223,7 +219,7 @@ }, fire = function (element, type, args) { - var evt, k, i, names, handlers, types = type.split(' '); + var i, j, l, names, handlers, types = type.split(' '); for (i = types.length; i--;) { type = types[i].replace(stripName, ''); if (names = types[i].replace(namespace, '')) names = names.split('.'); @@ -231,9 +227,10 @@ fireListener(nativeEvents[type], type, element); } else { handlers = registry.get(element, type); - for (k in handlers) { - if (handlers[own](k) && handlers[k].inNamespaces(names)) - handlers[k].handler.apply(element, [false].concat(args)); + args = [false].concat(args); + for (j = 0, l = handlers.length; j < l; j++) { + if (handlers[j].inNamespaces && handlers[j].inNamespaces(names)) + handlers[j].handler.apply(element, args); } } } @@ -241,7 +238,7 @@ }, fireListener = W3C_MODEL ? function (isNative, type, element) { - evt = doc.createEvent(isNative ? "HTMLEvents" : "UIEvents"); + var evt = doc.createEvent(isNative ? 'HTMLEvents' : 'UIEvents'); evt[isNative ? 'initEvent' : 'initUIEvent'](type, true, true, win, 1); element.dispatchEvent(evt); } : function (isNative, type, element) { @@ -250,26 +247,25 @@ }, clone = function (element, from, type) { - var i, handlers = registry.get(from, type) - for (i in handlers) { - handlers[own](i) && add(element, handlers[i].type, handlers[i].original) - } + var i = 0, handlers = registry.get(from, type), l = handlers.length; + for (;i < l; i++) + handlers[i].original && add(element, handlers[i].type, handlers[i].original) return element; }, + copyProps = 'altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which'.split(' '), // thanks to jQuery for this basis of this list + fixEvent = function (e) { var result = {}; - if (!e) { - return result; - } - var type = e.type, target = e.target || e.srcElement; + if (!e) return result; + var i, p, type = e.type, target = e.target || e.srcElement; result.preventDefault = fixEvent.preventDefault(e); result.stopPropagation = fixEvent.stopPropagation(e); - result.target = target && target.nodeType == 3 ? target.parentNode : target; + result.target = target && target.nodeType === 3 ? target.parentNode : target; if (~type.indexOf('key')) { result.keyCode = e.which || e.keyCode; } else if ((/click|mouse|menu/i).test(type)) { - result.rightClick = e.which == 3 || e.button == 2; + result.rightClick = e.which === 3 || e.button === 2; result.pos = { x: 0, y: 0 }; if (e.pageX || e.pageY) { result.clientX = e.pageX; @@ -278,36 +274,32 @@ result.clientX = e.clientX + doc.body.scrollLeft + root.scrollLeft; result.clientY = e.clientY + doc.body.scrollTop + root.scrollTop; } - overOut.test(type) && (result.relatedTarget = e.relatedTarget || e[(type == 'mouseover' ? 'from' : 'to') + 'Element']); + overOut.test(type) && (result.relatedTarget = e.relatedTarget || e[(type === 'mouseover' ? 'from' : 'to') + 'Element']); } - for (var k in e) { - if (!(k in result)) { - result[k] = e[k]; - } + for (i = copyProps.length; i--;) { + p = copyProps[i]; + if (!(p in result) && p in e) result[p] = e[p]; } return result; }; fixEvent.preventDefault = function (e) { return function () { - if (e.preventDefault) { + if (e.preventDefault) e.preventDefault(); - } - else { + else e.returnValue = false; - } }; - }; + } fixEvent.stopPropagation = function (e) { return function () { - if (e.stopPropagation) { + if (e.stopPropagation) e.stopPropagation(); - } else { + else e.cancelBubble = true; - } }; - }; + } var nativeEvents = { click: 1, dblclick: 1, mouseup: 1, mousedown: 1, contextmenu: 1, //mouse buttons mousewheel: 1, DOMMouseScroll: 1, //mouse wheel @@ -322,10 +314,8 @@ function check(event) { var related = event.relatedTarget; - if (!related) { - return related === null; - } - return (related != this && related.prefix != 'xul' && !/document/.test(this.toString()) && !isDescendant(this, related)); + if (!related) return related === null; + return (related !== this && related.prefix !== 'xul' && !/document/.test(this.toString()) && !isDescendant(this, related)); } var customEvents = { @@ -340,7 +330,7 @@ var cleanup = function () { var i, entries = registry.entries(); for (i in entries) { - if (entries[own](i) && entries[i].type !== 'unload') + if (entries[i].type && entries[i].type !== 'unload') remove(entries[i].element, entries[i].type); } win[detachEvent]('onunload', cleanup); From b644594a2ba869521d7964e3a138efb608524be2 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Mon, 28 Nov 2011 21:09:16 +1100 Subject: [PATCH 05/19] partial roll-back of #27 Custom events get the full fixEvent() treatment since #27 but it turns out that there's a *lot* of work in there for some browsers. So, now we just do a basic augmentation of non-native custom events rather than the full deal. --- src/bean.js | 56 ++++++++++++++++++++++++++++------------------------- 1 file changed, 30 insertions(+), 26 deletions(-) diff --git a/src/bean.js b/src/bean.js index 2739621..8c5e835 100644 --- a/src/bean.js +++ b/src/bean.js @@ -112,15 +112,16 @@ nativeHandler = function (element, fn, args) { return function (event) { - event = fixEvent(event || ((this.ownerDocument || this.document || this).parentWindow || win).event); + event = fixEvent(event || ((this.ownerDocument || this.document || this).parentWindow || win).event, true); return fn.apply(element, [event].concat(args)); }; }, - customHandler = function (element, fn, type, condition, args) { + customHandler = function (element, fn, type, condition, args, isNative) { return function (event) { if (condition ? condition.apply(this, arguments) : W3C_MODEL ? true : event && event.propertyName === '_on' + type || !event) { - event = event ? fixEvent(event || ((this.ownerDocument || this.document || this).parentWindow || win).event) : null; + if (event) + event = fixEvent(event || ((this.ownerDocument || this.document || this).parentWindow || win).event, isNative); fn.apply(element, event && (!args || args.length === 0) ? arguments : slice.call(arguments, event ? 0 : 1).concat(args)); } }; @@ -137,13 +138,13 @@ if (registry.has(element, type, fn)) { return element; } if (type === 'unload') fn = once(removeListener, element, type, fn, originalFn); if (customEvents[type]) { - fn = customEvents[type].condition ? customHandler(element, fn, type, customEvents[type].condition) : fn; + fn = customEvents[type].condition ? customHandler(element, fn, type, customEvents[type].condition, true) : fn; type = customEvents[type].base || type; } entry = registry.put(new RegEntry(element, type, fn, originalFn, namespaces[0] && namespaces)); entry.handler = entry.isNative ? nativeHandler(element, entry.handler, args) : - customHandler(element, entry.handler, type, false, args); + customHandler(element, entry.handler, type, false, args, false); if (entry.eventSupport) listener(entry.targetElement, entry.eventType, entry.handler, true, entry.customType); return element @@ -255,30 +256,33 @@ copyProps = 'altKey attrChange attrName bubbles button cancelable charCode clientX clientY ctrlKey currentTarget data detail eventPhase fromElement handler keyCode metaKey newValue offsetX offsetY pageX pageY prevValue relatedNode relatedTarget screenX screenY shiftKey srcElement target toElement view wheelDelta which'.split(' '), // thanks to jQuery for this basis of this list - fixEvent = function (e) { + fixEvent = function (event, isNative) { var result = {}; - if (!e) return result; - var i, p, type = e.type, target = e.target || e.srcElement; - result.preventDefault = fixEvent.preventDefault(e); - result.stopPropagation = fixEvent.stopPropagation(e); + if (!event) return result; + var i, p, type = event.type, target = event.target || event.srcElement; + result.preventDefault = fixEvent.preventDefault(event); + result.stopPropagation = fixEvent.stopPropagation(event); result.target = target && target.nodeType === 3 ? target.parentNode : target; - if (~type.indexOf('key')) { - result.keyCode = e.which || e.keyCode; - } else if ((/click|mouse|menu/i).test(type)) { - result.rightClick = e.which === 3 || e.button === 2; - result.pos = { x: 0, y: 0 }; - if (e.pageX || e.pageY) { - result.clientX = e.pageX; - result.clientY = e.pageY; - } else if (e.clientX || e.clientY) { - result.clientX = e.clientX + doc.body.scrollLeft + root.scrollLeft; - result.clientY = e.clientY + doc.body.scrollTop + root.scrollTop; + if (isNative) { // we only need basic augmentation on custom events + if (~type.indexOf('key')) { + result.keyCode = event.which || event.keyCode; + } else if ((/click|mouse|menu/i).test(type)) { + result.rightClick = event.which === 3 || event.button === 2; + result.pos = { x: 0, y: 0 }; + if (event.pageX || event.pageY) { + result.clientX = event.pageX; + result.clientY = event.pageY; + } else if (event.clientX || event.clientY) { + result.clientX = event.clientX + doc.body.scrollLeft + root.scrollLeft; + result.clientY = event.clientY + doc.body.scrollTop + root.scrollTop; + } + if (overOut.test(type)) + result.relatedTarget = event.relatedTarget || event[(type === 'mouseover' ? 'from' : 'to') + 'Element']; + } + for (i = copyProps.length; i--;) { + p = copyProps[i]; + if (!(p in result) && p in event) result[p] = event[p]; } - overOut.test(type) && (result.relatedTarget = e.relatedTarget || e[(type === 'mouseover' ? 'from' : 'to') + 'Element']); - } - for (i = copyProps.length; i--;) { - p = copyProps[i]; - if (!(p in result) && p in e) result[p] = e[p]; } return result; }; From 9318e5c6e928c687f8ff93093f5a44235dfd3021 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Mon, 28 Nov 2011 21:19:02 +1100 Subject: [PATCH 06/19] added benchmarks.html to test changes Added nwevents as a submodule as a reference, along with jQuery for benchmarking (even though they are not directly comparable to Bean). Added fixes for noConflict() to bean.js so we can compare it as original to src/bean.js as the new code. --- .gitmodules | 3 + bean.js | 6 +- support/nwevents | 1 + support/qwery | 2 +- tests/benchmarks.html | 208 ++++++++++++++++++++++++++++++++++++++++++ tests/index.html | 4 +- 6 files changed, 219 insertions(+), 5 deletions(-) create mode 160000 support/nwevents create mode 100644 tests/benchmarks.html diff --git a/.gitmodules b/.gitmodules index 93b0dce..625ef37 100644 --- a/.gitmodules +++ b/.gitmodules @@ -10,3 +10,6 @@ [submodule "support/qwery"] path = support/qwery url = git@github.com:ded/qwery.git +[submodule "support/nwevents"] + path = support/nwevents + url = https://github.com/dperini/nwevents.git diff --git a/bean.js b/bean.js index 318fefe..ff835a1 100644 --- a/bean.js +++ b/bean.js @@ -12,7 +12,9 @@ else if (typeof define == 'function' && typeof define.amd == 'object') define(definition); else this[name] = definition(); }('bean', function () { - var win = window, + var context = this, + old = this.bean, + win = window, __uid = 1, registry = {}, collected = {}, @@ -346,4 +348,4 @@ }; return bean; -}); \ No newline at end of file +}); diff --git a/support/nwevents b/support/nwevents new file mode 160000 index 0000000..558e899 --- /dev/null +++ b/support/nwevents @@ -0,0 +1 @@ +Subproject commit 558e8993f30d4f6f82c11ac11ceefa164ef0b70b diff --git a/support/qwery b/support/qwery index 33fe83a..b394aa8 160000 --- a/support/qwery +++ b/support/qwery @@ -1 +1 @@ -Subproject commit 33fe83a73dcd7a084ada3abc97470961fef21ddf +Subproject commit b394aa84e8edd44ab4d1f6d830e0ab92621b7217 diff --git a/tests/benchmarks.html b/tests/benchmarks.html new file mode 100644 index 0000000..087e3b0 --- /dev/null +++ b/tests/benchmarks.html @@ -0,0 +1,208 @@ + + + + + Bean Benchmarks + + + + + + + + + + + + + + + +

Bean Benchmarks

+

CAUTION: these libraries all handle events differently so comparing speed is a fraught exercise. The benchmarks here are intended to help improve Bean's speed with the other libaries included as a rough guide, knowing that the other libraries have various shortcuts that can make it easier. For example, NW doesn't have a flexibility comparable to Bean which allows it to optimise certain paths. Likewise, jQuery is happy to append its own unique IDs to both DOM elements and any object you want to do a pub/sub on; which Bean aims to avoid.

+
+
    +
    + + +
    +
    +
    +
    +
    +
    +
    + + + + + diff --git a/tests/index.html b/tests/index.html index 8a4667a..7182ebe 100644 --- a/tests/index.html +++ b/tests/index.html @@ -2,7 +2,7 @@ - event.js tests + Bean tests @@ -25,7 +25,7 @@ -

    Event Tests

    +

    Bean Tests

      From 195dd32f4f999aeb7cf4f273f6e6290004178cdd Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Sat, 3 Dec 2011 16:08:17 +1100 Subject: [PATCH 07/19] registry.has() optimisations --- src/bean.js | 16 ++++++++++------ 1 file changed, 10 insertions(+), 6 deletions(-) diff --git a/src/bean.js b/src/bean.js index 8c5e835..d3bf847 100644 --- a/src/bean.js +++ b/src/bean.js @@ -60,18 +60,22 @@ if (!type) { for (var t in map) map[own](t) && forAll(element, t, original, handler, fn); } else { - var i = 0, l, list = map[type], all = element === '*'; - if (!list) return - for (l = list.length; i < l; i++) { + var i = 0, list = map[type], all = element === '*'; + if (!list) return; + for (i = list.length; i--;) { if (all || list[i].matches(element, original, handler)) if (!fn(list[i], list, i, type)) return; } } }, has = function (element, type, original) { - var b = false; - forAll(element, type, original, null, function(entry) { return !(b = true); }); - return b; + var i, list = map[type] + if (list) { + for (i = list.length; i--;) { + if (list[i].matches(element, original, null)) return true; + } + } + return false; }, get = function (element, type, original) { var entries = []; From 4027c4689322364f4b6815477391ac63bedcb1b5 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Sat, 3 Dec 2011 16:08:50 +1100 Subject: [PATCH 08/19] multi-library benchmarks based on @fat's benchmark.js work new bean, old bean, NW & jQuery interesting, but hard to run on older browsers and slightly sus --- tests/benchmark.html | 513 +++++++++++++++++++++++++++++++----------- tests/benchmark.js | 2 +- tests/benchmarks.html | 6 +- 3 files changed, 391 insertions(+), 130 deletions(-) diff --git a/tests/benchmark.html b/tests/benchmark.html index 8b1602f..5e2e82e 100644 --- a/tests/benchmark.html +++ b/tests/benchmark.html @@ -3,148 +3,368 @@ Bean Benchmarks + + + + + + + + + + +

      Running Benchmark Suite

      + + + + + + + + + + +
      BeanNew BeanNWEventsjQuery
      - \ No newline at end of file + diff --git a/tests/benchmark.js b/tests/benchmark.js index f87653b..9fb89f0 100644 --- a/tests/benchmark.js +++ b/tests/benchmark.js @@ -2811,4 +2811,4 @@ if (has.air) { clock({ 'fn': noop, 'count': 1, 'options': {} }); } -}(this)); \ No newline at end of file +}(this)); diff --git a/tests/benchmarks.html b/tests/benchmarks.html index 087e3b0..77ee035 100644 --- a/tests/benchmarks.html +++ b/tests/benchmarks.html @@ -126,7 +126,7 @@

      Bean Benchmarks

      remove(el, type, fna[j]) if (+new Date - start >= maxms) break } - return Math.round((1000 / (+new Date - start)) * iters) + return Math.round(1000 / ((+new Date - start) / iters)) } function manyElementsBench(maxms, handlers, elements, type, add, remove, fire) { @@ -140,10 +140,10 @@

      Bean Benchmarks

      remove(ela[j % elements], type, fna[j]) if (+new Date - start >= maxms) break } - return Math.round((1000 / (+new Date - start)) * iters) + return Math.round(1000 / ((+new Date - start) / iters)) } -var maxms = 300, listeners = 25, elements = 10, fires = 10 +var maxms = 500, listeners = 25, elements = 10, fires = 10 var bench = [ function(ind) { result(ind, 'Element click add/click/remove', [ From f8b031256df8cdda7b27ed0874d2226fa1cf717e Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Sat, 3 Dec 2011 16:24:17 +1100 Subject: [PATCH 09/19] \t --- src/bean.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/bean.js b/src/bean.js index d3bf847..c8cceb1 100644 --- a/src/bean.js +++ b/src/bean.js @@ -62,7 +62,7 @@ } else { var i = 0, list = map[type], all = element === '*'; if (!list) return; - for (i = list.length; i--;) { + for (i = list.length; i--;) { if (all || list[i].matches(element, original, handler)) if (!fn(list[i], list, i, type)) return; } From de39ff969d6c537af6d7f26a0c072f9bd350852f Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Sun, 4 Dec 2011 19:52:55 +1100 Subject: [PATCH 10/19] more benchmark work + nano-timer applet --- src/bean.js | 15 ++- tests/benchmark.html | 302 +++++++++++++++++++++++++++---------------- tests/nano.jar | Bin 0 -> 293 bytes 3 files changed, 200 insertions(+), 117 deletions(-) create mode 100644 tests/nano.jar diff --git a/src/bean.js b/src/bean.js index c8cceb1..66304f5 100644 --- a/src/bean.js +++ b/src/bean.js @@ -57,7 +57,7 @@ registry = function () { var map = {}, forAll = function (element, type, original, handler, fn) { - if (!type) { + if (!type || type === '*') { for (var t in map) map[own](t) && forAll(element, t, original, handler, fn); } else { var i = 0, list = map[type], all = element === '*'; @@ -96,8 +96,14 @@ var t, entries = []; for (t in map) map(t) && (entries = entries.concat(map[t])); return entries; + }, + // probably temporary, not for production release + size = function() { + var c = 0 + forAll('*', '*', null, null, function() { c++ }) + return c; } - return { forAll: forAll, has: has, get: get, put: put, del: del, entries: entries }; + return { forAll: forAll, has: has, get: get, put: put, del: del, entries: entries, size: size }; }(), isDescendant = function (parent, node) { @@ -151,7 +157,6 @@ customHandler(element, entry.handler, type, false, args, false); if (entry.eventSupport) listener(entry.targetElement, entry.eventType, entry.handler, true, entry.customType); - return element }, removeListener = function (element, orgType, handler, names) { @@ -165,7 +170,6 @@ registry.del(entry) } } - return element; }, del = function (selector, fn, $) { @@ -352,6 +356,9 @@ return this; }; + // temporary for debugging, not for production release + bean.registry = registry + return bean; }); diff --git a/tests/benchmark.html b/tests/benchmark.html index 5e2e82e..b4a5706 100644 --- a/tests/benchmark.html +++ b/tests/benchmark.html @@ -3,11 +3,6 @@ Bean Benchmarks - - - - - - + @@ -15,9 +38,10 @@ var libs = [ 'bean', 'newbean', 'nw', 'jquery' ] , suite = new Benchmark.Suite + , i, j, elements, element, listeners, fn = function() {} function add(name, setup, exec, teardown) { - for (var i = 0; i < libs.length; i++) { + for (i = 0; i < libs.length; i++) { exec[libs[i]] && suite.add(name, exec[libs[i]], { 'libid': libs[i] , 'setup': typeof setup === 'function' ? setup : setup[libs[i]] @@ -26,34 +50,32 @@ } } - var setupElement = function() { var element = document.createElement('div') } + var setupElement = function() { element = document.createElement('div') } , teardownElement = { - 'bean': function() { bean.remove(element); element = null } - , 'newbean': function() { newbean.remove(element); element = null } + 'bean': function() { bean.remove(element); } + , 'newbean': function() { newbean.remove(element); } , 'nw': function() { - var j, listeners = NW.Event.getRegistered(NW.Event.Listeners, element, 'click', '*') + listeners = NW.Event.getRegistered(NW.Event.Listeners, element, 'click', '*') for (j = 0; j < listeners.length; j++) NW.Event.unlisten(element, 'click', listeners[j]) - element = null } - , 'jquery': function() { jQuery(element).off('click'); element = null } + , 'jquery': function() { jQuery(element).off('click') } } , setupUniqueElements = function() { - var i = this.count, elements = [] + i = this.count + elements = [] while (i--) elements.push(document.createElement('div')) i = 0 } , teardownUniqueElements = { - 'bean': function() { while (i--) bean.remove(elements[i]); elements = null } - , 'newbean': function() { while (i--) newbean.remove(elements[i]); elements = null } + 'bean': function() { while (i--) bean.remove(elements[i]) } + , 'newbean': function() { while (i--) newbean.remove(elements[i]) } , 'nw': function() { - var j, listeners while (i--) { - j, listeners = NW.Event.getRegistered(NW.Event.Listeners, elements[i], 'click', '*') + listeners = NW.Event.getRegistered(NW.Event.Listeners, elements[i], 'click', '*') for (j = 0; j < listeners.length; j++) NW.Event.unlisten(elements[i], 'click', listeners[j]) } - elements = null } - , 'jquery': function() { while (i--) jQuery(elements[i]).off('click'); elements = null } + , 'jquery': function() { while (i--) jQuery(elements[i]).off('click') } } add( 'add( element, event, fn )', setupElement @@ -77,21 +99,18 @@ var teardownElementCustom = { 'bean': teardownElement.bean, 'newbean': teardownElement.newbean, 'jquery': teardownElement.jquery , 'nw': function () { - var j, listeners = NW.Event.getRegistered(NW.Event.Listeners, element, 'fat', '*') + listeners = NW.Event.getRegistered(NW.Event.Listeners, element, 'fat', '*') for (j = 0; j < listeners.length; j++) NW.Event.unlisten(element, 'fat', listeners[j]) - element = null } } , teardownUniqueElementsCustom = { 'bean': teardownUniqueElements.bean , 'newbean': teardownUniqueElements.newbean , 'nw': function() { - var j, listeners while (i--) { - j, listeners = NW.Event.getRegistered(NW.Event.Listeners, elements[i], 'fat', '*') + listeners = NW.Event.getRegistered(NW.Event.Listeners, elements[i], 'fat', '*') for (j = 0; j < listeners.length; j++) NW.Event.unlisten(elements[i], 'fat', listeners[j]) } - elements = null } , 'jquery': teardownUniqueElements.jquery } @@ -133,21 +152,18 @@ var teardownElementDelegate = { 'bean': teardownElement.bean, 'newbean': teardownElement.newbean, 'jquery': teardownElement.jquery , 'nw': function () { - var j, listeners = NW.Event.getRegistered(NW.Event.Listeners, element, 'click', '*') + listeners = NW.Event.getRegistered(NW.Event.Listeners, element, 'click', '*') for (j = 0; j < listeners.length; j++) NW.Event.undelegate('a.close', 'click', listeners[j], element) - element = null } } , teardownUniqueElementsDelegate = { 'bean': teardownUniqueElements.bean , 'newbean': teardownUniqueElements.newbean , 'nw': function() { - var j, listeners while (i--) { - j, listeners = NW.Event.getRegistered(NW.Event.Listeners, elements[i], 'click', '*') + listeners = NW.Event.getRegistered(NW.Event.Listeners, elements[i], 'click', '*') for (j = 0; j < listeners.length; j++) NW.Event.undelegate(elements[i], 'click', listeners[j]) } - elements = null } , 'jquery': teardownUniqueElements.jquery } @@ -188,7 +204,8 @@ /* var setupElementRemove = { 'bean': function () { - var i = this.count, element, elements = [], j, fn = function() {} + i = this.count + elements = [] while (i--) { elements.push(element = document.createElement('div')) for (j = 10; j--;) bean.add(element, 'click', function () {}) @@ -197,7 +214,8 @@ i = 0 } , 'newbean': function () { - var i = this.count, element, elements = [], j, fn = function() {} + i = this.count + elements = [] while (i--) { elements.push(element = document.createElement('div')) for (j = 10; j--;) newbean.add(element, 'click', function () {}) @@ -206,7 +224,8 @@ i = 0 } , 'nw': function () { - var i = this.count, element, elements = [], j, fn = function() {} + i = this.count + elements = [] while (i--) { elements.push(element = document.createElement('div')) for (j = 10; j--;) NW.Event.listen(element, 'click', function () {}) @@ -215,7 +234,8 @@ i = 0 } , 'jquery': function () { - var i = this.count, element, elements = [], j, fn = function() {} + i = this.count + elements = [] while (i--) { elements.push(element = document.createElement('div')) for (j = 10; j--;) jQuery(element).on('click', function () {}) @@ -226,7 +246,8 @@ } , setupElementRemoveCustom = { 'bean': function () { - var i = this.count, element, elements = [], j, fn = function() {} + i = this.count + elements = [] while (i--) { elements.push(element = document.createElement('div')) for (j = 10; j--;) bean.add(element, 'fat', function () {}) @@ -235,7 +256,8 @@ i = 0 } , 'newbean': function () { - var i = this.count, element, elements = [], j, fn = function() {} + i = this.count + elements = [] while (i--) { elements.push(element = document.createElement('div')) for (j = 10; j--;) newbean.add(element, 'fat', function () {}) @@ -244,7 +266,8 @@ i = 0 } , 'nw': function () { - var i = this.count, element, elements = [], j, fn = function() {} + i = this.count + elements = [] while (i--) { elements.push(element = document.createElement('div')) for (j = 10; j--;) NW.Event.listen(element, 'fat', function () {}) @@ -253,7 +276,8 @@ i = 0 } , 'jquery': function () { - var i = this.count, element, elements = [], j, fn = function() {} + i = this.count + elements = [] while (i--) { elements.push(element = document.createElement('div')) for (j = 10; j--;) jQuery(element).on('fat', function () {}) @@ -264,7 +288,8 @@ } , setupElementRemoveNamespace = { 'bean': function () { - var i = this.count, element, elements = [], j, fn = function() {} + i = this.count + elements = [] while (i--) { elements.push(element = document.createElement('div')) for (j = 10; j--;) bean.add(element, 'click.fat', function () {}) @@ -273,7 +298,8 @@ i = 0 } , 'newbean': function () { - var i = this.count, element, elements = [], j, fn = function() {} + i = this.count + elements = [] while (i--) { elements.push(element = document.createElement('div')) for (j = 10; j--;) newbean.add(element, 'click.fat', function () {}) @@ -282,7 +308,8 @@ i = 0 } , 'jquery': function () { - var i = this.count, element, elements = [], j, fn = function() {} + i = this.count + elements = [] while (i--) { elements.push(element = document.createElement('div')) for (j = 10; j--;) jQuery(element).on('click.fat', function () {}) @@ -291,7 +318,7 @@ i = 0 } } - , teardownElementRemove = function () { elements = null; element = null; fn = null } + , teardownElementRemove = function () { } add( 'remove()', setupElementRemove , { @@ -306,7 +333,7 @@ 'bean': function () { bean.remove(elements[i++], 'click') } , 'newbean': function () { newbean.remove(elements[i++], 'click') } , 'nw': function () { - var j, listeners = NW.Event.getRegistered(NW.Event.Listeners, element[i], 'click', '*') + listeners = NW.Event.getRegistered(NW.Event.Listeners, element[i], 'click', '*') for (j = 0; j < listeners.length; j++) NW.Event.unlisten(elements[i], 'click', listeners[j]) i++ } @@ -319,7 +346,7 @@ 'bean': function () { bean.remove(elements[i++], 'fat') } , 'newbean': function () { newbean.remove(elements[i++], 'fat') } , 'nw': function () { - var j, listeners = NW.Event.getRegistered(NW.Event.Listeners, elements[i], 'fat', '*') + listeners = NW.Event.getRegistered(NW.Event.Listeners, elements[i], 'fat', '*') for (j = 0; j < listeners.length; j++) NW.Event.unlisten(elements[i], 'fat', listeners[j]) i++ } @@ -347,17 +374,16 @@ } // teardown , { - 'bean': function () { i = this.count; while (i--) bean.remove(elements[i]); elements = null; element = null } - , 'newbean': function () { i = this.count; while (i--) newbean.remove(elements[i]); elements = null; element = null } + 'bean': function () { i = this.count; while (i--) bean.remove(elements[i]) } + , 'newbean': function () { i = this.count; while (i--) newbean.remove(elements[i]) } , 'nw': function () { i = this.count while(i--) { - var j, listeners = NW.Event.getRegistered(NW.Event.Listeners, elements[i], 'click', '*') + listeners = NW.Event.getRegistered(NW.Event.Listeners, elements[i], 'click', '*') for (j = 0; j < listeners.length; j++) NW.Event.unlisten(elements[i], 'click', listeners[j]) } - elements = null; element = null } - , 'jquery': function () { i = this.count; while (i--) jQuery(elements[i]).off('click'); elements = null; element = null } + , 'jquery': function () { i = this.count; while (i--) jQuery(elements[i]).off('click') } } ) @@ -373,14 +399,13 @@ } // teardown , { - 'bean': function () { bean.remove(element); element = null } - , 'newbean': function () { newbean.remove(element); element = null } + 'bean': function () { bean.remove(element) } + , 'newbean': function () { newbean.remove(element) } , 'nw': function () { - var j, listeners = NW.Event.getRegistered(NW.Event.Listeners, element, 'click', '*') + listeners = NW.Event.getRegistered(NW.Event.Listeners, element, 'click', '*') for (j = 0; j < listeners.length; j++) NW.Event.unlisten(element, 'click', listeners[j]) - element = null } - , 'jquery': function () { jQuery(element).off('click'); element = null } + , 'jquery': function () { jQuery(element).off('click') } } ) @@ -396,14 +421,13 @@ } // teardown , { - 'bean': function () { bean.remove(element); element = null } - , 'newbean': function () { newbean.remove(element); element = null } + 'bean': function () { bean.remove(element) } + , 'newbean': function () { newbean.remove(element) } , 'nw': function () { - var j, listeners = NW.Event.getRegistered(NW.Event.Listeners, element, 'fat', '*') + listeners = NW.Event.getRegistered(NW.Event.Listeners, element, 'fat', '*') for (j = 0; j < listeners.length; j++) NW.Event.unlisten(element, 'fat', listeners[j]) - element = null } - , 'jquery': function () { jQuery(element).off('fat'); element = null } + , 'jquery': function () { jQuery(element).off('fat') } } ) */ @@ -413,15 +437,18 @@ , { // bean can't do this, it needs a a 'type' as well as a namespace // 'bean': function () { - // var i = 20, element = document.createElement('div') + // i = 20 + // element = document.createElement('div') // while (i--) bean.add(element, 'whatup.fat', function () {}) // } 'newbean': function () { - var i = 20, element = document.createElement('div') + i = 20 + element = document.createElement('div') while (i--) newbean.add(element, 'whatup.fat', function () {}) } //, 'jquery': function () { - // var i = 20, element = document.createElement('div') + // i = 20 + // element = document.createElement('div') // while (i--) jQuery(element).on('whatup.fat', function () {}) // } } @@ -433,9 +460,9 @@ } // teardown , { - // 'bean': function () { bean.remove(element); element = null } - 'newbean': function () { newbean.remove(element); element = null } - //, 'jquery': function () { jQuery(element).off('.fat'); element = null } + // 'bean': function () { bean.remove(element) } + 'newbean': function () { newbean.remove(element) } + //, 'jquery': function () { jQuery(element).off('.fat') } } ) @@ -447,7 +474,6 @@ document.body.appendChild(item) } ) - setTimeout(function() { suite.run({ 'async': true }) }, 100); function cycle(e, benchmark) { if (benchmark.error) throw benchmark.error @@ -473,28 +499,15 @@ cell.innerHTML = Benchmark.formatNumber(benchmark.hz.toFixed(0)) + ' ops/sec \xb1' + benchmark.stats.rme.toFixed(2) + '% (' + benchmark.stats.size + ' samp)' } + + if (!/[?&]nojava=true(?:&|$)/.test(location.search)) { + document.write('') + } + + window.onload = function() { + suite.run({ 'async': true }) + } + - - - -

      Running Benchmark Suite

      - - - - - - - - - - -
      BeanNew BeanNWEventsjQuery
      - Java not installed, won't use nano-timer diff --git a/tests/benchmark.js b/tests/benchmark.js index 9fb89f0..1277092 100644 --- a/tests/benchmark.js +++ b/tests/benchmark.js @@ -2114,7 +2114,7 @@ * @memberOf Benchmark * @type String */ - 'version': '0.2.2', + 'version': '0.3.0', /** * The default options copied by benchmark instances. @@ -2811,4 +2811,4 @@ if (has.air) { clock({ 'fn': noop, 'count': 1, 'options': {} }); } -}(this)); +}(this)); \ No newline at end of file From 65b7618a0a16b3b6df46b16feb7349a32e61edc1 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Mon, 5 Dec 2011 14:36:39 +1100 Subject: [PATCH 12/19] added 3 sequence benchmarks, add/fire/remove --- tests/benchmark.html | 112 +++++++++++++++++++++++++++++++++++++++++-- 1 file changed, 108 insertions(+), 4 deletions(-) diff --git a/tests/benchmark.html b/tests/benchmark.html index 48be08e..0b844cb 100644 --- a/tests/benchmark.html +++ b/tests/benchmark.html @@ -26,6 +26,10 @@

      Running Benchmark Suite

      + + + + - - - - - - - - - - - - - - -

      Bean Benchmarks

      -

      CAUTION: these libraries all handle events differently so comparing speed is a fraught exercise. The benchmarks here are intended to help improve Bean's speed with the other libaries included as a rough guide, knowing that the other libraries have various shortcuts that can make it easier. For example, NW doesn't have a flexibility comparable to Bean which allows it to optimise certain paths. Likewise, jQuery is happy to append its own unique IDs to both DOM elements and any object you want to do a pub/sub on; which Bean aims to avoid.

      -
      -
        -
        - - -
        -
        -
        -
        -
        -
        -
        - - - - - diff --git a/tests/tests.js b/tests/tests.js index 80f49d5..9f8f9c3 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -124,17 +124,17 @@ sink('add', function (test, ok) { bean.add(el, 'foo', function () {ok(true, 'additional custom event listeners trigger event 2')}); }); - test('one: should only trigger handler once', 1, function() { + test('one: should only trigger handler once', 1, function () { var el = document.getElementById('input') - bean.one(el, 'click', function() { ok(true, 'handler called exactly one time') }) + bean.one(el, 'click', function () { ok(true, 'handler called exactly one time') }) Syn.click(el) Syn.click(el) Syn.click(el) }); - test('one: should be removable', 0, function() { + test('one: should be removable', 0, function () { var el = document.getElementById('input') - , handler = function() { ok(false, 'handler shouldn\'t have been called') } + , handler = function () { ok(false, 'handler shouldn\'t have been called') } bean.one(el, 'click', handler) bean.remove(el, 'click', handler) Syn.click(el) @@ -266,6 +266,83 @@ sink('event object', function (test, ok) { Syn.key(el, 'f'); }); + // the idea here is that we have a whitelist in bean.js for properties to copy over from the original + // event object (if they exist) to the new synthetic one. But, there are a bunch of browser specific + // properties we don't care about. We list those properties here and then we check to see if there are + // any properties in source event objects that aren't being copied to the new event objects that we + // haven't specifically listed as 'ignorable'. This way we should be able to pick up new event properties + // browsers as they're implemented and then make a decision as to whether they should be copied or not + + var commonIgnorables = 'cancelBubble clipboardData defaultPrevented explicitOriginalTarget getPreventDefault initEvent initUIEvent isChar originalTarget preventCapture preventBubble rangeOffset rangeParent returnValue stopImmediatePropagation synthetic'.split(' ') + // stuff from IE8 and below + , oldIEIgnorables = 'recordset altLeft repeat reason data behaviorCookie source contentOverflow behaviorPart url shiftLeft dataFld qualifier wheelDelta bookmarks srcFilter nextPage srcUrn origin boundElements propertyName ctrlLeft'.split(' ') + , clickIgnorables = commonIgnorables.concat(oldIEIgnorables).concat('charCode defaultPrevented initMouseEvent keyCode layerX layerY initNSMouseEvent x y'.split(' ')) + , oldIEKeyIgnorables = 'fromElement toElement dataTransfer button x y screenX screenY clientX clientY offsetX offsetY'.split(' ') + , keyIgnorables = commonIgnorables.concat(oldIEIgnorables).concat(oldIEKeyIgnorables).concat('initKeyEvent layerX layerY pageX pageY'.split(' ')) + + , getEventObject = function (evType, elType, trigger, callback) { + var el = document.getElementById('input') + , handler = function (e) { + bean.remove(el) + callback(e) + } + bean.add(el, evType, handler) + trigger(el) + } + + , contains = function (arr, e) { + for (var i = arr.length; i--;) { + if (arr[i] === e) return true + } + return false + } + + , verifyEventObject = function (event, ignorables) { + var p, orig = event.originalEvent + for (var p in orig) { + if ( + !event.hasOwnProperty(p) + && !contains(ignorables, p) + && !/^[A-Z_\d]+$/.test(p) // STUFF_LIKE_THIS + && !/^moz[A-Z]/.test(p) // Mozilla prefixed properties + ) + ok(true, 'additional, uncopied property: "' + p + '"') + } + } + + var testMouseEvent = function (type, syn) { + getEventObject( + type + , 'button' + , function (el) { Syn[syn || type](el) } + , function (event) { + ok(!!event && !!event.originalEvent && event.type === type, 'got event object') + verifyEventObject(event, clickIgnorables) + } + ) + } + + test('click: has correct properties', 1, function () { testMouseEvent('click') }) + test('dblclick: has correct properties', 1, function () { testMouseEvent('dblclick') }) + test('mousedown: has correct properties', 1, function () { testMouseEvent('mousedown', 'click') }) + test('mouseup: has correct properties', 1, function () { testMouseEvent('mouseup', 'click') }) + + var testKeyEvent = function (type) { + getEventObject( + type + , 'input' + , function (el) { Syn.key(el, 'f') } + , function (event) { + ok(!!event && !!event.originalEvent && event.type === type, 'got event object') + verifyEventObject(event, keyIgnorables) + } + ) + } + + test('keyup: has correct properties', 1, function () { testKeyEvent('keyup') }) + test('keydown: has correct properties', 1, function () { testKeyEvent('keydown') }) + test('keypress: has correct properties', 1, function () { testKeyEvent('keypress') }) + }) sink('remove', function (test, ok) { @@ -291,22 +368,27 @@ sink('remove', function (test, ok) { Syn.click(el) }); - test('remove: should be able to remove mulitple events with an object literal', 1, function () { - var el = document.getElementById('input'), - handler1 = function () { - ok(true, 'remove mulitple events with an object literal1'); + test('remove: should be able to remove mulitple events with an object literal', 2, function () { + var el = document.getElementById('input') + , handler1 = function () { + ok(true, 'remove mulitple events with an object literal (1)') bean.remove(el, { - click: handler1, - keydown: handler2 - }); - Syn.click(el).key('j'); - }, - handler2 = function () { - ok(true, 'remove mulitple events with an object literal2'); - }; - bean.add(el, 'click', handler1); - bean.add(el, 'keydown', handler2); - Syn.click(el); + click: handler1 + , keydown: handler2 + }) + Syn.click(el).key('j') + } + , handler2 = function () { + ok(true, 'remove mulitple events with an object literal (1)') + } + , handler3 = function () { // should be called once + bean.remove(el, 'keydown', handler3) + ok(true, 'remove mulitple events with an object literal (3)') + } + bean.add(el, 'click', handler1) + bean.add(el, 'keydown', handler2) + bean.add(el, 'keydown', handler3) + Syn.click(el) }); test('remove: should be able to remove all events of a specific type', 2, function () { @@ -356,24 +438,72 @@ sink('remove', function (test, ok) { Syn.click(el); }); - test('remove: should be able to remove all events of a certain namespace', 1, function () { - var el = document.getElementById('input'), - handler1 = function () { - ok(true, 'remove all events 1'); - bean.remove(el, '.foo'); - Syn.click(el).key('j'); - }, - handler2 = function () { - ok(true, 'remove all events 2'); - }; - bean.add(el, 'click.foo', handler1); - bean.add(el, 'keydown.foo', handler2); - Syn.click(el); - }); + test('remove: should only remove events of specified type', 4, function () { + // testing that bean.remove(el, type) removes *only* of that type and no others + var el = document.getElementById('input') + , handler1 = function (e) { + ok(true, 'handled ' + e.type + ' event (1)') + } + , handler2 = function (e) { + ok(true, 'handled ' + e.type + ' event (2)') + bean.remove(el, e.type) + } + + bean.add(el, 'click', handler1) + bean.add(el, 'keyup', handler1) + bean.add(el, 'click', handler2) + bean.add(el, 'keyup', handler2) + Syn.click(el) + Syn.key(el, 'f') + Syn.click(el) + Syn.key(el, 'f') + }) + + test('remove: should only remove events for specified handler', 2, function () { + // testing that bean.remove(el, fn) removes *only* that handler and no others + var el = document.getElementById('input') + , c = 0 + , handler1 = function (e) { + ok(true, 'handled ' + e.type + ' event (1)') + } + , handler2 = function (e) { + ok(true, 'handled ' + e.type + ' event (2)') + ++c == 2 && bean.remove(el) + } + + bean.add(el, 'click', handler1) + bean.add(el, 'keyup', handler1) + bean.add(el, 'click', handler2) + bean.add(el, 'keyup', handler2) + bean.remove(el, handler1) + Syn.click(el) + Syn.key(el, 'f') + }) + + test('remove: should be able to remove all events of a certain namespace', 3, function () { + var el = document.getElementById('input') + , handler1 = function () { + ok(true, 'remove all events (1)') + bean.remove(el, '.foo') + Syn.click(el).key('j') + } + , handler2 = function () { + ok(true, 'remove all events (2)') + } + , handler3 = function () { // should be called twice + bean.remove(el, '.foo') + ok(true, 'remove all events (3)') + } + bean.remove(el) + bean.add(el, 'click.foo', handler1) + bean.add(el, 'keydown.foo', handler2) + bean.add(el, 'click.bar', handler3) + Syn.click(el) + }) }) -sink('clone', function (test, ok, before) { +sink('clone', function (test, ok, before, after) { var el1 = document.getElementById('input'); var el2 = document.getElementById('input2'); @@ -383,6 +513,11 @@ sink('clone', function (test, ok, before) { bean.remove(el2); }) + after(function () { + bean.remove(el1); + bean.remove(el2); + }) + test('clone: should be able to clone events of a specific type from one element to another', 2, function () { bean.add(el2, 'click', function () {ok(true, 'clones events of a specific type from one element to another 1')}); bean.add(el2, 'click', function () { @@ -451,12 +586,12 @@ sink('delegation', function (test, ok) { Syn.click(el4); }); - test('delegate: should be able to remove delegated handler', 1, function() { + test('delegate: should be able to remove delegated handler', 1, function () { var el1 = document.getElementById('foo'); var el2 = document.getElementById('bar'); bean.remove(el1); bean.remove(el2); - var fn = function() { + var fn = function () { ok(true, 'degegated event triggered once'); bean.remove(el1, 'click', fn); } @@ -533,4 +668,64 @@ sink('namespaces', function (test, ok) { }); -window.onload = start; +sink('custom types', function (test, ok) { + + test('custom types: mouseenter/mouseleave should wrap simple mouseover/mouseout', 2, function () { + var html = document.documentElement + , foo = document.getElementById('foo') + , bar = document.getElementById('bar') + , bang = document.getElementById('bang') + , me = function (e) { ok(true, 'triggers single mouseenter event') } + , ml = function (e) { ok(true, 'triggers single mouseleave event') } + bean.remove(foo) + bean.add(foo, 'mouseenter', me) + bean.add(foo, 'mouseleave', ml) + // relatedTarget is where the mouse came from for mouseover and where it's going to in mouseout + Syn.trigger('mouseover', { relatedTarget: html }, foo) + Syn.trigger('mouseover', { relatedTarget: foo }, bar) + Syn.trigger('mouseover', { relatedTarget: bar }, bang) + Syn.trigger('mouseout', { relatedTarget: bar }, bang) + Syn.trigger('mouseout', { relatedTarget: foo }, bar) + Syn.trigger('mouseout', { relatedTarget: html }, foo) + bean.remove(foo) + }) + + function testRemove(removeFn) { + var html = document.documentElement + , foo = document.getElementById('foo') + , me = function (e) { ok(true, 'triggers single mouseenter event') } + , ml = function (e) { ok(true, 'triggers single mouseleave event') } + + bean.remove(foo) + bean.add(foo, 'mouseenter', me) + bean.add(foo, 'mouseleave', ml) + Syn.trigger('mouseover', { relatedTarget: html }, foo) + Syn.trigger('mouseout', { relatedTarget: html }, foo) + removeFn(foo, me, ml) + Syn.trigger('mouseover', { relatedTarget: html }, foo) + Syn.trigger('mouseout', { relatedTarget: html }, foo) + } + + test('custom types: custom events should be removable', 2, function () { + testRemove(function (foo, me, ml) { + bean.remove(foo) + }) + }) + + test('custom types: custom events should be removable by type', 2, function () { + testRemove(function (foo, me, ml) { + bean.remove(foo, 'mouseenter') + bean.remove(foo, 'mouseleave') + }) + }) + + test('custom types: custom events should be removable by type+handler', 2, function () { + testRemove(function (foo, me, ml) { + bean.remove(foo, 'mouseenter', me) + bean.remove(foo, 'mouseleave', ml) + }) + }) + +}) + +window.onload = start From 9ca51bec4ff286a6cb987d6f192d696810bb4c57 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Fri, 23 Dec 2011 16:04:36 +1100 Subject: [PATCH 18/19] readme fixes --- README.md | 16 +++++++++++----- 1 file changed, 11 insertions(+), 5 deletions(-) diff --git a/README.md b/README.md index 6126f4d..a48cc40 100644 --- a/README.md +++ b/README.md @@ -99,7 +99,7 @@ one() 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) +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) ```javascript // remove a single event handlers @@ -108,11 +108,17 @@ bean.remove(element, 'click', handler); // remove all click handlers bean.remove(element, 'click'); +// remove handler for all events +bean.remove(element, handler); + // remove multiple events bean.remove(element, 'mousedown mouseup'); // remove all events bean.remove(element); + +// remove handlers for events using object literal +bean.remove(element, { click: clickHandler, keyup: keyupHandler }) ``` clone() @@ -142,7 +148,7 @@ bean.fire(element, 'mousedown mouseup'); custom events ------------- -Bean uses methods similar to [Dean Edward's event model](http://dean.edwards.name/weblog/2009/03/callbacks-vs-events/) to ensure custom events behave like real events, rather than just callbacks. +Bean uses methods similar to [Dean Edwards' event model](http://dean.edwards.name/weblog/2009/03/callbacks-vs-events/) to ensure custom events behave like real events, rather than just callbacks. For all intents and purposes, you can just think of them as native events, which will bubble up, and everything else you would expect... @@ -163,7 +169,7 @@ use them like regular events: object support -------------- -Good news, everything you can do in bean with an element, you can also do with an object! This is particularly useful for working with classes or plugins. +Good news, everything you can do in Bean with an element, you can also do with an object! This is particularly useful for working with classes or plugins. ```javascript var inst = new Klass(); @@ -220,9 +226,9 @@ point your browser at bean/tests/index.html Ender Integration API --------------------- -If you use bean with ender it's API is greatly extended through it's Bridge file. This extension aims to give bean the look and feel of jQuery, but at the tiny size of bean. +If you use Bean with Ender its API is greatly extended through its bridge file. This extension aims to give Bean the look and feel of jQuery, but at the tiny size of Bean. -Here's the run down of the method alias's added... +Here's the run down of the method alias' added... **ADD EVENTS** From 0d4556590b92ee92f814efbeb074223eb7d48c39 Mon Sep 17 00:00:00 2001 From: Rod Vagg Date: Fri, 23 Dec 2011 19:50:48 +1100 Subject: [PATCH 19/19] stop() yay! --- README.md | 6 +++++ src/bean.js | 64 ++++++++++++++++++++++++++++-------------------- tests/index.html | 3 +++ tests/tests.js | 41 +++++++++++++++++++++++++++++++ 4 files changed, 88 insertions(+), 26 deletions(-) diff --git a/README.md b/README.md index a48cc40..d532bef 100644 --- a/README.md +++ b/README.md @@ -199,6 +199,12 @@ bean.add(el, 'click', function (e) { e.stopPropagation(); }); +// a simple shortcut, since you usually want preventDefault() and stopPropagation() at the same time +// (works the same as the previous example) +bean.add(el, 'click', function (e) { + e.stop(); +}); + // DOMContentLoaded bean.add(document, 'DOMContentLoaded', fn); diff --git a/src/bean.js b/src/bean.js index a3b402c..5b4af24 100644 --- a/src/bean.js +++ b/src/bean.js @@ -59,22 +59,31 @@ var commonProps = 'altKey attrChange attrName bubbles cancelable ctrlKey currentTarget detail eventPhase getModifierState isTrusted metaKey relatedNode relatedTarget shiftKey srcElement target timeStamp type view which'.split(' ') , mouseProps = commonProps.concat('button buttons clientX clientY dataTransfer fromElement offsetX offsetY pageX pageY screenX screenY toElement'.split(' ')) , keyProps = commonProps.concat('char charCode key keyCode'.split(' ')) - , preventDefault = function (e) { + , preventDefault = 'preventDefault' + , createPreventDefault = function (e) { return function () { - if (e.preventDefault) - e.preventDefault() + if (e[preventDefault]) + e[preventDefault]() else e.returnValue = false } } - , stopPropagation = function (e) { + , stopPropagation = 'stopPropagation' + , createStopPropagation = function (e) { return function () { - if (e.stopPropagation) - e.stopPropagation() + if (e[stopPropagation]) + e[stopPropagation]() else e.cancelBubble = true } } + , createStop = function (e) { + return function () { + e[preventDefault]() + e[stopPropagation]() + e.stopped = true + } + } , copyProps = function (event, result, props) { var i, p for (i = props.length; i--;) { @@ -92,8 +101,9 @@ , type = event.type , target = event.target || event.srcElement - result.preventDefault = preventDefault(event) - result.stopPropagation = stopPropagation(event) + result[preventDefault] = createPreventDefault(event) + result[stopPropagation] = createStopPropagation(event) + result.stop = createStop(event) result.target = target && target.nodeType === 3 ? target.parentNode : target if (isNative) { // we only need basic augmentation on custom events, the rest is too expensive @@ -141,27 +151,29 @@ this.eventSupport = this.target[eventSupport] } - // given a list of namespaces, is our entry in any of them? - entry.prototype.inNamespaces = function (checkNamespaces) { - var i, j - if (!checkNamespaces) - return true - if (!this.namespaces) - return false - for (i = checkNamespaces.length; i--;) { - for (j = this.namespaces.length; j--;) { - if (checkNamespaces[i] === this.namespaces[j]) + entry.prototype = { + // given a list of namespaces, is our entry in any of them? + inNamespaces: function (checkNamespaces) { + var i, j + if (!checkNamespaces) return true + if (!this.namespaces) + return false + for (i = checkNamespaces.length; i--;) { + for (j = this.namespaces.length; j--;) { + if (checkNamespaces[i] === this.namespaces[j]) + return true + } + } + return false } - } - return false - } - // match by element, original fn (opt), handler fn (opt) - entry.prototype.matches = function (checkElement, checkOriginal, checkHandler) { - return this.element === checkElement && - (!checkOriginal || this.original === checkOriginal) && - (!checkHandler || this.handler === checkHandler) + // match by element, original fn (opt), handler fn (opt) + , matches: function (checkElement, checkOriginal, checkHandler) { + return this.element === checkElement && + (!checkOriginal || this.original === checkOriginal) && + (!checkHandler || this.handler === checkHandler) + } } return entry diff --git a/tests/index.html b/tests/index.html index 7182ebe..2147ce0 100644 --- a/tests/index.html +++ b/tests/index.html @@ -37,6 +37,9 @@

        Bean Tests

        +
        + +
        diff --git a/tests/tests.js b/tests/tests.js index 9f8f9c3..a8c6ece 100644 --- a/tests/tests.js +++ b/tests/tests.js @@ -257,6 +257,47 @@ sink('event object', function (test, ok) { bean.fire(el, 'customEvent'); }); + test('event: stop should preventDefault and stopPropagation', 1, function () { + // first feature-test, if we don't support native createEvent then there's + // no point running this test unfortunately + var supported = false + try { + document.createEvent('KeyEvents') + supported = true + } catch (e) { + try { + document.createEvent('TextEvent') + supported = true + } catch (e) { } + } + if (!supported) { + ok(true, 'not running test in this browser, native createEvent() not supported') + return + } + + // we should be able to prevent a keypress and event propagation with stop() + // on the keypress event, checking the parent doesn't receive the keypress + // and then checking the input contents on a keyup, it should be empty. + var txt = document.getElementById('txt') + , parent = document.getElementById('stopper') + , txtHandler = function (event) { + event.stop() + } + , txtCheckHandler = function (event) { + ok(!txt.value.length, 'input is has no text after keypress') + } + , parentHandler = function (event) { + ok(true, 'parent should not receive event') + bean.remove(parent) + } + + txt.value = '' + bean.add(txt, 'keypress', txtHandler) + bean.add(txt, 'keyup', txtCheckHandler) + bean.add(parent, 'keypress', parentHandler) + Syn.key(txt, 'f') + }) + test('event: should have keyCode', 1, function () { var el = document.getElementById('input'); bean.add(el, 'keypress', function (e) {