diff --git a/.gitignore b/.gitignore
index b512c09..1bd7226 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,2 @@
-node_modules
\ No newline at end of file
+node_modules
+*.swp
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/README.md b/README.md
index 6126f4d..d532bef 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();
@@ -193,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);
@@ -220,9 +232,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**
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/make/build.js b/make/build.js
index ef0501c..2133562 100644
--- a/make/build.js
+++ b/make/build.js
@@ -1,24 +1,31 @@
require('smoosh').config({
- "JAVASCRIPT": {
- "DIST_DIR": "./",
- "bean": [
- "./src/copyright.js",
- "./src/bean.js"
- ]
- },
- "JSHINT_OPTS": {
- "boss": true,
- "forin": false,
- "curly": false,
- "debug": false,
- "devel": false,
- "evil": false,
- "regexp": false,
- "undef": false,
- "sub": true,
- "white": false,
- "indent": 2,
- "whitespace": true,
- "asi": true
- }
-}).run().build().analyze();
\ No newline at end of file
+ 'JAVASCRIPT': {
+ 'DIST_DIR': './'
+ , 'bean': [
+ './src/copyright.js'
+ , './src/bean.js'
+ ]
+ }
+ , 'JSHINT_OPTS': {
+ 'boss': true
+ , 'forin': true
+ , 'curly': false
+ , 'debug': true
+ , 'devel': false
+ , 'evil': false
+ , 'regexp': false
+ , 'undef': true
+ , 'sub': true
+ , 'white': false
+ , 'indent': 2
+ , 'whitespace': true
+ , 'asi': true
+ , 'trailing': true
+ , 'latedef': true
+ , 'laxbreak': true
+ , 'browser': true
+ , 'eqeqeq': true
+ , 'bitwise': false
+ , 'loopfunc': false
+ }
+}).run().build().analyze()
diff --git a/src/bean.js b/src/bean.js
index ccf42f0..c5c2a1c 100644
--- a/src/bean.js
+++ b/src/bean.js
@@ -1,385 +1,468 @@
+/*global module:true, define:true*/
!function (name, context, definition) {
- if (typeof module != 'undefined') module.exports = definition(name, context);
- else if (typeof define == 'function' && typeof define.amd == 'object') define(definition);
+ if (typeof module !== 'undefined') module.exports = definition(name, context);
+ else if (typeof define === 'function' && typeof define.amd === 'object') define(definition);
else context[name] = definition(name, context);
}('bean', this, function (name, context) {
- var win = window,
- __uid = 1,
- old = context[name],
- registry = {},
- collected = {},
- overOut = /over|out/,
- namespace = /[^\.]*(?=\..*)\.|.*/,
- stripName = /\..*/,
- addEvent = 'addEventListener',
- attachEvent = 'attachEvent',
- removeEvent = 'removeEventListener',
- detachEvent = 'detachEvent',
- doc = document || {},
- root = doc.documentElement || {},
- 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;
+ var win = window
+ , old = context[name]
+ , overOut = /over|out/
+ , namespaceRegex = /[^\.]*(?=\..*)\.|.*/
+ , nameRegex = /\..*/
+ , addEvent = 'addEventListener'
+ , attachEvent = 'attachEvent'
+ , removeEvent = 'removeEventListener'
+ , detachEvent = 'detachEvent'
+ , doc = document || {}
+ , 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()
+
+ , nativeEvents = {
+ click: 1, dblclick: 1, mouseup: 1, mousedown: 1, contextmenu: 1 // mouse buttons
+ , mousewheel: 1, DOMMouseScroll: 1 // mouse wheel
+ , mouseover: 1, mouseout: 1, mousemove: 1, selectstart: 1, selectend: 1 // mouse movement
+ , keydown: 1, keypress: 1, keyup: 1 // keyboard
+ , orientationchange: 1 // mobile
+ , touchstart: 1, touchmove: 1, touchend: 1, touchcancel: 1 // touch
+ , gesturestart: 1, gesturechange: 1, gestureend: 1 // gesture
+ , focus: 1, blur: 1, change: 1, reset: 1, select: 1, submit: 1 // form elements
+ , load: 1, unload: 1, beforeunload: 1, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1 // window
+ , error: 1, abort: 1, scroll: 1 // misc
}
- node = node.parentNode;
- }
- },
-
- retrieveUid = function (obj, uid) {
- return (obj.__uid = uid && (uid + '::' + __uid++) || obj.__uid || __uid++);
- },
-
- retrieveEvents = function (element) {
- var uid = retrieveUid(element);
- return (registry[uid] = registry[uid] || {});
- },
-
- 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;
- }
- element[add ? attachEvent : detachEvent]('on' + type, fn);
- },
-
- nativeHandler = function (element, fn, args) {
- return function (event) {
- event = fixEvent(event || ((this.ownerDocument || this.document || this).parentWindow || win).event);
- return fn.apply(element, [event].concat(args));
- };
- },
-
- 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) {
- 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));
- }
- };
- },
-
- targetElement = function (element, isNative) {
- 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();
- };
- }
- 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);
- },
-
- removeListener = function (element, orgType, handler) {
- var uid = element.__uid, names, uids, i, events = retrieveEvents(element), type = orgType.replace(stripName, '');
-
- if (!events || !events[type]) {
- return element;
- }
- handler && handler.__one && (handler = handler.__one)
- names = orgType.replace(namespace, '');
- uids = names ? names.split('.') : [handler.__uid];
+ , customEvents = (function () {
+ function isDescendant(parent, node) {
+ while ((node = node.parentNode) !== null) {
+ if (node === parent) return true
+ }
+ return false
+ }
- 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);
+ 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))
+ }
+
+ return {
+ mouseenter: { base: 'mouseover', condition: check }
+ , mouseleave: { base: 'mouseout', condition: check }
+ , mousewheel: { base: /Firefox/.test(navigator.userAgent) ? 'DOMMouseScroll' : 'mousewheel' }
+ }
+ })()
+
+ , fixEvent = (function () {
+ 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 = 'preventDefault'
+ , createPreventDefault = function (e) {
+ return function () {
+ if (e[preventDefault])
+ e[preventDefault]()
+ else
+ e.returnValue = false
+ }
+ }
+ , stopPropagation = 'stopPropagation'
+ , createStopPropagation = function (e) {
+ return function () {
+ 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--;) {
+ p = props[i]
+ if (!(p in result) && p in event) result[p] = event[p]
+ }
+ }
+
+ return function (event, isNative) {
+ var result = { originalEvent: event, isNative: isNative }
+ if (!event)
+ return result
+
+ var props
+ , type = event.type
+ , target = event.target || event.srcElement
+
+ 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
+ if (type.indexOf('key') !== -1) {
+ props = keyProps
+ result.keyCode = event.which || event.keyCode
+ } else if ((/click|mouse|menu/i).test(type)) {
+ props = mouseProps
+ 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']
+ }
+ copyProps(event, result, props || commonProps)
+ }
+ return result
+ }
+ })()
+
+ // if we're in old IE we can't do onpropertychange on doc or win so we use doc.documentElement for both
+ , targetElement = function (element, isNative) {
+ return !W3C_MODEL && !isNative && (element === doc || element === win) ? root : element
}
- }
- destroyHandler(names); //get combos
- for (i = uids.length; i--; destroyHandler(uids[i])) {} //get singles
+ // we use one of these per listener, of any type
+ , 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.custom = customEvents[type]
+ this.isNative = nativeEvents[type] && element[eventSupport]
+ this.eventType = W3C_MODEL || this.isNative ? type : 'propertychange'
+ this.customType = !W3C_MODEL && !this.isNative && type
+ this.target = targetElement(element, this.isNative)
+ this.eventSupport = this.target[eventSupport]
+ }
- if (isEmpty(events[type])) {
- delete events[type];
- }
+ 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
+ }
- if (isEmpty(registry[uid])) {
- delete registry[uid];
- delete collected[uid];
- }
+ // 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
+ })()
+
+ , registry = (function () {
+ // our map stores arrays by event type, just because it's better than storing
+ // everything in a single array
+ var map = {}
+
+ // generic functional search of our registry for matching listeners,
+ // `fn` returns false to break out of the loop
+ , forAll = function (element, type, original, handler, fn) {
+ if (!type || type === '*') {
+ // search the whole registry
+ for (var t in map) {
+ if (map.hasOwnProperty(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++) {
+ if (all || list[i].matches(element, original, handler))
+ if (!fn(list[i], list, i, type))
+ return
+ }
+ }
+ }
+
+ , has = function (element, type, original) {
+ // we're not using forAll here simply because it's a bit slower and this
+ // needs to be fast
+ 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 = []
+ 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 (entry, list, i) {
+ list.splice(i, 1)
+ return false
+ })
+ }
+
+ // dump all entries, used for onunload
+ , entries = function () {
+ var t, entries = []
+ for (t in map) {
+ if (map.hasOwnProperty(t))
+ entries = entries.concat(map[t])
+ }
+ return entries
+ }
+
+ return { has: has, get: get, put: put, del: del, entries: entries }
+ })()
+
+ // add and remove listeners to DOM elements
+ , 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
+ element[add ? attachEvent : detachEvent]('on' + type, fn)
+ }
- return element;
- },
+ , nativeHandler = function (element, fn, args) {
+ return function (event) {
+ event = fixEvent(event || ((this.ownerDocument || this.document || this).parentWindow || win).event, true)
+ return fn.apply(element, [event].concat(args))
+ }
+ }
- 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--;) {
- if (array[i] == target) {
- return fn.apply(target, arguments);
+ , 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) {
+ 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))
}
}
}
- };
- },
- _add = function (meth, element, events, fn, delfn, $) {
- if (typeof events == 'object' && !fn) {
- for (var type in events) {
- events.hasOwnProperty(type) && _add(meth, element, type, events[type]);
+ , once = function (rm, element, type, fn, originalFn) {
+ // wrap the handler in a handler that does a remove as well
+ return function () {
+ rm(element, type, originalFn)
+ fn.apply(this, arguments)
+ }
}
- } 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)
+
+ , removeListener = function (element, orgType, handler, namespaces) {
+ var i, l, entry
+ , type = (orgType && orgType.replace(nameRegex, ''))
+ , handlers = registry.get(element, type, handler)
+
+ for (i = 0, l = handlers.length; i < l; i++) {
+ if (handlers[i].inNamespaces(namespaces)) {
+ if ((entry = handlers[i]).eventSupport)
+ listener(entry.target, entry.eventType, entry.handler, false, entry.type)
+ // TODO: this is problematic, we have a registry.get() and registry.del() that
+ // both do registry searches so we waste cycles doing this. Needs to be rolled into
+ // a single registry.forAll(fn) that removes while finding, but the catch is that
+ // we'll be splicing the arrays that we're iterating over. Needs extra tests to
+ // make sure we don't screw it up. @rvagg
+ registry.del(entry)
}
- 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));
+ }
}
- }
- return element;
- },
-
- 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)))
- },
-
- remove = function (element, orgEvents, fn) {
- var k, m, type, events, i,
- isString = typeof(orgEvents) == 'string',
- names = isString && orgEvents.replace(namespace, ''),
- rm = removeListener,
- attached = retrieveEvents(element);
- names = names && names.split('.');
- if (isString && /\s/.test(orgEvents)) {
- orgEvents = orgEvents.split(' ');
- i = orgEvents.length - 1;
- while (remove(element, orgEvents[i]) && 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('.'));
+
+ , addListener = function (element, orgType, fn, originalFn, args) {
+ var entry
+ , type = orgType.replace(nameRegex, '')
+ , namespaces = orgType.replace(namespaceRegex, '').split('.')
+
+ if (registry.has(element, type, fn))
+ return element // no dupe
+ if (type === 'unload')
+ fn = once(removeListener, element, type, fn, originalFn) // self clean-up
+ if (customEvents[type]) {
+ if (customEvents[type].condition)
+ fn = customHandler(element, fn, type, customEvents[type].condition, true)
+ 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, false)
+ if (entry.eventSupport)
+ listener(entry.target, entry.eventType, entry.handler, true, entry.customType)
+ }
+
+ , 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) {
+ for (i = array.length; i--;) {
+ if (array[i] === target) {
+ return fn.apply(target, arguments)
+ }
}
}
}
}
- 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
+
+ , remove = function (element, typeSpec, fn) {
+ var k, m, type, namespaces, i
+ , rm = removeListener
+ , isString = typeSpec && typeof typeSpec === 'string'
+
+ if (isString && typeSpec.indexOf(' ') > 0) {
+ // remove(el, 't1 t2 t3', fn) or remove(el, 't1 t2 t3')
+ typeSpec = typeSpec.split(' ')
+ for (i = typeSpec.length; i--;)
+ remove(element, typeSpec[i], fn)
+ return element
}
- }
- }
- return element;
- },
-
- fire = function (element, type, args) {
- var evt, k, i, m, 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));
+ type = isString && typeSpec.replace(nameRegex, '')
+ if (type && customEvents[type])
+ type = customEvents[type].type
+ if (!typeSpec || isString) {
+ // remove(el) or remove(el, t1.ns) or remove(el, .ns) or remove(el, .ns1.ns2.ns3)
+ if (namespaces = isString && typeSpec.replace(namespaceRegex, ''))
+ namespaces = namespaces.split('.')
+ rm(element, type, fn, namespaces)
+ } else if (typeof typeSpec === 'function') {
+ // remove(el, fn)
+ rm(element, null, typeSpec)
+ } else {
+ // remove(el, { t1: fn1, t2, fn2 })
+ for (k in typeSpec) {
+ if (typeSpec.hasOwnProperty(k))
+ remove(element, k, typeSpec[k])
}
}
- } else if (!args && element[eventSupport]) {
- fireListener(isNative, type, element);
- } else {
- for (k in handlers) {
- handlers.hasOwnProperty(k) && handlers[k].apply(element, [false].concat(args));
- }
+ return element
}
- }
- return element;
- },
-
- fireListener = W3C_MODEL ? function (isNative, type, element) {
- evt = document.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]++;
- },
-
- 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);
- }
- return element;
- },
- fixEvent = function (e) {
- var result = {};
- if (!e) {
- return result;
- }
- var 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;
- 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;
- }
- 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];
- }
- }
- return result;
- },
+ , add = function (element, events, fn, delfn, $) {
+ var type, types, i, args
+ , originalFn = fn
+ , isDel = fn && typeof fn === 'string'
- isEmpty = function (obj) {
- for (var prop in obj) {
- if (obj.hasOwnProperty(prop)) return false;
- }
- return true;
- }
+ if (events && !fn && typeof events === 'object') {
+ for (type in events) {
+ if (events.hasOwnProperty(type))
+ add.apply(this, [ element, type, events[type] ])
+ }
+ } else {
+ args = arguments.length > 3 ? slice.call(arguments, 3) : []
+ types = (isDel ? fn : events).split(' ')
+ isDel && (fn = del(events, (originalFn = delfn), $)) && (args = slice.call(args, 1))
+ // special case for one()
+ this === ONE && (fn = once(remove, element, events, fn, originalFn))
+ for (i = types.length; i--;) addListener(element, types[i], fn, originalFn, args)
+ }
+ return element
+ }
- fixEvent.preventDefault = function (e) {
- return function () {
- if (e.preventDefault) {
- e.preventDefault();
+ , one = function () {
+ return add.apply(ONE, arguments)
}
- else {
- e.returnValue = false;
+
+ , fireListener = W3C_MODEL ? function (isNative, type, element) {
+ var 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)
+ // if not-native then we're using onpropertychange so we just increment a custom property
+ isNative ? element.fireEvent('on' + type, doc.createEventObject()) : element['_on' + type]++
}
- };
- };
-
- fixEvent.stopPropagation = function (e) {
- return function () {
- if (e.stopPropagation) {
- e.stopPropagation();
- } else {
- e.cancelBubble = true;
+
+ , fire = function (element, type, args) {
+ var i, j, l, names, handlers
+ , types = type.split(' ')
+
+ for (i = types.length; i--;) {
+ type = types[i].replace(nameRegex, '')
+ if (names = types[i].replace(namespaceRegex, ''))
+ names = names.split('.')
+ if (!names && !args && element[eventSupport]) {
+ fireListener(nativeEvents[type], type, element)
+ } else {
+ // non-native event, either because of a namespace, arguments or a non DOM element
+ // iterate over all listeners and manually 'fire'
+ handlers = registry.get(element, type)
+ args = [false].concat(args)
+ for (j = 0, l = handlers.length; j < l; j++) {
+ if (handlers[j].inNamespaces(names))
+ handlers[j].handler.apply(element, args)
+ }
+ }
+ }
+ return element
}
- };
- };
-
- var nativeEvents = { click: 1, dblclick: 1, mouseup: 1, mousedown: 1, contextmenu: 1, //mouse buttons
- mousewheel: 1, DOMMouseScroll: 1, //mouse wheel
- mouseover: 1, mouseout: 1, mousemove: 1, selectstart: 1, selectend: 1, //mouse movement
- keydown: 1, keypress: 1, keyup: 1, //keyboard
- orientationchange: 1, // mobile
- touchstart: 1, touchmove: 1, touchend: 1, touchcancel: 1, // touch
- gesturestart: 1, gesturechange: 1, gestureend: 1, // gesture
- focus: 1, blur: 1, change: 1, reset: 1, select: 1, submit: 1, //form elements
- load: 1, unload: 1, beforeunload: 1, resize: 1, move: 1, DOMContentLoaded: 1, readystatechange: 1, //window
- error: 1, abort: 1, scroll: 1 }; //misc
-
- 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));
- }
- var customEvents = {
- mouseenter: { base: 'mouseover', condition: check },
- mouseleave: { base: 'mouseout', condition: check },
- mousewheel: { base: /Firefox/.test(navigator.userAgent) ? 'DOMMouseScroll' : 'mousewheel' }
- };
+ , clone = function (element, from, type) {
+ var i = 0
+ , handlers = registry.get(from, type)
+ , l = handlers.length
- var bean = { add: add, one: one, remove: remove, clone: clone, fire: fire };
+ for (;i < l; i++)
+ handlers[i].original && add(element, handlers[i].type, handlers[i].original)
+ return element
+ }
- var clean = function (el) {
- var uid = remove(el).__uid;
- if (uid) {
- delete collected[uid];
- delete registry[uid];
- }
- };
+ , bean = {
+ add: add
+ , one: one
+ , remove: remove
+ , clone: clone
+ , fire: fire
+ , noConflict: function () {
+ context[name] = old
+ return this
+ }
+ }
if (win[attachEvent]) {
- add(win, 'unload', function () {
- for (var k in collected) {
- collected.hasOwnProperty(k) && clean(collected[k]);
+ // for IE, clean up on unload to avoid leaks
+ var cleanup = function () {
+ var i, entries = registry.entries()
+ for (i in entries) {
+ if (entries[i].type && entries[i].type !== 'unload')
+ remove(entries[i].element, entries[i].type)
}
- win.CollectGarbage && CollectGarbage();
- });
+ win[detachEvent]('onunload', cleanup)
+ win.CollectGarbage && win.CollectGarbage()
+ }
+ win[attachEvent]('onunload', cleanup)
}
- bean.noConflict = function () {
- context[name] = old;
- return this;
- };
-
- return bean;
-
-});
+ return bean
+})
diff --git a/src/ender.js b/src/ender.js
index df025a0..4e08f89 100644
--- a/src/ender.js
+++ b/src/ender.js
@@ -1,62 +1,60 @@
!function ($) {
- var b = require('bean'),
- integrate = function (method, type, method2) {
- var _args = type ? [type] : [];
+ var b = require('bean')
+ , integrate = function (method, type, method2) {
+ var _args = type ? [type] : []
return function () {
for (var args, i = 0, l = this.length; i < l; i++) {
- args = [this[i]].concat(_args, Array.prototype.slice.call(arguments, 0));
- args.length == 4 && args.push($);
- !arguments.length && method == 'add' && type && (method = 'fire');
- b[method].apply(this, args);
+ args = [this[i]].concat(_args, Array.prototype.slice.call(arguments, 0))
+ args.length == 4 && args.push($)
+ !arguments.length && method == 'add' && type && (method = 'fire')
+ b[method].apply(this, args)
}
- return this;
- };
- };
-
- var add = integrate('add'),
- remove = integrate('remove'),
- fire = integrate('fire');
-
- var methods = {
-
- on: add,
- addListener: add,
- bind: add,
- listen: add,
- delegate: add,
-
- one: integrate('one'),
-
- off: remove,
- unbind: remove,
- unlisten: remove,
- removeListener: remove,
- undelegate: remove,
-
- emit: fire,
- trigger: fire,
+ return this
+ }
+ }
+ , add = integrate('add')
+ , remove = integrate('remove')
+ , fire = integrate('fire')
+
+ , methods = {
+ on: add
+ , addListener: add
+ , bind: add
+ , listen: add
+ , delegate: add
+
+ , one: integrate('one')
+
+ , off: remove
+ , unbind: remove
+ , unlisten: remove
+ , removeListener: remove
+ , undelegate: remove
+
+ , emit: fire
+ , trigger: fire
+
+ , cloneEvents: integrate('clone')
+
+ , hover: function (enter, leave, i) { // i for internal
+ for (i = this.length; i--;) {
+ b.add.call(this, this[i], 'mouseenter', enter)
+ b.add.call(this, this[i], 'mouseleave', leave)
+ }
+ return this
+ }
+ }
- cloneEvents: integrate('clone'),
+ , shortcuts = [
+ 'blur', 'change', 'click', 'dblclick', 'error', 'focus', 'focusin'
+ , 'focusout', 'keydown', 'keypress', 'keyup', 'load', 'mousedown'
+ , 'mouseenter', 'mouseleave', 'mouseout', 'mouseover', 'mouseup', 'mousemove'
+ , 'resize', 'scroll', 'select', 'submit', 'unload'
+ ]
- hover: function (enter, leave, i) { // i for internal
- for (i = this.length; i--;) {
- b.add.call(this, this[i], 'mouseenter', enter);
- b.add.call(this, this[i], 'mouseleave', leave);
- }
- return this;
- }
- };
-
- var i, shortcuts = [
- 'blur', 'change', 'click', 'dblclick', 'error', 'focus', 'focusin',
- 'focusout', 'keydown', 'keypress', 'keyup', 'load', 'mousedown',
- 'mouseenter', 'mouseleave', 'mouseout', 'mouseover', 'mouseup', 'mousemove',
- 'resize', 'scroll', 'select', 'submit', 'unload'
- ];
-
- for (i = shortcuts.length; i--;) {
- methods[shortcuts[i]] = integrate('add', shortcuts[i]);
+ for (var i = shortcuts.length; i--;) {
+ methods[shortcuts[i]] = integrate('add', shortcuts[i])
}
- $.ender(methods, true);
-}(ender);
+ $.ender(methods, true)
+}(ender)
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/benchmark.html b/tests/benchmark.html
index 9fb8132..778b51b 100644
--- a/tests/benchmark.html
+++ b/tests/benchmark.html
@@ -2,164 +2,671 @@
+ | ../bean.js | +../src/bean.js | +NWEvents | +jQuery | + + + +
---|