diff --git a/src/element/fire.js b/src/element/fire.js index 9762664b..f5a25728 100644 --- a/src/element/fire.js +++ b/src/element/fire.js @@ -1,3 +1,4 @@ +import _ from "../util/index"; import { DOM } from "../types"; import { MethodError } from "../errors"; import { JSCRIPT_VERSION, DOCUMENT, CUSTOM_EVENT_TYPE } from "../const"; @@ -18,14 +19,13 @@ DOM.register({ */ fire(type) { var node = this[0], - eventType = typeof type, - handler = {}, - hook, e, canContinue; + e, eventType, canContinue; - if (eventType === "string") { - if (hook = HOOK[type]) { - handler = hook(handler) || handler; - } + if (typeof type === "string") { + let hook = HOOK[type], + handler = {}; + + if (hook) handler = hook(handler) || handler; eventType = handler._type || type; } else { @@ -50,12 +50,12 @@ DOM.register({ canContinue = node.dispatchEvent(e); } - // Call native method. IE<9 dies on focus/blur to hidden element - if (canContinue && node[type] && (type !== "focus" && type !== "blur" || node.offsetWidth)) { - // Prevent re-triggering of the same event + // call native function to trigger default behavior + if (canContinue && node[type]) { + // prevent re-triggering of the current event EventHandler.skip = type; - node[type](); + _.safeInvoke(node, type); EventHandler.skip = null; } diff --git a/src/util/eventhandler.js b/src/util/eventhandler.js index 89839f4b..5aefc53c 100644 --- a/src/util/eventhandler.js +++ b/src/util/eventhandler.js @@ -4,109 +4,107 @@ import { $Element } from "../types"; import SelectorMatcher from "./selectormatcher"; import HOOK from "./eventhooks"; -/* - * Helper type to create an event handler - */ +function getEventProperty(name, e, type, node, target, currentTarget) { + if (typeof name === "number") { + return e.detail ? e.detail[name] : void 0; + } -var EventHandler = (type, selector, callback, props, el, once) => { - var node = el[0], - hook = HOOK[type], - matcher = SelectorMatcher(selector, node), - handler = (e) => { - e = e || WINDOW.event; - // early stop in case of default action - if (EventHandler.skip === type) return; - /* istanbul ignore if */ - if (handler._type === CUSTOM_EVENT_TYPE && e.srcUrn !== type) { - return; // handle custom events in legacy IE - } - // srcElement can be null in legacy IE when target is document - var target = e.target || e.srcElement || DOCUMENT, - currentTarget = matcher ? matcher(target) : node, - args; + if (typeof name !== "string") return name; + /* istanbul ignore if */ + if (JSCRIPT_VERSION < 9) { + switch (name) { + case "which": + return e.keyCode; + case "button": + var button = e.button; + // click: 1 === left; 2 === middle; 3 === right + return button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ); + case "pageX": + return e.clientX + HTML.scrollLeft - HTML.clientLeft; + case "pageY": + return e.clientY + HTML.scrollTop - HTML.clientTop; + case "preventDefault": + return () => e.returnValue = false; + case "stopPropagation": + return () => e.cancelBubble = true; + } + } - // early stop for late binding or when target doesn't match selector - if (!currentTarget) return; + switch (name) { + case "type": + return type; + case "defaultPrevented": + // IE8 and Android 2.3 use returnValue instead of defaultPrevented + return "defaultPrevented" in e ? e.defaultPrevented : e.returnValue === false; + case "target": + return $Element(target); + case "currentTarget": + return $Element(currentTarget); + case "relatedTarget": + return $Element(e.relatedTarget || e[(e.toElement === node ? "from" : "to") + "Element"]); + } - // off callback even if it throws an exception later - if (once) el.off(type, callback); + var value = e[name]; - if (!props || !props.length) { - // if props is not specified then prepend extra arguments - args = e.detail ? _.slice.call(e.detail, 1) : []; - } else { - args = props.map((name) => { - if (typeof name === "number") { - return e.detail ? e.detail[name] : void 0; - } + if (typeof value === "function") { + return () => value.apply(e, arguments); + } - if (typeof name !== "string") return name; - /* istanbul ignore if */ - if (JSCRIPT_VERSION < 9) { - switch (name) { - case "which": - return e.keyCode; - case "button": - var button = e.button; - // click: 1 === left; 2 === middle; 3 === right - return button & 1 ? 1 : ( button & 2 ? 3 : ( button & 4 ? 2 : 0 ) ); - case "pageX": - return e.clientX + HTML.scrollLeft - HTML.clientLeft; - case "pageY": - return e.clientY + HTML.scrollTop - HTML.clientTop; - case "preventDefault": - return () => e.returnValue = false; - case "stopPropagation": - return () => e.cancelBubble = true; - } - } + return value; +} - switch (name) { - case "type": - return type; - case "defaultPrevented": - // IE8 and Android 2.3 use returnValue instead of defaultPrevented - return "defaultPrevented" in e ? e.defaultPrevented : e.returnValue === false; - case "target": - return $Element(target); - case "currentTarget": - return $Element(currentTarget); - case "relatedTarget": - return $Element(e.relatedTarget || e[(e.toElement === node ? "from" : "to") + "Element"]); - } +function EventHandler(type, selector, callback, props, el, once) { + var node = el[0], + hook = HOOK[type], + matcher = SelectorMatcher(selector, node), + handler = (e) => { + e = e || WINDOW.event; + // early stop in case of default action + if (EventHandler.skip === type) return; + /* istanbul ignore if */ + if (handler._type === CUSTOM_EVENT_TYPE && e.srcUrn !== type) { + return; // handle custom events in legacy IE + } + // srcElement can be null in legacy IE when target is document + var target = e.target || e.srcElement || DOCUMENT, + currentTarget = matcher ? matcher(target) : node, + args; - var value = e[name]; + // early stop for late binding or when target doesn't match selector + if (!currentTarget) return; - if (typeof value === "function") { - return () => value.apply(e, arguments); - } + // off callback even if it throws an exception later + if (once) el.off(type, callback); - return value; - }); - } + if (props && props.length) { + args = props.map((name) => getEventProperty(name, e, type, node, target, currentTarget)); + } else { + // if props is not specified then prepend extra arguments + args = e.detail ? _.slice.call(e.detail, 1) : []; + } - // prevent default if handler returns false - if (callback.apply(el, args) === false) { - /* istanbul ignore if */ - if (JSCRIPT_VERSION < 9) { - e.returnValue = false; - } else { - e.preventDefault(); - } + // prevent default if handler returns false + if (callback.apply(el, args) === false) { + /* istanbul ignore if */ + if (JSCRIPT_VERSION < 9) { + e.returnValue = false; + } else { + e.preventDefault(); } - }; + } + }; - if (hook) handler = hook(handler, type) || handler; - /* istanbul ignore next */ - if (JSCRIPT_VERSION < 9 && !("on" + (handler._type || type) in node)) { - // handle custom events for IE8 - handler._type = CUSTOM_EVENT_TYPE; - } + if (hook) handler = hook(handler, type) || handler; + /* istanbul ignore next */ + if (JSCRIPT_VERSION < 9 && !("on" + (handler._type || type) in node)) { + // handle custom events for IE8 + handler._type = CUSTOM_EVENT_TYPE; + } - handler.type = selector ? type + " " + selector : type; - handler.callback = callback; + handler.type = selector ? type + " " + selector : type; + handler.callback = callback; - return handler; - }; + return handler; +} export default EventHandler;