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.