Skip to content

Commit

Permalink
Implemented: Add group option on drow-down in xml screen form (OFBIZ-…
Browse files Browse the repository at this point in the history
…13157)

When you define a drop-down field in xml form, add new optional level with group-options to organize options.

        <field name="group">
            <drop-down>
                <group-options description="A">
                    <option key="A1" description="Ad1"/>
                    <option key="A2" description="Ad2"/>
                </group-options>
                <group-options description="B">
                    <option key="B1" description="Bd1"/>
                    <option key="B2" description="Bd2"/>
                </group-options>
            </drop-down>
        </field>

Group-options can receive all options type :

        <field name="roleTypeId">
            <drop-down>
                <group-options description="Customer roles">
                    <entity-options entity-name="RoleType">
                        <entity-constraint name="parentType" vale="CUSTOMER"/>
                    </entity-options>
                </group-options>
                <group-options description="Supplier roles">
                    <entity-options entity-name="RoleType">
                        <entity-constraint name="parentType" vale="SUPPLIER"/>
                    </entity-options>
                </group-options>
                <group-options description="Other roles">
                    <list-options list-name="otherRoles"/>
                </group-options>
            </drop-down>
        </field>

At this time only the drop-down is supported, group-option can works for radio and check but not implement on ftl macro library.
  • Loading branch information
nmalin committed Oct 21, 2024
1 parent b2fdc8c commit 180ccc3
Show file tree
Hide file tree
Showing 5 changed files with 293 additions and 28 deletions.
23 changes: 23 additions & 0 deletions framework/widget/dtd/widget-form.xsd
Original file line number Diff line number Diff line change
Expand Up @@ -873,6 +873,7 @@ under the License.
<xs:element name="check" substitutionGroup="AllFields">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element ref="group-options" />
<xs:element ref="entity-options" />
<xs:element ref="list-options" />
<xs:element ref="option" />
Expand Down Expand Up @@ -1100,6 +1101,7 @@ under the License.
<xs:sequence>
<xs:element ref="auto-complete" minOccurs="0" maxOccurs="1" />
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element ref="group-options" />
<xs:element ref="entity-options" />
<xs:element ref="list-options" />
<xs:element ref="option" />
Expand Down Expand Up @@ -1383,6 +1385,7 @@ under the License.
<xs:element name="radio" substitutionGroup="AllFields">
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element ref="group-options" />
<xs:element ref="entity-options" />
<xs:element ref="list-options" />
<xs:element ref="option" />
Expand Down Expand Up @@ -1671,6 +1674,26 @@ under the License.
<xs:attribute type="xs:string" name="field-name" use="required" />
</xs:complexType>
</xs:element>
<xs:element name="group-options">
<xs:annotation>
<xs:documentation>group the options </xs:documentation>
</xs:annotation>
<xs:complexType>
<xs:choice minOccurs="0" maxOccurs="unbounded">
<xs:element ref="group-options" />
<xs:element ref="entity-options" />
<xs:element ref="list-options" />
<xs:element ref="option" />
</xs:choice>
<xs:attribute type="xs:string" name="id" />
<xs:attribute type="xs:string" name="widget-style" />
<xs:attribute type="xs:string" name="description" default="${description}">
<xs:annotation>
<xs:documentation>Will be presented to the user with field values substituted using the ${} syntax.</xs:documentation>
</xs:annotation>
</xs:attribute>
</xs:complexType>
</xs:element>
<xs:element name="in-place-editor">
<xs:annotation>
<xs:documentation>Enables in place editon for the display field.</xs:documentation>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,7 @@
import java.util.Map;
import java.util.Optional;
import java.util.TimeZone;
import java.util.UUID;
import java.util.function.Consumer;
import java.util.function.Predicate;
import java.util.stream.Collectors;
Expand Down Expand Up @@ -2183,6 +2184,152 @@ public void renderFieldString(Appendable writer, Map<String, Object> context, Fo
}
}

