
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 extends ResponsiveColumn> 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