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