1 /**
  2  * Hilo
  3  * Copyright 2015 alibaba.com
  4  * Licensed under the MIT License
  5  */
  6 
  7 /**
  8  * @language=zh
  9  * @class EventMixin是一个包含事件相关功能的mixin。可以通过 Class.mix(target, EventMixin) 来为target增加事件功能。
 10  * @mixin
 11  * @static
 12  * @module hilo/event/EventMixin
 13  * @requires hilo/core/Class
 14  */
 15 var EventMixin = {
 16     _listeners: null,
 17 
 18     /**
 19      * @language=zh
 20      * 增加一个事件监听。
 21      * @param {String} type 要监听的事件类型。
 22      * @param {Function} listener 事件监听回调函数。
 23      * @param {Boolean} once 是否是一次性监听,即回调函数响应一次后即删除,不再响应。
 24      * @returns {Object} 对象本身。链式调用支持。
 25      */
 26     on: function(type, listener, once){
 27         var listeners = (this._listeners = this._listeners || {});
 28         var eventListeners = (listeners[type] = listeners[type] || []);
 29         for(var i = 0, len = eventListeners.length; i < len; i++){
 30             var el = eventListeners[i];
 31             if(el.listener === listener) return;
 32         }
 33         eventListeners.push({listener:listener, once:once});
 34         return this;
 35     },
 36 
 37     /**
 38      * @language=zh
 39      * 删除一个事件监听。如果不传入任何参数,则删除所有的事件监听;如果不传入第二个参数,则删除指定类型的所有事件监听。
 40      * @param {String} type 要删除监听的事件类型。
 41      * @param {Function} listener 要删除监听的回调函数。
 42      * @returns {Object} 对象本身。链式调用支持。
 43      */
 44     off: function(type, listener){
 45         //remove all event listeners
 46         if(arguments.length == 0){
 47             this._listeners = null;
 48             return this;
 49         }
 50 
 51         var eventListeners = this._listeners && this._listeners[type];
 52         if(eventListeners){
 53             //remove event listeners by specified type
 54             if(arguments.length == 1){
 55                 delete this._listeners[type];
 56                 return this;
 57             }
 58 
 59             for(var i = 0, len = eventListeners.length; i < len; i++){
 60                 var el = eventListeners[i];
 61                 if(el.listener === listener){
 62                     eventListeners.splice(i, 1);
 63                     if(eventListeners.length === 0) delete this._listeners[type];
 64                     break;
 65                 }
 66             }
 67         }
 68         return this;
 69     },
 70 
 71     /**
 72      * @language=zh
 73      * 发送事件。当第一个参数类型为Object时,则把它作为一个整体事件对象。
 74      * @param {String} type 要发送的事件类型。
 75      * @param {Object} detail 要发送的事件的具体信息,即事件随带参数。
 76      * @returns {Boolean} 是否成功调度事件。
 77      */
 78     fire: function(type, detail){
 79         var event, eventType;
 80         if(typeof type === 'string'){
 81             eventType = type;
 82         }else{
 83             event = type;
 84             eventType = type.type;
 85         }
 86 
 87         var listeners = this._listeners;
 88         if(!listeners) return false;
 89 
 90         var eventListeners = listeners[eventType];
 91         if(eventListeners){
 92             var eventListenersCopy = eventListeners.slice(0);
 93             event = event || new EventObject(eventType, this, detail);
 94             if(event._stopped) return false;
 95 
 96             for(var i = 0; i < eventListenersCopy.length; i++){
 97                 var el = eventListenersCopy[i];
 98                 el.listener.call(this, event);
 99                 if(el.once) {
100                     var index = eventListeners.indexOf(el);
101                     if(index > -1){
102                         eventListeners.splice(index, 1);
103                     }
104                 }
105             }
106 
107             if(eventListeners.length == 0) delete listeners[eventType];
108             return true;
109         }
110         return false;
111     }
112 };
113 
114 /**
115  * @language=zh
116  * 事件对象类。当前仅为内部类,以后有需求的话可能会考虑独立为公开类。
117  */
118 var EventObject = Class.create({
119     constructor: function EventObject(type, target, detail){
120         this.type = type;
121         this.target = target;
122         this.detail = detail;
123         this.timeStamp = +new Date();
124     },
125 
126     type: null,
127     target: null,
128     detail: null,
129     timeStamp: 0,
130 
131     stopImmediatePropagation: function(){
132         this._stopped = true;
133     }
134 });
135 
136 //Trick: `stopImmediatePropagation` compatibility
137 var RawEvent = window.Event;
138 if(RawEvent){
139     var proto = RawEvent.prototype,
140         stop = proto.stopImmediatePropagation;
141     proto.stopImmediatePropagation = function(){
142         stop && stop.call(this);
143         this._stopped = true;
144     }
145 }
146