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