API Docs for: 0.0.1
Show:

File: src/event-dom/event-dom.js

"use strict";

/**
 * Integrates DOM-events to event. more about DOM-events:
 * http://www.smashingmagazine.com/2013/11/12/an-introduction-to-dom-events/
 *
 *
 * <i>Copyright (c) 2014 ITSA - https://github.com/itsa</i>
 * New BSD License - http://choosealicense.com/licenses/bsd-3-clause/
 *
 * @example
 * Event = require('event-dom')(window);
 *
 * @module event
 * @submodule event-dom
 * @class Event
 * @since 0.0.1
*/


var NAME = '[event-dom]: ',
    Event = require('itsa-event'),
    later = require('utils').later,
    createHashMap = require('js-ext/extra/hashmap.js').createMap,
    OUTSIDE = 'outside',
    REGEXP_NODE_ID = /^#\S+$/,
    REGEXP_EXTRACT_NODE_ID = /#(\S+)/,
    REGEXP_UI_OUTSIDE = /^.+outside$/,
    TIME_BTN_PRESSED = 200,
    PURE_BUTTON_ACTIVE = 'pure-button-active',
    UI = 'UI:',
    NODE = 'node',
    REMOVE = 'remove',
    INSERT = 'insert',
    CHANGE = 'change',
    ATTRIBUTE = 'attribute',
    TAP = 'tap',
    CLICK = 'click',
    MOUSEDOWN = 'mousedown',
    PANSTART = 'panstart',
    RIGHTCLICK = 'right'+CLICK,
    CENTERCLICK = 'center'+CLICK,
    ANCHOR_CLICK = 'anchor'+CLICK,
    EV_REMOVED = UI+NODE+REMOVE,
    EV_INSERTED = UI+NODE+INSERT,
    EV_CONTENT_CHANGE = UI+NODE+'content'+CHANGE,
    EV_ATTRIBUTE_REMOVED = UI+ATTRIBUTE+REMOVE,
    EV_ATTRIBUTE_CHANGED = UI+ATTRIBUTE+CHANGE,
    EV_ATTRIBUTE_INSERTED = UI+ATTRIBUTE+INSERT,
    mutationEventsDefined = false,
    NO_DEEP_SEARCH = {},
    ANCHOR_OFFSET = 2, // px
    _shiftPressed = false, // protected registration: to be set on tap-events
    _ctrlPressed = false, // protected registration: to be set on tap-events
    _metaPressed = false, // protected registration: to be set on tap-events
    startX, startY,

    /*
     * Internal hash containing all DOM-events that are listened for (at `document`).
     *
     * DOMEvents = {
     *     'tap': callbackFn,
     *     'mousemove': callbackFn,
     *     'keypress': callbackFn
     * }
     *
     * @property DOMEvents
     * @default {}
     * @type Object
     * @private
     * @since 0.0.1
    */
    DOMEvents = {};

    require('js-ext/lib/string.js');
    require('js-ext/lib/array.js');
    require('js-ext/lib/object.js');
    require('polyfill/polyfill-base.js');

