Quantcast
Channel: Experiencing Adobe Experience Manager (AEM, CQ)
Viewing all articles
Browse latest Browse all 525

AEM Cloud Service - Extend React SPA Sling Model for Page Style System support

$
0
0

Goal

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);



Viewing all articles
Browse latest Browse all 525

Trending Articles



<script src="https://jsc.adskeeper.com/r/s/rssing.com.1596347.js" async> </script>