All Downloads are FREE. Search and download functionalities are using the official Maven repository.

com.adobe.granite.ui.components.ComponentHelper Maven / Gradle / Ivy

There is a newer version: 2024.11.18751.20241128T090041Z-241100
Show newest version
/*************************************************************************
* 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
NameTypeDescription
granite:idProperty: StringELThe id attribute.
granite:relProperty: StringELThe class attribute. This is used to indicate the semantic relationship * of the component similar to {@code rel} attribute.
granite:classProperty: StringELThe class attribute.
granite:titleProperty: StringThe title attribute. This property is i18nable.
granite:hiddenProperty: BooleanThe hidden attribute.
granite:itemscopeProperty: BooleanThe itemscope attribute.
granite:itemtypeProperty: StringThe itemtype attribute.
granite:itempropProperty: StringThe itemprop attribute.
granite:dataNodeEach 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: *

    *
  1. layoutResource is not null, the resourceType is layoutResource's RT
  2. *
  3. layoutResource is null, the resourceType is {@link Config#LAYOUT} child * node's RT
  4. *
  5. the resourceType is default layout as a catch-all fallback
  6. *
* * @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; } } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy