Skip to content

Instantly share code, notes, and snippets.

@dlh3
Created April 7, 2020 22:35
Show Gist options
  • Save dlh3/248aa56ba7301f65e29f435c7cb03de0 to your computer and use it in GitHub Desktop.
Save dlh3/248aa56ba7301f65e29f435c7cb03de0 to your computer and use it in GitHub Desktop.
AEM RequestAttributesHelper for passing parameters to HTL resource and script includes

Request Attributes Helper

A use-class for passing and accessing request attributes between HTL script and resource includes.

Traditionally, additional parameters couldn't be passed to script (data-sly-include) or resource (data-sly-resource) includes in HTL.
In 6.3, AEM introduced the ability to add requestAttributes, provided by a use-class.

This implementation combines the setting of requestAttributes (with a specified prefix, or default "default_") with a map-interface for retrieving any request attributes.

Usage

Setting Attributes

The attributes are specified in the data-sly-use expression, passing the arguments we want to add. In order to add the provided arguments as requestAttributes, we invoke the save() method in the data-sly-include or data-sly-resource. The use-class will not automatically save the provided arguments unless save() is invoked.

<sly data-sly-use.attributes="${'com.example.use.RequestAttributesHelper' @ default_text='This is a title'}"
     data-sly-resource="${'heading' @ requestAttributes=attributes.save} />

The default prefix for arguments to be added as request attributes is "default_", but the prefix can be specified with parameter prefix. Any other arguments which don't start with the prefix will be ignored.

<sly data-sly-use.attributes="${'com.example.use.RequestAttributesHelper' @ prefix='_', _text='This is a title'}" 
     data-sly-resource="${'heading' @ requestAttributes=attributes.save} />

If you want to add all bindings (that the use-class was initialized with), you can specify an empty prefix.

Note: the bindings will contain a lot more entries than just your provided arguments, so this is generally not something you want to do. This ability exists as a helper during development.

<sly data-sly-use.attributes="${'com.example.use.RequestAttributesHelper' @ prefix='', heading_text='This is a title'}" 
     data-sly-resource="${'heading' @ requestAttributes=attributes.save}/>

Usage for data-sly-include is identical.

<sly data-sly-use.attributes="${'com.example.use.RequestAttributesHelper' @ default_text='This is a title'}"
     data-sly-include="${'heading.html' @ requestAttributes=attributes.save} />

Accessing Attributes

All request attributes can be accessed with this use-class. It implements Map, so the attributes can be accessed directly from the use-object.

Note: this use-class is not a real Map implementation and should not be expected to work in that way. It only implements get() and keySet(), to support property access in HTL.

<h1 data-sly-use.attributes="com.example.use.RequestAttributesHelper">${properties.text || attributes.default_text}</h1>

Any request attribute can be accessed through this helper. This is provided as a utility, and is not necessarily the recommended approach.

<p data-sly-use.attributes="com.example.use.RequestAttributesHelper">WCM mode: ${attributes.wcmmode}</p>
<img data-sly-test.src="${properties.fileReference || (editContext && '/libs/cq/ui/widgets/themes/default/placeholders/img.png')}"
data-sly-use.attributes="com.example.use.RequestAttributesHelper"
width="${properties.width || attributes.default_width}"
height="${properties.height || attributes.default_height}"
src="${src}"
alt="${properties.alt || properties.jcr:title}"
title="${properties.jcr:title}"
class="cq-dd-image"/>
<!--/* This will work with data-sly-resource */-->
<div class="image"
data-sly-use.attributes="${'com.example.use.RequestAttributesHelper' @ default_width=80, default_height=120}"
data-sly-resource="${'image' @ requestAttributes=attributes.save}">
</div>
<!--/* But also works with data-sly-include */-->
<div class="image"
data-sly-use.attributes="${'com.example.use.RequestAttributesHelper' @ default_width=80, default_height=120}"
data-sly-include="${'image.html' @ requestAttributes=attributes.save}">
</div>
package com.example.use;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Iterators;
import org.apache.sling.scripting.sightly.pojo.Use;
import javax.script.Bindings;
import javax.servlet.ServletRequest;
import java.util.HashMap;
import java.util.Map;
import java.util.Set;
import java.util.stream.Collectors;
/**
* Request Attributes Helper, for passing and accessing request attributes between HTL script and resource includes.
*
* Traditionally, additional parameters couldn't be passed to script (data-sly-include) or resource (data-sly-resource)
* includes in HTL. In 6.3, AEM introduced the ability to add requestAttributes, provided by a use-class. This
* implementation combines the setting of requestAttributes (with a specified prefix, or default "default_") with a
* map-interface for retrieving any request attributes.
*
* Usage, setting attributes:
* {@code
* <sly data-sly-use.attributes="${'com.example.use.RequestAttributesHelper' @
* prefix='x_', x_width=400, x_height=300}"
* data-sly-resource="${'subcomponent' @ requestAttributes=attributes.save}" />
* }
*
* Usage, reading attributes (in the subresource or included script):
* {@code
* <img data-sly-use.attributes="com.example.use.RequestAttributesHelper"
* width="${attributes.x_width}" height="${attributes.x_height}" />
* }
*/
public class RequestAttributesHelper extends HashMap<String, Object> implements Use {
private static final String PN_PREFIX = "prefix";
private static final String PN_DEFAULT_PREFIX = "default_";
private Bindings bindings;
@Override
public void init(Bindings bindings) {
this.bindings = bindings;
}
public Map<String, Object> save() {
String prefix = (String) bindings.getOrDefault(PN_PREFIX, PN_DEFAULT_PREFIX);
return bindings.entrySet()
.stream()
.filter(entry -> entry.getKey().startsWith(prefix))
.filter(entry -> entry.getValue() != null)
.collect(Collectors.toMap(Map.Entry::getKey, Map.Entry::getValue));
}
@Override
public Object get(Object key) {
return getRequest().getAttribute(key.toString());
}
@Override
public Set<String> keySet() {
// implemented to support HTL `in` relational operator
return ImmutableSet.copyOf(Iterators.forEnumeration(getRequest().getAttributeNames()));
}
// TODO: though not necessary, Map's values(), entrySet(), and containsKey() could be easily added
private ServletRequest getRequest() {
return ((ServletRequest) bindings.get("request"));
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment