1 /**
  2  * @fileOverview scalable event framework for kissy (refer DOM3 Events)
  3  *               how to fire event just like browser?
  4  * @author  yiminghe@gmail.com,lifesinger@gmail.com
  5  */
  6 KISSY.add('event/base', function (S, DOM, EventObject, Utils, handle, _data, special) {
  7 
  8     var isValidTarget = Utils.isValidTarget,
  9         splitAndRun = Utils.splitAndRun,
 10         getNodeName = DOM.nodeName,
 11         trim = S.trim,
 12         TRIGGERED_NONE = Utils.TRIGGERED_NONE;
 13 
 14     /**
 15      * @namespace The event utility provides functions to add and remove event listeners.
 16      * @name Event
 17      */
 18     var Event =
 19     /**
 20      * @lends Event
 21      */
 22     {
 23 
 24         _clone:function (src, dest) {
 25 
 26             if (dest.nodeType !== DOM.ELEMENT_NODE ||
 27                 !_data._hasData(src)) {
 28                 return;
 29             }
 30             var eventDesc = _data._data(src),
 31                 events = eventDesc.events;
 32             S.each(events, function (handlers, type) {
 33                 S.each(handlers, function (handler) {
 34                     // scope undefined 时不能写死在 handlers 中,否则不能保证 clone 时的 this
 35                     Event.on(dest, type, {
 36                         data:handler.data,
 37                         fn:handler.fn,
 38                         groups:handler.groups,
 39                         last:handler.last,
 40                         originalType:handler.originalType,
 41                         scope:handler.scope,
 42                         selector:handler.selector
 43                     });
 44                 });
 45             });
 46         },
 47 
 48         /**
 49          * fire event,simulate bubble in browser. similar to dispatchEvent in DOM3 Events
 50          * @memberOf Event
 51          * @param targets html nodes
 52          * @param {String|Event.Object} eventType event type
 53          * @param [eventData] additional event data
 54          * @param {Boolean} [onlyHandlers] only fire handlers
 55          * @returns {Boolean} The return value of fire/dispatchEvent indicates
 56          *                 whether any of the listeners which handled the event called preventDefault.
 57          *                 If preventDefault was called the value is false, else the value is true.
 58          */
 59         fire:function (targets, eventType, eventData, onlyHandlers/*internal usage*/) {
 60             var ret = true, r;
 61             // custom event firing moved to target.js
 62             eventData = eventData || {};
 63             if (S.isString(eventType)) {
 64                 eventType = trim(eventType);
 65                 if (eventType.indexOf(" ") > -1) {
 66                     splitAndRun(eventType, function (t) {
 67                         r = Event.fire(targets, t, eventData, onlyHandlers);
 68                         if (ret !== false) {
 69                             ret = r;
 70                         }
 71                     });
 72                     return ret;
 73                 }
 74                 // protect event type
 75                 eventData.type = eventType;
 76             } else if (eventType instanceof EventObject) {
 77                 eventData = eventType;
 78                 eventType = eventData.type;
 79             }
 80 
 81             var typedGroups = Utils.getTypedGroups(eventType),
 82                 _ks_groups = typedGroups[1];
 83 
 84             if (_ks_groups) {
 85                 _ks_groups = Utils.getGroupsRe(_ks_groups);
 86             }
 87 
 88             eventType = typedGroups[0];
 89 
 90             S.mix(eventData, {
 91                 type:eventType,
 92                 _ks_groups:_ks_groups
 93             });
 94 
 95             targets = DOM.query(targets);
 96             for (var i = targets.length - 1; i >= 0; i--) {
 97                 r = fireDOMEvent(targets[i], eventType, eventData, onlyHandlers);
 98                 if (ret !== false) {
 99                     ret = r;
100                 }
101             }
102             return ret;
103         },
104 
105         /**
106          * does not cause default behavior to occur
107          * does not bubble up the DOM hierarchy
108          * @param targets
109          * @param {Event.Object | String} eventType
110          * @param [eventData]
111          */
112         fireHandler:function (targets, eventType, eventData) {
113             return Event.fire(targets, eventType, eventData, 1);
114         }
115     };
116 
117     /**
118      * fire dom event from bottom to up , emulate dispatchEvent in DOM3 Events
119      * @return {Boolean} The return value of dispatchEvent indicates
120      *                 whether any of the listeners which handled the event called preventDefault.
121      *                 If preventDefault was called the value is false, else the value is true.
122      */
123     function fireDOMEvent(target, eventType, eventData, onlyHandlers) {
124         if (!isValidTarget(target)) {
125             return false;
126         }
127         var s = special[eventType];
128         // TODO bug : when fire mouseenter , it also fire mouseover in firefox/chrome
129         if (s && s['onFix']) {
130             eventType = s['onFix'];
131         }
132 
133         var event,
134             ret = true;
135         if (eventData instanceof EventObject) {
136             event = eventData;
137         } else {
138             event = new EventObject(target, undefined, eventType);
139             S.mix(event, eventData);
140         }
141         /**
142          * identify event as fired manually
143          */
144         event._ks_fired = 1;
145         /*
146          The target of the event is the EventTarget on which dispatchEvent is called.
147          */
148         // TODO: protect target , but incompatible
149         // event.target=target;
150         // protect type
151         event.type = eventType;
152 
153         // onlyHandlers is equal to event.halt()
154         // but we can not call event.halt()
155         // because handle will check event.isPropagationStopped
156 
157         var cur = target,
158             t,
159             win = DOM._getWin(cur.ownerDocument || cur),
160             ontype = "on" + eventType;
161 
162         //bubble up dom tree
163         do {
164             event.currentTarget = cur;
165             t = handle(cur, event);
166             if (ret !== false) {
167                 ret = t;
168             }
169             // Trigger an inline bound script
170             if (cur[ ontype ] && cur[ ontype ].call(cur) === false) {
171                 event.preventDefault();
172             }
173             // Bubble up to document, then to window
174             cur = cur.parentNode ||
175                 cur.ownerDocument ||
176                 (cur === target.ownerDocument) && win;
177         } while (!onlyHandlers && cur && !event.isPropagationStopped);
178 
179         if (!onlyHandlers && !event.isDefaultPrevented) {
180             if (!(eventType === "click" &&
181                 getNodeName(target) == "a")) {
182                 var old;
183                 try {
184                     // execute default action on dom node
185                     // so exclude window
186                     // exclude focus/blue on hidden element
187                     if (ontype &&
188                         target[ eventType ] &&
189                         (
190                             (eventType !== "focus" && eventType !== "blur") ||
191                                 target.offsetWidth !== 0
192                             ) &&
193                         !S.isWindow(target)) {
194                         // Don't re-trigger an onFOO event when we call its FOO() method
195                         old = target[ ontype ];
196 
197                         if (old) {
198                             target[ ontype ] = null;
199                         }
200 
201                         // 记录当前 trigger 触发
202                         Utils.Event_Triggered = eventType;
203 
204                         // 只触发默认事件,而不要执行绑定的用户回调
205                         // 同步触发
206                         target[ eventType ]();
207                     }
208                 } catch (ieError) {
209                     S.log("trigger action error : ");
210                     S.log(ieError);
211                 }
212 
213                 if (old) {
214                     target[ ontype ] = old;
215                 }
216 
217                 Utils.Event_Triggered = TRIGGERED_NONE;
218             }
219         }
220         return ret;
221     }
222 
223     return Event;
224 }, {
225     requires:["dom", "./object", "./utils", './handle', './data', './special']
226 });
227 
228 /**
229  * yiminghe@gmail.com : 2011-12-15
230  *  - 重构,粒度更细,新的架构
231  *
232  * 2011-11-24
233  *  - 自定义事件和 dom 事件操作彻底分离
234  *  - TODO: group event from DOM3 Event
235  *
236  * 2011-06-07
237  *  - refer : http://www.w3.org/TR/2001/WD-DOM-Level-3-Events-20010823/events.html
238  *  - 重构
239  *  - eventHandler 一个元素一个而不是一个元素一个事件一个,节省内存
240  *  - 减少闭包使用,prevent ie 内存泄露?
241  *  - 增加 fire ,模拟冒泡处理 dom 事件
242  */
243