com.adobe.granite.ui.components.ComponentHelper Maven / Gradle / Ivy
Show all versions of aem-sdk-api Show documentation
/*************************************************************************
* ADOBE CONFIDENTIAL
* ___________________
*
* Copyright 2013 Adobe
* All Rights Reserved.
*
* NOTICE: All information contained herein is, and remains
* the property of Adobe and its suppliers, if any. The intellectual
* and technical concepts contained herein are proprietary to Adobe
* and its suppliers and are protected by all applicable intellectual
* property laws, including trade secret and copyright laws.
* Dissemination of this information or reproduction of this material
* is strictly forbidden unless prior written permission is obtained
* from Adobe.
**************************************************************************/
package com.adobe.granite.ui.components;
import java.io.IOException;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
import javax.servlet.ServletException;
import javax.servlet.jsp.PageContext;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.scripting.SlingBindings;
import org.apache.sling.api.scripting.SlingScriptHelper;
import org.apache.sling.scripting.jsp.util.JspSlingHttpServletResponseWrapper;
import com.adobe.granite.ui.components.ds.DataSource;
import com.adobe.granite.ui.components.ds.EmptyDataSource;
import com.adobe.granite.ui.components.impl.BaseComponentHelper;
import com.adobe.granite.ui.components.impl.SlingIncludeObjectFactory;
import com.adobe.granite.ui.components.rendercondition.RenderCondition;
import com.adobe.granite.ui.components.rendercondition.RenderConditionHelper;
import com.adobe.granite.ui.components.rendercondition.SimpleRenderCondition;
import com.adobe.granite.xss.XSSAPI;
import com.day.cq.i18n.I18n;
/**
* A convenient helper for development of Granite UI components that are
* implemented using JSP.
*/
public class ComponentHelper {
private static final String DEFAULT_LAYOUT_RT = "granite/ui/components/foundation/layouts/container";
private final BaseComponentHelper base;
private Config config;
private Value value;
private State state;
private RenderConditionHelper renderConditionHelper;
private SlingIncludeObjectFactory dataFetcher;
public ComponentHelper(@Nonnull PageContext pageContext) {
SlingBindings bindings = (SlingBindings) pageContext.getRequest().getAttribute(SlingBindings.class.getName());
SlingScriptHelper sling = bindings.getSling();
if (sling == null) {
throw new RuntimeException("SlingScriptHelper is not available");
}
SlingHttpServletRequest request = bindings.getRequest();
if (request == null) {
throw new RuntimeException("SlingHttpServletRequest is not available");
}
base = new BaseComponentHelper(sling, request, new JspSlingHttpServletResponseWrapper(pageContext));
}
/**
* Returns I18n appropriate for the current page.
*
* @return I18n appropriate for the current page
*/
@Nonnull
public I18n getI18n() {
return base.getI18n();
}
/**
* Returns XSSAPI based on the current request.
*
* @return XSSAPI based on the current request
*/
@Nonnull
public XSSAPI getXss() {
return base.getXss();
}
/**
* Returns the config of current resource of the page.
*
* @return the config of current resource of the page
*/
@SuppressWarnings("null")
@Nonnull
public Config getConfig() {
if (config == null) {
config = new Config(base.getRequest().getResource());
}
return config;
}
/**
* Returns the values that is applicable for the current page.
*
* @return the values that is applicable for the current page
*/
@SuppressWarnings("null")
@Nonnull
public Value getValue() {
if (value == null) {
value = new Value(base.getRequest(), getConfig());
}
return value;
}
/**
* Returns the ExpressionHelper appropriate for the current page.
*
* @return the ExpressionHelper appropriate for the current page
*/
@Nonnull
public ExpressionHelper getExpressionHelper() {
return base.getExpressionHelper();
}
/**
* Returns the client state.
*
* @return the client state
*/
@SuppressWarnings("null")
@Nonnull
public State getState() {
if (state == null) {
state = new State(base.getRequest());
}
return state;
}
@SuppressWarnings("null")
@Nonnull
private RenderConditionHelper getRenderConditionHelper() {
if (renderConditionHelper == null) {
renderConditionHelper = new RenderConditionHelper(base.getRequest(), base.getResponse());
}
return renderConditionHelper;
}
@SuppressWarnings("null")
@Nonnull
private SlingIncludeObjectFactory getSlingIncludeObjectFactory() {
if (dataFetcher == null) {
dataFetcher = new SlingIncludeObjectFactory(base.getRequest(), base.getResponse());
}
return dataFetcher;
}
/**
* Consumes the current available tag for current page. If the request doesn't
* have the tag applicable to the page, a new tag is created.
*
* There is a mechanism such that a tag can be passed to another page when
* including that page. This method is intended as a way to consume the tag
* passed by other page. Component developer can make use this method to get the
* main tag of the component regardless if there is a tag passed by other page
* or not.
*
* @return the tag
* @see #include(Resource, Tag)
* @see #include(Resource, String, Tag)
*/
@Nonnull
public Tag consumeTag() {
return base.consumeTag();
}
/**
* Consumes the current available layout resource for current page. This method
* first attempts to return the layout resource from the options, second it will
* attempt to return {@link Config#LAYOUT} child node, otherwise {@code null} is
* returned.
*
* This method is usually called by layout implementation to get the layout
* resource that can be passed by the caller.
*
* @return the resource
* @see #includeForLayout(Resource, Options)
* @see #includeForLayout(Resource, Resource, Options)
*/
@CheckForNull
public Resource consumeLayoutResource() {
Resource layout = getOptions().layoutResource();
return layout == null ? base.getRequest().getResource().getChild(Config.LAYOUT) : layout;
}
/**
* The overload of {@link #populateCommonAttrs(AttrBuilder, Resource)} using the
* current request's resource as the source.
*
* @param attrs
* the attribute builder
*/
public void populateCommonAttrs(@Nonnull AttrBuilder attrs) {
base.populateCommonAttrs(attrs);
}
/**
* Populates the common attributes to the given {@link AttrBuilder}.
*
*
* Currently, the following common properties and nodes are read and processed
* from the given resource:
*
*
* The common attributes to the given AttrBuilder
*
*
* Name
* Type
* Description
*
*
*
* granite:id
* Property: StringEL
* The id attribute.
*
*
* granite:rel
* Property: StringEL
* The class attribute. This is used to indicate the semantic relationship
* of the component similar to {@code rel} attribute.
*
*
* granite:class
* Property: StringEL
* The class attribute.
*
*
* granite:title
* Property: String
* The title attribute. This property is i18nable.
*
*
* granite:hidden
* Property: Boolean
* The hidden attribute.
*
*
* granite:itemscope
* Property: Boolean
* The itemscope attribute.
*
*
* granite:itemtype
* Property: String
* The itemtype attribute.
*
*
* granite:itemprop
* Property: String
* The itemprop attribute.
*
*
* granite:data
* Node
* Each property of this node is converted into a data-* attribute. If the
* property value is an instance of a String, it will be interpreted as
* StringEL. The property having a prefixed name is ignored.
*
*
*
*
* @param attrs
* The attribute builder to populate to
* @param src
* The resource of the source of the config
*/
public void populateCommonAttrs(@Nonnull AttrBuilder attrs, @Nonnull Resource src) {
base.populateCommonAttrs(attrs, src);
}
/**
* Returns the options passed from another page. If no options is passed, empty
* options is returned.
*
* There is a mechanism such that options can be passed to another page when
* including that page.
*
* @return the options passed from another page. if no options is passed, empty
* options is returned.
* @see #include(Resource, Options)
* @see #include(Resource, String, Options)
*/
@Nonnull
public Options getOptions() {
com.adobe.granite.ui.components.Options baseOptions = base.getOptions();
if (Options.class.isAssignableFrom(baseOptions.getClass())) {
return (Options) baseOptions;
}
return new Options().tag(baseOptions.tag()).rootField(baseOptions.rootField());
}
/**
* Returns the layout config of current resource of the page. This method is
* setting the default resource type of the layout.
*
* @return the layout config of current resource of the page
* @see LayoutBuilder#getResourceType()
*/
@Nonnull
public LayoutBuilder getLayout() {
return LayoutBuilder.from(consumeLayoutResource(), DEFAULT_LAYOUT_RT);
}
/**
* Returns the associated resource type of current resource for the purpose
* rendering read only version. First the granite:readOnlyResourceType property
* of the resource type of the current resource (the RT) is used. Otherwise it
* is defaulted to {@code readonly} child resource of the RT.
*
* @return the associated resource type of current resource
*/
@CheckForNull
public String getReadOnlyResourceType() {
return base.getReadOnlyResourceType();
}
/**
* Returns the associated resource type of the given content resource for the
* purpose rendering read only version. First the granite:readOnlyResourceType
* property of the resource type of the content resource (the RT) is used.
* Otherwise it is defaulted to {@code readonly} child resource of the RT.
*
* @param resource
* the resource
* @return the associated resource type of the given content resource
*/
@CheckForNull
public String getReadOnlyResourceType(@Nonnull Resource resource) {
return base.getReadOnlyResourceType(resource);
}
/**
* Returns the datasource for items of the current resource. This is an overload
* of {@link #getItemDataSource(Resource)} with resource is the current request
* resource.
*
* @return the data source for items of the current resource
* @throws ServletException
* in case there's a servlet error while fetching data
* @throws IOException
* in case there's an i/o error while fetching data
*/
@Nonnull
public DataSource getItemDataSource() throws ServletException, IOException {
return base.getItemDataSource();
}
/**
* Returns the datasource for items of the given resource. This method can be
* used to fetch the items that are specified literally using
* {@link Config#ITEMS} subresource; or specified as datasource using
* {@link Config#DATASOURCE} subresource.
*
* If there is no {@link Config#ITEMS} or {@link Config#DATASOURCE} subresource,
* then {@link EmptyDataSource} is returned.
*
* In contrast with {@link #asDataSource(Resource, Resource)}, this method looks
* for the datasource resource of the given resource. i.e. The given resource is
* the parent of the items, not the datasource resource itself. The given
* resource is also used as the context resource when calling
* {@link #asDataSource(Resource, Resource)} internally.
*
* @param resource
* the resource
* @return the data source for items of the given resource
* @throws ServletException
* in case there's a servlet error while fetching data
* @throws IOException
* in case there's an i/o error while fetching data
*/
@Nonnull
public DataSource getItemDataSource(@Nonnull Resource resource) throws ServletException, IOException {
return base.getItemDataSource(resource);
}
/**
* Returns the datasource given its datasource resource. This method is an
* overload of {@link #asDataSource(Resource, Resource)} with context is
* {@code null}.
*
* @param datasource
* the resource representing the datasource
* @return the datasource given its datasource resource
* @throws ServletException
* in case there's a servlet error while fetching data
* @throws IOException
* in case there's an i/o error while fetching data
*/
public DataSource asDataSource(@CheckForNull Resource datasource) throws ServletException, IOException {
return base.asDataSource(datasource);
}
/**
* Returns the datasource given its datasource resource.
*
* @param datasource
* The resource representing the datasource
* @param context
* The context resource that is returned when calling
* {@link SlingHttpServletRequest#getResource()} at the datasource
* implementation. If this is {@code null}, the given datasource is
* used.
* @return {@code null} if the given datasource is {@code null}.
* @throws ServletException
* in case there's a servlet error while fetching data
* @throws IOException
* in case there's an i/o error while fetching data
*/
public DataSource asDataSource(@CheckForNull Resource datasource, @CheckForNull Resource context)
throws ServletException, IOException {
return base.asDataSource(datasource, context);
}
/**
* Returns the render condition of the current resource. This method is an
* overload of {@link #getRenderCondition(Resource)} using the current resource.
*
* The render condition is specified by {@code granite:rendercondition} or
* {@code rendercondition} subresource.
*
* Contrast this with {@link #getRenderCondition(Resource, boolean)}, where only
* {@code granite:rendercondition} is checked. This method is meant for backward
* compatibility; otherwise it is better to use
* {@link #getRenderCondition(Resource, boolean)} for performance. Once the
* transition is over, this method will have the same behaviour as
* {@link #getRenderCondition(Resource, boolean)} with {@code cache} =
* {@code false}.
*
* @return the render condition of the current resource
* @throws ServletException
* in case there's a servlet error
* @throws IOException
* in case there's an i/o error
*/
@Nonnull
public RenderCondition getRenderCondition() throws ServletException, IOException {
return getRenderCondition(base.getRequest().getResource());
}
/**
* Returns the render condition of the given resource.
*
* The render condition is specified by {@code granite:rendercondition} or
* {@code rendercondition} subresource.
*
* Contrast this with {@link #getRenderCondition(Resource, boolean)}, where only
* {@code granite:rendercondition} is checked. This method is meant for backward
* compatibility; otherwise it is better to use
* {@link #getRenderCondition(Resource, boolean)} for performance. Once the
* transition is over, this method will have the same behaviour as
* {@link #getRenderCondition(Resource, boolean)} with {@code cache} =
* {@code false}.
*
* @param resource
* the resource
* @return the render condition of the given resource
* @throws ServletException
* in case there's a servlet error
* @throws IOException
* in case there's an i/o error
*/
@Nonnull
public RenderCondition getRenderCondition(@Nonnull Resource resource) throws ServletException, IOException {
RenderCondition rc = null;
Resource condition = resource.getChild("granite:rendercondition");
if (condition == null) {
condition = resource.getChild(Config.RENDERCONDITION);
}
if (condition != null) {
String resourceType = getResourceType(condition,
"granite/ui/components/foundation/renderconditions/simple");
rc = getSlingIncludeObjectFactory().get(condition, resourceType, RenderCondition.class);
}
if (rc == null) {
rc = SimpleRenderCondition.TRUE;
}
return rc;
}
/**
* Returns the render condition of the given resource.
*
* The render condition is specified by {@code granite:rendercondition}
* subresource, unlike {@link #getRenderCondition(Resource)}.
*
* @param resource
* The resource
* @param cache
* {@code true} to cache the result; Use it when checking render
* condition of other resource (typically the item resource) so that
* the render condition is only resolved once.
*
* @return The render condition of the given resource; never {@code null}.
*
* @throws ServletException
* in case there's a servlet error
* @throws IOException
* in case there's an i/o error
*/
@Nonnull
public RenderCondition getRenderCondition(@Nonnull Resource resource, boolean cache)
throws ServletException, IOException {
return getRenderConditionHelper().getRenderCondition(resource, cache);
}
/**
* Returns the icon class(es) for the given icon string from the content
* property.
*
* @param icon
* the icon string
* @return the icon class(es) for the given icon string from the content
* property, or {@code null} if the given icon is null
*/
@SuppressWarnings("null")
public String getIconClass(@CheckForNull String icon) {
// In the future we can make it pluggable using OSGi so that others can provide
// their own icons based on certain icon class pattern.
if (icon == null) {
return null;
}
if (!icon.startsWith("icon-")) {
return icon;
}
return "coral-Icon--" + toCamel(icon.substring(5));
}
@SuppressWarnings("null")
@Nonnull
private static String toCamel(@Nonnull String s) {
String[] parts = s.split("-");
StringBuilder b = new StringBuilder();
b.append(parts[0]);
for (int i = 1; i < parts.length; i++) {
b.append(parts[i].substring(0, 1).toUpperCase());
b.append(parts[i].substring(1));
}
return b.toString();
}
@SuppressWarnings("null")
@CheckForNull
private static String getResourceType(@Nonnull Resource resource) {
return resource.getValueMap().get(ResourceResolver.PROPERTY_RESOURCE_TYPE, String.class);
}
@SuppressWarnings("null")
@Nonnull
private static String getResourceType(@Nonnull Resource resource, @Nonnull String defaultValue) {
return resource.getValueMap().get(ResourceResolver.PROPERTY_RESOURCE_TYPE, defaultValue);
}
/**
* Includes the given resource and passes the given tag to its renderer. This
* method performs similarly to <sling:include resource="" />.
*
* @param resource
* the resource to include
* @param tag
* the tag
* @throws ServletException
* in case there's a servlet error
* @throws IOException
* in case there's an i/o error
*/
public void include(@Nonnull Resource resource, @Nonnull Tag tag) throws ServletException, IOException {
include(resource, null, tag);
}
/**
* Includes the given resource and passes the given options to its renderer.
* This method performs similarly to <sling:include resource="" />.
*
* @param resource
* the resource to include
* @param options
* the options
* @throws ServletException
* in case there's a servlet error
* @throws IOException
* in case there's an i/o error
*/
public void include(@Nonnull Resource resource, @Nonnull Options options) throws ServletException, IOException {
include(resource, null, options);
}
/**
* Includes the given resource with the given resourceType and passes the given
* tag to its renderer. This method performs similarly to <sling:include
* resource="" resourceType="" />.
*
* @param resource
* the resource to include
* @param resourceType
* the resource type
* @param tag
* the tag
* @throws ServletException
* in case there's a servlet error
* @throws IOException
* in case there's an i/o error
*/
public void include(@Nonnull Resource resource, @CheckForNull String resourceType, @Nonnull Tag tag)
throws ServletException, IOException {
include(resource, resourceType, new Options().tag(tag));
}
/**
* Includes the given resource with the given resourceType and passes the given
* options to its renderer. This method performs similarly to <sling:include
* resource="" resourceType="" />.
*
* @param resource
* the resource to include
* @param resourceType
* the resource type
* @param options
* the options
* @throws ServletException
* in case there's a servlet error
* @throws IOException
* in case there's an i/o error
*/
public void include(@Nonnull Resource resource, @CheckForNull String resourceType, @Nonnull Options options)
throws ServletException, IOException {
include(resource, resourceType, null, options);
}
/**
* Includes the given resource with the given resourceType and passes the given
* options to its renderer. This method performs similarly to <sling:include
* resource="" replaceSelectors="" resourceType="" />.
*
* @param resource
* the resource to include
* @param resourceType
* the resource type
* @param selectors
* the selectors to be included as part of the request.
* @param options
* the options
* @throws ServletException
* in case there's a servlet error
* @throws IOException
* in case there's an i/o error
*/
public void include(@Nonnull Resource resource, @CheckForNull String resourceType, @CheckForNull String selectors,
@Nonnull Options options) throws ServletException, IOException {
base.include(resource, resourceType, selectors, options);
}
/**
* A convenient overload to
* {@link #includeForLayout(Resource, Resource, Options)}, with layoutResource
* as {@code null}.
*
* @param resource
* the resource to include
* @param options
* the options
* @throws ServletException
* in case there's a servlet error
* @throws IOException
* in case there's an i/o error
*/
public void includeForLayout(@Nonnull Resource resource, @Nonnull Options options)
throws ServletException, IOException {
includeForLayout(resource, null, options);
}
/**
* Includes the given resource to be rendered by the given layoutResource. This
* method is used by a component to delegate the rendering process to a layout.
*
* If layoutResource is not {@code null}, the
* {@link Options#layoutResource(Resource)} is set.
*
* This method will attempt to derive the resourceType to be passed to
* {@link #include(Resource, String, Options)} based the following priorities:
*
* - layoutResource is not null, the resourceType is layoutResource's RT
* - layoutResource is null, the resourceType is {@link Config#LAYOUT} child
* node's RT
* - the resourceType is default layout as a catch-all fallback
*
*
* @param resource
* the resource to include
* @param layoutResource
* the layout resource to render the given resource with
* @param options
* the options
* @throws ServletException
* in case there's a servlet error
* @throws IOException
* in case there's an i/o error
*/
public void includeForLayout(@Nonnull Resource resource, @CheckForNull Resource layoutResource,
@Nonnull Options options) throws ServletException, IOException {
String resourceType = null;
if (layoutResource == null) {
Resource r = resource.getChild(Config.LAYOUT);
if (r != null) {
resourceType = getResourceType(r);
}
} else {
resourceType = getResourceType(layoutResource);
options.layoutResource(layoutResource);
}
if (resourceType == null) {
resourceType = DEFAULT_LAYOUT_RT;
}
include(resource, resourceType, options);
}
/**
* Calls the given script and passes the given options to its renderer. This
* method performs similarly to <sling:call script="" />.
*
* @param script
* the script to be called
* @param options
* the options
* @throws ServletException
* in case there's a servlet error
* @throws IOException
* in case there's an i/o error
*/
public void call(@Nonnull String script, @Nonnull Options options) throws ServletException, IOException {
base.call(script, options);
}
/**
* Checks if the provided link begins with a http/s protocol or // and if so,
* rewrites it to a relative link.
*
* If the link does not match the above criteria, it is returned unchanged.
*
*
* Examples of external links covered by this method
*
* - http://example.com to /example.com
*
- https://example.com to /example.com
*
- //example.com to /example.com
*
- http:///example.com to /example.com
*
- https://///example.com to /example.com
*
* @param href - link to be checked
* @return the link rewritten to a relative link if it matched the criteria
*
*/
public String transformLinkInUriIfExternal(String href) {
return base.transformLinkInUriIfExternal(href);
}
/**
* An options to be passed to the included resource's renderer.
*/
public static class Options extends com.adobe.granite.ui.components.Options {
private Resource layout;
/**
* Creates a new instance.
*/
public Options() {
}
/**
* Sets the tag.
*
* @param tag
* the tag
* @return the options
*/
@Override
@Nonnull
public Options tag(@CheckForNull Tag tag) {
super.tag(tag);
return this;
}
/**
* Sets {@code true} to make the renderer (the field) should render itself as
* root field; {@code false} otherwise.
*
* A root field is a field that acts in its own context, instead of as part of a
* composite field. For example, sizing field consists of weight and height
* fields. So sizing field is a composite field and wants to leverage the
* existing number field for width and height. In this case when sizing field is
* including ( {@link ComponentHelper#include(Resource, Options)}) the number
* field, it should set this option as {@code false}.
*
* The field implementation is free to interpret the exact behaviour of
* root/non-root field. Most likely scenario, the root field will handle it own
* sizing state (e.g. inline-block/block state), while non root field will not,
* where the parent composite field is managing it.
*
* @param flag
* the flag
* @return this instance
*/
@Override
@Nonnull
public Options rootField(boolean flag) {
super.rootField(flag);
return this;
}
/**
* Returns the layout resource.
*
* @return the layout resource
*/
@CheckForNull
public Resource layoutResource() {
return layout;
}
/**
* Sets the layout resource.
*
* @param r
* the layout resource to set
* @return this instance
*/
@Nonnull
public Options layoutResource(@CheckForNull Resource r) {
this.layout = r;
return this;
}
}
}