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

AEM 6560 - React SPA Dynamic Media Smart Crop Image Component

$
0
0

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...

                                                        http://localhost:4502/apps/eaem-sites-spa-how-to-react/smart-crop-renditions/renditions.html/content/dam/home-assets/chaitra-birthday.JPG

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: 

                          http://localhost:4502/content/eaem-sites-spa-how-to-react/us/en/eaem-home/jcr:content/root/responsivegrid/dm_image_smart_crop.model.json


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



Viewing all articles
Browse latest Browse all 525

Trending Articles



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