/*
* author:prk
* date:2008-07-31
* comment:
*/
if (!window.Event) var Event = { };
// 为Event注册一些键名
Object.extend(Event, {
KEY_BACKSPACE: 8,
KEY_TAB: 9,
KEY_RETURN: 13,
KEY_ESC: 27,
KEY_LEFT: 37,
KEY_UP: 38,
KEY_RIGHT: 39,
KEY_DOWN: 40,
KEY_DELETE: 46,
KEY_HOME: 36,
KEY_END: 35,
KEY_PAGEUP: 33,
KEY_PAGEDOWN: 34,
KEY_INSERT: 45,
// 缓存
cache: { },
// 事件相关连的元素,只对mouseover,mouseout有效。
relatedTarget: function(event) {
var element;
// event.type,事件名
switch(event.type) {
case 'mouseover': element = event.fromElement; break;
case 'mouseout': element = event.toElement; break;
default: return null;
}
// 通过Extend 包装原配的emement为Prototype的Element
// 在YUI则进行了resolveTextNode的处理。觉得有必要。
return Element.extend(element);
}
});
Event.Methods = (function() {
// 判断是按了鼠标的什么键,右,左,中间啊,
// 解决Brower的兼容性
var isButton;
if (Prototype.Browser.IE) {
var buttonMap = { 0: 1, 1: 4, 2: 2 };
isButton = function(event, code) {
return event.button == buttonMap[code];
};
} else if (Prototype.Browser.WebKit) {
isButton = function(event, code) {
switch (code) {
case 0: return event.which == 1 && !event.metaKey;
case 1: return event.which == 1 && event.metaKey;
default: return false;
}
};
} else {
isButton = function(event, code) {
return event.which ? (event.which === code + 1) : (event.button === code);
};
}
// Ext 通过下面三行解决兼容问题。
// var btnMap = Ext.isIE ? {1:0,4:1,2:2} :
// (Ext.isSafari ? {1:0,2:1,3:2} : {0:0,1:1,2:2});
// this.button = e.button ? btnMap[e.button] : (e.which ? e.which-1 : -1);
return {
isLeftClick: function(event) { return isButton(event, 0) },
isMiddleClick: function(event) { return isButton(event, 1) },
isRightClick: function(event) { return isButton(event, 2) },
// 定位event 's element
element: function(event) {
// 包装Event
event = Event.extend(event);
var node = event.target, currentTarget = event.currentTarget, type = event.type;
// target:Returns a reference to the target to which the event was
// originally dispatched
// currentTarget:Returns a reference to the currently registered target
// for the event.
// 解决FF的兼容
if (currentTarget && currentTarget.tagName) {
// Firefox screws up the "click" event when moving between radio buttons
// via arrow keys. It also screws up the "load" and "error" events on
// images, reporting the document as the target instead of the original
// image.
if (['load', 'error'].include(type) ||
(currentTarget.tagName.toUpperCase() === "INPUT" && currentTarget.type === "radio" && type === "click"))
node = currentTarget;
}
// node.nodeType == Node.TEXT_NODE ? node.parentNode
// 是YUI Event中resolveTextNode
return Element.extend(node && node.nodeType == Node.TEXT_NODE ?
node.parentNode : node);
},
// 查找到event的原始节点的祖先节点。expression只能是标签名 如a
findElement: function(event, expression) {
var element = Event.element(event);
if (!expression) return element;
var elements = [element].concat(element.ancestors());
return Selector.findElement(elements, expression, 0);
},
// 事件的元素的x,y坐标,相对于页面的。
pointer: function(event) {
/*
* pageX/Y:coordinate of the event relative to the page layerX/Y: coordinate
* of the event relative to the current layer screenX/Y: position of the
* event on the screen Browser区域 clientX /Y: position of the event
* 页面在Browser中区域。
*/
var docElement = document.documentElement,
body = document.body || { scrollLeft: 0, scrollTop: 0 };
return {
x: event.pageX || (event.clientX +
(docElement.scrollLeft || body.scrollLeft) -
(docElement.clientLeft || 0)),
y: event.pageY || (event.clientY +
(docElement.scrollTop || body.scrollTop) -
(docElement.clientTop || 0))
};
},
pointerX: function(event) { return Event.pointer(event).x },
pointerY: function(event) { return Event.pointer(event).y },
// 中断事件
stop: function(event) {
Event.extend(event);
event.preventDefault();
event.stopPropagation();
event.stopped = true;
}
};
})();
// 实现Event的确extend
Event.extend = (function() {
// 作用就是把函数的调用对象变成函数是第一个参数。条理化。
var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
m[name] = Event.Methods[name].methodize();
return m;
});
if (Prototype.Browser.IE) {
Object.extend(methods, {
stopPropagation: function() { this.cancelBubble = true },
preventDefault: function() { this.returnValue = false },
inspect: function() { return "[object Event]" }
});
return function(event) {
if (!event) return false;
if (event._extendedByPrototype) return event;
var pointer = Event.pointer(event);
// 为事件注册属性
Object.extend(event, {
_extendedByPrototype: Prototype.emptyFunction,
target: Element.extend(event.srcElement),
relatedTarget: Event.relatedTarget(event),
pageX: pointer.x,
pageY: pointer.y
});
// 推迟到每次调用时,才把methods考到event
return Object.extend(event, methods);
};
} else {
// 把条理化的方法注册到Event.prototype
Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__'];
Object.extend(Event.prototype, methods);
return Prototype.K;
}
})();
Object.extend(Event, (function() {
var cache = Event.cache;
function getEventID(element) {
// Event ID is stored as the 0th index in a one-item array so that it
// won't get copied to a new node when cloneNode is called.
// 这种处理递增的Id很妙,arguments.callee.id=getEventID.id,同时也设定getEventID.id
if (element === window) return 1;
if (element._prototypeEventID) return element._prototypeEventID[0];
return (element._prototypeEventID = [arguments.callee.id++])[0];
}
getEventID.id = 2;
function getDOMEventName(eventName) {
if (eventName && eventName.include(':')) return "dataavailable";
return eventName;
}
// 根据Id从cache中取得或生成一个元素的缓存。
function getCacheForID(id) {
return cache[id] = cache[id] || { };
}
// 根据Id,eventName从cache中取得或生成一个元素的的事件名数组
function getWrappersForEventName(id, eventName) {
var c = getCacheForID(id);
return c[eventName] = c[eventName] || [];
}
// 包装handler,主要作用取Id,缓存。
function createWrapper(element, eventName, handler) {
// create id,and create cache for id.
var id = getEventID(element), c = getCacheForID(id);
// Attach the element itself onto its cache entry so we can retrieve it for
// cleanup on page unload. {3:{element:element}}
if (!c.element) c.element = element;
// {3:{element:element,eventName:[]}}
var w = getWrappersForEventName(id, eventName);
// 有的话,不加入了
if (w.pluck("handler").include(handler)) return false;
// wrapper FN
var wrapper = function(event) {
if (!Event || !Event.extend ||
(event.eventName && event.eventName != eventName))
return false;
handler.call(element, Event.extend(event));
};
// add static method:handler=original handler
// {3:{element:element,eventName:[{handler:handler,wrapper:wrapper}]}}
wrapper.handler = handler;
// store in cache.
w.push(wrapper);
return wrapper;
}
// 根据id, eventName, handler找已经cache的Wrapper
function findWrapper(id, eventName, handler) {
// {3:{element:element,eventName:[wrapper1,wrapper1]}}
// w=[wrapper1,wrapper1]
var w = getWrappersForEventName(id, eventName);
// 函数在数组中找到符合条件的数组元素,很方便
return w.find(function(wrapper) { return wrapper.handler == handler });
}
// 从eventName对应的数组中去掉符合条件的。
function destroyWrapper(id, eventName, handler) {
var c = getCacheForID(id);
if (!c[eventName]) return false;
c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
}
// Loop through all elements and remove all handlers on page unload. IE
// needs this in order to prevent memory leaks.
function purgeListeners() {
var element, entry;
for (var i in Event.cache) {
// cache={3:{element:element,eventName:[wrapper1,wrapper1]}}
entry = Event.cache[i];
// unscribe the element 's listen.
Event.stopObserving(entry.element);
entry.element = null;
}
}
function onStop() {
document.detachEvent("onstop", onStop);
purgeListeners();
}
function onBeforeUnload() {
if (document.readyState === "interactive") {
document.attachEvent("onstop", onStop);
(function() { document.detachEvent("onstop", onStop); }).defer();
}
}
// 防止IE内存泄露
if (window.attachEvent && !window.addEventListener) {
// Internet Explorer needs to remove event handlers on page unload
// in order to avoid memory leaks.
window.attachEvent("onunload", purgeListeners);
// IE also doesn't fire the unload event if the page is navigated away
// from before it's done loading. Workaround adapted from
// http://blog.moxiecode.com/2008/04/08/unload-event-never-fires-in-ie/.
window.attachEvent("onbeforeunload", onBeforeUnload);
}
// Safari has a dummy event handler on page unload so that it won't
// use its bfcache. Safari <= 3.1 has an issue with restoring the "document"
// object when page is returned to via the back button using its bfcache.
else if (Prototype.Browser.WebKit) {
window.addEventListener("unload", Prototype.emptyFunction, false);
}
return {
// 为element的某个事件注册处理函数。
observe: function(element, eventName, handler) {
element = $(element);
var name = getDOMEventName(eventName);
// cache={id:{element:element,eventName:[wrapper1{handler:handler},wrapper2{handler:handler}]}}
var wrapper = createWrapper(element, eventName, handler);
if (!wrapper) return element;
// IE和FF的兼容,addEventListener
if (element.addEventListener) {
element.addEventListener(name, wrapper, false);
} else {
element.attachEvent("on" + name, wrapper);
}
return element;
},
//unscribe the Listener
stopObserving: function(element, eventName, handler) {
element = $(element);
eventName = Object.isString(eventName) ? eventName : null;
// cache={id:{element:element,eventName:[wrapper1{handler:handler},wrapper2{handler:handler}]}}
var id = getEventID(element), c = cache[id];
if (!c) {
return element;
}
// handler不存在,eventName存在,就unscribe all the Listener of this eventName
else if (!handler && eventName) {
getWrappersForEventName(id, eventName).each(function(wrapper) {
Event.stopObserving(element, eventName, wrapper.handler);
});
return element;
}
// eventName不存在,unscribe all the Listener of this element。
else if (!eventName) {
Object.keys(c).without("element").each(function(eventName) {
Event.stopObserving(element, eventName);
});
return element;
}
var wrapper = findWrapper(id, eventName, handler);
if (!wrapper) return element;
// unregist element Listener.
var name = getDOMEventName(eventName);
// 从Dom element removeEventListener
if (element.removeEventListener) {//FF
element.removeEventListener(name, wrapper, false);
} else {//IE
element.detachEvent("on" + name, wrapper);
}
// 清除相当的handler
destroyWrapper(id, eventName, handler);
return element;
},
// 手工触发DOM元素监听。
fire: function(element, eventName, memo) {
// http://www.nabble.com/firefox-DOM-does-not-have-the-fireEvent-function-t2188792.html
// 在FF中实现IE fireEvent方法
element = $(element);
//不知道为了兼容那一个FF系列的游览器。
if (element == document && document.createEvent && !element.dispatchEvent)
element = document.documentElement;
/*
* initEvent 该方法将初始化 Document.createEvent() 方法创建的合成 Event 对象的 type
* 属性、bubbles 属性和 cancelable 属性。 只有在新创建的 Event 对象被 Document 对象或 Element
* 对象的 dispatchEvent() 方法分派之前,才能调用 Event.initEvent() 方法。
*/
var event;
if (document.createEvent) {
// FF中人工fire,先create event,second: initEvent,then dispatchEvent
event = document.createEvent("HTMLEvents");
// event.initEvent(eventType,canBubble,cancelable)
event.initEvent("dataavailable", true, true);
} else {// IE
event = document.createEventObject();
event.eventType = "ondataavailable";
}
event.eventName = eventName;
event.memo = memo || { };
if (document.createEvent) {// FF
element.dispatchEvent(event);
} else {// IE
element.fireEvent(event.eventType, event);
}
return Event.extend(event);
}
};
})());
// Event的静态方法
Object.extend(Event, Event.Methods);
// Element的静态方法
Element.addMethods({
fire: Event.fire,
observe: Event.observe,
stopObserving: Event.stopObserving
});
// document方法,但条理化。也就是
// fire: function(element, eventName, memo)不要传入element
//它现在已经默认为document
Object.extend(document, {
fire: Element.Methods.fire.methodize(),
observe: Element.Methods.observe.methodize(),
stopObserving: Element.Methods.stopObserving.methodize(),
loaded: false
});
(function() {
/*
* Support for the DOMContentLoaded event is based on work by Dan Webb,
* Matthias Miller, Dean Edwards, John Resig and Diego Perini.
*/
var timer;
function fireContentLoadedEvent() {
if (document.loaded) return;
if (timer) window.clearInterval(timer);
document.loaded = true;
// mothodize,document.fire("dom:loaded")==Element.Methods.fire(document,"dom:loaded")
document.fire("dom:loaded");
}
function isCssLoaded() {
return true;
}
// FF系列
if (document.addEventListener) {
// Opera\WebKit isCssLoaded
if (Prototype.Browser.Opera) {
isCssLoaded = function() {
var sheets = document.styleSheets, length = sheets.length;
while (length--) if (sheets[length].disabled) return false;
return true;
};
// Force check to end when window loads
Event.observe(window, "load", function() { isCssLoaded = function() { return true } });
}
else if (Prototype.Browser.WebKit) {
isCssLoaded = function() {
var length = document.getElementsByTagName('style').length,
links = document.getElementsByTagName('link');
for (var i=0, link; link = links[i]; i++)
if(link.getAttribute('rel') == "stylesheet") length++;
return document.styleSheets.length >= length;
};
}
document.addEventListener("DOMContentLoaded", function() {
// Ensure all stylesheets are loaded, solves Opera/Safari issue
// arguments.callee
// /Reference to the currently executing function.
if (!isCssLoaded()) return arguments.callee.defer();
fireContentLoadedEvent();
}, false);
} else {// IE
document.attachEvent("onreadystatechange", function() {
if (document.readyState == "complete") {
document.detachEvent("onreadystatechange", arguments.callee);
fireContentLoadedEvent();
}
});
if (window == top) {
timer = setInterval(function() {
try {
document.documentElement.doScroll("left");
} catch(e) { return }
fireContentLoadedEvent();
}, 10);
}
}
// Safari <3.1 doesn't support DOMContentLoaded
if (Prototype.Browser.WebKit && (navigator.userAgent.match(/AppleWebKit\/(\d+)/)[1] < 525)) {
timer = setInterval(function() {
if (/loaded|complete/.test(document.readyState) && isCssLoaded())
fireContentLoadedEvent();
}, 10);
}
// Worst case fallback...
Event.observe(window, "load", fireContentLoadedEvent);
})();