Skip to content

Commit 0dec4a2

Browse files
authored
Merge pull request #31 from github/bind-bugs
fix(bind): Fix eventname and controller binding bugs.
2 parents 7aaf07f + 8b7a90a commit 0dec4a2

2 files changed

Lines changed: 70 additions & 14 deletions

File tree

catalyst/src/bind.ts

Lines changed: 35 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -4,23 +4,44 @@
44
*/
55
export function bind(controller: HTMLElement) {
66
const tag = controller.tagName.toLowerCase()
7-
for (const el of controller.querySelectorAll(`[data-action*=":${tag}#"]`)) {
7+
const actionAttributeMatcher = `[data-action*=":${tag}#"]`
8+
9+
for (const el of controller.querySelectorAll(actionAttributeMatcher)) {
810
// Ignore nested elements
911
if (el.closest(tag) !== controller) continue
12+
bindActionsToController(controller, el)
13+
}
14+
15+
// Also bind the controller to itself
16+
if (controller.matches(actionAttributeMatcher)) {
17+
bindActionsToController(controller, controller)
18+
}
19+
}
20+
21+
// Bind the data-action attribute of a single element to the controller
22+
function bindActionsToController(controller: HTMLElement, el: Element) {
23+
const tag = controller.tagName.toLowerCase()
24+
25+
// Match the pattern of `eventName:constructor#method`.
26+
for (const binding of (el.getAttribute('data-action') || '').split(' ')) {
27+
const [rest, method] = binding.split('#')
28+
29+
// eventName may contain `:` so account for that
30+
// by splitting by the last instance of `:`
31+
const colonIndex = rest.lastIndexOf(':')
32+
if (colonIndex < 0) continue
33+
34+
const handler = rest.slice(colonIndex + 1)
35+
if (handler !== tag) continue
36+
37+
const eventName = rest.slice(0, colonIndex)
1038

11-
// Match the pattern of `eventName:constructor#method`.
12-
for (const binding of (el.getAttribute('data-action') || '').split(' ')) {
13-
const [rest, method] = binding.split('#')
14-
const [eventName, handler] = rest.split(':')
15-
if (handler !== tag) continue
16-
17-
// Check the `method` is present on the prototype
18-
const methodDescriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(controller), method)
19-
if (methodDescriptor && typeof methodDescriptor.value == 'function') {
20-
el.addEventListener(eventName, (event: Event) => {
21-
;(controller as any)[method](event)
22-
})
23-
}
39+
// Check the `method` is present on the prototype
40+
const methodDescriptor = Object.getOwnPropertyDescriptor(Object.getPrototypeOf(controller), method)
41+
if (methodDescriptor && typeof methodDescriptor.value == 'function') {
42+
el.addEventListener(eventName, (event: Event) => {
43+
;(controller as any)[method](event)
44+
})
2445
}
2546
}
2647
}

catalyst/test/bind.js

Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ describe('bind', () => {
1616
}
1717
querySelectorAll() {}
1818
foo() {}
19+
matches() {}
1920
}
2021

2122
it('queries for Elements matching data-action*="tagname"', () => {
@@ -42,6 +43,40 @@ describe('bind', () => {
4243
expect(instance.foo).to.have.been.called(1)
4344
})
4445

46+
it('allows for the presence of `:` in an event name', () => {
47+
const instance = new MyController()
48+
spy.on(instance, 'foo')
49+
const el = new FakeElement()
50+
instance.querySelectorAll = () => [el]
51+
el.closest = () => instance
52+
el.getAttribute = () => 'custom:event:my-controller#foo'
53+
spy.on(el, 'addEventListener')
54+
bind(instance)
55+
expect(el.addEventListener).to.have.been.called.once.with('custom:event')
56+
const {calls} = el.addEventListener.__spy
57+
const fn = calls[0][1]
58+
expect(instance.foo).to.have.not.been.called()
59+
fn()
60+
expect(instance.foo).to.have.been.called(1)
61+
})
62+
63+
it('binds events on the controller to itself', () => {
64+
const instance = new MyController()
65+
spy.on(instance, 'foo')
66+
instance.matches = () => true
67+
instance.getAttribute = () => 'click:my-controller#foo'
68+
instance.addEventListener = () => true
69+
instance.querySelectorAll = () => []
70+
spy.on(instance, 'addEventListener')
71+
bind(instance)
72+
expect(instance.addEventListener).to.have.been.called.once.with('click')
73+
const {calls} = instance.addEventListener.__spy
74+
const fn = calls[0][1]
75+
expect(instance.foo).to.have.not.been.called()
76+
fn()
77+
expect(instance.foo).to.have.been.called(1)
78+
})
79+
4580
it('does not bind elements whose closest selector is not this controller', () => {
4681
const instance = new MyController()
4782
spy.on(instance, 'foo')

0 commit comments

Comments
 (0)