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

com.day.cq.wcm.foundation.model.responsivegrid.ResponsiveGrid Maven / Gradle / Ivy

/*************************************************************************
 *
 * ADOBE CONFIDENTIAL
 * __________________
 *
 *  Copyright 2017 Adobe Systems Incorporated
 *  All Rights Reserved.
 *
 * NOTICE:  All information contained herein is, and remains
 * the property of Adobe Systems Incorporated and its suppliers,
 * if any.  The intellectual and technical concepts contained
 * herein are proprietary to Adobe Systems Incorporated and its
 * suppliers and are protected by trade secret or copyright law.
 * Dissemination of this information or reproduction of this material
 * is strictly forbidden unless prior written permission is obtained
 * from Adobe Systems Incorporated.
 **************************************************************************/
package com.day.cq.wcm.foundation.model.responsivegrid;

import aQute.bnd.annotation.ProviderType;
import com.adobe.cq.export.json.ComponentExporter;
import com.adobe.cq.export.json.ExporterConstants;
import com.adobe.cq.export.json.SlingModelFilter;
import com.adobe.cq.wcm.style.ComponentStyleInfo;
import com.day.cq.wcm.api.NameConstants;
import com.day.cq.wcm.api.TemplatedResource;
import com.day.cq.wcm.api.WCMMode;
import com.day.cq.wcm.api.components.Component;
import com.day.cq.wcm.api.designer.ComponentStyle;
import com.day.cq.wcm.api.designer.Style;
import com.day.cq.wcm.commons.WCMUtils;
import com.day.cq.wcm.foundation.model.export.AllowedComponentsExporter;
import com.day.cq.wcm.foundation.model.responsivegrid.export.ResponsiveGridExporter;
import com.fasterxml.jackson.annotation.JsonInclude;
import com.fasterxml.jackson.annotation.JsonProperty;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;

import org.apache.commons.lang3.StringUtils;
import org.apache.sling.api.SlingHttpServletRequest;
import org.apache.sling.api.resource.AbstractResource;
import org.apache.sling.api.resource.Resource;
import org.apache.sling.api.resource.ResourceMetadata;
import org.apache.sling.api.resource.ResourceResolver;
import org.apache.sling.api.resource.ResourceWrapper;
import org.apache.sling.api.resource.ValueMap;
import org.apache.sling.models.annotations.DefaultInjectionStrategy;
import org.apache.sling.models.annotations.Exporter;
import org.apache.sling.models.annotations.Model;
import org.apache.sling.models.annotations.injectorspecific.OSGiService;
import org.apache.sling.models.annotations.injectorspecific.ScriptVariable;
import org.apache.sling.models.annotations.injectorspecific.Self;
import org.apache.sling.models.annotations.injectorspecific.SlingObject;
import org.apache.sling.models.factory.ModelFactory;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;
import javax.annotation.PostConstruct;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collection;
import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import static com.day.cq.wcm.foundation.model.responsivegrid.ResponsiveGridUtils.createClassname;

/**
 * Sling model for the Responsive grid component.
 * A Responsive grid component provides access to its columns and generates a set of specific responsive data.
 */
@Model(
        adaptables = SlingHttpServletRequest.class,
        adapters = {ResponsiveGrid.class, ComponentExporter.class},
        defaultInjectionStrategy = DefaultInjectionStrategy.OPTIONAL,
        resourceType = ResponsiveGrid.RESOURCE_TYPE)
@Exporter(
        name = ExporterConstants.SLING_MODEL_EXPORTER_NAME,
        selector = ExporterConstants.SLING_MODEL_SELECTOR,
        extensions = ExporterConstants.SLING_MODEL_EXTENSION)
@JsonSerialize(as = ResponsiveGridExporter.class)
@ProviderType
public class ResponsiveGrid extends AbstractResource implements ResponsiveGridExporter {

    private static final String CQ_USE_LEGACY_RESPONSIVE_BEHAVIOUR = "cq:useLegacyResponsiveBehaviour";

    protected static final String RESOURCE_TYPE = "wcm/foundation/components/responsivegrid";
    static final String COLUMNS = "columns";
    static final String OFFSET = "offset";
    static final String WIDTH = "width";

    @Self
    private SlingHttpServletRequest slingRequest;

    @SlingObject
    volatile Resource resource;

    @ScriptVariable
    private ValueMap properties;

