diff --git a/CHANGELOG b/CHANGELOG index 6d99751..3e65271 100644 --- a/CHANGELOG +++ b/CHANGELOG @@ -1,5 +1,19 @@ *SVN* +* Make the eventName and handler arguments to Event.stopObserving optional. If no handler is specified, all handlers for the given event are unregistered. If no event name is specified, all observed events on the element are unregistered. [sam] + +* Add cross-support for the DOMContentLoaded event through a Prototype custom event on document called "contentloaded". The DOMContentLoaded event fires before window.load, when the entire HTML document, but not necessarily its images, stylesheets or other assets, has loaded. Based on [6596]. [sam, Mislav Marohnić] + Example: + document.observe("contentloaded", function() { + $$("a").invoke("identify"); // give all tags an ID + }); + +* Add Event.fire and Element.Methods.fire for firing custom events. Prototype custom events piggyback on a real DOM event ("ondataavailable"), so they bubble and cancel. You can fire custom events from any element, or fire global events on the document object. Observe custom events just as you'd observe a regular DOM event. [sam, Seth Dillingham] + +* Extend the event object with methods from Event.Methods and normalize it in IE. [sam, Mislav Marohnić] + +* Remove support for observing the capturing phase of DOM events, since we can't support it in all browsers. [sam] + * Add Ajax.Response object which supports the following methods: responseJSON, headerJSON, getHeader, getAllHeaders and handles browser discrepancies in the other response methods. Add sanitizeJSON, evalJS and evalJSON to Ajax.Request. Closes #8122, #8006, #7295. [Tobie Langel] * Add an isRunningFromRake property to unit tests. [Tobie Langel] diff --git a/src/dom.js b/src/dom.js index c6c5458..5069973 100644 --- a/src/dom.js +++ b/src/dom.js @@ -22,10 +22,28 @@ if (Prototype.BrowserFeatures.XPath) { /*--------------------------------------------------------------------------*/ +if (!window.Node) + var Node = { }; + +Object.extend(Node, { + ELEMENT_NODE: 1, + ATTRIBUTE_NODE: 2, + TEXT_NODE: 3, + CDATA_SECTION_NODE: 4, + ENTITY_REFERENCE_NODE: 5, + ENTITY_NODE: 6, + PROCESSING_INSTRUCTION_NODE: 7, + COMMENT_NODE: 8, + DOCUMENT_NODE: 9, + DOCUMENT_TYPE_NODE: 10, + DOCUMENT_FRAGMENT_NODE: 11, + NOTATION_NODE: 12 +}); + (function() { var element = this.Element; this.Element = function(tagName, attributes) { - attributes = attributes || {}; + attributes = attributes || { }; tagName = tagName.toLowerCase(); var cache = Element.cache; if (Prototype.Browser.IE && attributes.name) { @@ -36,10 +54,10 @@ if (Prototype.BrowserFeatures.XPath) { if (!cache[tagName]) cache[tagName] = Element.extend(document.createElement(tagName)); return Element.writeAttribute(cache[tagName].cloneNode(false), attributes); }; - Object.extend(this.Element, element || {}); + Object.extend(this.Element, element || { }); }).call(window); -Element.cache = {}; +Element.cache = { }; Element.Methods = { visible: function(element) { @@ -127,7 +145,7 @@ Element.Methods = { wrap: function(element, wrapper, attributes) { element = $(element); if (Object.isElement(wrapper)) - $(wrapper).writeAttribute(attributes || {}); + $(wrapper).writeAttribute(attributes || { }); else if (typeof wrapper == 'string') wrapper = new Element(wrapper, attributes); else wrapper = new Element('div', wrapper); if (element.parentNode) @@ -259,7 +277,7 @@ Element.Methods = { writeAttribute: function(element, name, value) { element = $(element); - var attributes = {}, t = Element._attributeTranslations.write; + var attributes = { }, t = Element._attributeTranslations.write; if (typeof name == 'object') attributes = name; else attributes[name] = value === undefined ? true : value; @@ -315,16 +333,6 @@ Element.Methods = { 'removeClassName' : 'addClassName'](className); }, - observe: function() { - Event.observe.apply(Event, arguments); - return $A(arguments).first(); - }, - - stopObserving: function() { - Event.stopObserving.apply(Event, arguments); - return $A(arguments).first(); - }, - // removes whitespace-only text node children cleanWhitespace: function(element) { element = $(element); @@ -585,7 +593,7 @@ Element.Methods = { setHeight: true, offsetTop: 0, offsetLeft: 0 - }, arguments[2] || {}); + }, arguments[2] || { }); // find page position of source source = $(source); @@ -663,7 +671,7 @@ Element._attributeTranslations = { className: 'class', htmlFor: 'for' }, - values: {} + values: { } } }; @@ -868,7 +876,7 @@ else if (Prototype.Browser.WebKit) { var n = document.createTextNode(' '); element.appendChild(n); element.removeChild(n); - } catch (e) {} + } catch (e) { } return element; } @@ -1019,13 +1027,13 @@ Element.Methods.Simulated = { } }; -Element.Methods.ByTag = {}; +Element.Methods.ByTag = { }; Object.extend(Element, Element.Methods); if (!Prototype.BrowserFeatures.ElementExtensions && document.createElement('div').__proto__) { - window.HTMLElement = {}; + window.HTMLElement = { }; window.HTMLElement.prototype = document.createElement('div').__proto__; Prototype.BrowserFeatures.ElementExtensions = true; } @@ -1034,7 +1042,7 @@ Element.extend = (function() { if (Prototype.BrowserFeatures.SpecificElementExtensions) return Prototype.K; - var Methods = {}, ByTag = Element.Methods.ByTag; + var Methods = { }, ByTag = Element.Methods.ByTag; var extend = Object.extend(function(element) { if (!element || element._extendedByPrototype || @@ -1093,7 +1101,7 @@ Element.addMethods = function(methods) { methods = arguments[1]; } - if (!tagName) Object.extend(Element.Methods, methods || {}); + if (!tagName) Object.extend(Element.Methods, methods || { }); else { if (Object.isArray(tagName)) tagName.each(extend); else extend(tagName); @@ -1102,7 +1110,7 @@ Element.addMethods = function(methods) { function extend(tagName) { tagName = tagName.toUpperCase(); if (!Element.Methods.ByTag[tagName]) - Element.Methods.ByTag[tagName] = {}; + Element.Methods.ByTag[tagName] = { }; Object.extend(Element.Methods.ByTag[tagName], methods); } @@ -1136,7 +1144,7 @@ Element.addMethods = function(methods) { klass = 'HTML' + tagName.capitalize() + 'Element'; if (window[klass]) return window[klass]; - window[klass] = {}; + window[klass] = { }; window[klass].prototype = document.createElement(tagName).__proto__; return window[klass]; } @@ -1158,5 +1166,5 @@ Element.addMethods = function(methods) { delete Element.ByTag; if (Element.extend.refresh) Element.extend.refresh(); - Element.cache = {}; + Element.cache = { }; }; diff --git a/src/event.js b/src/event.js index e1a3ea9..6016c76 100644 --- a/src/event.js +++ b/src/event.js @@ -1,6 +1,4 @@ -if (!window.Event) { - var Event = new Object(); -} +if (!window.Event) var Event = { }; Object.extend(Event, { KEY_BACKSPACE: 8, @@ -16,34 +14,32 @@ Object.extend(Event, { KEY_END: 35, KEY_PAGEUP: 33, KEY_PAGEDOWN: 34, + KEY_INSERT: 45, + + DOMEvents: ['click', 'dblclick', 'mousedown', 'mouseup', 'mouseover', + 'mousemove', 'mouseout', 'keypress', 'keydown', 'keyup', + 'load', 'unload', 'abort', 'error', 'resize', 'scroll', + 'select', 'change', 'submit', 'reset', 'focus', 'blur', + 'DOMFocusIn', 'DOMFocusOut', 'DOMActivate', + 'DOMSubtreeModified', 'DOMNodeInserted', + 'NodeInsertedIntoDocument', 'DOMAttrModified', + 'DOMCharacterDataModified'], - element: function(event) { - return $(event.target || event.srcElement); - }, - - isLeftClick: function(event) { - return (((event.which) && (event.which == 1)) || - ((event.button) && (event.button == 1))); - }, - - pointerX: function(event) { - return event.pageX || (event.clientX + - (document.documentElement.scrollLeft || document.body.scrollLeft)); - }, - - pointerY: function(event) { - return event.pageY || (event.clientY + - (document.documentElement.scrollTop || document.body.scrollTop)); - }, - - stop: function(event) { - if (event.preventDefault) { - event.preventDefault(); - event.stopPropagation(); - } else { - event.returnValue = false; - event.cancelBubble = true; + relatedTarget: function(event) { + var element; + switch(event.type) { + case 'mouseover': element = event.fromElement; break; + case 'mouseout': element = event.toElement; break; + default: return null; } + return Element.extend(element); + } +}); + +Event.Methods = { + element: function(event) { + var node = event.target; + return Element.extend(node.nodeType == Node.TEXT_NODE ? node.parentNode : node); }, findElement: function(event, expression) { @@ -51,57 +47,251 @@ Object.extend(Event, { return element.match(expression) ? element : element.up(expression); }, - observers: false, - - _observeAndCache: function(element, name, observer, useCapture) { - if (!this.observers) this.observers = []; - if (element.addEventListener) { - this.observers.push([element, name, observer, useCapture]); - element.addEventListener(name, observer, useCapture); - } else if (element.attachEvent) { - this.observers.push([element, name, observer, useCapture]); - element.attachEvent('on' + name, observer); - } - }, - - unloadCache: function() { - if (!Event.observers) return; - for (var i = 0, length = Event.observers.length; i < length; i++) { - Event.stopObserving.apply(this, Event.observers[i]); - Event.observers[i][0] = null; - } - Event.observers = false; + isLeftClick: function(event) { + return (((event.which) && (event.which == 1)) || + ((event.button) && (event.button == 1))); }, - observe: function(element, name, observer, useCapture) { - element = $(element); - useCapture = useCapture || false; - - if (name == 'keypress' && - (Prototype.Browser.WebKit || element.attachEvent)) - name = 'keydown'; - - Event._observeAndCache(element, name, observer, useCapture); + pointer: function(event) { + return { + x: event.pageX || (event.clientX + + (document.documentElement.scrollLeft || document.body.scrollLeft)), + y: event.pageY || (event.clientY + + (document.documentElement.scrollTop || document.body.scrollTop)) + }; }, - stopObserving: function(element, name, observer, useCapture) { - element = $(element); - useCapture = useCapture || false; + pointerX: function(event) { return Event.pointer(event).x }, + pointerY: function(event) { return Event.pointer(event).y }, + + stop: function(event) { + event.preventDefault(); + event.stopPropagation(); + } +}; + +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; + + 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); + }; - if (name == 'keypress' && - (Prototype.Browser.WebKit || element.attachEvent)) - name = 'keydown'; + } else { + Event.prototype = Event.prototype || document.createEvent("Events").__proto__; + Object.extend(Event.prototype, methods); + return Prototype.K; + } +})(); + +Object.extend(Event, (function() { + var cache = { }; + + function getEventID(element) { + if (element._eventID) return element._eventID; + arguments.callee.id = arguments.callee.id || 1; + return element._eventID = ++arguments.callee.id; + } + + function getDOMEventName(eventName) { + if (!Event.DOMEvents.include(eventName)) return "dataavailable"; + return { keypress: "keydown" }[eventName] || eventName; + } + + function getCacheForID(id) { + return cache[id] = cache[id] || { }; + } + + function getWrappersForEventName(id, eventName) { + var c = getCacheForID(id); + return c[eventName] = c[eventName] || []; + } + + function createWrapper(id, eventName, handler) { + var c = getWrappersForEventName(id, eventName); + if (c.pluck("handler").include(handler)) return false; - if (element.removeEventListener) { - element.removeEventListener(name, observer, useCapture); - } else if (element.detachEvent) { - try { - element.detachEvent('on' + name, observer); - } catch (e) {} + var wrapper = function(event) { + if (event.eventName && event.eventName != eventName) + return false; + + Event.extend(event); + handler.call(event.target, event); + }; + + wrapper.handler = handler; + c.push(wrapper); + return wrapper; + } + + function findWrapper(id, eventName, handler) { + var c = getWrappersForEventName(id, eventName); + return c.find(function(wrapper) { return wrapper.handler == handler }); + } + + function destroyWrapper(id, eventName, handler) { + var c = getCacheForID(id), name = getDOMEventName(eventName); + if (!c[name]) return false; + c[name] = c[name].without(findWrapper(id, eventName, handler)); + } + + function destroyCache() { + for (var id in cache) + for (var eventName in cache[id]) + cache[id][eventName] = null; + } + + if (window.attachEvent) { + window.attachEvent("onunload", destroyCache); + } + + return { + observe: function(element, eventName, handler) { + element = $(element); + var id = getEventID(element), name = getDOMEventName(eventName); + + var wrapper = createWrapper(id, eventName, handler); + if (!wrapper) return false; + + if (element.addEventListener) { + element.addEventListener(name, wrapper, false); + } else { + element.attachEvent("on" + name, wrapper); + } + }, + + stopObserving: function(element, eventName, handler) { + element = $(element); + var id = getEventID(element), name = getDOMEventName(eventName); + + if (!handler && eventName) { + return getWrappersForEventName(id, eventName).each(function(wrapper) { + element.stopObserving(eventName, wrapper.handler); + }) && false; + + } else if (!eventName) { + return Object.keys(getCacheForID(id)).each(function(eventName) { + element.stopObserving(eventName); + }) && false; + } + + var wrapper = findWrapper(id, eventName, handler); + if (!wrapper) return false; + + if (element.removeEventListener) { + element.removeEventListener(name, wrapper, false); + } else { + element.detachEvent("on" + name, wrapper); + } + }, + + fire: function(element, eventName, memo) { + element = $(element); + if (element == document && document.createEvent && !element.dispatchEvent) + element = document.documentElement; + + if (document.createEvent) { + var event = document.createEvent("Events"); + event.initEvent("dataavailable", true, true); + } else { + var 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 element; } + }; +})()); + +Object.extend(Event, Event.Methods); + +Element.addMethods({ + fire: function() { + Event.fire.apply(Event, arguments); + return $A(arguments).first(); + }, + + observe: function() { + Event.observe.apply(Event, arguments); + return $A(arguments).first(); + }, + + stopObserving: function() { + Event.stopObserving.apply(Event, arguments); + return $A(arguments).first(); } }); -/* prevent memory leaks in IE */ -if (Prototype.Browser.IE) - Event.observe(window, 'unload', Event.unloadCache, false); +Object.extend(document, { + fire: Element.Methods.fire.methodize(), + observe: Element.Methods.observe.methodize(), + stopObserving: Element.Methods.stopObserving.methodize() +}); + +(function() { + /* Support for the DOMContentLoaded event is based on work by Dan Webb, + Matthias Miller, Dean Edwards and John Resig. */ + + var timer, fired = false; + + function fireContentLoadedEvent() { + if (fired) return; + if (timer) window.clearInterval(timer); + document.fire("contentloaded"); + fired = true; + } + + if (document.addEventListener) { + if (Prototype.Browser.WebKit) { + timer = window.setInterval(function() { + if (/loaded|complete/.test(document.readyState)) + fireContentLoadedEvent(); + }, 0); + + Event.observe(window, "load", fireContentLoadedEvent); + + } else { + document.addEventListener("DOMContentLoaded", fireContentLoadedEvent, false); + } + + } else { + var dummy = location.protocol == "https:" ? "https://javascript:void(0)" : "javascript:void(0)"; + document.write(" + + + + + +

Prototype Unit test file

+

+ Test of event handling code in event.js +

+ + +
+ + + + + + + +