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()%>">

<%
    }
%>


1 comment:

  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