    @ScriptVariable
    private Style currentStyle;

    @OSGiService
    private ModelFactory modelFactory;

    @OSGiService
    private SlingModelFilter slingModelFilter;

    /**
     * Class names of the responsive grid
     */
    private String classNames;

    /**
     * Map containing all the class names exposed by columns
     */
    private final Map columnClassNames = new HashMap<>();

    /**
     * Child columns of the responsive grid
     */
    private final Map childColumns = new LinkedHashMap<>();

    /**
     * Number of columns set on the design or the default number of columns
     */
    private int columns;

    private String classNamesPrefix;

    private WCMMode wcmMode;

    private Resource effectiveResource;

    @PostConstruct
    protected void initModel() {
        Map breakpoints = new HashMap<>();
        classNamesPrefix = currentStyle.get("cssPrefix", ResponsiveConstants.DEFAULT_CSS_PREFIX);
        Resource columnConfig = resource.getChild(NameConstants.NN_RESPONSIVE_CONFIG);
        Resource parent = resource.getParent();
        Resource responsiveParentCfg = null;
        if (parent != null) {
            responsiveParentCfg = parent.getChild(NameConstants.NN_RESPONSIVE_CONFIG);
        }
        columns = currentStyle.get(COLUMNS, ResponsiveConstants.DEFAULT_COLUMNS);
        int width = currentStyle.get(COLUMNS, 0);
        int offset = currentStyle.get(OFFSET, 0);
        boolean hasDesignValues = (width > 0) || currentStyle.containsKey(OFFSET);
        Map initialDefaultWidths = new HashMap<>();
        int initialDefaultWidth = ResponsiveConstants.DEFAULT_COLUMNS;
        int initialDefaultOffset = 0;
        Resource effectiveResource = getEffectiveResource();
        // The initial width may also come from the configuration of the structure resource backing-up the current grid
        Resource effectiveResponsiveConfig =
                effectiveResource.getChild(
                        NameConstants.NN_RESPONSIVE_CONFIG + "/" +
                                ResponsiveConstants.BREAKPOINT_VARIANT_NAME_DEFAULT);

        // Get the initial default width from the responsive configuration of the current resource
        if (effectiveResponsiveConfig != null && effectiveResponsiveConfig.getValueMap().containsKey(WIDTH)) {
            initialDefaultWidth =
                    effectiveResponsiveConfig.getValueMap().get(WIDTH, ResponsiveConstants.DEFAULT_COLUMNS);
            initialDefaultOffset = effectiveResponsiveConfig.getValueMap().get(OFFSET, 0);
        }

        // Ensure that the initial default width isn't larger than the width of its parent
        if (responsiveParentCfg != null && !getLegacyResponsiveBehaviourConfig()) {
            Iterable parentBreakpoints = responsiveParentCfg.getChildren();

            for (Resource breakpoint : parentBreakpoints) {
                String name = breakpoint.getName();

                ValueMap parentCfg = breakpoint.adaptTo(ValueMap.class);
                int constrainedWidth = ResponsiveGridUtils.getConstrainedWidthAndOffset(initialDefaultWidth, initialDefaultOffset, parentCfg.get(WIDTH, width))[0];
                initialDefaultWidths.put(name, constrainedWidth);
            }

            if (initialDefaultWidths.containsKey(ResponsiveConstants.BREAKPOINT_VARIANT_NAME_DEFAULT)) {
                initialDefaultWidth = initialDefaultWidths.get(ResponsiveConstants.BREAKPOINT_VARIANT_NAME_DEFAULT);
            }
        }

        if (columnConfig != null) {
            for (Iterator resCfgIt = columnConfig.listChildren(); resCfgIt.hasNext(); ) {
                Resource resCfg = resCfgIt.next();
                String breakpointName = resCfg.getName();
                ValueMap cfg = resCfg.adaptTo(ValueMap.class);
                Breakpoint.ResponsiveBehavior behavior =
                        Breakpoint.ResponsiveBehavior.valueOf(
                                cfg.get("behavior",
                                        Breakpoint.ResponsiveBehavior.none.toString()));

                if (!hasDesignValues) {
                    int resWidth = cfg.get(WIDTH, 0);
                    int resOffset = cfg.get(OFFSET, 0);

                    width = (resWidth > 0) ? resWidth : width;
                    offset = (resOffset > 0) ? resOffset : offset;
                }


                if (width > 0 && responsiveParentCfg != null) {
                    // Adapt the width and offset based on the one of the parent
                    Resource parentBreakpoint = responsiveParentCfg.getChild(breakpointName);
                    // The width + the offset of the responsive grid cannot be bigger than the one of its parent
                    if (parentBreakpoint != null) {
                        ValueMap parentCfg = parentBreakpoint.adaptTo(ValueMap.class);
                        int parentWidth = parentCfg.get(WIDTH, width);

                        int[] widthOffset = ResponsiveGridUtils.getConstrainedWidthAndOffset(width, offset, parentWidth);
                        width = widthOffset[0];
                        offset = widthOffset[1];
                    }
                }

                // config exists but no width property is set. Let's not add this breakpoint
                if(width > 0) {
                    breakpoints.put(breakpointName, new Breakpoint(breakpointName, width, offset, behavior));
                }
            }
        }

        createDefaultBreakpoint(breakpoints, offset, initialDefaultWidth);
        Set columnBreakpointNames = createResponsiveColumns(breakpoints);
        Map missingBreakpoints = ResponsiveGridUtils.getMissingBreakpoints(breakpoints, columnBreakpointNames, initialDefaultWidths);
        breakpoints.putAll(missingBreakpoints);

        int effectiveColumns = currentStyle.get(COLUMNS, initialDefaultWidth);
        String initial = String.join(" ", Arrays.asList(classNamesPrefix, createClassname(Arrays.asList(classNamesPrefix, Integer.toString(effectiveColumns))), properties.get(ComponentStyle.PN_CSS_CLASS, "")));
        classNames = ResponsiveGridUtils.createClassNames(initial, breakpoints, this::generateBreakpointCssClasses);

        wcmMode = WCMMode.fromRequest(slingRequest);
    }

