/** * @ignore * custom event for dom. * @author yiminghe@gmail.com */ KISSY.add('event/dom/base/observable', function (S, DOM, special, Utils, DOMEventObserver, DOMEventObject, Event) { // 记录手工 fire(domElement,type) 时的 type // 再在浏览器通知的系统 eventHandler 中检查 // 如果相同,那么证明已经 fire 过了,不要再次触发了 var _Utils = Event._Utils; /** * custom event for dom * @param {Object} cfg * @private * @class KISSY.Event.ObservableDOMEvent * @extends KISSY.Event.ObservableEvent */ function ObservableDOMEvent(cfg) { var self = this; S.mix(self, cfg); self.reset(); /** * html node which binds current custom event * @cfg {HTMLElement} currentTarget */ } S.extend(ObservableDOMEvent, Event._ObservableEvent, { setup: function () { var self = this, type = self.type, s = special[type] || {}, currentTarget = self.currentTarget, eventDesc = Utils.data(currentTarget), handle = eventDesc.handle; // 第一次注册该事件,dom 节点才需要注册 dom 事件 if (!s.setup || s.setup.call(currentTarget, type) === false) { Utils.simpleAdd(currentTarget, type, handle) } }, constructor: ObservableDOMEvent, reset: function () { var self = this; ObservableDOMEvent.superclass.reset.call(self); self.delegateCount = 0; self.lastCount = 0; }, /** * notify current event 's observers * @param {KISSY.Event.DOMEventObject} event * @return {*} return false if one of custom event 's observers else * return last value of custom event 's observers 's return value. */ notify: function (event) { /* As some listeners may remove themselves from the event, the original array length is dynamic. So, let's make a copy of all listeners, so we are sure we'll call all of them. */ /* DOM3 Events: EventListenerList objects in the DOM are live. ?? */ var target = event['target'], self = this, currentTarget = self.currentTarget, observers = self.observers, currentTarget0, allObservers = [], ret, gRet, observerObj, i, j, delegateCount = self.delegateCount || 0, len, currentTargetObservers, currentTargetObserver, observer; // collect delegated observers and corresponding element // by jq // Avoid disabled elements in IE (#6911) // non-left-click bubbling in Firefox (#3861),firefox 8 fix it if (delegateCount && !target.disabled) { while (target != currentTarget) { currentTargetObservers = []; for (i = 0; i < delegateCount; i++) { observer = observers[i]; if (DOM.test(target, observer.selector)) { currentTargetObservers.push(observer); } } if (currentTargetObservers.length) { allObservers.push({ currentTarget: target, 'currentTargetObservers': currentTargetObservers }); } target = target.parentNode || currentTarget; } } // root node's observers is placed at end position of add observers // in case child node stopPropagation of root node's observers allObservers.push({ currentTarget: currentTarget, // http://www.w3.org/TR/dom/#dispatching-events // Let listeners be a static list of the event listeners // associated with the object for which these steps are run. currentTargetObservers: observers.slice(delegateCount) }); for (i = 0, len = allObservers.length; !event.isPropagationStopped() && i < len; ++i) { observerObj = allObservers[i]; currentTargetObservers = observerObj.currentTargetObservers; currentTarget0 = observerObj.currentTarget; event.currentTarget = currentTarget0; for (j = 0; !event.isImmediatePropagationStopped() && j < currentTargetObservers.length; j++) { currentTargetObserver = currentTargetObservers[j]; ret = currentTargetObserver.notify(event, self); // 和 jQuery 逻辑保持一致 // 有一个 false,最终结果就是 false // 否则等于最后一个返回值 if (gRet !== false) { gRet = ret; } } } // fire 时判断如果 preventDefault,则返回 false 否则返回 true // 这里返回值意义不同 return gRet; }, /** * fire dom event from bottom to up , emulate dispatchEvent in DOM3 Events * @param {Object|KISSY.Event.DOMEventObject} [event] additional event data * @return {*} return false if one of custom event 's observers (include bubbled) else * return last value of custom event 's observers (include bubbled) 's return value. */ fire: function (event, onlyHandlers/*internal usage*/) { event = event || {}; var self = this, eventType = self.type, s = special[eventType]; // TODO bug: when fire mouseenter, it also fire mouseover in firefox/chrome if (s && s['onFix']) { eventType = s['onFix']; } var customEvent, eventData, currentTarget = self.currentTarget, ret = true; event.type = eventType; if (!(event instanceof DOMEventObject)) { eventData = event; event = new DOMEventObject({ currentTarget: currentTarget, target: currentTarget }); S.mix(event, eventData); } // onlyHandlers is equal to event.halt() // but we can not call event.halt() // because handle will check event.isPropagationStopped var cur = currentTarget, t, win = DOM.getWindow(cur.ownerDocument || cur), curDocument = win.document, eventPath = [], eventPathIndex = 0, ontype = 'on' + eventType; // http://www.w3.org/TR/dom/#dispatching-events // let event path be a static ordered list of all its ancestors in tree order, // or let event path be the empty list otherwise. do { eventPath.push(cur); // Bubble up to document, then to window cur = cur.parentNode || cur.ownerDocument || (cur === curDocument) && win; } while (cur); cur = eventPath[eventPathIndex]; // bubble up dom tree do { event.currentTarget = cur; customEvent = ObservableDOMEvent.getCustomEvent(cur, eventType); // default bubble for html node if (customEvent) { t = customEvent.notify(event); if (ret !== false) { ret = t; } } // Trigger an inline bound script if (cur[ ontype ] && cur[ ontype ].call(cur) === false) { event.preventDefault(); } cur = eventPath[++eventPathIndex]; } while (!onlyHandlers && cur && !event.isPropagationStopped()); if (!onlyHandlers && !event.isDefaultPrevented()) { // now all browser support click // https://developer.mozilla.org/en-US/docs/DOM/element.click var old; try { // execute default action on dom node // so exclude window // exclude focus/blue on hidden element if (ontype && currentTarget[ eventType ] && ( ( eventType !== 'focus' && eventType !== 'blur') || currentTarget.offsetWidth !== 0 ) && !S.isWindow(currentTarget)) { // Don't re-trigger an onFOO event when we call its FOO() method old = currentTarget[ ontype ]; if (old) { currentTarget[ ontype ] = null; } // 记录当前 trigger 触发 ObservableDOMEvent.triggeredEvent = eventType; // 只触发默认事件,而不要执行绑定的用户回调 // 同步触发 currentTarget[ eventType ](); } } catch (eError) { S.log('trigger action error: '); S.log(eError); } if (old) { currentTarget[ ontype ] = old; } ObservableDOMEvent.triggeredEvent = ''; } return ret; }, /** * add a observer to custom event's observers * @param {Object} cfg {@link KISSY.Event.DOMEventObserver} 's config */ on: function (cfg) { var self = this, observers = self.observers, s = special[self.type] || {}, // clone event observer = cfg instanceof DOMEventObserver ? cfg : new DOMEventObserver(cfg); if (self.findObserver(observer) == -1) { // 增加 listener if (observer.selector) { observers.splice(self.delegateCount, 0, observer); self.delegateCount++; } else { if (observer.last) { observers.push(observer); self.lastCount++; } else { observers.splice(observers.length - self.lastCount, 0, observer); } } if (s.add) { s.add.call(self.currentTarget, observer); } } }, /** * remove some observers from current event 's observers by observer config param * @param {Object} cfg {@link KISSY.Event.DOMEventObserver} 's config */ detach: function (cfg) { var groupsRe, self = this, s = special[self.type] || {}, hasSelector = 'selector' in cfg, selector = cfg.selector, context = cfg.context, fn = cfg.fn, currentTarget = self.currentTarget, observers = self.observers, groups = cfg.groups; if (!observers.length) { return; } if (groups) { groupsRe = _Utils.getGroupsRe(groups); } var i, j, t, observer, observerContext, len = observers.length; // 移除 fn if (fn || hasSelector || groupsRe) { context = context || currentTarget; for (i = 0, j = 0, t = []; i < len; ++i) { observer = observers[i]; observerContext = observer.context || currentTarget; if ( (context != observerContext) || // 指定了函数,函数不相等,保留 (fn && fn != observer.fn) || // 1.没指定函数 // 1.1 没有指定选择器,删掉 else2 // 1.2 指定选择器,字符串为空 // 1.2.1 指定选择器,字符串为空,待比较 observer 有选择器,删掉 else // 1.2.2 指定选择器,字符串为空,待比较 observer 没有选择器,保留 // 1.3 指定选择器,字符串不为空,字符串相等,删掉 else // 1.4 指定选择器,字符串不为空,字符串不相等,保留 // 2.指定了函数且函数相等 // 2.1 没有指定选择器,删掉 else // 2.2 指定选择器,字符串为空 // 2.2.1 指定选择器,字符串为空,待比较 observer 有选择器,删掉 else // 2.2.2 指定选择器,字符串为空,待比较 observer 没有选择器,保留 // 2.3 指定选择器,字符串不为空,字符串相等,删掉 else // 2.4 指定选择器,字符串不为空,字符串不相等,保留 ( hasSelector && ( (selector && selector != observer.selector) || (!selector && !observer.selector) ) ) || // 指定了删除的某些组,而该 observer 不属于这些组,保留,否则删除 (groupsRe && !observer.groups.match(groupsRe)) ) { t[j++] = observer; } else { if (observer.selector && self.delegateCount) { self.delegateCount--; } if (observer.last && self.lastCount) { self.lastCount--; } if (s.remove) { s.remove.call(currentTarget, observer); } } } self.observers = t; } else { // 全部删除 self.reset(); } self.checkMemory(); }, checkMemory: function () { var self = this, type = self.type, events, handle, s = special[type] || {}, currentTarget = self.currentTarget, eventDesc = Utils.data(currentTarget); if (eventDesc) { events = eventDesc.events; if (!self.hasObserver()) { handle = eventDesc.handle; // remove(el, type) or fn 已移除光 // dom node need to detach handler from dom node if ((!s['tearDown'] || s['tearDown'].call(currentTarget, type) === false)) { Utils.simpleRemove(currentTarget, type, handle); } // remove currentTarget's single event description delete events[type]; } // remove currentTarget's all events description if (S.isEmptyObject(events)) { eventDesc.handle = null; Utils.removeData(currentTarget); } } } }); ObservableDOMEvent.triggeredEvent = ''; /** * get custom event from html node by event type. * @param {HTMLElement} node * @param {String} type event type * @return {KISSY.Event.ObservableDOMEvent} */ ObservableDOMEvent.getCustomEvent = function (node, type) { var eventDesc = Utils.data(node), events; if (eventDesc) { events = eventDesc.events; } if (events) { return events[type]; } return undefined; }; ObservableDOMEvent.getCustomEvents = function (node, create) { var eventDesc = Utils.data(node); if (!eventDesc && create) { Utils.data(node, eventDesc = {}); } return eventDesc; }; return ObservableDOMEvent; }, { requires: ['dom', './special', './utils', './observer', './object', 'event/base'] });