From 5eb22520f1f03573a99ac13e53effd3cb48b47ac Mon Sep 17 00:00:00 2001 From: Nathan Sobo Date: Fri, 5 Sep 2014 08:57:47 -0600 Subject: [PATCH] Order multiple matching listeners by selector specificity --- spec/command-registry-spec.coffee | 12 ++++++++++++ src/command-registry.coffee | 26 ++++++++++++++++++++++---- 2 files changed, 34 insertions(+), 4 deletions(-) diff --git a/spec/command-registry-spec.coffee b/spec/command-registry-spec.coffee index 0addcde6a..28071932a 100644 --- a/spec/command-registry-spec.coffee +++ b/spec/command-registry-spec.coffee @@ -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'] diff --git a/src/command-registry.coffee b/src/command-registry.coffee index 3042f7d8d..69776319d 100644 --- a/src/command-registry.coffee +++ b/src/command-registry.coffee @@ -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