module.exports = function (window) {
    var DOCUMENT = window.document,
        isMobile = require('useragent')(window).isMobile,
        _domSelToFunc, _evCallback, _findCurrentTargets, _preProcessor, _setupEvents, _setupMutationListener, _teardownMutationListener,
        _setupDomListener, _teardownDomListener, SORT, _sortFunc, _sortFuncReversed, _getSubscribers, _selToFunc, MUTATION_EVENTS, preventClick;

    require('vdom')(window);

    window._ITSAmodules || Object.protectedProp(window, '_ITSAmodules', createHashMap());

    if (window._ITSAmodules.EventDom) {
        return Event; // Event was already extended
    }

    MUTATION_EVENTS = [EV_REMOVED, EV_INSERTED, EV_CONTENT_CHANGE, EV_ATTRIBUTE_REMOVED, , EV_ATTRIBUTE_CHANGED, EV_ATTRIBUTE_INSERTED];

    /*
     * Transforms the selector to a valid function
     *
     * @method _selToFunc
     * @param customEvent {String} the customEvent that is transported to the eventsystem
     * @param subscriber {Object} subscriber
     * @param subscriber.o {Object} context
     * @param subscriber.cb {Function} callbackFn
     * @param subscriber.f {Function|String} filter
     * @private
     * @since 0.0.1
     */
    _selToFunc = function(customEvent, subscriber) {
        Event._sellist.some(function(selFn) {
            return selFn(customEvent, subscriber);
        });
    };

    /*
     * Creates a filterfunction out of a css-selector. To be used for catching any dom-element, without restrictions
     * of any context (like Parcels can --> Parcel.Event uses _parcelSelToDom instead)
     * On "non-outside" events, subscriber.t is set to the node that first matches the selector
     * so it can be used to set as e.target in the final subscriber
     *
     * @method _domSelToFunc
     * @param customEvent {String} the customEvent that is transported to the eventsystem
     * @param subscriber {Object} subscriber
     * @param subscriber.o {Object} context
     * @param subscriber.cb {Function} callbackFn
     * @param subscriber.f {Function|String} filter
     * @private
     * @since 0.0.1
     */
    _domSelToFunc = function(customEvent, subscriber) {
        // this stage is runned during subscription
        var outsideEvent = REGEXP_UI_OUTSIDE.test(customEvent),
            selector = subscriber.f,
            context = subscriber.o,
            vnode = subscriber.o.vnode,
            isCustomElement = vnode && vnode.isItag,
            visibleContent = isCustomElement && !vnode.domNode.contentHidden,
            nodeid, byExactId, newTarget, deepSearch;

        console.log(NAME, '_domSelToFunc type of selector = '+typeof selector);
        // note: selector could still be a function: in case another subscriber
        // already changed it.
        if ((!selector && !isCustomElement) || (typeof selector === 'function')) {
            subscriber.n || (subscriber.n = isCustomElement ? context : DOCUMENT);
            return true;
        }
        selector || (selector='');

        nodeid = selector.match(REGEXP_EXTRACT_NODE_ID);
        nodeid ? (subscriber.nId=nodeid[1]) : (subscriber.n=isCustomElement ? context : DOCUMENT);
        byExactId = REGEXP_NODE_ID.test(selector);

        deepSearch = !NO_DEEP_SEARCH[customEvent];
        // set the selector to `subscriber._s` so that e.currentTarget can calculate it:
        subscriber._s = selector;

        subscriber.f = function(e) {
            // this stage is runned when the event happens
            console.log(NAME, '_domSelToFunc inside filter. selector: '+selector);
            var node = e.target,
                vnode = node.vnode,
                character1 = selector && selector.substr(1),
                match = false;
            if (!isCustomElement || visibleContent || context.contains(node)) {
                if (selector==='') {
                    match = true;
                }
                else {
                    // e.target is the most deeply node in the dom-tree that caught the event
                    // our listener uses `selector` which might be a node higher up the tree.
                    // we will reset e.target to this node (if there is a match)
                    // note that e.currentTarget will always be `document` --> we're not interested in that
                    // also, we don't check for `node`, but for node.matchesSelector: the highest level `document`
                    // is not null, yet it doesn't have .matchesSelector so it would fail
                    // we DON'T want this for the focus and blur event!
                    if (deepSearch) {
                        if (vnode) {
                            // we go through the vdom
                            if (!vnode.removedFromDOM) {
                                while (vnode && !match) {
                                    console.log(NAME, '_domSelToFunc inside filter check match using the vdom');
                                    match = byExactId ? (vnode.id===character1) : vnode.matchesSelector(selector);
                                    // if there is a match, then set
                                    // e.target to the target that matches the selector
                                    if (match && !outsideEvent) {
                                        subscriber.t = vnode.domNode;
                                    }
                                    vnode = vnode.vParent;
                                }
                            }
                        }
                        else {
                            // we go through the dom
                            while (node.matchesSelector && !match) {
                                console.log(NAME, '_domSelToFunc inside filter check match using the dom');
                                match = byExactId ? (node.id===character1) : node.matchesSelector(selector);
                                // if there is a match, then set
                                // e.target to the target that matches the selector
                                if (match && !outsideEvent) {
                                    subscriber.t = node;
                                }
                                node = node.parentNode;
                            }
                        }
                    }
                    else {
                        console.log(NAME, '_domSelToFunc inside filter check match using the vdom');
                        if (vnode) {
                            match = byExactId ? (vnode.id===character1) : vnode.matchesSelector(selector);
                        }
                        else {
                            match = node.matchesSelector && (byExactId ? (node.id===character1) : node.matchesSelector(selector));
                        }
                        match && (e.sourceTarget=node);
                    }
                }
            }
            if (outsideEvent && !match) {
                // there is a match for the outside-event:
                // we need to set e.sourceTarget and e.target:
                newTarget = DOCUMENT.getElement(selector, true);
                if (newTarget) {
                    e.sourceTarget = node;
                    subscriber.t = newTarget;
                }
                else {
                    // make return `false` because the selector is not in the dom
                    match = true;
                }
            }
            console.log(NAME, '_domSelToFunc filter returns '+(!outsideEvent ? match : !match));
            return !outsideEvent ? match : !match;
        };
        return true;
    };

    // at this point, we need to find out what are the current node-refs. whenever there is
    // a filter that starts with `#` --> in those cases we have a bubble-chain, because the selector isn't
    // set up with `document` at its root.
    // we couldn't do this at time of subscribtion, for the nodes might not be there at that time.
    // however, we only need to do this once: we store the value if we find them
    // no problem when the nodes leave the dom later: the previous filter wouldn't pass
    _findCurrentTargets = function(subscribers) {
        console.log(NAME, '_findCurrentTargets');
        subscribers.forEach(
            function(subscriber) {
                console.log(NAME, '_findCurrentTargets for single subscriber. nId: '+subscriber.nId);
                subscriber.nId && (subscriber.n=DOCUMENT.getElementById(subscriber.nId, true));
            }
        );
    };

    /*
     * Generates an event through our Event-system. Does the actual transportation from DOM-events
     * into our Eventsystem. It also looks at the response of our Eventsystem: if our system
     * halts or preventDefaults the customEvent, then the original DOM-event will be preventDefaulted.
     *
     * @method _evCallback
     * @param e {Object} eventobject
     * @private
     * @since 0.0.1
     */
    _evCallback = function(e) {
        console.log(NAME, '_evCallback');
        var allSubscribers = Event._subs,
            eType = e.type,
            eTarget = e.target,
            supportHammer = !!Event.Hammer,
            mobileEvents = supportHammer && isMobile,
            eventobject, subs, wildcard_named_subs, named_wildcard_subs, wildcard_wildcard_subs, subsOutside,
            subscribers, eventobjectOutside, wildcard_named_subsOutside, customEvent, eventName, which, payloadGetters;

        eventName = eType;
        // first: a `click` event might be needed to transformed into `rightclick`:
        if (eventName===CLICK) {
            which = e.which;
            (which===2) && (eventName=CENTERCLICK);
            (which===3) && (eventName=RIGHTCLICK);
        }
        if ((eventName===TAP) && (!eTarget.vnode || (eTarget.vnode.tag!=='A'))) {
            // prevent the next click-event
            preventClick = true;
            e.clientX || (e.clientX = e.center && e.center.x);
            e.clientY || (e.clientY = e.center && e.center.y);
        }
        else if (preventClick && (eventName===CLICK)) {
            preventClick = false;
            return;
        }

        if ((eventName===(mobileEvents ? PANSTART : MOUSEDOWN)) && (eTarget.vnode && ((eTarget.vnode.tag==='A') || eTarget.inside('a')))) {
            // backup position in case of inside anchor
            startX = e.clientX || (e.center && e.center.x);
            startY = e.clientY || (e.center && e.center.y);
        }

        if (eventName===CLICK) {
            if (eTarget.vnode && ((e.target.vnode.tag==='A') || eTarget.inside('a'))) {
                eventName = ANCHOR_CLICK;
                e.clientX || (e.clientX = e.center && e.center.x);
                e.clientY || (e.clientY = e.center && e.center.y);
                // ALSO: determine the offset between the latest mousedown and the current mouseposition
                // if there is an offset, then the user is scrolling and doesn't want to follow the link!
                if ((Math.abs(startX-e.clientX)>=ANCHOR_OFFSET) || (Math.abs(startY-e.clientY)>=ANCHOR_OFFSET)) {
                    e.preventDefault();
                    return;
                }
            }
            else {
                eventName = TAP;
                e.center = {
                    x: e.clientX,
                    y: e.clientY
                };
                e.eventType = 4;
                // 'pointerType' is a getter:
                Object.defineProperties(e, {
                    pointerType: {
                        get: function() {
                            return 'mouse';
                        }
                    }
                });
                e.tapCount = 1;
            }
        }

        if (eventName===TAP) {
            Object.defineProperties(e, {
                shiftKey: {
                    get: function() {
                        return _shiftPressed;
                    }
                },
                ctrlKey: {
                    get: function() {
                        return _ctrlPressed;
                    }
                },
                metaKey: {
                    get: function() {
                        return _metaPressed;
                    }
                }
            });
            payloadGetters = {
                shiftKey: _shiftPressed,
                ctrlKey: _ctrlPressed,
                metaKey: _metaPressed
            };
        }

        customEvent = 'UI:'+eventName;

        subs = allSubscribers[customEvent];
        wildcard_named_subs = allSubscribers['*:'+eventName];
        named_wildcard_subs = allSubscribers['UI:*'];
        wildcard_wildcard_subs = allSubscribers['*:*'];

        // Emit the dom-event though our eventsystem:
        // NOTE: emit() needs to be synchronous! otherwise we wouldn't be able
        // to preventDefault in time
        //
        // e = eventobject from the DOM-event OR gesture-event
        // eventobject = eventobject from our Eventsystem, which get returned by calling `emit()`

        // now so the work:
        subscribers = _getSubscribers(e, true, subs, wildcard_named_subs, named_wildcard_subs, wildcard_wildcard_subs);
        eventobject = Event._emit(e.target, customEvent, e, subscribers, [], _preProcessor, false, payloadGetters);

        // now check outside subscribers
        subsOutside = allSubscribers[customEvent+OUTSIDE];
        wildcard_named_subsOutside = allSubscribers['*:'+eventName+OUTSIDE];
        subscribers = _getSubscribers(e, true, subsOutside, wildcard_named_subsOutside);
        eventobjectOutside = Event._emit(e.target, customEvent+OUTSIDE, e, subscribers, [], _preProcessor, false, payloadGetters);

        // if eventobject was preventdefaulted or halted: take appropriate action on
        // the original dom-event. Note: only the original event can caused this, not the outsideevent
        // stopPropagation on the original eventobject has no impact on our eventsystem, but who know who else is watching...
        // be carefull though: not all gesture events have e.stopPropagation
        eventobject.status.halted && e.stopPropagation && e.stopPropagation();
        // now we might need to preventDefault the original event.
        // be carefull though: not all gesture events have e.preventDefault
        if ((eventobject.status.halted || eventobject.status.defaultPrevented || eventobject.status.defaultPreventedContinue) && e.preventDefault) {
            e.preventDefault();
        }

        if (eventobject.status.ok) {
            // last step: invoke the aftersubscribers
            // we need to do this asynchronous: this way we pass them AFTER the DOM-event's defaultFn
            // also make sure to paas-in the payload of the manipulated eventobject
            subscribers = _getSubscribers(eventobject, false, subs, wildcard_named_subs, named_wildcard_subs, wildcard_wildcard_subs);
            (subscribers.length>0) && later(Event._emit.bind(Event, e.target, customEvent, eventobject, [], subscribers, _preProcessor, true, payloadGetters), 10);

            // now check outside subscribers
            subscribers = _getSubscribers(eventobjectOutside, false, subsOutside, wildcard_named_subsOutside);
            (subscribers.length>0) && later(Event._emit.bind(Event, e.target, customEvent+OUTSIDE, eventobjectOutside, [], subscribers, _preProcessor, true, payloadGetters), 10);
        }
    };

    /*
     * Creates an array of subscribers in the right order, conform their position in the DOM.
     * Only subscribers that match the filter are involved.
     *
     * @method _getSubscribers
     * @param e {Object} eventobject
     * @param before {Boolean} whether it is a before- or after-subscriber
     * @param subs {Array} array with subscribers
     * @param wildcard_named_subs {Array} array with subscribers
     * @param named_wildcard_subs {Array} array with subscribers
     * @param wildcard_wildcard_subs {Array} array with subscribers
     * @private
     * @since 0.0.1
     */
    _getSubscribers = function(e, before, subs, wildcard_named_subs, named_wildcard_subs, wildcard_wildcard_subs) {
        var subscribers = [],
            beforeOrAfter = before ? 'b' : 'a',
            saveConcat = function(extrasubs) {
                extrasubs && extrasubs[beforeOrAfter] && (subscribers=subscribers.concat(extrasubs[beforeOrAfter]));
            };
        saveConcat(subs);
        saveConcat(wildcard_named_subs);
        saveConcat(named_wildcard_subs);
        saveConcat(wildcard_wildcard_subs);
        if (subscribers.length>0) {
            subscribers = function(array, testFunc) {
                // quickest way to filter an array: see http://jsperf.com/array-filter-performance/4
                var filtered = array.slice(0), i;
                for (i=array.length-1; i>=0; i--) {
                    console.log(NAME, 'filtercheck for subscriber');
                    testFunc(array[i]) || filtered.splice(i, 1);
                }
                return filtered;
            }(subscribers, function(subscriber) {return (!subscriber.f || subscriber.f.call(subscriber.o, e));});
            if (subscribers.length>0) {
// _findCurrentTargets(subscribers);
                // sorting, based upon the sortFn
                subscribers.sort(SORT);
            }
        }
        return subscribers;
    };

    /*
     * Sets e.target and e.sourceTarget for the single subscriber.
     * Needs to be done for evenry single subscriber, because with a single event, these values change for each subscriber
     *
     * @method _preProcessor
     * @param subscriber {Object} subscriber
     * @param subscriber.o {Object} context
     * @param subscriber.cb {Function} callbackFn
     * @param subscriber.f {Function|String} filter
     * @param e {Object} eventobject
     * @private
     * @since 0.0.1
     */
    _preProcessor = function(subscriber, e) {
        console.log(NAME, '_preProcessor');
        // inside the aftersubscribers, we may need exit right away.
        // this would be the case whenever stopPropagation or stopImmediatePropagation was called
        // in case the subscribernode equals the node on which stopImmediatePropagation was called: return true
        var propagationStopped, immediatePropagationStopped,
            targetnode = (subscriber.t || subscriber.n);

        immediatePropagationStopped = e.status.immediatePropagationStopped;
        if (immediatePropagationStopped && ((immediatePropagationStopped===targetnode) || !immediatePropagationStopped.contains(targetnode))) {
            console.log(NAME, '_preProcessor will return true because of immediatePropagationStopped');
            return true;
        }
        // in case the subscribernode does not fall within or equals the node on which stopPropagation was called: return true
        propagationStopped = e.status.propagationStopped;
        if (propagationStopped && (propagationStopped!==targetnode) && !propagationStopped.contains(targetnode)) {
            console.log(NAME, '_preProcessor will return true because of propagationStopped');
            return true;
        }

        e._s = subscriber._s;

        // now we might need to set e.target to the right node:
        // the filterfunction might have found the true domnode that should act as e.target
        // and set it at subscriber.t
        // also, we need to backup the original e.target: this one should be reset when
        // we encounter a subscriber with its own filterfunction instead of selector
        if (subscriber.t) {
            e.sourceTarget || (e.sourceTarget=e.target);
            e.target = subscriber.t;
        }
        else {
            e.sourceTarget && (e.target=e.sourceTarget);
        }
        return false;
    };

    /*
     * Transports DOM-events to the Event-system. Catches events at their most early stage:
     * their capture-phase. When these events happen, a new customEvent is generated by our own
     * Eventsystem, by calling _evCallback(). This way we keep DOM-events and our Eventsystem completely separated.
     *
     * @method _setupDomListener
     * @param customEvent {String} the customEvent that is transported to the eventsystem
     * @param subscriber {Object} subscriber
     * @param subscriber.o {Object} context
     * @param subscriber.cb {Function} callbackFn
     * @param subscriber.f {Function|String} filter
     * @private
     * @since 0.0.1
     */
    _setupDomListener = function(customEvent, subscriber) {
        console.log(NAME, '_setupDomListener');
        var eventSplitted = customEvent.split(':'),
            emitterName = eventSplitted[0],
            eventName = eventSplitted[1],
            outsideEvent = REGEXP_UI_OUTSIDE.test(eventName);

        // be careful: anyone could also register an `outside`-event.
        // in those cases, the DOM-listener must be set up without `outside`
        outsideEvent && (eventName=eventName.substring(0, eventName.length-7));

        // if eventName equals `mouseover` or `mouseleave` then we quit:
        // people should use `mouseover` and `mouseout`
        if ((eventName==='mouseenter') || (eventName==='mouseleave')) {
            console.warn(NAME, 'Subscription to '+eventName+' not supported, use mouseover and mouseout: this eventsystem uses these non-noisy so they act as mouseenter and mouseleave');
            return;
        }

        // only accept tap-events, yet later on we WILL need to listen for click-events
        (eventName===CLICK) && (eventName=TAP);

        // now transform the subscriber's filter from css-string into a filterfunction
        _selToFunc(emitterName+':'+eventName+(outsideEvent ? OUTSIDE : ''), subscriber);

        // already registered? then return, also return if someone registered for UI:*
        if (DOMEvents[eventName] || (eventName==='*')) {
            // cautious: one might have registered the event, but not yet the outsideevent.
            // in that case: save this setting:
            if (outsideEvent) {
                DOMEvents[eventName+OUTSIDE] = true;
                (eventName===TAP) && (DOMEvents[CLICK+OUTSIDE]=true);
            }
            return;
        }

        DOMEvents[eventName] = true;
        if (outsideEvent) {
            DOMEvents[eventName+OUTSIDE] = true;
            ((eventName===TAP) || (eventName===ANCHOR_CLICK)) && (DOMEvents[CLICK+OUTSIDE]=true);
        }
        // one exception: windowresize should listen to the window-object
        if (eventName==='resize') {
            window.addEventListener(eventName, _evCallback);
        }
        else {
            ((eventName===RIGHTCLICK) || (eventName===CENTERCLICK)) && (eventName=TAP);
            // important: set the third argument `true` so we listen to the capture-phase.
            DOCUMENT.addEventListener(eventName, _evCallback, true);
            // listen for both `tap` and `click` events to happen
            ((eventName===TAP) || (eventName===ANCHOR_CLICK)) && DOCUMENT.addEventListener(CLICK, _evCallback, true);
        }
    };

    _setupEvents = function() {
        var lastFocussed;

        // make sure disabled buttons don't work:
        Event.before([TAP, 'press'], function(e) {
            e.preventDefault();
        }, '.pure-button-disabled, button[disabled]');

        // make sure that a focussed button which recieves an keypress also fires the `tap`-event
        // note: the `click`-event will always be fired by the browser
        Event.before(
            'keydown',
            function(e) {
                e._buttonPressed = true;
                Event.emit(e.target, 'UI:tap', e);
            },
            function(e) {
                var keyCode = e.keyCode;
                return (e.target.getTagName()==='BUTTON') && ((keyCode===13) || (keyCode===32));
            }
        );

        Event.after(
            ['keydown', 'keyup'],
            function(e) {
                _shiftPressed = e.shiftKey; // protected registration: to be set on tap-events
                _ctrlPressed = e.ctrlKey; // protected registration: to be set on tap-events
                _metaPressed = e.metaKey; // protected registration: to be set on tap-events
            }
        );

        // make sure that a focussed button which recieves an keypress also fires the `tap`-event
        // note: the `click`-event will always be fired by the browser
        Event.after(
            TAP,
            function(e) {
                var buttonNode = e.target;
                if (e._buttonPressed) {
                    buttonNode.setClass(PURE_BUTTON_ACTIVE);
                    e._noRender = true;
                    // even if the node isn't in the DOM, we can still try to manipulate it:
                    // the vdom makes sure no errors occur when the node is already removed
                    later(buttonNode.removeClass.bind(buttonNode, PURE_BUTTON_ACTIVE), TIME_BTN_PRESSED);
                }
            }
        );

        // fix activeElement on Mac
        Event.before('focus', function(e) {
            // will come here more often because of bubblechain
            // however, the last pass-through will set the deepest node
            lastFocussed = e.target;
        });


        // patching DOCUMENT.activeElement because it doesn't work well in a Mac: https://developer.mozilla.org/en-US/docs/Web/API/document.activeElement
        // DOCUMENT._activeElement is used with the patch for DOCUMENT.activeElement its getter
        Event.after('focus', function() {
            DOCUMENT._activeElement = lastFocussed;
        });

        Event.before('blur', function() {
            DOCUMENT._activeElement = null;
        });

        // Note: window.document has no prototype
        Object.defineProperty(DOCUMENT, 'activeElement', {
            get: function() {
                return DOCUMENT._activeElement || DOCUMENT.body;
            }
        });

    };

    _setupMutationListener = function() {
        DOCUMENT.hasMutationSubs = true;
        if (!mutationEventsDefined) {
            Event.defineEvent(EV_REMOVED).unPreventable();
            Event.defineEvent(EV_INSERTED).unPreventable();
            Event.defineEvent(EV_CONTENT_CHANGE).unPreventable();
            Event.defineEvent(EV_ATTRIBUTE_REMOVED).unPreventable();
            Event.defineEvent(EV_ATTRIBUTE_CHANGED).unPreventable();
            Event.defineEvent(EV_ATTRIBUTE_INSERTED).unPreventable();
            mutationEventsDefined = true;
        }
    };

    /*
     *
     * @method _sortFunc
     * @param customEvent {String}
     * @private
     * @return {Function|undefined} sortable function
     * @since 0.0.1
     */
    _sortFunc = function(subscriberOne, subscriberTwo) {
        return (subscriberTwo.t || subscriberTwo.n).contains(subscriberOne.t || subscriberOne.n) ? -1 : 1;
    };

    /*
     *
     * @method _sortFunc
     * @param customEvent {String}
     * @private
     * @return {Function|undefined} sortable function
     * @since 0.0.1
     */
    _sortFuncReversed = function(subscriberOne, subscriberTwo) {
        return (subscriberOne.t || subscriberOne.n).contains(subscriberTwo.t || subscriberTwo.n) ? 1 : -1;
    };

    /*
     * Removes DOM-eventsubscribers from document when they are no longer needed.
     *
     * @method _teardownDomListener
     * @param customEvent {String} the customEvent that is transported to the eventsystem
     * @private
     * @since 0.0.2
     */
    _teardownDomListener = function(customEvent) {
        var customEventWithoutOutside = customEvent.endsWith(OUTSIDE) ? customEvent.substr(0, customEvent.length-7) : customEvent,
            eventSplitted = customEventWithoutOutside.split(':'),
            eventName = eventSplitted[1],
            stillInUse;

        if ((customEventWithoutOutside===CLICK) || (customEventWithoutOutside===RIGHTCLICK) || (customEventWithoutOutside===CENTERCLICK)) {
            stillInUse = Event._subs[CLICK] ||
                         Event._subs[CLICK+OUTSIDE];
                         Event._subs[RIGHTCLICK] ||
                         Event._subs[RIGHTCLICK+OUTSIDE],
                         Event._subs[CENTERCLICK] ||
                         Event._subs[CENTERCLICK+OUTSIDE];
            eventName = CLICK;
        }
        else {
            stillInUse = Event._subs[customEventWithoutOutside] || Event._subs[customEventWithoutOutside+OUTSIDE];
        }
        if (!stillInUse) {
            console.log(NAME, '_teardownDomListener '+customEvent);
            // remove eventlistener from `document`
            // one exeption: windowresize should listen to the window-object
            if (eventName==='resize') {
                window.removeEventListener(eventName, _evCallback);
            }
            else {
                // important: set the third argument `true` so we listen to the capture-phase.
                DOCUMENT.removeEventListener(eventName, _evCallback, true);
            }
            delete DOMEvents[eventName];
        }
    };

    _teardownMutationListener = function() {
        if (!Event._subs[EV_REMOVED] &&
            !Event._subs[EV_INSERTED] &&
            !Event._subs[EV_CONTENT_CHANGE] &&
            !Event._subs[EV_ATTRIBUTE_REMOVED] &&
            !Event._subs[EV_ATTRIBUTE_CHANGED] &&
            !Event._subs[EV_ATTRIBUTE_INSERTED]
        ) {
            DOCUMENT.hasMutationSubs = false;
        }
    };

    // Now a very tricky one:
    // Some browsers do an array.sort down-top instead of top-down.
    // In those cases we need another sortFn, for the position on an equal match should fall
    // behind instead of before (which is the case on top-down sort)
    [1,2].sort(function(a /*, b */) {
        SORT || (SORT=(a===2) ? _sortFuncReversed : _sortFunc);
    });

    // Now we do some initialization in order to make DOM-events work:

    Object.defineProperty(Event._defaultEventObj, 'currentTarget', {
        get: function() {
            var ev = this,
                e_target = ev.target;
            if (e_target && ev._s) {
                if (e_target.matches(ev._s)) {
                    return e_target;
                }
                return e_target.inside(ev._s) || undefined;
            }
        }
    });

    // Notify when someone subscribes to an UI:* event
    // if so: then we might need to define a customEvent for it:
    // alse define the specific DOM-methods that can be called on the eventobject: `stopPropagation` and `stopImmediatePropagation`
    Event.notify(UI+'*', _setupDomListener, Event)
         ._setEventObjProperty('stopPropagation', function() {this.status.ok || (this.status.propagationStopped = this.target);})
         ._setEventObjProperty('stopImmediatePropagation', function() {this.status.ok || (this.status.immediatePropagationStopped = this.target);});

    // Notify when someone subscribes to any event at all --> we might need to transform the filterFn from a selector into a true fnction
    // this is already done automaticly by _setupDomListener fo UI:* events
    Event.notify('*:*', function(customEvent, subscriber) {
        var eventSplitted = customEvent.split(':'),
            emitterName = eventSplitted[0];
        if ((emitterName!=='UI') && (typeof subscriber.f==='string')) {
            // now transform the subscriber's filter from css-string into a filterfunction
            _selToFunc(customEvent, subscriber);
        }
    }, Event);

    // Notify when someone detaches an UI:* event
    // if so: then we might need to detach the native listener on `document`
    Event.notifyDetach(UI+'*', _teardownDomListener, Event);

    Event._sellist = [_domSelToFunc];

    _setupEvents();

    // making Element to be able to emit using event-emitter.
    // NOT only HTMLElements --> SVGElements need to have this emitter to:
    (function(ElementPrototype) {
        ElementPrototype.merge(Event.Emitter('UI'));

       /**
        * Gets one Element, specified by the css-selector. Either when alreasy available, or when it gets inserted in the dom.
        * To retrieve a single element by id,
        * you need to prepend the id-name with a `#`. When multiple Element's match, the first is returned.
        *
        * Returns a Promise with the Element as variable.
        *
        * @method getElementOnAvailable
        * @for Element
        * @param cssSelector {String} css-selector to match
        * @param [inspectProtectedNodes=false] {Boolean} no deepsearch in protected Nodes or iTags --> by default, these elements should be hidden
        * @return {Promise} with the Element that was search for as variable.
        * @since 0.0.1
        */
        ElementPrototype.getElementOnAvailable = function(cssSelector, inspectProtectedNodes) {
            var instance = this;
            // node not currently in the dom --> setup a listener:
            return new window.Promise(function(resolve) {
                var node, listener;
                node = instance.getElement(cssSelector, inspectProtectedNodes);
                if (node) {
                    resolve(node);
                }
                else {
                    listener = Event.after('nodeinsert', function() {
                        // because it could be that `inspectProtectedNodes` prevents the node from return as a truthy value,
                        // we need to check again if the new node matches:
                        var newnode = instance.getElement(cssSelector, inspectProtectedNodes);
                        if (newnode) {
                            resolve(newnode);
                            listener.detach();
                        }
                    }, cssSelector);
                }
            });
        };

    }(window.Element.prototype));

    DOCUMENT.getElementOnAvailable = function(cssSelector, inspectProtectedNodes) {
        return DOCUMENT.documentElement.getElementOnAvailable(cssSelector, inspectProtectedNodes);
    };

    // Notify when someone subscribes to an UI:* event
    // if so: then we might need to define a customEvent for it:
    // alse define the specific DOM-methods that can be called on the eventobject: `stopPropagation` and `stopImmediatePropagation`
    Event.notify(MUTATION_EVENTS, _setupMutationListener, Event);

    // Notify when someone detaches an UI:* event
    // if so: then we might need to detach the native listener on `document`
    Event.notifyDetach(MUTATION_EVENTS, _teardownMutationListener, Event);

    // Note: window.document has no prototype
    DOCUMENT.suppressMutationEvents = function(suppress) {
        this._suppressMutationEvents = suppress;
    };

    // Event.noDeepDomEvt and Event._domCallback are the only method that is added to Event.
    // We need to do this, because `event-mobile` needs access to the same method.
    // We could have done without this method and instead listen for a custom-event to handle
    // Mobile events, however, that would lead into 2 eventcycli which isn't performant.

   /**
    *
    * @method noDeepDomEvt
    * @param domEvent {String} the eventName that should be processed without deepsearch
    * @param e {Object} eventobject
    * @for Event
    * @chainable
    * @since 0.0.1
    */
    Event.noDeepDomEvt = function(domEvent) {
        domEvent.contains(':') || (domEvent=UI+domEvent);
        NO_DEEP_SEARCH[domEvent] = true;
        return this;
    };

   /**
    * Does the actual transportation from DOM-events into the Eventsystem. It also looks at the response of
    * the Eventsystem: on e.halt() or e.preventDefault(), the original DOM-event will be preventDefaulted.
    *
    * @method _domCallback
    * @param eventName {String} the customEvent that is transported to the eventsystem
    * @param e {Object} eventobject
    * @private
    * @since 0.0.1
    */
    Event._domCallback = function(e) {
        _evCallback(e);
    };

    // store module:
    window._ITSAmodules.EventDom = Event;
    return Event;
};