Skip to content

Commit c008d4c

Browse files
Support custom attributes for CSS and JS includes (#2)
1 parent 808db05 commit c008d4c

File tree

8 files changed

+79
-19
lines changed

8 files changed

+79
-19
lines changed

changes.xml

+3
Original file line numberDiff line numberDiff line change
@@ -30,6 +30,9 @@
3030
<action type="add" dev="sseifert" issue="3">
3131
All HTML attributes for JS and CSS includes are now rendered in alphabetically order.
3232
</action>
33+
<action type="add" dev="sseifert" issue="1">
34+
Support custom attributes for CSS and JS includes.
35+
</action>
3336
<action type="update" dev="sseifert">
3437
Switch to Java 11 as minimum version.
3538
</action>

src/main/java/io/wcm/wcm/ui/clientlibs/components/CSSInclude.java

+10-3
Original file line numberDiff line numberDiff line change
@@ -62,19 +62,22 @@ public class CSSInclude {
6262
private Object categories;
6363
@RequestAttribute(injectionStrategy = InjectionStrategy.OPTIONAL)
6464
private String rel;
65+
@RequestAttribute(injectionStrategy = InjectionStrategy.OPTIONAL)
66+
private Object customAttributes;
6567

6668
private String include;
6769

6870
@PostConstruct
6971
private void activate() {
7072
// build include string
71-
String[] categoryArray = IncludeUtil.toCategoryArray(categories);
73+
String[] categoryArray = IncludeUtil.toArray(categories);
7274
if (categoryArray != null) {
7375
List<String> libraryPaths = IncludeUtil.getLibraryUrls(htmlLibraryManager, resourceResolver,
7476
categoryArray, LibraryType.CSS);
7577
if (!libraryPaths.isEmpty()) {
7678
Map<String, String> attrs = validateAndBuildAttributes();
77-
this.include = buildIncludeString(libraryPaths, attrs);
79+
Map<String, String> customAttrs = IncludeUtil.getCustomAttributes(customAttributes);
80+
this.include = buildIncludeString(libraryPaths, attrs, customAttrs);
7881
}
7982
}
8083
}
@@ -100,13 +103,17 @@ private void activate() {
100103
/**
101104
* Build CSS link tags for all client libraries with the defined custom script tag attributes set.
102105
* @param libraryPaths Library paths
106+
* @param attrs HTML attributes for link tag
107+
* @param customAttrs Custom HTML attributes for script tag
103108
* @return HTML markup with script tags
104109
*/
105-
private @NotNull String buildIncludeString(@NotNull List<String> libraryPaths, @NotNull Map<String, String> attrs) {
110+
private @NotNull String buildIncludeString(@NotNull List<String> libraryPaths, @NotNull Map<String, String> attrs,
111+
@NotNull Map<String, String> customAttrs) {
106112
StringBuilder markup = new StringBuilder();
107113
for (String libraryPath : libraryPaths) {
108114
HtmlTagBuilder builder = new HtmlTagBuilder("link", false, xssApi);
109115
builder.setAttrs(attrs);
116+
builder.setAttrs(customAttrs);
110117
builder.setAttr("href", libraryPath);
111118
markup.append(builder.build());
112119
}

src/main/java/io/wcm/wcm/ui/clientlibs/components/IncludeUtil.java

+34-1
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,10 @@
2020
package io.wcm.wcm.ui.clientlibs.components;
2121

2222
import java.lang.reflect.Array;
23+
import java.util.Collections;
24+
import java.util.HashMap;
2325
import java.util.List;
26+
import java.util.Map;
2427
import java.util.Objects;
2528
import java.util.stream.Collectors;
2629

@@ -44,7 +47,7 @@ private IncludeUtil() {
4447
/**
4548
* @return Array of clientlib category names as specified in HTL script
4649
*/
47-
public static @Nullable String[] toCategoryArray(Object categories) {
50+
public static @Nullable String[] toArray(Object categories) {
4851
String[] categoryArray = null;
4952
if (categories instanceof String) {
5053
categoryArray = new String[] { (String)categories };
@@ -100,4 +103,34 @@ else if (resourceResolver.getResource(library.getPath()) == null) {
100103
return path;
101104
}
102105

106+
/**
107+
* Transform list of custom attributes to a map.
108+
* @param customAttributes List of custom attributes in syntax "attr=value" for each item.
109+
* @return Map with custom attributes
110+
*/
111+
public static @NotNull Map<String, String> getCustomAttributes(@Nullable Object customAttributes) {
112+
String[] customAttributesArray = toArray(customAttributes);
113+
if (customAttributesArray == null) {
114+
return Collections.emptyMap();
115+
}
116+
Map<String, String> result = new HashMap<>();
117+
for (String item : customAttributesArray) {
118+
if (item != null) {
119+
int separator = item.indexOf('=');
120+
String name;
121+
String value;
122+
if (separator > 0) {
123+
name = item.substring(0, separator);
124+
value = item.substring(separator + 1);
125+
}
126+
else {
127+
name = item;
128+
value = null;
129+
}
130+
result.put(name, value);
131+
}
132+
}
133+
return result;
134+
}
135+
103136
}

src/main/java/io/wcm/wcm/ui/clientlibs/components/JSInclude.java

+9-3
Original file line numberDiff line numberDiff line change
@@ -82,19 +82,22 @@ public class JSInclude {
8282
private String referrerpolicy;
8383
@RequestAttribute(injectionStrategy = InjectionStrategy.OPTIONAL)
8484
private String type;
85+
@RequestAttribute(injectionStrategy = InjectionStrategy.OPTIONAL)
86+
private Object customAttributes;
8587

8688
private String include;
8789

8890
@PostConstruct
8991
private void activate() {
9092
// build include string
91-
String[] categoryArray = IncludeUtil.toCategoryArray(categories);
93+
String[] categoryArray = IncludeUtil.toArray(categories);
9294
if (categoryArray != null) {
9395
List<String> libraryPaths = IncludeUtil.getLibraryUrls(htmlLibraryManager, resourceResolver,
9496
categoryArray, LibraryType.JS);
9597
if (!libraryPaths.isEmpty()) {
9698
Map<String, String> attrs = validateAndBuildAttributes();
97-
this.include = buildIncludeString(libraryPaths, attrs);
99+
Map<String, String> customAttrs = IncludeUtil.getCustomAttributes(customAttributes);
100+
this.include = buildIncludeString(libraryPaths, attrs, customAttrs);
98101
}
99102
}
100103
}
@@ -137,13 +140,16 @@ private void activate() {
137140
* Build script tags for all client libraries with the defined custom script tag attributes set.
138141
* @param libraryPaths Library paths
139142
* @param attrs HTML attributes for script tag
143+
* @param customAttrs Custom HTML attributes for script tag
140144
* @return HTML markup with script tags
141145
*/
142-
private @NotNull String buildIncludeString(@NotNull List<String> libraryPaths, @NotNull Map<String, String> attrs) {
146+
private @NotNull String buildIncludeString(@NotNull List<String> libraryPaths, @NotNull Map<String, String> attrs,
147+
@NotNull Map<String, String> customAttrs) {
143148
StringBuilder markup = new StringBuilder();
144149
for (String libraryPath : libraryPaths) {
145150
HtmlTagBuilder builder = new HtmlTagBuilder("script", true, xssApi);
146151
builder.setAttrs(attrs);
152+
builder.setAttrs(customAttrs);
147153
builder.setAttr("src", libraryPath);
148154
markup.append(builder.build());
149155
}

src/main/webapp/app-root/sightly/templates/clientlib.html

+6-4
Original file line numberDiff line numberDiff line change
@@ -9,13 +9,14 @@
99
* @param nonce see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-nonce
1010
* @param referrerpolicy see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-referrerpolicy
1111
* @param type see https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#attr-type
12+
* @param customAttributes List of custom attributes, each list item in syntax 'attr=value' or just 'attr'
1213
*/-->
1314
<template data-sly-template.js="${@ categories, async, crossorigin, defer, integrity,
14-
nomodule, nonce, referrerpolicy, type}">
15+
nomodule, nonce, referrerpolicy, type, customAttributes}">
1516
<sly data-sly-test="${request.getResourceResolver}"
1617
data-sly-use.clientlib="${'io.wcm.wcm.ui.clientlibs.components.JSInclude' @
1718
categories=categories, async=async, crossorigin=crossorigin, defer=defer, integrity=integrity,
18-
nomodule=nomodule, nonce=nonce, referrerpolicy=referrerpolicy, type=type}">
19+
nomodule=nomodule, nonce=nonce, referrerpolicy=referrerpolicy, type=type, customAttributes=customAttributes}">
1920
${clientlib.include @ context='unsafe'}
2021
</sly>
2122
</template>
@@ -24,11 +25,12 @@
2425
* Template used for including CSS client libraries.
2526
* @param categories Client Library categories
2627
* @param rel prefetch|preload see https://developer.mozilla.org/en-US/docs/Web/HTML/Attributes/rel
28+
* @param customAttributes List of custom attributes, each list item in syntax 'attr=value' or just 'attr'
2729
*/-->
28-
<template data-sly-template.css="${@ categories, rel}">
30+
<template data-sly-template.css="${@ categories, customAttributes}">
2931
<sly data-sly-test="${request.getResourceResolver}"
3032
data-sly-use.clientlib="${'io.wcm.wcm.ui.clientlibs.components.CSSInclude' @
31-
categories=categories, rel=rel}">
33+
categories=categories, rel=rel, customAttributes=customAttributes}">
3234
${clientlib.include @ context='unsafe'}
3335
</sly>
3436
</template>

src/site/markdown/usage.md

+3
Original file line numberDiff line numberDiff line change
@@ -27,9 +27,11 @@ The following advanced script tag attributes are supported:
2727
* `nonce` = {string}
2828
* `referrerpolicy` = no-referrer | no-referrer-when-downgrade | origin | origin-when-cross-origin | same-origin | strict-origin | strict-origin-when-cross-origin | unsafe-url
2929
* `type` = module | text/javascript
30+
* `customAttributes` - set arbitrary HTML attributes, e.g. `customAttributes=['attr1=value 1','data-attr2=5','attr3']`
3031

3132
See https://developer.mozilla.org/en-US/docs/Web/HTML/Element/script#Attributes for a full documentation of this attributes.
3233

34+
3335
### Include CSS Files
3436

3537
Include CSS without special attributes:
@@ -42,3 +44,4 @@ Include CSS without special attributes:
4244
The following advanced link tag attributes are supported:
4345

4446
* `rel` = prefetch | preload (if not given, `rel="stylesheet" type="text/css"` is set)
47+
* `customAttributes` - set arbitrary HTML attributes, e.g. `customAttributes=['attr1=value 1','data-attr2=5','attr3']`

src/test/java/io/wcm/wcm/ui/clientlibs/components/CSSIncludeTest.java

+5-4
Original file line numberDiff line numberDiff line change
@@ -85,12 +85,13 @@ void testSingleProxy() {
8585
}
8686

8787
@Test
88-
void testMulti() {
88+
void testMultiWithCustom() {
8989
context.request().setAttribute("categories", CATEGORIES_MULTIPLE);
90+
context.request().setAttribute("customAttributes", new String[] { "attr1=value1", "data-attr2=5", "attr3" });
9091
CSSInclude underTest = AdaptTo.notNull(context.request(), CSSInclude.class);
91-
assertEquals("<link href=\"/etc/clientlibs/app1/clientlib3.min.css\" rel=\"stylesheet\" type=\"text/css\">\n"
92-
+ "<link href=\"/etc.clientlibs/app1/clientlibs/clientlib4_proxy.min.css\" rel=\"stylesheet\" type=\"text/css\">\n"
93-
+ "<link href=\"/etc.clientlibs/app1/clientlibs/clientlib5_proxy.min.css\" rel=\"stylesheet\" type=\"text/css\">\n",
92+
assertEquals("<link attr1=\"value1\" attr3 data-attr2=\"5\" href=\"/etc/clientlibs/app1/clientlib3.min.css\" rel=\"stylesheet\" type=\"text/css\">\n"
93+
+ "<link attr1=\"value1\" attr3 data-attr2=\"5\" href=\"/etc.clientlibs/app1/clientlibs/clientlib4_proxy.min.css\" rel=\"stylesheet\" type=\"text/css\">\n"
94+
+ "<link attr1=\"value1\" attr3 data-attr2=\"5\" href=\"/etc.clientlibs/app1/clientlibs/clientlib5_proxy.min.css\" rel=\"stylesheet\" type=\"text/css\">\n",
9495
underTest.getInclude());
9596
}
9697

src/test/java/io/wcm/wcm/ui/clientlibs/components/JSIncludeTest.java

+9-4
Original file line numberDiff line numberDiff line change
@@ -127,15 +127,20 @@ void testSingleInvalidAttributes() {
127127
}
128128

129129
@Test
130-
void testMultiAttributes() {
130+
void testMultiAttributesWithCustom() {
131131
context.request().setAttribute("categories", CATEGORIES_MULTIPLE);
132132
context.request().setAttribute("async", true);
133133
context.request().setAttribute("nomodule", true);
134134
context.request().setAttribute("type", "text/javascript");
135+
context.request().setAttribute("customAttributes", new String[] { "attr1=value1", "data-attr2=5", "attr3" });
135136
JSInclude underTest = AdaptTo.notNull(context.request(), JSInclude.class);
136-
assertEquals("<script async nomodule src=\"/etc/clientlibs/app1/clientlib3.min.js\" type=\"text/javascript\"></script>\n"
137-
+ "<script async nomodule src=\"/etc.clientlibs/app1/clientlibs/clientlib4_proxy.min.js\" type=\"text/javascript\"></script>\n"
138-
+ "<script async nomodule src=\"/etc.clientlibs/app1/clientlibs/clientlib5_proxy.min.js\" type=\"text/javascript\"></script>\n",
137+
assertEquals(
138+
"<script async attr1=\"value1\" attr3 data-attr2=\"5\" nomodule "
139+
+ "src=\"/etc/clientlibs/app1/clientlib3.min.js\" type=\"text/javascript\"></script>\n"
140+
+ "<script async attr1=\"value1\" attr3 data-attr2=\"5\" nomodule "
141+
+ "src=\"/etc.clientlibs/app1/clientlibs/clientlib4_proxy.min.js\" type=\"text/javascript\"></script>\n"
142+
+ "<script async attr1=\"value1\" attr3 data-attr2=\"5\" nomodule "
143+
+ "src=\"/etc.clientlibs/app1/clientlibs/clientlib5_proxy.min.js\" type=\"text/javascript\"></script>\n",
139144
underTest.getInclude());
140145
}
141146

0 commit comments

Comments
 (0)