UNPKG

tui-dom

Version:
340 lines (293 loc) 9.15 kB
/** * DOM event utility module. * @fileoverview Module for handle DOM events * @author NHN Ent. FE Development Lab <dl_javascript@nhnent.com> */ import {getRect} from './domutil'; import util from 'tui-code-snippet'; const EVENT_KEY = '_feEventKey'; /** * @module * @ignore */ /** * Get event collection for specific HTML element * @param {HTMLElement} element - HTML element * @param {string} [type] - event type * @returns {(object|Map)} */ function safeEvent(element, type) { let events = element[EVENT_KEY]; if (!events) { events = element[EVENT_KEY] = {}; } if (type) { let handlerMap = events[type]; if (!handlerMap) { handlerMap = events[type] = new util.Map(); } events = handlerMap; } return events; } /** * Memorize DOM event handler for unbinding * @param {HTMLElement} element - element to bind events * @param {string} type - events name * @param {function} keyFn - handler function that user passed at on() use * @param {function} valueFn - handler function that wrapped by domevent for * implementing some features */ function memorizeHandler(element, type, keyFn, valueFn) { const map = safeEvent(element, type); let items = map.get(keyFn); if (items) { items.push(valueFn); } else { items = [valueFn]; map.set(keyFn, items); } } /** * Forget memorized DOM event handlers * @param {HTMLElement} element - element to bind events * @param {string} type - events name * @param {function} keyFn - handler function that user passed at on() use */ function forgetHandler(element, type, keyFn) { safeEvent(element, type).delete(keyFn); } /** * Bind DOM events * @param {HTMLElement} element - element to bind events * @param {string} type - events name * @param {function} handler - handler function or context for handler * method * @param {object} [context] context - context for handler method. */ function bindEvent(element, type, handler, context) { /** * Event handler * @param {Event} e - event object */ function eventHandler(e) { handler.call(context || element, e || window.event); } /** * Event handler for normalize mouseenter event * @param {MouseEvent} e - event object */ function mouseEnterHandler(e) { e = e || window.event; if (checkMouse(element, e)) { eventHandler(e); } } if ('addEventListener' in element) { if (type === 'mouseenter' || type === 'mouseleave') { type = (type === 'mouseenter') ? 'mouseover' : 'mouseout'; element.addEventListener(type, mouseEnterHandler); memorizeHandler(element, type, handler, mouseEnterHandler); } else { element.addEventListener(type, eventHandler); memorizeHandler(element, type, handler, eventHandler); } } else if ('attachEvent' in element) { element.attachEvent(`on${type}`, eventHandler); memorizeHandler(element, type, handler, eventHandler); } } /** * Unbind DOM events * @param {HTMLElement} element - element to unbind events * @param {string} type - events name * @param {function} handler - handler function or context for handler * method */ function unbindEvent(element, type, handler) { const events = safeEvent(element, type); const items = events.get(handler); if (!items) { return; } forgetHandler(element, type, handler); util.forEach(items, func => { if ('removeEventListener' in element) { element.removeEventListener(type, func); } else if ('detachEvent' in element) { element.detachEvent(`on${type}`, func); } }); } /** * Bind DOM events * @param {HTMLElement} element - element to bind events * @param {(string|object)} types - Space splitted events names or * eventName:handler object * @param {(function|object)} handler - handler function or context for handler * method * @param {object} [context] context - context for handler method. * @name on * @memberof tui.dom * @function */ export function on(element, types, handler, context) { if (util.isString(types)) { util.forEach(types.split(/\s+/g), type => { bindEvent(element, type, handler, context); }); return; } util.forEach(types, (func, type) => { bindEvent(element, type, func, handler); }); } /** * Bind DOM event. this event will unbind after invokes. * @param {HTMLElement} element - HTMLElement to bind events. * @param {(string|object)} types - Space splitted events names or * eventName:handler object. * @param {*} handler - handler function or context for handler method. * @param {*} [context] - context object for handler method. * @name once * @memberof tui.dom * @function */ export function once(element, types, handler, context) { if (util.isObject(types)) { for (const [fn, type] of types) { once(element, type, fn, handler); } return; } const onceHandler = (...args) => { handler.apply(context || element, args); off(element, types, onceHandler, context); }; on(element, types, onceHandler, context); } /** * Unbind DOM events * @param {HTMLElement} element - element to unbindbind events * @param {(string|object)} types - Space splitted events names or * eventName:handler object * @param {(function|object)} handler - handler function or context for handler * method * @name off * @memberof tui.dom * @function */ export function off(element, types, handler) { if (util.isString(types)) { util.forEach(types.split(/\s+/g), type => { unbindEvent(element, type, handler); }); return; } util.forEach(types, (func, type) => { unbindEvent(element, type, func); }); } /** * Check mouse was leave event element with ignoreing child nodes * @param {HTMLElement} element - element to check * @param {MouseEvent} e - mouse event * @returns {boolean} whether mouse leave element? * @name checkMouse * @memberof tui.dom * @function */ export function checkMouse(element, e) { let related = e.relatedTarget; if (!related) { return true; } try { while (related && (related !== element)) { related = related.parentNode; } } catch (err) { return false; } return (related !== element); } const primaryButton = ['0', '1', '3', '5', '7']; const secondaryButton = ['2', '6']; const wheelButton = ['4']; const isStandardMouseEvent = !_isIE8AndEarlier(); /** * test if browser is IE8 and earlier(IE6 or IE7) * @returns {boolean} - whether browser is IE6 ~ 8 or not * @private */ export function _isIE8AndEarlier() { return (navigator.userAgent.indexOf('msie 8') > -1) || (navigator.userAgent.indexOf('msie 7') > -1) || (navigator.userAgent.indexOf('msie 6') > -1); } /** * Normalize mouse event's button attributes. * * Can detect which button is clicked by this method. * * Meaning of return numbers * * - 0: primary mouse button * - 1: wheel button or center button * - 2: secondary mouse button * @param {MouseEvent} mouseEvent - The mouse event object want to know. * @returns {number} - The value of meaning which button is clicked? * @name getMouseButton * @memberof tui.dom * @function */ export function getMouseButton(mouseEvent) { if (isStandardMouseEvent) { return mouseEvent.button; } return _getMouseButtonIE8AndEarlier(mouseEvent); } /** * Normalize return value of mouseEvent.button * Make same to standard MouseEvent's button value * @param {DispCEventObj} mouseEvent - mouse event object * @returns {number|null} - id indicating which mouse button is clicked * @private */ export function _getMouseButtonIE8AndEarlier(mouseEvent) { const button = String(mouseEvent.button); if (util.inArray(button, primaryButton) > -1) { return 0; } else if (util.inArray(button, secondaryButton) > -1) { return 2; } else if (util.inArray(button, wheelButton) > -1) { return 1; } return null; } /** * Get mouse position from mouse event * * If supplied relatveElement parameter then return relative position based on * element * @param {(MouseEvent|object|number[])} position - mouse position object * @param {HTMLElement} relativeElement HTML element that calculate relative * position * @returns {number[]} mouse position * @name getMousePosition * @memberof tui.dom * @function */ export function getMousePosition(position, relativeElement) { const isArray = util.isArray(position); const clientX = isArray ? position[0] : position.clientX; const clientY = isArray ? position[1] : position.clientY; if (!relativeElement) { return [clientX, clientY]; } const rect = getRect(relativeElement); return [ clientX - rect.left - relativeElement.clientLeft, clientY - rect.top - relativeElement.clientTop ]; }