diff --git a/changes.xml b/changes.xml index 81895bb..902cfb4 100644 --- a/changes.xml +++ b/changes.xml @@ -30,6 +30,9 @@ All HTML attributes for JS and CSS includes are now rendered in alphabetically order. + + Support custom attributes for CSS and JS includes. + Switch to Java 11 as minimum version. diff --git a/src/main/java/io/wcm/wcm/ui/clientlibs/components/CSSInclude.java b/src/main/java/io/wcm/wcm/ui/clientlibs/components/CSSInclude.java index f93ab64..57ea21f 100644 --- a/src/main/java/io/wcm/wcm/ui/clientlibs/components/CSSInclude.java +++ b/src/main/java/io/wcm/wcm/ui/clientlibs/components/CSSInclude.java @@ -62,19 +62,22 @@ public class CSSInclude { private Object categories; @RequestAttribute(injectionStrategy = InjectionStrategy.OPTIONAL) private String rel; + @RequestAttribute(injectionStrategy = InjectionStrategy.OPTIONAL) + private Object customAttributes; private String include; @PostConstruct private void activate() { // build include string - String[] categoryArray = IncludeUtil.toCategoryArray(categories); + String[] categoryArray = IncludeUtil.toArray(categories); if (categoryArray != null) { List libraryPaths = IncludeUtil.getLibraryUrls(htmlLibraryManager, resourceResolver, categoryArray, LibraryType.CSS); if (!libraryPaths.isEmpty()) { Map attrs = validateAndBuildAttributes(); - this.include = buildIncludeString(libraryPaths, attrs); + Map customAttrs = IncludeUtil.getCustomAttributes(customAttributes); + this.include = buildIncludeString(libraryPaths, attrs, customAttrs); } } } @@ -100,13 +103,17 @@ private void activate() { /** * Build CSS link tags for all client libraries with the defined custom script tag attributes set. * @param libraryPaths Library paths + * @param attrs HTML attributes for link tag + * @param customAttrs Custom HTML attributes for script tag * @return HTML markup with script tags */ - private @NotNull String buildIncludeString(@NotNull List libraryPaths, @NotNull Map attrs) { + private @NotNull String buildIncludeString(@NotNull List libraryPaths, @NotNull Map attrs, + @NotNull Map customAttrs) { StringBuilder markup = new StringBuilder(); for (String libraryPath : libraryPaths) { HtmlTagBuilder builder = new HtmlTagBuilder("link", false, xssApi); builder.setAttrs(attrs); + builder.setAttrs(customAttrs); builder.setAttr("href", libraryPath); markup.append(builder.build()); } diff --git a/src/main/java/io/wcm/wcm/ui/clientlibs/components/IncludeUtil.java b/src/main/java/io/wcm/wcm/ui/clientlibs/components/IncludeUtil.java index ff145a7..02bcf3e 100644 --- a/src/main/java/io/wcm/wcm/ui/clientlibs/components/IncludeUtil.java +++ b/src/main/java/io/wcm/wcm/ui/clientlibs/components/IncludeUtil.java @@ -20,7 +20,10 @@ package io.wcm.wcm.ui.clientlibs.components; import java.lang.reflect.Array; +import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; import java.util.Objects; import java.util.stream.Collectors; @@ -44,7 +47,7 @@ private IncludeUtil() { /** * @return Array of clientlib category names as specified in HTL script */ - public static @Nullable String[] toCategoryArray(Object categories) { + public static @Nullable String[] toArray(Object categories) { String[] categoryArray = null; if (categories instanceof String) { categoryArray = new String[] { (String)categories }; @@ -100,4 +103,34 @@ else if (resourceResolver.getResource(library.getPath()) == null) { return path; } + /** + * Transform list of custom attributes to a map. + * @param customAttributes List of custom attributes in syntax "attr=value" for each item. + * @return Map with custom attributes + */ + public static @NotNull Map getCustomAttributes(@Nullable Object customAttributes) { + String[] customAttributesArray = toArray(customAttributes); + if (customAttributesArray == null) { + return Collections.emptyMap(); + } + Map result = new HashMap<>(); + for (String item : customAttributesArray) { + if (item != null) { + int separator = item.indexOf('='); + String name; + String value; + if (separator > 0) { + name = item.substring(0, separator); + value = item.substring(separator + 1); + } + else { + name = item; + value = null; + } + result.put(name, value); + } + } + return result; + } + } diff --git a/src/main/java/io/wcm/wcm/ui/clientlibs/components/JSInclude.java b/src/main/java/io/wcm/wcm/ui/clientlibs/components/JSInclude.java index 035cf73..6e33197 100644 --- a/src/main/java/io/wcm/wcm/ui/clientlibs/components/JSInclude.java +++ b/src/main/java/io/wcm/wcm/ui/clientlibs/components/JSInclude.java @@ -82,19 +82,22 @@ public class JSInclude { private String referrerpolicy; @RequestAttribute(injectionStrategy = InjectionStrategy.OPTIONAL) private String type; + @RequestAttribute(injectionStrategy = InjectionStrategy.OPTIONAL) + private Object customAttributes; private String include; @PostConstruct private void activate() { // build include string - String[] categoryArray = IncludeUtil.toCategoryArray(categories); + String[] categoryArray = IncludeUtil.toArray(categories); if (categoryArray != null) { List libraryPaths = IncludeUtil.getLibraryUrls(htmlLibraryManager, resourceResolver, categoryArray, LibraryType.JS); if (!libraryPaths.isEmpty()) { Map attrs = validateAndBuildAttributes(); - this.include = buildIncludeString(libraryPaths, attrs); + Map customAttrs = IncludeUtil.getCustomAttributes(customAttributes); + this.include = buildIncludeString(libraryPaths, attrs, customAttrs); } } } @@ -137,13 +140,16 @@ private void activate() { * Build script tags for all client libraries with the defined custom script tag attributes set. * @param libraryPaths Library paths * @param attrs HTML attributes for script tag + * @param customAttrs Custom HTML attributes for script tag * @return HTML markup with script tags */ - private @NotNull String buildIncludeString(@NotNull List libraryPaths, @NotNull Map attrs) { + private @NotNull String buildIncludeString(@NotNull List libraryPaths, @NotNull Map attrs, + @NotNull Map customAttrs) { StringBuilder markup = new StringBuilder(); for (String libraryPath : libraryPaths) { HtmlTagBuilder builder = new HtmlTagBuilder("script", true, xssApi); builder.setAttrs(attrs); + builder.setAttrs(customAttrs); builder.setAttr("src", libraryPath); markup.append(builder.build()); } diff --git a/src/main/webapp/app-root/sightly/templates/clientlib.html b/src/main/webapp/app-root/sightly/templates/clientlib.html index 3241284..b412248 100644 --- a/src/main/webapp/app-root/sightly/templates/clientlib.html +++ b/src/main/webapp/app-root/sightly/templates/clientlib.html @@ -9,13 +9,14 @@ * @param nonce see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-nonce * @param referrerpolicy see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-referrerpolicy * @param type see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-type + * @param customAttributes List of custom attributes, each list item in syntax 'attr=value' or just 'attr' */--> @@ -24,11 +25,12 @@ * Template used for including CSS client libraries. * @param categories Client Library categories * @param rel prefetch|preload see https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel + * @param customAttributes List of custom attributes, each list item in syntax 'attr=value' or just 'attr' */--> -