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
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
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
5) Add the following code to /apps/multifieldimage/image/image.jsp
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()%>">
<%
}
%>

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