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 var getElement, setElement; 27 28 (function () { 29 var elements = { }; 30 getElement = function (uid) { 31 return elements[uid]; 32 }; 33 setElement = function (uid, element) { 34 elements[uid] = element; 35 }; 36 })(); 37 38 function createListener(uid, handler) { 39 return { 40 handler: handler, 41 wrappedHandler: createWrappedHandler(uid, handler) 42 }; 43 } 44 45 function createWrappedHandler(uid, handler) { 46 return function (e) { 47 handler.call(getElement(uid), e || window.event); 48 }; 49 } 50 51 function createDispatcher(uid, eventName) { 52 return function (e) { 53 if (handlers[uid] && handlers[uid][eventName]) { 54 var handlersForEvent = handlers[uid][eventName]; 55 for (var i = 0, len = handlersForEvent.length; i < len; i++) { 56 handlersForEvent[i].call(this, e || window.event); 57 } 58 } 59 }; 60 } 61 62 var shouldUseAddListenerRemoveListener = ( 63 areHostMethods(document.documentElement, 'addEventListener', 'removeEventListener') && 64 areHostMethods(window, 'addEventListener', 'removeEventListener')), 65 66 shouldUseAttachEventDetachEvent = ( 67 areHostMethods(document.documentElement, 'attachEvent', 'detachEvent') && 68 areHostMethods(window, 'attachEvent', 'detachEvent')), 69 70 // IE branch 71 listeners = { }, 72 73 // DOM L0 branch 74 handlers = { }, 75 76 addListener, removeListener; 77 78 if (shouldUseAddListenerRemoveListener) { 79 /** @ignore */ 80 addListener = function (element, eventName, handler) { 81 element.addEventListener(eventName, handler, false); 82 }; 83 /** @ignore */ 84 removeListener = function (element, eventName, handler) { 85 element.removeEventListener(eventName, handler, false); 86 }; 87 } 88 89 else if (shouldUseAttachEventDetachEvent) { 90 /** @ignore */ 91 addListener = function (element, eventName, handler) { 92 var uid = getUniqueId(element); 93 setElement(uid, element); 94 if (!listeners[uid]) { 95 listeners[uid] = { }; 96 } 97 if (!listeners[uid][eventName]) { 98 listeners[uid][eventName] = [ ]; 99 100 } 101 var listener = createListener(uid, handler); 102 listeners[uid][eventName].push(listener); 103 element.attachEvent('on' + eventName, listener.wrappedHandler); 104 }; 105 /** @ignore */ 106 removeListener = function (element, eventName, handler) { 107 var uid = getUniqueId(element), listener; 108 if (listeners[uid] && listeners[uid][eventName]) { 109 for (var i = 0, len = listeners[uid][eventName].length; i < len; i++) { 110 listener = listeners[uid][eventName][i]; 111 if (listener && listener.handler === handler) { 112 element.detachEvent('on' + eventName, listener.wrappedHandler); 113 listeners[uid][eventName][i] = null; 114 } 115 } 116 } 117 }; 118 } 119 else { 120 /** @ignore */ 121 addListener = function (element, eventName, handler) { 122 var uid = getUniqueId(element); 123 if (!handlers[uid]) { 124 handlers[uid] = { }; 125 } 126 if (!handlers[uid][eventName]) { 127 handlers[uid][eventName] = [ ]; 128 var existingHandler = element['on' + eventName]; 129 if (existingHandler) { 130 handlers[uid][eventName].push(existingHandler); 131 } 132 element['on' + eventName] = createDispatcher(uid, eventName); 133 } 134 handlers[uid][eventName].push(handler); 135 }; 136 /** @ignore */ 137 removeListener = function (element, eventName, handler) { 138 var uid = getUniqueId(element); 139 if (handlers[uid] && handlers[uid][eventName]) { 140 var handlersForEvent = handlers[uid][eventName]; 141 for (var i = 0, len = handlersForEvent.length; i < len; i++) { 142 if (handlersForEvent[i] === handler) { 143 handlersForEvent.splice(i, 1); 144 } 145 } 146 } 147 }; 148 } 149 150 /** 151 * Adds an event listener to an element 152 * @mthod addListener 153 * @memberOf fabric.util 154 * @function 155 * @param {HTMLElement} element 156 * @param {String} eventName 157 * @param {Function} handler 158 */ 159 fabric.util.addListener = addListener; 160 161 /** 162 * Removes an event listener from an element 163 * @mthod removeListener 164 * @memberOf fabric.util 165 * @function 166 * @param {HTMLElement} element 167 * @param {String} eventName 168 * @param {Function} handler 169 */ 170 fabric.util.removeListener = removeListener; 171 172 var customEventListeners = { }; 173 174 /** 175 * @mthod observeEvent 176 * @memberOf fabric.util 177 * @param {String} eventName 178 * @param {Function} handler 179 */ 180 function observeEvent(eventName, handler) { 181 if (!customEventListeners[eventName]) { 182 customEventListeners[eventName] = [ ]; 183 } 184 customEventListeners[eventName].push(handler); 185 } 186 187 /** 188 * Fires event with an optional memo object 189 * @mthod fireEvent 190 * @memberOf fabric.util 191 * @param {String} eventName 192 * @param {Object} [memo] 193 */ 194 function fireEvent(eventName, memo) { 195 var listenersForEvent = customEventListeners[eventName]; 196 if (!listenersForEvent) return; 197 for (var i = 0, len = listenersForEvent.length; i < len; i++) { 198 // avoiding try/catch for perf. reasons 199 listenersForEvent[i]({ memo: memo }); 200 } 201 } 202 203 /** 204 * Cross-browser wrapper for getting event's coordinates 205 * @method getPointer 206 * @memberOf fabric.util 207 * @param {Event} event 208 */ 209 function getPointer(event) { 210 // TODO (kangax): this method needs fixing 211 return { x: pointerX(event), y: pointerY(event) }; 212 } 213 214 function pointerX(event) { 215 var docElement = document.documentElement, 216 body = document.body || { scrollLeft: 0 }; 217 218 // looks like in IE (<9) clientX at certain point (apparently when mouseup fires on VML element) 219 // is represented as COM object, with all the consequences, like "unknown" type and error on [[Get]] 220 // need to investigate later 221 return event.pageX || ((typeof event.clientX != 'unknown' ? event.clientX : 0) + 222 (docElement.scrollLeft || body.scrollLeft) - 223 (docElement.clientLeft || 0)); 224 } 225 226 function pointerY(event) { 227 var docElement = document.documentElement, 228 body = document.body || { scrollTop: 0 }; 229 230 return event.pageY || ((typeof event.clientY != 'unknown' ? event.clientY : 0) + 231 (docElement.scrollTop || body.scrollTop) - 232 (docElement.clientTop || 0)); 233 } 234 235 fabric.util.getPointer = getPointer; 236 fabric.util.observeEvent = observeEvent; 237 fabric.util.fireEvent = fireEvent; 238 })(this);