    private void createDefaultBreakpoint(Map breakpoints, int offset, int initialDefaultWidth) {
        if (!breakpoints.containsKey(ResponsiveConstants.BREAKPOINT_VARIANT_NAME_DEFAULT)) {
            int columns = currentStyle.get(COLUMNS, initialDefaultWidth);
            breakpoints.put(
                    ResponsiveConstants.BREAKPOINT_VARIANT_NAME_DEFAULT,
                    new Breakpoint(ResponsiveConstants.BREAKPOINT_VARIANT_NAME_DEFAULT,
                            columns,
                            offset,
                            Breakpoint.ResponsiveBehavior.none));
        }
    }

    /**
     * Returns true if the legacy-responsive-behaviour-config of the Responsive Grid is set to true, false otherwise.
     * Traverses up the component resource hierarchy, starting from the responsive grid itself, and returns the first value set on any
     * component resource or false, if none found.
     *
     * @return whether Legacy Responsive behaviour is On or Off for the Responsive Grid
     */
    @Nonnull
    private boolean getLegacyResponsiveBehaviourConfig() {
        ResourceResolver resourceResolver = resource.getResourceResolver();
        String effectiveResourcePath = getEffectiveResource().getPath();
        // original grid resource
        Resource currentResource = resourceResolver.getResource(effectiveResourcePath);

        while (currentResource != null) {
            final Component componentResource = WCMUtils.getComponent(currentResource);
            if (componentResource == null) {
                return false;
            }

            final Boolean legacyResponsiveConfig = componentResource.getProperties().get(CQ_USE_LEGACY_RESPONSIVE_BEHAVIOUR, Boolean.class);
            if (legacyResponsiveConfig != null) {
                return legacyResponsiveConfig;
            }

            currentResource = currentResource.getParent();
        }

        return false;
    }

    /**
     * @return Set of created responsive column names
     */
    Set createResponsiveColumns(Map breakpoints) {
        Set columnBreakpointNames = new HashSet<>();
        Iterable filteredChildren = slingModelFilter.filterChildResources(getEffectiveResource().getChildren());
        for (Resource child : filteredChildren) {
            ResponsiveColumn column =
                    new ResponsiveColumn(child, breakpoints, classNamesPrefix, slingRequest, modelFactory);
            childColumns.put(column.getResource().getName(), column);
            Map columnBreakpoints = column.getBreakpoints();
            columnClassNames.put(column.getResource().getName(), column.getColumnClassNames());

            if (columnBreakpoints != null) {
                columnBreakpointNames.addAll(columnBreakpoints.keySet());
            }
        }
        return columnBreakpointNames;
    }

