UNPKG

coffeescript-ui

Version:
440 lines (367 loc) 11.4 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 ### # Events Inter-"Process"-Communication for CUI # # # # @example How to listen to and trigger events # # Event.listen # type: [ "click", "dblclick" ] # node: jQuery Element or CUI DOM Element # call: (ev, info) -> # selector: jQuery like path selector to filter events # # # Event.trigger # type: "content-resize" # node: jQuery Element or CUI DOM Element # bubble: set to yes if event should bubble up or down the DOM tree # info: info Map, contains eventsEvent for DOMElements and # the internal "waits" queue # # Event.ignore # type: "<type>" # node: jQuery or DOM Element # # # CUIEvents bound to a node will be checked for the existance in the # DOM tree prio execution. If they don't exist (after they # have been inserted), the CUI.Listener will be deleted. # # All events need to be registered or a warning is output at the console. # # CUI.Events.registerEvent(options) # options are the default options for the event # type: <type> # bubble: true|false # # class CUI.Events extends CUI.Element @defaults: maxWait: 1500 @__listeners = [] @__eventRegistry = {} @__getListenersForNode: (node) -> if node == document or node == window @__listeners else CUI.dom.data(node, "listeners") @__registerListener: (listener) -> CUI.util.assert(listener instanceof CUI.Listener, "CUI.Events.__registerListener", "listener needs to be instance of Listener", listener: listener) node = listener.getNode() listeners = @__getListenersForNode(node) if not listeners listeners = [] CUI.dom.data(node, "listeners", listeners) listeners.push(listener) if node instanceof HTMLElement node.setAttribute("data-cui-listeners", "") @ @__getActiveListeners: (doc=document) -> if doc == document listeners = @__listeners.slice(0) else listeners = [] if CUI.dom.matches(doc, '[data-cui-listeners]') listeners.push.apply(listeners, CUI.dom.data(doc, "listeners")) for el in CUI.dom.matchSelector(doc, "[data-cui-listeners]") listeners.push.apply(listeners, CUI.dom.data(el, "listeners")) listeners @unregisterListener: (listener) -> node = listener.getNode() arr = @__getListenersForNode(node) CUI.util.assert(arr, "CUI.Events.unregisterListeners", "Listeners not found for node.", node: node, listener: listener) # console.error "unregistring listeenr", listener.getUniqueId() CUI.util.removeFromArray(listener, arr) if arr.length == 0 and node instanceof HTMLElement node.removeAttribute("cui-events-listener-element") CUI.dom.removeData(node, "listeners") # console.debug "removing listeners from node", node[0] @ # wait for an event on a node @wait: (_opts) -> opts = CUI.Element.readOpts _opts, "CUI.Events.wait", # event type type: mandatory: true check: String node: mandatory: true check: (v) -> CUI.dom.isNode(v) # optionally wait for a timeout # if set to <= 0, wait forever maxWait: default: CUI.defaults.class.Events.defaults.maxWait check: (v) -> i = parseInt(v) if isNaN(i) false else if i == -1 true else if i >= 0 true else false dfrs = [] listeners = [] _node = CUI.dom.getNode(opts.node) dfr = new CUI.Deferred() listeners.push CUI.Events.listen type: opts.type node: _node call: -> dfr.resolve() return dfrs.push(dfr) master_dfr = new CUI.Deferred() master_dfr.always -> for listener in listeners listener.destroy() return CUI.when(dfrs) .fail -> master_dfr.reject() .done -> master_dfr.resolve() if opts.maxWait >= 0 CUI.setTimeout ms: opts.maxWait call: -> for dfr in dfrs if dfr.state() == "pending" dfr.reject() return master_dfr.promise() # register a listener # @param listener PlainObject or CUI.Listener @listen: (_listener) -> listener = CUI.Listener.require(_listener, "CUI.Events.listen") @__registerListener(listener) listener @trigger: (_event) -> event = CUI.Event.require(_event, "CUI.Events.trigger") # allwo event calls to return a promise # wait for all promises before # returning from this methos # info = event.getInfo() waits = [] info.__waits = waits # use the standard event system for this kind of events bubble = event.isBubble() sink = event.isSink() exclude = event.isExcludeSelf() node = event.getNode() # console.debug "trigger event", event.getType(), info, event.getUniqueId(), bubble, sink, exclude, event.isInDOM() if bubble or not event.isInDOM() event.dispatch() else # dispatch sets a native event and through that, the event # gains a target. without the dispatch, we need to set # the target for this event event.setTarget(node) if exclude and not bubble and not sink CUI.util.assert(false, "CUI.Events.trigger", "Unable to trigger event with bubble == false, sink == false and exclude_self == true.", event: event) if sink or (not sink and not bubble and not exclude and event.isInDOM()) # if event.getType() == "toolbox" # console.debug "sink event...", event.getType(), event.getNode() triggerListeners = [] for listener, idx in @__getActiveListeners() if event.getType() not in listener.getTypes() continue if listener.matchesEvent(event) == null continue # console.error "triggering...", event.getType() triggerListeners.push(listener) # if triggerListeners.length == 0 # console.warn("CUI.Events.trigger: No listeners found for Event #{event.getType()}.", event: event, activeListeners: @active()) triggerListeners.sort (a, b) -> CUI.util.compareIndex(a.getDepthFromLastMatchedEvent(), b.getDepthFromLastMatchedEvent()) stopNodes = [] ev_node = event.getNode() for listener in triggerListeners listener_node = listener.getNode() # console.debug "listener:", listener, listener.getDepthFromLastMatchedEvent() if listener_node and stopNodes.length > 0 listener_node_parents = CUI.dom.parents(listener_node) skip = false for stopNode in stopNodes for listener_node_parent in listener_node_parents if listener_node_parent == stopNode skip = true break if skip # node is below the stop node, skip continue event.setCurrentTarget(listener_node) listener.handleEvent(event, "sink") if event.isImmediatePropagationStopped() # console.debug "immediate stopped!" break # add to stop nodes if the depth is at least 0 meaning that # the listener has a node if event.isPropagationStopped() and listener_node # console.debug "adding stop node", listener_node[0] stopNodes.push(listener_node) return CUI.when(waits) @ignore: (filter, doc=document) -> # , debug=false) -> # console.debug "CUI.Events.ignore", filter, filter.instance?.getUniqueId?() for listener in @__getActiveListeners(doc) if not filter or CUI.util.isEmptyObject(filter) or listener.matchesFilter(filter) # if debug # console.debug("CUI.Events.ignore: ignoring listener:", listener.getNode(), DOM.data(listener.getNode()).listeners?.length, filter.instance?.getUniqueId?()) listener.destroy() @ @dump: (filter={}) -> for listener in @__getActiveListeners() if CUI.util.isEmptyObject(filter) or listener.matchesFilter(filter) console.debug("Listener", listener.getTypes(), (if listener.getNode() then "NODE" else "-"), listener) @ @dumpTopLevel: -> for listener in @__listeners console.debug("Listener [document, window]", listener.getTypes(), listener.getInstance()) for listener in CUI.dom.data(document.documentElement, "listeners") console.debug("Listener [document.documentElement]", listener.getTypes(), listener.getInstance(), listener) @ @hasEventType: (type) -> !!@__eventRegistry[type] # returns event info by type @getEventType: (type) -> ev = @__eventRegistry[type] CUI.util.assert(ev, "Unknown event type \"#{type}\". Use CUI.Events.registerEvent to register this type.") return ev @getEventTypeAliases: (type) -> @getEventType(type).alias or [type] @registerEvent: (event, allow_array=true) -> if not CUI.util.isArray(event.type) or not allow_array CUI.util.assert(CUI.util.isString(event?.type) and event.type.length > 0, "CUI.Events.registerEvent", "event.type must be String.", event: event) register_other_type = (_type) => _event = CUI.util.copyObject(event, true) _event.type = _type @registerEvent(_event, false) if CUI.util.isArray(event.type) for type in event.type register_other_type(type) else if event.hasOwnProperty("DOMEvent") console.error("event.DOMEvent is obsolete") delete(event.DOMEvent) if event.hasOwnProperty("CUIEvent") console.error("event.CUIEvent is obsolete") delete(event.CUIEvent) @__eventRegistry[event.type] = event if event.alias for type in event.alias if not @__eventRegistry[type] register_other_type(type) @ @__init: -> defaults = BrowserEvents: bubble: true DOM: bubble: true CUI: eventClass: CUI.CUIEvent sink: true KeyboardEvents: eventClass: CUI.KeyboardEvent bubble: true MouseEvents: eventClass: CUI.MouseEvent bubble: true TouchEvents: eventClass: CUI.TouchEvent bubble: true for block, events of { MouseEvents: mousemove: {} mouseover: {} mouseout: {} mouseleave: {} mouseenter: {} wheel: eventClass: CUI.WheelEvent bubble: false mousedown: {} mouseup: {} click: {} dblclick: {} contextmenu: {} TouchEvents: touchstart: {} touchend: {} touchmove: {} touchcancel: {} touchforchange: {} gesturestart: {} gestureend: {} gesturechange: {} KeyboardEvents: input: bubble: false keyup: {} keydown: {} keypress: {} BrowserEvents: beforeunload: {} unload: {} load: {} error: {} close: {} popstate: {} dragstart: {} dragleave: {} dragenter: {} message: {} fullscreenchange: alias: "fullscreenchange mozfullscreenchange webkitfullscreenchange MSFullscreenChange".split(" ") hashchange: bubble: false change: bubble: false focus: bubble: false blur: bubble: false paste: bubble: false dragover: bubble: false drop: bubble: false scroll: bubble: false selectstart: bubble: false animationstart: alias: "animationstart MSAnimationStart webkitAnimationStart".split(" ") bubble: false animationend: alias: "animationend MSAnimationEnd webkitAnimationEnd".split(" ") bubble: false transitionend: alias: "transitionend webkitTransitionEnd MSTransitionEnd".split(" ") bubble: false resize: bubble: false DOM: "content-resize": eventClass: CUI.CUIEvent CUI: # "load_server": {} # "unload_server": {} "viewport-resize": {} } for type, ev of events CUI.util.mergeMap(ev, defaults[block]) ev.type = type @registerEvent(ev) CUI.Events.__init() CUI.defaults.class.Events = CUI.Events