mirror of
https://github.com/atom/atom.git
synced 2026-04-28 03:01:47 -04:00
Integrate jQuery::on and ::trigger with command registry dispatch
This commit is contained in:
@@ -55,7 +55,7 @@
|
||||
"season": "^1.0.2",
|
||||
"semver": "1.1.4",
|
||||
"serializable": "^1",
|
||||
"space-pen": "3.6.1",
|
||||
"space-pen": "3.7.0",
|
||||
"temp": "0.7.0",
|
||||
"text-buffer": "^3.2.8",
|
||||
"theorist": "^1.0.2",
|
||||
|
||||
@@ -14,7 +14,10 @@ describe "CommandRegistry", ->
|
||||
parent.appendChild(child)
|
||||
document.querySelector('#jasmine-content').appendChild(parent)
|
||||
|
||||
registry = new CommandRegistry(parent)
|
||||
registry = new CommandRegistry
|
||||
|
||||
afterEach ->
|
||||
registry.destroy()
|
||||
|
||||
describe "when a command event is dispatched on an element", ->
|
||||
it "invokes callbacks with selectors matching the target", ->
|
||||
@@ -105,6 +108,16 @@ describe "CommandRegistry", ->
|
||||
grandchild.dispatchEvent(dispatchedEvent)
|
||||
expect(dispatchedEvent.preventDefault).toHaveBeenCalled()
|
||||
|
||||
it "forwards .abortKeyBinding() calls from the synthetic event to the original", ->
|
||||
calls = []
|
||||
|
||||
registry.add '.child', 'command', (event) -> event.abortKeyBinding()
|
||||
|
||||
dispatchedEvent = new CustomEvent('command', bubbles: true)
|
||||
dispatchedEvent.abortKeyBinding = jasmine.createSpy('abortKeyBinding')
|
||||
grandchild.dispatchEvent(dispatchedEvent)
|
||||
expect(dispatchedEvent.abortKeyBinding).toHaveBeenCalled()
|
||||
|
||||
it "allows listeners to be removed via a disposable returned by ::add", ->
|
||||
calls = []
|
||||
|
||||
|
||||
@@ -72,7 +72,6 @@ beforeEach ->
|
||||
atom.project = new Project(paths: [projectPath])
|
||||
atom.workspace = new Workspace()
|
||||
atom.keymaps.keyBindings = _.clone(keyBindingsToRestore)
|
||||
atom.commands.setRootNode(document.body)
|
||||
atom.commands.restoreSnapshot(commandsToRestore)
|
||||
|
||||
window.resetTimeouts()
|
||||
|
||||
@@ -47,14 +47,6 @@ describe "Window", ->
|
||||
$(window).trigger 'window:close'
|
||||
expect(atom.close).toHaveBeenCalled()
|
||||
|
||||
it "emits the beforeunload event", ->
|
||||
$(window).off 'beforeunload'
|
||||
beforeunload = jasmine.createSpy('beforeunload').andReturn(false)
|
||||
$(window).on 'beforeunload', beforeunload
|
||||
|
||||
$(window).trigger 'window:close'
|
||||
expect(beforeunload).toHaveBeenCalled()
|
||||
|
||||
describe "beforeunload event", ->
|
||||
[beforeUnloadEvent] = []
|
||||
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
{Disposable, CompositeDisposable} = require 'event-kit'
|
||||
{Emitter, Disposable, CompositeDisposable} = require 'event-kit'
|
||||
{specificity} = require 'clear-cut'
|
||||
_ = require 'underscore-plus'
|
||||
{$} = require './space-pen-extensions'
|
||||
@@ -47,15 +47,11 @@ class CommandRegistry
|
||||
@registeredCommands = {}
|
||||
@selectorBasedListenersByCommandName = {}
|
||||
@inlineListenersByCommandName = {}
|
||||
@emitter = new Emitter
|
||||
|
||||
getRootNode: -> @rootNode
|
||||
|
||||
setRootNode: (newRootNode) ->
|
||||
oldRootNode = @rootNode
|
||||
@rootNode = newRootNode
|
||||
destroy: ->
|
||||
for commandName of @registeredCommands
|
||||
@removeDOMListener(oldRootNode, commandName)
|
||||
@addDOMListener(newRootNode, commandName)
|
||||
window.removeEventListener(commandName, @handleCommandEvent, true)
|
||||
|
||||
# Public: Add one or more command listeners associated with a selector.
|
||||
#
|
||||
@@ -122,7 +118,6 @@ class CommandRegistry
|
||||
new Disposable =>
|
||||
listenersForElement.splice(listenersForElement.indexOf(listener), 1)
|
||||
listenersForCommand.delete(element) if listenersForElement.length is 0
|
||||
@listenerRemovedForCommand(commandName)
|
||||
|
||||
# Public: Find all registered commands matching a query.
|
||||
#
|
||||
@@ -137,12 +132,11 @@ class CommandRegistry
|
||||
# `$::command` method.
|
||||
findCommands: ({target}) ->
|
||||
commands = []
|
||||
target = @rootNode unless @rootNode.contains(target)
|
||||
currentTarget = target
|
||||
loop
|
||||
for commandName, listeners of @selectorBasedListenersByCommandName
|
||||
for listener in listeners
|
||||
if currentTarget.webkitMatchesSelector(listener.selector)
|
||||
if currentTarget.webkitMatchesSelector?(listener.selector)
|
||||
commands.push
|
||||
name: commandName
|
||||
displayName: _.humanizeEventName(commandName)
|
||||
@@ -168,11 +162,18 @@ class CommandRegistry
|
||||
#
|
||||
# * `target` The DOM node at which to start bubbling the command event.
|
||||
# * `commandName` {String} indicating the name of the command to dispatch.
|
||||
dispatch: (target, commandName) ->
|
||||
event = new CustomEvent(commandName, bubbles: true)
|
||||
eventWithTarget = Object.create(event, target: value: target)
|
||||
dispatch: (target, commandName, detail) ->
|
||||
event = new CustomEvent(commandName, {bubbles: true, detail})
|
||||
eventWithTarget = Object.create event,
|
||||
target: value: target
|
||||
preventDefault: value: ->
|
||||
stopPropagation: value: ->
|
||||
stopImmediatePropagation: value: ->
|
||||
@handleCommandEvent(eventWithTarget)
|
||||
|
||||
onWillDispatch: (callback) ->
|
||||
@emitter.on 'will-dispatch', callback
|
||||
|
||||
getSnapshot: ->
|
||||
snapshot = {}
|
||||
for commandName, listeners of @selectorBasedListenersByCommandName
|
||||
@@ -180,15 +181,9 @@ class CommandRegistry
|
||||
snapshot
|
||||
|
||||
restoreSnapshot: (snapshot) ->
|
||||
rootNode = @getRootNode()
|
||||
@setRootNode(null) # clear listeners for current commands
|
||||
@registeredCommands = {}
|
||||
@inlineListenersByCommandName = {}
|
||||
@selectorBasedListenersByCommandName = {}
|
||||
for commandName, listeners of snapshot
|
||||
@selectorBasedListenersByCommandName[commandName] = listeners.slice()
|
||||
@registeredCommands[commandName] = true
|
||||
@setRootNode(rootNode) # restore listeners for commands in snapshot
|
||||
|
||||
handleCommandEvent: (originalEvent) =>
|
||||
originalEvent.__handledByCommandRegistry = true
|
||||
@@ -197,7 +192,6 @@ class CommandRegistry
|
||||
immediatePropagationStopped = false
|
||||
matched = false
|
||||
currentTarget = originalEvent.target
|
||||
invokedListeners = []
|
||||
|
||||
syntheticEvent = Object.create originalEvent,
|
||||
eventPhase: value: Event.BUBBLING_PHASE
|
||||
@@ -209,50 +203,38 @@ class CommandRegistry
|
||||
originalEvent.stopImmediatePropagation()
|
||||
propagationStopped = true
|
||||
immediatePropagationStopped = true
|
||||
disableInvokedListeners: value: ->
|
||||
listener.enabled = false for listener in invokedListeners
|
||||
-> listener.enabled = true for listener in invokedListeners
|
||||
abortKeyBinding: value: ->
|
||||
originalEvent.abortKeyBinding?()
|
||||
|
||||
@emitter.emit 'will-dispatch', syntheticEvent
|
||||
|
||||
loop
|
||||
inlineListeners = @inlineListenersByCommandName[originalEvent.type]?.get(currentTarget) ? []
|
||||
selectorBasedListeners =
|
||||
(@selectorBasedListenersByCommandName[originalEvent.type] ? [])
|
||||
.filter (listener) -> currentTarget.webkitMatchesSelector(listener.selector)
|
||||
.sort (a, b) -> a.compare(b)
|
||||
listeners = inlineListeners.concat(selectorBasedListeners)
|
||||
listeners = @inlineListenersByCommandName[originalEvent.type]?.get(currentTarget) ? []
|
||||
unless currentTarget is window or currentTarget is document
|
||||
selectorBasedListeners =
|
||||
(@selectorBasedListenersByCommandName[originalEvent.type] ? [])
|
||||
.filter (listener) -> currentTarget.webkitMatchesSelector(listener.selector)
|
||||
.sort (a, b) -> a.compare(b)
|
||||
listeners = listeners.concat(selectorBasedListeners)
|
||||
|
||||
matched = true if listeners.length > 0
|
||||
|
||||
for listener in listeners when listener.enabled
|
||||
for listener in listeners
|
||||
break if immediatePropagationStopped
|
||||
invokedListeners.push(listener)
|
||||
listener.callback.call(currentTarget, syntheticEvent)
|
||||
|
||||
break if currentTarget is @rootNode
|
||||
break if currentTarget is window
|
||||
break if propagationStopped
|
||||
currentTarget = currentTarget.parentNode
|
||||
break unless currentTarget?
|
||||
currentTarget = currentTarget.parentNode ? window
|
||||
|
||||
matched
|
||||
|
||||
handleJQueryCommandEvent: (event) =>
|
||||
@handleCommandEvent(event) unless event.originalEvent?.__handledByCommandRegistry
|
||||
|
||||
commandRegistered: (commandName) ->
|
||||
unless @registeredCommands[commandName]
|
||||
@addDOMListener(@rootNode, commandName)
|
||||
window.addEventListener(commandName, @handleCommandEvent, true)
|
||||
@registeredCommands[commandName] = true
|
||||
|
||||
addDOMListener: (node, commandName) ->
|
||||
node?.addEventListener(commandName, @handleCommandEvent, true)
|
||||
$(node).on commandName, @handleJQueryCommandEvent
|
||||
|
||||
removeDOMListener: (node, commandName) ->
|
||||
node?.removeEventListener(commandName, @handleCommandEvent, true)
|
||||
$(node).off commandName, @handleJQueryCommandEvent
|
||||
|
||||
class SelectorBasedListener
|
||||
enabled: true
|
||||
|
||||
constructor: (@selector, @callback) ->
|
||||
@specificity = (SpecificityCache[@selector] ?= specificity(@selector))
|
||||
@sequenceNumber = SequenceCount++
|
||||
@@ -262,6 +244,4 @@ class SelectorBasedListener
|
||||
other.sequenceNumber - @sequenceNumber
|
||||
|
||||
class InlineListener
|
||||
enabled: true
|
||||
|
||||
constructor: (@callback) ->
|
||||
|
||||
@@ -334,9 +334,17 @@ class Package
|
||||
@activationCommandSubscriptions = new CompositeDisposable
|
||||
for selector, commands of @getActivationCommands()
|
||||
for command in commands
|
||||
@activationCommandSubscriptions.add(
|
||||
atom.commands.add(selector, command, @handleActivationCommand)
|
||||
)
|
||||
do (selector, command) =>
|
||||
@activationCommandSubscriptions.add(atom.commands.onWillDispatch (event) =>
|
||||
return unless event.type is command
|
||||
currentTarget = event.target
|
||||
while currentTarget
|
||||
if currentTarget.webkitMatchesSelector(selector)
|
||||
@activationCommandSubscriptions.dispose()
|
||||
@activateNow()
|
||||
break
|
||||
currentTarget = currentTarget.parentElement
|
||||
)
|
||||
|
||||
getActivationCommands: ->
|
||||
return @activationCommands if @activationCommands?
|
||||
@@ -368,14 +376,6 @@ class Package
|
||||
|
||||
@activationCommands
|
||||
|
||||
handleActivationCommand: (event) =>
|
||||
event.stopImmediatePropagation()
|
||||
@activationCommandSubscriptions.dispose()
|
||||
reenableInvokedListeners = event.disableInvokedListeners()
|
||||
@activateNow()
|
||||
event.target.dispatchEvent(new CustomEvent(event.type, bubbles: true))
|
||||
reenableInvokedListeners()
|
||||
|
||||
# Does the given module path contain native code?
|
||||
isNativeModule: (modulePath) ->
|
||||
try
|
||||
|
||||
@@ -10,6 +10,69 @@ jQuery.cleanData = (elements) ->
|
||||
jQuery(element).view()?.unsubscribe() for element in elements
|
||||
originalCleanData(elements)
|
||||
|
||||
NativeEventNames = new Set
|
||||
NativeEventNames.add(nativeEvent) for nativeEvent in ["blur", "focus", "focusin",
|
||||
"focusout", "load", "resize", "scroll", "unload", "click", "dblclick", "mousedown",
|
||||
"mouseup", "mousemove", "mouseover", "mouseout", "mouseenter", "mouseleave", "change",
|
||||
"select", "submit", "keydown", "keypress", "keyup", "error", "contextmenu"]
|
||||
|
||||
originalTrigger = jQuery.fn.trigger
|
||||
jQuery.fn.trigger = (eventName, data) ->
|
||||
if NativeEventNames.has(eventName) or typeof eventName is 'object'
|
||||
originalTrigger.call(this, eventName, data)
|
||||
else
|
||||
for element in this
|
||||
atom.commands.dispatch(element, eventName, data)
|
||||
this
|
||||
|
||||
HandlersByOriginalHandler = new WeakMap
|
||||
CommandDisposablesByElement = new WeakMap
|
||||
|
||||
AddEventListener = (element, type, listener) ->
|
||||
if NativeEventNames.has(type)
|
||||
element.addEventListener(type, listener)
|
||||
else
|
||||
disposable = atom.commands.add(element, type, listener)
|
||||
|
||||
unless disposablesByType = CommandDisposablesByElement.get(element)
|
||||
disposablesByType = {}
|
||||
CommandDisposablesByElement.set(element, disposablesByType)
|
||||
|
||||
unless disposablesByListener = disposablesByType[type]
|
||||
disposablesByListener = new WeakMap
|
||||
disposablesByType[type] = disposablesByListener
|
||||
|
||||
disposablesByListener.set(listener, disposable)
|
||||
|
||||
RemoveEventListener = (element, type, listener) ->
|
||||
if NativeEventNames.has(type)
|
||||
element.removeEventListener(type, listener)
|
||||
else
|
||||
CommandDisposablesByElement.get(element)?[type]?.get(listener)?.dispose()
|
||||
|
||||
JQueryEventAdd = jQuery.event.add
|
||||
jQuery.event.add = (elem, types, originalHandler, data, selector) ->
|
||||
handler = (event) ->
|
||||
if arguments.length is 1 and event.originalEvent?.detail?
|
||||
{detail} = event.originalEvent
|
||||
if Array.isArray(detail)
|
||||
originalHandler.apply(this, [event].concat(detail))
|
||||
else
|
||||
originalHandler.call(this, event, detail)
|
||||
else
|
||||
originalHandler.apply(this, arguments)
|
||||
|
||||
HandlersByOriginalHandler.set(originalHandler, handler)
|
||||
|
||||
console.log "jquery.event.add...", elem, types if global.debug
|
||||
JQueryEventAdd.call(this, elem, types, handler, data, selector, AddEventListener if atom?.commands?)
|
||||
|
||||
JQueryEventRemove = jQuery.event.remove
|
||||
jQuery.event.remove = (elem, types, originalHandler, selector, mappedTypes) ->
|
||||
if originalHandler?
|
||||
handler = HandlersByOriginalHandler.get(originalHandler) ? originalHandler
|
||||
JQueryEventRemove(elem, types, handler, selector, mappedTypes, RemoveEventListener if atom?.commands?)
|
||||
|
||||
tooltipDefaults =
|
||||
delay:
|
||||
show: 1000
|
||||
|
||||
@@ -10,7 +10,6 @@ module.exports =
|
||||
class WorkspaceElement extends HTMLElement
|
||||
createdCallback: ->
|
||||
@subscriptions = new CompositeDisposable
|
||||
atom.commands.setRootNode(this)
|
||||
@initializeContent()
|
||||
@observeScrollbarStyle()
|
||||
@observeTextEditorFontConfig()
|
||||
|
||||
Reference in New Issue
Block a user