AEM CQ 56 - Multi Image Component

Goal


A better implementation is available here

Create a multi image component for dynamically adding images. The component dialog provides icons for adding/removing/reordering images. Check demo video



This is a work in progress and not production ready ( install package not provided ) with few bugs.

1) Reorder is not yet implemented
2) Refresh page is needed sometimes, after adding a new image
3) Crop parameters are not saved sometimes

Prerequisites, References


If you are new to CQ

1) Read this post on how to create a sample page component

2) Read this post for image component customization

Solution


1) Create folder /apps/multifieldimage and copy /libs/foundation/components/logo to /apps/multifieldimage; rename /apps/multifieldimage/logo to /apps/multifieldimage/image and logo.jsp to image.jsp

2) Replace the content of /apps/multifieldimage/image/dialog with following xml code

<?xml version="1.0" encoding="UTF-8"?>
<jcr:root xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0"
    jcr:primaryType="cq:Dialog"
    activeTab="{Long}0"
    title="Logo (Design)"
    xtype="tabpanel">
    <items jcr:primaryType="cq:WidgetCollection">
        <basic
            jcr:primaryType="cq:Widget"
            title="Images"
            xtype="panel">
            <items jcr:primaryType="cq:WidgetCollection">
                <images
                    jcr:primaryType="cq:Widget"
                    border="false"
                    hideLabel="false"
                    imagePoolMax="8"
                    name="./images"
                    xtype="multiimagewidget">
                    <imageConfig
                        jcr:primaryType="cq:Widget"
                        cropParameter="imageCrop"
                        ddGroups="[media]"
                        fileNameParameter="imageName"
                        fileReferenceParameter="imageReference"
                        height="200"
                        mapParameter="imageMap"
                        name="image"
                        parentPrefix="demo"
                        rotateParameter="imageRotate"
                        sizeLimit="100"/>
                </images>
            </items>
        </basic>
    </items>
</jcr:root>


3) Line #17 imagePoolMax is a configurable parameter for the maximum number of images a user can dynamically add in a dialog

4) Create a clientlib /apps/multifieldimage/image/clientlib/multiimage.js and add the following code

var MyClientLib = MyClientLib || {};

CQ.Ext.override(CQ.html5.form.SmartImage, {
    syncFormElements: function() {
        if(!this.fileNameField.getEl().dom){
            return;
        }

        CQ.html5.form.SmartImage.superclass.syncFormElements.call(this);

        var toolCnt = this.imageToolDefs.length;

        for (var toolIndex = 0; toolIndex < toolCnt; toolIndex++) {
            var toolToProcess = this.imageToolDefs[toolIndex];
            toolToProcess.transferToField();
        }
    } ,

    processRecord: function(record, path){
        CQ.html5.form.SmartImage.superclass.processRecord.call(this,record, path);
        var imagePanel = this.ownerCt;

        if(record.data[imagePanel.imageConfig.parentName]){
            imagePanel.setVisible(true);

            var widget = imagePanel.ownerCt;

            if(widget.imagePanels[0] == imagePanel){
                return;
            }

            var poolPanels = widget.poolPanels;

            widget.poolPanels = poolPanels.splice(1, poolPanels.length );
            widget.imagePanels.push(imagePanel);
        }
    }
});

