0
-if (!window.Event) var Event = { };
0
- relatedTarget: function(event) {
0
- case 'mouseover': element = event.fromElement; break;
0
- case 'mouseout': element = event.toElement; break;
+ var IE_EVENT_SYSTEM = (window.attachEvent && !window.opera);
0
+ // Define keycode constants.
0
+ Object.extend(Event, {
0
+ function _isButton(event, code) {
0
+ case 0: return event.which == 1 && !event.metaKey;
0
+ case 1: return event.which == 1 && event.metakey;
0
+ default: return false;
0
- return Element.extend(element);
0
-Event.Methods = (function() {
0
- if (Prototype.Browser.IE) {
+ if (window.attachEvent && !window.opera) {
0
+ // IE's event system doesn't map left/right/middle the same way.
0
var buttonMap = { 0: 1, 1: 4, 2: 2 };
0
- isButton = function(event, code) {
0
- return event.button == buttonMap[code];
0
+ _isButton = function(event, code) {
0
+ return event.button === buttonMap[code];
0
- } else if (Prototype.Browser.WebKit) {
0
- isButton = function(event, code) {
0
+ } else if (Prototype.Browser.Safari) {
0
+ // In Safari we have to account for when the user holds down
0
+ _isButton = function(event, code) {
0
- case 0: return event.which == 1 && !event.metaKey;
0
- case 1: return event.which == 1 && event.metaKey;
0
+ case 0: return event.which == 1 && !event.metaKey;
0
+ case 1: return event.which == 1 && event.metakey;
0
+ function _isLeftClick(event) { return _isButton(event, 0); }
0
+ function _isRightClick(event) { return _isButton(event, 1); }
0
+ function _isMiddleClick(event) { return _isButton(event, 2); }
0
+ function _element(event) {
0
+ event = Event.extend(event);
0
- isButton = function(event, code) {
0
- return event.which ? (event.which === code + 1) : (event.button === code);
0
+ var node = event.target, type = event.type,
0
+ currentTarget = event.currentTarget;
0
+ if (currentTarget && currentTarget.tagName) {
0
+ // Firefox screws up the "click" event when moving between radio buttons
0
+ // via arrow keys. It also screws up the "load" and "error" events on images,
0
+ // reporting the document as the target instead of the original image.
0
+ if (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
0
+ && currentTarget.type === 'radio')
0
- isLeftClick: function(event) { return isButton(event, 0) },
0
- isMiddleClick: function(event) { return isButton(event, 1) },
0
- isRightClick: function(event) { return isButton(event, 2) },
0
+ if (type === 'load' || type === 'error')
0
- element: function(event) {
0
- event = Event.extend(event);
0
+ // Fix a Safari bug where a text node gets passed as the target of an
0
+ // anchor click rather than the anchor itself.
0
+ if (node.nodeType == Node.TEXT_NODE)
0
+ node = node.parentNode;
0
- var node = event.target,
0
- currentTarget = event.currentTarget;
0
- if (currentTarget && currentTarget.tagName) {
0
- // Firefox screws up the "click" event when moving between radio buttons
0
- // via arrow keys. It also screws up the "load" and "error" events on images,
0
- // reporting the document as the target instead of the original image.
0
- if (type === 'load' || type === 'error' ||
0
- (type === 'click' && currentTarget.tagName.toLowerCase() === 'input'
0
- && currentTarget.type === 'radio'))
0
- if (node.nodeType == Node.TEXT_NODE) node = node.parentNode;
0
- return Element.extend(node);
0
- findElement: function(event, expression) {
0
- var element = Event.element(event);
0
- if (!expression) return element;
0
- var elements = [element].concat(element.ancestors());
0
- return Selector.findElement(elements, expression, 0);
0
- pointer: function(event) {
0
- var docElement = document.documentElement,
0
- body = document.body || { scrollLeft: 0, scrollTop: 0 };
0
- x: event.pageX || (event.clientX +
0
- (docElement.scrollLeft || body.scrollLeft) -
0
- (docElement.clientLeft || 0)),
0
- y: event.pageY || (event.clientY +
0
- (docElement.scrollTop || body.scrollTop) -
0
- (docElement.clientTop || 0))
0
- pointerX: function(event) { return Event.pointer(event).x },
0
- pointerY: function(event) { return Event.pointer(event).y },
0
- stop: function(event) {
0
- event.preventDefault();
0
- event.stopPropagation();
0
+ return Element.extend(node);
0
+ function _findElement(event, expression) {
0
+ var element = Event.element(event);
0
+ if (!expression) return element;
0
+ var elements = [element].concat(element.ancestors());
0
+ return Selector.findElement(elements, expression, 0);
0
+ function _pointer(event) {
0
+ var docElement = document.documentElement,
0
+ body = document.body || { scrollLeft: 0, scrollTop: 0 };
0
+ x: event.pageX || (event.clientX +
0
+ (docElement.scrollLeft || body.scrollLeft) -
0
+ (docElement.clientLeft || 0)),
0
+ y: event.pageY || (event.clientY +
0
+ (docElement.scrollTop || body.scrollTop) -
0
+ (docElement.clientTop || 0))
+ function _pointerX(event) { return _pointer(event).x; }
0
+ function _pointerY(event) { return _pointer(event).y; }
0
+ function _stop(event) {
0
+ event.preventDefault();
0
+ event.stopPropagation();
0
+ // Set a "stopped" property so that a custom event can be inspected
0
+ // after the fact to determine whether or not it was stopped.
0
+ // Simulates the relatedTarget property in IE.
0
+ function _relatedTarget(event) {
0
+ case 'mouseover': element = event.fromElement; break;
0
+ case 'mouseout': element = event.toElement; break;
0
+ return Element.extend(element);
0
+ findElement: _findElement,
0
+ isLeftClick: _isLeftClick,
0
+ isRightClick: _isRightClick,
0
+ isMiddleClick: _isMiddleClick,
0
-Event.extend = (function() {
0
- var methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
0
+ // Compile the list of methods that get extended onto Events.
0
+ var _methods = Object.keys(Event.Methods).inject({ }, function(m, name) {
0
m[name] = Event.Methods[name].methodize();
0
- if (Prototype.Browser.IE) {
0
- Object.extend(methods, {
0
- stopPropagation: function() { this.cancelBubble = true },
0
- preventDefault: function() { this.returnValue = false },
0
- inspect: function() { return "[object Event]" }
0
+ if (IE_EVENT_SYSTEM) {
0
+ Object.extend(_methods, {
0
+ stopPropagation: function() { this.cancelBubble = true; },
0
+ preventDefault: function() { this.returnValue = false; },
0
+ inspect: function() { return "[object Event]"; }
0
+ // IE's method for extending events.
0
+ function _extend(event) {
0
+ if (!event) return false;
0
+ if (event._extendedByPrototype) return event;
0
+ event._extendedByPrototype = Prototype.emptyFunction;
0
+ var pointer = _pointer(event);
0
+ Object.extend(event, {
0
+ target: event.srcElement,
0
+ relatedTarget: _relatedTarget(event),
0
- return function(event) {
0
- if (!event) return false;
0
- if (event._extendedByPrototype) return event;
0
- event._extendedByPrototype = Prototype.emptyFunction;
0
- var pointer = Event.pointer(event);
0
- Object.extend(event, {
0
- target: event.srcElement,
0
- relatedTarget: Event.relatedTarget(event),
0
- return Object.extend(event, methods);
0
+ return Object.extend(event, _methods);
0
+ if (IE_EVENT_SYSTEM) {
0
+ Event.extend = _extend;
0
- Event.prototype = Event.prototype || document.createEvent("HTMLEvents")['__proto__'];
0
- Object.extend(Event.prototype, methods);
0
+ Event.prototype = window.Event.prototype || document.createEvent("HTMLEvents")['__proto__'];
0
+ Object.extend(Event.prototype, _methods);
0
+ Event.extend = Prototype.K;
0
-Object.extend(Event, (function() {
0
- var cache = Event.cache;
0
- function getEventID(element) {
0
+ function _getEventID(element) {
0
if (element._prototypeEventID) return element._prototypeEventID[0];
0
- arguments.callee.id = arguments.callee.id || 1;
0
return element._prototypeEventID = [++arguments.callee.id];
0
- function getDOMEventName(eventName) {
0
+ function _getDOMEventName(eventName) {
0
if (eventName && eventName.include(':')) return "dataavailable";
0
- function getCacheForID(id) {
0
- return cache[id] = cache[id] || { };
0
+ function _getCacheForID(id) {
0
+ return Event.cache[id] = Event.cache[id] || { };
0
- function getWrappersForEventName(id, eventName) {
0
- var c = getCacheForID(id);
0
+ function _getRespondersForEvent(id, eventName) {
0
+ var c = _getCacheForID(id);
0
return c[eventName] = c[eventName] || [];
0
- function createWrapper(element, eventName, handler) {
0
- var id = getEventID(element);
0
- var c = getWrappersForEventName(id, eventName);
0
- if (c.pluck("handler").include(handler)) return false;
0
+ function _createResponder(element, eventName, handler) {
0
+ var id = _getEventID(element), r = _getRespondersForEvent(id, eventName);
0
- var wrapper = function(event) {
0
- if (!Event || !Event.extend ||
0
- (event.eventName && event.eventName != eventName))
0
+ // Work around the issue that permits a handler to be attached more than
0
+ // once to the same element & event type.
0
+ if (r.pluck('handler').include(handler)) return false;
0
+ var responder = function(event) {
0
+ if (!Event || !Event.extend) return false;
0
+ // If it's a custom event, but not the _correct_ custom event, ignore it.
0
+ if (!Object.isUndefined(event.eventName) &&
0
+ event.eventName !== eventName)
0
handler.call(element, event);
0
- wrapper.handler = handler;
0
+ responder.handler = handler;
0
- function findWrapper(id, eventName, handler) {
0
- var c = getWrappersForEventName(id, eventName);
0
- return c.find(function(wrapper) { return wrapper.handler == handler });
0
+ function _findResponder(id, eventName, handler) {
0
+ var r = _getRespondersForEvent(id, eventName);
0
+ return r.find( function(responder) {
0
+ return responder.handler === handler;
0
- function destroyWrapper(id, eventName, handler) {
0
- var c = getCacheForID(id);
0
- if (!c[eventName]) return false;
0
- c[eventName] = c[eventName].without(findWrapper(id, eventName, handler));
0
+ function _destroyResponder(id, eventName, handler) {
0
+ var c = _getCacheForID(id);
0
+ if (Object.isUndefined(c[eventName])) return false;
0
+ c[eventName] = c[eventName].without(_findResponder(id, eventName, handler));
0
- function destroyCache() {
0
+ function _destroyCache() {
0
+ for (var id in Event.cache) {
0
for (var eventName in cache[id])
0
cache[id][eventName] = null;
0
// Internet Explorer needs to remove event handlers on page unload
0
// in order to avoid memory leaks.
0
- if (
window.attachEvent) {
0
+ if (
IE_EVENT_SYSTEM) {
0
window.attachEvent("onunload", destroyCache);
0
- // Safari
has a dummy event handler on page unload so that it won't
0
+ // Safari
needs a dummy event handler on page unload so that it won't
0
// use its bfcache. Safari <= 3.1 has an issue with restoring the "document"
0
// object when page is returned to via the back button using its bfcache.
0
if (Prototype.Browser.WebKit) {
0
window.addEventListener('unload', Prototype.emptyFunction, false);
0
+ function _observe(element, eventName, handler) {
0
+ var name = _getDOMEventName(eventName);
0
+ var responder = _createResponder(element, eventName, handler);
0
+ if (!responder) return element;
0
+ if (element.addEventListener) {
0
+ element.addEventListener(name, responder, false);
0
+ element.attachEvent("on" + name, responder);
0
+ function _stopObserving(element, eventName, handler) {
0
+ var id = _getEventID(element), name = _getDOMEventName(eventName);
0
- observe: function(element, eventName, handler) {
0
- var name = getDOMEventName(eventName);
0
- var wrapper = createWrapper(element, eventName, handler);
0
- if (!wrapper) return element;
0
- if (element.addEventListener) {
0
- element.addEventListener(name, wrapper, false);
0
- element.attachEvent("on" + name, wrapper);
0
+ if (eventName && !handler) {
0
+ // If an event name is passed without a handler, we stop observing all
0
+ // handlers of that type.
0
+ _getRespondersForEvent(id, eventName).each( function(r) {
0
+ element.stopObserving(eventName, r.handler);
0
- stopObserving: function(element, eventName, handler) {
0
- var id = getEventID(element), name = getDOMEventName(eventName);
0
- if (!handler && eventName) {
0
- getWrappersForEventName(id, eventName).each(function(wrapper) {
0
- element.stopObserving(eventName, wrapper.handler);
0
- } else if (!eventName) {
0
- Object.keys(getCacheForID(id)).each(function(eventName) {
0
- element.stopObserving(eventName);
0
- var wrapper = findWrapper(id, eventName, handler);
0
- if (!wrapper) return element;
0
- if (element.removeEventListener) {
0
- element.removeEventListener(name, wrapper, false);
0
- element.detachEvent("on" + name, wrapper);
0
- destroyWrapper(id, eventName, handler);
0
+ } else if (!eventName) {
0
+ // If both the event name and the handler are omitted, we stop observing
0
+ // _all_ handlers on the element.
0
+ Object.keys(_getCacheForID(id)).each( function(eventName) {
0
+ element.stopObserving(eventName);
0
+ var responder = _findResponder(id, eventName, handler);
0
+ if (!responder) return element;
0
+ if (element.removeEventListener) {
0
+ element.removeEventListener(name, responder, false);
0
+ element.detachEvent("on" + name, responder);
0
+ _destroyResponder(id, eventName, handler);
0
- fire: function(element, eventName, memo) {
0
- if (element == document && document.createEvent && !element.dispatchEvent)
0
- element = document.documentElement;
0
- if (document.createEvent) {
0
- event = document.createEvent("HTMLEvents");
0
- event.initEvent("dataavailable", true, true);
0
- event = document.createEventObject();
0
- event.eventType = "ondataavailable";
0
- event.eventName = eventName;
0
- event.memo = memo || { };
0
- if (document.createEvent) {
0
- element.dispatchEvent(event);
0
- element.fireEvent(event.eventType, event);
0
- return Event.extend(event);
0
+ function _fire(element, eventName, memo) {
0
+ // In the W3C system, all calls to document.fire should treat
0
+ // document.documentElement as the target.
0
+ if (element == document && document.createEvent && !element.dispatchEvent)
0
+ element = document.documentElement;
0
+ if (document.createEvent) {
0
+ event = document.createEvent("HTMLEvents");
0
+ event.initEvent("dataavailable", true, true);
0
+ event = document.createEventObject();
0
+ event.eventType = "ondataavailable";
0
-Object.extend(Event, Event.Methods);
0
- observe: Event.observe,
0
- stopObserving: Event.stopObserving
0
+ event.eventName = eventName;
0
+ event.memo = memo || { };
0
+ if (document.createEvent) {
0
+ element.dispatchEvent(event);
0
+ element.fireEvent(event.eventType, event);
0
+ return Event.extend(event);
0
+ Object.extend(Event, {
0
+ stopObserving: _stopObserving
0
+ stopObserving: _stopObserving
0
+ Object.extend(document, {
0
+ fire: _fire.methodize(),
0
+ observe: _observe.methodize(),
0
+ stopObserving: _stopObserving.methodize(),
0
+ Object.extend(Event, Event.Methods);
0
-Object.extend(document, {
0
- fire: Element.Methods.fire.methodize(),
0
- observe: Element.Methods.observe.methodize(),
0
- stopObserving: Element.Methods.stopObserving.methodize(),
0
+ // Export to the global scope.
0
+ Object.extend(window.Event, Event);
0
/* Support for the DOMContentLoaded event is based on work by Dan Webb,
0
Matthias Miller, Dean Edwards and John Resig. */
0
- function
fireContentLoadedEvent() {
0
+ function
_fireContentLoadedEvent() {
0
if (document.loaded) return;
0
- if (timer) window.clearInterval(timer);
0
+ if (_timer) window.clearInterval(_timer);
0
document.fire("dom:loaded");
0
document.loaded = true;
0
+ function _webkitContentLoadedCheck() {
0
+ var s = document.readyState;
0
+ if (s === "loaded" || s === "complete")
0
+ _fireContentLoadedEvent();
0
+ function _IEContentLoadedCheck() {
0
+ if (this.readyState == "complete") {
0
+ this.onreadystatechange = null;
0
+ _fireContentLoadedEvent();
0
if (document.addEventListener) {
0
if (Prototype.Browser.WebKit) {
0
- timer = window.setInterval(function() {
0
- if (/loaded|complete/.test(document.readyState))
0
- fireContentLoadedEvent();
0
- Event.observe(window, "load", fireContentLoadedEvent);
0
+ _timer = window.setInterval(_webkitContentLoadedCheck, 0);
0
+ Event.observe(window, "load", _fireContentLoadedEvent);
0
- document.addEventListener("DOMContentLoaded",
0
- fireContentLoadedEvent, false);
0
+ document.addEventListener("DOMContentLoaded",
0
+ _fireContentLoadedEvent, false);
0
document.write("<script id=__onDOMContentLoaded defer src=//:><\/script>");
0
- $("__onDOMContentLoaded").onreadystatechange = function() {
0
- if (this.readyState == "complete") {
0
- this.onreadystatechange = null;
0
- fireContentLoadedEvent();
0
+ $("__onDOMContentLoaded").onreadystatechange = _IEContentLoadedCheck;
0
\ No newline at end of file
To pre-empt the obvious question: I used the initial underscores because I wanted some way of distinguishing private stuff (both functions and variables) from local stuff.
ya I donno about the underscores i mean (every function declared inside the closure is private so adding the underscore to all of them seem a bit much.
Some optimization can be made on Event#pointer(X|Y). It would be faster if Event#pointerX and Event#pointerY would be seperated so when calling one of them you don’t have the overhead of getting the unwanted pointer x or y. Event#pointer could then call both.
I don’t have a preference on the underscores but some consistency would be nice, I noticed Tobie didn’t use them in the rewritten parts I’ve seen so far.
It would be good to use IE_EVENT_SYSTEM here.
I can go either way on whether we use underscores for functions. But when we refer to variables declared outside the function, I think it’s critical for our own sanity to know which ones are being defined inside our handy closure and which ones will go all the way up to “window” before they’re resolved.
Couldn’t we go the other way round (i.e. do the standard compliant stuff first, then test for ‘attachEvent’ in window). That would avoid the ugly opera sniff here (which isn’t safe).
Regarding _foo vs foo function names, I’d advocate using the underscore prefix only for private methods.
You mean just for utility methods and not for the stuff we “export” to window?