public static class GroupOptions {
private final FlexibleStringExpander description;
private final FlexibleStringExpander id;
private final FlexibleStringExpander widgetStyle;

private final List<OptionSource> optionSources;
private final List<GroupOptions> groupOptions;

public GroupOptions(Element groupOptionsElement, ModelFormField modelFormField) {
super();
this.description = FlexibleStringExpander.getInstance(groupOptionsElement.getAttribute("description"));
this.id = FlexibleStringExpander.getInstance(groupOptionsElement.getAttribute("id"));
this.widgetStyle = FlexibleStringExpander.getInstance(groupOptionsElement.getAttribute("widgetStyle"));

List<? extends Element> childElements = UtilXml.childElementList(groupOptionsElement);
List<OptionSource> optionSources = new ArrayList<>();
List<GroupOptions> groupOptions = new ArrayList<>();
if (!childElements.isEmpty()) {
for (Element childElement : childElements) {
switch (childElement.getLocalName()) {
case "option":
optionSources.add(new SingleOption(childElement, modelFormField));
break;
case "list-options":
optionSources.add(new ListOptions(childElement, modelFormField));
break;
case "entity-options":
optionSources.add(new EntityOptions(childElement, modelFormField));
break;
case "group-options":
groupOptions.add(new GroupOptions(childElement, modelFormField));
break;
}
}
}
this.optionSources = Collections.unmodifiableList(optionSources);
this.groupOptions = Collections.unmodifiableList(groupOptions);
}

private GroupOptions(GroupOptions original, ModelFormField modelFormField) {
super();
this.description = original.description;
this.id = original.id;
this.widgetStyle = original.widgetStyle;
List<OptionSource> optionSources = new ArrayList<>(original.optionSources.size());
for (OptionSource source : original.optionSources) {
optionSources.add(source.copy(modelFormField));
}
this.optionSources = Collections.unmodifiableList(optionSources);
List<GroupOptions> groupOptions = new ArrayList<>(original.groupOptions.size());
for (GroupOptions group : original.groupOptions) {
groupOptions.add(group.copy(modelFormField));
}
this.groupOptions = Collections.unmodifiableList(groupOptions);
}

/**
* TODO
* @return
*/
public GroupOptions(ModelFormField modelFormField) {
super();
this.description = FlexibleStringExpander.getInstance("");
this.id = FlexibleStringExpander.getInstance("");
this.widgetStyle = FlexibleStringExpander.getInstance("");
this.optionSources = Collections.emptyList();
this.groupOptions = Collections.emptyList();
}

/**
* TODO
* @return
*/
public FlexibleStringExpander getDescription() {
return description;
}

/**
* TODO
* @return
*/
public String getDescription(Map<String, Object> context) {
return this.description.expandString(context);
}

/**
* TODO
* @return
*/
public FlexibleStringExpander getId() {
return id;
}

/**
* TODO
* @return
*/
public String getId(Map<String, Object> context) {
String id = this.id.expandString(context);
return UtilValidate.isNotEmpty(id) ? id
: UUID.randomUUID().toString().replace("-", "");
}

/**
* TODO
* @return
*/
public FlexibleStringExpander getWidgetStyle() {
return widgetStyle;
}

/**
* TODO
* @return
*/
public String getWidgetStyle(Map<String, Object> context) {
return this.widgetStyle.expandString(context);
}

/**
* TODO
* @return
*/
public List<OptionValue> getAllOptionValues(Map<String, Object> context, Delegator delegator) {
List<OptionValue> optionValues = new LinkedList<>();
for (OptionSource optionSource : this.optionSources) {
optionSource.addOptionValues(optionValues, context, delegator);
}
return optionValues;
}
/**
* TODO
* @return
*/
public List<GroupOptions> getGroupOptions() {
return groupOptions;
}

/**
* TODO
* @return
*/
public GroupOptions copy(ModelFormField modelFormField) {
return new GroupOptions(this, modelFormField);
}
}
/**
* Models the &lt;entity-options&gt; element.
* @see <code>widget-form.xsd</code>
Expand Down Expand Up @@ -2409,12 +2556,14 @@ public static String getDescriptionForOptionKey(String key, List<OptionValue> al

private final FlexibleStringExpander noCurrentSelectedKey;
private final List<OptionSource> optionSources;
private final List<GroupOptions> groupOptions;

public FieldInfoWithOptions(Element element, ModelFormField modelFormField) {
super(element, modelFormField);
this.noCurrentSelectedKey = FlexibleStringExpander.getInstance(element.getAttribute("no-current-selected-key"));
// read all option and entity-options sub-elements, maintaining order
ArrayList<OptionSource> optionSources = new ArrayList<>();
ArrayList<GroupOptions> groupSources = new ArrayList<>();
List<? extends Element> childElements = UtilXml.childElementList(element);
if (!childElements.isEmpty()) {
for (Element childElement : childElements) {
Expand All @@ -2425,6 +2574,8 @@ public FieldInfoWithOptions(Element element, ModelFormField modelFormField) {
optionSources.add(new ListOptions(childElement, modelFormField));
} else if ("entity-options".equals(childName)) {
optionSources.add(new EntityOptions(childElement, modelFormField));
} else if ("group-options".equals(childName)) {
groupSources.add(new GroupOptions(childElement, modelFormField));
}
}
} else {
Expand All @@ -2433,6 +2584,7 @@ public FieldInfoWithOptions(Element element, ModelFormField modelFormField) {
}
optionSources.trimToSize();
this.optionSources = Collections.unmodifiableList(optionSources);
this.groupOptions = Collections.unmodifiableList(groupSources);
}

// Copy constructor.
Expand All @@ -2448,18 +2600,25 @@ protected FieldInfoWithOptions(FieldInfoWithOptions original, ModelFormField mod
}
this.optionSources = Collections.unmodifiableList(optionSources);
}
List<GroupOptions> groupOptions = new ArrayList<>(original.groupOptions.size());
for (GroupOptions group: original.groupOptions) {
groupOptions.add(group.copy(modelFormField));
}
this.groupOptions = groupOptions;
}

protected FieldInfoWithOptions(int fieldSource, int fieldType, List<OptionSource> optionSources) {
super(fieldSource, fieldType, null);
this.noCurrentSelectedKey = FlexibleStringExpander.getInstance("");
this.optionSources = Collections.unmodifiableList(new ArrayList<>(optionSources));
this.groupOptions = Collections.emptyList();
}

public FieldInfoWithOptions(int fieldSource, int fieldType, ModelFormField modelFormField) {
super(fieldSource, fieldType, modelFormField);
this.noCurrentSelectedKey = FlexibleStringExpander.getInstance("");
this.optionSources = Collections.emptyList();
this.groupOptions = Collections.emptyList();
}

/**
Expand Down Expand Up @@ -2500,6 +2659,13 @@ public String getNoCurrentSelectedKey(Map<String, Object> context) {
public List<OptionSource> getOptionSources() {
return optionSources;
}
/**
* Gets group options.
* @return the group options
*/
public List<GroupOptions> getGroupOptions() {
return groupOptions;
}
}

