| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557 |
- /**
- * EventUtils.js
- *
- * Copyright, Moxiecode Systems AB
- * Released under LGPL License.
- *
- * License: http://www.tinymce.com/license
- * Contributing: http://www.tinymce.com/contributing
- */
- /*jshint loopfunc:true*/
- /*eslint no-loop-func:0 */
- define("tinymce/dom/EventUtils", [], function() {
- "use strict";
- var eventExpandoPrefix = "mce-data-";
- var mouseEventRe = /^(?:mouse|contextmenu)|click/;
- var deprecated = {keyLocation: 1, layerX: 1, layerY: 1, returnValue: 1};
- /**
- * Binds a native event to a callback on the speified target.
- */
- function addEvent(target, name, callback, capture) {
- if (target.addEventListener) {
- target.addEventListener(name, callback, capture || false);
- } else if (target.attachEvent) {
- target.attachEvent('on' + name, callback);
- }
- }
- /**
- * Unbinds a native event callback on the specified target.
- */
- function removeEvent(target, name, callback, capture) {
- if (target.removeEventListener) {
- target.removeEventListener(name, callback, capture || false);
- } else if (target.detachEvent) {
- target.detachEvent('on' + name, callback);
- }
- }
- /**
- * Normalizes a native event object or just adds the event specific methods on a custom event.
- */
- function fix(originalEvent, data) {
- var name, event = data || {}, undef;
- // Dummy function that gets replaced on the delegation state functions
- function returnFalse() {
- return false;
- }
- // Dummy function that gets replaced on the delegation state functions
- function returnTrue() {
- return true;
- }
- // Copy all properties from the original event
- for (name in originalEvent) {
- // layerX/layerY is deprecated in Chrome and produces a warning
- if (!deprecated[name]) {
- event[name] = originalEvent[name];
- }
- }
- // Normalize target IE uses srcElement
- if (!event.target) {
- event.target = event.srcElement || document;
- }
- // Calculate pageX/Y if missing and clientX/Y available
- if (originalEvent && mouseEventRe.test(originalEvent.type) && originalEvent.pageX === undef && originalEvent.clientX !== undef) {
- var eventDoc = event.target.ownerDocument || document;
- var doc = eventDoc.documentElement;
- var body = eventDoc.body;
- event.pageX = originalEvent.clientX + (doc && doc.scrollLeft || body && body.scrollLeft || 0 ) -
- ( doc && doc.clientLeft || body && body.clientLeft || 0);
- event.pageY = originalEvent.clientY + (doc && doc.scrollTop || body && body.scrollTop || 0 ) -
- ( doc && doc.clientTop || body && body.clientTop || 0);
- }
- // Add preventDefault method
- event.preventDefault = function() {
- event.isDefaultPrevented = returnTrue;
- // Execute preventDefault on the original event object
- if (originalEvent) {
- if (originalEvent.preventDefault) {
- originalEvent.preventDefault();
- } else {
- originalEvent.returnValue = false; // IE
- }
- }
- };
- // Add stopPropagation
- event.stopPropagation = function() {
- event.isPropagationStopped = returnTrue;
- // Execute stopPropagation on the original event object
- if (originalEvent) {
- if (originalEvent.stopPropagation) {
- originalEvent.stopPropagation();
- } else {
- originalEvent.cancelBubble = true; // IE
- }
- }
- };
- // Add stopImmediatePropagation
- event.stopImmediatePropagation = function() {
- event.isImmediatePropagationStopped = returnTrue;
- event.stopPropagation();
- };
- // Add event delegation states
- if (!event.isDefaultPrevented) {
- event.isDefaultPrevented = returnFalse;
- event.isPropagationStopped = returnFalse;
- event.isImmediatePropagationStopped = returnFalse;
- }
- return event;
- }
- /**
- * Bind a DOMContentLoaded event across browsers and executes the callback once the page DOM is initialized.
- * It will also set/check the domLoaded state of the event_utils instance so ready isn't called multiple times.
- */
- function bindOnReady(win, callback, eventUtils) {
- var doc = win.document, event = {type: 'ready'};
- if (eventUtils.domLoaded) {
- callback(event);
- return;
- }
- // Gets called when the DOM is ready
- function readyHandler() {
- if (!eventUtils.domLoaded) {
- eventUtils.domLoaded = true;
- callback(event);
- }
- }
- function waitForDomLoaded() {
- // Check complete or interactive state if there is a body
- // element on some iframes IE 8 will produce a null body
- if (doc.readyState === "complete" || (doc.readyState === "interactive" && doc.body)) {
- removeEvent(doc, "readystatechange", waitForDomLoaded);
- readyHandler();
- }
- }
- function tryScroll() {
- try {
- // If IE is used, use the trick by Diego Perini licensed under MIT by request to the author.
- // http://javascript.nwbox.com/IEContentLoaded/
- doc.documentElement.doScroll("left");
- } catch (ex) {
- setTimeout(tryScroll, 0);
- return;
- }
- readyHandler();
- }
- // Use W3C method
- if (doc.addEventListener) {
- if (doc.readyState === "complete") {
- readyHandler();
- } else {
- addEvent(win, 'DOMContentLoaded', readyHandler);
- }
- } else {
- // Use IE method
- addEvent(doc, "readystatechange", waitForDomLoaded);
- // Wait until we can scroll, when we can the DOM is initialized
- if (doc.documentElement.doScroll && win.self === win.top) {
- tryScroll();
- }
- }
- // Fallback if any of the above methods should fail for some odd reason
- addEvent(win, 'load', readyHandler);
- }
- /**
- * This class enables you to bind/unbind native events to elements and normalize it's behavior across browsers.
- */
- function EventUtils() {
- var self = this, events = {}, count, expando, hasFocusIn, hasMouseEnterLeave, mouseEnterLeave;
- expando = eventExpandoPrefix + (+new Date()).toString(32);
- hasMouseEnterLeave = "onmouseenter" in document.documentElement;
- hasFocusIn = "onfocusin" in document.documentElement;
- mouseEnterLeave = {mouseenter: 'mouseover', mouseleave: 'mouseout'};
- count = 1;
- // State if the DOMContentLoaded was executed or not
- self.domLoaded = false;
- self.events = events;
- /**
- * Executes all event handler callbacks for a specific event.
- *
- * @private
- * @param {Event} evt Event object.
- * @param {String} id Expando id value to look for.
- */
- function executeHandlers(evt, id) {
- var callbackList, i, l, callback, container = events[id];
- callbackList = container && container[evt.type];
- if (callbackList) {
- for (i = 0, l = callbackList.length; i < l; i++) {
- callback = callbackList[i];
- // Check if callback exists might be removed if a unbind is called inside the callback
- if (callback && callback.func.call(callback.scope, evt) === false) {
- evt.preventDefault();
- }
- // Should we stop propagation to immediate listeners
- if (evt.isImmediatePropagationStopped()) {
- return;
- }
- }
- }
- }
- /**
- * Binds a callback to an event on the specified target.
- *
- * @method bind
- * @param {Object} target Target node/window or custom object.
- * @param {String} names Name of the event to bind.
- * @param {function} callback Callback function to execute when the event occurs.
- * @param {Object} scope Scope to call the callback function on, defaults to target.
- * @return {function} Callback function that got bound.
- */
- self.bind = function(target, names, callback, scope) {
- var id, callbackList, i, name, fakeName, nativeHandler, capture, win = window;
- // Native event handler function patches the event and executes the callbacks for the expando
- function defaultNativeHandler(evt) {
- executeHandlers(fix(evt || win.event), id);
- }
- // Don't bind to text nodes or comments
- if (!target || target.nodeType === 3 || target.nodeType === 8) {
- return;
- }
- // Create or get events id for the target
- if (!target[expando]) {
- id = count++;
- target[expando] = id;
- events[id] = {};
- } else {
- id = target[expando];
- }
- // Setup the specified scope or use the target as a default
- scope = scope || target;
- // Split names and bind each event, enables you to bind multiple events with one call
- names = names.split(' ');
- i = names.length;
- while (i--) {
- name = names[i];
- nativeHandler = defaultNativeHandler;
- fakeName = capture = false;
- // Use ready instead of DOMContentLoaded
- if (name === "DOMContentLoaded") {
- name = "ready";
- }
- // DOM is already ready
- if (self.domLoaded && name === "ready" && target.readyState == 'complete') {
- callback.call(scope, fix({type: name}));
- continue;
- }
- // Handle mouseenter/mouseleaver
- if (!hasMouseEnterLeave) {
- fakeName = mouseEnterLeave[name];
- if (fakeName) {
- nativeHandler = function(evt) {
- var current, related;
- current = evt.currentTarget;
- related = evt.relatedTarget;
- // Check if related is inside the current target if it's not then the event should
- // be ignored since it's a mouseover/mouseout inside the element
- if (related && current.contains) {
- // Use contains for performance
- related = current.contains(related);
- } else {
- while (related && related !== current) {
- related = related.parentNode;
- }
- }
- // Fire fake event
- if (!related) {
- evt = fix(evt || win.event);
- evt.type = evt.type === 'mouseout' ? 'mouseleave' : 'mouseenter';
- evt.target = current;
- executeHandlers(evt, id);
- }
- };
- }
- }
- // Fake bubbeling of focusin/focusout
- if (!hasFocusIn && (name === "focusin" || name === "focusout")) {
- capture = true;
- fakeName = name === "focusin" ? "focus" : "blur";
- nativeHandler = function(evt) {
- evt = fix(evt || win.event);
- evt.type = evt.type === 'focus' ? 'focusin' : 'focusout';
- executeHandlers(evt, id);
- };
- }
- // Setup callback list and bind native event
- callbackList = events[id][name];
- if (!callbackList) {
- events[id][name] = callbackList = [{func: callback, scope: scope}];
- callbackList.fakeName = fakeName;
- callbackList.capture = capture;
- // Add the nativeHandler to the callback list so that we can later unbind it
- callbackList.nativeHandler = nativeHandler;
- // Check if the target has native events support
- if (name === "ready") {
- bindOnReady(target, nativeHandler, self);
- } else {
- addEvent(target, fakeName || name, nativeHandler, capture);
- }
- } else {
- if (name === "ready" && self.domLoaded) {
- callback({type: name});
- } else {
- // If it already has an native handler then just push the callback
- callbackList.push({func: callback, scope: scope});
- }
- }
- }
- target = callbackList = 0; // Clean memory for IE
- return callback;
- };
- /**
- * Unbinds the specified event by name, name and callback or all events on the target.
- *
- * @method unbind
- * @param {Object} target Target node/window or custom object.
- * @param {String} names Optional event name to unbind.
- * @param {function} callback Optional callback function to unbind.
- * @return {EventUtils} Event utils instance.
- */
- self.unbind = function(target, names, callback) {
- var id, callbackList, i, ci, name, eventMap;
- // Don't bind to text nodes or comments
- if (!target || target.nodeType === 3 || target.nodeType === 8) {
- return self;
- }
- // Unbind event or events if the target has the expando
- id = target[expando];
- if (id) {
- eventMap = events[id];
- // Specific callback
- if (names) {
- names = names.split(' ');
- i = names.length;
- while (i--) {
- name = names[i];
- callbackList = eventMap[name];
- // Unbind the event if it exists in the map
- if (callbackList) {
- // Remove specified callback
- if (callback) {
- ci = callbackList.length;
- while (ci--) {
- if (callbackList[ci].func === callback) {
- var nativeHandler = callbackList.nativeHandler;
- var fakeName = callbackList.fakeName, capture = callbackList.capture;
- // Clone callbackList since unbind inside a callback would otherwise break the handlers loop
- callbackList = callbackList.slice(0, ci).concat(callbackList.slice(ci + 1));
- callbackList.nativeHandler = nativeHandler;
- callbackList.fakeName = fakeName;
- callbackList.capture = capture;
- eventMap[name] = callbackList;
- }
- }
- }
- // Remove all callbacks if there isn't a specified callback or there is no callbacks left
- if (!callback || callbackList.length === 0) {
- delete eventMap[name];
- removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture);
- }
- }
- }
- } else {
- // All events for a specific element
- for (name in eventMap) {
- callbackList = eventMap[name];
- removeEvent(target, callbackList.fakeName || name, callbackList.nativeHandler, callbackList.capture);
- }
- eventMap = {};
- }
- // Check if object is empty, if it isn't then we won't remove the expando map
- for (name in eventMap) {
- return self;
- }
- // Delete event object
- delete events[id];
- // Remove expando from target
- try {
- // IE will fail here since it can't delete properties from window
- delete target[expando];
- } catch (ex) {
- // IE will set it to null
- target[expando] = null;
- }
- }
- return self;
- };
- /**
- * Fires the specified event on the specified target.
- *
- * @method fire
- * @param {Object} target Target node/window or custom object.
- * @param {String} name Event name to fire.
- * @param {Object} args Optional arguments to send to the observers.
- * @return {EventUtils} Event utils instance.
- */
- self.fire = function(target, name, args) {
- var id;
- // Don't bind to text nodes or comments
- if (!target || target.nodeType === 3 || target.nodeType === 8) {
- return self;
- }
- // Build event object by patching the args
- args = fix(null, args);
- args.type = name;
- args.target = target;
- do {
- // Found an expando that means there is listeners to execute
- id = target[expando];
- if (id) {
- executeHandlers(args, id);
- }
- // Walk up the DOM
- target = target.parentNode || target.ownerDocument || target.defaultView || target.parentWindow;
- } while (target && !args.isPropagationStopped());
- return self;
- };
- /**
- * Removes all bound event listeners for the specified target. This will also remove any bound
- * listeners to child nodes within that target.
- *
- * @method clean
- * @param {Object} target Target node/window object.
- * @return {EventUtils} Event utils instance.
- */
- self.clean = function(target) {
- var i, children, unbind = self.unbind;
- // Don't bind to text nodes or comments
- if (!target || target.nodeType === 3 || target.nodeType === 8) {
- return self;
- }
- // Unbind any element on the specificed target
- if (target[expando]) {
- unbind(target);
- }
- // Target doesn't have getElementsByTagName it's probably a window object then use it's document to find the children
- if (!target.getElementsByTagName) {
- target = target.document;
- }
- // Remove events from each child element
- if (target && target.getElementsByTagName) {
- unbind(target);
- children = target.getElementsByTagName('*');
- i = children.length;
- while (i--) {
- target = children[i];
- if (target[expando]) {
- unbind(target);
- }
- }
- }
- return self;
- };
- /**
- * Destroys the event object. Call this on IE to remove memory leaks.
- */
- self.destroy = function() {
- events = {};
- };
- // Legacy function for canceling events
- self.cancel = function(e) {
- if (e) {
- e.preventDefault();
- e.stopImmediatePropagation();
- }
- return false;
- };
- }
- EventUtils.Event = new EventUtils();
- EventUtils.Event.bind(window, 'ready', function() {});
- return EventUtils;
- });
|