Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support custom attributes for CSS and JS includes #2

Merged
merged 8 commits into from
Nov 17, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions changes.xml
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,9 @@
<action type="add" dev="sseifert" issue="3">
All HTML attributes for JS and CSS includes are now rendered in alphabetically order.
</action>
<action type="add" dev="sseifert" issue="1">
Support custom attributes for CSS and JS includes.
</action>
<action type="update" dev="sseifert">
Switch to Java 11 as minimum version.
</action>
Expand Down
13 changes: 10 additions & 3 deletions src/main/java/io/wcm/wcm/ui/clientlibs/components/CSSInclude.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> libraryPaths = IncludeUtil.getLibraryUrls(htmlLibraryManager, resourceResolver,
categoryArray, LibraryType.CSS);
if (!libraryPaths.isEmpty()) {
Map<String, String> attrs = validateAndBuildAttributes();
this.include = buildIncludeString(libraryPaths, attrs);
Map<String, String> customAttrs = IncludeUtil.getCustomAttributes(customAttributes);
this.include = buildIncludeString(libraryPaths, attrs, customAttrs);
}
}
}
Expand All @@ -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<String> libraryPaths, @NotNull Map<String, String> attrs) {
private @NotNull String buildIncludeString(@NotNull List<String> libraryPaths, @NotNull Map<String, String> attrs,
@NotNull Map<String, String> 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());
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;

Expand All @@ -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 };
Expand Down Expand Up @@ -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<String, String> getCustomAttributes(@Nullable Object customAttributes) {
String[] customAttributesArray = toArray(customAttributes);
if (customAttributesArray == null) {
return Collections.emptyMap();
}
Map<String, String> 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;
}

}
12 changes: 9 additions & 3 deletions src/main/java/io/wcm/wcm/ui/clientlibs/components/JSInclude.java
Original file line number Diff line number Diff line change
Expand Up @@ -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<String> libraryPaths = IncludeUtil.getLibraryUrls(htmlLibraryManager, resourceResolver,
categoryArray, LibraryType.JS);
if (!libraryPaths.isEmpty()) {
Map<String, String> attrs = validateAndBuildAttributes();
this.include = buildIncludeString(libraryPaths, attrs);
Map<String, String> customAttrs = IncludeUtil.getCustomAttributes(customAttributes);
this.include = buildIncludeString(libraryPaths, attrs, customAttrs);
}
}
}
Expand Down Expand Up @@ -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<String> libraryPaths, @NotNull Map<String, String> attrs) {
private @NotNull String buildIncludeString(@NotNull List<String> libraryPaths, @NotNull Map<String, String> attrs,
@NotNull Map<String, String> 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());
}
Expand Down
10 changes: 6 additions & 4 deletions src/main/webapp/app-root/sightly/templates/clientlib.html
Original file line number Diff line number Diff line change
Expand Up @@ -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'
*/-->
<template data-sly-template.js="${@ categories, async, crossorigin, defer, integrity,
nomodule, nonce, referrerpolicy, type}">
nomodule, nonce, referrerpolicy, type, customAttributes}">
<sly data-sly-test="${request.getResourceResolver}"
data-sly-use.clientlib="${'io.wcm.wcm.ui.clientlibs.components.JSInclude' @
categories=categories, async=async, crossorigin=crossorigin, defer=defer, integrity=integrity,
nomodule=nomodule, nonce=nonce, referrerpolicy=referrerpolicy, type=type}">
nomodule=nomodule, nonce=nonce, referrerpolicy=referrerpolicy, type=type, customAttributes=customAttributes}">
${clientlib.include @ context='unsafe'}
</sly>
</template>
Expand All @@ -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'
*/-->
<template data-sly-template.css="${@ categories, rel}">
<template data-sly-template.css="${@ categories, customAttributes}">
<sly data-sly-test="${request.getResourceResolver}"
data-sly-use.clientlib="${'io.wcm.wcm.ui.clientlibs.components.CSSInclude' @
categories=categories, rel=rel}">
categories=categories, rel=rel, customAttributes=customAttributes}">
${clientlib.include @ context='unsafe'}
</sly>
</template>
Expand Down
3 changes: 3 additions & 0 deletions src/site/markdown/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -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:
Expand All @@ -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']`
Original file line number Diff line number Diff line change
Expand Up @@ -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("<link href=\"/etc/clientlibs/app1/clientlib3.min.css\" rel=\"stylesheet\" type=\"text/css\">\n"
+ "<link href=\"/etc.clientlibs/app1/clientlibs/clientlib4_proxy.min.css\" rel=\"stylesheet\" type=\"text/css\">\n"
+ "<link href=\"/etc.clientlibs/app1/clientlibs/clientlib5_proxy.min.css\" rel=\"stylesheet\" type=\"text/css\">\n",
assertEquals("<link attr1=\"value1\" attr3 data-attr2=\"5\" href=\"/etc/clientlibs/app1/clientlib3.min.css\" rel=\"stylesheet\" type=\"text/css\">\n"
+ "<link attr1=\"value1\" attr3 data-attr2=\"5\" href=\"/etc.clientlibs/app1/clientlibs/clientlib4_proxy.min.css\" rel=\"stylesheet\" type=\"text/css\">\n"
+ "<link attr1=\"value1\" attr3 data-attr2=\"5\" href=\"/etc.clientlibs/app1/clientlibs/clientlib5_proxy.min.css\" rel=\"stylesheet\" type=\"text/css\">\n",
underTest.getInclude());
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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("<script async nomodule src=\"/etc/clientlibs/app1/clientlib3.min.js\" type=\"text/javascript\"></script>\n"
+ "<script async nomodule src=\"/etc.clientlibs/app1/clientlibs/clientlib4_proxy.min.js\" type=\"text/javascript\"></script>\n"
+ "<script async nomodule src=\"/etc.clientlibs/app1/clientlibs/clientlib5_proxy.min.js\" type=\"text/javascript\"></script>\n",
assertEquals(
"<script async attr1=\"value1\" attr3 data-attr2=\"5\" nomodule "
+ "src=\"/etc/clientlibs/app1/clientlib3.min.js\" type=\"text/javascript\"></script>\n"
+ "<script async attr1=\"value1\" attr3 data-attr2=\"5\" nomodule "
+ "src=\"/etc.clientlibs/app1/clientlibs/clientlib4_proxy.min.js\" type=\"text/javascript\"></script>\n"
+ "<script async attr1=\"value1\" attr3 data-attr2=\"5\" nomodule "
+ "src=\"/etc.clientlibs/app1/clientlibs/clientlib5_proxy.min.js\" type=\"text/javascript\"></script>\n",
underTest.getInclude());
}

Expand Down