Order multiple matching listeners by selector specificity

This commit is contained in:
Nathan Sobo
2014-09-05 08:57:47 -06:00
parent a075aa2b07
commit 5eb22520f1
2 changed files with 34 additions and 4 deletions

View File

@@ -31,6 +31,7 @@ describe "CommandRegistry", ->
it "invokes callbacks with selectors matching ancestors of the target", ->
calls = []
registry.add 'command', '.child', (event) ->
expect(this).toBe child
expect(event.target).toBe grandchild
@@ -45,3 +46,14 @@ describe "CommandRegistry", ->
grandchild.dispatchEvent(new CustomEvent('command', bubbles: true))
expect(calls).toEqual ['child', 'parent']
it "orders multiple matching listeners for an element by selector specificity", ->
child.classList.add('foo', 'bar')
calls = []
registry.add 'command', '.foo.bar', -> calls.push('.foo.bar')
registry.add 'command', '.foo', -> calls.push('.foo')
registry.add 'command', '.bar', -> calls.push('.bar') # specificity ties favor commands added later, like CSS
grandchild.dispatchEvent(new CustomEvent('command', bubbles: true))
expect(calls).toEqual ['.foo.bar', '.bar', '.foo']

View File

@@ -1,3 +1,8 @@
{specificity} = require 'clear-cut'
SequenceCount = 0
SpecificityCache = {}
module.exports =
class CommandRegistry
constructor: (@rootNode) ->
@@ -8,7 +13,7 @@ class CommandRegistry
@rootNode.addEventListener(commandName, @dispatchCommand, true)
@listenersByCommandName[commandName] = []
@listenersByCommandName[commandName].push({selector, callback})
@listenersByCommandName[commandName].push(new CommandListener(selector, callback))
dispatchCommand: (event) =>
syntheticEvent = Object.create event,
@@ -17,9 +22,22 @@ class CommandRegistry
currentTarget = event.target
loop
for listener in @listenersByCommandName[event.type]
if currentTarget.webkitMatchesSelector(listener.selector)
listener.callback.call(currentTarget, syntheticEvent)
matchingListeners =
@listenersByCommandName[event.type]
.filter (listener) -> currentTarget.webkitMatchesSelector(listener.selector)
.sort (a, b) -> a.compare(b)
for listener in matchingListeners
listener.callback.call(currentTarget, syntheticEvent)
break if currentTarget is @rootNode
currentTarget = currentTarget.parentNode
class CommandListener
constructor: (@selector, @callback) ->
@specificity = (SpecificityCache[@selector] ?= specificity(@selector))
@sequenceNumber = SequenceCount++
compare: (other) ->
other.specificity - @specificity or
other.sequenceNumber - @sequenceNumber