MyClientLib.MultiImageWidget = CQ.Ext.extend(CQ.Ext.Panel, {
    BASE_ID : 'MultiImageWidgetPanel',
    imagePanels: [],
    poolPanels: [],

    constructor: function(config){
        config = config || {};

        var defaults = { "layout" : "form", border: false };
        config = CQ.Util.applyDefaults(config, defaults);

        MyClientLib.MultiImageWidget.superclass.constructor.call(this, config);
    },

    getImageConfig: function(suffix){
        var config = CQ.Util.copyObject(this.imageConfig);

        config.id = this.BASE_ID + "-" + suffix;

        var parentPrefix = config.parentPrefix;

        if(!parentPrefix){
            parentPrefix = "demo";
        }

        parentPrefix = parentPrefix + "-" + suffix;
        config.parentName = parentPrefix;

        var changeParams = ["cropParameter", "fileNameParameter","fileReferenceParameter","mapParameter","rotateParameter","name"];

        CQ.Ext.each(changeParams, function(cItem){
            config[cItem] = "./" + parentPrefix + "/" + config[cItem];
        });

        config.xtype = "html5smartimage";

        return config;
    },

    initComponent: function () {
        MyClientLib.MultiImageWidget.superclass.initComponent.call(this);

        var imagePoolMax = this.imagePoolMax;

        if(!imagePoolMax){
            this.imagePoolMax = 10;
        }

        var suffix = 1;

        var image = new MyClientLib.MultiImage({imageConfig : this.getImageConfig(suffix++)});
        this.imagePanels.push(image);
        this.add(image);

        var pooledImage;

        for(var i = 0; i < this.imagePoolMax - 1; i++){
            pooledImage = new MyClientLib.MultiImage({imageConfig : this.getImageConfig(suffix++)});
            pooledImage.setVisible(false);

            this.poolPanels.push(pooledImage);
            this.add(pooledImage);
        }

        var dialog = this.findParentByType('dialog');
        var widget = this;

        dialog.on('beforesubmit', function(){
            CQ.Ext.each(this.poolPanels, function(i){
                widget.remove(i, true);
            });
        },this);
    },

    setValue: function (value) {
    }
});

CQ.Ext.reg("multiimagewidget", MyClientLib.MultiImageWidget);

MyClientLib.MultiImage = CQ.Ext.extend(CQ.Ext.Panel, {
    imageConfig: '',
    style: "margin-bottom: 10px",

    tools: [{
        id: "plus",
        handler: function(e, toolEl, panel, tc){
            var widget = panel.ownerCt;
            var poolPanels = widget.poolPanels;

            var image = poolPanels[0];
            image.setVisible(true);

            widget.poolPanels = poolPanels.splice(1, poolPanels.length );
            widget.imagePanels.push(image);
        }
    },{
        id: "toggle",
        handler: function(e, toolEl, panel, tc){
            alert("Reorder up is a work in progress, and the icon is different as the .x-tool-top is not available in cq css")
        }
    },{
        id: "down",
        handler: function(e, toolEl, panel, tc){
            alert("Reorder down is a work in progress")
        }
    },{
        id: "minus",
        handler: function(e, toolEl, panel, tc){
            var widget = panel.ownerCt;

            var image = panel.find('xtype', 'html5smartimage')[0];
            var path = image.dataPath + "/" + panel.imageConfig.parentName;

            $.ajax({
                url: path,
                dataType: "json",
                type: 'DELETE',
                success: function(data){

                }
            });

            widget.remove(panel, true);
        }
    }],

    constructor: function(config){
        config = config || {};

        MyClientLib.MultiImage.superclass.constructor.call(this, config);
    },

    initComponent: function () {
        MyClientLib.MultiImage.superclass.initComponent.call(this);

        var config = this.imageConfig;
        this.add(config);
    },

    validate: function(){
        return true;
    },

    getName: function(){
        return this.name;
    }
});

CQ.Ext.reg("multiimagepanel", MyClientLib.MultiImage);

5) Add the following code to /apps/multifieldimage/image/image.jsp

<%@include file="/libs/foundation/global.jsp"%>

<%@ page import="java.util.Iterator" %>

<%
    Iterator<Resource> children = resource.listChildren();
    Resource res = null;

    if(!children.hasNext()){
%>
    Configure Images

<%
    }

    while(children.hasNext()){
        res = children.next();
%>
        Image

        <img src="<%=res.adaptTo(Node.class).getProperty("imageReference").getString()%>">

<%
    }
%>


3 comments:

  1. Hi, can you please explain why there is a need to override syncformelements and processrecord methods and what you're doing in the overridden method?

    For my case, I am not trying a multifield implementation. I am trying to clone a panel with a html5smartimage in it and add the cloned panel to a tabpanel (when author clicks a button). When submitting the dialog, I get an error like "this.fileNameField.getEl().dom is undefined" in syncformelements method. In your code above, you are avoiding .dom field being empty in overridden syncformelements.

    I would like to know why this error occurs and whether your overriding is a workaround for it.

    ReplyDelete
  2. In my opinion, you will understand homework importance after reading https://college-homework-help.org/blog/why-is-homework-important. It was really helpful at least for me.

    ReplyDelete
  3. There are so many great things you can do if you have some free time. We at Royalessays service help students to write their essay and any other writing assignments. Studying can be so much easier when you know who you should ask for help.

    ReplyDelete