├── .gitignore ├── .jshintignore ├── .jshintrc ├── .travis.yml ├── LICENCE ├── README.md ├── add-event.js ├── docs.mli ├── dom-delegator.js ├── index.js ├── package.json ├── proxy-event.js ├── remove-event.js └── test ├── dom-add-event.js ├── dom-add-multiple.js ├── dom-data-events.js ├── dom-delegator.js ├── dom-duplicate-event.js ├── dom-global-listeners.js ├── dom-handle.js ├── dom-propagation.js ├── dom-remove-event.js ├── dom-targets.js ├── dom-unlisten-to.js ├── index.js └── lib ├── add-sink-event.js ├── create-event.js ├── h.js └── sink-handler.js /.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | .monitor 3 | .*.swp 4 | .nodemonignore 5 | releases 6 | *.log 7 | *.err 8 | fleet.json 9 | public/browserify 10 | bin/*.json 11 | .bin 12 | build 13 | compile 14 | .lock-wscript 15 | coverage 16 | node_modules 17 | -------------------------------------------------------------------------------- /.jshintignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | coverage 3 | -------------------------------------------------------------------------------- /.jshintrc: -------------------------------------------------------------------------------- 1 | { 2 | "maxdepth": 4, 3 | "maxstatements": 200, 4 | "maxcomplexity": 12, 5 | "maxlen": 80, 6 | "maxparams": 5, 7 | 8 | "curly": true, 9 | "eqeqeq": true, 10 | "immed": true, 11 | "latedef": false, 12 | "noarg": true, 13 | "noempty": true, 14 | "nonew": true, 15 | "undef": true, 16 | "unused": "vars", 17 | "trailing": true, 18 | 19 | "quotmark": true, 20 | "expr": true, 21 | "asi": true, 22 | 23 | "browser": false, 24 | "esnext": true, 25 | "devel": false, 26 | "node": false, 27 | "nonstandard": false, 28 | 29 | "predef": ["require", "module", "__dirname", "__filename"] 30 | } 31 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | node_js: 3 | - 0.8 4 | - "0.10" 5 | before_script: 6 | - npm install 7 | - npm install istanbul coveralls 8 | script: npm run travis-test 9 | -------------------------------------------------------------------------------- /LICENCE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2013 Raynos. 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # dom-delegator 2 | 3 | 10 | 11 | 12 | 13 | Decorate elements with delegated events 14 | 15 | `dom-delegator` allows you to attach an `EventHandler` to 16 | a dom element. 17 | 18 | When event of the correct type occurs `dom-delegator` will 19 | invoke your `EventHandler` 20 | 21 | This allows you to seperate your event listeners from your 22 | event writers. Sprinkle your event writers in the template 23 | in one part of your codebase. Attach listeners to the event 24 | sources in some other part of the code base. 25 | 26 | This decouples the event definition in the DOM from your event 27 | listeners in your application code. 28 | 29 | Also see [`html-delegator`](https://github.com/Raynos/html-delegator) 30 | for the same idea using html `data-` attributes. 31 | 32 | ## Example 33 | 34 | ```html 35 |
36 |
bar
37 |
baz
38 |
39 | ``` 40 | 41 | ```js 42 | var document = require("global/document") 43 | var Delegator = require("dom-delegator") 44 | var EventEmitter = require("events").EventEmitter 45 | 46 | var del = Delegator() 47 | var emitter = EventEmitter() 48 | emitter.on('textClicked', function (value) { 49 | // either 'bar' or 'baz' depending on which 50 | // `
` was clicked 51 | console.log("doSomething", value.type) 52 | }) 53 | 54 | var elem = document.querySelector(".foo") 55 | 56 | // add individual elems. (in a different file?) 57 | del.addEventListener(elem.querySelector(".bar"), "click", function (ev) { 58 | emmitter.emit('textClicked', { type: 'bar' }) 59 | }) 60 | del.addEventListener(elem.querySelector(".baz"), "click", function (ev) { 61 | emitter.emit('textClicked', { type: 'baz' }) 62 | }) 63 | ``` 64 | 65 | ## Example (global listeners) 66 | 67 | Sometimes you don't want to add events bound to an element but 68 | instead listen to them globally. 69 | 70 | ```js 71 | var Delegator = require("dom-delegator") 72 | 73 | var d = Delegator() 74 | d.addGlobalEventListener("keydown", function (ev) { 75 | // hit for every global key press 76 | // can implement keyboard shortcuts 77 | 78 | 79 | }) 80 | 81 | d.addEventListener(document.documentElement, "keydown", function (ev) { 82 | // hit for every keydown that is not captured 83 | // by an element listener lower in the tree 84 | 85 | // by default dom-delegator does not bubble events up 86 | // to other listeners on parent nodes 87 | 88 | // you can use global event listeners to intercept everything 89 | // even if there are listeners lower in the tree 90 | }) 91 | ``` 92 | 93 | ## Installation 94 | 95 | `npm install dom-delegator` 96 | 97 | ## Contributors 98 | 99 | - Raynos 100 | 101 | ## MIT Licenced 102 | 103 | [1]: https://secure.travis-ci.org/Raynos/dom-delegator.png 104 | [2]: https://travis-ci.org/Raynos/dom-delegator 105 | [3]: https://badge.fury.io/js/dom-delegator.png 106 | [4]: https://badge.fury.io/js/dom-delegator 107 | [5]: https://coveralls.io/repos/Raynos/dom-delegator/badge.png 108 | [6]: https://coveralls.io/r/Raynos/dom-delegator 109 | [7]: https://gemnasium.com/Raynos/dom-delegator.png 110 | [8]: https://gemnasium.com/Raynos/dom-delegator 111 | [9]: https://david-dm.org/Raynos/dom-delegator.png 112 | [10]: https://david-dm.org/Raynos/dom-delegator 113 | [11]: https://ci.testling.com/Raynos/dom-delegator.png 114 | [12]: https://ci.testling.com/Raynos/dom-delegator 115 | -------------------------------------------------------------------------------- /add-event.js: -------------------------------------------------------------------------------- 1 | var EvStore = require("ev-store") 2 | 3 | module.exports = addEvent 4 | 5 | function addEvent(target, type, handler) { 6 | var events = EvStore(target) 7 | var event = events[type] 8 | 9 | if (!event) { 10 | events[type] = handler 11 | } else if (Array.isArray(event)) { 12 | if (event.indexOf(handler) === -1) { 13 | event.push(handler) 14 | } 15 | } else if (event !== handler) { 16 | events[type] = [event, handler] 17 | } 18 | } 19 | -------------------------------------------------------------------------------- /docs.mli: -------------------------------------------------------------------------------- 1 | type ProxyEvent := DOMEvent & { 2 | _rawEvent: DOMEvent, 3 | currentTarget: DOMNode, 4 | preventDefault: () => void, 5 | startPropagation: () => void 6 | } 7 | 8 | type Handle : { 9 | type: "dom-delegator-handle" 10 | } 11 | 12 | type EventHandler := Function | { 13 | handleEvent: Function 14 | } | Handle 15 | 16 | type Delegator := { 17 | target: DOMNode, 18 | listenTo: (eventName: String) => void, 19 | unlistenTo: (eventName: String) => void, 20 | 21 | addEventListener: (DOMNode, String, EventHandler) => void, 22 | removeEventListener: (DOMNode, String, EventHandler) => void, 23 | addGlobalEventListener: (String, EventHandler) => void, 24 | removeGlobalEventListener: (String, EventHandler) => void 25 | 26 | allocateHandle : (fn: (T) => void) => Handle, 27 | transformHandle : ( 28 | handle: Handle, 29 | lambda: (T, broadcast: (S) => void) => void 30 | ) => Handle 31 | } 32 | 33 | dom-delegator := (opts?: { 34 | defaultEvents?: Boolean, 35 | document?: Document 36 | }) => Delegator 37 | 38 | dom-delegator/dom-delegator := (doc?: Document) => Delegator 39 | 40 | dom-delegator/add-event := ( 41 | target: DOMNode, 42 | type: String, 43 | fn: EventHandler 44 | ) => void 45 | 46 | dom-delegator/proxy-event := (DOMEvent) => ProxyEvent 47 | 48 | dom-delegator/remove-event := ( 49 | target: DOMNode, 50 | type: String, 51 | fn: EventHandler 52 | ) => void 53 | -------------------------------------------------------------------------------- /dom-delegator.js: -------------------------------------------------------------------------------- 1 | var globalDocument = require("global/document") 2 | var EvStore = require("ev-store") 3 | var createStore = require("weakmap-shim/create-store") 4 | 5 | var addEvent = require("./add-event.js") 6 | var removeEvent = require("./remove-event.js") 7 | var ProxyEvent = require("./proxy-event.js") 8 | 9 | var HANDLER_STORE = createStore() 10 | 11 | module.exports = DOMDelegator 12 | 13 | function DOMDelegator(document) { 14 | if (!(this instanceof DOMDelegator)) { 15 | return new DOMDelegator(document); 16 | } 17 | 18 | document = document || globalDocument 19 | 20 | this.target = document.documentElement 21 | this.events = {} 22 | this.rawEventListeners = {} 23 | this.globalListeners = {} 24 | } 25 | 26 | DOMDelegator.prototype.addEventListener = addEvent 27 | DOMDelegator.prototype.removeEventListener = removeEvent 28 | 29 | DOMDelegator.allocateHandle = 30 | function allocateHandle(func) { 31 | var handle = new Handle() 32 | 33 | HANDLER_STORE(handle).func = func; 34 | 35 | return handle 36 | } 37 | 38 | DOMDelegator.transformHandle = 39 | function transformHandle(handle, broadcast) { 40 | var func = HANDLER_STORE(handle).func 41 | 42 | return this.allocateHandle(function (ev) { 43 | broadcast(ev, func); 44 | }) 45 | } 46 | 47 | DOMDelegator.prototype.addGlobalEventListener = 48 | function addGlobalEventListener(eventName, fn) { 49 | var listeners = this.globalListeners[eventName] || []; 50 | if (listeners.indexOf(fn) === -1) { 51 | listeners.push(fn) 52 | } 53 | 54 | this.globalListeners[eventName] = listeners; 55 | } 56 | 57 | DOMDelegator.prototype.removeGlobalEventListener = 58 | function removeGlobalEventListener(eventName, fn) { 59 | var listeners = this.globalListeners[eventName] || []; 60 | 61 | var index = listeners.indexOf(fn) 62 | if (index !== -1) { 63 | listeners.splice(index, 1) 64 | } 65 | } 66 | 67 | DOMDelegator.prototype.listenTo = function listenTo(eventName) { 68 | if (!(eventName in this.events)) { 69 | this.events[eventName] = 0; 70 | } 71 | 72 | this.events[eventName]++; 73 | 74 | if (this.events[eventName] !== 1) { 75 | return 76 | } 77 | 78 | var listener = this.rawEventListeners[eventName] 79 | if (!listener) { 80 | listener = this.rawEventListeners[eventName] = 81 | createHandler(eventName, this) 82 | } 83 | 84 | this.target.addEventListener(eventName, listener, true) 85 | } 86 | 87 | DOMDelegator.prototype.unlistenTo = function unlistenTo(eventName) { 88 | if (!(eventName in this.events)) { 89 | this.events[eventName] = 0; 90 | } 91 | 92 | if (this.events[eventName] === 0) { 93 | throw new Error("already unlistened to event."); 94 | } 95 | 96 | this.events[eventName]--; 97 | 98 | if (this.events[eventName] !== 0) { 99 | return 100 | } 101 | 102 | var listener = this.rawEventListeners[eventName] 103 | 104 | if (!listener) { 105 | throw new Error("dom-delegator#unlistenTo: cannot " + 106 | "unlisten to " + eventName) 107 | } 108 | 109 | this.target.removeEventListener(eventName, listener, true) 110 | } 111 | 112 | function createHandler(eventName, delegator) { 113 | var globalListeners = delegator.globalListeners; 114 | var delegatorTarget = delegator.target; 115 | 116 | return handler 117 | 118 | function handler(ev) { 119 | var globalHandlers = globalListeners[eventName] || [] 120 | 121 | if (globalHandlers.length > 0) { 122 | var globalEvent = new ProxyEvent(ev); 123 | globalEvent.currentTarget = delegatorTarget; 124 | callListeners(globalHandlers, globalEvent) 125 | } 126 | 127 | findAndInvokeListeners(ev.target, ev, eventName) 128 | } 129 | } 130 | 131 | function findAndInvokeListeners(elem, ev, eventName) { 132 | var listener = getListener(elem, eventName) 133 | 134 | if (listener && listener.handlers.length > 0) { 135 | var listenerEvent = new ProxyEvent(ev); 136 | listenerEvent.currentTarget = listener.currentTarget 137 | callListeners(listener.handlers, listenerEvent) 138 | 139 | if (listenerEvent._bubbles) { 140 | var nextTarget = listener.currentTarget.parentNode 141 | findAndInvokeListeners(nextTarget, ev, eventName) 142 | } 143 | } 144 | } 145 | 146 | function getListener(target, type) { 147 | // terminate recursion if parent is `null` 148 | if (target === null || typeof target === "undefined") { 149 | return null 150 | } 151 | 152 | var events = EvStore(target) 153 | // fetch list of handler fns for this event 154 | var handler = events[type] 155 | var allHandler = events.event 156 | 157 | if (!handler && !allHandler) { 158 | return getListener(target.parentNode, type) 159 | } 160 | 161 | var handlers = [].concat(handler || [], allHandler || []) 162 | return new Listener(target, handlers) 163 | } 164 | 165 | function callListeners(handlers, ev) { 166 | handlers.forEach(function (handler) { 167 | if (typeof handler === "function") { 168 | handler(ev) 169 | } else if (typeof handler.handleEvent === "function") { 170 | handler.handleEvent(ev) 171 | } else if (handler.type === "dom-delegator-handle") { 172 | HANDLER_STORE(handler).func(ev) 173 | } else { 174 | throw new Error("dom-delegator: unknown handler " + 175 | "found: " + JSON.stringify(handlers)); 176 | } 177 | }) 178 | } 179 | 180 | function Listener(target, handlers) { 181 | this.currentTarget = target 182 | this.handlers = handlers 183 | } 184 | 185 | function Handle() { 186 | this.type = "dom-delegator-handle" 187 | } 188 | -------------------------------------------------------------------------------- /index.js: -------------------------------------------------------------------------------- 1 | var Individual = require("individual") 2 | var cuid = require("cuid") 3 | var globalDocument = require("global/document") 4 | 5 | var DOMDelegator = require("./dom-delegator.js") 6 | 7 | var versionKey = "13" 8 | var cacheKey = "__DOM_DELEGATOR_CACHE@" + versionKey 9 | var cacheTokenKey = "__DOM_DELEGATOR_CACHE_TOKEN@" + versionKey 10 | var delegatorCache = Individual(cacheKey, { 11 | delegators: {} 12 | }) 13 | var commonEvents = [ 14 | "blur", "change", "click", "contextmenu", "dblclick", 15 | "error","focus", "focusin", "focusout", "input", "keydown", 16 | "keypress", "keyup", "load", "mousedown", "mouseup", 17 | "resize", "select", "submit", "touchcancel", 18 | "touchend", "touchstart", "unload" 19 | ] 20 | 21 | /* Delegator is a thin wrapper around a singleton `DOMDelegator` 22 | instance. 23 | 24 | Only one DOMDelegator should exist because we do not want 25 | duplicate event listeners bound to the DOM. 26 | 27 | `Delegator` will also `listenTo()` all events unless 28 | every caller opts out of it 29 | */ 30 | module.exports = Delegator 31 | 32 | function Delegator(opts) { 33 | opts = opts || {} 34 | var document = opts.document || globalDocument 35 | 36 | var cacheKey = document[cacheTokenKey] 37 | 38 | if (!cacheKey) { 39 | cacheKey = 40 | document[cacheTokenKey] = cuid() 41 | } 42 | 43 | var delegator = delegatorCache.delegators[cacheKey] 44 | 45 | if (!delegator) { 46 | delegator = delegatorCache.delegators[cacheKey] = 47 | new DOMDelegator(document) 48 | } 49 | 50 | if (opts.defaultEvents !== false) { 51 | for (var i = 0; i < commonEvents.length; i++) { 52 | delegator.listenTo(commonEvents[i]) 53 | } 54 | } 55 | 56 | return delegator 57 | } 58 | 59 | Delegator.allocateHandle = DOMDelegator.allocateHandle; 60 | Delegator.transformHandle = DOMDelegator.transformHandle; 61 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "dom-delegator", 3 | "version": "13.1.0", 4 | "description": "Decorate elements with delegated events", 5 | "keywords": [], 6 | "author": "Raynos ", 7 | "repository": "git://github.com/Raynos/dom-delegator.git", 8 | "main": "index", 9 | "homepage": "https://github.com/Raynos/dom-delegator", 10 | "contributors": [ 11 | { 12 | "name": "Raynos" 13 | } 14 | ], 15 | "bugs": { 16 | "url": "https://github.com/Raynos/dom-delegator/issues", 17 | "email": "raynos2@gmail.com" 18 | }, 19 | "dependencies": { 20 | "cuid": "^1.2.4", 21 | "ev-store": "^7.0.0", 22 | "global": "^4.2.1", 23 | "individual": "^2.0.0", 24 | "inherits": "^2.0.1", 25 | "weakmap-shim": "^1.0.0", 26 | "xtend": "^2.2.0" 27 | }, 28 | "devDependencies": { 29 | "cuid": "^1.2.4", 30 | "event-sinks": "~1.0.1", 31 | "istanbul": "^0.2.6", 32 | "jshint": "^2.5.0", 33 | "pre-commit": "0.0.5", 34 | "run-browser": "^1.3.1", 35 | "synthetic-dom-events": "git://github.com/Raynos/synthetic-dom-events", 36 | "tap-spec": "^0.1.9", 37 | "tape": "^2.12.3" 38 | }, 39 | "licenses": [ 40 | { 41 | "type": "MIT", 42 | "url": "http://github.com/Raynos/dom-delegator/raw/master/LICENSE" 43 | } 44 | ], 45 | "scripts": { 46 | "test": "jshint . && node ./test/index.js | tap-spec", 47 | "browser": "run-browser ./test/index.js", 48 | "phantom": "run-browser ./test/index.js -b | tap-spec", 49 | "cover": "istanbul cover --print detail ./test/index.js", 50 | "view-cover": "istanbul report html && google-chrome ./coverage/index.html" 51 | }, 52 | "testling": { 53 | "files": "test/index.js", 54 | "browsers": [ 55 | "ie/8..latest", 56 | "firefox/16..latest", 57 | "firefox/nightly", 58 | "chrome/22..latest", 59 | "chrome/canary", 60 | "opera/12..latest", 61 | "opera/next", 62 | "safari/5.1..latest", 63 | "ipad/6.0..latest", 64 | "iphone/6.0..latest", 65 | "android-browser/4.2..latest" 66 | ] 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /proxy-event.js: -------------------------------------------------------------------------------- 1 | var inherits = require("inherits") 2 | 3 | var ALL_PROPS = [ 4 | "altKey", "bubbles", "cancelable", "ctrlKey", 5 | "eventPhase", "metaKey", "relatedTarget", "shiftKey", 6 | "target", "timeStamp", "type", "view", "which" 7 | ] 8 | var KEY_PROPS = ["char", "charCode", "key", "keyCode"] 9 | var MOUSE_PROPS = [ 10 | "button", "buttons", "clientX", "clientY", "layerX", 11 | "layerY", "offsetX", "offsetY", "pageX", "pageY", 12 | "screenX", "screenY", "toElement" 13 | ] 14 | 15 | var rkeyEvent = /^key|input/ 16 | var rmouseEvent = /^(?:mouse|pointer|contextmenu)|click/ 17 | 18 | module.exports = ProxyEvent 19 | 20 | function ProxyEvent(ev) { 21 | if (!(this instanceof ProxyEvent)) { 22 | return new ProxyEvent(ev) 23 | } 24 | 25 | if (rkeyEvent.test(ev.type)) { 26 | return new KeyEvent(ev) 27 | } else if (rmouseEvent.test(ev.type)) { 28 | return new MouseEvent(ev) 29 | } 30 | 31 | for (var i = 0; i < ALL_PROPS.length; i++) { 32 | var propKey = ALL_PROPS[i] 33 | this[propKey] = ev[propKey] 34 | } 35 | 36 | this._rawEvent = ev 37 | this._bubbles = false; 38 | } 39 | 40 | ProxyEvent.prototype.preventDefault = function () { 41 | this._rawEvent.preventDefault() 42 | } 43 | 44 | ProxyEvent.prototype.startPropagation = function () { 45 | this._bubbles = true; 46 | } 47 | 48 | function MouseEvent(ev) { 49 | for (var i = 0; i < ALL_PROPS.length; i++) { 50 | var propKey = ALL_PROPS[i] 51 | this[propKey] = ev[propKey] 52 | } 53 | 54 | for (var j = 0; j < MOUSE_PROPS.length; j++) { 55 | var mousePropKey = MOUSE_PROPS[j] 56 | this[mousePropKey] = ev[mousePropKey] 57 | } 58 | 59 | this._rawEvent = ev 60 | } 61 | 62 | inherits(MouseEvent, ProxyEvent) 63 | 64 | function KeyEvent(ev) { 65 | for (var i = 0; i < ALL_PROPS.length; i++) { 66 | var propKey = ALL_PROPS[i] 67 | this[propKey] = ev[propKey] 68 | } 69 | 70 | for (var j = 0; j < KEY_PROPS.length; j++) { 71 | var keyPropKey = KEY_PROPS[j] 72 | this[keyPropKey] = ev[keyPropKey] 73 | } 74 | 75 | this._rawEvent = ev 76 | } 77 | 78 | inherits(KeyEvent, ProxyEvent) 79 | -------------------------------------------------------------------------------- /remove-event.js: -------------------------------------------------------------------------------- 1 | var EvStore = require("ev-store") 2 | 3 | module.exports = removeEvent 4 | 5 | function removeEvent(target, type, handler) { 6 | var events = EvStore(target) 7 | var event = events[type] 8 | 9 | if (!event) { 10 | return 11 | } else if (Array.isArray(event)) { 12 | var index = event.indexOf(handler) 13 | if (index !== -1) { 14 | event.splice(index, 1) 15 | } 16 | } else if (event === handler) { 17 | events[type] = null 18 | } 19 | } 20 | -------------------------------------------------------------------------------- /test/dom-add-event.js: -------------------------------------------------------------------------------- 1 | var test = require("tape") 2 | var cuid = require("cuid") 3 | var setImmediate = require("timers").setImmediate 4 | var document = require("global/document") 5 | var EventSinks = require("event-sinks/geval") 6 | 7 | var h = require("./lib/h.js") 8 | var addSinkEvent = require("./lib/add-sink-event.js") 9 | var createEvent = require("./lib/create-event.js") 10 | 11 | var Delegator = require("../index.js") 12 | 13 | test("Delegator is a function", function (assert) { 14 | assert.equal(typeof Delegator, "function") 15 | assert.end() 16 | }) 17 | 18 | test("can listen to events", function (assert) { 19 | var elem = h("div") 20 | document.body.appendChild(elem) 21 | 22 | var d = Delegator(elem) 23 | var events = EventSinks(d.id, ["foo"]) 24 | var called = 0 25 | var id = cuid() 26 | 27 | addSinkEvent(elem, "click", events.sinks.foo, { 28 | id: id 29 | }) 30 | 31 | events.foo(function (value) { 32 | called++ 33 | 34 | assert.equal(value.id, id) 35 | }) 36 | 37 | var ev = createEvent("click") 38 | elem.dispatchEvent(ev) 39 | 40 | setImmediate(function () { 41 | assert.equal(called, 1) 42 | 43 | document.body.removeChild(elem) 44 | assert.end() 45 | }) 46 | }) 47 | 48 | test("can set different data on same sink", function (assert) { 49 | var elem = h("foo", [ 50 | h("bar"), 51 | h("baz") 52 | ]) 53 | document.body.appendChild(elem) 54 | 55 | var d = Delegator(elem) 56 | var events = EventSinks(d.id, ["foo"]) 57 | var foo = events.sinks.foo 58 | var values = [] 59 | 60 | addSinkEvent(elem.childNodes[0], "click", foo, { 61 | name: "bar" 62 | }) 63 | 64 | addSinkEvent(elem.childNodes[1], "click", foo, { 65 | name: "baz" 66 | }) 67 | 68 | events.foo(function (value) { 69 | values.push(value) 70 | }) 71 | 72 | var ev = createEvent("click") 73 | elem.childNodes[0].dispatchEvent(ev) 74 | 75 | var ev2 = createEvent("click") 76 | elem.childNodes[1].dispatchEvent(ev2) 77 | 78 | setImmediate(function () { 79 | assert.equal(values.length, 2) 80 | assert.equal(values[0].name, "bar") 81 | assert.equal(values[1].name, "baz") 82 | 83 | document.body.removeChild(elem) 84 | assert.end() 85 | }) 86 | }) 87 | 88 | test("can register multiple sinks", function (assert) { 89 | var elem = h("foo", [ 90 | h("bar"), 91 | h("baz") 92 | ]) 93 | document.body.appendChild(elem) 94 | 95 | var d = Delegator(elem) 96 | var events = EventSinks(d.id, ["bar", "baz"]) 97 | 98 | var bar = events.sinks.bar, baz = events.sinks.baz 99 | var hash = {} 100 | 101 | addSinkEvent(elem.childNodes[0], "click", bar, { 102 | name: "baz" 103 | }) 104 | 105 | addSinkEvent(elem.childNodes[1], "click", baz, { 106 | name: "bar" 107 | }) 108 | 109 | events.bar(function (value) { 110 | hash.bar = value 111 | }) 112 | 113 | events.baz(function (value) { 114 | hash.baz = value 115 | }) 116 | 117 | var ev = createEvent("click") 118 | elem.childNodes[0].dispatchEvent(ev) 119 | 120 | var ev2 = createEvent("click") 121 | elem.childNodes[1].dispatchEvent(ev2) 122 | 123 | setImmediate(function () { 124 | assert.ok("bar" in hash) 125 | assert.ok("baz" in hash) 126 | assert.equal(hash.bar.name, "baz") 127 | assert.equal(hash.baz.name, "bar") 128 | 129 | document.body.removeChild(elem) 130 | assert.end() 131 | }) 132 | }) 133 | -------------------------------------------------------------------------------- /test/dom-add-multiple.js: -------------------------------------------------------------------------------- 1 | var test = require("tape") 2 | var setImmediate = require("timers").setImmediate 3 | var document = require("global/document") 4 | var Sink = require("event-sinks/sink") 5 | 6 | var h = require("./lib/h.js") 7 | var addSinkEvent = require("./lib/add-sink-event.js") 8 | var createEvent = require("./lib/create-event.js") 9 | 10 | var Delegator = require("../index.js") 11 | 12 | test("adding multiple listeners", function (assert) { 13 | var elem = h("div") 14 | document.body.appendChild(elem) 15 | 16 | var d = Delegator(elem) 17 | var values = [] 18 | var sink = Sink(d.id, "", function (value) { 19 | values.push(value) 20 | }) 21 | 22 | addSinkEvent(elem, "event", sink, { key: "foo" }) 23 | addSinkEvent(elem, "event", sink, { key: "bar" }) 24 | 25 | var ev = createEvent("click") 26 | elem.dispatchEvent(ev) 27 | 28 | setImmediate(function () { 29 | assert.equal(values.length, 2) 30 | assert.equal(values[0].key, "foo") 31 | assert.equal(values[1].key, "bar") 32 | 33 | document.body.removeChild(elem) 34 | assert.end() 35 | }) 36 | }) 37 | 38 | test("add multiple listeners of different types", function (assert) { 39 | var elem = h("div") 40 | document.body.appendChild(elem) 41 | 42 | var d = Delegator(elem) 43 | var values = [] 44 | var sink = Sink(d.id, "", function (value) { 45 | values.push(value) 46 | }) 47 | 48 | addSinkEvent(elem, "event", sink, { key: "foo" }) 49 | addSinkEvent(elem, "click", sink, { key: "bar" }) 50 | 51 | var ev = createEvent("click") 52 | elem.dispatchEvent(ev) 53 | 54 | setImmediate(function () { 55 | assert.equal(values.length, 2) 56 | assert.equal(values[0].key, "bar") 57 | assert.equal(values[1].key, "foo") 58 | 59 | document.body.removeChild(elem) 60 | assert.end() 61 | }) 62 | }) 63 | -------------------------------------------------------------------------------- /test/dom-data-events.js: -------------------------------------------------------------------------------- 1 | var test = require("tape") 2 | var setImmediate = require("timers").setImmediate 3 | var document = require("global/document") 4 | var EvStore = require("ev-store") 5 | 6 | var h = require("./lib/h.js") 7 | var createEvent = require("./lib/create-event.js") 8 | 9 | var Delegator = require("../index.js") 10 | 11 | test("setting event listeners with ev-store directly", function (assert) { 12 | var elem = h("div") 13 | document.body.appendChild(elem) 14 | 15 | Delegator() 16 | var values = [] 17 | 18 | EvStore(elem).click = function (ev) { 19 | values.push(ev) 20 | } 21 | 22 | var ev = createEvent("click") 23 | elem.dispatchEvent(ev) 24 | 25 | setImmediate(function () { 26 | assert.equal(values.length, 1) 27 | assert.equal(values[0].target, elem) 28 | assert.equal(typeof values[0].preventDefault, "function") 29 | assert.equal(typeof values[0].startPropagation, "function") 30 | 31 | document.body.removeChild(elem) 32 | assert.end() 33 | }) 34 | }) 35 | 36 | test("setting an id'd event handler", function (assert) { 37 | var elem = h("div") 38 | document.body.appendChild(elem) 39 | 40 | var d = Delegator() 41 | var values = [] 42 | var eventValues = [] 43 | 44 | var handler = { 45 | handleEvent: function (ev) { 46 | values.push(ev) 47 | }, 48 | id: d.id 49 | } 50 | var eventHandler = { 51 | handleEvent: function (ev) { 52 | eventValues.push(ev) 53 | }, 54 | id: d.id 55 | } 56 | 57 | EvStore(elem).click = handler 58 | EvStore(elem).event = eventHandler 59 | 60 | var ev = createEvent("click") 61 | elem.dispatchEvent(ev) 62 | 63 | setImmediate(function () { 64 | assert.equal(values.length, 1) 65 | assert.equal(values[0].target, elem) 66 | 67 | assert.equal(eventValues.length, 1) 68 | assert.equal(eventValues[0].target, elem) 69 | assert.equal(eventValues[0].type, "click") 70 | 71 | document.body.removeChild(elem) 72 | assert.end() 73 | }) 74 | }) 75 | 76 | test("setting data-event to array", function (assert) { 77 | var elem = h("div") 78 | document.body.appendChild(elem) 79 | 80 | Delegator() 81 | var values = [] 82 | 83 | EvStore(elem).click = [function (ev) { 84 | values.push(ev) 85 | }, function (ev) { 86 | values.push(ev) 87 | }] 88 | 89 | var ev = createEvent("click") 90 | elem.dispatchEvent(ev) 91 | 92 | setImmediate(function () { 93 | assert.equal(values.length, 2) 94 | assert.equal(values[0], values[1]) 95 | assert.equal(values[0].target, elem) 96 | 97 | document.body.removeChild(elem) 98 | assert.end() 99 | }) 100 | }) 101 | 102 | test("data-event to array of id'd handlers", function (assert) { 103 | var elem = h("div") 104 | document.body.appendChild(elem) 105 | 106 | var d = Delegator() 107 | var changes = [] 108 | var submits = [] 109 | 110 | var submitHandler = { 111 | handleEvent: function (ev) { 112 | submits.push(ev) 113 | }, 114 | id: d.id 115 | } 116 | var changeHandler = { 117 | handleEvent: function (ev) { 118 | changes.push(ev) 119 | }, 120 | id: d.id 121 | } 122 | 123 | EvStore(elem).event = [submitHandler, changeHandler] 124 | 125 | var ev = createEvent("click") 126 | elem.dispatchEvent(ev) 127 | 128 | setImmediate(function () { 129 | assert.equal(submits.length, 1) 130 | assert.equal(changes.length, 1) 131 | 132 | assert.equal(submits[0], changes[0]) 133 | assert.equal(submits[0].target, elem) 134 | 135 | document.body.removeChild(elem) 136 | assert.end() 137 | }) 138 | }) 139 | -------------------------------------------------------------------------------- /test/dom-delegator.js: -------------------------------------------------------------------------------- 1 | var test = require("tape") 2 | var setImmediate = require("timers").setImmediate 3 | var document = require("global/document") 4 | 5 | var h = require("./lib/h.js") 6 | var createEvent = require("./lib/create-event.js") 7 | 8 | var addEvent = require("../add-event.js") 9 | var Delegator = require("../index.js") 10 | 11 | test("delegator with no args", function (assert) { 12 | var elem = h("div") 13 | document.body.appendChild(elem) 14 | 15 | Delegator() 16 | var values = [] 17 | 18 | addEvent(elem, "click", function (ev) { 19 | values.push(ev) 20 | }) 21 | 22 | var ev = createEvent("click") 23 | elem.dispatchEvent(ev) 24 | 25 | setImmediate(function () { 26 | assert.equal(values.length, 1) 27 | assert.equal(values[0].target, elem) 28 | 29 | document.body.removeChild(elem) 30 | assert.end() 31 | }) 32 | }) 33 | 34 | test("delegator with addEventListener", function (assert) { 35 | var elem = h("div") 36 | document.body.appendChild(elem) 37 | 38 | var d = Delegator() 39 | var values = [] 40 | 41 | d.addEventListener(elem, "click", function (ev) { 42 | values.push(ev) 43 | }) 44 | 45 | var ev = createEvent("click") 46 | elem.dispatchEvent(ev) 47 | 48 | setImmediate(function () { 49 | assert.equal(values.length, 1) 50 | assert.equal(values[0].target, elem) 51 | 52 | document.body.removeChild(elem) 53 | assert.end() 54 | }) 55 | }) 56 | 57 | test("delegator with no args", function (assert) { 58 | var elem = h("div") 59 | document.body.appendChild(elem) 60 | 61 | var d = Delegator({ 62 | defaultEvents: false 63 | }) 64 | d.listenTo("click") 65 | var values = [] 66 | 67 | addEvent(elem, "click", function (ev) { 68 | values.push(ev) 69 | }) 70 | 71 | var ev = createEvent("click") 72 | elem.dispatchEvent(ev) 73 | 74 | setImmediate(function () { 75 | assert.equal(values.length, 1) 76 | assert.equal(values[0].target, elem) 77 | 78 | document.body.removeChild(elem) 79 | assert.end() 80 | }) 81 | }) 82 | -------------------------------------------------------------------------------- /test/dom-duplicate-event.js: -------------------------------------------------------------------------------- 1 | var test = require("tape") 2 | var setImmediate = require("timers").setImmediate 3 | var document = require("global/document") 4 | 5 | var h = require("./lib/h.js") 6 | var createEvent = require("./lib/create-event.js") 7 | 8 | var removeEvent = require("../remove-event.js") 9 | var addEvent = require("../add-event.js") 10 | var Delegator = require("../index.js") 11 | 12 | test("adding same function twice", function (assert) { 13 | var elem = h("div") 14 | document.body.appendChild(elem) 15 | 16 | Delegator() 17 | var values = [] 18 | 19 | var fn = function (ev) { 20 | values.push(ev) 21 | } 22 | 23 | addEvent(elem, "click", fn) 24 | addEvent(elem, "click", fn) 25 | 26 | var ev = createEvent("click") 27 | elem.dispatchEvent(ev) 28 | 29 | setImmediate(function () { 30 | assert.equal(values.length, 1) 31 | assert.equal(values[0] && values[0].target, elem) 32 | 33 | document.body.removeChild(elem) 34 | assert.end() 35 | }) 36 | }) 37 | 38 | test("adding function twice for multi events", function (assert) { 39 | var elem = h("div") 40 | document.body.appendChild(elem) 41 | 42 | Delegator() 43 | var values = [] 44 | 45 | var fn = function (ev) { 46 | values.push(["fn", ev]) 47 | } 48 | 49 | var fn2 = function (ev) { 50 | values.push(["fn2", ev]) 51 | } 52 | 53 | addEvent(elem, "click", fn2) 54 | addEvent(elem, "click", fn2) 55 | 56 | addEvent(elem, "click", fn) 57 | addEvent(elem, "click", fn) 58 | 59 | var ev = createEvent("click") 60 | elem.dispatchEvent(ev) 61 | 62 | setImmediate(function () { 63 | assert.equal(values.length, 2) 64 | assert.equal(values[0][0], "fn2") 65 | assert.equal(values[1][0], "fn") 66 | assert.equal(values[0] && values[0][1].target, elem) 67 | 68 | document.body.removeChild(elem) 69 | assert.end() 70 | }) 71 | }) 72 | 73 | test("adding same event handler twice", function (assert) { 74 | var elem = h("div") 75 | document.body.appendChild(elem) 76 | 77 | Delegator() 78 | var values = [] 79 | 80 | var handler = { 81 | handleEvent: function (ev) { 82 | values.push(ev) 83 | } 84 | } 85 | 86 | addEvent(elem, "click", handler) 87 | addEvent(elem, "click", handler) 88 | 89 | var ev = createEvent("click") 90 | elem.dispatchEvent(ev) 91 | 92 | setImmediate(function () { 93 | assert.equal(values.length, 1) 94 | assert.equal(values[0] && values[0].target, elem) 95 | 96 | document.body.removeChild(elem) 97 | assert.end() 98 | }) 99 | }) 100 | 101 | test("double removing multiple events", function (assert) { 102 | var elem = h("div") 103 | document.body.appendChild(elem) 104 | 105 | Delegator() 106 | var values = [] 107 | 108 | var fn = function (ev) { 109 | values.push(["fn", ev]) 110 | } 111 | 112 | var fn2 = function (ev) { 113 | values.push(["fn2", ev]) 114 | } 115 | 116 | addEvent(elem, "click", fn2) 117 | addEvent(elem, "click", fn2) 118 | 119 | addEvent(elem, "click", fn) 120 | addEvent(elem, "click", fn) 121 | removeEvent(elem, "click", fn) 122 | removeEvent(elem, "click", fn) 123 | 124 | var ev = createEvent("click") 125 | elem.dispatchEvent(ev) 126 | 127 | setImmediate(function () { 128 | assert.equal(values.length, 1) 129 | assert.equal(values[0][0], "fn2") 130 | assert.equal(values[0] && values[0][1].target, elem) 131 | 132 | document.body.removeChild(elem) 133 | assert.end() 134 | }) 135 | }) 136 | -------------------------------------------------------------------------------- /test/dom-global-listeners.js: -------------------------------------------------------------------------------- 1 | var test = require("tape") 2 | var setImmediate = require("timers").setImmediate 3 | var document = require("global/document") 4 | 5 | var createEvent = require("./lib/create-event.js") 6 | 7 | var Delegator = require("../index.js") 8 | 9 | test("global listeners", function (assert) { 10 | var d = Delegator() 11 | var values = [] 12 | 13 | function fn(ev) { 14 | values.push(["fn", ev]) 15 | } 16 | 17 | function fn2(ev) { 18 | values.push(["fn2", ev]) 19 | } 20 | 21 | d.addGlobalEventListener("click", fn) 22 | d.addGlobalEventListener("click", fn2) 23 | 24 | 25 | var ev2 = createEvent("click") 26 | document.documentElement.dispatchEvent(ev2) 27 | 28 | d.removeGlobalEventListener("click", fn) 29 | 30 | var ev3 = createEvent("click") 31 | document.documentElement.dispatchEvent(ev3) 32 | 33 | setImmediate(function () { 34 | assert.equal(values.length, 3) 35 | assert.equal(values[0][0], "fn") 36 | assert.equal(values[1][0], "fn2") 37 | assert.equal(values[2][0], "fn2") 38 | 39 | assert.end() 40 | }) 41 | }) 42 | 43 | test("duplicates", function (assert) { 44 | var d = Delegator() 45 | var values = [] 46 | 47 | function fn(ev) { 48 | values.push(["fn", ev]) 49 | } 50 | 51 | function fn2(ev) { 52 | values.push(["fn2", ev]) 53 | } 54 | 55 | d.addGlobalEventListener("click", fn) 56 | d.addGlobalEventListener("click", fn) 57 | d.addGlobalEventListener("click", fn2) 58 | d.addGlobalEventListener("click", fn2) 59 | 60 | 61 | var ev2 = createEvent("click") 62 | document.documentElement.dispatchEvent(ev2) 63 | 64 | setImmediate(function () { 65 | assert.equal(values.length, 2) 66 | assert.equal(values[0][0], "fn") 67 | assert.equal(values[1][0], "fn2") 68 | 69 | assert.end() 70 | }) 71 | }) 72 | 73 | test("duplicate removes", function (assert) { 74 | var d = Delegator() 75 | var values = [] 76 | 77 | function fn(ev) { 78 | values.push(["fn", ev]) 79 | } 80 | 81 | function fn2(ev) { 82 | values.push(["fn2", ev]) 83 | } 84 | 85 | d.addGlobalEventListener("click", fn) 86 | d.addGlobalEventListener("click", fn2) 87 | 88 | 89 | var ev2 = createEvent("click") 90 | document.documentElement.dispatchEvent(ev2) 91 | 92 | assert.doesNotThrow(function () { 93 | d.removeGlobalEventListener("badevent", fn) 94 | }) 95 | 96 | d.removeGlobalEventListener("click", fn) 97 | d.removeGlobalEventListener("click", fn) 98 | 99 | var ev3 = createEvent("click") 100 | document.documentElement.dispatchEvent(ev3) 101 | 102 | setImmediate(function () { 103 | assert.equal(values.length, 3) 104 | assert.equal(values[0][0], "fn") 105 | assert.equal(values[1][0], "fn2") 106 | assert.equal(values[2][0], "fn2") 107 | 108 | assert.end() 109 | }) 110 | }) 111 | -------------------------------------------------------------------------------- /test/dom-handle.js: -------------------------------------------------------------------------------- 1 | var test = require("tape") 2 | var document = require("global/document") 3 | var setImmediate = require("timers").setImmediate 4 | 5 | var h = require("./lib/h.js") 6 | var createEvent = require("./lib/create-event.js") 7 | 8 | var Delegator = require("../index.js") 9 | 10 | test("can listen to handles", function (assert) { 11 | var elem = allocElem() 12 | var d = Delegator() 13 | 14 | var results = [] 15 | 16 | var handle = Delegator.allocateHandle(function (ev) { 17 | results.push(ev) 18 | }) 19 | d.addEventListener(elem, "click", handle) 20 | 21 | dispatchClick(elem, function () { 22 | assert.ok(true) 23 | assert.equal(results.length, 1) 24 | assert.equal(results[0].type, "click") 25 | 26 | freeElem(elem) 27 | assert.end() 28 | }) 29 | }) 30 | 31 | test("can transform a handle", function (assert) { 32 | var elem = allocElem() 33 | var d = Delegator() 34 | 35 | var results = [] 36 | 37 | var handle = Delegator.allocateHandle(function (ev) { 38 | results.push(ev) 39 | }) 40 | 41 | var handle2 = Delegator.transformHandle(handle, function (ev, write) { 42 | write({ foo: "bar", type: ev.type }) 43 | }) 44 | d.addEventListener(elem, "click", handle2) 45 | 46 | dispatchClick(elem, function () { 47 | assert.ok(true) 48 | assert.equal(results.length, 1) 49 | 50 | freeElem(elem) 51 | assert.end() 52 | }) 53 | }) 54 | 55 | test("transform handle respects falsey", function (assert) { 56 | var elem = allocElem() 57 | var d = Delegator() 58 | 59 | var results = [] 60 | 61 | var handle = Delegator.allocateHandle(function (ev) { 62 | results.push(ev) 63 | }) 64 | 65 | var handle2 = Delegator.transformHandle(handle, function (ev) { 66 | return null 67 | }) 68 | d.addEventListener(elem, "click", handle2) 69 | 70 | dispatchClick(elem, function () { 71 | assert.ok(true) 72 | assert.equal(results.length, 0) 73 | 74 | freeElem(elem) 75 | assert.end() 76 | }) 77 | }) 78 | 79 | function allocElem() { 80 | var elem = h("div") 81 | document.body.appendChild(elem) 82 | 83 | return elem 84 | } 85 | 86 | function freeElem(elem) { 87 | document.body.removeChild(elem) 88 | } 89 | 90 | function dispatchClick(elem, cb) { 91 | var ev = createEvent("click") 92 | elem.dispatchEvent(ev) 93 | 94 | setImmediate(cb) 95 | } 96 | -------------------------------------------------------------------------------- /test/dom-propagation.js: -------------------------------------------------------------------------------- 1 | var test = require("tape") 2 | var setImmediate = require("timers").setImmediate 3 | var document = require("global/document") 4 | 5 | var createEvent = require("./lib/create-event.js") 6 | var h = require("./lib/h.js") 7 | 8 | var Delegator = require("../index.js") 9 | 10 | test("multiple listeners and propagation", function (assert) { 11 | var elem = h("div", [ 12 | h("p", [ 13 | h("span") 14 | ]) 15 | ]) 16 | document.body.appendChild(elem) 17 | 18 | var d = Delegator() 19 | var tuples = [] 20 | 21 | d.addEventListener(elem, "click", onParent) 22 | d.addEventListener(elem.childNodes[0], "click", onChild) 23 | d.addEventListener(elem.childNodes[0].childNodes[0], 24 | "click", onGrandChild) 25 | 26 | 27 | var ev = createEvent("click") 28 | elem.childNodes[0].childNodes[0].dispatchEvent(ev) 29 | 30 | setImmediate(function () { 31 | assert.equal(tuples.length, 3) 32 | assert.equal(tuples[0][0], "grandchild") 33 | assert.equal(tuples[1][0], "child") 34 | assert.equal(tuples[2][0], "parent") 35 | 36 | assert.equal(tuples[0][1].currentTarget.tagName, "SPAN") 37 | assert.equal(tuples[1][1].currentTarget.tagName, "P") 38 | assert.equal(tuples[2][1].currentTarget.tagName, "DIV") 39 | 40 | document.body.removeChild(elem) 41 | assert.end() 42 | }) 43 | 44 | function onParent(ev) { 45 | tuples.push(["parent", ev]) 46 | } 47 | function onChild(ev) { 48 | tuples.push(["child", ev]) 49 | ev.startPropagation() 50 | } 51 | function onGrandChild(ev) { 52 | tuples.push(["grandchild", ev]) 53 | ev.startPropagation() 54 | } 55 | }) 56 | 57 | test("multiple listeners and partial propagation", function (assert) { 58 | var elem = h("div", [ 59 | h("p", [ 60 | h("span") 61 | ]) 62 | ]) 63 | document.body.appendChild(elem) 64 | 65 | var d = Delegator() 66 | var tuples = [] 67 | 68 | d.addEventListener(elem, "click", onParent) 69 | d.addEventListener(elem.childNodes[0], "click", onChild) 70 | d.addEventListener(elem.childNodes[0].childNodes[0], 71 | "click", onGrandChild) 72 | 73 | 74 | var ev = createEvent("click") 75 | elem.childNodes[0].childNodes[0].dispatchEvent(ev) 76 | 77 | setImmediate(function () { 78 | assert.equal(tuples.length, 2) 79 | assert.equal(tuples[0][0], "grandchild") 80 | assert.equal(tuples[1][0], "child") 81 | 82 | assert.equal(tuples[0][1].currentTarget.tagName, "SPAN") 83 | assert.equal(tuples[1][1].currentTarget.tagName, "P") 84 | 85 | document.body.removeChild(elem) 86 | assert.end() 87 | }) 88 | 89 | function onParent(ev) { 90 | tuples.push(["parent", ev]) 91 | } 92 | function onChild(ev) { 93 | tuples.push(["child", ev]) 94 | } 95 | function onGrandChild(ev) { 96 | tuples.push(["grandchild", ev]) 97 | ev.startPropagation() 98 | } 99 | }) 100 | 101 | test("multiple listeners and no propagation", function (assert) { 102 | var elem = h("div", [ 103 | h("p", [ 104 | h("span") 105 | ]) 106 | ]) 107 | document.body.appendChild(elem) 108 | 109 | var d = Delegator() 110 | var tuples = [] 111 | 112 | d.addEventListener(elem, "click", onParent) 113 | d.addEventListener(elem.childNodes[0], "click", onChild) 114 | d.addEventListener(elem.childNodes[0].childNodes[0], 115 | "click", onGrandChild) 116 | 117 | 118 | var ev = createEvent("click") 119 | elem.childNodes[0].childNodes[0].dispatchEvent(ev) 120 | 121 | setImmediate(function () { 122 | assert.equal(tuples.length, 1) 123 | assert.equal(tuples[0][0], "grandchild") 124 | 125 | assert.equal(tuples[0][1].currentTarget.tagName, "SPAN") 126 | 127 | document.body.removeChild(elem) 128 | assert.end() 129 | }) 130 | 131 | function onParent(ev) { 132 | tuples.push(["parent", ev]) 133 | } 134 | function onChild(ev) { 135 | tuples.push(["child", ev]) 136 | } 137 | function onGrandChild(ev) { 138 | tuples.push(["grandchild", ev]) 139 | } 140 | }) 141 | -------------------------------------------------------------------------------- /test/dom-remove-event.js: -------------------------------------------------------------------------------- 1 | var test = require("tape") 2 | var setImmediate = require("timers").setImmediate 3 | var document = require("global/document") 4 | 5 | var h = require("./lib/h.js") 6 | var createEvent = require("./lib/create-event.js") 7 | 8 | var addEvent = require("../add-event.js") 9 | var removeEvent = require("../remove-event.js") 10 | var Delegator = require("../index.js") 11 | 12 | test("removing event", function (assert) { 13 | var elem = h("div") 14 | document.body.appendChild(elem) 15 | 16 | Delegator() 17 | var values = [] 18 | 19 | function fn(ev) { 20 | values.push(ev) 21 | } 22 | 23 | addEvent(elem, "click", fn) 24 | removeEvent(elem, "click", fn) 25 | 26 | 27 | var ev = createEvent("click") 28 | elem.dispatchEvent(ev) 29 | 30 | setImmediate(function () { 31 | assert.equal(values.length, 0) 32 | 33 | document.body.removeChild(elem) 34 | assert.end() 35 | }) 36 | }) 37 | 38 | test("remove one of multiple events", function (assert) { 39 | var elem = h("div") 40 | document.body.appendChild(elem) 41 | 42 | var d = Delegator() 43 | var values = [] 44 | 45 | function fn1(ev) { 46 | values.push(["fn1", ev]) 47 | } 48 | 49 | function fn2(ev) { 50 | values.push(["fn2", ev]) 51 | } 52 | 53 | function fn3(ev) { 54 | values.push(["fn3", ev]) 55 | } 56 | 57 | d.addEventListener(elem, "click", fn1) 58 | d.addEventListener(elem, "click", fn2) 59 | d.addEventListener(elem, "click", fn3) 60 | 61 | d.removeEventListener(elem, "click", fn2) 62 | 63 | var ev = createEvent("click") 64 | elem.dispatchEvent(ev) 65 | 66 | setImmediate(function () { 67 | assert.equal(values.length, 2) 68 | assert.equal(values[0][0], "fn1") 69 | assert.equal(values[1][0], "fn3") 70 | assert.equal(values[0][1].target, elem) 71 | 72 | document.body.removeChild(elem) 73 | assert.end() 74 | }) 75 | }) 76 | 77 | test("removing event doesn't throw", function (assert) { 78 | var elem = h("div") 79 | document.body.appendChild(elem) 80 | var d = Delegator() 81 | 82 | assert.doesNotThrow(function () { 83 | d.removeEventListener(elem, "click", function () {}) 84 | }) 85 | 86 | document.body.removeChild(elem) 87 | assert.end() 88 | }) 89 | 90 | test("removing other listener", function (assert) { 91 | var elem = h("div") 92 | document.body.appendChild(elem) 93 | var d = Delegator() 94 | 95 | d.addEventListener(elem, "click", function () {}) 96 | 97 | assert.doesNotThrow(function () { 98 | d.removeEventListener(elem, "click", function () {}) 99 | }) 100 | 101 | document.body.removeChild(elem) 102 | assert.end() 103 | }) 104 | -------------------------------------------------------------------------------- /test/dom-targets.js: -------------------------------------------------------------------------------- 1 | var test = require("tape") 2 | var setImmediate = require("timers").setImmediate 3 | var document = require("global/document") 4 | 5 | var h = require("./lib/h.js") 6 | var createEvent = require("./lib/create-event.js") 7 | 8 | var addEvent = require("../add-event.js") 9 | var Delegator = require("../index.js") 10 | 11 | test("dispatched events have correct targets", function (assert) { 12 | var elem = h("div", [ h("div") ]) 13 | document.body.appendChild(elem) 14 | 15 | Delegator() 16 | var values = [] 17 | 18 | addEvent(elem, "click", function (ev) { 19 | values.push(ev) 20 | }) 21 | 22 | var ev = createEvent("click") 23 | elem.childNodes[0].dispatchEvent(ev) 24 | 25 | setImmediate(function () { 26 | assert.equal(values.length, 1) 27 | assert.equal(values[0].target, elem.childNodes[0]) 28 | assert.equal(values[0].currentTarget, elem) 29 | 30 | document.body.removeChild(elem) 31 | assert.end() 32 | }) 33 | }) 34 | 35 | test("dispatch event with no handler", function (assert) { 36 | var elem = h("div") 37 | document.body.appendChild(elem) 38 | 39 | Delegator() 40 | var values = [] 41 | 42 | addEvent(elem, "click", function (ev) { 43 | values.push(ev) 44 | }) 45 | 46 | var ev = createEvent("keypress") 47 | elem.dispatchEvent(ev) 48 | 49 | setImmediate(function () { 50 | assert.equal(values.length, 0) 51 | 52 | document.body.removeChild(elem) 53 | assert.end() 54 | }) 55 | }) 56 | -------------------------------------------------------------------------------- /test/dom-unlisten-to.js: -------------------------------------------------------------------------------- 1 | var test = require("tape") 2 | var setImmediate = require("timers").setImmediate 3 | var Document = require("global/document").constructor 4 | 5 | var h = require("./lib/h.js") 6 | var createEvent = require("./lib/create-event.js") 7 | 8 | var addEvent = require("../add-event.js") 9 | var Delegator = require("../dom-delegator.js") 10 | 11 | test("unlistening to event", function (assert) { 12 | var elem = h("div") 13 | var doc = new Document() 14 | doc.body.appendChild(elem) 15 | 16 | var d = Delegator(doc) 17 | d.listenTo("click") 18 | var values = [] 19 | 20 | var fn = function (ev) { 21 | values.push(ev) 22 | } 23 | 24 | addEvent(elem, "click", fn) 25 | 26 | d.unlistenTo("click") 27 | 28 | assert.throws(function () { 29 | d.unlistenTo("click") 30 | }) 31 | 32 | var ev = createEvent("click") 33 | elem.dispatchEvent(ev) 34 | 35 | setImmediate(function () { 36 | assert.equal(values.length, 0) 37 | 38 | doc.body.removeChild(elem) 39 | assert.end() 40 | }) 41 | }) 42 | 43 | test("delegator throws if mutated manually", function (assert) { 44 | var d = Delegator() 45 | 46 | d.listenTo("foobar") 47 | 48 | // NAUGHTY MUTATE 49 | d.rawEventListeners.foobar = null 50 | 51 | assert.throws(function () { 52 | d.unlistenTo("foobar") 53 | }, /cannot unlisten to foobar/) 54 | 55 | assert.end() 56 | }) 57 | -------------------------------------------------------------------------------- /test/index.js: -------------------------------------------------------------------------------- 1 | require("./dom-add-event.js") 2 | require("./dom-remove-event.js") 3 | require("./dom-add-multiple.js") 4 | require("./dom-targets.js") 5 | require("./dom-delegator.js") 6 | require("./dom-data-events.js") 7 | require("./dom-duplicate-event.js") 8 | require("./dom-unlisten-to.js") 9 | require("./dom-global-listeners.js") 10 | require("./dom-propagation.js") 11 | require("./dom-handle.js") 12 | -------------------------------------------------------------------------------- /test/lib/add-sink-event.js: -------------------------------------------------------------------------------- 1 | var addEvent = require("../../add-event.js") 2 | var SinkHandler = require("./sink-handler.js") 3 | 4 | module.exports = addSinkEvent 5 | 6 | function addSinkEvent(elem, eventName, sink, data) { 7 | return addEvent(elem, eventName, 8 | new SinkHandler(sink, data)) 9 | } 10 | -------------------------------------------------------------------------------- /test/lib/create-event.js: -------------------------------------------------------------------------------- 1 | var DOMEvent = require("synthetic-dom-events") 2 | 3 | module.exports = createEvent 4 | 5 | function createEvent(type, attrs) { 6 | attrs = attrs || {} 7 | attrs.bubbles = true 8 | 9 | return DOMEvent(type, attrs) 10 | } 11 | -------------------------------------------------------------------------------- /test/lib/h.js: -------------------------------------------------------------------------------- 1 | var document = require("global/document") 2 | 3 | module.exports = h 4 | 5 | function h(tagName, children) { 6 | var elem = document.createElement(tagName) 7 | if (children) { 8 | children.forEach(function (child) { 9 | elem.appendChild(child) 10 | }) 11 | } 12 | return elem 13 | } 14 | -------------------------------------------------------------------------------- /test/lib/sink-handler.js: -------------------------------------------------------------------------------- 1 | module.exports = SinkHandler 2 | 3 | function SinkHandler(sink, data) { 4 | if (!(this instanceof SinkHandler)) { 5 | return new SinkHandler(sink, data) 6 | } 7 | 8 | this.sink = sink 9 | this.data = data 10 | } 11 | 12 | SinkHandler.prototype.handleEvent = function handleEvent(ev) { 13 | this.sink.write(this.data) 14 | } 15 | --------------------------------------------------------------------------------