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);