Skip to content

Commit

Permalink
Add events/delegate
Browse files Browse the repository at this point in the history
  • Loading branch information
conradz committed Jul 8, 2013
1 parent 6b24d89 commit 3969364
Show file tree
Hide file tree
Showing 3 changed files with 161 additions and 1 deletion.
34 changes: 33 additions & 1 deletion docs/events.md
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,38 @@
Utilities for manipulating and observing DOM events.


## delegate(root, selector, eventNames, callback)

Adds `callback` as an event listener that will listen for events that bubble
from elements that match `selector`.

`root` can be a single DOM node or a list of nodes. `selector` is string
containing a CSS selector that filters the nodes that the event can be triggered
on. `eventNames` is an array or space-separated string that contains the event
names that the listener will be attached to.

`callback` will only be called when an event that is included in `eventNames` is
triggered on an element that is a child of `root` and matches the specified CSS
selector. It will not be called when an event is triggered directly on `root`.
The first argument to `callback` will be the `Event` object and the context will
be the child node that matches `selector`.

The return value is an object that contains a `.remove` function that when
called will remove the event listener. You cannot remove the listener with
[`removeListener`](#removeListener).

Example:
```js
delegate(container, "button.action", "click", function() {
// Called only when a button contained in `container` with a class of
// `action` is clicked.
// `this` is the DOM element for the button that was clicked.
});
```

See also: [`listen`](#listen)


## listen(nodes, eventNames, callback):Object

Adds `callback` as an event listener to all the nodes for the specified events.
Expand Down Expand Up @@ -37,7 +69,7 @@ var listener = listen(buttons, "click", function(event) {
listener.remove();
```

See also: [`removeListener`](#removeListener)
See also: [`delegate`](#delegate), [`removeListener`](#removeListener)


## removeListener(nodes, eventNames, callback)
Expand Down
29 changes: 29 additions & 0 deletions source/events/delegate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
define(["./listen", "../dom/matches"], function(listen, matches) {

function getTarget(event, selector, root) {
var el = event.target;

while (el) {
if (el === root) {
return;
} else if (matches(selector, el)) {
return el;
} else {
el = el.parentElement;
}
}
}

function listenOn(root, selector, eventName, handler) {
return listen(root, eventName, function(e) {
var target = getTarget(e, selector, root);
if (target) {
handler.call(target, e);
}
});
}

return listenOn;

});

99 changes: 99 additions & 0 deletions tests/events/spec-delegate.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,99 @@
define(["cane/events/delegate"], function(delegate) {

describe("events/delegate", function() {

it("should listen for events on elements that match", function() {
var main = document.createElement("div"),
child = document.createElement("span"),
event = document.createEvent("Event"),
handler = sinon.spy();

delegate(main, ".child", "test", handler);

event.initEvent("test", true, true);
child.className = "child";
main.appendChild(child);
// See https://code.google.com/p/chromium/issues/detail?id=120494
// Element must be attached to body in Chrome/Webkit
document.body.appendChild(main);
child.dispatchEvent(event);
document.body.removeChild(main);

expect(handler.calledOn(child)).toBe(true);
});

it("should ignore events on elements that do not match", function() {
var main = document.createElement("div"),
child = document.createElement("span"),
event = document.createEvent("Event"),
handler = sinon.spy();

delegate(main, ".ignore", "test", handler);

event.initEvent("test", true, true);
main.appendChild(child);
document.body.appendChild(main);
child.dispatchEvent(event);
document.body.removeChild(main);

expect(handler.called).toBe(false);
});

it("should ignore events on main element", function() {
var main = document.createElement("div"),
event = document.createEvent("Event"),
handler = sinon.spy();

delegate(main, "div", "test", handler);

event.initEvent("test", true, true);
document.body.appendChild(main);
main.dispatchEvent(event);
document.body.removeChild(main);

expect(handler.called).toBe(false);
});

it("should return an object with .remove function", function() {
var main = document.createElement("div"),
child = document.createElement("span"),
event = document.createEvent("Event"),
handler = sinon.spy();

var listener = delegate(main, "span", "test", handler);

event.initEvent("test", true, true);
main.appendChild(child);
document.body.appendChild(main);
child.dispatchEvent(event);
listener.remove();
child.dispatchEvent(event);

expect(handler.calledOnce).toBe(true);
});

it("should accept multiple elements", function() {
var main1 = document.createElement("div"),
main2 = document.createElement("div"),
child1 = document.createElement("span"),
child2 = document.createElement("span"),
event = document.createEvent("Event"),
handler = sinon.spy();

delegate([main1, main2], "span", "test", handler);

event.initEvent("test", true, true);
main1.appendChild(child1);
main2.appendChild(child2);
document.body.appendChild(main1);
document.body.appendChild(main2);

child1.dispatchEvent(event);
child2.dispatchEvent(event);

expect(handler.calledTwice).toBe(true);
});

});

});

0 comments on commit 3969364

Please sign in to comment.