UNPKG

coffeescript-ui

Version:
290 lines (227 loc) 7.96 kB
### * coffeescript-ui - Coffeescript User Interface System (CUI) * Copyright (c) 2013 - 2016 Programmfabrik GmbH * MIT Licence * https://github.com/programmfabrik/coffeescript-ui, http://www.coffeescript-ui.org ### class CUI.Listener extends CUI.Element initOpts: -> super() @addOpts # the type of the event(s) to listen to type: mandatory: true check: (v) -> CUI.util.isString(v) or CUI.util.isArray(v) # an optional element to bind this listener to # if given, the event will only be triggered node: default: document.documentElement mandatory: true check: (v) -> CUI.dom.isNode(v) # call this function when event is triggered call: mandatory: true check: (v) -> CUI.util.isFunction(v) # if set only listen once only_once: check: Boolean # passthru for jquery selector selector: check: (v) -> CUI.util.isString(v) or CUI.util.isFunction(v) # abritrary object to match when "ignore" is called instance: {} # catch during capture phase capture: default: false check: Boolean readOpts: -> super() if CUI.util.isString(@_type) @__types = @_type.split(/\s+/) else @__types = @_type @__node = CUI.dom.getNode(@_node) for type in @__types ev = CUI.Events.getEventType(type) if CUI.util.isString(@_selector) CUI.util.assert(@__node instanceof HTMLElement or @__node == document, "new CUI.Listener", "opts.selector requires the node to be instance of HTMLElement.", opts: @opts) @__handleDOMEvent = (ev) => # console.debug "handleDOMEvent", ev.type, @getUniqueId(), @ @__handleDOMEventInternal(ev) if @_selector if CUI.util.isString(@_selector) @__selector = (target, node) => CUI.dom.closestUntil(target, @_selector, node) else @__selector = @_selector @__registerDOMEvent() @ isCapture: -> @_capture getNode: -> @__node getTypes: -> @__types __registerDOMEvent: -> for _type in @getTypes() for type in CUI.Events.getEventTypeAliases(_type) @__node.addEventListener(type, @__handleDOMEvent, @isCapture()) @ __handleDOMEventInternal: (ev) -> if @__selector # filter this by the selector # check if from the target up, we match a selector currentTarget = @__selector(ev.target, @__node) if not currentTarget return false else currentTarget = @__node # this event was synthecially created by CUI.Events.trigger # or previously handled by an CUI.Listener # or dispatched by Event.dispatch if ev.__cui_event event = ev.__cui_event # console.error "use event from eventsevent", ev, eventsEvent.getType(), eventsEvent.getUniqueId() else event = CUI.Event.createFromDOMEvent(ev) # store this for the bubble phase, so that this events stays # the same, throughout its livetime ev.__cui_event = event # console.error "create event from DOM event", ev, eventsEvent.getType(), eventsEvent.getUniqueId() event.setCurrentTarget(currentTarget) # console.debug "handleDOMEvent", @__cls, ev.type, eventsEvent.__cls if @isCapture() ret = @handleEvent(event, "capture") else ret = @handleEvent(event, "bubble") return ret isOnlyOnce: -> @_only_once destroy: -> if @isDestroyed() return CUI.Events.unregisterListener(@) for _type in @getTypes() # @__node.removeClass("cui-debug-listen-#{type}") # for type in CUI.Events.getEventTypeAliases(_type) @__node.removeEventListener(type, @__handleDOMEvent, @isCapture()) super() # returns the distance of the listener node # to the events node. with no node, return -1 # returns null if no match matchesEvent: (event) -> CUI.util.assert(event instanceof CUI.Event, "CUI.Listener.matchesEvent", "event needs to be instance of CUI.Event.") delete(@__depth) if event.getType() not in @getTypes() return null # if not @__node # @__depth = -1 # return @__depth ev_node = event.getNode() @__depth = 0 if @isCapture() # ignore sink events return null # if bubble is set to false the event needs to happen inside DOM # tree in order for us to consider ourselves. if it happened outside # the DOM tree, our node has already been triggered by the native # event if not event.isExcludeSelf() and not event.isBubble() and event.isInDOM() # if the event bubbles, we are touched by the native event already # so we can ignore us if @__node == ev_node return @__depth # if @getNode() == document # console.debug "listener", @getNode(), DOM.parents(@__node), ev_node if event.isSink() for parent in CUI.dom.parents(@__node) @__depth++ if parent == ev_node return @__depth delete(@__depth) return null getDepthFromLastMatchedEvent: -> @__depth # if the calls return promises, we # return a promise, otherwise we return the last ret # nothing handleEvent: (event, phase) -> CUI.util.assert(event instanceof CUI.Event, "CUI.Listener.handleEvent", "event needs to be instance of CUI.Event", event: event) event.__setPhase(phase) event.__setListener(@) if @isOnlyOnce() # console.debug "destroying one time event listener...", event, @ @destroy() # this calls "destroy" on us inst = @getInstance() if inst and inst instanceof CUI.Element and inst.isDestroyed() @destroy() return # try ret = @_call.call(@, event, event.getInfo()) # catch ex # console.error("Handle Event error \"#{ex}\". Type: ", event.getType(), @) # throw(ex) # console.debug "CUI.Listener.handleEvent", event, info if CUI.util.isPromise(ret) info = event.getInfo() if not info.__waits CUI.util.assert(false, "CUI.Listener.handleEvent", "Event \"#{event.getType()}\" to handle was not triggered by CUI.Events.trigger, but instead by a regular DOMEvent.\n\nMake sure that, if your handler returns a Promise, the event is triggered by CUI.Events.trigger.", event: event, listener: @, return: ret) info.__waits.push(ret) ret getInstance: -> @_instance matchesFilter: (filter) -> if filter instanceof CUI.Listener return filter == @ CUI.util.assert(CUI.util.isPlainObject(filter), "CUI.Listener.matchesFilter", "filter needs to be PlainObject.") match = true filtered = false if filter.node filter_node = CUI.dom.getNode(filter.node) filtered = true match = !!CUI.dom.closestUntil(@__node, filter_node) if match and filter.type filtered = true if CUI.util.isArray(filter.type) match = false for _type in filter.type match = _type in @__types if match break else match = filter.type in @__types if match and filter.call filtered = true match = filter.call == @_call if match and filter.instance filtered = true match = filter.instance == @getInstance() CUI.util.assert(filtered, "Listener.matchesFilter", "Filter did not filter anything, make sure you have 'node', 'type', 'call', or 'instance' set.", filter: filter) return match @require: (listener, func) -> if CUI.util.isPlainObject(listener) listenerFunc = null if listener.type not instanceof Array types = [listener.type] else types = listener.type for type in types ev = CUI.Events.getEventType(type) CUI.util.assert(ev, "#{func}", "listener.type needs to be registered", listener: listener) if ev.listenerClass CUI.util.assert(not listenerFunc or listenerFunc == ev.listenerClass, "#{func}", "listenerFunction differs for different listener types.", listener: listener) listenerFunc = ev.listenerClass else CUI.util.assert(not listenerFunc or listenerFunc == CUI.Listener, "#{func}", "listenerFunction differs for different listener types.", listener: listener) listenerFunc = CUI.Listener listen = new listenerFunc(listener) else listen = listener CUI.util.assert(listen instanceof CUI.Listener, "#{func}", "listener needs to be PlainObject or instance of CUI.Listener.") listen