. But this widget can't handle the multiple fields.
Create new composite multi field widget which displays each field(set) in a separate tab. The tabular separation of field sets helps to organize the fields easily rather than showing each field set one after the other vertically
<%@include file="/libs/granite/ui/global.jsp" %><%
%><%@page session="false"
import="java.util.HashMap,
java.util.Iterator,
java.util.List,
java.util.ArrayList,
org.apache.sling.api.resource.ResourceResolver,
org.apache.sling.api.wrappers.ValueMapDecorator,
com.adobe.granite.ui.components.AttrBuilder,
com.adobe.granite.ui.components.ComponentHelper,
com.adobe.granite.ui.components.ComponentHelper.Options,
com.adobe.granite.ui.components.Config,
com.adobe.granite.ui.components.Field,
com.adobe.granite.ui.components.Tag,
com.adobe.granite.ui.components.Value" %><%--###
Example::
+ myinput
- name = "myTabularMultiField"
- sling:resourceType = "/apps/authoring/touchui/widgets/tabular-multifield"
+ field
- sling:resourceType = "granite/ui/components/foundation/container"
+ items
+ pathfield
- sling:resourceType = "granite/ui/components/foundation/form/pathbrowser"
- name = "./one"
+ textfield
- sling:resourceType = "granite/ui/components/foundation/form/textfield"
- name = "./two"
###--%>
<%
Config cfg = cmp.getConfig();
ValueMap vm = (ValueMap) request.getAttribute(Field.class.getName());
Tag tag = cmp.consumeTag();
AttrBuilder attrs = tag.getAttrs();
attrs.add("id", cfg.get("id", String.class));
attrs.addRel(cfg.get("rel", String.class));
attrs.addClass(cfg.get("class", String.class));
attrs.add("title", i18n.getVar(cfg.get("title", String.class)));
String name = cfg.get("name", String.class);
attrs.add("name", name);
attrs.addClass("tabular-Multifield");
attrs.add("data-init", "tabularmultifield");
attrs.addOthers(cfg.getProperties(), "id", "rel", "name", "class", "title", "fieldLabel", "fieldDescription", "renderReadOnly");
Resource field = cfg.getChild("field");
List<TabInfo> tabsInfo = getTabsInfo(request, resourceResolver, name);
%><div <%= attrs.build() %>><%
if (cfg.get("deleteHint", true)) {
for (TabInfo tabInfo : tabsInfo) {
%><input type="hidden" name="./<%= name != null ? name + "/" : "" + tabInfo.getNodeName() %>@Delete"><%
}
}
%>
<div class="coral-TabPanel" data-init="tabs">
<nav class="coral-TabPanel-navigation">
<%for (TabInfo tabInfo : tabsInfo) {%>
<a class="coral-TabPanel-tab" href="#" data-toggle="tab">
<span><%=tabInfo.getName()%><span>
<i class="close-tab-button js-tabular-Multifield-remove coral-Icon coral-Icon--close coral-Icon--sizeXS"></i>
</span></span>
</a>
<%}%>
<a class="coral-TabPanel-tab js-tabular-Multifield-add" href="#" data-target="#field-add" data-toggle="tab">
<i class="coral-Icon coral-Icon--add coral-Icon--sizeXS"></i>
</a>
</nav>
<div class="coral-TabPanel-content">
<%for (TabInfo tabInfo : tabsInfo) {%>
<section class="coral-TabPanel-pane" role="tabpanel">
<% include(field, tabInfo.getValues(), cmp, request); %>
</section>
<%}%>
<section class="dummy-section coral-TabPanel-pane" style="display:none">
</section>
</div>
</div>
<script class="js-tabular-Multifield-input-template" type="text/html"><% include(field, cmp, request); %></script>
</div><%!
private void include(Resource field, ComponentHelper cmp, HttpServletRequest request) throws Exception {
// include the field with no value set at all
ValueMap existingVM = (ValueMap) request.getAttribute(Value.FORM_VALUESS_ATTRIBUTE);
String existingPath = (String) request.getAttribute(Value.CONTENTPATH_ATTRIBUTE);
request.removeAttribute(Value.FORM_VALUESS_ATTRIBUTE);
request.removeAttribute(Value.CONTENTPATH_ATTRIBUTE);
cmp.include(field, new Options().rootField(false));
request.setAttribute(Value.FORM_VALUESS_ATTRIBUTE, existingVM);
request.setAttribute(Value.CONTENTPATH_ATTRIBUTE, existingPath);
}
private void include(Resource field, ValueMap map, ComponentHelper cmp, HttpServletRequest request) throws Exception {
ValueMap existing = (ValueMap) request.getAttribute(Value.FORM_VALUESS_ATTRIBUTE);
request.setAttribute(Value.FORM_VALUESS_ATTRIBUTE, map);
cmp.include(field, new Options().rootField(false));
request.setAttribute(Value.FORM_VALUESS_ATTRIBUTE, existing);
}
private List<TabInfo> getTabsInfo(HttpServletRequest request, ResourceResolver resolver, String name) throws Exception {
List<TabInfo> tabsInfo = new ArrayList<TabInfo>();
String contentPath = (String) request.getAttribute(Value.CONTENTPATH_ATTRIBUTE);
if (name != null) {
contentPath = contentPath + "/" + name;
}
Resource contentRes = resolver.getResource(contentPath);
if (contentRes != null) {
for (Iterator<Resource> it = contentRes.listChildren(); it.hasNext(); ) {
Resource r = it.next();
String rName = r.getName();
if (rName.startsWith("tab")) {
String tabIndex = rName.replace("tab", "");
String tabName = "Tab " + tabIndex;
ValueMap vm = r.adaptTo(ValueMap.class);
TabInfo tabInfo = new TabInfo(tabName, rName, vm);
tabsInfo.add(tabInfo);
}
}
}
return tabsInfo;
}
class TabInfo {
private String name;
private String nodeName;
private ValueMap values;
TabInfo(String name, String nodeName, ValueMap values) {
this.name = name;
this.nodeName = nodeName;
this.values = values;
}
String getName() {
return name;
}
String getNodeName() {
return nodeName;
}
ValueMap getValues() {
return values;
}
}
%>
2. Create "cq.authoring.dialog" clientlibs in /apps/authoring/touchui/widgets/tabular-multifield/clientlibs
- create js.txt in the clientlibs folder with the following content
#base=js
tabular-multi-field.js
- create css.txt in the clientlibs folder with the following content
#base=css
tabular-multi-field.css
- create tabular-multi-field.js file at /apps/authoring/touchui/widgets/tabular-multifield/clientlibs/js/tabular-multi-field.js with the following content
(function ($, console) {
"use strict";
var listTemplate =
"<div class=\"coral-TabPanel\" data-init=\"tabs\"><nav class=\"coral-TabPanel-navigation\"></nav><div class=\"coral-TabPanel-content\"></div></div>";
CUI.TabularMultifield = new Class({
toString: "TabularMultifield",
extend: CUI.Widget,
/**
@extends CUI.Widget
@classdesc A composite field that allows you to add/remove multiple instances of a component.
The component is added based on a template defined in a <code><script type="text/html"></code> tag.
The current added components are managed inside a <code>div.tabular-Multifield</code> element.
@desc Creates a TabularMultifield component.
@constructs
*/
construct: function (options) {
this.script = this.$element.find(".js-tabular-Multifield-input-template");
this.tabsContainer = this.$element.find(".coral-TabPanel").data("tabs");
var prevTabIndex = 0;
var tabs = this.tabsContainer._getTabs();
tabs.each(function() {
var $this = $(this);
var i = $this.text().trim().replace("Tab ", "");
if (!isNaN(i)) {
var index = parseInt(i);
if (index > prevTabIndex) {
prevTabIndex = index;
}
}
});
this.tabIndex = prevTabIndex === 0 ? 1: prevTabIndex + 1;
if (this.tabsContainer.length === 0) {
this.tabsContainer = $(listTemplate).prependTo(this.$element).find(".coral-TabPanel").data("tabs");
}
var tabularCompositeFieldName = this.$element.attr("name") || "";
if (tabularCompositeFieldName) {
tabularCompositeFieldName+= "/";
}
var panels = this.tabsContainer._getPanels();
var self = this;
panels.each(function(){
var $this = $(this);
var panelIndex = $this.index();
var tabNumber = $(tabs[panelIndex]).text().trim().replace("Tab ", "");
self.adjustInputNames($this, tabularCompositeFieldName + "tab" + tabNumber);
self._handleRadioButtons($this);
});
this._adjustMarkup();
this._addListeners();
},
_handleRadioButtons: function(item) {
item.find("input[type='radio']").each(function(){
var $this = $(this);
if ($this.attr("checked") == "checked") {
$this.attr("aria-selected", true);
$this.prop("checked", "checked");
}
});
},
/**
* Enhances the markup required for multifield.
* @private
*/
_adjustMarkup: function () {
this.$element.addClass("coral-Multifield");
},
/**
* Initializes listeners.
* @private
*/
_addListeners: function () {
var self = this;
var tabularCompositeFieldName = self.$element.attr("name") || "";
if (tabularCompositeFieldName) {
tabularCompositeFieldName+= "/";
}
//add
this.$element.on("click", ".js-tabular-Multifield-add", function (e) {
var item = $(self.script.html().trim());
self.adjustInputNames(item, tabularCompositeFieldName + "tab" + self.tabIndex);
self.handleCheckBoxesDefaultValue(item);
var tabCount = self.tabsContainer._getTabs().length;
var tabContent = "<span>Tab " + self.tabIndex + "<span> <i class=\"close-tab-button js-tabular-Multifield-remove coral-Icon coral-Icon--close coral-Icon--sizeXS\"></i>"
self.tabsContainer.addItem({tabContent:tabContent, panelContent:item, index:tabCount-1});
self.tabIndex++;
$(self.$element).trigger("cui-contentloaded");
});
//remove
this.$element.on("click", ".js-tabular-Multifield-remove", function (e) {
var $this = $(this);
var tabIndex = $(this).closest(".coral-TabPanel-tab.is-active").index();
self.tabsContainer.removeItem(tabIndex);
var tabCount = self.tabsContainer._getTabs().length;
if (tabCount === 1) {
self.tabIndex = 1;
}
});
},
handleCheckBoxesDefaultValue: function(item) {
item.find("input[type='checkbox'][data-check-in-multifield='true']").each(function(){
var $this = $(this);
$this.prop("checked", true);
});
},
adjustInputNames: function (item, prefix) {
item.find("[name]").each(function(){
var $this = $(this);
var name = $this.attr("name");
$this.attr("name", "./" + prefix + name.substr(1));
});
}
});
CUI.Widget.registry.register("tabularmultifield", CUI.TabularMultifield);
if (CUI.options.dataAPI) {
$(document).on("cui-contentloaded.data-api", function (e) {
CUI.TabularMultifield.init($("[data-init~=tabularmultifield]", e.target));
});
}
})(jQuery, window.console);
- create tabular-multi-field.css at /apps/authoring/touchui/widgets/tabular-multifield/clientlibs/css/tabular-multi-field.css with the following content
.tabular-Multifield a[data-toggle="tab"] i {
font-size: 0.5rem;
padding-right: 1rem;
padding-left: 0.5rem;
}
.tabular-Multifield a[data-toggle="tab"] {
text-transform: none;
}
Now you can use this multi field widget in authoring dialog, with the following example structure:
<tabular_multifield
jcr:primaryType="nt:unstructured"
sling:resourceType="/apps/authoring/touchui/widgets/tabular-multifield"
fieldLabel="My Tabular Multi Field"
name="tabularFields">
<field
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/container">
<items jcr:primaryType="nt:unstructured">
<one
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/pathbrowser"
fieldLabel="path browser"
name="./path"
rootPath="/content"/>
<two
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/textfield"
fieldLabel="text field"
name="./text"/>
<position
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/radiogroup"
fieldDescription="alignment"
fieldLabel="Choose"
text="Alignment">
<items jcr:primaryType="nt:unstructured">
<left
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/radio"
class="slide-item"
name="./overlayPosition"
text="Left"
value="left"/>
<center
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/radio"
checked="true"
class="slide-item"
name="./overlayPosition"
text="Center"
value="center"/>
<right
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/radio"
class="slide-item"
name="./overlayPosition"
text="Right"
value="right"/>
</items>
</position>
<checkbox
jcr:primaryType="nt:unstructured"
sling:resourceType="granite/ui/components/foundation/form/checkbox"
check-in-multifield="{Boolean}true"
fieldDescription="My Checkbox"
fieldLabel="checkbox field"
name="./mycheckBox"
text="My Checkbox"
value="checked"/>
</items>
</field>
</tabular_multifield>