1 (function (global) { 2 3 /* EVENT HANDLING */ 4 5 function areHostMethods(object) { 6 var methodNames = Array.prototype.slice.call(arguments, 1), 7 t, i, len = methodNames.length; 8 for (i = 0; i < len; i++) { 9 t = typeof object[methodNames[i]]; 10 if (!(/^(?:function|object|unknown)$/).test(t)) return false; 11 } 12 return true; 13 } 14 var getUniqueId = (function () { 15 if (typeof document.documentElement.uniqueID !== 'undefined') { 16 return function (element) { 17 return element.uniqueID; 18 }; 19 } 20 var uid = 0; 21 return function (element) { 22 return element.__uniqueID || (element.__uniqueID = 'uniqueID__' + uid++); 23 }; 24 })(); 25 26 /** @ignore */ 27 var getElement, setElement; 28 29 (function () { 30 var elements = { }; 31 /** @ignore */ 32 getElement = function (uid) { 33 return elements[uid]; 34 }; 35 /** @ignore */ 36 setElement = function (uid, element) { 37 elements[uid] = element; 38 }; 39 })(); 40 41 function createListener(uid, handler) { 42 return { 43 handler: handler, 44 wrappedHandler: createWrappedHandler(uid, handler) 45 }; 46 } 47 48 function createWrappedHandler(uid, handler) { 49 return function (e) { 50 handler.call(getElement(uid), e || window.event); 51 }; 52 } 53 54 function createDispatcher(uid, eventName) { 55 return function (e) { 56 if (handlers[uid] && handlers[uid][eventName]) { 57 var handlersForEvent = handlers[uid][eventName]; 58 for (var i = 0, len = handlersForEvent.length; i < len; i++) { 59 handlersForEvent[i].call(this, e || window.event); 60 } 61 } 62 }; 63 } 64 65 var shouldUseAddListenerRemoveListener = ( 66 areHostMethods(document.documentElement, 'addEventListener', 'removeEventListener') && 67 areHostMethods(window, 'addEventListener', 'removeEventListener')), 68 69 shouldUseAttachEventDetachEvent = ( 70 areHostMethods(document.documentElement, 'attachEvent', 'detachEvent') && 71 areHostMethods(window, 'attachEvent', 'detachEvent')), 72 73 // IE branch 74 listeners = { }, 75 76 // DOM L0 branch 77 handlers = { }, 78 79 addListener, removeListener; 80 81 if (shouldUseAddListenerRemoveListener) { 82 /** @ignore */ 83 addListener = function (element, eventName, handler) { 84 element.addEventListener(eventName, handler, false); 85 }; 86 /** @ignore */ 87 removeListener = function (element, eventName, handler) { 88 element.removeEventListener(eventName, handler, false); 89 }; 90 } 91 92 else if (shouldUseAttachEventDetachEvent) { 93 /** @ignore */ 94 addListener = function (element, eventName, handler) { 95 var uid = getUniqueId(element); 96 setElement(uid, element); 97 if (!listeners[uid]) { 98 listeners[uid] = { }; 99 } 100 if (!listeners[uid][eventName]) { 101 listeners[uid][eventName] = [ ]; 102 103 } 104 var listener = createListener(uid, handler); 105 listeners[uid][eventName].push(listener); 106 element.attachEvent('on' + eventName, listener.wrappedHandler); 107 }; 108 /** @ignore */ 109 removeListener = function (element, eventName, handler) { 110 var uid = getUniqueId(element), listener; 111 if (listeners[uid] && listeners[uid][eventName]) { 112 for (var i = 0, len = listeners[uid][eventName].length; i < len; i++) { 113 listener = listeners[uid][eventName][i]; 114 if (listener && listener.handler === handler) { 115 element.detachEvent('on' + eventName, listener.wrappedHandler); 116 listeners[uid][eventName][i] = null; 117 } 118 } 119 } 120 }; 121 } 122 else { 123 /** @ignore */ 124 addListener = function (element, eventName, handler) { 125 var uid = getUniqueId(element); 126 if (!handlers[uid]) { 127 handlers[uid] = { }; 128 } 129 if (!handlers[uid][eventName]) { 130 handlers[uid][eventName] = [ ]; 131 var existingHandler = element['on' + eventName]; 132 if (existingHandler) { 133 handlers[uid][eventName].push(existingHandler); 134 } 135 element['on' + eventName] = createDispatcher(uid, eventName); 136 } 137 handlers[uid][eventName].push(handler); 138 }; 139 /** @ignore */ 140 removeListener = function (element, eventName, handler) { 141 var uid = getUniqueId(element); 142 if (handlers[uid] && handlers[uid][eventName]) { 143 var handlersForEvent = handlers[uid][eventName]; 144 for (var i = 0, len = handlersForEvent.length; i < len; i++) { 145 if (handlersForEvent[i] === handler) { 146 handlersForEvent.splice(i, 1); 147 } 148 } 149 } 150 }; 151 } 152 153 /** 154 * Adds an event listener to an element 155 * @mthod addListener 156 * @memberOf fabric.util 157 * @function 158 * @param {HTMLElement} element 159 * @param {String} eventName 160 * @param {Function} handler 161 */ 162 fabric.util.addListener = addListener; 163 164 /** 165 * Removes an event listener from an element 166 * @mthod removeListener 167 * @memberOf fabric.util 168 * @function 169 * @param {HTMLElement} element 170 * @param {String} eventName 171 * @param {Function} handler 172 */ 173 fabric.util.removeListener = removeListener; 174 175 /** 176 * Cross-browser wrapper for getting event's coordinates 177 * @method getPointer 178 * @memberOf fabric.util 179 * @param {Event} event 180 */ 181 function getPointer(event) { 182 // TODO (kangax): this method needs fixing 183 return { x: pointerX(event), y: pointerY(event) }; 184 } 185 186 function pointerX(event) { 187 var docElement = document.documentElement, 188 body = document.body || { scrollLeft: 0 }; 189 190 // looks like in IE (<9) clientX at certain point (apparently when mouseup fires on VML element) 191 // is represented as COM object, with all the consequences, like "unknown" type and error on [[Get]] 192 // need to investigate later 193 return event.pageX || ((typeof event.clientX != 'unknown' ? event.clientX : 0) + 194 (docElement.scrollLeft || body.scrollLeft) - 195 (docElement.clientLeft || 0)); 196 } 197 198 function pointerY(event) { 199 var docElement = document.documentElement, 200 body = document.body || { scrollTop: 0 }; 201 202 return event.pageY || ((typeof event.clientY != 'unknown' ? event.clientY : 0) + 203 (docElement.scrollTop || body.scrollTop) - 204 (docElement.clientTop || 0)); 205 } 206 207 fabric.util.getPointer = getPointer; 208 209 fabric.util.object.extend(fabric.util, fabric.Observable); 210 211 })(this);