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) (#842)

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 authored Oct 23, 2024
1 parent 63201f5 commit 24fc14a
Show file tree
Hide file tree
Showing 5 changed files with 301 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,160 @@ public void renderFieldString(Appendable writer, Map<String, Object> context, Fo
}
}

/**
* Models the &lt;group-options&gt; element.
* @see <code>widget-form.xsd</code>
*/
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;

/**
* Create a new groupOptions instance from xml element
* @param groupOptionsElement
* @param modelFormField
*/
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);
}

/**
* Copy an existing groupOptions to a new one
* @param original
* @param modelFormField
*/
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);
}

/**
* create a groupOptions from a modelFormField
* @param modelFormField
*/
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();
}

/**
* @return description present for a groupOptions instance
*/
public FlexibleStringExpander getDescription() {
return description;
}

/**
* @return parsed description with context for a groupOptions instance
*/
public String getDescription(Map<String, Object> context) {
return this.description.expandString(context);
}

/**
* @return unique reference for a groupOptions instance
*/
public FlexibleStringExpander getId() {
return id;
}

/**
* @return parsed unique reference with context for a groupOptions instance
*/
public String getId(Map<String, Object> context) {
String id = this.id.expandString(context);
return UtilValidate.isNotEmpty(id) ? id
: UUID.randomUUID().toString().replace("-", "");
}

/**
* @return widgetStyle present for a groupOptions instance
*/
public FlexibleStringExpander getWidgetStyle() {
return widgetStyle;
}

/**
* @return parsed widgetStyle with context for a groupOptions instance
*/
public String getWidgetStyle(Map<String, Object> context) {
return this.widgetStyle.expandString(context);
}

/**
* Compute all options define for groupOptions instance
* @return options list present on this groupOptions
*/
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;
}
/**
* @return groupOptions sub list
*/
public List<GroupOptions> getGroupOptions() {
return groupOptions;
}

/**
* Duplicate the groupOptions
* @return new groupOptions instance
*/
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 +2564,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 +2582,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 +2592,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 +2608,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 +2667,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
Loading

0 comments on commit 24fc14a

Please sign in to comment.