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'
*/-->
+ nomodule, nonce, referrerpolicy, type, customAttributes}">
+ nomodule=nomodule, nonce=nonce, referrerpolicy=referrerpolicy, type=type, customAttributes=customAttributes}">
${clientlib.include @ context='unsafe'}
@@ -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'
*/-->
-
+
+ categories=categories, rel=rel, customAttributes=customAttributes}">
${clientlib.include @ context='unsafe'}
diff --git a/src/site/markdown/usage.md b/src/site/markdown/usage.md
index 2a3e88d..8bb4741 100644
--- a/src/site/markdown/usage.md
+++ b/src/site/markdown/usage.md
@@ -27,9 +27,11 @@ The following advanced script tag attributes are supported:
* `nonce` = {string}
* `referrerpolicy` = no-referrer | no-referrer-when-downgrade | origin | origin-when-cross-origin | same-origin | strict-origin | strict-origin-when-cross-origin | unsafe-url
* `type` = module | text/javascript
+* `customAttributes` - set arbitrary HTML attributes, e.g. `customAttributes=['attr1=value 1','data-attr2=5','attr3']`
See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#Attributes for a full documentation of this attributes.
+
### Include CSS Files
Include CSS without special attributes:
@@ -42,3 +44,4 @@ Include CSS without special attributes:
The following advanced link tag attributes are supported:
* `rel` = prefetch | preload (if not given, `rel="stylesheet" type="text/css"` is set)
+* `customAttributes` - set arbitrary HTML attributes, e.g. `customAttributes=['attr1=value 1','data-attr2=5','attr3']`
diff --git a/src/test/java/io/wcm/wcm/ui/clientlibs/components/CSSIncludeTest.java b/src/test/java/io/wcm/wcm/ui/clientlibs/components/CSSIncludeTest.java
index ffce0d4..7e68dd5 100644
--- a/src/test/java/io/wcm/wcm/ui/clientlibs/components/CSSIncludeTest.java
+++ b/src/test/java/io/wcm/wcm/ui/clientlibs/components/CSSIncludeTest.java
@@ -85,12 +85,13 @@ void testSingleProxy() {
}
@Test
- void testMulti() {
+ void testMultiWithCustom() {
context.request().setAttribute("categories", CATEGORIES_MULTIPLE);
+ context.request().setAttribute("customAttributes", new String[] { "attr1=value1", "data-attr2=5", "attr3" });
CSSInclude underTest = AdaptTo.notNull(context.request(), CSSInclude.class);
- assertEquals("\n"
- + "\n"
- + "\n",
+ assertEquals("\n"
+ + "\n"
+ + "\n",
underTest.getInclude());
}
diff --git a/src/test/java/io/wcm/wcm/ui/clientlibs/components/JSIncludeTest.java b/src/test/java/io/wcm/wcm/ui/clientlibs/components/JSIncludeTest.java
index 441db80..8e2230a 100644
--- a/src/test/java/io/wcm/wcm/ui/clientlibs/components/JSIncludeTest.java
+++ b/src/test/java/io/wcm/wcm/ui/clientlibs/components/JSIncludeTest.java
@@ -127,15 +127,20 @@ void testSingleInvalidAttributes() {
}
@Test
- void testMultiAttributes() {
+ void testMultiAttributesWithCustom() {
context.request().setAttribute("categories", CATEGORIES_MULTIPLE);
context.request().setAttribute("async", true);
context.request().setAttribute("nomodule", true);
context.request().setAttribute("type", "text/javascript");
+ context.request().setAttribute("customAttributes", new String[] { "attr1=value1", "data-attr2=5", "attr3" });
JSInclude underTest = AdaptTo.notNull(context.request(), JSInclude.class);
- assertEquals("\n"
- + "\n"
- + "\n",
+ assertEquals(
+ "\n"
+ + "\n"
+ + "\n",
underTest.getInclude());
}