Refactored event.js.

This commit is contained in:
Andrew Dupont 2008-10-02 01:00:05 -05:00
parent d55932fc8c
commit b878bfe3ff
1 changed files with 334 additions and 271 deletions

View File

@ -1,24 +1,120 @@
if (!window.Event) var 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,
(function() {
var IE_EVENT_SYSTEM = (window.attachEvent && !window.opera);
var Event = {};
cache: { },
// Define keycode constants.
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
});
Event.cache = {};
function _isButton(event, code) {
switch (code) {
case 0: return event.which == 1 && !event.metaKey;
case 1: return event.which == 1 && event.metakey;
default: return false;
}
}
if (window.attachEvent && !window.opera) {
// IE's event system doesn't map left/right/middle the same way.
var buttonMap = { 0: 1, 1: 4, 2: 2 };
_isButton = function(event, code) {
return event.button === buttonMap[code];
};
} else if (Prototype.Browser.Safari) {
// In Safari we have to account for when the user holds down
// the "meta" key.
_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;
}
};
}
relatedTarget: function(event) {
function _isLeftClick(event) { return _isButton(event, 0); }
function _isRightClick(event) { return _isButton(event, 1); }
function _isMiddleClick(event) { return _isButton(event, 2); }
function _element(event) {
event = Event.extend(event);
var node = event.target, type = event.type,
currentTarget = event.currentTarget;
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 (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
&& currentTarget.type === 'radio')
node = currentTarget;
if (type === 'load' || type === 'error')
node = currentTarget;
}
// Fix a Safari bug where a text node gets passed as the target of an
// anchor click rather than the anchor itself.
if (node.nodeType == Node.TEXT_NODE)
node = node.parentNode;
return Element.extend(node);
}
function _findElement(event, expression) {
var element = Event.element(event);
if (!expression) return element;
var elements = [element].concat(element.ancestors());
return Selector.findElement(elements, expression, 0);
}
function _pointer(event) {
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))
};
}
function _pointerX(event) { return _pointer(event).x; }
function _pointerY(event) { return _pointer(event).y; }
function _stop(event) {
Event.extend(event);
event.preventDefault();
event.stopPropagation();
// Set a "stopped" property so that a custom event can be inspected
// after the fact to determine whether or not it was stopped.
event.stopped = true;
}
// Simulates the relatedTarget property in IE.
function _relatedTarget(event) {
var element;
switch(event.type) {
case 'mouseover': element = event.fromElement; break;
@ -27,322 +123,289 @@ Object.extend(Event, {
}
return Element.extend(element);
}
});
Event.Methods = (function() {
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);
};
}
return {
isLeftClick: function(event) { return isButton(event, 0) },
isMiddleClick: function(event) { return isButton(event, 1) },
isRightClick: function(event) { return isButton(event, 2) },
element: function(event) {
event = Event.extend(event);
var node = event.target,
type = event.type,
currentTarget = event.currentTarget;
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 (type === 'load' || type === 'error' ||
(type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
&& currentTarget.type === 'radio'))
node = currentTarget;
}
if (node.nodeType == Node.TEXT_NODE) node = node.parentNode;
return Element.extend(node);
},
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);
},
pointer: function(event) {
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.Methods = {
pointer: _pointer,
pointerX: _pointerX,
pointerY: _pointerY,
element: _element,
findElement: _findElement,
isLeftClick: _isLeftClick,
isRightClick: _isRightClick,
isMiddleClick: _isMiddleClick,
stop: _stop
};
})();
Event.extend = (function() {
var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
// Compile the list of methods that get extended onto Events.
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]" }
if (IE_EVENT_SYSTEM) {
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;
event._extendedByPrototype = Prototype.emptyFunction;
var pointer = Event.pointer(event);
Object.extend(event, {
target: event.srcElement,
relatedTarget: Event.relatedTarget(event),
pageX: pointer.x,
pageY: pointer.y
});
return Object.extend(event, methods);
};
} else {
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) {
// IE's method for extending events.
function _extend(event) {
if (!event) return false;
if (event._extendedByPrototype) return event;
event._extendedByPrototype = Prototype.emptyFunction;
var pointer = _pointer(event);
Object.extend(event, {
target: event.srcElement,
relatedTarget: _relatedTarget(event),
pageX: pointer.x,
pageY: pointer.y
});
return Object.extend(event, _methods);
}
if (IE_EVENT_SYSTEM) {
Event.extend = _extend;
} else {
Event.prototype = window.Event.prototype || document.createEvent("HTMLEvents")['__proto__'];
Object.extend(Event.prototype, _methods);
Event.extend = Prototype.K;
}
function _getEventID(element) {
if (element._prototypeEventID) return element._prototypeEventID[0];
arguments.callee.id = arguments.callee.id || 1;
return element._prototypeEventID = [++arguments.callee.id];
}
function getDOMEventName(eventName) {
_getEventID.id = 1;
function _getDOMEventName(eventName) {
if (eventName && eventName.include(':')) return "dataavailable";
return eventName;
}
function getCacheForID(id) {
return cache[id] = cache[id] || { };
function _getCacheForID(id) {
return Event.cache[id] = Event.cache[id] || { };
}
function getWrappersForEventName(id, eventName) {
var c = getCacheForID(id);
function _getRespondersForEvent(id, eventName) {
var c = _getCacheForID(id);
return c[eventName] = c[eventName] || [];
}
function createWrapper(element, eventName, handler) {
var id = getEventID(element);
var c = getWrappersForEventName(id, eventName);
if (c.pluck("handler").include(handler)) return false;
function _createResponder(element, eventName, handler) {
var id = _getEventID(element), r = _getRespondersForEvent(id, eventName);
var wrapper = function(event) {
if (!Event || !Event.extend ||
(event.eventName && event.eventName != eventName))
return false;
// Work around the issue that permits a handler to be attached more than
// once to the same element & event type.
if (r.pluck('handler').include(handler)) return false;
var responder = function(event) {
if (!Event || !Event.extend) return false;
// If it's a custom event, but not the _correct_ custom event, ignore it.
if (!Object.isUndefined(event.eventName) &&
event.eventName !== eventName)
return false;
Event.extend(event);
handler.call(element, event);
};
wrapper.handler = handler;
c.push(wrapper);
return wrapper;
responder.handler = handler;
r.push(responder);
return responder;
}
function findWrapper(id, eventName, handler) {
var c = getWrappersForEventName(id, eventName);
return c.find(function(wrapper) { return wrapper.handler == handler });
function _findResponder(id, eventName, handler) {
var r = _getRespondersForEvent(id, eventName);
return r.find( function(responder) {
return responder.handler === handler;
});
}
function destroyWrapper(id, eventName, handler) {
var c = getCacheForID(id);
if (!c[eventName]) return false;
c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
function _destroyResponder(id, eventName, handler) {
var c = _getCacheForID(id);
if (Object.isUndefined(c[eventName])) return false;
c[eventName] = c[eventName].without(_findResponder(id, eventName, handler));
}
function destroyCache() {
for (var id in cache)
function _destroyCache() {
for (var id in Event.cache) {
for (var eventName in cache[id])
cache[id][eventName] = null;
}
}
// Internet Explorer needs to remove event handlers on page unload
// in order to avoid memory leaks.
if (window.attachEvent) {
if (IE_EVENT_SYSTEM) {
window.attachEvent("onunload", destroyCache);
}
// Safari has a dummy event handler on page unload so that it won't
// Safari needs 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.
if (Prototype.Browser.WebKit) {
window.addEventListener('unload', Prototype.emptyFunction, false);
}
}
function _observe(element, eventName, handler) {
element = $(element);
return {
observe: function(element, eventName, handler) {
element = $(element);
var name = getDOMEventName(eventName);
var wrapper = createWrapper(element, eventName, handler);
if (!wrapper) return element;
if (element.addEventListener) {
element.addEventListener(name, wrapper, false);
} else {
element.attachEvent("on" + name, wrapper);
}
return element;
},
stopObserving: function(element, eventName, handler) {
element = $(element);
var id = getEventID(element), name = getDOMEventName(eventName);
if (!handler && eventName) {
getWrappersForEventName(id, eventName).each(function(wrapper) {
element.stopObserving(eventName, wrapper.handler);
});
return element;
} else if (!eventName) {
Object.keys(getCacheForID(id)).each(function(eventName) {
element.stopObserving(eventName);
});
return element;
}
var wrapper = findWrapper(id, eventName, handler);
if (!wrapper) return element;
if (element.removeEventListener) {
element.removeEventListener(name, wrapper, false);
} else {
element.detachEvent("on" + name, wrapper);
}
destroyWrapper(id, eventName, handler);
return element;
},
fire: function(element, eventName, memo) {
element = $(element);
if (element == document && document.createEvent && !element.dispatchEvent)
element = document.documentElement;
var event;
if (document.createEvent) {
event = document.createEvent("HTMLEvents");
event.initEvent("dataavailable", true, true);
} else {
event = document.createEventObject();
event.eventType = "ondataavailable";
}
event.eventName = eventName;
event.memo = memo || { };
if (document.createEvent) {
element.dispatchEvent(event);
} else {
element.fireEvent(event.eventType, event);
}
return Event.extend(event);
var name = _getDOMEventName(eventName);
var responder = _createResponder(element, eventName, handler);
if (!responder) return element;
if (element.addEventListener) {
element.addEventListener(name, responder, false);
} else {
element.attachEvent("on" + name, responder);
}
};
})());
return element;
}
function _stopObserving(element, eventName, handler) {
element = $(element);
var id = _getEventID(element), name = _getDOMEventName(eventName);
if (eventName && !handler) {
// If an event name is passed without a handler, we stop observing all
// handlers of that type.
_getRespondersForEvent(id, eventName).each( function(r) {
element.stopObserving(eventName, r.handler);
});
return element;
} else if (!eventName) {
// If both the event name and the handler are omitted, we stop observing
// _all_ handlers on the element.
Object.keys(_getCacheForID(id)).each( function(eventName) {
element.stopObserving(eventName);
});
return element;
}
var responder = _findResponder(id, eventName, handler);
if (!responder) return element;
if (element.removeEventListener) {
element.removeEventListener(name, responder, false);
} else {
element.detachEvent("on" + name, responder);
}
_destroyResponder(id, eventName, handler);
return element;
}
function _fire(element, eventName, memo) {
element = $(element);
// In the W3C system, all calls to document.fire should treat
// document.documentElement as the target.
if (element == document && document.createEvent && !element.dispatchEvent)
element = document.documentElement;
var event;
if (document.createEvent) {
event = document.createEvent("HTMLEvents");
event.initEvent("dataavailable", true, true);
} else {
event = document.createEventObject();
event.eventType = "ondataavailable";
}
event.eventName = eventName;
event.memo = memo || { };
if (document.createEvent) {
element.dispatchEvent(event);
} else {
element.fireEvent(event.eventType, event);
}
return Event.extend(event);
}
Object.extend(Event, {
fire: _fire,
observe: _observe,
stopObserving: _stopObserving
});
Element.addMethods({
fire: _fire,
observe: _observe,
stopObserving: _stopObserving
});
Object.extend(document, {
fire: _fire.methodize(),
observe: _observe.methodize(),
stopObserving: _stopObserving.methodize(),
loaded: false
});
Object.extend(Event, Event.Methods);
Object.extend(Event, Event.Methods);
Element.addMethods({
fire: Event.fire,
observe: Event.observe,
stopObserving: Event.stopObserving
});
Object.extend(document, {
fire: Element.Methods.fire.methodize(),
observe: Element.Methods.observe.methodize(),
stopObserving: Element.Methods.stopObserving.methodize(),
loaded: false
});
// Export to the global scope.
if (window.Event) {
Object.extend(window.Event, Event);
} else {
window.Event = Event;
}
})();
(function() {
/* Support for the DOMContentLoaded event is based on work by Dan Webb,
Matthias Miller, Dean Edwards and John Resig. */
var timer;
var _timer;
function fireContentLoadedEvent() {
function _fireContentLoadedEvent() {
if (document.loaded) return;
if (timer) window.clearInterval(timer);
if (_timer) window.clearInterval(_timer);
document.fire("dom:loaded");
document.loaded = true;
}
function _webkitContentLoadedCheck() {
var s = document.readyState;
if (s === "loaded" || s === "complete")
_fireContentLoadedEvent();
}
function _IEContentLoadedCheck() {
if (this.readyState == "complete") {
this.onreadystatechange = null;
_fireContentLoadedEvent();
}
}
if (document.addEventListener) {
if (Prototype.Browser.WebKit) {
timer = window.setInterval(function() {
if (/loaded|complete/.test(document.readyState))
fireContentLoadedEvent();
}, 0);
Event.observe(window, "load", fireContentLoadedEvent);
_timer = window.setInterval(_webkitContentLoadedCheck, 0);
Event.observe(window, "load", _fireContentLoadedEvent);
} else {
document.addEventListener("DOMContentLoaded",
fireContentLoadedEvent, false);
document.addEventListener("DOMContentLoaded",
_fireContentLoadedEvent, false);
}
} else {
document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
$("__onDOMContentLoaded").onreadystatechange = function() {
if (this.readyState == "complete") {
this.onreadystatechange = null;
fireContentLoadedEvent();
}
};
$("__onDOMContentLoaded").onreadystatechange = _IEContentLoadedCheck;
}
})();
})();