You are viewing a plain text version of this content. The canonical link for it is here.
Posted to dev@tapestry.apache.org by hl...@apache.org on 2014/08/07 20:55:44 UTC
git commit: TAP5-2371: Prevent interaction with page until fully
loaded
Repository: tapestry-5
Updated Branches:
refs/heads/master 50ebb518e -> 0119f9a89
TAP5-2371: Prevent interaction with page until fully loaded
Project: http://git-wip-us.apache.org/repos/asf/tapestry-5/repo
Commit: http://git-wip-us.apache.org/repos/asf/tapestry-5/commit/0119f9a8
Tree: http://git-wip-us.apache.org/repos/asf/tapestry-5/tree/0119f9a8
Diff: http://git-wip-us.apache.org/repos/asf/tapestry-5/diff/0119f9a8
Branch: refs/heads/master
Commit: 0119f9a890c8becb80fa1d5f36c1ab1f4ec8660c
Parents: 50ebb51
Author: Howard M. Lewis Ship <hl...@apache.org>
Authored: Thu Aug 7 11:55:59 2014 -0700
Committer: Howard M. Lewis Ship <hl...@apache.org>
Committed: Thu Aug 7 11:55:59 2014 -0700
----------------------------------------------------------------------
.../META-INF/modules/t5/core/pageinit.coffee | 5 ++-
.../org/apache/tapestry5/SymbolConstants.java | 15 +++++--
.../internal/services/DocumentLinkerImpl.java | 39 +++++++++++-------
.../tapestry5/modules/TapestryModule.java | 7 +++-
.../assets/tapestry5/pageloader-mask.gif | Bin 0 -> 13270 bytes
.../META-INF/assets/tapestry5/tapestry.css | 41 +++++++++++++++++++
.../services/DocumentLinkerImplTest.groovy | 32 +++++++--------
7 files changed, 103 insertions(+), 36 deletions(-)
----------------------------------------------------------------------
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/0119f9a8/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/pageinit.coffee
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/pageinit.coffee b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/pageinit.coffee
index 1161335..a500ef7 100644
--- a/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/pageinit.coffee
+++ b/tapestry-core/src/main/coffeescript/META-INF/modules/t5/core/pageinit.coffee
@@ -1,5 +1,3 @@
-# Copyright 2012, 2013 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
@@ -129,6 +127,9 @@ define ["underscore", "./console", "./dom", "./events"],
dom.body.attr "data-page-initialized", "true"
+ for mask in dom.body.find ".pageloading-mask"
+ mask.remove()
+
exports = _.extend loadLibrariesAndInitialize,
# Passed a list of initializers, executes each initializer in order. Due to asynchronous loading
# of modules, the exact order in which initializer functions are invoked is not predictable.
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/0119f9a8/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
index bef3df4..0257c1e 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/SymbolConstants.java
@@ -510,13 +510,13 @@ public class SymbolConstants
* @since 5.4
*/
public static final String OMIT_EXPIRATION_CACHE_CONTROL_HEADER = "tapestry.omit-expiration-cache-control-header";
-
+
/**
* Defines whether HTML5 features should be used. Value used in the default implementation of
- * {@link Html5Support#isHtml5SupportEnabled()}. Default value: <code>false</code>.
+ * {@link Html5Support#isHtml5SupportEnabled()}. Default value: <code>false</code>.
*
- * @since 5.4
* @see Html5Support#isHtml5SupportEnabled()
+ * @since 5.4
*/
public static final String ENABLE_HTML5_SUPPORT = "tapestry.enable-html5-support";
@@ -527,4 +527,13 @@ public class SymbolConstants
* @since 5.4
*/
public static final String RESTRICTIVE_ENVIRONMENT = "tapestry.restrictive-environment";
+
+ /**
+ * If true, then when a page includes any JavaScript, a {@code script} block is added to insert
+ * a pageloader mask into the page; the pageloader mask ensure that the user can't interact with the page
+ * until after the page is fully initialized.
+ *
+ * @since 5.4
+ */
+ public static final String ENABLE_PAGELOADING_MASK = "tapestry.enable-pageloading-mask";
}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/0119f9a8/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinkerImpl.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinkerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinkerImpl.java
index 67d4dba..a6f5198 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinkerImpl.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/DocumentLinkerImpl.java
@@ -1,5 +1,3 @@
-// Copyright 2007-2013 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
@@ -23,28 +21,27 @@ import org.apache.tapestry5.services.javascript.ModuleConfigurationCallback;
import org.apache.tapestry5.services.javascript.ModuleManager;
import org.apache.tapestry5.services.javascript.StylesheetLink;
-import java.util.Collections;
import java.util.List;
import java.util.Set;
public class DocumentLinkerImpl implements DocumentLinker
{
-
+
private final static Set<String> HTML_MIME_TYPES = CollectionFactory.newSet("text/html", "application/xml+xhtml");
-
+
private final List<String> coreLibraryURLs = CollectionFactory.newList();
private final List<String> libraryURLs = CollectionFactory.newList();
private final ModuleInitsManager initsManager = new ModuleInitsManager();
-
+
private final List<ModuleConfigurationCallback> moduleConfigurationCallbacks = CollectionFactory.newList();
private final List<StylesheetLink> includedStylesheets = CollectionFactory.newList();
private final ModuleManager moduleManager;
- private final boolean omitGeneratorMetaTag;
+ private final boolean omitGeneratorMetaTag, enablePageloadingMask;
private final String tapestryBanner;
@@ -56,13 +53,14 @@ public class DocumentLinkerImpl implements DocumentLinker
* used to identify the root folder for dynamically loaded modules
* @param omitGeneratorMetaTag
* via symbol configuration
+ * @param enablePageloadingMask
* @param tapestryVersion
- * version of Tapestry framework (for meta tag)
*/
- public DocumentLinkerImpl(ModuleManager moduleManager, boolean omitGeneratorMetaTag, String tapestryVersion)
+ public DocumentLinkerImpl(ModuleManager moduleManager, boolean omitGeneratorMetaTag, boolean enablePageloadingMask, String tapestryVersion)
{
this.moduleManager = moduleManager;
this.omitGeneratorMetaTag = omitGeneratorMetaTag;
+ this.enablePageloadingMask = enablePageloadingMask;
tapestryBanner = String.format("Apache Tapestry Framework (version %s)", tapestryVersion);
}
@@ -115,11 +113,12 @@ public class DocumentLinkerImpl implements DocumentLinker
{
return;
}
-
+
// TAP5-2200: Generating XML from pages and templates is not possible anymore
// only add JavaScript and CSS if we're actually generating
final String mimeType = document.getMimeType();
- if (mimeType != null && !HTML_MIME_TYPES.contains(mimeType)) {
+ if (mimeType != null && !HTML_MIME_TYPES.contains(mimeType))
+ {
return;
}
@@ -182,7 +181,7 @@ public class DocumentLinkerImpl implements DocumentLinker
// TAPESTRY-2364
- addScriptsToEndOfBody(body);
+ addContentToBody(body);
}
/**
@@ -219,8 +218,20 @@ public class DocumentLinkerImpl implements DocumentLinker
* @param body
* element to add the dynamic scripting to
*/
- protected void addScriptsToEndOfBody(Element body)
+ protected void addContentToBody(Element body)
{
+ if (enablePageloadingMask)
+ {
+ // This adds a mask element to the page, based on the Bootstrap modal dialog backdrop. The mark
+ // is present immediately, but fades in visually after a short delay, and is removed
+ // after page initialization is complete. For a client that doesn't have JavaScript enabled,
+ // this will do nothing (though I suspect the page will not behave to expectations!).
+ Element script = body.element("script", "type", "text/javascript");
+ script.raw("document.write(\"<div class=\\\"pageloading-mask\\\"><div></div></div>\");");
+
+ script.moveToTop(body);
+ }
+
moduleManager.writeConfiguration(body, moduleConfigurationCallbacks);
// Write the core libraries, which includes RequireJS:
@@ -294,5 +305,5 @@ public class DocumentLinkerImpl implements DocumentLinker
assert callback != null;
moduleConfigurationCallbacks.add(callback);
}
-
+
}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/0119f9a8/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
index a90003f..195b617 100644
--- a/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
+++ b/tapestry-core/src/main/java/org/apache/tapestry5/modules/TapestryModule.java
@@ -1732,13 +1732,16 @@ public final class TapestryModule
@Symbol(SymbolConstants.INCLUDE_CORE_STACK)
final boolean includeCoreStack,
+ @Symbol(SymbolConstants.ENABLE_PAGELOADING_MASK)
+ final boolean enablePageloadingMask,
+
final ValidationDecoratorFactory validationDecoratorFactory)
{
MarkupRendererFilter documentLinker = new MarkupRendererFilter()
{
public void renderMarkup(MarkupWriter writer, MarkupRenderer renderer)
{
- DocumentLinkerImpl linker = new DocumentLinkerImpl(moduleManager, omitGeneratorMeta, tapestryVersion);
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(moduleManager, omitGeneratorMeta, enablePageloadingMask, tapestryVersion);
environment.push(DocumentLinker.class, linker);
@@ -2137,6 +2140,8 @@ public final class TapestryModule
configuration.add(SymbolConstants.ENABLE_HTML5_SUPPORT, false);
configuration.add(SymbolConstants.RESTRICTIVE_ENVIRONMENT, false);
+
+ configuration.add(SymbolConstants.ENABLE_PAGELOADING_MASK, true);
}
/**
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/0119f9a8/tapestry-core/src/main/resources/META-INF/assets/tapestry5/pageloader-mask.gif
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/pageloader-mask.gif b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/pageloader-mask.gif
new file mode 100644
index 0000000..861468d
Binary files /dev/null and b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/pageloader-mask.gif differ
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/0119f9a8/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tapestry.css
----------------------------------------------------------------------
diff --git a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tapestry.css b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tapestry.css
index c7d47eb..b1d7025 100644
--- a/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tapestry.css
+++ b/tapestry-core/src/main/resources/META-INF/assets/tapestry5/tapestry.css
@@ -31,3 +31,44 @@ div.datefield-popup.well {
text-align: right;
}
+@-webkit-keyframes pageloading-mask-fade-in {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: .50
+ }
+}
+
+@-moz-keyframes pageloading-mask-fade-in {
+ from {
+ opacity: 0;
+ }
+ to {
+ opacity: .50
+ }
+}
+
+/** Added via JavaScript when a page is initially loaded, then removed after all page initializations occur. */
+.pageloading-mask {
+ position: fixed;
+ top: 0;
+ right: 0;
+ bottom: 0;
+ left: 0;
+ z-index: 1040;
+ background-color: #000;
+ -webkit-animation-name: pageloading-mask-fade-in;
+ -webkit-animation-duration: 250ms;
+ -webkit-animation-delay: 250ms;
+ -webkit-animation-fill-mode: forwards;
+ -moz-animation-name: pageloading-mask-fade-in;
+ -moz-animation-duration: 250ms;
+ -moz-animation-delay: 250ms;
+ -moz-animation-fill-mode: forwards;
+}
+
+.pageloading-mask div {
+ height: 100%;
+ background: url(pageloader-mask.gif) no-repeat center center;
+}
http://git-wip-us.apache.org/repos/asf/tapestry-5/blob/0119f9a8/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/DocumentLinkerImplTest.groovy
----------------------------------------------------------------------
diff --git a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/DocumentLinkerImplTest.groovy b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/DocumentLinkerImplTest.groovy
index 4535403..b1a335f 100644
--- a/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/DocumentLinkerImplTest.groovy
+++ b/tapestry-core/src/test/groovy/org/apache/tapestry5/internal/services/DocumentLinkerImplTest.groovy
@@ -32,7 +32,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
document.newRootElement("not-html").text("not an HTML document")
- DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, "1.2.3")
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, false, "1.2.3")
// Only checked if there's something to link.
@@ -55,7 +55,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
document.newRootElement("not-html").text("not an HTML document")
- DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, "1.2.3")
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, false, "1.2.3")
// Only checked if there's something to link.
@@ -76,7 +76,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
void missing_root_element_is_a_noop() {
Document document = new Document()
- DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, "1.2.3")
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, false, "1.2.3")
linker.addLibrary("foo.js")
linker.addScript(InitializationPriority.NORMAL, "doSomething();")
@@ -94,7 +94,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
def manager = mockModuleManager(["core.js", "foo.js", "bar/baz.js"], [new JSONArray("t5/core/pageinit:evalJavaScript", "pageINIT();")])
- DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, true, "1.2.3")
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, true, false, "1.2.3")
replay()
@@ -122,7 +122,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
document.newRootElement("html").element("body").element("p").text("Ready to be marked with generator meta.")
- DocumentLinkerImpl linker = new DocumentLinkerImpl(null, false, "1.2.3")
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(null, false, false, "1.2.3")
linker.updateDocument(document)
@@ -141,7 +141,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
document.newRootElement("no_html").text("Generator meta only added if root is html tag.")
- DocumentLinkerImpl linker = new DocumentLinkerImpl(null, false, "1.2.3")
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(null, false, false, "1.2.3")
linker.updateDocument(document)
@@ -158,7 +158,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
document.newRootElement("html").element("body").element("p").text("Ready to be updated with styles.")
- DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, "1.2.3")
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, false, "1.2.3")
linker.addStylesheetLink(new StylesheetLink("foo.css"))
linker.addStylesheetLink(new StylesheetLink("bar/baz.css", new StylesheetOptions("print")))
@@ -178,7 +178,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
document.newRootElement("html").element("head").comment(" existing head ").container.element("body").text(
"body content")
- DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, "1.2.3")
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, false, "1.2.3")
linker.addStylesheetLink(new StylesheetLink("foo.css"))
@@ -198,7 +198,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
def manager = mockModuleManager([], [new JSONArray("t5/core/pageinit:evalJavaScript", "doSomething();")])
- DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, true, "1.2.3")
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, true, true, "1.2.3")
replay()
@@ -207,7 +207,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
linker.updateDocument(document)
check document, '''
-<html><body data-page-initialized="false"><p>Ready to be updated with scripts.</p><!--MM-CONFIG--><!--MM-INIT--></body></html>
+<html><body data-page-initialized="false"><script type="text/javascript">document.write("<div class=\\"pageloading-mask\\"><div></div></div>");</script><p>Ready to be updated with scripts.</p><!--MM-CONFIG--><!--MM-INIT--></body></html>
'''
verify()
@@ -224,7 +224,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
def manager = mockModuleManager(["foo.js"], [])
- DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, true, "1.2.3")
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, true, false, "1.2.3")
replay()
@@ -251,7 +251,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
def manager = mockModuleManager([], [new JSONArray("['immediate/module:myfunc', {'fred':'barney'}]")])
- DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, true, "1.2.3")
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, true, false, "1.2.3")
replay()
@@ -273,7 +273,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
document.newRootElement("html")
- DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, "1.2.3")
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, false, "1.2.3")
linker.addStylesheetLink(new StylesheetLink("everybody.css"))
linker.addStylesheetLink(new StylesheetLink("just_ie.css", new StylesheetOptions().withCondition("IE")))
@@ -295,7 +295,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
document.newRootElement("html")
- DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, "1.2.3")
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(null, true, false, "1.2.3")
linker.addStylesheetLink(new StylesheetLink("whatever.css"))
linker.addStylesheetLink(new StylesheetLink("insertion-point.css", new StylesheetOptions().asAjaxInsertionPoint()))
@@ -319,7 +319,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
new JSONArray("my/other/module:normal", 111, 222),
new JSONArray("my/other/module:late", 333, 444)])
- DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, true, "1.2.3")
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, true, false, "1.2.3")
replay()
@@ -347,7 +347,7 @@ class DocumentLinkerImplTest extends InternalBaseTestCase {
def manager = mockModuleManager([], ["my/module",
new JSONArray("my/other/module:normal", 111, 222)])
- DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, true, "1.2.3")
+ DocumentLinkerImpl linker = new DocumentLinkerImpl(manager, true, false, "1.2.3")
replay()