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