Goal
Create a React SPA Smart Crop Image component /apps/eaem-sites-spa-how-to-react/components/dm-image-smart-crop to show the dynamic crops for different breakpoints...
Demo | Package Install | Github
Setup DM Image Profiles
Configure Folder with DM Info
Smart Crops
Component Dialog
Desktop
Mobile
Solution
1) To get the smart crops of an image, create the following nt:file /apps/eaem-sites-spa-how-to-react/smart-crop-renditions/smart-crop-renditions.jsp to return the dynamic crops as JSON (serves as a client side datasource for crops drop down, created in next steps...)
<%@include file="/libs/granite/ui/global.jsp"%> <%@page session="false" import="java.util.Iterator, org.apache.sling.commons.json.JSONObject, com.adobe.granite.ui.components.Config"%> <% Config cfg = cmp.getConfig(); ValueMap dynVM = null; JSONObject dynRenditions = new JSONObject(); Resource dynResource = null; response.setContentType("application/json"); for (Iterator<Resource> items = cmp.getItemDataSource().iterator(); items.hasNext();) { JSONObject dynRendition = new JSONObject(); dynResource = items.next(); dynVM = dynResource.getValueMap(); String name = String.valueOf(dynVM.get("breakpoint-name")); dynRendition.put("type", "IMAGE"); dynRendition.put("name", name); dynRendition.put("url", dynVM.get("copyurl")); dynRenditions.put(name, dynRendition); } dynRenditions.write(response.getWriter()); %>
2) Create /apps/eaem-sites-spa-how-to-react/smart-crop-renditions/renditions and set the sling:resourceType to /apps/eaem-sites-spa-how-to-react/smart-crop-renditions. This is the content node for fetching renditions...
3) Create node /apps/eaem-sites-spa-how-to-react/clientlibs/clientlib-extensions of type cq:ClientLibraryFolder, add String[] property categories with value [cq.authoring.dialog.all], String[] property dependencies with value lodash.
4) Create file (nt:file) /apps/eaem-sites-spa-how-to-react/clientlibs/clientlib-extensions/js.txt, add
dm-smart-crops.js
5) Create file (nt:file) /apps/eaem-sites-spa-how-to-react/clientlibs/clientlib-extensions/dm-smart-crops.js, add the following code...
(function ($, $document) { var DM_FILE_REF = "[name='./fileReference']", CROPS_MF = "[data-granite-coral-multifield-name='./crops']", SMART_CROPS_URL = "/apps/eaem-sites-spa-how-to-react/smart-crop-renditions/renditions.html", dynRenditions = {}; $document.on('dialog-ready', loadSmartCrops); function loadSmartCrops() { var dialogPath; try { dialogPath = Granite.author.DialogFrame.currentDialog.editable.slingPath; } catch (err) { console.log("Error getting dialog path...", err); } if (!dialogPath) { return; } dialogPath = dialogPath.substring(0, dialogPath.lastIndexOf(".json")); $.ajax(dialogPath + ".2.json").done(handleCropsMF); } function handleCropsMF(dialogData) { var $cropsMF = $(CROPS_MF), mfName = $cropsMF.attr("data-granite-coral-multifield-name"), selectData = dialogData[mfName.substr(2)]; $cropsMF.find("coral-select").each(function (index, cropSelect) { var $cropSelect = $(cropSelect), selUrl, name = $cropSelect.attr("name"); name = name.substring(mfName.length + 1); name = name.substring(0,name.indexOf("/")); if(selectData[name]){ selUrl = selectData[name]["url"]; } loadCropsInSelect($cropSelect, selUrl); }); $cropsMF.on("change", function () { var multifield = this; _.defer(function () { var justAddedItem = multifield.items.last(), $cropSelect = $(justAddedItem).find("coral-select"); loadCropsInSelect($cropSelect); }); }); $(DM_FILE_REF).closest("coral-fileupload").on("change", function(){ dynRenditions = {}; $cropsMF.find("coral-select").each(function (index, cropSelect) { var $cropSelect = $(cropSelect); $cropSelect[0].items.clear(); loadCropsInSelect($cropSelect); }); }) } function getCoralSelectItem(text, value, selected) { return '<coral-select-item value="' + value + '" ' + selected + '>' + text + '</coral-select-item>'; } function loadCropsInSelect($cropSelect, selectedValue) { var $fileRef = $(DM_FILE_REF), fileRef = $fileRef.val(); if ( !fileRef || ($cropSelect[0].items.length > 1)) { return; } if (_.isEmpty(dynRenditions)) { $.ajax({url: SMART_CROPS_URL + fileRef, async: false}).done(function (renditions) { dynRenditions = renditions; addInSelect(); }); } else { addInSelect(); } function addInSelect() { _.each(dynRenditions, function (rendition) { $cropSelect.append(getCoralSelectItem(rendition.name, rendition.url, ((selectedValue == rendition.url) ? "selected" : ""))); }); } } }(jQuery, jQuery(document)));
6) Create the component /apps/eaem-sites-spa-how-to-react/components/dm-image-smart-crop. In the next step we'd be creating the react render type script...
7) Add the component render script in eaem-sites-react-spa-dm-image\ui.frontend\src\components\DMSmartCropImage\DMSmartCropImage.tsx with the following code...
import { MapTo } from '@adobe/cq-react-editable-components'; import React, { Component } from 'react'; import { Link } from "react-router-dom"; import CSS from 'csstype'; function isObjectEmpty(obj) { return (Object.keys(obj).length == 0); } interface ImageComponentProps { smartCrops: object fileReference: string imageLink: string } interface ImageComponentState { imageSrc: string } const ImageEditConfig = { emptyLabel: 'Dynamic Media Smart Crop Image - Experience AEM', isEmpty: function (props) { return (!props || !props.fileReference || (props.fileReference.trim().length < 1)); } }; class Image extends React.Component<ImageComponentProps, ImageComponentState> { constructor(props: ImageComponentProps) { super(props); this.state = { imageSrc: this.imageUrl() } } componentDidMount() { window.addEventListener('resize', this.updateImage.bind(this)); } componentDidUpdate(){ console.log("in update"); const currentSrc = this.state.imageSrc; const newSrc = this.imageUrl(); if(currentSrc != newSrc){ this.updateImage(); } } componentWillUnmount() { window.removeEventListener('resize', this.updateImage); } updateImage(){ this.setState({ imageSrc: this.imageUrl() }) } imageUrl() { const imageProps = this.props; let src = imageProps.fileReference; if (!isObjectEmpty(imageProps.smartCrops)) { const breakPoints = Object.keys(imageProps.smartCrops).sort((a: any, b: any) => b - a); for (const i in breakPoints) { let bp = parseInt(breakPoints[i]); if (bp < window.innerWidth) { src = imageProps.smartCrops[bp]; break; } } } return src; } get imageHTML() { const imgStyles: CSS.Properties = { display : 'block', marginLeft: 'auto', marginRight: 'auto' }; return ( <Link to={this.props.imageLink}> <img src={this.state.imageSrc} style={imgStyles} /> </Link> ); } render() { return this.imageHTML; } } export default MapTo('eaem-sites-spa-how-to-react/components/dm-image-smart-crop')(Image, ImageEditConfig);
8) DMSmartCropImage was imported in ui.frontend\src\components\import-components.js
...
import './DMSmartCropImage/DMSmartCropImage';
9) Create a Sling Model Exporter com.eaem.core.models.ImageComponentSlingExporter for exporting the component properties
SPA App Model Export:
http://localhost:4502/content/eaem-sites-spa-how-to-react/us/en.model.json
Container Component Model Export:
10) Add the following code in com.eaem.core.models.ImageComponentSlingExporter
package com.eaem.core.models; import com.adobe.cq.export.json.ComponentExporter; import com.adobe.cq.export.json.ExporterConstants; import com.adobe.cq.wcm.core.components.models.Image; import org.apache.sling.api.SlingHttpServletRequest; import org.apache.sling.api.resource.Resource; import org.apache.sling.api.resource.ValueMap; import org.apache.sling.models.annotations.Exporter; import org.apache.sling.models.annotations.Model; import org.apache.sling.models.annotations.Optional; import org.apache.sling.models.annotations.injectorspecific.SlingObject; import org.apache.sling.models.annotations.injectorspecific.ValueMapValue; import javax.annotation.PostConstruct; import javax.inject.Inject; import java.util.Iterator; import java.util.LinkedHashMap; import java.util.Map; @Model( adaptables = {SlingHttpServletRequest.class}, adapters = {ComponentExporter.class}, resourceType = { "eaem-sites-spa-how-to-react/components/image", "eaem-sites-spa-how-to-react/components/dm-image-smart-crop" } ) @Exporter( name = ExporterConstants.SLING_MODEL_EXPORTER_NAME, extensions = ExporterConstants.SLING_MODEL_EXTENSION ) public class ImageComponentSlingExporter implements ComponentExporter { @Inject private Resource resource; @ValueMapValue @Optional private String imageLink; @ValueMapValue @Optional private String fileReference; @ValueMapValue @Optional private boolean openInNewWindow; private Map<String, String> smartCrops; @PostConstruct protected void initModel() { smartCrops = new LinkedHashMap<String, String>(); Resource cropsRes = resource.getChild("crops"); if(cropsRes == null){ return; } Iterator<Resource> itr = cropsRes.listChildren(); ValueMap vm = null; while(itr.hasNext()){ vm = itr.next().getValueMap(); smartCrops.put(vm.get("breakpoint", ""), vm.get("url", "")); } } @Override public String getExportedType() { return resource.getResourceType(); } public Map<String, String> getSmartCrops() { return smartCrops; } public String getImageLink() { return imageLink; } public void setImageLink(String imageLink) { this.imageLink = imageLink; } public String getFileReference() { return fileReference; } public void setFileReference(String fileReference) { this.fileReference = fileReference; } public boolean isOpenInNewWindow() { return openInNewWindow; } public void setOpenInNewWindow(boolean openInNewWindow) { this.openInNewWindow = openInNewWindow; } }
Hi Sreekanth, How can can I implement the same for custom components with property name as siteLogo instead of fileReference.
ReplyDeleteHow to get the Image renditions in Model/servlet instead of jsp(smart-crop-renditions.jsp)? can you please share.
ReplyDelete