/**
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,7 @@
import org.apache.ofbiz.widget.renderer.FormRenderer;
import org.apache.ofbiz.widget.renderer.Paginator;
import org.apache.ofbiz.widget.renderer.VisualTheme;
import org.apache.ofbiz.widget.renderer.macro.model.GroupOption;
import org.apache.ofbiz.widget.renderer.macro.model.Option;
import org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtl;
import org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtlMacroCall;
Expand Down Expand Up @@ -928,6 +929,7 @@ public RenderableFtl dropDownField(final Map<String, Object> context,
}

final var allOptionValues = dropDownField.getAllOptionValues(context, WidgetWorker.getDelegator(context));
final var allGroupValues = dropDownField.getGroupOptions();
final var explicitDescription =
// Populate explicitDescription with the description from the option associated with the current value.
allOptionValues.stream()
Expand Down Expand Up @@ -957,15 +959,11 @@ public RenderableFtl dropDownField(final Map<String, Object> context,
: UtilMisc.toList(currentValue))
: Collections.emptyList();

var optionsList = allOptionValues.stream()
.map(optionValue -> {
var encodedKey = encode(optionValue.getKey(), modelFormField, context);
var truncatedDescription = truncate(optionValue.getDescription(), textSizeOptional);
var selected = currentValuesList.contains(optionValue.getKey());

return new Option(encodedKey, truncatedDescription, selected);
})
.collect(Collectors.toList());
var optionsList = new ArrayList<>();
if (UtilValidate.isNotEmpty(allGroupValues)) {
optionsList.addAll(populateGroupAndOptions(context, allGroupValues, modelFormField, textSizeOptional, currentValuesList));
}
optionsList.addAll(populateOptions(context, allOptionValues, modelFormField, textSizeOptional, currentValuesList));

builder.objectParameter("options", optionsList);

Expand All @@ -989,6 +987,43 @@ public RenderableFtl dropDownField(final Map<String, Object> context,
return builder.build();
}

private List<Object> populateGroupAndOptions(Map<String, Object> context, List<ModelFormField.GroupOptions> allGroupOptions,
ModelFormField modelFormField, Optional<Integer> textSizeOptional, List<String> currentValuesList) {
if (UtilValidate.isEmpty(allGroupOptions)) {
return new ArrayList<>();
}
return UtilGenerics.cast(allGroupOptions.stream()
.map(groupOptions -> {
var groupOptionId = groupOptions.getId(context);
var truncatedDescription = truncate(groupOptions.getDescription(context), textSizeOptional);
var widgetStyle = groupOptions.getWidgetStyle(context);
List<Object> optionsInGroupList = new ArrayList<>();
optionsInGroupList.addAll(populateGroupAndOptions(context,
groupOptions.getGroupOptions(),
modelFormField, textSizeOptional, currentValuesList));
optionsInGroupList.addAll(populateOptions(context,
groupOptions.getAllOptionValues(context, WidgetWorker.getDelegator(context)),
modelFormField, textSizeOptional, currentValuesList));
return new GroupOption(groupOptionId, truncatedDescription, widgetStyle, optionsInGroupList);
})
.toList());
}
private List<Object> populateOptions(Map<String, Object> context, List<ModelFormField.OptionValue> allOptionValues,
ModelFormField modelFormField, Optional<Integer> textSizeOptional, List<String> currentValuesList) {
if (UtilValidate.isEmpty(allOptionValues)) {
return new ArrayList<>();
}
return UtilGenerics.cast(allOptionValues.stream()
.map(optionValue -> {
var encodedKey = encode(optionValue.getKey(), modelFormField, context);
var truncatedDescription = truncate(optionValue.getDescription(), textSizeOptional);
var selected = currentValuesList.contains(optionValue.getKey());

return new Option(encodedKey, truncatedDescription, selected);
})
.toList());
}

/**
* Create an ajaxXxxx JavaScript CSV string from a list of UpdateArea objects. See
* <code>OfbizUtil.js</code>.
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*/
package org.apache.ofbiz.widget.renderer.macro.model;

import java.util.List;

/**
* Record representing a group option in a drop-down (select) list.
*/
public record GroupOption(String id,
String description,
String widgetStyle,
List<Object> options) {
}
Loading

0 comments on commit 180ccc3

Please sign in to comment.