You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@tapestry.apache.org by hl...@apache.org on 2012/08/09 00:04:54 UTC
[1/2] git commit: Create core/builder module for dynamically
constructing Elements Create core/exceptionframe for displaying the Ajax
exception frame Rewire t5-ajax to use core/exceptionframe Rework other code
to use core/spi:domReady()
Updated Branches:
refs/heads/5.4-js-rewrite c562186a3 -> f9d53e733
Create core/builder module for dynamically constructing Elements
Create core/exceptionframe for displaying the Ajax exception frame
Rewire t5-ajax to use core/exceptionframe
Rework other code to use core/spi:domReady()
Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/f9d53e73
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/f9d53e73
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/f9d53e73
Branch: refs/heads/5.4-js-rewrite
Commit: f9d53e73340a37e297e5e6eeab870a37ad4581a6
Parents: 88045f0
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Wed Aug 8 15:04:35 2012 -0700
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Wed Aug 8 15:04:35 2012 -0700
----------------------------------------------------------------------
.../META-INF/modules/core/builder.coffee | 107 ++++++++++++
.../META-INF/modules/core/exceptionframe.coffee | 73 +++++++++
.../coffeescript/META-INF/modules/core/spi.coffee | 126 +++++++++++----
.../coffeescript/META-INF/modules/core/zone.coffee | 5 +-
.../main/resources/org/apache/tapestry5/t5-ajax.js | 80 +---------
.../resources/org/apache/tapestry5/t5-prototype.js | 12 +-
.../resources/org/apache/tapestry5/tapestry.js | 2 +-
7 files changed, 284 insertions(+), 121 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f9d53e73/tapestry-core/src/main/coffeescript/META-INF/modules/core/builder.coffee
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/core/builder.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/core/builder.coffee
new file mode 100644
index 0000000..0bf76d8
--- /dev/null
+++ b/tapestry-core/src/main/coffeescript/META-INF/modules/core/builder.coffee
@@ -0,0 +1,107 @@
+# Copyright 2012 The Apache Software Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http:#www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# core/builder
+#
+# A system for constructing DOM element nodes for a particular structure in minimal code. The basic syntax is:
+# builder(elementDescription, body...) and the result is a DOM element. The element description is
+# primarily the name of the element.
+#
+# It may contain sequences of ".name"; these appropriate CSS syntax to describe a CSS class name for the element. The
+# element name may be omitted when CSS class names are specified, in which case "div" is the default. Multiple CSS class
+# names are allowed.
+#
+# The description may also include a '>' character; this represents the start of a nested element; in this way
+# a structure can quickly be specified.
+#
+# The body may consist of:
+# Objects: used to specify attributes and event handles of the element
+# Strings: literal text
+# Array: a nested element definition
+# For an Object, each key and value is simply added as an attribute. However, keys that start with "on" are assumed to
+# be event handler functions. The special key "on" consists of nested event handlers for the keys. The following are
+# equivlent:
+# { onclick: -> ... } and
+# { on: { click: -> ... }}
+define ["_", "core/spi"], (_, spi) ->
+ createElement = (elementDescription) ->
+ # TODO: Support #id for setting the id of an element, maybe others, such as ?name for the name of an input element.
+ # That will require a regex or more sophisticated parsing.
+ terms = elementDescription.trim().split(".")
+
+ elementName = terms.shift() or "div"
+
+ element = document.createElement elementName
+
+ element.className = terms.join " "
+
+ return element
+
+ addAttributes = (element, attributes) ->
+ return unless attributes
+
+ wrapper = spi.wrap element
+
+ for name, value of attributes
+ if name is "on"
+ for eventName, handler of value
+ wrapper.on eventName, handler
+ else if name.startsWith "on"
+ wrapper.on (name.substring 2), value
+ else
+ wrapper.setAttribute name, value
+
+ return null
+
+ addAttributesAndBody = (element, body) ->
+ for nested in body
+ unless nested?
+ # Ignore null nodes
+ else if _.isString nested
+ element.appendChild document.createTextNode nested
+ else if _.isArray nested
+ [elementDescription, nestedBody...] = nested
+ nestedElement = buildTree elementDescription, nestedBody
+ element.appendChild nestedElement
+ else if _.isObject nested
+ addAttributes element, nested
+ else throw new Error "Unexpected body value <#{nested}> while building DOM elements."
+
+ return null
+
+ buildTree = (elementDescription, body) ->
+ splitx = elementDescription.indexOf ">"
+ currentDescription =
+ if splitx is -1
+ elementDescription
+ else
+ elementDescription.substring 0, splitx
+
+ element = createElement currentDescription
+
+ if splitx is -1
+ addAttributesAndBody element, body
+ else
+ nestedDescription = elementDescription.substring splitx + 1
+ nestedElement = buildTree nestedDescription, body
+ element.appendChild nestedElement
+
+ return element
+
+ # The module exports a single function that builds the tree of elements and returns the top element, wrapped as an
+ # spi.ElementWrapper.
+ (elementDescription, body...) ->
+ element = buildTree elementDescription, body
+
+ spi.wrap element
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f9d53e73/tapestry-core/src/main/coffeescript/META-INF/modules/core/exceptionframe.coffee
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/core/exceptionframe.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/core/exceptionframe.coffee
new file mode 100644
index 0000000..0293f2b
--- /dev/null
+++ b/tapestry-core/src/main/coffeescript/META-INF/modules/core/exceptionframe.coffee
@@ -0,0 +1,73 @@
+# Copyright 2012 The Apache Software Foundation
+#
+# Licensed under the Apache License, Version 2.0 (the "License");
+# you may not use this file except in compliance with the License.
+# You may obtain a copy of the License at
+#
+# http:#www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS,
+# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+# See the License for the specific language governing permissions and
+# limitations under the License.
+
+# core/exceptionframe
+#
+# Manages a special element used to present a HTML exception report from an Ajax request (where a non-markup response
+# was expected, including a partial page render response).
+
+define ["core/spi", "core/builder", "_"],
+ (spi, builder, _) ->
+ container = null
+ iframe = null
+ iframeDocument = null
+
+ write = (content) ->
+ # Clear current content:
+ iframeDocument.open()
+ # Write new content:
+ iframeDocument.write content
+ iframeDocument.close()
+
+ clear = (event) ->
+ event.stop()
+ write ""
+ container.hide()
+
+ # Called after the window has resized to adjust the size of the iframe.
+ resize = ->
+ dims = spi.viewportDimensions()
+ ;
+
+ iframe.width = dims.width - 100
+ iframe.height = dims.height - 120
+
+ create = ->
+ return if container
+
+ container = builder ".t-exception-container",
+ ["iframe.t-exception-frame", width: "100%"],
+ [".t-exception-controls > span.t-exception-close", "Close"]
+
+ spi.body().append container.hide()
+
+ iframe = (container.find "iframe").element
+
+ # See http://xkr.us/articles/dom/iframe-document/
+
+ iframeDocument = iframe.contentWindow or iframe.contentDocument
+ if iframeDocument.document
+ iframeDocument = iframeDocument.document
+
+ container.on "click", ".t-exception-close", clear
+
+ spi.on window, "resize", (_.debounce resize, 20)
+
+ # Export single function:
+
+ (exceptionContent) ->
+ create()
+ write exceptionContent
+ resize()
+ container.show()
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f9d53e73/tapestry-core/src/main/coffeescript/META-INF/modules/core/spi.coffee
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/core/spi.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/core/spi.coffee
index ff40b3e..e9aacce 100644
--- a/tapestry-core/src/main/coffeescript/META-INF/modules/core/spi.coffee
+++ b/tapestry-core/src/main/coffeescript/META-INF/modules/core/spi.coffee
@@ -13,35 +13,63 @@
# limitations under the License.
# Service Provider Interface
-# This is the core of the abstraction layer that allows the majority of components to operate
-# without caring whether the underlying infrastructure framework is Prototype, jQuery, or something else.
-# This is the standard SPI, which wraps Prototype ... but does it in a way that makes it relatively
-# easy to swap in jQuery instead.
+#
+# This is the core of the abstraction layer that allows the majority of components to operate without caring whether the
+# underlying infrastructure framework is Prototype, jQuery, or something else. This is the standard SPI, which wraps
+# Prototype ... but does it in a way that makes it relatively easy to swap in jQuery instead.
# TODO: Define a dependency on "prototype" when that's exposed as a stub module.
define ["_"], (_) ->
split = (str) ->
_(str.split " ").reject (s) -> s is ""
+ # Converts content (provided to ElementWrapper.update() or .append()) into an appropriate type. This primarily exists
+ # to validate the value, and to "unpack" an ElementWrapper into a DOM element.
+ convertContent = (content) ->
+ if _.isString content
+ return content
+
+ if _.isElement content
+ return content
+
+ if content.constructor?.name is "ElementWrapper"
+ return content.element
+
+ throw new Error "Provided value <#{content}> is not valid as DOM element content."
+
# Generic view of an Event that is passed to a handler function.
+ #
# Properties:
+ # nativeEvent - the native Event object, which may provide additional information
# memo - the object passed to ElementWrapper.trigger()
+ # type - the name of the event that was triggered
+ # char - the character value of the pressed key, if a printable character (string)
+ # key -The key value of the pressed key. This is the same as the char property for printable keys,
+ # or a key name for others.
# stop() - function to prevent propogation of the event (both bubbling and the default action).
- class Event
+ class EventWrapper
- constructor: (@prototypeEvent) ->
- @memo = @prototypeEvent.memo
+ constructor: (event) ->
+ @nativeEvent = event
+ @memo = event.memo
+ @type = event.type
+
+ @char = event.char
+ @key = event.key
# Stops the event which prevents further propagation of the event,
# as well as event bubbling.
stop: ->
- @prototypeEvent.stop()
+ @nativeEvent.stop()
# Value returned from on(); an EventHandler is used to stop listening to
- # events, or even pause listening.
+ # events, or even temporarily pause listening.
class EventHandler
# Registers the handler as an event listener for matching elements and event names.
+ # Note: it is possible to add handlers for events on the window object, but
+ # start() and stop() do not do anything for such events.
+ #
# elements - array of DOM elements
# eventNames - array of event names
# match - selector to match bubbled elements, or null
@@ -52,14 +80,19 @@ define ["_"], (_) ->
wrapped = (prototypeEvent, matchedElement) ->
# Set "this" to be the matched element (jQuery style), rather than
# the element on which the event is observed.
- handler.call(matchedElement, new Event prototypeEvent)
+ handler.call(matchedElement, new EventWrapper prototypeEvent)
# Prototype Event.Handler instances
@protoHandlers = []
_.each elements, (element) =>
_.each eventNames, (eventName) =>
- @protoHandlers.push element.on eventName, match, wrapped
+ if element is window
+ unless _.isEmpty match
+ throw Error("Matching of elements by selector is not supported for window events.")
+ Event.observe element, eventName, wrapped
+ else
+ @protoHandlers.push element.on eventName, match, wrapped
# Invoked after stop() to restart event listening. Returns this EventHandler instance.
start: ->
@@ -100,10 +133,10 @@ define ["_"], (_) ->
getAttribute: (name) ->
@element.readAttribute name
- # Set the value of the attribute to the given value, then returns this
- # ElementWrapper.
- # Note: Prototype has special support for values null, true, and false
- # that may not be duplicated by other implementations of the SPI.
+ # Set the value of the attribute to the given value, then returns this ElementWrapper.
+ #
+ # Note: Prototype has special support for values null, true, and false that may not be duplicated by other
+ # implementations of the SPI.
setAttribute: (name, value) ->
# TODO: case where name is an object, i.e., multiple attributes in a single call.
# Well, you can just do it, but its not guaranteed to work the same across
@@ -125,16 +158,36 @@ define ["_"], (_) ->
@element.addClassName name
this
- # Updates this element with new content, replacing any old content. The new content
- # may be HTML text, or a DOM element, or null (to remove the body of the element).
+ # Updates this element with new content, replacing any old content. The new content may be HTML text, or a DOM
+ # element, or null (to remove the body of the element).
+ #
# Returns this ElementWrapper.
update: (content) ->
- @element.update content
+ @element.update (convertContent content)
+ this
+
+ # Appends new content (Element or HTML markup string) to the element.
+ #
+ # Returns this ElementWrapper.
+ append: (content) ->
+ @element.insert bottom: (convertContent content)
this
- # Returns an ElementWrapper for this element's containing element. The ElementWrapper
- # is created lazily, and cached. Returns null if this element has no parentNode (either because
- # this element is the document object, or because this element is not yet attached to the DOM).
+ # Finds the first child element that matches the CSS selector.
+ #
+ # Returns the ElementWrapper for the child element, or null if not found.
+ find: (selector) ->
+ match = @element.down selector
+
+ # Prototype returns undefined if not found, we want to return null.
+ if match
+ new ElementWrapper match
+ else
+ return null
+
+ # Returns an ElementWrapper for this element's containing element. The ElementWrapper is created lazily, and
+ # cached. Returns null if this element has no parentNode (either because this element is the document object, or
+ # because this element is not yet attached to the DOM).
getContainer: ->
unless @container
return null unless element.parentNode
@@ -142,14 +195,15 @@ define ["_"], (_) ->
@container
- # Returns true if this element is visible, false otherwise. This does not check
- # to see if all containers of the element are visible.
+ # Returns true if this element is visible, false otherwise. This does not check to see if all containers of the
+ # element are visible.
visible: ->
@element.visible()
# Fires a named event. Returns this ElementWrapper.
+ #
# eventName - name of event to trigger on the wrapped Element
- # memo - optional value assocated with the event; available as Event.memo in event handler functions
+ # memo - optional value assocated with the event; available as WrappedeEvent.memo in event handler functions
trigger: (eventName, memo) ->
throw new Error("Attempt to trigger event with null event name") unless eventName?
@@ -157,20 +211,17 @@ define ["_"], (_) ->
this
# Adds an event handler for one or more events.
+ #
# events - one or more event names, separated by spaces
# match - optional: CSS expression used as a filter; only events that bubble
# up to the wrapped element from an originating element that matches the CSS expression
# will invoke the handler.
- # handler - function invoked; the function is passed an Event object.
+ # handler - function invoked; the function is passed an EventWrapper object.
+ #
# Returns an EventHandler object, making it possible to turn event observation on or off.
on: (events, match, handler) ->
exports.on @element, events, match, handler
- # Searches for elements contained within this element, matching the CSS selector.
- # Returns an array of matching elements.
- find: (selector) ->
- @element.select selector
-
parseSelectorToElements = (selector) ->
if _.isString selector
return $$ selector
@@ -183,10 +234,12 @@ define ["_"], (_) ->
[selector]
+ bodyWrapper = null
+
exports =
- # Invokes the callback only once the DOM has finished loading all elements (other resources, such as images,
- # may still be in-transit). This is a safe time to search the DOM, modify attributes, and attach event handlers.
+ # Invokes the callback only once the DOM has finished loading all elements (other resources, such as images, may
+ # still be in-transit). This is a safe time to search the DOM, modify attributes, and attach event handlers.
# Returns this modules exports, for chained calls.
domReady: (callback) ->
document.observe "dom:loaded", callback
@@ -213,8 +266,15 @@ define ["_"], (_) ->
# Returns a wrapper for the provided DOM element that includes key behaviors:
# hide(), show(), remove(), etc.
- # element - a DOM element, or the unique id of a DOM element
+ # element - a DOM element, or the window, or the unique id of a DOM element
# Returns the ElementWrapper.
wrap: (element) ->
throw new Error("Attempt to wrap a null DOM element") unless element
new ElementWrapper element
+
+ # Returns a wrapped version of the document.body element. Care must be take to not invoke this function before the body
+ # element exists; typically only after the DOM has loaded, such as a domReady() callback.
+ body: -> bodyWrapper ?= (exports.wrap document.body)
+
+ # Returns the current dimensions of the viewport. An object with keys width and height is returned.
+ viewportDimensions: -> document.viewport.getDimensions()
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f9d53e73/tapestry-core/src/main/coffeescript/META-INF/modules/core/zone.coffee
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/core/zone.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/core/zone.coffee
index 8986cef..afbb04c 100644
--- a/tapestry-core/src/main/coffeescript/META-INF/modules/core/zone.coffee
+++ b/tapestry-core/src/main/coffeescript/META-INF/modules/core/zone.coffee
@@ -15,9 +15,7 @@
define ["core/spi", "core/events"],
(spi, events) ->
spi.domReady ->
- body = spi.wrap document.body
-
- body.on events.zone.update, (event) ->
+ spi.body().on events.zone.update, (event) ->
zone = spi.wrap this
zone.trigger events.zone.willUpdate
@@ -30,6 +28,5 @@ define ["core/spi", "core/events"],
zone.trigger events.zone.didUpdate
-
# No meaningful value is returned.
true
\ No newline at end of file
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f9d53e73/tapestry-core/src/main/resources/org/apache/tapestry5/t5-ajax.js
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/org/apache/tapestry5/t5-ajax.js b/tapestry-core/src/main/resources/org/apache/tapestry5/t5-ajax.js
index 32f024c..ff2a4eb 100644
--- a/tapestry-core/src/main/resources/org/apache/tapestry5/t5-ajax.js
+++ b/tapestry-core/src/main/resources/org/apache/tapestry5/t5-ajax.js
@@ -17,89 +17,15 @@
* Defines Tapestry Ajax support, which includes sending requests and receiving
* replies, but also includes default handlers for errors and failures, and
* processing of Tapestry's partial page render response (a common response for
- * many types of Ajax requests). `
+ * many types of Ajax requests).
*/
-define("core/compat/t5-ajax", ["core/compat/t5-spi"], function () {
+define("core/compat/t5-ajax", ["core/exceptionframe"], function (exceptionframe) {
T5.define("ajax", function () {
- var _ = T5._;
- var $ = T5.$;
- var spi = T5.spi;
-
- var exceptionContainer, iframe, iframeDocument;
function noop() {
}
- function writeToErrorIFrame(content) {
- // Clear current content.
- iframeDocument.open();
- // Write in the new content.
- iframeDocument.write(content);
- iframeDocument.close();
-
- }
-
- function resizeExceptionDialog() {
- // Very Prototype specific!
- var dims = document.viewport.getDimensions();
-
- iframe.width = dims.width - 100;
- iframe.height = dims.height - (100 + 20);
- }
-
- /**
- * When there's a server-side failure, Tapestry sends back the exception report page as HTML.
- * This function creates and displays a dialog that presents that content to the user using
- * a created iframe element.
- * @param exceptionContext HTML markup for the exception report
- */
- function showExceptionDialog(exceptionContent) {
-
- if (!exceptionContainer) {
- var markup = [ "<div class='t-exception-container'>",
- "<iframe class='t-exception-frame' width='100%'></iframe>",
- "<div class='t-exception-controls'>",
- "<span class='t-exception-close'>Close</span>",
- "</div>",
- "</div>"].join("");
-
- exceptionContainer = T5.dom.find(T5.dom.appendMarkup(document.body, markup), 'div.t-exception-container');
-
- iframe = T5.dom.find(exceptionContainer, "iframe");
-
- // See http://xkr.us/articles/dom/iframe-document/
-
- iframeDocument = (iframe.contentWindow || iframe.contentDocument);
- if (iframeDocument.document) {
- iframeDocument = iframeDocument.document;
- }
-
- var closeButton = T5.dom.find(exceptionContainer, ".t-exception-close");
-
- T5.dom.observe(closeButton, "click", function (event) {
- event.stop();
- writeToErrorIFrame("");
- T5.dom.hide(exceptionContainer);
- });
-
- // Call it now to set initial width/height.
-
- resizeExceptionDialog();
-
- // Very Prototype specific:
-
- // See http://groups.google.com/group/prototype-scriptaculous/browse_thread/thread/1b0ce3e94020121f/cdbab773fd8e7a4b
- // debounced to handle the rate at which IE sends the resizes (every pixel!)
-
- Event.observe(window, "resize", _.debounce(resizeExceptionDialog, 20));
- }
-
-
- writeToErrorIFrame(exceptionContent);
-
- exceptionContainer.show();
- }
function defaultFailure(transport) {
}
@@ -146,7 +72,7 @@ define("core/compat/t5-ajax", ["core/compat/t5-spi"], function () {
defaultFailure: defaultFailure,
defaultException: defaultException,
defaultSuccess: noop,
- showExceptionDialog: showExceptionDialog,
+ showExceptionDialog: exceptionframe,
request: request
};
});
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f9d53e73/tapestry-core/src/main/resources/org/apache/tapestry5/t5-prototype.js
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/org/apache/tapestry5/t5-prototype.js b/tapestry-core/src/main/resources/org/apache/tapestry5/t5-prototype.js
index 183b2ea..e12500c 100644
--- a/tapestry-core/src/main/resources/org/apache/tapestry5/t5-prototype.js
+++ b/tapestry-core/src/main/resources/org/apache/tapestry5/t5-prototype.js
@@ -18,12 +18,12 @@
* Prototype JavaScript library. May also make modifications to Prototype to
* work with Tapestry.
*/
-define("core/compat/t5-spi", ["core/compat/t5", "core/compat/t5-events", "core/compat/t5-pubsub"], function () {
+define("core/compat/t5-spi", ["core/spi", "core/compat/t5", "core/compat/t5-events", "core/compat/t5-pubsub"], function (spi) {
T5.define("spi", function () {
function observe(element, eventName, listener) {
- var handler = $(element).on(eventName, listener);
+ var handler = spi.on(element, eventName, listener);
element = null;
eventName = null;
@@ -34,17 +34,17 @@ define("core/compat/t5-spi", ["core/compat/t5", "core/compat/t5-events", "core/c
};
}
- document.observe("dom:loaded", function () {
+ /** This will likely go soon. */
+ spi.domReady(function () {
T5.sub(T5.events.REMOVE_EVENT_HANDLERS, null, function (element) {
+ // TODO: Remaining Prototype dependency here:
Event.stopObserving(element);
}
);
});
function appendMarkup(element, markup) {
- var element = $(element);
-
- element.insert({ bottom: markup });
+ spi.wrap(element).append(markup);
return element;
}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/f9d53e73/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js b/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js
index c9cc2cd..570ffe5 100644
--- a/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js
+++ b/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js
@@ -180,7 +180,7 @@ define("core/compat/tapestry", [
* document object (support for which is provided by Prototype).
*/
onDOMLoaded: function (callback) {
- document.observe("dom:loaded", callback);
+ spi.domReady(callback);
},
/**