Author: hlship
Date: Fri Oct 31 16:42:34 2008
New Revision: 709601
URL: http://svn.apache.org/viewvc?rev=709601&view=rev
Log:
TAP5-313: Provide configuration to move links to JavaScript libraries to the
top of the page
Added:
tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/internal/services/add_script_links_at_top.txt
Modified:
tapestry/tapestry5/trunk/src/site/apt/cookbook/lib.apt
tapestry/tapestry5/trunk/src/site/apt/guide/conf.apt
tapestry/tapestry5/trunk/src/site/apt/index.apt
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinkerImpl.java
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/DocumentLinkerImplTest.java
tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/internal/services/add_script_links.txt
Modified: tapestry/tapestry5/trunk/src/site/apt/cookbook/lib.apt
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/src/site/apt/cookbook/lib.apt?rev=709601&r1=709600&r2=709601&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/src/site/apt/cookbook/lib.apt (original)
+++ tapestry/tapestry5/trunk/src/site/apt/cookbook/lib.apt Fri Oct 31 16:42:34
2008
@@ -223,7 +223,9 @@
the application was deployed as happyapp.war).
Tapestry uses a far-future expiration date for classpath assets; this allows
browsers to aggresively cache
- the file, but this causes a problem should a later version of the library
change the file.
+ the file, but this causes a problem should a later version of the library
change the file. This is discussed
+ in detail in
+ {{{http://developer.yahoo.com/performance/rules.html#expires}Yahoo's
Performance Best Practices}}.
To handle this problem, you should map your library assets to a versioned
folder. This can be accomplished using
another contribution from the HappyModule, this time to the
ClasspathAssetAliasManager service
Modified: tapestry/tapestry5/trunk/src/site/apt/guide/conf.apt
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/src/site/apt/guide/conf.apt?rev=709601&r1=709600&r2=709601&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/src/site/apt/guide/conf.apt (original)
+++ tapestry/tapestry5/trunk/src/site/apt/guide/conf.apt Fri Oct 31 16:42:34
2008
@@ -173,6 +173,12 @@
If true, then the page may only be accessed via HTTPS. The
{{{../apidocs/org/apache/tapestry5/annotations/[EMAIL PROTECTED]
annotation will set this value to true.
+ [tapestry.script-at-top]
+ If true, then links for external JavaScript libraries will be placed at
the top of the page, just inside the
+ \<body\> element, rather than in the default location (at the bottom). The
\<script\> block for per-page
+ initialization will be placed at the bottom of the page in either case.
The default (scripts at the bottom)
+ was chosen in accordance with
{{{http://developer.yahoo.com/performance/rules.html#js_bottom}Yahoo Peformance
Best Practices}}.
+
[tapestry.scriptaculous]
The path to the embedded copy of
{{{http://script.aculo.us/}script.aculo.us}} packaged with Tapestry. This value
may be overridden
to use a different version of the script.aculo.us library. Tapestry's
default version is 1.8.1
Modified: tapestry/tapestry5/trunk/src/site/apt/index.apt
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/src/site/apt/index.apt?rev=709601&r1=709600&r2=709601&view=diff
==============================================================================
--- tapestry/tapestry5/trunk/src/site/apt/index.apt (original)
+++ tapestry/tapestry5/trunk/src/site/apt/index.apt Fri Oct 31 16:42:34 2008
@@ -68,6 +68,8 @@
New And Of Note
+ * You can now configure Tapestry to move \<script\> links to the top of the
page.
+
* Event handler methods for Ajax requests may now return a page name, page
class or page instance to force
the browser to redirect to the page.
Modified:
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java?rev=709601&r1=709600&r2=709601&view=diff
==============================================================================
---
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
(original)
+++
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
Fri Oct 31 16:42:34 2008
@@ -91,4 +91,11 @@
* This is used by the default exception report handler service.
*/
public static final String EXCEPTION_REPORT_PAGE =
"tapestry.exception-report-page";
+
+ /**
+ * If true, then links for external JavaScript libraries are placed at the
top of the document (just inside the
+ * <body> element). If false, the default, then the libraries are
placed at the bottom of the document.
+ * Per-page initialization always goes at the bottom.
+ */
+ public static final String SCRIPTS_AT_TOP = "tapestry.script-at-top";
}
Modified:
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinkerImpl.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinkerImpl.java?rev=709601&r1=709600&r2=709601&view=diff
==============================================================================
---
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinkerImpl.java
(original)
+++
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinkerImpl.java
Fri Oct 31 16:42:34 2008
@@ -34,9 +34,12 @@
private final boolean developmentMode;
- public DocumentLinkerImpl(boolean productionMode)
+ private final boolean scriptsAtTop;
+
+ public DocumentLinkerImpl(boolean productionMode, boolean scriptsAtTop)
{
developmentMode = !productionMode;
+ this.scriptsAtTop = scriptsAtTop;
}
public void addStylesheetLink(String styleURL, String media)
@@ -97,7 +100,7 @@
// TAPESTRY-2364
- if (!scripts.isEmpty()) addScriptLinksForIncludedScripts(body,
scripts);
+ addScriptLinksForIncludedScripts(body, scripts);
addDynamicScriptBlock(body);
}
@@ -130,16 +133,24 @@
}
/**
- * Adds a script link for each included script. This is only invoked if
there are scripts to include.
+ * Adds a script link for each included script.
*
* @param body element to add the script links to
- * @param scripts
+ * @param scripts scripts to add
*/
protected void addScriptLinksForIncludedScripts(Element body, List<String>
scripts)
{
- for (String scriptURL : scripts)
+ int count = scripts.size();
+
+ for (int i = 0; i < count; i++)
{
- body.element("script", "src", scriptURL, "type",
"text/javascript");
+ String scriptURL = scripts.get(i);
+
+ Element element = scriptsAtTop
+ ? body.elementAt(i, "script")
+ : body.element("script");
+
+ element.attributes("src", scriptURL, "type", "text/javascript");
}
}
Modified:
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java?rev=709601&r1=709600&r2=709601&view=diff
==============================================================================
---
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java
(original)
+++
tapestry/tapestry5/trunk/tapestry-core/src/main/java/org/apache/tapestry5/services/TapestryModule.java
Fri Oct 31 16:42:34 2008
@@ -1469,6 +1469,9 @@
@Symbol(SymbolConstants.PRODUCTION_MODE)
final boolean productionMode,
+
@Symbol(SymbolConstants.SCRIPTS_AT_TOP)
+ final boolean scriptsAtTop,
+
@Path("${tapestry.default-stylesheet}")
final Asset stylesheetAsset,
@@ -1485,7 +1488,7 @@
{
public void renderMarkup(MarkupWriter writer, MarkupRenderer
renderer)
{
- DocumentLinkerImpl linker = new
DocumentLinkerImpl(productionMode);
+ DocumentLinkerImpl linker = new
DocumentLinkerImpl(productionMode, scriptsAtTop);
RenderSupportImpl support = new RenderSupportImpl(linker,
symbolSource, assetSource,
@@ -1858,6 +1861,8 @@
"WEB-INF/${" +
InternalConstants.TAPESTRY_APP_NAME_SYMBOL + "}.properties");
configuration.add(SymbolConstants.EXCEPTION_REPORT_PAGE,
"ExceptionReport");
+
+ configuration.add(SymbolConstants.SCRIPTS_AT_TOP, "false");
}
@@ -2049,9 +2054,9 @@
/**
* Contributes filters: <dl> <dt>Ajax</dt> <dd>Determines if the request
is Ajax oriented, and redirects to an
* alternative handler if so</dd> <dt>ImmediateRender</dt> <dd>When [EMAIL
PROTECTED]
- *
org.apache.tapestry5.SymbolConstants#SUPPRESS_REDIRECT_FROM_ACTION_REQUESTS
immediate action response rendering}
- * is enabled, generates the markup response (instead of a page redirect
response, which is the normal behavior)
- * </dd> <dt>Secure</dt> <dd>Sends a redirect if an non-secure request
accesses a secure page</dd></dl>
+ * SymbolConstants#SUPPRESS_REDIRECT_FROM_ACTION_REQUESTS immediate action
response rendering} is enabled, generates
+ * the markup response (instead of a page redirect response, which is the
normal behavior) </dd> <dt>Secure</dt>
+ * <dd>Sends a redirect if an non-secure request accesses a secure
page</dd></dl>
*/
public void
contributeComponentEventRequestHandler(OrderedConfiguration<ComponentEventRequestFilter>
configuration,
final
RequestSecurityManager requestSecurityManager,
Modified:
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js?rev=709601&r1=709600&r2=709601&view=diff
==============================================================================
---
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js
(original)
+++
tapestry/tapestry5/trunk/tapestry-core/src/main/resources/org/apache/tapestry5/tapestry.js
Fri Oct 31 16:42:34 2008
@@ -249,19 +249,45 @@
/**
* Passed the JSON content of a Tapestry partial markup response, extracts
* the script and stylesheet information. JavaScript libraries and
stylesheets are loaded,
- * then any script code is evaluated. All three keys are optional:
+ * then the callback is invoked. All three keys are optional:
* <dl>
+ * <dt>redirectURL</dt> <dd>URL to redirect to (in which case, the
callback is not invoked)</dd>
* <dt>scripts</dt><dd>Array of strings (URIs of scripts)</dd>
* <dt>stylesheets</dt><dd>Array of hashes, each hash has key href and
optional key media</dd>
- * <dt>script</dt> <dd>JavaScript to be executed once all scripts are
loaded</dd></dl>
+ *
+ * @param reply JSON response object from the server
+ * @param callback function invoked after the scripts have all loaded
(presumably, to update the DOM)
*/
- processScriptInReply : function(reply)
+ loadScriptsInReply : function(reply, callback)
{
- Tapestry.ScriptManager.addScripts(reply.scripts, reply.script);
+ var redirectURL = reply.redirectURL;
+
+ if (redirectURL)
+ {
+ window.location.pathname = redirectURL;
+
+ // Don't bother loading scripts or invoking the callback.
+
+ return;
+ }
Tapestry.ScriptManager.addStylesheets(reply.stylesheets);
- Tapestry.onDomLoadedCallback();
+ Tapestry.ScriptManager.addScripts(reply.scripts,
+ function()
+ {
+ callback.call(this);
+
+ // After the callback updates the DOM
+ // (presumably), continue on with
+ // evaluating the reply.script
+ // and other final steps.
+
+ if (reply.script) eval(reply.script);
+
+ Tapestry.onDomLoadedCallback();
+
+ });
},
/**
@@ -270,7 +296,6 @@
ajaxFailureHandler : function(response)
{
var message = response.getHeader("X-Tapestry-ErrorMessage");
- "A communication error with the server has occurred.";
Tapestry.updateAjaxConsole("t-err", "Communication with the server
failed: " + message);
@@ -1263,26 +1288,17 @@
},
/**
- * Invoked with a reply (i.e., transport.responseJSON), this updates the
zone's div
+ * Invoked with a reply (i.e., transport.responseJSON), this updates the
zone's div
* and processes any JavaScript in the reply. The response should have a
* content key, and may have script, scripts and stylesheets keys.
* @param reply response in JSON format appropriate to a Tapestry.Zone
*/
processReply : function(reply)
{
- var redirect = reply.redirectURL;
-
- if (redirect)
- {
- window.location.pathname = redirect;
- }
- else
+ Tapestry.loadScriptsInReply(reply, function()
{
-
this.show(reply.content);
-
- Tapestry.processScriptInReply(reply);
- }
+ }.bind(this));
},
/** Initiates an Ajax request to update this zone by sending a request
@@ -1401,24 +1417,22 @@
var param = { };
param[this.below ? "after" : "before"] = newElement;
- // Add the new element with the downloaded content.
-
- this.element.insert(param);
-
- // Update the empty element with the content from the server
-
- newElement.update(reply.content);
+ Tapestry.loadScriptsInReply(reply, function()
+ {
+ // Add the new element with the downloaded content.
- newElement.id = reply.elementId;
+ this.element.insert(param);
- // Handle any scripting issues.
+ // Update the empty element with the content from the
server
- Tapestry.processScriptInReply(reply);
+ newElement.update(reply.content);
- // Add some animation to reveal it all.
+ newElement.id = reply.elementId;
- this.showFunc(newElement);
+ // Add some animation to reveal it all.
+ this.showFunc(newElement);
+ }.bind(this));
}.bind(this);
Tapestry.ajaxRequest(this.url, successHandler);
@@ -1429,20 +1443,19 @@
});
/**
- * Coordinates the execution of JavaScript code blocks (via eval) with the
loading
- * of an array of <script> elements.
+ * Wait for a set of JavaScript libraries to load (in terms of DOM script
elements), then invokes a callback function.
*/
-Tapestry.DependentExecutor = Class.create({
+Tapestry.ScriptLoadMonitor = Class.create({
- initialize : function(prereqs, dependent)
+ initialize : function(scriptElements, callback)
{
- this.dependent = dependent;
+ this.callback = callback;
this.loaded = 0;
- this.toload = prereqs.length;
+ this.toload = scriptElements.length;
var executor = this;
- prereqs.each(function (scriptElement)
+ scriptElements.each(function (scriptElement)
{
if (Prototype.Browser.IE)
{
@@ -1464,16 +1477,20 @@
scriptElement.onload =
executor.loadComplete.bindAsEventListener(executor, scriptElement);
}
});
+
+ // If no scripts to actually load, call the callback immediately.
+
+ if (this.toload == 0) this.callback.call(this);
},
- loadComplete : function(element)
+ loadComplete : function()
{
this.loaded++;
// Evaluated the dependent script only once all the elements have
loaded.
if (this.loaded == this.toload)
- eval(this.dependent);
+ this.callback.call(this);
}
});
@@ -1523,7 +1540,13 @@
return false;
},
- addScripts: function(scripts, dependent)
+ /**
+ * Add scripts, as needed, to the document, then waits for them all to
load, and finally, calls
+ * the callback function.
+ * @param scripts Array of scripts to load
+ * @param callback invoked after scripts are loaded
+ */
+ addScripts: function(scripts, callback)
{
var added = new Array();
@@ -1552,15 +1575,7 @@
}
- if (!dependent) return;
-
- if (added.length)
- {
- new Tapestry.DependentExecutor(added, dependent);
- return;
- }
-
- eval(dependent);
+ new Tapestry.ScriptLoadMonitor(added, callback);
},
addStylesheets : function(stylesheets)
Modified:
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/DocumentLinkerImplTest.java
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/DocumentLinkerImplTest.java?rev=709601&r1=709600&r2=709601&view=diff
==============================================================================
---
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/DocumentLinkerImplTest.java
(original)
+++
tapestry/tapestry5/trunk/tapestry-core/src/test/java/org/apache/tapestry5/internal/services/DocumentLinkerImplTest.java
Fri Oct 31 16:42:34 2008
@@ -33,7 +33,7 @@
document.newRootElement("not-html").text("not an HTML document");
- DocumentLinkerImpl linker = new DocumentLinkerImpl(true);
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(true, false);
linker.addScript("foo.js");
linker.addScript("doSomething();");
@@ -50,10 +50,11 @@
document.newRootElement("html").element("body").element("p").text("Ready to be
updated with scripts.");
- DocumentLinkerImpl linker = new DocumentLinkerImpl(true);
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(true, false);
linker.addScriptLink("foo.js");
linker.addScriptLink("bar/baz.js");
+ linker.addScript("pageInitialization();");
linker.updateDocument(document);
@@ -61,13 +62,31 @@
}
@Test
+ public void add_script_links_at_top() throws Exception
+ {
+ Document document = new Document(new XMLMarkupModel());
+
+
document.newRootElement("html").element("body").element("p").text("Ready to be
updated with scripts at top.");
+
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(true, true);
+
+ linker.addScriptLink("foo.js");
+ linker.addScriptLink("bar/baz.js");
+ linker.addScript("pageInitialization();");
+
+ linker.updateDocument(document);
+
+ check(document, "add_script_links_at_top.txt");
+ }
+
+ @Test
public void add_style_links() throws Exception
{
Document document = new Document(new XMLMarkupModel());
document.newRootElement("html").element("body").element("p").text("Ready to be
updated with styles.");
- DocumentLinkerImpl linker = new DocumentLinkerImpl(true);
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(true, false);
linker.addStylesheetLink("foo.css", null);
linker.addStylesheetLink("bar/baz.css", "print");
@@ -84,7 +103,7 @@
document.newRootElement("html").element("body").element("p").text("Ready to be
updated with styles.");
- DocumentLinkerImpl linker = new DocumentLinkerImpl(true);
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(true, false);
linker.addStylesheetLink("foo.css", null);
linker.addStylesheetLink("bar/baz.css", "print");
@@ -105,7 +124,7 @@
document.newRootElement("html").element("head").comment("existing
head").getParent()
.element("body").text("body content");
- DocumentLinkerImpl linker = new DocumentLinkerImpl(true);
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(true, false);
linker.addStylesheetLink("foo.css", null);
@@ -121,7 +140,7 @@
document.newRootElement("html").element("body").element("p").text("Ready to be
updated with scripts.");
- DocumentLinkerImpl linker = new DocumentLinkerImpl(true);
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(true, false);
for (int i = 0; i < 3; i++)
{
@@ -142,7 +161,7 @@
document.newRootElement("html").element("body").element("p").text("Ready to be
updated with scripts.");
- DocumentLinkerImpl linker = new DocumentLinkerImpl(true);
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(true, false);
linker.addScript("doSomething();");
linker.addScript("doSomethingElse();");
@@ -159,7 +178,7 @@
document.newRootElement("html").element("body").element("p").text("Ready to be
updated with scripts.");
- DocumentLinkerImpl linker = new DocumentLinkerImpl(false);
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(false, false);
linker.addScriptLink("foo.js");
@@ -178,7 +197,7 @@
document.newRootElement("html").element("notbody").element("p").text("Ready to
be updated with scripts.");
- DocumentLinkerImpl linker = new DocumentLinkerImpl(true);
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(true, false);
linker.addScriptLink("foo.js");
@@ -194,13 +213,12 @@
document.newRootElement("html").element("body").element("p").text("Ready to be
updated with scripts.");
- DocumentLinkerImpl linker = new DocumentLinkerImpl(true);
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(true, false);
linker.addScript("for (var i = 0; i < 5; i++) { doIt(i); }");
linker.updateDocument(document);
assertEquals(document.toString(),
readFile("script_written_raw.txt").trim());
-
}
}
Modified:
tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/internal/services/add_script_links.txt
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/internal/services/add_script_links.txt?rev=709601&r1=709600&r2=709601&view=diff
==============================================================================
---
tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/internal/services/add_script_links.txt
(original)
+++
tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/internal/services/add_script_links.txt
Fri Oct 31 16:42:34 2008
@@ -1,2 +1,8 @@
<?xml version="1.0"?>
-<html><body><p>Ready to be updated with scripts.</p><script src="foo.js"
type="text/javascript"/><script src="bar/baz.js"
type="text/javascript"/></body></html>
\ No newline at end of file
+<html><body><p>Ready to be updated with scripts.</p><script src="foo.js"
type="text/javascript"/><script src="bar/baz.js"
type="text/javascript"/><script type="text/javascript">
+<!--
+Tapestry.onDOMLoaded(function() {
+pageInitialization();
+});
+// -->
+</script></body></html>
\ No newline at end of file
Added:
tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/internal/services/add_script_links_at_top.txt
URL:
http://svn.apache.org/viewvc/tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/internal/services/add_script_links_at_top.txt?rev=709601&view=auto
==============================================================================
---
tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/internal/services/add_script_links_at_top.txt
(added)
+++
tapestry/tapestry5/trunk/tapestry-core/src/test/resources/org/apache/tapestry5/internal/services/add_script_links_at_top.txt
Fri Oct 31 16:42:34 2008
@@ -0,0 +1,8 @@
+<?xml version="1.0"?>
+<html><body><script src="foo.js" type="text/javascript"/><script
src="bar/baz.js" type="text/javascript"/><p>Ready to be updated with scripts at
top.</p><script type="text/javascript">
+<!--
+Tapestry.onDOMLoaded(function() {
+pageInitialization();
+});
+// -->
+</script></body></html>
\ No newline at end of file