Goal
Create a component with Touch UI (Coral UI) Dialog and Multifield (granite/ui/components/foundation/form/multifield) widget. Here we extend product multifield and create a composite multifield /apps/touch-ui-multi-field-panel/multifield , configure it with two form fields, a Textfield (granite/ui/components/foundation/form/textfield), Pathbrowser (granite/ui/components/foundation/form/pathbrowser). End result is a simple Dashboard Component
Classic UI Multifield is available here
Touch UI Multifield storing values as child nodes is available here
For Touch UI Image Multifield check this post
Nested multifield (or Multi multifield) configuration is available here
Demo | Package Install | Package Install with widget code added in clientlib
Bug Fixes
Adding Composite Multifield in multiple Tabs doesn't work - Demo | Package Install
Touch UI Dialog
Dialog Structure in CRX
Solution
1) Login to CRXDE Lite (http://localhost:4502/crx/de) and create a folder (nt:folder) /apps/touch-ui-multi-field-panel
2) Create a component (cq:Component) /apps/touch-ui-multi-field-panel/sample-multi-field. Check this post on how to create a CQ component
3) Add the following xml for /apps/touch-ui-multi-field-panel/sample-multi-field/dialog created. Ideally a cq:Dialog should be configured with necessary widgets for displaying dialog in Classic UI. This post is on Touch UI dialogs so keep it simple (node required for opening the touch ui dialog created in next step)
<?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" title="Multi Field" xtype="dialog"/>
4) Create a nt:unstructured node /apps/touch-ui-multi-field-panel/sample-multi-field/cq:dialog with following xml (the dialog UI as xml)
<?xml version="1.0" encoding="UTF-8"?> <jcr:root xmlns:sling="http://sling.apache.org/jcr/sling/1.0" xmlns:cq="http://www.day.com/jcr/cq/1.0" xmlns:jcr="http://www.jcp.org/jcr/1.0" xmlns:nt="http://www.jcp.org/jcr/nt/1.0" jcr:primaryType="nt:unstructured" jcr:title="Multifield TouchUI Component" sling:resourceType="cq/gui/components/authoring/dialog" helpPath="en/cq/current/wcm/default_components.html#Text"> <content jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/container"> <layout jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns"/> <items jcr:primaryType="nt:unstructured"> <column jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/container"> <items jcr:primaryType="nt:unstructured"> <fieldset jcr:primaryType="nt:unstructured" jcr:title="Sample Dashboard" sling:resourceType="granite/ui/components/foundation/form/fieldset"> <layout jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns"/> <items jcr:primaryType="nt:unstructured"> <column jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/container"> <items jcr:primaryType="nt:unstructured"> <dashboard jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/form/textfield" fieldDescription="Enter Dashboard name" fieldLabel="Dashboard name" name="./dashboard"/> <pages jcr:primaryType="nt:unstructured" sling:resourceType="/apps/touch-ui-multi-field-panel/multifield" class="full-width" fieldDescription="Click '+' to add a new page" fieldLabel="URLs"> <field jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/form/fieldset" name="./items"> <layout jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/layouts/fixedcolumns" method="absolute"/> <items jcr:primaryType="nt:unstructured"> <column jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/container"> <items jcr:primaryType="nt:unstructured"> <page jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/form/textfield" fieldDescription="Enter Page Name" fieldLabel="Page Name" name="./page"/> <path jcr:primaryType="nt:unstructured" sling:resourceType="granite/ui/components/foundation/form/pathbrowser" fieldDescription="Select Path" fieldLabel="Path" name="./path" rootPath="/content"/> </items> </column> </items> </field> </pages> </items> </column> </items> </fieldset> </items> </column> </items> </content> </jcr:root>
5) The composite multifield node /apps/touch-ui-multi-field-panel/sample-multi-field/cq:dialog/content/items/column/items/fieldset/items/column/items/pages is of extended multifield type /apps/touch-ui-multi-field-panel/multifield (created in next steps)
6) Different layouts can be used to structure the multifield, here we use granite/ui/components/foundation/layouts/fixedcolumns layout
7) Create folder (sling:Folder) /apps/touch-ui-multi-field-panel/multifield
8) Create file (nt:file) /apps/touch-ui-multi-field-panel/multifield/multifield.jsp (for rendering the widget) and add following code. Inline JS was added for simplicity, its always recommended to create a clientlib (cq:ClientLibraryFolder) for javascript code
<%@ page import="com.adobe.granite.ui.components.Config" %> <%@ page import="org.slf4j.Logger" %> <%@ page import="org.slf4j.LoggerFactory" %> <%@ page import="com.adobe.granite.ui.components.Value" %> <%@ page import="org.apache.commons.lang3.StringUtils" %> <%@include file="/libs/granite/ui/global.jsp" %> <%--include ootb multifield--%> <sling:include resourceType="/libs/granite/ui/components/foundation/form/multifield"/> <%! private final Logger mLog = LoggerFactory.getLogger(this.getClass()); %> <% Config mCfg = cmp.getConfig(); Resource mField = mCfg.getChild("field"); if (mField == null) { mLog.warn("Field node doesn't exist"); return; } ValueMap mVM = mField.adaptTo(ValueMap.class); String mName = mVM.get("name", ""); if ("".equals(mName)) { mLog.warn("name property doesn't exist on field node"); return; } Value mValue = ((ComponentHelper) cmp).getValue(); //get the values added in multifield String[] mItems = mValue.get(mName, String[].class); %> <script> (function () { //function to add values into multifield widgets. The values are stored in CRX by collectDataFromFields() as json //eg. {"page":"English","path":"/content/geometrixx/en"} var addDataInFields = function () { var mValues = [ <%= StringUtils.join(mValue.get(mName, String[].class), ",") %> ], mName = '<%=mName%>', $fieldSets = $("[class='coral-Form-fieldset'][data-name='" + mName + "']"); var record, $fields, $field, name; $fieldSets.each(function (i, fieldSet) { $fields = $(fieldSet).find("[name]"); record = mValues[i]; if (!record) { return; } $fields.each(function (j, field) { $field = $(field); name = $field.attr("name"); if (!name) { return; } //strip ./ if (name.indexOf("./") == 0) { name = name.substring(2); } $field.val(record[name]); }); }); }; //collect data from widgets in multifield and POST them to CRX as JSON var collectDataFromFields = function(){ $(document).on("click", ".cq-dialog-submit", function () { var $form = $(this).closest("form.foundation-form"), mName = '<%=mName%>'; //get all the input fields of multifield var $fieldSets = $("[class='coral-Form-fieldset'][data-name='" + mName + "']"); var record, $fields, $field, name; $fieldSets.each(function (i, fieldSet) { $fields = $(fieldSet).find("[name]"); record = {}; $fields.each(function (j, field) { $field = $(field); name = $field.attr("name"); if (!name) { return; } //strip ./ if (name.indexOf("./") == 0) { name = name.substring(2); } record[name] = $field.val(); //remove the field, so that individual values are not POSTed $field.remove(); }); if ($.isEmptyObject(record)) { return; } //add the record JSON in a hidden field as string $('<input />').attr('type', 'hidden') .attr('name', mName) .attr('value', JSON.stringify(record)) .appendTo($form); }); }); }; $(document).ready(function () { addDataInFields(); collectDataFromFields(); }); })(); </script>
9) sling:include at #9 includes the product multifield /libs/granite/ui/components/foundation/form/multifield after which the code necessary for creating a composite multifield is added
10) The function collectDataFromFields() at #80 registers a click listener on dialog submit, to collect the multifield form data, create a json to group it and add in a hidden field before data is POSTed to CRX. The function addDataInFields() at #44 reads json data added previously and fills the multifield fields, when dialog is reopened. The extension uses simple jquery calls to get and set data; based on the widget type more code may be necessary for supporting complex Granite UI widgets (granite/ui/components/foundation/form)
11) Create the component jsp /apps/touch-ui-multi-field-panel/sample-multi-field/sample-multi-field.jsp for rendering data entered in dialog
<%@ page import="org.apache.sling.commons.json.JSONObject" %> <%@ page import="java.io.PrintWriter" %> <%@include file="/libs/foundation/global.jsp" %> <%@page session="false" %> <div style="display: block; border-style: solid; border-width: 1px; margin: 10px; padding: 10px"> <b>Multi Field Sample Dashboard</b> <% try { Property property = null; if (currentNode.hasProperty("items")) { property = currentNode.getProperty("items"); } if (property != null) { JSONObject obj = null; Value[] values = null; if (property.isMultiple()) { values = property.getValues(); } else { values = new Value[1]; values[0] = property.getValue(); } for (Value val : values) { obj = new JSONObject(val.getString()); %> Page : <b><%= obj.get("page") %></b>, URL : <b><a href="<%= obj.get("path") %>.html" target="_blank"><%= obj.get("path") %></a></b> <% } } else { %> Add values in dialog <% } } catch (Exception e) { e.printStackTrace(new PrintWriter(out)); } %> </div>
Great example! It's very generic and suitable to any kind of needs I can imagine. And because of this, comes the unpleasant question: why this widget has not been included into the Service Pack 2 between the components of the foundation?
ReplyDeleteThanks,
Alessandro
Hi Sreekanth ,
ReplyDeletei think i found a small bug during my tests.
I am not able to delete the last element of a multifield.
Can you confirm that?
Thanks
Axel
hello Axel, thanks for testing it out, yeah last element or more precisely if there is only one element left in the multifield, deleting it wouldn't make it go away. save and open the dialog, it still exists... i think it's a product multifield bug, check page properties -> vanity url multifield
DeleteThis comment has been removed by the author.
DeleteHey guys, I found a workaround for this, just have a node under the items node (at the same level where you have the cutom multi field node) and add the following properties to it:
Deletename="./[name]@Delete"
value="null"
This did the trick for me.
Happy Coding :)
Hi Vinay,
DeleteI have the same problem and I'll try this out.
Do you know if this is a 'hack' to get it to work, or is it an undocumented requirement?
Hi Chris,
DeleteI had a similar situation with checkbox in general(outside of multifield) and found that the solution works. I found similar fix/implementation in many OOTB components(/libs/commerce/components/productpageproxy/cq:dialog/content/items/columns/items/tabs/items/advanced/items/cug/items/deleteenable) and tried this on multifield too and it worked well for me; seems like some js in the framework reads for @Delete names and removes corresponding property when saving dialog values.
Since its available in OOTB components, i am not sure if I can call it a "hack" :)
Hello Sreekanth,
ReplyDeleteI was creating multifield today. My use case was that i had to configure two tabs. Both the tabs have multifield and they are exactly the same but one tab configuration was for IOS and other for android. When i am entering and saving the values within both the tabs, they are combining into the first tab. The name parameter of the multifield is different for both the tabs and i was hoping to see two different properties on the content node.
For example,
the value are configured into iosItems or androidConfig whichever tab appears first in the list . Is it a product bug?
Dialog File http://tempsend.com/B11E7E925B
Screenshot for how data is being saved. -: http://tempsend.com/0B33D34C73
Can you please help? You will have to change the tab configuration, to point to your local project for testing.
Rahul, i see that the non-clientlib version works fine for multiple tabs, demo here - https://drive.google.com/file/d/0B4d6KmbLkAumWEdGZUU2a1pMQkk/view?usp=sharing
DeleteThere was a bug in clientlib one, fixed (check the demo and package install in "Bug Fixes" section above)
Hey Sreekanth,
ReplyDeleteThat did resolve the issue. Thanks a ton :)
Cheers
Hi,
ReplyDeleteI am facing issue in custom multifield component.
can i have source code for both touch ui and classic ui and they both should sync up in case of custom multifield
This is great! However, I'm having issues getting the demo to work in 6.1. Getting the 'Uncaught RangeError: Maximum call stack size exceeded'. Same is happening with nested multifield demo [0]. Any ideas? Thanks in advance.
ReplyDelete[0] http://experience-aem.blogspot.com/2015/03/aem-6-sp2-touch-ui-coral-ui-nested-multi-multifield.html
Nevermind. Got this working by removing the previously installed nested event and composite ones, then clearing /var/clientlibs and browser cache
DeleteHi Sreekanth,
ReplyDeleteHow to make rich text mandatory or required in touch ui of AEM? Need help.
Hello satya, check this http://experience-aem.blogspot.com/2015/08/aem-61-support-required-property-on-richtext-editor-rte-of-touchui.html
DeleteHelped a lot thanks sree
DeleteI have rich text in touch ui which has an option of required plugins in full view,but how can i get those plugins rendering in touch ui rich text multifield?
DeleteHi Sreekanth, Thanks for the article, I am trying to move the custom multifield widget into touch ui cq:dialog from the dialog. Could you please provide an example for converting custom multifield widget into touch ui dialog. Also if you can provide me more details about the richtext editor in the dialog as I am not able to convert this as well into the granite touch ui dialog. Thanks
ReplyDeleteSandy, check the dialog conversion tool..
Deletehttp://docs.adobe.com/docs/en/aem/6-1/develop/dev-tools/dialog-conversion.html
https://github.com/Adobe-Consulting-Services/acs-aem-commons/blob/master/bundle/src/main/java/com/adobe/acs/commons/util/impl/MultifieldPanelDialogRewriteRule.java
Thanks, this is helpful. I'm trying to set a default value in the .content.xml, and it's not working - the multifield just defaults to empty. Is that something that should work with this code (so I'm doing something wrong), or would I need to tweak it?
ReplyDeleteAwesome..Followed the same steps as you mentioned in the post. Working fine. Thanks alot.
ReplyDeleteI got another requirement, instead of saving the multi composite data as json object I need to save it as string arrays.
Example: empName: John, Peter, White and location: India, UK, US.
The empName and location should be stored as string arrays and as two different properties in content node.
Please help me in achieving this.
Thanks,
Sravani
Hi Sreekanth,
DeleteAny update on this? Waiting for your reply.
Thanks in Advance.
Sravani Vagicharla.
This comment has been removed by the author.
ReplyDeletetry this http://experience-aem.blogspot.com/2015/11/aem-61-sample-granite-widget-in-touch-ui-extending-multifield-to-limit-items.html
DeleteHi Sreekanth,
ReplyDeleteCreated new Touch UI Workflow Dialog, which has multifield widget in it, it has select box as child, When clicking on "Add Field" button in the "multi field" widget, it's not loading select box,but throwing following exception/warning in console.
"An instance of Select is already attached to the specified target element. Future versions of CoralUI will throw an exception at this point."
I tried adding text box in the multi field, resulting in same problem, Does the workflow dialog support multi field widget or Am i doing any thing wrong?
Posted my question in AEM forums ,if you need more info please have a look
http://help-forums.adobe.com/content/adobeforums/en/experience-manager-forum/adobe-experience-manager.topic.html/forum__ez9u-aem_6_1_createda.html
Thanks,
Chandra
Chandra, you'll find a sample of select in multifield here (check the bug fixes section) - http://experience-aem.blogspot.com/2015/06/aem-61-touch-ui-composite-multifield-store-values-as-child-nodes.html
DeleteCan you upload a package of your workflow dialog to dropbox and share the url here....
This is not working for check box.
ReplyDeleteVasim, try this out:
Deletein the addDataInFields method of the javascript code replace line 74 with:
if ($field[0].className == "coral-Checkbox-input" && record[name]) {
$(field).prop('checked', true);
} else {
$field.val(record[name]);
}
and line 108 (collectDataFromFields function) with:
if ($field[0].className == "coral-Checkbox-input") {
if ($(field).is(':checked')) {
record[name] = $(field).val();
} else {
record[name] = "";
}
} else {
record[name] = $field.val();
}
This comment has been removed by the author.
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteHi! Followed all the steps given in the post, but how do I test it? there no site in the content tab in aem( I have no idea how AEM works)
ReplyDeleteThis comment has been removed by the author.
ReplyDeleteHi Sreekanth, I am using Bug fix version in my application. I am facing strange issue
ReplyDeleteIssue: When First time dialog open then data entries not filled in form (because dialog-ready event not trigger) but when I open dialog again then everything works fine.
Can you please help why dialog is not in ready state at first time and how to resolve this issue ?
Gourav,
DeleteI am facing the same issue. Did you get a solution for this problem.
Hi ,
ReplyDeleteIn my case in classic ui dialog content is stored in string format as below
name - raul, ram
place - delhi, noida
but in touch ui dialog using sling resource type - granite/multifield data is store as above
but while editing dialog its not gettig pre-populated with stored value
so what js we can write to prepoulate from string array.
Above example is to store content and prepoulate from JSON, but I need from String Array
Hello,
ReplyDeleteI Created multifield in touch ui dialog, but some times my content is getting cleared. Can anyone help with this situation
Hello,
ReplyDeleteI Created multifield in touch ui dialog, but some times my content is getting cleared. Can anyone help with this situation
Hi,
ReplyDeleteCan we have multifield within a multifield?
Hello, Am facing problem when i use this multifield for page properties dialog. Can someone tell me how can i resolve this issue.
ReplyDeleteSreekanth, Can you please provide a component which works in AEM 6.0, where a composite multifield stores a value in JSON string. It stores as individual array values.
ReplyDeletePOST on dialog close does not send the value of json string along with form data.
Hello Sreekanth ,
ReplyDeleteI want to use both Touch UI Multifield storing values as child nodes and Touch UI Multifield in my project . But problem is both js has same name and categories is also kept as same . So how to tackle this
Hi Sreekanth,
ReplyDeleteI have a requirement to add mandatory field. I tried multiple things to amend the code but nothing worked. Can you assist and provide a script to make attributes mandatory?
Hi Suri, were you able to make the field mandatory ? I am also facing the same issue, could you please let me know ?
Delete