This is an automated email from the ASF dual-hosted git repository. thiagohp pushed a commit to branch feature/requirejs-less in repository https://gitbox.apache.org/repos/asf/tapestry-5.git
The following commit(s) were added to refs/heads/feature/requirejs-less by this push: new ead827a04 TAP5-2810: More needed support code for Require.js-less mode ead827a04 is described below commit ead827a0425863649f03697fbfffc6bb84ee9940 Author: Thiago H. de Paula Figueiredo <thi...@arsmachina.com.br> AuthorDate: Sat Jul 26 12:24:23 2025 -0300 TAP5-2810: More needed support code for Require.js-less mode --- 5_10_RELEASE_NOTES.md | 11 +- .../apache/tapestry5/corelib/components/Zone.java | 8 +- .../internal/services/DocumentLinkerImpl.java | 29 +- .../internal/services/ajax/BaseInitialization.java | 10 + .../services/ajax/EsModuleInitializationImpl.java | 12 +- .../internal/services/ajax/EsShimManagerImpl.java | 66 ++ .../services/ajax/JavaScriptSupportImpl.java | 27 +- .../services/javascript/EsModuleManagerImpl.java | 58 +- .../services/javascript/EsShimDispatcher.java | 31 +- .../apache/tapestry5/modules/JavaScriptModule.java | 189 ++-- .../apache/tapestry5/modules/TapestryModule.java | 7 +- .../services/javascript/EsModuleManager.java | 14 +- .../tapestry5/services/javascript/EsShim.java | 91 +- .../services/javascript/EsShimManager.java | 49 + .../javascript/ExtensibleJavaScriptStack.java | 18 +- .../services/javascript/JavaScriptStack.java | 8 + .../services/javascript/StackExtension.java | 10 + .../services/javascript/StackExtensionType.java | 7 + .../META-INF/assets/es-modules/t5/underscore.js | 5 - .../typescript/src/t5/core/t53-compatibility.ts | 56 + .../integration/app1/components/Border.java | 14 +- .../tapestry5/integration/app1/pages/Index.java | 1150 ++++++++++---------- .../META-INF/assets/es-modules/app/test-support.js | 14 + 23 files changed, 1118 insertions(+), 766 deletions(-) diff --git a/5_10_RELEASE_NOTES.md b/5_10_RELEASE_NOTES.md index b893fdec3..4a942b709 100644 --- a/5_10_RELEASE_NOTES.md +++ b/5_10_RELEASE_NOTES.md @@ -31,13 +31,10 @@ Scratch pad for changes destined for the 5.10.0 release notes page. # Notes about Require.js disabled mode -* When using Bootstrap 3, the `t5/bootstrap/*` modules had automatic dependency - management (for example, if you `bootstrap/tooltip`, `bootstrap/transition` - would automatically be included too through Require.js). This doesn't happen - when Require.js is disabled. So, for example, when importing `bootstrap/tooltip`, - you should import `bootstrap/trasition` first. Notice Bootstrap 3 JavaScript - files don't have any module management code on it (Require.js nor ES modules) - +* Underscore.js, jQuery and Require.js are not included in the default stack + (i.e. the set of JavaScript files which are included in pages by default). + If you need to use Underscore.js or jQuery, they're automatically available for + import as `underscore` and `jquery`, respectively. # Overall notes diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Zone.java b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Zone.java index b4e9b0282..7bfd9b720 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Zone.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/corelib/components/Zone.java @@ -20,6 +20,7 @@ import org.apache.tapestry5.corelib.internal.FormSupportAdapter; import org.apache.tapestry5.corelib.internal.HiddenFieldPositioner; import org.apache.tapestry5.dom.Element; import org.apache.tapestry5.internal.services.RequestConstants; +import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper; import org.apache.tapestry5.ioc.annotations.Inject; import org.apache.tapestry5.ioc.annotations.Symbol; import org.apache.tapestry5.json.JSONObject; @@ -68,7 +69,6 @@ import org.slf4j.Logger; * @see FormFragment */ @SupportsInformalParameters -@Import(module = "t5/core/zone") public class Zone implements ClientBodyElement { /** @@ -144,6 +144,9 @@ public class Zone implements ClientBodyElement @Inject private HiddenFieldLocationRules rules; + + @Inject + private RequireJsModeHelper requireJsModeHelper; private String clientId; @@ -175,6 +178,9 @@ public class Zone implements ClientBodyElement void beginRender(MarkupWriter writer) { + + requireJsModeHelper.importModule("t5/core/zone"); + clientId = resources.isBound("id") ? idParameter : javascriptSupport.allocateClientId(resources); Element e = writer.element(elementName, 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 8814e7faf..fd885ce87 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 @@ -53,6 +53,8 @@ public class DocumentLinkerImpl implements DocumentLinker private final boolean omitGeneratorMetaTag, enablePageloadingMask; private final String tapestryBanner; + + private final boolean requireJsEnabled; // Initially false; set to true when a scriptURL or any kind of initialization is added. private boolean hasScriptsOrInitializations; @@ -66,12 +68,14 @@ public class DocumentLinkerImpl implements DocumentLinker * @param tapestryVersion */ public DocumentLinkerImpl(ModuleManager moduleManager, EsModuleManager esModuleManager, - boolean omitGeneratorMetaTag, boolean enablePageloadingMask, String tapestryVersion) + boolean omitGeneratorMetaTag, boolean enablePageloadingMask, String tapestryVersion, + boolean requireJsEnabled) { this.moduleManager = moduleManager; this.esModuleManager = esModuleManager; this.omitGeneratorMetaTag = omitGeneratorMetaTag; this.enablePageloadingMask = enablePageloadingMask; + this.requireJsEnabled = requireJsEnabled; tapestryBanner = "Apache Tapestry Framework (version " + tapestryVersion + ')'; } @@ -149,8 +153,6 @@ public class DocumentLinkerImpl implements DocumentLinker addElementBefore(head, existingMeta, "meta", "name", "generator", "content", tapestryBanner); } - - addScriptElements(root); final List<EsModuleInitialization> esModuleInits = esModulesinitsManager.getInits(); if (isHtmlRoot && !esModuleInits.isEmpty()) @@ -158,6 +160,8 @@ public class DocumentLinkerImpl implements DocumentLinker esModuleManager.writeImportMap(root.find("head"), esModuleConfigurationCallbacks); esModuleManager.writeImports(root, esModuleInits); } + + addScriptElements(root); } @@ -252,8 +256,11 @@ public class DocumentLinkerImpl implements DocumentLinker script.moveToTop(body); } - - moduleManager.writeConfiguration(body, moduleConfigurationCallbacks); + + if (requireJsEnabled) + { + moduleManager.writeConfiguration(body, moduleConfigurationCallbacks); + } // Write the core libraries, which includes RequireJS: @@ -264,9 +271,15 @@ public class DocumentLinkerImpl implements DocumentLinker "src", url); } - // Write the initialization at this point. - - moduleManager.writeInitialization(body, libraryURLs, initsManager.getSortedInits()); + if (requireJsEnabled) + { + // Write the initialization at this point. + moduleManager.writeInitialization(body, libraryURLs, initsManager.getSortedInits()); + } + else + { + esModuleManager.writeInitialization(body, libraryURLs); + } } private static Element createTemporaryContainer(Element headElement, String existingElementName, String newElementName) diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/BaseInitialization.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/BaseInitialization.java index 589a8850a..7714dc461 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/BaseInitialization.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/BaseInitialization.java @@ -1,3 +1,13 @@ +// 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 package org.apache.tapestry5.internal.services.ajax; import org.apache.tapestry5.ioc.internal.util.InternalUtils; diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/EsModuleInitializationImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/EsModuleInitializationImpl.java index 3b3bfeb4a..eb4e32137 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/EsModuleInitializationImpl.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/EsModuleInitializationImpl.java @@ -1,3 +1,13 @@ +// 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 package org.apache.tapestry5.internal.services.ajax; import java.util.Collections; @@ -14,7 +24,7 @@ public class EsModuleInitializationImpl extends BaseInitialization<EsModuleIniti private ImportPlacement placement = ImportPlacement.BODY_BOTTOM; private Object[] arguments; - EsModuleInitializationImpl(String moduleName) + public EsModuleInitializationImpl(String moduleName) { super(moduleName); } diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/EsShimManagerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/EsShimManagerImpl.java new file mode 100644 index 000000000..f8a8c0dd8 --- /dev/null +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/EsShimManagerImpl.java @@ -0,0 +1,66 @@ +// 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. + +package org.apache.tapestry5.internal.services.ajax; + +import java.util.Map; + +import org.apache.tapestry5.SymbolConstants; +import org.apache.tapestry5.commons.Resource; +import org.apache.tapestry5.ioc.annotations.Symbol; +import org.apache.tapestry5.services.PathConstructor; +import org.apache.tapestry5.services.javascript.EsShimManager; + +public class EsShimManagerImpl implements EsShimManager +{ + + private static final String ES_SUBPATH = "es-shims"; + + private final Map<String, Resource> shims; + + private final PathConstructor pathConstructor; + + private final String assetPrefix; + + public EsShimManagerImpl(Map<String, Resource> shims, + PathConstructor pathConstructor, + @Symbol(SymbolConstants.ASSET_PATH_PREFIX) + String assetPrefix) + { + super(); + this.shims = shims; + this.assetPrefix = assetPrefix; + this.pathConstructor = pathConstructor; + } + + /** + * Returns the shims as a (module name, module resource) map. + * @return a {@code Map<String, Resource>} + */ + @Override + public Map<String, Resource> getShims() + { + return shims; + } + + @Override + public String getRequestPrefix(boolean compress) + { + return pathConstructor.constructDispatchPath(assetPrefix + "/" + (compress ? ES_SUBPATH + ".gz" : ES_SUBPATH) + "/"); + } + + @Override + public String getUrl(String moduleName) { + return getRequestPrefix(true) + moduleName + ".js"; + } + +} diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImpl.java index a065b4b6c..db44db661 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImpl.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/ajax/JavaScriptSupportImpl.java @@ -34,6 +34,7 @@ import org.apache.tapestry5.ioc.internal.util.InternalUtils; import org.apache.tapestry5.ioc.util.IdAllocator; import org.apache.tapestry5.json.JSONArray; import org.apache.tapestry5.json.JSONObject; +import org.apache.tapestry5.services.javascript.AbstractInitialization; import org.apache.tapestry5.services.javascript.EsModuleConfigurationCallback; import org.apache.tapestry5.services.javascript.EsModuleInitialization; import org.apache.tapestry5.services.javascript.Initialization; @@ -46,6 +47,8 @@ import org.apache.tapestry5.services.javascript.StylesheetLink; public class JavaScriptSupportImpl implements JavaScriptSupport { + private static final String PAGEINIT_MODULE_NAME = "t5/core/pageinit"; + private final IdAllocator idAllocator; private final DocumentLinker linker; @@ -133,10 +136,21 @@ public class JavaScriptSupportImpl implements JavaScriptSupport public void commit() { + final String pageInitModuleName = PAGEINIT_MODULE_NAME; // TODO make no Require.js version of this if (focusFieldId != null) { - require("t5/core/pageinit").invoke("focus").with(focusFieldId); + final AbstractInitialization<?> initialization; + + if (requireJsEnabled) + { + initialization = require(pageInitModuleName); + } + else + { + initialization = importEsModule(pageInitModuleName); + } + initialization.invoke("focus").with(focusFieldId); } F.flow(stylesheetLinks).each(new Worker<StylesheetLink>() @@ -224,7 +238,16 @@ public class JavaScriptSupportImpl implements JavaScriptSupport if (partialMode) { - require("t5/core/pageinit").invoke("evalJavaScript").with(newScript); + final AbstractInitialization<?> initialization; + if (requireJsEnabled) + { + initialization = require(PAGEINIT_MODULE_NAME); + } + else + { + initialization = importEsModule(PAGEINIT_MODULE_NAME); + } + initialization.invoke("evalJavaScript").with(newScript); } else { linker.addScript(priority, newScript); diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/EsModuleManagerImpl.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/EsModuleManagerImpl.java index a73a2fe64..5cfebf058 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/EsModuleManagerImpl.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/EsModuleManagerImpl.java @@ -17,6 +17,7 @@ import java.util.ArrayList; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; import org.apache.tapestry5.Asset; import org.apache.tapestry5.SymbolConstants; @@ -31,15 +32,18 @@ import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker; import org.apache.tapestry5.ioc.annotations.Symbol; import org.apache.tapestry5.ioc.services.ClasspathMatcher; import org.apache.tapestry5.ioc.services.ClasspathScanner; +import org.apache.tapestry5.json.JSONArray; import org.apache.tapestry5.json.JSONCollection; import org.apache.tapestry5.json.JSONLiteral; import org.apache.tapestry5.json.JSONObject; import org.apache.tapestry5.services.AssetSource; +import org.apache.tapestry5.services.Core; import org.apache.tapestry5.services.assets.StreamableResourceSource; import org.apache.tapestry5.services.javascript.EsModuleConfigurationCallback; import org.apache.tapestry5.services.javascript.EsModuleInitialization; import org.apache.tapestry5.services.javascript.EsModuleManager; import org.apache.tapestry5.services.javascript.ImportPlacement; +import org.apache.tapestry5.services.javascript.JavaScriptStack; public class EsModuleManagerImpl implements EsModuleManager { @@ -60,6 +64,8 @@ public class EsModuleManagerImpl implements EsModuleManager private final Set<String> extensions; private final AssetSource assetSource; + + private final List<EsModuleInitialization> coreStackInits; // Note: ConcurrentHashMap does not support null as a value, alas. We use classpathRoot as a null. private final Map<String, String> cache = CollectionFactory.newConcurrentMap(); @@ -74,6 +80,8 @@ public class EsModuleManagerImpl implements EsModuleManager private final List<EsModuleConfigurationCallback> globalPerRequestCallbacks; + private final String infraProvider; + public EsModuleManagerImpl( List<EsModuleManagerContribution> contributions, AssetSource assetSource, @@ -82,14 +90,24 @@ public class EsModuleManagerImpl implements EsModuleManager boolean compactJSON, @Symbol(TapestryHttpSymbolConstants.PRODUCTION_MODE) boolean productionMode, + @Symbol(SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER) + String infraProvider, ClasspathScanner classpathScanner, - ResourceChangeTracker resourceChangeTracker) + ResourceChangeTracker resourceChangeTracker, + @Core JavaScriptStack javaScriptStack) { this.compactJSON = compactJSON; this.assetSource = assetSource; this.classpathScanner = classpathScanner; this.productionMode = productionMode; this.resourceChangeTracker = resourceChangeTracker; + this.infraProvider = infraProvider; + + final List<String> coreStackEsModules = javaScriptStack.getEsModules(); + + coreStackInits = coreStackEsModules.stream() + .map(i -> new EsModuleInitializationImpl(i)) + .collect(Collectors.toList()); baseCallbacks = new ArrayList<>(); globalPerRequestCallbacks = new ArrayList<>(); @@ -151,12 +169,22 @@ public class EsModuleManagerImpl implements EsModuleManager final Set<String> scan = classpathScanner.scan(CLASSPATH_ROOT, matcher); for (String file : scan) { - String id = file.replace(CLASSPATH_ROOT, ""); - id = id.substring(0, id.lastIndexOf('.')); + String moduleName = file.replace(CLASSPATH_ROOT, ""); + moduleName = moduleName.substring(0, moduleName.lastIndexOf('.')); + + if (moduleName.startsWith("t5/core/t5-core-dom-")) + { + // They're treated specially. + continue; + } + else if (moduleName.equals("t5/core/dom")) + { + file = file.replace("t5/core/dom", "t5/core/t5-core-dom-" + infraProvider); + } final Asset asset = assetSource.getClasspathAsset(file); resourceChangeTracker.trackResource(asset.getResource()); - imports.put(id, asset.toClientURL()); + imports.put(moduleName, asset.toClientURL()); } } catch (IOException e) { @@ -192,7 +220,11 @@ public class EsModuleManagerImpl implements EsModuleManager String functionName; Object[] arguments; - for (EsModuleInitialization i : inits) + List<EsModuleInitialization> allInits = new ArrayList<>(coreStackInits.size() + inits.size()); + allInits.addAll(coreStackInits); + allInits.addAll(inits); + + for (EsModuleInitialization i : allInits) { init = (EsModuleInitializationImpl) i; @@ -281,6 +313,22 @@ public class EsModuleManagerImpl implements EsModuleManager } + @Override + public void writeInitialization(Element body, List<String> libraryURLs) + { + + Element element = body.element("script", "type", "module"); + + element.raw(String.format("import pageinit from \"t5/core/pageinit\";\npageinit(%s, []);", + convert(libraryURLs))); + } + + private String convert(List<?> input) + { + return new JSONArray().putAll(input).toString(compactJSON); + } + + static String convertToJsFunctionParameters(Object[] arguments, boolean compactJSON) { String result; diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/EsShimDispatcher.java b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/EsShimDispatcher.java index fb1a24da9..03ec9874a 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/EsShimDispatcher.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/internal/services/javascript/EsShimDispatcher.java @@ -16,7 +16,6 @@ import java.io.IOException; import java.util.HashMap; import java.util.Map; -import org.apache.tapestry5.SymbolConstants; import org.apache.tapestry5.commons.Resource; import org.apache.tapestry5.http.services.Dispatcher; import org.apache.tapestry5.http.services.Request; @@ -26,13 +25,11 @@ import org.apache.tapestry5.internal.services.ResourceStreamer; import org.apache.tapestry5.internal.services.assets.ResourceChangeTracker; import org.apache.tapestry5.ioc.IOOperation; import org.apache.tapestry5.ioc.OperationTracker; -import org.apache.tapestry5.ioc.annotations.Symbol; -import org.apache.tapestry5.ioc.annotations.UsesMappedConfiguration; -import org.apache.tapestry5.services.PathConstructor; import org.apache.tapestry5.services.assets.StreamableResource; import org.apache.tapestry5.services.assets.StreamableResourceProcessing; import org.apache.tapestry5.services.assets.StreamableResourceSource; import org.apache.tapestry5.services.javascript.EsShim; +import org.apache.tapestry5.services.javascript.EsShimManager; import jakarta.servlet.http.HttpServletResponse; @@ -43,12 +40,9 @@ import jakarta.servlet.http.HttpServletResponse; * * @see EsShim */ -@UsesMappedConfiguration(Resource.class) public class EsShimDispatcher implements Dispatcher { - private static final String ES_SUBPATH = "es-shims"; - private final ResourceStreamer streamer; private final OperationTracker tracker; @@ -59,27 +53,26 @@ public class EsShimDispatcher implements Dispatcher private Map<String, StreamableResource> shimMap; - public EsShimDispatcher(Map<String, Resource> configuration, + public EsShimDispatcher(EsShimManager esShimManager, StreamableResourceSource streamableResourceSource, ResourceChangeTracker resourceChangeTracker, ResourceStreamer streamer, OperationTracker tracker, - PathConstructor pathConstructor, - @Symbol(SymbolConstants.ASSET_PATH_PREFIX) - String assetPrefix, boolean compress) { this.streamer = streamer; this.tracker = tracker; this.compress = compress; - this.shimMap = new HashMap<>(configuration.size()); + + final Map<String, Resource> shims = esShimManager.getShims(); + this.shimMap = new HashMap<>(shims.size()); try { - for (String moduleName : configuration.keySet()) + for (String moduleName : shims.keySet()) { shimMap.put(moduleName, streamableResourceSource.getStreamableResource( - configuration.get(moduleName), StreamableResourceProcessing.COMPRESSION_ENABLED, resourceChangeTracker)); + shims.get(moduleName), StreamableResourceProcessing.COMPRESSION_ENABLED, resourceChangeTracker)); } } catch (IOException e) @@ -87,7 +80,7 @@ public class EsShimDispatcher implements Dispatcher throw new RuntimeException(e); } - requestPrefix = pathConstructor.constructDispatchPath(assetPrefix + "/" + (compress ? ES_SUBPATH + ".gz" : ES_SUBPATH) + "/"); + requestPrefix = esShimManager.getRequestPrefix(compress); } public boolean dispatch(Request request, Response response) throws IOException @@ -110,11 +103,6 @@ public class EsShimDispatcher implements Dispatcher } - public String getUrl(String moduleName) - { - return requestPrefix + moduleName; - } - private boolean handleModuleRequest(String extraPath, Response response) throws IOException { int dotx = extraPath.lastIndexOf('.'); @@ -141,7 +129,8 @@ public class EsShimDispatcher implements Dispatcher if (resource != null) { - return streamer.streamResource(resource, resource.getChecksum(), ResourceStreamer.DEFAULT_OPTIONS); + final String checksum = resource.getChecksum(); + return streamer.streamResource(resource, checksum, ResourceStreamer.DEFAULT_OPTIONS); } return false; diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java b/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java index 8b6bf81a0..3ea83348c 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/modules/JavaScriptModule.java @@ -14,7 +14,6 @@ package org.apache.tapestry5.modules; import java.util.Locale; -import org.apache.tapestry5.Asset; import org.apache.tapestry5.BooleanHook; import org.apache.tapestry5.MarkupWriter; import org.apache.tapestry5.SymbolConstants; @@ -29,6 +28,7 @@ import org.apache.tapestry5.http.services.Request; import org.apache.tapestry5.internal.InternalConstants; import org.apache.tapestry5.internal.services.DocumentLinker; import org.apache.tapestry5.internal.services.ResourceStreamer; +import org.apache.tapestry5.internal.services.ajax.EsShimManagerImpl; import org.apache.tapestry5.internal.services.ajax.JavaScriptSupportImpl; import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper; import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelperImpl; @@ -50,7 +50,6 @@ import org.apache.tapestry5.ioc.annotations.Primary; import org.apache.tapestry5.ioc.annotations.Symbol; import org.apache.tapestry5.ioc.services.FactoryDefaults; import org.apache.tapestry5.ioc.services.SymbolProvider; -import org.apache.tapestry5.ioc.services.SymbolSource; import org.apache.tapestry5.ioc.util.IdAllocator; import org.apache.tapestry5.json.JSONObject; import org.apache.tapestry5.services.AssetSource; @@ -64,13 +63,15 @@ import org.apache.tapestry5.services.MarkupRendererFilter; import org.apache.tapestry5.services.PartialMarkupRenderer; import org.apache.tapestry5.services.PartialMarkupRendererFilter; import org.apache.tapestry5.services.PathConstructor; +import org.apache.tapestry5.services.assets.StreamableResourceSource; import org.apache.tapestry5.services.compatibility.Compatibility; import org.apache.tapestry5.services.compatibility.Trait; import org.apache.tapestry5.services.javascript.AMDWrapper; -import org.apache.tapestry5.services.javascript.EsShim; import org.apache.tapestry5.services.javascript.EsModuleConfigurationCallback; import org.apache.tapestry5.services.javascript.EsModuleManager; import org.apache.tapestry5.services.javascript.EsModuleManager.EsModuleManagerContribution; +import org.apache.tapestry5.services.javascript.EsShim; +import org.apache.tapestry5.services.javascript.EsShimManager; import org.apache.tapestry5.services.javascript.ExtensibleJavaScriptStack; import org.apache.tapestry5.services.javascript.JavaScriptModuleConfiguration; import org.apache.tapestry5.services.javascript.JavaScriptStack; @@ -108,7 +109,7 @@ public class JavaScriptModule binder.bind(JavaScriptStack.class, ExtensibleJavaScriptStack.class).withMarker(Core.class).withId("CoreJavaScriptStack"); binder.bind(JavaScriptStack.class, ExtensibleJavaScriptStack.class).withMarker(Internal.class).withId("InternalJavaScriptStack"); binder.bind(RequireJsModeHelper.class, RequireJsModeHelperImpl.class); - binder.bind(EsShimDispatcher.class); + binder.bind(EsShimManager.class, EsShimManagerImpl.class); } /** @@ -135,15 +136,15 @@ public class JavaScriptModule /** * The core JavaScriptStack has a number of entries: * <dl> - * <dt>requirejs</dt> <dd>The RequireJS AMD JavaScript library</dd> + * <dt>requirejs</dt> <dd>The RequireJS AMD JavaScript library (if Require.js is enabled)</dd> * <dt>scriptaculous.js, effects.js</dt> <dd>Optional JavaScript libraries in compatibility mode (see {@link Trait#SCRIPTACULOUS})</dd> * <dt>t53-compatibility.js</dt> <dd>Optional JavaScript library (see {@link Trait#INITIALIZERS})</dd> - * <dt>underscore-library, underscore-module</dt> + * <dt>underscore-library, underscore-module (if Require.js is enabled. An ES module version of it is provided by Tapestry)</dt> * <dt>The Underscore JavaScript library, and the shim that allows underscore to be injected</dt> * <dt>t5/core/init</dt> <dd>Optional module related to t53-compatibility.js</dd> - * <dt>jquery-library</dt> <dd>The jQuery library</dd> - * <dt>jquery-noconflict</dt> <dd>Switches jQuery to no-conflict mode (only present when the infrastructure is "prototype").</dd> - * <dt>jquery</dt> <dd>A module shim that allows jQuery to be injected (and also switches jQuery to no-conflict mode)</dd> + * <dt>jquery-library</dt> <dd>The jQuery library (if Require.js is enabled. An ES module version of it is provided by Tapestry)</dd> + * <dt>jquery-noconflict</dt> <dd>Switches jQuery to no-conflict mode (only present when the infrastructure is "prototype" and Require.js is enabled).</dd> + * <dt>jquery</dt> <dd>A module shim that allows jQuery to be injected (and also switches jQuery to no-conflict mode) (if Require.js is enabled. An ES module version of it is provided by Tapestry)</dd> * <dt>bootstrap.css, tapestry.css, exception-frame.css, tapestry-console.css, tree.css</dt> * <dd>CSS files</dd> * <dt>t5/core/[...]</dt> @@ -159,10 +160,14 @@ public class JavaScriptModule public static void setupCoreJavaScriptStack(OrderedConfiguration<StackExtension> configuration, Compatibility compatibility, @Symbol(SymbolConstants.JAVASCRIPT_INFRASTRUCTURE_PROVIDER) - String provider) + String provider, + @Symbol(SymbolConstants.REQUIRE_JS_ENABLED) boolean requireJsEnabled) { - configuration.add("requirejs", StackExtension.library(ROOT + "/require.js")); - configuration.add("underscore-library", StackExtension.library(ROOT + "/underscore-1.13.7.js")); + if (requireJsEnabled) + { + configuration.add("requirejs", StackExtension.library(ROOT + "/require.js")); + configuration.add("underscore-library", StackExtension.library(ROOT + "/underscore-1.13.7.js")); + } if (provider.equals("prototype")) { @@ -180,18 +185,32 @@ public class JavaScriptModule if (compatibility.enabled(Trait.INITIALIZERS)) { - add(configuration, StackExtensionType.LIBRARY, ROOT + "/t53-compatibility.js"); - configuration.add("t5/core/init", new StackExtension(StackExtensionType.MODULE, "t5/core/init")); + if (requireJsEnabled) + { + add(configuration, StackExtensionType.LIBRARY, ROOT + "/t53-compatibility.js"); + configuration.add("t5/core/init", new StackExtension(StackExtensionType.MODULE, "t5/core/init")); + } + else + { + add(configuration, StackExtensionType.ES_MODULE, "t5/core/t53-compatibility"); + add(configuration, StackExtensionType.ES_MODULE, "t5/core/init"); + } } - configuration.add("jquery-library", StackExtension.library(ROOT + "/jquery.js")); + if (requireJsEnabled) + { + configuration.add("jquery-library", StackExtension.library(ROOT + "/jquery.js")); + } - if (provider.equals("prototype")) + if (provider.equals("prototype") && requireJsEnabled) { configuration.add("jquery-noconflict", StackExtension.library(ROOT + "/jquery-noconflict.js")); } - add(configuration, StackExtensionType.MODULE, "jquery"); + if (requireJsEnabled) + { + add(configuration, StackExtensionType.MODULE, "jquery"); + } add(configuration, StackExtensionType.STYLESHEET, "${" + SymbolConstants.FONT_AWESOME_ROOT + "}/css/font-awesome.css"); @@ -289,11 +308,13 @@ public class JavaScriptModule JavaScriptStackSource javaScriptStackSource, JavaScriptStackPathConstructor javaScriptStackPathConstructor, LocalizationSetter localizationSetter, + EsShimManager esShimManager, + StreamableResourceSource streamableResourceSource, + ResourceChangeTracker resourceChangeTracker, @Symbol(SymbolConstants.MODULE_PATH_PREFIX) String modulePathPrefix, @Symbol(SymbolConstants.ASSET_PATH_PREFIX) - String assetPathPrefix, - EsShimDispatcher esShimDispatcher) + String assetPathPrefix) { configuration.add("Modules", new ModuleDispatcher(moduleManager, resourceStreamer, tracker, pathConstructor, @@ -307,7 +328,13 @@ public class JavaScriptModule assetPathPrefix, true), "after:Modules", "before:ComponentEvent"); - configuration.add("EsShims", esShimDispatcher, "after:Asset", "before:ComponentEvent"); + configuration.add("EsShims", new EsShimDispatcher(esShimManager, streamableResourceSource, resourceChangeTracker, + resourceStreamer, tracker, false), + "before:Asset", "before:ComponentEvent"); + + configuration.add("CompressedEsShims", new EsShimDispatcher(esShimManager, streamableResourceSource, resourceChangeTracker, + resourceStreamer, tracker, true), + "after:EsShims", "before:Asset", "before:ComponentEvent"); } @@ -552,7 +579,7 @@ public class JavaScriptModule } - @Contribute(EsShimDispatcher.class) + @Contribute(EsShimManager.class) public static void setupBaseEsShims( MappedConfiguration<String, Resource> configuration, @Path("${tapestry.asset.root}/bootstrap/js/transition.js") @@ -560,11 +587,14 @@ public class JavaScriptModule @Path("${tapestry.asset.root}/bootstrap4/js/bootstrap-util.js") Resource bootstrapUtil, Compatibility compatibility, - AssetSource assetSource, - EsShimDispatcher esShimDispatcher) + AssetSource assetSource) { -// configuration.add("jquery", new JavaScriptModuleConfiguration(jqueryShim)); + final Resource jQuery = assetSource.getClasspathAsset("/META-INF/assets/tapestry5/jquery.js") + .getResource(); + configuration.add("jquery", new EsShim(jQuery) + .defaultExport("jQuery.noConflict()") + .getResource()); if (compatibility.enabled(Trait.BOOTSTRAP_3)) { @@ -576,25 +606,23 @@ public class JavaScriptModule final String popoverModuleName = "bootstrap/popover"; configuration.add(popoverModuleName, - EsModuleManagerContribution.base( - new EsShim(popover).importModule("bootstrap/tooltip"), - createCallback(popoverModuleName, esShimDispatcher))); + new EsShim(popover) + .importModule("jquery") + .importModule("bootstrap/tooltip") + .getResource()); } if (compatibility.enabled(Trait.BOOTSTRAP_4)) { final String bootstrapUtilModuleName = "bootstrap/bootstrap-util"; configuration.add(bootstrapUtilModuleName, - EsModuleManagerContribution.base( - new EsShim(bootstrapUtil), - createCallback(bootstrapUtilModuleName, esShimDispatcher))); + new EsShim(bootstrapUtil) + .getResource()); final String popperModuleName = "bootstrap/popper"; final Resource popper = bootstrapUtil.forFile("popper.js"); configuration.add(popperModuleName, - EsModuleManagerContribution.base( - new EsShim(popper), - createCallback(popperModuleName, esShimDispatcher))); + new EsShim(popper).getResource()); for (String name : new String[]{"alert", "button", "carousel", "collapse", "dropdown", "modal", "scrollspy", "tab", "tooltip"}) @@ -604,11 +632,10 @@ public class JavaScriptModule { final String moduleName = "bootstrap/" + name; configuration.add(moduleName, - EsModuleManagerContribution.base( - new EsShim(lib) - .importModule(bootstrapUtilModuleName) - .importModule(popperModuleName), - createCallback(popperModuleName, esShimDispatcher))); + new EsShim(lib) + .importModule(bootstrapUtilModuleName) + .importModule(popperModuleName) + .getResource()); } } } @@ -622,78 +649,16 @@ public class JavaScriptModule } } -// @Contribute(EsModuleManager.class) -// public static void setupBaseEsModules( -// OrderedConfiguration<EsModuleManagerContribution> configuration, -// @Path("${tapestry.asset.root}/bootstrap/js/transition.js") -// Resource transition, -// @Path("${tapestry.asset.root}/bootstrap4/js/bootstrap-util.js") -// Resource bootstrapUtil, -// Compatibility compatibility, -// AssetSource assetSource, -// EsShimDispatcher esShimDispatcher) -// { -// -//// configuration.add("jquery", new JavaScriptModuleConfiguration(jqueryShim)); -// -// if (compatibility.enabled(Trait.BOOTSTRAP_3)) -// { -// final String[] modules = new String[]{"affix", "alert", "button", "carousel", "collapse", "dropdown", "modal", -// "scrollspy", "tab", "tooltip"}; -// addBootstrap3EsShims(configuration, modules, transition); -// -// Resource popover = transition.forFile("popover.js"); -// -// final String popoverModuleName = "bootstrap/popover"; -// configuration.add(popoverModuleName, -// EsModuleManagerContribution.base( -// new EsShim(popover).importModule("bootstrap/tooltip"), -// createCallback(popoverModuleName, esShimDispatcher))); -// } -// -// if (compatibility.enabled(Trait.BOOTSTRAP_4)) -// { -// final String bootstrapUtilModuleName = "bootstrap/bootstrap-util"; -// configuration.add(bootstrapUtilModuleName, -// EsModuleManagerContribution.base( -// new EsShim(bootstrapUtil), -// createCallback(bootstrapUtilModuleName, esShimDispatcher))); -// -// final String popperModuleName = "bootstrap/popper"; -// final Resource popper = bootstrapUtil.forFile("popper.js"); -// configuration.add(popperModuleName, -// EsModuleManagerContribution.base( -// new EsShim(popper), -// createCallback(popperModuleName, esShimDispatcher))); -// -// for (String name : new String[]{"alert", "button", "carousel", "collapse", "dropdown", "modal", -// "scrollspy", "tab", "tooltip"}) -// { -// Resource lib = bootstrapUtil.forFile(name + ".js"); -// if (lib.exists()) -// { -// final String moduleName = "bootstrap/" + name; -// configuration.add(moduleName, -// EsModuleManagerContribution.base( -// new EsShim(lib) -// .importModule(bootstrapUtilModuleName) -// .importModule(popperModuleName), -// createCallback(popperModuleName, esShimDispatcher))); -// } -// } -// } -// -// // Just the minimum to have alerts and AJAX validation working when Bootstrap -// // is completely disabled -// if (!compatibility.enabled(Trait.BOOTSTRAP_3) && !compatibility.enabled(Trait.BOOTSTRAP_4)) -// { -// final String[] modules = new String[]{"alert", "dropdown", "collapse"}; -// addBootstrap3EsShims(configuration, modules, transition); -// } -// } - - private static EsModuleConfigurationCallback createCallback(final String moduleName, EsShimDispatcher esShimDispatcher) { - return c -> EsModuleConfigurationCallback.setImport(c, moduleName, esShimDispatcher.getUrl(moduleName)); + @Contribute(EsModuleManager.class) + public static void setupBaseEsModules( + OrderedConfiguration<EsModuleManagerContribution> configuration, + EsShimManager esShimManager) + { + for (String moduleName : esShimManager.getShims().keySet()) + { + configuration.add(moduleName, EsModuleManagerContribution.base( + c -> EsModuleConfigurationCallback.setImport(c, moduleName, esShimManager.getUrl(moduleName)))); + } } private static void addBootstrap3EsShims( @@ -707,7 +672,9 @@ public class JavaScriptModule final Resource resource = reference.forFile(module + ".js"); if (resource.exists()) { - configuration.add(moduleId, resource); + configuration.add(moduleId, new EsShim(resource) + .importModule("jquery") + .getResource()); } } } 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 73a69419e..0709fd572 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 @@ -1810,6 +1810,9 @@ public final class TapestryModule @Symbol(SymbolConstants.ENABLE_PAGELOADING_MASK) final boolean enablePageloadingMask, + + @Symbol(SymbolConstants.REQUIRE_JS_ENABLED) + final boolean requireJsEnabled, final ValidationDecoratorFactory validationDecoratorFactory) { @@ -1817,7 +1820,9 @@ public final class TapestryModule { public void renderMarkup(MarkupWriter writer, MarkupRenderer renderer) { - DocumentLinkerImpl linker = new DocumentLinkerImpl(moduleManager, esModuleManager, omitGeneratorMeta, enablePageloadingMask, tapestryVersion); + DocumentLinkerImpl linker = new DocumentLinkerImpl(moduleManager, + esModuleManager, omitGeneratorMeta, enablePageloadingMask, + tapestryVersion, requireJsEnabled); environment.push(DocumentLinker.class, linker); diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/EsModuleManager.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/EsModuleManager.java index edd3dfbb0..277d02d99 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/EsModuleManager.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/EsModuleManager.java @@ -45,7 +45,7 @@ public interface EsModuleManager /** * Invoked by the internal {@link org.apache.tapestry5.internal.services.DocumentLinker} service to write the * ES module imports (as per {@link JavaScriptSupport#importEsModule(String)} into the page. - * this occurs after the ES module infrastructure + * This occurs after the ES module infrastructure * has been written into the page, along with the core libraries. * * @param root @@ -55,6 +55,18 @@ public interface EsModuleManager */ void writeImports(Element root, List<EsModuleInitialization> inits); + + /** + * Invoked by the internal {@link org.apache.tapestry5.internal.services.DocumentLinker} service to write the + * calls to {@code t5/core/pageinit} module. + * this occurs after the ES module infrastructure + * has been written into the page, along with the core libraries. + * + * @param root + * {@code <root>} element of the page. + */ + void writeInitialization(Element body, List<String> libraryURLs); + /** * Encapsulates a contribution to {@linkplain EsModuleManager}. * diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/EsShim.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/EsShim.java index ca20f71b4..89a2cde2d 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/EsShim.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/EsShim.java @@ -17,8 +17,9 @@ import java.io.IOException; import java.io.InputStream; import java.io.SequenceInputStream; import java.net.URL; -import java.util.LinkedHashMap; +import java.util.HashMap; import java.util.Map; +import java.util.Vector; import org.apache.tapestry5.commons.Resource; import org.apache.tapestry5.internal.util.VirtualResource; @@ -29,7 +30,8 @@ import org.apache.tapestry5.internal.util.VirtualResource; * * @since 5.10.0 */ -public class EsShim { +public class EsShim +{ /** * The underlying resource, usually a JavaScript library @@ -41,9 +43,15 @@ public class EsShim { * the values being the respective parameter names for the module's factory * function. */ - private final Map<String, String> importConfig = new LinkedHashMap<String, String>(); + private final Map<String, String> importConfig = new HashMap<String, String>(); + + /** + * The default export. + */ + private String defaultExport; - public EsShim(final Resource resource) { + public EsShim(final Resource resource) + { this.resource = resource; } @@ -54,10 +62,11 @@ public class EsShim { * the name of the required module, e.g. <code>jQuery</code> * @param variableName * the module's corresponding variable name - * @return this ESWrapper for further configuration + * @return this EsShim for further configuration */ public EsShim importModule(final String moduleName, - final String variableName) { + final String variableName) + { importConfig.put(moduleName, variableName); return this; } @@ -70,72 +79,104 @@ public class EsShim { * @param moduleName * the name of the required module, e.g. * <code>bootstrap/transition</code> - * @return this ESWrapper for further configuration + * @return this EsShim for further configuration */ - public EsShim importModule(final String moduleName) { + public EsShim importModule(final String moduleName) + { importConfig.put(moduleName, null); return this; } + + /** + * Defines the default export of this module. + * @param defaultExport the variable or expression to be the + * default export of the module + * @return this EsShim for further configuration + */ + public EsShim defaultExport(final String defaultExport) + { + this.defaultExport = defaultExport; + return this; + } /** - * Returns a virtual resource representing this wrapper. + * Returns a virtual resource representing this shim. * @return a {@linkplain Resource}. */ - public Resource getResource() { - return new ESModuleWrapperResource(resource, importConfig); + public Resource getResource() + { + return new ESModuleWrapperResource(resource, importConfig, defaultExport); } /** - * A virtual resource that wraps a plain JavaScript library as an AMD + * A virtual resource that wraps a plain JavaScript library as an ES * module. - * */ private final static class ESModuleWrapperResource extends VirtualResource { private final Resource resource; private final Map<String, String> importConfig; + private final String defaultExport; public ESModuleWrapperResource(final Resource resource, - final Map<String, String> importConfig) + final Map<String, String> importConfig, + final String defaultExport) { this.resource = resource; this.importConfig = importConfig; + this.defaultExport = defaultExport; } @Override public InputStream openStream() throws IOException { - StringBuilder sb = new StringBuilder(); + StringBuilder imports = new StringBuilder(); for (String module : importConfig.keySet()) { final String variableName = importConfig.get(module); - sb.append("import "); + imports.append("import "); if (variableName != null) { - sb.append(variableName); - sb.append(" from "); + imports.append(variableName); + imports.append(" from "); } - sb.append("\""); - sb.append(module); - sb.append("\";\n"); + imports.append("\""); + imports.append(module); + imports.append("\";\n"); } - return new SequenceInputStream(toInputStream(sb), resource.openStream()); + + // Vector since SequenceInputStream doesn't have a varargs constructor + // nor something better than an Enumeration when you have more than + // 2 InputStreams to join. + Vector<InputStream> v = new Vector<>(); + v.add(toInputStream(imports)); + v.add(resource.openStream()); + if (defaultExport != null) + { + v.add(toInputStream(String.format("\n export default %s;\n", + defaultExport))); + } + + return new SequenceInputStream(v.elements()); } @Override - public String getFile() { + public String getFile() + { return "generated-module-for-" + resource.getFile(); } @Override - public URL toURL() { + public URL toURL() + { return null; } @Override - public String toString() { + public String toString() + { return "ES module wrapper for " + resource.toString(); } diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/EsShimManager.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/EsShimManager.java new file mode 100644 index 000000000..f9d5c5f13 --- /dev/null +++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/EsShimManager.java @@ -0,0 +1,49 @@ +// 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. + +package org.apache.tapestry5.services.javascript; + +import java.util.Map; + +import org.apache.tapestry5.commons.Resource; +import org.apache.tapestry5.ioc.annotations.UsesMappedConfiguration; + +/** + * Service managing the ES shims. + * + * @since 5.10.0 + */ +@UsesMappedConfiguration(Resource.class) +public interface EsShimManager +{ + + /** + * Returns the shims as a (module name, module resource) map. + * @return a {@code Map<String, Resource>} + */ + Map<String, Resource> getShims(); + + /** + * Returns the request prefix to be used for ES shim URLs. + * @param compress a {@code boolean} to inform whether it's the compressed asset URL or not. + * @return the request prefix. + */ + String getRequestPrefix(boolean compress); + + /** + * Returns the full URL of a module. + * @param moduleName a module name. + * @return its URL. + */ + String getUrl(String moduleName); + +} diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/ExtensibleJavaScriptStack.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/ExtensibleJavaScriptStack.java index 8fa030fe5..0845431dd 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/ExtensibleJavaScriptStack.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/ExtensibleJavaScriptStack.java @@ -12,6 +12,8 @@ package org.apache.tapestry5.services.javascript; +import java.util.List; + import org.apache.tapestry5.Asset; import org.apache.tapestry5.func.F; import org.apache.tapestry5.func.Flow; @@ -23,8 +25,6 @@ import org.apache.tapestry5.ioc.annotations.UsesOrderedConfiguration; import org.apache.tapestry5.ioc.internal.util.InternalUtils; import org.apache.tapestry5.services.AssetSource; -import java.util.List; - /** * An extensible implementation of {@link JavaScriptStack} that can be used as the implementation of a service. * The contributions to the service are used to supply the libraries, stylesheets, and initialization for a @@ -50,6 +50,8 @@ public class ExtensibleJavaScriptStack implements JavaScriptStack private final List<String> stacks; private final List<String> modules; + + private final List<String> esModules; private final String initialization; @@ -116,6 +118,8 @@ public class ExtensibleJavaScriptStack implements JavaScriptStack stacks = extensions.filter(by(StackExtensionType.STACK)).map(extractValue).toList(); modules = extensions.filter(by(StackExtensionType.MODULE)).map(extractValue).toList(); + + esModules = extensions.filter(by(StackExtensionType.ES_MODULE)).map(extractValue).toList(); stylesheets = extensions.filter(by(StackExtensionType.STYLESHEET)).map(extractValue).map(stringToAsset) .map(assetToStylesheetLink).toList(); @@ -147,31 +151,41 @@ public class ExtensibleJavaScriptStack implements JavaScriptStack } } + @Override public List<String> getStacks() { return stacks; } + @Override public List<Asset> getJavaScriptLibraries() { return libraries; } + @Override public List<StylesheetLink> getStylesheets() { return stylesheets; } + @Override public String getInitialization() { return initialization; } + @Override public List<String> getModules() { return modules; } + @Override + public List<String> getEsModules() { + return esModules; + } + @Override public JavaScriptAggregationStrategy getJavaScriptAggregationStrategy() { diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptStack.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptStack.java index 777c7a118..25555e396 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptStack.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/JavaScriptStack.java @@ -72,6 +72,14 @@ public interface JavaScriptStack * @since 5.4 */ List<String> getModules(); + + /** + * Returns a list of ES modules to be automatically included in all pages. + * + * @see EsModuleManager + * @since 5.10.0 + */ + List<String> getEsModules(); /** * Identifies how to aggregate JavaScript within the stack. diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/StackExtension.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/StackExtension.java index 67cc22d7b..42c463f38 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/StackExtension.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/StackExtension.java @@ -62,6 +62,16 @@ public class StackExtension return new StackExtension(StackExtensionType.MODULE, name); } + /** + * Convenience for defining an ES_MODULE. + * + * @since 5.10.0 + */ + public static StackExtension esModule(String name) + { + return new StackExtension(StackExtensionType.ES_MODULE, name); + } + /** * Convenience for defining a STYLESHEET. * diff --git a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/StackExtensionType.java b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/StackExtensionType.java index a4f0d7b48..0dccf2e44 100644 --- a/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/StackExtensionType.java +++ b/tapestry-core/src/main/java/org/apache/tapestry5/services/javascript/StackExtensionType.java @@ -69,6 +69,13 @@ public enum StackExtensionType * @since 5.4 */ MODULE, + + /** + * An ES module to be automatically imported. + * + * @since 5.10.0 + */ + ES_MODULE, /** * Overrides the {@linkplain JavaScriptStack#getJavaScriptAggregationStrategy() JavaScript aggregation strategy} diff --git a/tapestry-core/src/main/resources/META-INF/assets/es-modules/t5/underscore.js b/tapestry-core/src/main/resources/META-INF/assets/es-modules/t5/underscore.js deleted file mode 100644 index dd5899219..000000000 --- a/tapestry-core/src/main/resources/META-INF/assets/es-modules/t5/underscore.js +++ /dev/null @@ -1,5 +0,0 @@ -// Underscore.js 1.13.7 -// https://underscorejs.org -// (c) 2009-2024 Jeremy Ashkenas, Julian Gonggrijp, and DocumentCloud and Investigative Reporters & Editors -// Underscore may be freely distributed under the MIT license. -var VERSION="1.13.7",root="object"==typeof self&&self.self===self&&self||"object"==typeof global&&global.global===global&&global||Function("return this")()||{},ArrayProto=Array.prototype,ObjProto=Object.prototype,SymbolProto="undefined"!=typeof Symbol?Symbol.prototype:null,push=ArrayProto.push,slice=ArrayProto.slice,toString=ObjProto.toString,hasOwnProperty=ObjProto.hasOwnProperty,supportsArrayBuffer="undefined"!=typeof ArrayBuffer,supportsDataView="undefined"!=typeof DataView,nativeIsAr [...] \ No newline at end of file diff --git a/tapestry-core/src/main/typescript/src/t5/core/t53-compatibility.ts b/tapestry-core/src/main/typescript/src/t5/core/t53-compatibility.ts new file mode 100644 index 000000000..5edf150a1 --- /dev/null +++ b/tapestry-core/src/main/typescript/src/t5/core/t53-compatibility.ts @@ -0,0 +1,56 @@ +// Copyright 2012, 2015 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. + +// Provides a small amount of backwards compatibility to the Tapestry 5.3 approach. +// This provides placeholders for the following: +// +// * `T5` namespace, including `extend`, `define`, and `initializers`, `extendInitializers`, and `_` properties +// +// * `Tapestry` namespace: just the `Initializer` property, as an alias of `T5.initializers` + +import _ from "underscore"; + +// @ts-ignore +var T5, Tapestry; +T5 = { + _: _, + // @ts-ignore + extend: function(destination, source) { + if (_.isFunction(source)) { + source = source(); + } + return _.extend(destination, source); + }, + // @ts-ignore + define: function(name, source) { + var namespace; + namespace = {}; + T5[name] = namespace; + // @ts-ignore + return T5.extend(namespace, source); + }, + initializers: {}, + // @ts-ignore + extendInitializers: function(source) { + // @ts-ignore + return T5.extend(T5.initializers, source); + } +}; +Tapestry = { + Initializer: T5.initializers +}; +// @ts-ignore +window.T5 = T5; +// @ts-ignore +window.Tapestry = Tapestry; diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/Border.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/Border.java index b5a6d8e13..5ffe581ea 100644 --- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/Border.java +++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/components/Border.java @@ -13,9 +13,11 @@ package org.apache.tapestry5.integration.app1.components; import org.apache.tapestry5.ComponentResources; +import org.apache.tapestry5.annotations.BeginRender; import org.apache.tapestry5.annotations.Import; import org.apache.tapestry5.annotations.Property; import org.apache.tapestry5.http.services.Request; +import org.apache.tapestry5.internal.services.ajax.RequireJsModeHelper; import org.apache.tapestry5.ioc.annotations.Inject; import org.apache.tapestry5.services.pageload.PageClassLoaderContextManager; @@ -25,7 +27,7 @@ import java.util.Calendar; * Here's a component with a template, including a t:body element. Really should rename this to "Layout" as that's the * T5 naming. */ -@Import(stylesheet = "context:css/app.css", module = {"bootstrap/collapse", "app/test-support"}) +@Import(stylesheet = "context:css/app.css") public class Border { @Inject @@ -37,6 +39,9 @@ public class Border @Inject @Property private PageClassLoaderContextManager pccm; + + @Inject + private RequireJsModeHelper requireJsModeHelper; public static final int year; @@ -67,4 +72,11 @@ public class Border return version + " from " + System.getProperty("java.vendor"); } + @BeginRender + void beginRender() + { + requireJsModeHelper.importModule("bootstrap/collapse"); + requireJsModeHelper.importModule("app/test-support"); + } + } diff --git a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java index 42338f94a..fcad59e0a 100644 --- a/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java +++ b/tapestry-core/src/test/java/org/apache/tapestry5/integration/app1/pages/Index.java @@ -58,581 +58,581 @@ public class Index private static final List<Item> ITEMS = CollectionFactory .newList( -// new Item("PublishEventDemo", "@PublishEvent Demo", "Publishing server-side events to client-side code (JavaScript)"), -// -// new Item("StaticActivationContextValueDemo", "@StaticActivationContextValue Demo", "Demonstrates the usage of @StaticActivationContextValue"), -// -// new Item("rest/RestWithOnEventDemo", "REST with @OnEvent Demo", "Demonstrates the usage of @OnEvent to handle REST requests"), -// -// new Item("rest/RestWithEventHandlerMethodNameDemo", "REST with Event Handler Method Name Demo", "Demonstrates the usage of event handler method names to handle REST requests"), -// -// new Item("Html5DateFieldDemo", "Html5DateField Demo", "Choosing dates using the native HTML5 date picker"), -// -//// new Item("ZoneFormDemo", "Zone Form Decoration", "Fields inside an Ajax-updatd Form are still decorated properly."), -// -// new Item("AjaxValidationDemo", "Ajax Validation", "Demonstrated proper integration of server-side validation and client-side field decoration."), -// -// new Item("OverrideEventHandlerDemo", "Event Handler Override Demo", "Event Handler methods overridden by sub-classes invoke base-class correctly."), -// -// new Item("LogoSubclass", "Base class Assets in sub-classes", "Assets are resolved for the parent class if that's where the annotations are."), -// -// new Item("MissingRequiredARP", "Missing Query Parameter for @ActivationRequestParameter", "Activating a page with a required @ActivationRequestParameter, but no matching query parameter, is an error."), -// -//// new Item("DateFieldValidationDemo", "DateField Validation Demo", -//// "Use of DateField component when client validation is disabled."), -// -// new Item("MixinParameters54", "Strict Mixin Parameters", "In the 5.4 DTD, Parameter Mixins must be qualified with the mixin id."), -// -// new Item("AsyncDemo", "Async Links and Forms Demo", "Async (XHR) Updates without a containing Zone."), -// -// new Item("FormCancelActionDemo", "Form Cancel Action Demo", "FormSupport.addCancel() support"), -// -// new Item("AjaxRadioDemo", "Ajax Radio Demo", "Radio components inside an Ajax form"), -// -// new Item("TimeIntervalDemo", "TimeInterval Demo", "Interval component, based on Moment.js"), -// -// new Item("LocalDateDemo", "LocalDate Demo", "LocalDate component, based on Moment.js"), -// -// new Item("EmptyIfDemo", "Empty If Demo", "Ensure an empty If can still render."), -// -// new Item("MissingAssetDemo", "Missing Asset Demo", "Error when injecting an asset that does not exist."), -// -// new Item("ConfirmDemo", "Confirm Mixin Demo", "Confirm an action when clicking it."), -// -// new Item("SingleErrorDemo", "Single Error", "Using Error component to customize where the errors for a field will be displayed."), -// -// new Item("JavaScriptTests", "JavaScript Tests", "Client-side tests using Mocha and Chai"), -// -// new Item("ModuleInitDemo", "Module-based Initialization Demo", "Invoke a module function to perform page initialization"), -// -// new Item("OperationWorkerDemo", "Operation Worker Demo", "Demonstrate use of @Operation annotation on component methods"), -// -// new Item("MixinParameterDefault", "Mixin Parameter with Default", "Ensure that a mixin parameter with a default value is not reported as unbound."), -// -// new Item("MixinVsInformalParameter", "Mixin Parameter vs. Informal Parameter", "Informal Paramters vs. Mixin parameter of same name"), -// -// new Item("inherit/childa", "TAP5-1656 Demo", "Test a reported bug in component inheritance"), -// -// new Item("ComponentInsideBlockDemo", "Component Inside Block Demo", "Verify that a component, inside a block, is still an embedded "), -// -// new Item("EventMethodUnmatchedComponentId", "Unmatched Component Id in Event Method Demo", "Show that referencing a component that does not exist in an event handler method name is an error."), -// -// new Item("AlertsDemo", "Alerts Demo", "Managing alerts both traditional and Ajax"), -// -// new Item("ClientConsoleDemo", "Client Console Demo", "Demo for the JavaScript client-side console"), -// -// new Item("InvalidFormalParameterDemo", "Unmatched Formal Parameter with @Component", "Parameters specified with @Component annotation must match formal parameters"), -// -// new Item("NullBindingToPrimitive", "Null Bound to Primitive Demo", "Correct exception when a primitive parameter is bound to null"), -// -// new Item("TreeDemo", "Tree Component Demo", "Demo of Tree Component"), -// -// new Item("TreeSelectionDemo", "Tree Component Selection Demo", "Demo of Selection with Tree Component"), -// -// new Item("TreeNoRootsDemo", "Tree Component No Roots Demo", "Demo of No Roots with Tree Component"), -// -// new Item("InvalidExpressionInDynamicTemplate", "Invalid Dynamic Expression", -// "Invalid expression in a Dynamic Template"), -// -// new Item("DynamicDemo", "Dynamic Demo", "Basic Dynamic component tests"), -// -// new Item("DynamicExpansionsDemo", "Expansions in Dynamic Templates", -// "Expansions inside Dynamic component content and attributes"), -// -// new Item("PACAnnotationDemo", "PageActivationContext Demo", -// "Shows that @PageActivationContext fields are set before calls to the activate event handler."), -// -// new Item("PACMultipleAnnotationDemo", "PageActivationContext Multiple Demo", -// "Demonstrates multiple @PageActivationContext fields."), -// -// new Item("PublicFieldAccessDemo", "Public Field Access Demo", "Demonstrates TAP5-1222 fix"), -// -// new Item("ActivationRequestParameterDemo", "ActivationRequestParameter Annotation Demo", -// "Use of @ActivationRequestParameter to encode page state into query parameters"), -// -// new Item("LibraryMessagesDemo", "Library Messages Demo", -// "Demo ability to contribute additional message catalog resources to the application global catalog."), -// -// new Item("MultiZoneUpdateInsideForm", "MultiZone Update inside a Form", -// "Update multiple zones within a single Form."), -// -// new Item("ZoneFormUpdateDemo", "Zone/Form Update Demo", "Updating a Zone inside a Form"), -// -// new Item("MultiZoneStringBodyDemo", "MultiZone String Body Demo", -// "Multi-zone updates in a loop using strings coerced into blocks"), -// -// new Item("RenderNotificationDemo", "RenderNotification Demo", "Use of RenderNotification mixin"), -// -// new Item("InjectMessagesDemo", "Inject Global Messages into Service Demo", -// "Ensure that it is possible to inject the application global message catalog into a service"), -// -// new Item("ReloadDemo", "Reloadable Service Implementation Demo", -// "Used when manually testing service reloads"), -// -// new Item("RequestParameterDemo", "RequestParameter Annotation Demo", -// "Use of @RequestParameter annotation on event handler method parameters"), -// -// new Item("CancelDemo", "Cancel Demo", "Use of the cancel option with Submit"), -// -// new Item("CanceledEventDemo", "Canceled Event Demo", "Triggering of the canceled event from a form."), -// -// new Item("PageResetDemo", "PageReset Annotation Demo", -// "Use of PageReset annotation to re-initialize page state"), -// -// new Item("TestOnlyServiceDemo", "Test Only Service Demo", -// "IoC module available via web.xml configuration"), -// -// new Item("RenderObjectExceptionDemo", "RenderObject Exception Demo", -// "Demonstrate how exceptions when rendering default objects are displayed."), -// -// new Item("MultiLevelInheritDemo", "Multi-Level Inherit Demo", -// "Use of inherit: binding prefix across three levels"), -// -// new Item("HiddenDemo", "Hidden Demo", "Demo the use of the Hidden component."), -// -// new Item("FormZoneDemo", "Form Zone Demo", "Use a form to update a zone."), -// -// new Item("ZoneUpdateNamespace", "Zone/Namespace Interaction", "Prove that TAP5-573 is fixed"), -// -// new Item("AbstractComponentDemo", "Abstract Component Demo", "Error when a component is abstract"), -// -// new Item("TemplateOverrideDemo", "Template Override Demo", -// "Child component extends and overrides parent template."), -// -// new Item("MultiZoneUpdateDemo", "Multiple Zone Update Demo", -// "A single request can now update multiple Zones"), -// -// new Item("LinkSubmitInZoneDemo", "LinkSubmit inside Zone", -// "Ensure that a LinkSubmit works correctly when its containing Form updates a Zone"), -// -// new Item("ProgressiveDemo", "ProgressiveDisplay Demo", "Progressive Enhancement via a component"), -// -// new Item("ClientNumericValidationDemo", "Client-Side Numeric Validation", -// "Client-side locale-specific validation"), -// -// new Item("PublishParametersDemo", "Publish Parameters Demo", -// "Use of @Component.publishParameters attribute."), -// -// new Item("LinkSubmitDemo", "LinkSubmit Demo", "JavaScript LinkSubmit component"), -// -// new Item("LinkSubmitWithoutValidatorDemo", "LinkSubmit Without Validator Demo", -// "Demonstrates that the LinkSubmit component is working without a validator on any of fields in the form"), -// -// new Item("PerFormValidationMessageDemo", "Per-Form Validation Messages", -// "Per-form configuration of validation messages and constraints."), -// -// new Item("EmptyLoopDemo", "Empty Loop Demo", "Use of empty parameter with the Loop component."), -// -// new Item("GenericLoopDemo", "Generic Loop Demo", -// "Use of generic parameters with the Loop component."), -// -// new Item("LoopWithMixinDemo", "Loop With Mixin Demo", -// "Use a mixin with a Loop component."), -// -// new Item("BlankPasswordDemo", "Blank Password Demo", -// "Show that a blank value in a PasswordField does not update the server side value."), -// -// new Item("GridFormEncoderDemo", "Grid Form Encoder Demo", -// "Grid inside a Form using the ValueEncoder option"), -// -// new Item("GridFormWithInitialSortMixinDemo", "Grid Form With Initial Sort Mixin Demo", -// "Grid inside a Form using the InitialSort mixin"), -// -// new Item("DateFieldAjaxFormLoop", "DateField inside AjaxFormLoop", -// "Show that DateField component works correctly inside AjaxFormLoop"), -// -// new Item("NestedForm", "Nested Form Demo", "Error when a Form is nested inside another Form."), -// -// new Item("UnhandledEventDemo", "Unhandled Event Demo", -// "Events that don't have matching event handlers cause exceptions"), -// -// new Item("PrimitiveDefaultDemo", "Primitive Default Demo", -// "Primitive value returned from parameter default method"), -// -// new Item("ValidateFormValidationExceptionDemo", "ValidationForm ValidationException Demo", -// "Throwing a ValidationException from the validateForm event handler."), -// -// new Item("ClientFormatDemo", "Client Format Validation", "Client-side input format validation"), -// -// new Item("ShortGrid", "Short Grid", -// "Grid where the number of claimed rows is less than the number of actual rows"), -// -// new Item("NullParameterDemo", "Null Parameter Demo", "Binding a not-null parameter to null."), -// -// new Item("nestedbeaneditor", "Nested BeanEditor", -// "BeanEditor as override for property editor in BeanEditForm"), -// -// new Item("actionpage", "Action Page", "tests fixture for ActionLink component"), -// -// new Item("cleancachedemo", "Clean Cache Demo", "cache cleared properly during Ajax calls"), -// -// new Item("numberbeaneditordemo", "Number BeanEditor Demo", -// "use of nulls and wrapper types with BeanEditor"), -// -// new Item("forminjectordemo", "FormInjector Demo", "extending a form dynamically via Ajax"), -// -// new Item("music", "Music Page", "demo handling of edge cases of page naming"), -// -// new Item("PersistentDemo", "Persistent Demo", "storing and clearing persistent properties"), -// -// new Item("ActionViaLinkDemo", "Action via Link Demo", "tests creating an action link explicitly"), -// -// new Item("FormFragmentDemo", "Form Fragment Demo", "page with dynamic form sections"), -// -// new Item("BooleanDemo", "Boolean Property Demo", -// "demo boolean properties using both is and get prefixes"), -// -// new Item("DeleteFromGridDemo", "Delete From Grid", "demo deleting items form a Grid"), -// -// new Item("RenderErrorDemo", "Render Error Demo", "reporting of errors while rendering"), -// -// new Item("nested/AssetDemo", "AssetDemo", "declaring an image using Assets"), -// -// new Item("nested/ActionDemo", "Action With Context Demo", -// "using action links with context on page with activation context"), -// -// new Item("blockdemo", "BlockDemo", "use of blocks to control rendering"), -// -// new Item("countdown", "Countdown Page", "defining component using @Component annotation"), -// -// new Item("injectdemo", "Inject Demo", "use of various kinds of injection"), -// -// new Item("instancemixin", "InstanceMixin", "mixin added to a particular component instance"), -// -// new Item("TextFieldWrapperTypeDemo", "TextField Wrapper Types", -// "use of TextField to edit numeric wrapper types (not primitives) "), -// -// new Item("EnvironmentalDemo", "Environmental Annotation Usage", -// "Storing and retrieving Environmental values"), -// -// new Item("Expansion", "Expansion Page", "Use of expansions in templates"), -// -// new Item("ExpansionSubclass", "ExpansionSubclass", -// "components can inherit templates from base classes"), -// -// new Item("Localization", "Localization", "access localized messages from the component catalog"), -// -// new Item("NumberSelect", "NumberSelect", "passivate/activate page context demo"), -// -// new Item("ParameterConflict", "Template Overridden by Class Page", -// "Parameters in the class override those in the template"), -// -// new Item("ParameterDefault", "ParameterDefault", "defaulter methods for component parameters"), -// -// new Item("passwordfielddemo", "PasswordFieldDemo", "test for the PasswordField component"), -// -// new Item("rendercomponentdemo", "RenderComponentDemo", -// "components that \"nominate\" other components to render"), -// -// new Item("renderphaseorder", "RenderPhaseOrder", -// "order of operations when invoking render phase methods"), -// -// new Item("simpleform", "SimpleForm", "first pass at writing Form and TextField components"), -// -// new Item("OptionGroupForm", "OptionGroupForm Demo", "Select with Option Group"), -// -// new Item("validform", "ValidForm", "server-side input validation"), -// -// new Item("ToDoListVolatile", "ToDo List (Volatile)", "Loops and Submit inside Form, volatile mode"), -// -// new Item("MissingTemplate", "Missing Template Demo", -// "Demo for what happens when a template is not found for a page"), -// -// new Item("nested/zonedemo", "Zone Demo", "dynamic updates within a page"), -// -// new Item("todolist", "ToDo List", "Loops and Submit inside Form using primary key encoder"), -// -// new Item("flashdemo", "FlashDemo", "demonstrate 'flash' persistence"), -// -// new Item("beaneditordemo", "BeanEditor Demo", "demonstrate the BeanEditor mega-component"), -// -// new Item("pageloadeddemo", "PageLoaded Demo", "shows that page lifecycle methods are invoked"), -// -// new Item("griddemo", "Grid Demo", "default Grid component"), -// -// new Item("GridInLoopDemo", "Grid In Loop Demo", "Grid inside loop with different model on each iteration"), -// -// new Item("nullgrid", "Null Grid", "handling of null source for Grid"), -// -// new Item("gridsetdemo", "Grid Set Demo", "handling of Set sources for Grid"), -// -// new Item("gridenumdemo", "Grid Enum Demo", "handling of enum types in the Grid"), -// -// new Item("GridRemoveReorderDemo", "Grid Remove/Reorder Demo", -// "handling of remove and reorder parameters"), -// -// new Item("EmptyGrid", "Empty Grid Demo", "show table for empty data sources"), -// -// new Item("GridEarlyPagingDemo", "Grid Early Paging", "set a Grid's current page before rendering"), -// -// new Item("protected", "Protected Page", -// "Demonstrate result of non-void return from a page's activate method"), -// -// new Item("Kicker", "Kicker", "demos complex page and component context in links"), -// -// new Item("simpletrackgriddemo", "SimpleTrack Grid Demo", -// "customizing the model for a Grid around an interface"), -// -// new Item("pagelinkcontext", "PageLink Context Demo", -// "passing explicit context in a page render link"), -// -// new Item("pagecontextinform", "Page Context in Form", "passivate/activate page context in Form", -// "betty", "wilma", "context with spaces", "context/with/slashes"), -// -// new Item("ValidBeanEditorDemo", "Client Validation Demo", "BeanEditor with validation enabled"), -// -// new Item("Unreachable", "Unreachable Page", "page not reachable due to IgnoredPathsFilter"), -// -// new Item("renderabledemo", "Renderable Demo", -// "shows that render phase methods can return a Renderable"), -// -// new Item("inheritedbindingsdemo", "Inherited Bindings Demo", -// "Tests for components that inherit bindings from containing components"), -// -// new Item("ClientPersistenceDemo", "Client Persistence Demo", -// "component field values persisted on the client side"), -// -// new Item("attributeExpansionsDemo", "Attribute Expansions Demo", -// "use expansions inside attributes of ordinary elements"), -// -// new Item("PaletteDemo", "Palette Demo", "multiple selection component"), -// new Item("PaletteGroupedDemo", "Palette Grouped Demo", "multiple selection component (grouped)"), -// -// new Item("ReturnTypes", "Return Types", "tests various event handler return types"), -// -// new Item("FormEncodingType", "Form Encoding Type", -// "Test ability to set an encoding type for a Form"), -// -// new Item("RadioDemo", "RadioDemo", "Use of the RadioGroup and Radio components"), -// -// new Item("RegexpDemo", "Regexp Demo", "Use of the Regexp validator"), -// -// new Item("BeanEditRemoveReorder", "BeanEdit Remove/Reorder", -// "Use of the remove and reorder parameters with BeanEditForm"), -// -// new Item("MultiBeanEditDemo", "MultiBeanEdit Demo", -// "Multiple BeanEditor components in a single form"), -// -// new Item("GridFormDemo", "Grid Form Demo", "Grid operating inside a Form"), -// -// new Item("DateFieldDemo", "DateField Demo", "using DateField by itself on a page"), -// -// new Item("BeanEditDateDemo", "BeanEditor / Date Demo", -// "Use of date properties inside BeanEditor and BeanDisplay"), -// -// new Item("eventmethodtranslate", "EventMethod Translator", -// "Demo ability to provide toclient and parseclient event handler methods"), -// -// new Item("autocompletedemo", "Autocomplete Mixin Demo", -// "Demo the autocomplete mixin for text fields"), -// -// new Item("componentparameter", "ComponentParameter Demo", -// " Demo using a component type as a parameter type and succesfully passing a component"), -// -// new Item("inheritinformalsdemo", "Inherit Informal Parameters Demo", -// "Demo a component which inherits informal parameters from its container"), -// -// new Item("disabledfields", "Disabled Fields", -// "Demonstrate a bunch of disabled fields, to verify that the RenderDisabled mixin works and is being used properly"), -// -// new Item("BeanEditorOverride", "BeanEditor Override", -// "Property editor overrides work for the BeanEditor component itself (not just the BeanEditForm component)"), -// -// new Item("varbindingdemo", "Var Binding Demo", "use of the var: binding prefix"), -// -// new Item("leangriddemo", "Lean Grid Demo", -// "Grid component with lean parameter turned on, to eliminate CSS class attributes in TD and TH elements"), -// -// new Item("blockcaller", "Action Links off of Active Page", -// "Actions can exist on pages other than the active page, via Blocks."), -// -// new Item("unlessdemo", "Unless Demo", "use of the Unless component"), -// -// new Item("delegateinline", "Inline Delegate", -// "Using the delegate component to create inline components"), -// -// new Item("MagicValueEncoder", "Magic ValueEncoder Demo", -// "Automatic creation of ValueEncoder using the TypeCoercer"), -// -// new Item("NullStrategyDemo", "Null Field Strategy Demo", "use of the nulls parameter of TextField"), -// -// new Item("OverrideValidationDecorator", "Override Validation Decorator", -// "override the default validation decorator"), -// -// new Item("ExceptionEventDemo", "Exception Event Demo", "handling component event exceptions"), -// -// new Item("AddedGridColumnsDemo", "Added Grid Columns Demo", "programatically adding grid columns"), -// -// new Item("PrimitiveArrayParameterDemo", "Primitive Array Parameter Demo", -// "use primitive array as parameter type"), -// -// new Item("RenderPhaseMethodExceptionDemo", "Render Phase Method Exception Demo", -// "render phase methods may throw checked exceptions"), -// -// new Item("TrackEditor", "Generic Page Class Demo", -// "demo use of generics with component classes and, particularily, with property types"), -// -// new Item("IndirectProtectedFields", "Protected Fields Demo", -// "demo exception when component class contains protected fields"), -// -// new Item("injectcomponentdemo", "Inject Component Demo", "inject component defined in template"), -// -// new Item("cachedpage", "Cached Annotation", "Caching method return values"), -// -// new Item("cachedpage2", "Cached Annotation2", "Caching method return values w/ inheritence"), -// -// new Item("inplacegriddemo", "In-Place Grid Demo", "Grid that updates in-place using Ajax"), -// -// new Item("methodadvicedemo", "Method Advice Demo", "Advising component methods."), -// -// new Item("HasBodyDemo", "Has Body Demo", "Verify the hasBody() method of ComponentResources"), -// -// new Item("BeanEditorBeanEditContext", "BeanEditor BeanEditContext", -// "BeanEditContext is pushed into enviroment by BeanEditor."), -// -// new Item("InformalParametersDemo", "Informal Parameters Demo", -// "Access to informal parameters names and values"), -// -// new Item("FormFieldOutsideForm", "Form Field Outside Form", -// "Nice exception message for common problem of form fields outside forms"), -// -// new Item("SubmitWithContext", "Submit With Context", "Providing a context for Submit component"), -// -// new Item("MessageConstraintGeneratorDemo", "Validation Constraints From Messages", -// "Providing validators to apply from a properties file"), -// -// new Item("RenderClientIdDemo", "RenderClientId Mixin", -// "Force render of client-side id of a client element via the RenderClientId mixin"), -// -// new Item("BindParameterDemo", "BindParameter mixin annotation", -// "Accessing component parameter values from a mixin"), -// -// new Item("BindParameterNoSuchParameter", "BindParameter error handling", -// "BindParameter throws exception if the containing component doesn't have a matching parameter"), -// -// new Item("BindParameterOnComponent", "BindParameter on component", -// "Verify that BindParameter can only be used on mixin fields"), -// -// new Item("MixinOrderingDemo", "Mixin Ordering Demo", "Various mixin-ordering scenarios"), -// -// new Item( -// "MissingComponentClassException", -// "Missing Component Class Exception", -// "Meaningful exception message thrown when component class can't be determined from template or field in containing component."), -// -// new Item("SessionAttributeDemo", "SessionAttribute Demo", -// "Annotation to map a field to a specific session attribute"), -// -// new Item("BeanEditCalendarDemo", "BeanEditor / Calendar Demo", -// "Use of calendar properties inside BeanEditor and BeanDisplay"), -// -// new Item("TriggerDemo", "Trigger Demo", "Use of Trigger component"), -// -// new Item("ImageSubmitDemo", "Submit with an Image Demo", -// "Make sure that submit with the image parameter set triggers the 'selected' event."), -// -// new Item("SelectZoneDemo", "Select Zone Demo", "Use a Select component to update a zone."), -// -// new Item("AssetProtectionDemo", "Asset Protection Demo", -// "AssetProtectionDispatcher is properly contributed and functioning"), -// -// new Item("BeanDisplayEnumDemo", "BeanDisplay Enum Demo", -// "User represenation of enum values is correctly read from messages"), -// -// new Item("unavailablecomponentdemo", "Report Location of Unavailable Component", -// "Report Location of Unavailable Component"), -// -// new Item("discardafterdemo", "@DiscardAfter Demo", "Demo using @DiscardAfter annotation"), -// -// new Item("SelectDemo", "Select Demo", "Validation decoration for Select"), -// -// new Item("SelectModelFromObjectsAndPropertyNameDemo", "SelectModel from objects and property name", -// "Creating a SelectModel from a list of objects and a label property name"), -// -// new Item("SelectModelFromObjectsDemo", "SelectModel from objects", -// "Creating a SelectModel from a list of objects"), -// -// new Item("SelectModelCoercionDemo", "SelectModel coercion", -// "Creating a SelectModel from a list of objects using coercion"), -// -// new Item("DecoratePageRenderLinkDemo", "Decorate Page Render Link Demo", -// "Decorating page render links"), -// -// new Item("DecorateComponentEventLinkDemo", "Decorate Component Event Link Demo", -// "Decorating event links"), -// -// new Item("ValidatorMacroDemo", "Validator Macro Demo", "Using validator macros"), -// -// new Item("AtInjectDemo", "@jakarta.inject.Inject Demo", "Using @jakarta.inject.Inject for injection"), -// -// new Item("LinkQueryParameters", "Link Query Parameters Demo", -// "Providing Query Parameters directly to link components as a map of key=parameter name, value=parameter values"), -// -// new Item("ChecklistDemo", "Checklist Demo", "Use Checklist component"), -// -// new Item("BeanEditFormPrepareBubbling", "BeanEditor Prepare Bubbling Demo", "Prepare event bubbling"), -// -// new Item("NestedFormFragment", "Nested Form Fragment Demo", "Nesting Form Fragments work properly"), -// -// new Item("MapExpressionInExpansions", "Map Expressions in Expansions Demo", "Maps can be used in expansions"), -// -// new Item("ExpressionInJsFunction", "Expressions in JS Functions Demo", "Expressions can be used inside javascript functions"), -// -// new Item("FormFieldFocusDemo", "FormFieldFocus (DEPRECATED) Demo", "Setting the Form focus on a specific field"), -// -// new Item("FormFragmentExplicitVisibleBoundsDemo", "Form Fragment Explicit Visible Bounds Demo", "Check for form fragment parent visibility can be bounded to"), -// -// new Item("OverrideFieldFocusDemo", "OverrideFieldFocus Demo", "Setting the focus in a form to a specific field"), -// -// new Item("OverrideLabelClassDemo", "Override Label Class Demo", "Setting class attribute on Label component"), -// -// new Item("FormLinkParameters", "FormLinkParameters Demo", "Form link parameters should be unescaped for a hidden field"), -// -// new Item("KnownActivationContextDemo", "Known Activation Context Demo", "Page is displayed normally if called without context (TAP5-2070)", -// "Exact"), -// -// new Item("UnknownActivationContextDemo", "Unknown Activation Context Demo", "Page refuse to serve if called with an unknown activation context (TAP5-2070)", -// "Unwanted", "context"), -// -// new Item("ModuleConfigurationCallbackDemo", "ModuleConfigurationCallback Demo", "Shows an example of changing the Require.js configuration using JavaScriptSupport.addModuleConfigurationDemo()"), -// -// new Item("PartialTemplateRendererDemo", "PartialTemplateRenderer Demo", "Shows some examples of rendering blocks and components to a String using PartialTemplateRenderer"), -// -// new Item("nested/PageThatThrowsException", "Reload on nested page", "Tests a page reload from a nested page's exception report"), -// -// new Item("inplacegridinloopdemo", "In-Place Grid in a Loop Demo", "In-place grid in a loop"), -// -// new Item("GenericTypeDemo", "Generic bound type demo", "Tests that generic type info is available for generic bindings"), -// -// new Item("FormFieldClientIdParameterDemo", "Form Field clientId Parameter Demo", "Shows and tests how to explicitly set the id of a form field component"), -// -// new Item("gridwithsubmitwithcontextdemo", "Grid with Submit with context", "A grid whose rows contain a Submit component with context"), -// -// new Item("textfieldwithnullvalidateparameter", "TextField with null validate parameter", "A TextField whose validate parameter is bound to null"), -// -// new Item("validateCheckboxMustBeChecked", "Validate Checkbox Must Be Checked", "A form that trigger validate in " + -// "error event on submit when checkbox is not checked"), -// -// new Item("validateCheckboxMustBeUnchecked", "Validate Checkbox Must Be Unchecked", "A form that trigger validate in " + -// "error event on submit when checkbox is checked"), -// -// new Item("validateInErrorEvent", "Validate in error Event", "A form that trigger validate in " + -// "error event on submit when textfield is empty"), -// -// new Item("onactivateredirect", "OnActivateRedirect Demo", "A page that redirects to itself from" -// + " its activation method"), -// -// new Item("BeanEditorWithFormFragmentDemo", "Bean Editor With Form Fragment Demo", "TriggerFragment mixin used inside a BeanEditor"), -// -// new Item("ObjectEditorDemo","Object Editor Demo","Edit Bean with address objects"), -// -// new Item("IfDemo","If Demo","If component with all its options"), -// -// new Item("RecursiveDemo","Recursive Demo","Recursive component example"), -// -// new Item("SelfRecursiveDemo", "Self-Recursive Demo", "check for handling of self-recursive components"), -// -// new Item("EsModuleDemo", "ES Module Demo", "tests and demonstrations for the ES module support") + new Item("PublishEventDemo", "@PublishEvent Demo", "Publishing server-side events to client-side code (JavaScript)"), + + new Item("StaticActivationContextValueDemo", "@StaticActivationContextValue Demo", "Demonstrates the usage of @StaticActivationContextValue"), + + new Item("rest/RestWithOnEventDemo", "REST with @OnEvent Demo", "Demonstrates the usage of @OnEvent to handle REST requests"), + + new Item("rest/RestWithEventHandlerMethodNameDemo", "REST with Event Handler Method Name Demo", "Demonstrates the usage of event handler method names to handle REST requests"), + + new Item("Html5DateFieldDemo", "Html5DateField Demo", "Choosing dates using the native HTML5 date picker"), + +// new Item("ZoneFormDemo", "Zone Form Decoration", "Fields inside an Ajax-updatd Form are still decorated properly."), + + new Item("AjaxValidationDemo", "Ajax Validation", "Demonstrated proper integration of server-side validation and client-side field decoration."), + + new Item("OverrideEventHandlerDemo", "Event Handler Override Demo", "Event Handler methods overridden by sub-classes invoke base-class correctly."), + + new Item("LogoSubclass", "Base class Assets in sub-classes", "Assets are resolved for the parent class if that's where the annotations are."), + + new Item("MissingRequiredARP", "Missing Query Parameter for @ActivationRequestParameter", "Activating a page with a required @ActivationRequestParameter, but no matching query parameter, is an error."), + +// new Item("DateFieldValidationDemo", "DateField Validation Demo", +// "Use of DateField component when client validation is disabled."), + + new Item("MixinParameters54", "Strict Mixin Parameters", "In the 5.4 DTD, Parameter Mixins must be qualified with the mixin id."), + + new Item("AsyncDemo", "Async Links and Forms Demo", "Async (XHR) Updates without a containing Zone."), + + new Item("FormCancelActionDemo", "Form Cancel Action Demo", "FormSupport.addCancel() support"), + + new Item("AjaxRadioDemo", "Ajax Radio Demo", "Radio components inside an Ajax form"), + + new Item("TimeIntervalDemo", "TimeInterval Demo", "Interval component, based on Moment.js"), + + new Item("LocalDateDemo", "LocalDate Demo", "LocalDate component, based on Moment.js"), + + new Item("EmptyIfDemo", "Empty If Demo", "Ensure an empty If can still render."), + + new Item("MissingAssetDemo", "Missing Asset Demo", "Error when injecting an asset that does not exist."), + + new Item("ConfirmDemo", "Confirm Mixin Demo", "Confirm an action when clicking it."), + + new Item("SingleErrorDemo", "Single Error", "Using Error component to customize where the errors for a field will be displayed."), + + new Item("JavaScriptTests", "JavaScript Tests", "Client-side tests using Mocha and Chai"), + + new Item("ModuleInitDemo", "Module-based Initialization Demo", "Invoke a module function to perform page initialization"), + + new Item("OperationWorkerDemo", "Operation Worker Demo", "Demonstrate use of @Operation annotation on component methods"), + + new Item("MixinParameterDefault", "Mixin Parameter with Default", "Ensure that a mixin parameter with a default value is not reported as unbound."), + + new Item("MixinVsInformalParameter", "Mixin Parameter vs. Informal Parameter", "Informal Paramters vs. Mixin parameter of same name"), + + new Item("inherit/childa", "TAP5-1656 Demo", "Test a reported bug in component inheritance"), + + new Item("ComponentInsideBlockDemo", "Component Inside Block Demo", "Verify that a component, inside a block, is still an embedded "), + + new Item("EventMethodUnmatchedComponentId", "Unmatched Component Id in Event Method Demo", "Show that referencing a component that does not exist in an event handler method name is an error."), + + new Item("AlertsDemo", "Alerts Demo", "Managing alerts both traditional and Ajax"), + + new Item("ClientConsoleDemo", "Client Console Demo", "Demo for the JavaScript client-side console"), + + new Item("InvalidFormalParameterDemo", "Unmatched Formal Parameter with @Component", "Parameters specified with @Component annotation must match formal parameters"), + + new Item("NullBindingToPrimitive", "Null Bound to Primitive Demo", "Correct exception when a primitive parameter is bound to null"), + + new Item("TreeDemo", "Tree Component Demo", "Demo of Tree Component"), + + new Item("TreeSelectionDemo", "Tree Component Selection Demo", "Demo of Selection with Tree Component"), + + new Item("TreeNoRootsDemo", "Tree Component No Roots Demo", "Demo of No Roots with Tree Component"), + + new Item("InvalidExpressionInDynamicTemplate", "Invalid Dynamic Expression", + "Invalid expression in a Dynamic Template"), + + new Item("DynamicDemo", "Dynamic Demo", "Basic Dynamic component tests"), + + new Item("DynamicExpansionsDemo", "Expansions in Dynamic Templates", + "Expansions inside Dynamic component content and attributes"), + + new Item("PACAnnotationDemo", "PageActivationContext Demo", + "Shows that @PageActivationContext fields are set before calls to the activate event handler."), + + new Item("PACMultipleAnnotationDemo", "PageActivationContext Multiple Demo", + "Demonstrates multiple @PageActivationContext fields."), + + new Item("PublicFieldAccessDemo", "Public Field Access Demo", "Demonstrates TAP5-1222 fix"), + + new Item("ActivationRequestParameterDemo", "ActivationRequestParameter Annotation Demo", + "Use of @ActivationRequestParameter to encode page state into query parameters"), + + new Item("LibraryMessagesDemo", "Library Messages Demo", + "Demo ability to contribute additional message catalog resources to the application global catalog."), + + new Item("MultiZoneUpdateInsideForm", "MultiZone Update inside a Form", + "Update multiple zones within a single Form."), + + new Item("ZoneFormUpdateDemo", "Zone/Form Update Demo", "Updating a Zone inside a Form"), + + new Item("MultiZoneStringBodyDemo", "MultiZone String Body Demo", + "Multi-zone updates in a loop using strings coerced into blocks"), + + new Item("RenderNotificationDemo", "RenderNotification Demo", "Use of RenderNotification mixin"), + + new Item("InjectMessagesDemo", "Inject Global Messages into Service Demo", + "Ensure that it is possible to inject the application global message catalog into a service"), + + new Item("ReloadDemo", "Reloadable Service Implementation Demo", + "Used when manually testing service reloads"), + + new Item("RequestParameterDemo", "RequestParameter Annotation Demo", + "Use of @RequestParameter annotation on event handler method parameters"), + + new Item("CancelDemo", "Cancel Demo", "Use of the cancel option with Submit"), + + new Item("CanceledEventDemo", "Canceled Event Demo", "Triggering of the canceled event from a form."), + + new Item("PageResetDemo", "PageReset Annotation Demo", + "Use of PageReset annotation to re-initialize page state"), + + new Item("TestOnlyServiceDemo", "Test Only Service Demo", + "IoC module available via web.xml configuration"), + + new Item("RenderObjectExceptionDemo", "RenderObject Exception Demo", + "Demonstrate how exceptions when rendering default objects are displayed."), + + new Item("MultiLevelInheritDemo", "Multi-Level Inherit Demo", + "Use of inherit: binding prefix across three levels"), + + new Item("HiddenDemo", "Hidden Demo", "Demo the use of the Hidden component."), + + new Item("FormZoneDemo", "Form Zone Demo", "Use a form to update a zone."), + + new Item("ZoneUpdateNamespace", "Zone/Namespace Interaction", "Prove that TAP5-573 is fixed"), + + new Item("AbstractComponentDemo", "Abstract Component Demo", "Error when a component is abstract"), + + new Item("TemplateOverrideDemo", "Template Override Demo", + "Child component extends and overrides parent template."), + + new Item("MultiZoneUpdateDemo", "Multiple Zone Update Demo", + "A single request can now update multiple Zones"), + + new Item("LinkSubmitInZoneDemo", "LinkSubmit inside Zone", + "Ensure that a LinkSubmit works correctly when its containing Form updates a Zone"), + + new Item("ProgressiveDemo", "ProgressiveDisplay Demo", "Progressive Enhancement via a component"), + + new Item("ClientNumericValidationDemo", "Client-Side Numeric Validation", + "Client-side locale-specific validation"), + + new Item("PublishParametersDemo", "Publish Parameters Demo", + "Use of @Component.publishParameters attribute."), + + new Item("LinkSubmitDemo", "LinkSubmit Demo", "JavaScript LinkSubmit component"), + + new Item("LinkSubmitWithoutValidatorDemo", "LinkSubmit Without Validator Demo", + "Demonstrates that the LinkSubmit component is working without a validator on any of fields in the form"), + + new Item("PerFormValidationMessageDemo", "Per-Form Validation Messages", + "Per-form configuration of validation messages and constraints."), + + new Item("EmptyLoopDemo", "Empty Loop Demo", "Use of empty parameter with the Loop component."), + + new Item("GenericLoopDemo", "Generic Loop Demo", + "Use of generic parameters with the Loop component."), + + new Item("LoopWithMixinDemo", "Loop With Mixin Demo", + "Use a mixin with a Loop component."), + + new Item("BlankPasswordDemo", "Blank Password Demo", + "Show that a blank value in a PasswordField does not update the server side value."), + + new Item("GridFormEncoderDemo", "Grid Form Encoder Demo", + "Grid inside a Form using the ValueEncoder option"), + + new Item("GridFormWithInitialSortMixinDemo", "Grid Form With Initial Sort Mixin Demo", + "Grid inside a Form using the InitialSort mixin"), + + new Item("DateFieldAjaxFormLoop", "DateField inside AjaxFormLoop", + "Show that DateField component works correctly inside AjaxFormLoop"), + + new Item("NestedForm", "Nested Form Demo", "Error when a Form is nested inside another Form."), + + new Item("UnhandledEventDemo", "Unhandled Event Demo", + "Events that don't have matching event handlers cause exceptions"), + + new Item("PrimitiveDefaultDemo", "Primitive Default Demo", + "Primitive value returned from parameter default method"), + + new Item("ValidateFormValidationExceptionDemo", "ValidationForm ValidationException Demo", + "Throwing a ValidationException from the validateForm event handler."), + + new Item("ClientFormatDemo", "Client Format Validation", "Client-side input format validation"), + + new Item("ShortGrid", "Short Grid", + "Grid where the number of claimed rows is less than the number of actual rows"), + + new Item("NullParameterDemo", "Null Parameter Demo", "Binding a not-null parameter to null."), + + new Item("nestedbeaneditor", "Nested BeanEditor", + "BeanEditor as override for property editor in BeanEditForm"), + + new Item("actionpage", "Action Page", "tests fixture for ActionLink component"), + + new Item("cleancachedemo", "Clean Cache Demo", "cache cleared properly during Ajax calls"), + + new Item("numberbeaneditordemo", "Number BeanEditor Demo", + "use of nulls and wrapper types with BeanEditor"), + + new Item("forminjectordemo", "FormInjector Demo", "extending a form dynamically via Ajax"), + + new Item("music", "Music Page", "demo handling of edge cases of page naming"), + + new Item("PersistentDemo", "Persistent Demo", "storing and clearing persistent properties"), + + new Item("ActionViaLinkDemo", "Action via Link Demo", "tests creating an action link explicitly"), + + new Item("FormFragmentDemo", "Form Fragment Demo", "page with dynamic form sections"), + + new Item("BooleanDemo", "Boolean Property Demo", + "demo boolean properties using both is and get prefixes"), + + new Item("DeleteFromGridDemo", "Delete From Grid", "demo deleting items form a Grid"), + + new Item("RenderErrorDemo", "Render Error Demo", "reporting of errors while rendering"), + + new Item("nested/AssetDemo", "AssetDemo", "declaring an image using Assets"), + + new Item("nested/ActionDemo", "Action With Context Demo", + "using action links with context on page with activation context"), + + new Item("blockdemo", "BlockDemo", "use of blocks to control rendering"), + + new Item("countdown", "Countdown Page", "defining component using @Component annotation"), + + new Item("injectdemo", "Inject Demo", "use of various kinds of injection"), + + new Item("instancemixin", "InstanceMixin", "mixin added to a particular component instance"), + + new Item("TextFieldWrapperTypeDemo", "TextField Wrapper Types", + "use of TextField to edit numeric wrapper types (not primitives) "), + + new Item("EnvironmentalDemo", "Environmental Annotation Usage", + "Storing and retrieving Environmental values"), + + new Item("Expansion", "Expansion Page", "Use of expansions in templates"), + + new Item("ExpansionSubclass", "ExpansionSubclass", + "components can inherit templates from base classes"), + + new Item("Localization", "Localization", "access localized messages from the component catalog"), + + new Item("NumberSelect", "NumberSelect", "passivate/activate page context demo"), + + new Item("ParameterConflict", "Template Overridden by Class Page", + "Parameters in the class override those in the template"), + + new Item("ParameterDefault", "ParameterDefault", "defaulter methods for component parameters"), + + new Item("passwordfielddemo", "PasswordFieldDemo", "test for the PasswordField component"), + + new Item("rendercomponentdemo", "RenderComponentDemo", + "components that \"nominate\" other components to render"), + + new Item("renderphaseorder", "RenderPhaseOrder", + "order of operations when invoking render phase methods"), + + new Item("simpleform", "SimpleForm", "first pass at writing Form and TextField components"), + + new Item("OptionGroupForm", "OptionGroupForm Demo", "Select with Option Group"), + + new Item("validform", "ValidForm", "server-side input validation"), + + new Item("ToDoListVolatile", "ToDo List (Volatile)", "Loops and Submit inside Form, volatile mode"), + + new Item("MissingTemplate", "Missing Template Demo", + "Demo for what happens when a template is not found for a page"), + + new Item("nested/zonedemo", "Zone Demo", "dynamic updates within a page"), + + new Item("todolist", "ToDo List", "Loops and Submit inside Form using primary key encoder"), + + new Item("flashdemo", "FlashDemo", "demonstrate 'flash' persistence"), + + new Item("beaneditordemo", "BeanEditor Demo", "demonstrate the BeanEditor mega-component"), + + new Item("pageloadeddemo", "PageLoaded Demo", "shows that page lifecycle methods are invoked"), + + new Item("griddemo", "Grid Demo", "default Grid component"), + + new Item("GridInLoopDemo", "Grid In Loop Demo", "Grid inside loop with different model on each iteration"), + + new Item("nullgrid", "Null Grid", "handling of null source for Grid"), + + new Item("gridsetdemo", "Grid Set Demo", "handling of Set sources for Grid"), + + new Item("gridenumdemo", "Grid Enum Demo", "handling of enum types in the Grid"), + + new Item("GridRemoveReorderDemo", "Grid Remove/Reorder Demo", + "handling of remove and reorder parameters"), + + new Item("EmptyGrid", "Empty Grid Demo", "show table for empty data sources"), + + new Item("GridEarlyPagingDemo", "Grid Early Paging", "set a Grid's current page before rendering"), + + new Item("protected", "Protected Page", + "Demonstrate result of non-void return from a page's activate method"), + + new Item("Kicker", "Kicker", "demos complex page and component context in links"), + + new Item("simpletrackgriddemo", "SimpleTrack Grid Demo", + "customizing the model for a Grid around an interface"), + + new Item("pagelinkcontext", "PageLink Context Demo", + "passing explicit context in a page render link"), + + new Item("pagecontextinform", "Page Context in Form", "passivate/activate page context in Form", + "betty", "wilma", "context with spaces", "context/with/slashes"), + + new Item("ValidBeanEditorDemo", "Client Validation Demo", "BeanEditor with validation enabled"), + + new Item("Unreachable", "Unreachable Page", "page not reachable due to IgnoredPathsFilter"), + + new Item("renderabledemo", "Renderable Demo", + "shows that render phase methods can return a Renderable"), + + new Item("inheritedbindingsdemo", "Inherited Bindings Demo", + "Tests for components that inherit bindings from containing components"), + + new Item("ClientPersistenceDemo", "Client Persistence Demo", + "component field values persisted on the client side"), + + new Item("attributeExpansionsDemo", "Attribute Expansions Demo", + "use expansions inside attributes of ordinary elements"), + + new Item("PaletteDemo", "Palette Demo", "multiple selection component"), + new Item("PaletteGroupedDemo", "Palette Grouped Demo", "multiple selection component (grouped)"), + + new Item("ReturnTypes", "Return Types", "tests various event handler return types"), + + new Item("FormEncodingType", "Form Encoding Type", + "Test ability to set an encoding type for a Form"), + + new Item("RadioDemo", "RadioDemo", "Use of the RadioGroup and Radio components"), + + new Item("RegexpDemo", "Regexp Demo", "Use of the Regexp validator"), + + new Item("BeanEditRemoveReorder", "BeanEdit Remove/Reorder", + "Use of the remove and reorder parameters with BeanEditForm"), + + new Item("MultiBeanEditDemo", "MultiBeanEdit Demo", + "Multiple BeanEditor components in a single form"), + + new Item("GridFormDemo", "Grid Form Demo", "Grid operating inside a Form"), + + new Item("DateFieldDemo", "DateField Demo", "using DateField by itself on a page"), + + new Item("BeanEditDateDemo", "BeanEditor / Date Demo", + "Use of date properties inside BeanEditor and BeanDisplay"), + + new Item("eventmethodtranslate", "EventMethod Translator", + "Demo ability to provide toclient and parseclient event handler methods"), + + new Item("autocompletedemo", "Autocomplete Mixin Demo", + "Demo the autocomplete mixin for text fields"), + + new Item("componentparameter", "ComponentParameter Demo", + " Demo using a component type as a parameter type and succesfully passing a component"), + + new Item("inheritinformalsdemo", "Inherit Informal Parameters Demo", + "Demo a component which inherits informal parameters from its container"), + + new Item("disabledfields", "Disabled Fields", + "Demonstrate a bunch of disabled fields, to verify that the RenderDisabled mixin works and is being used properly"), + + new Item("BeanEditorOverride", "BeanEditor Override", + "Property editor overrides work for the BeanEditor component itself (not just the BeanEditForm component)"), + + new Item("varbindingdemo", "Var Binding Demo", "use of the var: binding prefix"), + + new Item("leangriddemo", "Lean Grid Demo", + "Grid component with lean parameter turned on, to eliminate CSS class attributes in TD and TH elements"), + + new Item("blockcaller", "Action Links off of Active Page", + "Actions can exist on pages other than the active page, via Blocks."), + + new Item("unlessdemo", "Unless Demo", "use of the Unless component"), + + new Item("delegateinline", "Inline Delegate", + "Using the delegate component to create inline components"), + + new Item("MagicValueEncoder", "Magic ValueEncoder Demo", + "Automatic creation of ValueEncoder using the TypeCoercer"), + + new Item("NullStrategyDemo", "Null Field Strategy Demo", "use of the nulls parameter of TextField"), + + new Item("OverrideValidationDecorator", "Override Validation Decorator", + "override the default validation decorator"), + + new Item("ExceptionEventDemo", "Exception Event Demo", "handling component event exceptions"), + + new Item("AddedGridColumnsDemo", "Added Grid Columns Demo", "programatically adding grid columns"), + + new Item("PrimitiveArrayParameterDemo", "Primitive Array Parameter Demo", + "use primitive array as parameter type"), + + new Item("RenderPhaseMethodExceptionDemo", "Render Phase Method Exception Demo", + "render phase methods may throw checked exceptions"), + + new Item("TrackEditor", "Generic Page Class Demo", + "demo use of generics with component classes and, particularily, with property types"), + + new Item("IndirectProtectedFields", "Protected Fields Demo", + "demo exception when component class contains protected fields"), + + new Item("injectcomponentdemo", "Inject Component Demo", "inject component defined in template"), + + new Item("cachedpage", "Cached Annotation", "Caching method return values"), + + new Item("cachedpage2", "Cached Annotation2", "Caching method return values w/ inheritence"), + + new Item("inplacegriddemo", "In-Place Grid Demo", "Grid that updates in-place using Ajax"), + + new Item("methodadvicedemo", "Method Advice Demo", "Advising component methods."), + + new Item("HasBodyDemo", "Has Body Demo", "Verify the hasBody() method of ComponentResources"), + + new Item("BeanEditorBeanEditContext", "BeanEditor BeanEditContext", + "BeanEditContext is pushed into enviroment by BeanEditor."), + + new Item("InformalParametersDemo", "Informal Parameters Demo", + "Access to informal parameters names and values"), + + new Item("FormFieldOutsideForm", "Form Field Outside Form", + "Nice exception message for common problem of form fields outside forms"), + + new Item("SubmitWithContext", "Submit With Context", "Providing a context for Submit component"), + + new Item("MessageConstraintGeneratorDemo", "Validation Constraints From Messages", + "Providing validators to apply from a properties file"), + + new Item("RenderClientIdDemo", "RenderClientId Mixin", + "Force render of client-side id of a client element via the RenderClientId mixin"), + + new Item("BindParameterDemo", "BindParameter mixin annotation", + "Accessing component parameter values from a mixin"), + + new Item("BindParameterNoSuchParameter", "BindParameter error handling", + "BindParameter throws exception if the containing component doesn't have a matching parameter"), + + new Item("BindParameterOnComponent", "BindParameter on component", + "Verify that BindParameter can only be used on mixin fields"), + + new Item("MixinOrderingDemo", "Mixin Ordering Demo", "Various mixin-ordering scenarios"), + + new Item( + "MissingComponentClassException", + "Missing Component Class Exception", + "Meaningful exception message thrown when component class can't be determined from template or field in containing component."), + + new Item("SessionAttributeDemo", "SessionAttribute Demo", + "Annotation to map a field to a specific session attribute"), + + new Item("BeanEditCalendarDemo", "BeanEditor / Calendar Demo", + "Use of calendar properties inside BeanEditor and BeanDisplay"), + + new Item("TriggerDemo", "Trigger Demo", "Use of Trigger component"), + + new Item("ImageSubmitDemo", "Submit with an Image Demo", + "Make sure that submit with the image parameter set triggers the 'selected' event."), + + new Item("SelectZoneDemo", "Select Zone Demo", "Use a Select component to update a zone."), + + new Item("AssetProtectionDemo", "Asset Protection Demo", + "AssetProtectionDispatcher is properly contributed and functioning"), + + new Item("BeanDisplayEnumDemo", "BeanDisplay Enum Demo", + "User represenation of enum values is correctly read from messages"), + + new Item("unavailablecomponentdemo", "Report Location of Unavailable Component", + "Report Location of Unavailable Component"), + + new Item("discardafterdemo", "@DiscardAfter Demo", "Demo using @DiscardAfter annotation"), + + new Item("SelectDemo", "Select Demo", "Validation decoration for Select"), + + new Item("SelectModelFromObjectsAndPropertyNameDemo", "SelectModel from objects and property name", + "Creating a SelectModel from a list of objects and a label property name"), + + new Item("SelectModelFromObjectsDemo", "SelectModel from objects", + "Creating a SelectModel from a list of objects"), + + new Item("SelectModelCoercionDemo", "SelectModel coercion", + "Creating a SelectModel from a list of objects using coercion"), + + new Item("DecoratePageRenderLinkDemo", "Decorate Page Render Link Demo", + "Decorating page render links"), + + new Item("DecorateComponentEventLinkDemo", "Decorate Component Event Link Demo", + "Decorating event links"), + + new Item("ValidatorMacroDemo", "Validator Macro Demo", "Using validator macros"), + + new Item("AtInjectDemo", "@jakarta.inject.Inject Demo", "Using @jakarta.inject.Inject for injection"), + + new Item("LinkQueryParameters", "Link Query Parameters Demo", + "Providing Query Parameters directly to link components as a map of key=parameter name, value=parameter values"), + + new Item("ChecklistDemo", "Checklist Demo", "Use Checklist component"), + + new Item("BeanEditFormPrepareBubbling", "BeanEditor Prepare Bubbling Demo", "Prepare event bubbling"), + + new Item("NestedFormFragment", "Nested Form Fragment Demo", "Nesting Form Fragments work properly"), + + new Item("MapExpressionInExpansions", "Map Expressions in Expansions Demo", "Maps can be used in expansions"), + + new Item("ExpressionInJsFunction", "Expressions in JS Functions Demo", "Expressions can be used inside javascript functions"), + + new Item("FormFieldFocusDemo", "FormFieldFocus (DEPRECATED) Demo", "Setting the Form focus on a specific field"), + + new Item("FormFragmentExplicitVisibleBoundsDemo", "Form Fragment Explicit Visible Bounds Demo", "Check for form fragment parent visibility can be bounded to"), + + new Item("OverrideFieldFocusDemo", "OverrideFieldFocus Demo", "Setting the focus in a form to a specific field"), + + new Item("OverrideLabelClassDemo", "Override Label Class Demo", "Setting class attribute on Label component"), + + new Item("FormLinkParameters", "FormLinkParameters Demo", "Form link parameters should be unescaped for a hidden field"), + + new Item("KnownActivationContextDemo", "Known Activation Context Demo", "Page is displayed normally if called without context (TAP5-2070)", + "Exact"), + + new Item("UnknownActivationContextDemo", "Unknown Activation Context Demo", "Page refuse to serve if called with an unknown activation context (TAP5-2070)", + "Unwanted", "context"), + + new Item("ModuleConfigurationCallbackDemo", "ModuleConfigurationCallback Demo", "Shows an example of changing the Require.js configuration using JavaScriptSupport.addModuleConfigurationDemo()"), + + new Item("PartialTemplateRendererDemo", "PartialTemplateRenderer Demo", "Shows some examples of rendering blocks and components to a String using PartialTemplateRenderer"), + + new Item("nested/PageThatThrowsException", "Reload on nested page", "Tests a page reload from a nested page's exception report"), + + new Item("inplacegridinloopdemo", "In-Place Grid in a Loop Demo", "In-place grid in a loop"), + + new Item("GenericTypeDemo", "Generic bound type demo", "Tests that generic type info is available for generic bindings"), + + new Item("FormFieldClientIdParameterDemo", "Form Field clientId Parameter Demo", "Shows and tests how to explicitly set the id of a form field component"), + + new Item("gridwithsubmitwithcontextdemo", "Grid with Submit with context", "A grid whose rows contain a Submit component with context"), + + new Item("textfieldwithnullvalidateparameter", "TextField with null validate parameter", "A TextField whose validate parameter is bound to null"), + + new Item("validateCheckboxMustBeChecked", "Validate Checkbox Must Be Checked", "A form that trigger validate in " + + "error event on submit when checkbox is not checked"), + + new Item("validateCheckboxMustBeUnchecked", "Validate Checkbox Must Be Unchecked", "A form that trigger validate in " + + "error event on submit when checkbox is checked"), + + new Item("validateInErrorEvent", "Validate in error Event", "A form that trigger validate in " + + "error event on submit when textfield is empty"), + + new Item("onactivateredirect", "OnActivateRedirect Demo", "A page that redirects to itself from" + + " its activation method"), + + new Item("BeanEditorWithFormFragmentDemo", "Bean Editor With Form Fragment Demo", "TriggerFragment mixin used inside a BeanEditor"), + + new Item("ObjectEditorDemo","Object Editor Demo","Edit Bean with address objects"), + + new Item("IfDemo","If Demo","If component with all its options"), + + new Item("RecursiveDemo","Recursive Demo","Recursive component example"), + + new Item("SelfRecursiveDemo", "Self-Recursive Demo", "check for handling of self-recursive components"), + + new Item("EsModuleDemo", "ES Module Demo", "tests and demonstrations for the ES module support") ); static diff --git a/tapestry-core/src/test/resources/META-INF/assets/es-modules/app/test-support.js b/tapestry-core/src/test/resources/META-INF/assets/es-modules/app/test-support.js new file mode 100644 index 000000000..7728ef0c1 --- /dev/null +++ b/tapestry-core/src/test/resources/META-INF/assets/es-modules/app/test-support.js @@ -0,0 +1,14 @@ +// Provide test support functions that can be addressed via Selenium. + +// TODO: Maybe move this to main, for external re-use? + +import dom from "t5/core/dom"; + +const exports = { + findCSSMatchCount(selector) { return dom.body.find(selector).length; }, + doesNotExist(elementId) { return (dom(elementId)) === null; } +}; + +window.testSupport = exports; + +export default exports;