Goal
AEM Cloud Version : 2021.5.5257.20210505T101930Z-210429 (May 05, 2021)
Extend the JSON model eg. /content/eaem-cs-spa-style-system/us/en.model.json for supporting Page Style System in a React SPA. The following extension adds page level css classes configured via Style System in the model's children cssClassNames property eg.spa page basicpage eaempage--background-aero and necessary logic on the react side to apply it...
Demo | Package Install | Content Package | Github
Style System in Template Policy
Styles on Page
Style CSS in Model
Solution
1) Create the project using following maven archetype command
mvn -B archetype:generate -D archetypeGroupId=com.adobe.aem -D archetypeArtifactId=aem-project-archetype -D archetypeVersion=24 -D aemVersion=cloud -D appTitle="Experience AEM SPA Style System" -D appId="eaem-cs-spa-style-system" -D groupId="apps.experienceaem.sites.spa" -D frontendModule=react -D includeExamples=n -D includeDispatcherConfig=n
2) Create the CSS file for Page Style System classes /apps/eaem-cs-spa-style-system/clientlibs/clientlib-base/main.css with the following code, add it in /apps/eaem-cs-spa-style-system/clientlibs/clientlib-base/css.txt
.eaempage--background-gray{ background-color: #f1f1f1; } .eaempage--background-white{ background-color: #ffffff; } .eaempage--background-black{ background-color: #000000; } .eaempage--background-beige{ background-color: #EEE1C6; } .eaempage--background-aero{ background-color: #CAF1DE; }
3) Add a filter apps.experienceaem.sites.spa.core.filters.EAEMDefaultModelJSONFilter for intercepting model.json requests, iterate child pages and add the configured style classes in property cssClassNames
package apps.experienceaem.sites.spa.core.filters; import com.day.cq.search.PredicateGroup; import com.day.cq.search.Query; import com.day.cq.search.QueryBuilder; import com.day.cq.search.result.Hit; import com.day.cq.search.result.SearchResult; import com.day.cq.wcm.api.policies.ContentPolicy; import com.day.cq.wcm.api.policies.ContentPolicyManager; import org.apache.commons.lang3.ArrayUtils; import org.apache.commons.lang3.StringUtils; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.SlingHttpServletResponse; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ResourceResolver; import org.apache.sling.api.resource.ValueMap; import org.apache.sling.api.wrappers.SlingHttpServletResponseWrapper; import org.apache.sling.commons.json.JSONArray; import org.json.JSONObject; import org.osgi.service.component.annotations.Component; import org.osgi.framework.Constants; import org.osgi.service.component.annotations.Reference; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import javax.jcr.Session; import javax.servlet.*; import java.io.*; import java.util.Arrays; import java.util.HashMap; import java.util.Iterator; import java.util.Map; @Component( service = Filter.class, immediate = true, name = "Experience AEM Default Sling Model Response Modifier Servlet Filter", property = { Constants.SERVICE_RANKING + ":Integer=-99", "sling.filter.scope=COMPONENT", "sling.filter.pattern=.*.model.json" } ) public class EAEMDefaultModelJSONFilter implements Filter { private static Logger log = LoggerFactory.getLogger(EAEMDefaultModelJSONFilter.class); public static String EAEM_DATA = "eaemData"; private static final String SLING_VANITYPATH = "sling:vanityPath"; private static final String CQ_STYLE_IDS = "cq:styleIds"; private static final String SLING_VANITYPATH_JSON_PROP = "slingVanityPath"; private static final String CSS_CLASS_NAMES = "cssClassNames"; private static final String CHILDREN = ":children"; @Reference private QueryBuilder builder; @Override public void init(FilterConfig filterConfig) throws ServletException { } @Override public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException { SlingHttpServletRequest slingRequest = (SlingHttpServletRequest)request; String uri = slingRequest.getRequestURI(); if(!uri.endsWith(".model.json")){ chain.doFilter(request, response); return; } SlingHttpServletResponse modelResponse = new DefaultSlingModelResponseWrapper((SlingHttpServletResponse)response); chain.doFilter(slingRequest, modelResponse); PrintWriter responseWriter = response.getWriter(); responseWriter.write(getModifiedContent(modelResponse.toString(), slingRequest)); } private String getModifiedContent(String origContent, SlingHttpServletRequest slingRequest){ String modifiedContent = origContent; try{ JSONObject model = new JSONObject(origContent); addAddnPropertiesInPageModel(model, slingRequest); model = (JSONObject) replaceEaemDataObject(model); modifiedContent = model.toString(); }catch(Exception e){ log.error("Error modifying model JSON content", e); modifiedContent = origContent; } return modifiedContent; } private void addAddnPropertiesInPageModel(JSONObject model, SlingHttpServletRequest slingRequest) throws Exception{ if(!model.has(CHILDREN)){ return; } JSONObject childrenModel = model.getJSONObject(CHILDREN); Iterator<String> childrenItr = childrenModel.keys(); ResourceResolver resolver = slingRequest.getResourceResolver(); Resource pageContent; while(childrenItr.hasNext()) { String pagePath = childrenItr.next(); JSONObject childData = childrenModel.getJSONObject(pagePath); pageContent = resolver.getResource(pagePath + "/jcr:content"); if(pageContent == null){ continue; } ValueMap vm = pageContent.getValueMap(); String[] slingVanityPaths = vm.get(SLING_VANITYPATH, String[].class); if(ArrayUtils.isNotEmpty(slingVanityPaths)){ JSONArray vanityPaths = new JSONArray(); Arrays.stream(slingVanityPaths).forEach(vanityPaths::put); childData.put(SLING_VANITYPATH_JSON_PROP, vanityPaths); } if(!childData.has(CSS_CLASS_NAMES)){ continue; } String styles = childData.getString(CSS_CLASS_NAMES); String addnClasses = getCssClasses(resolver, pagePath, vm.get(CQ_STYLE_IDS, String[].class)); if(!styles.contains(addnClasses)){ childData.put(CSS_CLASS_NAMES, styles + " " + addnClasses); } } } private String getCssClasses(ResourceResolver resolver, String pagePath, String[] styleIds) throws Exception{ ContentPolicyManager policyManager = resolver.adaptTo(ContentPolicyManager.class); Resource contentPolicyResource = getContentPolicyResource(resolver, resolver.getResource(pagePath)); if( (contentPolicyResource == null) || ArrayUtils.isEmpty(styleIds)){ return ""; } String styleClasses = ""; for(String styleId : styleIds){ Query query = builder.createQuery(PredicateGroup.create(getStyleQueryPredicateMap(contentPolicyResource.getPath(), styleId)), resolver.adaptTo(Session.class)); SearchResult result = query.getResult(); for (Hit hit : result.getHits()) { styleClasses = styleClasses + hit.getProperties().get("cq:styleClasses") + " "; } } return styleClasses.trim(); } private Resource getContentPolicyResource(ResourceResolver resolver, Resource pageRes) { ContentPolicyManager policyManager = resolver.adaptTo(ContentPolicyManager.class); if (policyManager == null) { return null; } ContentPolicy policy = policyManager.getPolicy(pageRes); if (policy == null) { return null; } return policy.adaptTo(Resource.class); } private static Map<String, String> getStyleQueryPredicateMap(String stylePath, String styleId) { Map<String, String> map = new HashMap<>(); map.put("path", stylePath); map.put("1_property","cq:styleId"); map.put("1_property.value",styleId); map.put("p.hits","selective"); map.put("p.properties","cq:styleClasses"); return map; } private Object replaceEaemDataObject(JSONObject jsonObject) throws Exception{ Iterator<String> itr = jsonObject.keys(); String key; JSONObject modJSONObj = new JSONObject(); Object jsonValue = null; while(itr.hasNext()){ key = itr.next(); if(key.equals(EAEM_DATA)){ JSONObject eaemData = (JSONObject)jsonObject.get(EAEM_DATA); eaemData.put(":type" , jsonObject.get(":type")); return eaemData; }else{ jsonValue = jsonObject.get(key); if(JSONObject.class.isInstance(jsonValue)){ modJSONObj.put(key, replaceEaemDataObject((JSONObject)jsonValue)); }else{ modJSONObj.put(key, jsonValue); } } } return modJSONObj; } @Override public void destroy() { } private class DefaultSlingModelResponseWrapper extends SlingHttpServletResponseWrapper { private CharArrayWriter writer; public DefaultSlingModelResponseWrapper (final SlingHttpServletResponse response) { super(response); writer = new CharArrayWriter(); } public PrintWriter getWriter() throws IOException { return new PrintWriter(writer); } public String toString() { return writer.toString(); } } }
4) Apply the cssClassNames (line #8) in eaem-cs-spa-style-system\ui.frontend\src\App.js
import { Page, withModel } from '@adobe/cq-react-editable-components'; import React from 'react'; // This component is the application entry point class App extends Page { render() { return ( <div className={this.props.cssClassNames}> {this.childComponents} {this.childPages} </div> ); } } export default withModel(App);
No comments:
Post a Comment