    List generateBreakpointCssClasses(Breakpoint breakpoint) {
        String breakpointName = breakpoint.getName();
        int width = breakpoint.getWidth();
        List breakpointClasses = new ArrayList<>();
        if (width > 0) {
            String widthBreakpointClassname = createClassname(Arrays.asList(classNamesPrefix, breakpointName, Integer.toString(width)));
            breakpointClasses.add(widthBreakpointClassname);
        }
        return breakpointClasses;
    }

    @Override
    public String getGridClassNames() {
        return classNames;
    }

    @Nonnull
    @Override
    public Map getColumnClassNames() {
        return columnClassNames;
    }

    @Override
    public int getColumnCount() {
        return columns;
    }

    /**
     * @return The columns of the current responsive grid.
     */
    @Nonnull
    public Collection getColumns() {
        return childColumns.values();
    }

    /**
     * @param  The type of the resource
     * @return Returns the resource optionally wrapped into a {@link TemplatedResource}
     * AdobePatentID="P6273-US"
     */
    @Nonnull
    public  T getEffectiveResource() {
        if (effectiveResource == null) {
            effectiveResource = getEffectiveResourceInternal();
        }
        return (T) effectiveResource;
    }

    private Resource getEffectiveResourceInternal() {
        if (resource instanceof TemplatedResource) {
            return resource;
        }

        if (resource instanceof ResourceWrapper) {
            Resource wrappedResource = ((ResourceWrapper) resource).getResource();
            if (wrappedResource instanceof TemplatedResource) {
                return resource;
            }
        }

        Resource templatedResource = slingRequest.adaptTo(TemplatedResource.class);

        if (templatedResource == null) {
            return resource;
        } else {
            return templatedResource;
        }
    }

    @Nonnull
    public String getPath() {
        return resource.getPath();
    }

    /**
     * {@inheritDoc}
     */
    @JsonProperty("allowedComponents")
    @JsonInclude(JsonInclude.Include.NON_NULL)
    public AllowedComponentsExporter getExportedAllowedComponents() {
        if(wcmMode != WCMMode.DISABLED){
            return slingRequest.adaptTo(AllowedComponentsExporter.class);
        }else{
            //on WCMMode disabled, we do not want to send this data over, as it's redundant and quite big.
            return null;
        }

    }

    @Nonnull
    @Override
    public Map getExportedItems() {
        return childColumns;
    }

    @Nonnull
    @Override
    public String[] getExportedItemsOrder() {
        return childColumns.isEmpty() ?
                new String[0] : childColumns.keySet().toArray(new String[0]);
    }

    @Nonnull
    @Override
    public String getExportedType() {
        return resource.getResourceType();
    }

    /**
     * @return The columns of the current responsive grid.
     * @deprecated Use {@link #getColumns()}
     */
    @Deprecated
    public List getParagraphs() {
        return new ArrayList<>(getColumns());
    }

    /**
     * @return The CSS class names to be applied to the current grid.
     * @deprecated Use {@link #getGridClassNames()}
     */
    @Deprecated
    public String getCssClass() {
        return classNames;
    }

    /**
     * @deprecated
     */
    @Nonnull
    @Override
    @Deprecated
    public String getResourceType() {
        return resource.getResourceType();
    }

    /**
     * @deprecated
     */
    @Deprecated
    public String getResourceSuperType() {
        return resource.getResourceSuperType();
    }

    /**
     * @deprecated
     */
    @Nonnull
    @Override
    @Deprecated
    public ResourceMetadata getResourceMetadata() {
        return resource.getResourceMetadata();
    }

    /**
     * @deprecated
     */
    @Nonnull
    @Override
    @Deprecated
    public ResourceResolver getResourceResolver() {
        return resource.getResourceResolver();
    }

    @Override
    @Nullable
    public String getAppliedCssClasses() {

        return Optional.ofNullable(this.resource.adaptTo(ComponentStyleInfo.class))
                .map(ComponentStyleInfo::getAppliedCssClasses)
                .filter(StringUtils::isNotBlank)
                .orElse(null);		// Returning null so sling model exporters don't return anything for this property if not configured
    }
}




© 2015 - 2025 Weber Informatics LLC | Privacy Policy