This is an automated email from the ASF dual-hosted git repository.

danwatford pushed a commit to branch trunk
in repository https://gitbox.apache.org/repos/asf/ofbiz-framework.git


The following commit(s) were added to refs/heads/trunk by this push:
     new 1c96f00476 Improved: Use RenderableFtlElements to render drop down 
fields (OFBIZ-12127)
1c96f00476 is described below

commit 1c96f0047618dbea3872248defa8566306c0769c
Author: Daniel Watford <dan...@watfordconsulting.com>
AuthorDate: Mon Mar 4 21:15:08 2024 +0000

    Improved: Use RenderableFtlElements to render drop down fields (OFBIZ-12127)
    
    Part of the OFBIZ-11456 refactoring effort.
    
    Removed unused renderDropDownField FTL macro attributes from the various
    FormMacroLibrary.ftl files. Synchronised the attribute lists among the
    files and changed some attributes to have default values and/or use
    non-string types.
    
    Moved logic used to construct the call to the renderDropDownField macro
    into RenderableFtlFormElementsBuilder where the logic now constructs a
    RenderableFtlMacroCall object rather than a long string.
    
    Use of RenderableFtlMacroCall means objects, rather than string
    representations of objects, are passed to FTL. This has allowed removal
    of workarounds introduced in OFBIZ-6504 to escape characters passed to
    Freemarker in macro attributes.
---
 .../apache/ofbiz/widget/model/ModelFormField.java  |  30 ++-
 .../ofbiz/widget/model/XmlWidgetFieldVisitor.java  |   7 +-
 .../widget/renderer/macro/MacroFormRenderer.java   | 245 +--------------------
 .../macro/RenderableFtlFormElementsBuilder.java    | 158 ++++++++++++-
 .../ofbiz/widget/renderer/macro/model/Option.java  |  25 +++
 .../macro/renderable/RenderableFtlMacroCall.java   |   4 +
 .../renderer/macro/MacroFormRendererTest.java      |  25 ---
 .../template/macro/CsvFormMacroLibrary.ftl         |   5 +-
 .../template/macro/FoFormMacroLibrary.ftl          |   5 +-
 .../template/macro/HtmlFormMacroLibrary.ftl        |  34 ++-
 .../template/macro/TextFormMacroLibrary.ftl        |  11 +-
 .../template/macro/XlsFormMacroLibrary.ftl         |   7 +-
 .../template/macro/XmlFormMacroLibrary.ftl         |   5 +-
 13 files changed, 260 insertions(+), 301 deletions(-)

diff --git 
a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormField.java
 
b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormField.java
index a06743b4c1..1907d2f016 100644
--- 
a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormField.java
+++ 
b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/ModelFormField.java
@@ -1953,7 +1953,7 @@ public final class ModelFormField {
         private final int otherFieldSize;
         private final String size;
         private final SubHyperlink subHyperlink;
-        private final String textSize;
+        private final Optional<Integer> textSize;
 
         private DropDownField(DropDownField original, ModelFormField 
modelFormField) {
             super(original, modelFormField);
@@ -2006,11 +2006,8 @@ public final class ModelFormField {
             } else {
                 this.subHyperlink = null;
             }
-            String textSize = element.getAttribute("text-size");
-            if (textSize.isEmpty()) {
-                textSize = "0";
-            }
-            this.textSize = textSize;
+
+            this.textSize = parseElementAttributeAsOptionalInteger(element, 
"text-size");
         }
 
         public DropDownField(int fieldSource, List<OptionSource> 
optionSources) {
@@ -2023,7 +2020,7 @@ public final class ModelFormField {
             this.otherFieldSize = 0;
             this.size = "1";
             this.subHyperlink = null;
-            this.textSize = "0";
+            this.textSize = Optional.empty();
         }
 
         public DropDownField(int fieldSource, ModelFormField modelFormField) {
@@ -2036,7 +2033,7 @@ public final class ModelFormField {
             this.otherFieldSize = 0;
             this.size = "1";
             this.subHyperlink = null;
-            this.textSize = "0";
+            this.textSize = Optional.empty();
         }
 
         public DropDownField(ModelFormField modelFormField) {
@@ -2049,7 +2046,7 @@ public final class ModelFormField {
             this.otherFieldSize = 0;
             this.size = "1";
             this.subHyperlink = null;
-            this.textSize = "0";
+            this.textSize = Optional.empty();
         }
 
         @Override
@@ -2159,7 +2156,7 @@ public final class ModelFormField {
          * Gets text size.
          * @return the text size
          */
-        public String getTextSize() {
+        public Optional<Integer> getTextSize() {
             return this.textSize;
         }
 
@@ -5600,4 +5597,17 @@ public final class ModelFormField {
             formStringRenderer.renderTextFindField(writer, context, this);
         }
     }
+
+    private static Optional<Integer> 
parseElementAttributeAsOptionalInteger(Element element, String attributeName) {
+        String attributeValue = element.getAttribute(attributeName);
+        if (UtilValidate.isNotEmpty(attributeValue)) {
+            try {
+                return Optional.of(Integer.parseInt(attributeValue));
+            } catch (NumberFormatException e) {
+                Debug.logError("Could not parse the " + attributeName + " 
value of the text element: ["
+                        + attributeValue + "],", MODULE);
+            }
+        }
+        return Optional.empty();
+    }
 }
diff --git 
a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/XmlWidgetFieldVisitor.java
 
b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/XmlWidgetFieldVisitor.java
index 143a3b1ce4..bf880e7d08 100644
--- 
a/framework/widget/src/main/java/org/apache/ofbiz/widget/model/XmlWidgetFieldVisitor.java
+++ 
b/framework/widget/src/main/java/org/apache/ofbiz/widget/model/XmlWidgetFieldVisitor.java
@@ -131,7 +131,12 @@ public class XmlWidgetFieldVisitor extends 
XmlAbstractWidgetVisitor implements M
         visitAttribute("current-description", 
dropDownField.getCurrentDescription());
         visitAttribute("other-field-size", dropDownField.getOtherFieldSize());
         visitAttribute("size", dropDownField.getSize());
-        visitAttribute("text-size", dropDownField.getTextSize());
+
+        var textSizeOptional = dropDownField.getTextSize();
+        if (textSizeOptional.isPresent()) {
+            visitAttribute("text-size", textSizeOptional.get());
+        }
+
         visitFieldInfoWithOptions(dropDownField);
         visitAutoComplete(dropDownField.getAutoComplete());
         visitSubHyperlink(dropDownField.getSubHyperlink());
diff --git 
a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroFormRenderer.java
 
b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroFormRenderer.java
index fafdfd4821..07afa93ebe 100644
--- 
a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroFormRenderer.java
+++ 
b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/MacroFormRenderer.java
@@ -65,7 +65,6 @@ import 
org.apache.ofbiz.widget.model.ModelFormField.DateTimeField;
 import org.apache.ofbiz.widget.model.ModelFormField.DisplayEntityField;
 import org.apache.ofbiz.widget.model.ModelFormField.DisplayField;
 import org.apache.ofbiz.widget.model.ModelFormField.DropDownField;
-import org.apache.ofbiz.widget.model.ModelFormField.FieldInfoWithOptions;
 import org.apache.ofbiz.widget.model.ModelFormField.FileField;
 import org.apache.ofbiz.widget.model.ModelFormField.HiddenField;
 import org.apache.ofbiz.widget.model.ModelFormField.HyperlinkField;
@@ -268,252 +267,14 @@ public final class MacroFormRenderer implements 
FormStringRenderer {
 
     @Override
     public void renderDropDownField(Appendable writer, Map<String, Object> 
context, DropDownField dropDownField) throws IOException {
-        ModelFormField modelFormField = dropDownField.getModelFormField();
-        ModelForm modelForm = modelFormField.getModelForm();
-        String currentValue = modelFormField.getEntry(context);
-        String conditionGroup = modelFormField.getConditionGroup();
-        boolean disabled = modelFormField.getDisabled(context);
-        List<ModelFormField.OptionValue> allOptionValues = 
dropDownField.getAllOptionValues(context, WidgetWorker.getDelegator(context));
-        ModelFormField.AutoComplete autoComplete = 
dropDownField.getAutoComplete();
-        String event = modelFormField.getEvent();
-        String action = modelFormField.getAction(context);
-        Integer textSize = 0;
-        if (UtilValidate.isNotEmpty(dropDownField.getTextSize())) {
-            try {
-                textSize = Integer.parseInt(dropDownField.getTextSize());
-            } catch (NumberFormatException nfe) {
-                Debug.logError(nfe, "Error reading size of a field fieldName=" 
+ dropDownField.getModelFormField().getFieldName() + " FormName= "
-                        + 
dropDownField.getModelFormField().getModelForm().getName(), MODULE);
-            }
-            if (textSize > 0 && UtilValidate.isNotEmpty(currentValue) && 
currentValue.length() > textSize) {
-                currentValue = currentValue.substring(0, textSize - 8) + "..." 
+ currentValue.substring(currentValue.length() - 5);
-            }
-        }
-        boolean ajaxEnabled = autoComplete != null && this.javaScriptEnabled;
-        String className = "";
-        String alert = "false";
-        String name = modelFormField.getParameterName(context);
-        String id = modelFormField.getCurrentContainerId(context);
-        String multiple = dropDownField.getAllowMultiple() ? "multiple" : "";
-        String otherFieldName = "";
-        String formName = modelForm.getName();
-        String size = dropDownField.getSize();
-        String dDFCurrent = dropDownField.getCurrent();
-        String firstInList = "";
-        String explicitDescription = "";
-        String allowEmpty = "";
-        StringBuilder options = new StringBuilder();
-        StringBuilder ajaxOptions = new StringBuilder();
-        if (UtilValidate.isNotEmpty(modelFormField.getWidgetStyle())) {
-            className = modelFormField.getWidgetStyle();
-            if (modelFormField.shouldBeRed(context)) {
-                alert = "true";
-            }
-        }
-        //check for required field style on single forms
-        if (shouldApplyRequiredField(modelFormField)) {
-            String requiredStyle = modelFormField.getRequiredFieldStyle();
-            if (UtilValidate.isEmpty(requiredStyle)) {
-                requiredStyle = "required";
-            }
-            if (UtilValidate.isEmpty(className)) {
-                className = requiredStyle;
-            } else {
-                className = requiredStyle + " " + className;
-            }
-        }
-        String currentDescription = null;
-        if (UtilValidate.isNotEmpty(currentValue)) {
-            for (ModelFormField.OptionValue optionValue : allOptionValues) {
-                if (optionValue.getKey().equals(currentValue)) {
-                    currentDescription = optionValue.getDescription();
-                    break;
-                }
-            }
-        }
-        int otherFieldSize = dropDownField.getOtherFieldSize();
-        if (otherFieldSize > 0) {
-            otherFieldName = dropDownField.getParameterNameOther(context);
-        }
-        // if the current value should go first, stick it in
-        if (UtilValidate.isNotEmpty(currentValue) && 
"first-in-list".equals(dropDownField.getCurrent())) {
-            firstInList = "first-in-list";
-        }
-        explicitDescription = (currentDescription != null ? currentDescription 
: dropDownField.getCurrentDescription(context));
-        if (UtilValidate.isEmpty(explicitDescription)) {
-            explicitDescription = 
(FieldInfoWithOptions.getDescriptionForOptionKey(currentValue, 
allOptionValues));
-        }
-        if (textSize > 0 && UtilValidate.isNotEmpty(explicitDescription) && 
explicitDescription.length() > textSize) {
-            explicitDescription = explicitDescription.substring(0, textSize - 
8) + "..."
-                    + 
explicitDescription.substring(explicitDescription.length() - 5);
-        }
-        explicitDescription = encode(explicitDescription, modelFormField, 
context);
-        // if allow empty is true, add an empty option
-        if (dropDownField.getAllowEmpty()) {
-            allowEmpty = "Y";
-        }
-        List<String> currentValueList = null;
-        if (UtilValidate.isNotEmpty(currentValue) && 
dropDownField.getAllowMultiple()) {
-            // If currentValue is Array, it will start with [
-            if (currentValue.startsWith("[")) {
-                currentValueList = StringUtil.toList(currentValue);
-            } else {
-                currentValueList = UtilMisc.toList(currentValue);
-            }
-        }
-        options.append("[");
-        Iterator<ModelFormField.OptionValue> optionValueIter = 
allOptionValues.iterator();
-        int count = 0;
-        while (optionValueIter.hasNext()) {
-            ModelFormField.OptionValue optionValue = optionValueIter.next();
-            if (options.length() > 1) {
-                options.append(",");
-            }
-            options.append("{'key':'");
-            String key = encode(optionValue.getKey(), modelFormField, context);
-            options.append(key);
-            options.append("'");
-            options.append(",'description':'");
-            String description = optionValue.getDescription();
-            if (textSize > 0 && description.length() > textSize) {
-                description = description.substring(0, textSize - 8) + "..." + 
description.substring(description.length() - 5);
-            }
-            options.append(encode(description.replaceAll("'", "\\\\\'"), 
modelFormField, context)); // related to OFBIZ-6504
+        writeFtlElement(writer, 
renderableFtlFormElementsBuilder.dropDownField(context, dropDownField, 
this.javaScriptEnabled));
 
-            if (UtilValidate.isNotEmpty(currentValueList)) {
-                options.append("'");
-                options.append(",'selected':'");
-                if (currentValueList.contains(optionValue.getKey())) {
-                    options.append("selected");
-                } else {
-                    options.append("");
-                }
-            }
-
-            options.append("'}");
-            if (ajaxEnabled) {
-                count++;
-                ajaxOptions.append(optionValue.getKey()).append(": ");
-                ajaxOptions.append(" 
'").append(optionValue.getDescription()).append("'");
-                if (count != allOptionValues.size()) {
-                    ajaxOptions.append(", ");
-                }
-            }
-        }
-        options.append("]");
-        String noCurrentSelectedKey = 
dropDownField.getNoCurrentSelectedKey(context);
-        String otherValue = "";
-        String fieldName = "";
-        // Adapted from work by Yucca Korpela
-        // http://www.cs.tut.fi/~jkorpela/forms/combo.html
-        if (otherFieldSize > 0) {
-            fieldName = modelFormField.getParameterName(context);
-            Map<String, ? extends Object> dataMap = 
modelFormField.getMap(context);
-            if (dataMap == null) {
-                dataMap = context;
-            }
-            Object otherValueObj = dataMap.get(otherFieldName);
-            otherValue = (otherValueObj == null) ? "" : 
otherValueObj.toString();
-        }
-        String frequency = "";
-        String minChars = "";
-        String choices = "";
-        String autoSelect = "";
-        String partialSearch = "";
-        String partialChars = "";
-        String ignoreCase = "";
-        String fullSearch = "";
-        if (ajaxEnabled) {
-            frequency = autoComplete.getFrequency();
-            minChars = autoComplete.getMinChars();
-            choices = autoComplete.getChoices();
-            autoSelect = autoComplete.getAutoSelect();
-            partialSearch = autoComplete.getPartialSearch();
-            partialChars = autoComplete.getPartialChars();
-            ignoreCase = autoComplete.getIgnoreCase();
-            fullSearch = autoComplete.getFullSearch();
-        }
-        String tabindex = modelFormField.getTabindex();
-        StringWriter sr = new StringWriter();
-        sr.append("<@renderDropDownField ");
-        sr.append("name=\"");
-        sr.append(name);
-        sr.append("\" className=\"");
-        sr.append(className);
-        sr.append("\" alert=\"");
-        sr.append(alert);
-        sr.append("\" id=\"");
-        sr.append(id);
-        sr.append("\" multiple=\"");
-        sr.append(multiple);
-        sr.append("\" formName=\"");
-        sr.append(formName);
-        sr.append("\" otherFieldName=\"");
-        sr.append(otherFieldName);
-        sr.append("\" event=\"");
-        if (event != null) {
-            sr.append(event);
-        }
-        sr.append("\" action=\"");
-        if (action != null) {
-            sr.append(action);
-        }
-        sr.append("\" size=\"");
-        sr.append(size);
-        sr.append("\" firstInList=\"");
-        sr.append(firstInList);
-        sr.append("\" currentValue=\"");
-        sr.append(currentValue);
-        sr.append("\" explicitDescription=\"");
-        sr.append(explicitDescription);
-        sr.append("\" allowEmpty=\"");
-        sr.append(allowEmpty);
-        sr.append("\" options=");
-        sr.append(options.toString());
-        sr.append(" fieldName=\"");
-        sr.append(fieldName);
-        sr.append("\" otherFieldName=\"");
-        sr.append(otherFieldName);
-        sr.append("\" otherValue=\"");
-        sr.append(otherValue);
-        sr.append("\" otherFieldSize=");
-        sr.append(Integer.toString(otherFieldSize));
-        sr.append(" dDFCurrent=\"");
-        sr.append(dDFCurrent);
-        sr.append("\" ajaxEnabled=");
-        sr.append(Boolean.toString(ajaxEnabled));
-        sr.append(" noCurrentSelectedKey=\"");
-        sr.append(noCurrentSelectedKey);
-        sr.append("\" ajaxOptions=\"");
-        sr.append(ajaxOptions.toString());
-        sr.append("\" frequency=\"");
-        sr.append(frequency);
-        sr.append("\" minChars=\"");
-        sr.append(minChars);
-        sr.append("\" choices=\"");
-        sr.append(choices);
-        sr.append("\" autoSelect=\"");
-        sr.append(autoSelect);
-        sr.append("\" partialSearch=\"");
-        sr.append(partialSearch);
-        sr.append("\" partialChars=\"");
-        sr.append(partialChars);
-        sr.append("\" ignoreCase=\"");
-        sr.append(ignoreCase);
-        sr.append("\" fullSearch=\"");
-        sr.append(fullSearch);
-        sr.append("\" conditionGroup=\"");
-        sr.append(conditionGroup);
-        sr.append("\" tabindex=\"");
-        sr.append(tabindex);
-        sr.append("\" disabled=");
-        sr.append(Boolean.toString(disabled));
-        sr.append(" />");
-        executeMacro(writer, sr.toString());
         ModelFormField.SubHyperlink subHyperlink = 
dropDownField.getSubHyperlink();
         if (subHyperlink != null && subHyperlink.shouldUse(context)) {
             makeHyperlinkString(writer, subHyperlink, context);
         }
+
+        ModelFormField modelFormField = dropDownField.getModelFormField();
         this.appendTooltip(writer, context, modelFormField);
     }
 
diff --git 
a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/RenderableFtlFormElementsBuilder.java
 
b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/RenderableFtlFormElementsBuilder.java
index 853ae19b28..79af56caee 100644
--- 
a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/RenderableFtlFormElementsBuilder.java
+++ 
b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/RenderableFtlFormElementsBuilder.java
@@ -22,10 +22,12 @@ import java.io.StringWriter;
 import java.net.URI;
 import java.sql.Timestamp;
 import java.util.ArrayList;
+import java.util.Collections;
 import java.util.Iterator;
 import java.util.List;
 import java.util.Locale;
 import java.util.Map;
+import java.util.Optional;
 import java.util.Set;
 import java.util.UUID;
 import java.util.function.Function;
@@ -38,6 +40,8 @@ import javax.servlet.http.HttpSession;
 
 import com.ibm.icu.util.Calendar;
 import org.apache.ofbiz.base.util.Debug;
+import org.apache.ofbiz.base.util.StringUtil;
+import org.apache.ofbiz.base.util.UtilCodec;
 import org.apache.ofbiz.base.util.UtilFormatOut;
 import org.apache.ofbiz.base.util.UtilGenerics;
 import org.apache.ofbiz.base.util.UtilHttp;
@@ -57,6 +61,7 @@ import org.apache.ofbiz.widget.model.ModelTheme;
 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.Option;
 import org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtl;
 import 
org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtlMacroCall;
 import 
org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtlMacroCall.RenderableFtlMacroCallBuilder;
@@ -78,6 +83,8 @@ public final class RenderableFtlFormElementsBuilder {
 
     private final StaticContentUrlProvider staticContentUrlProvider;
 
+    private final UtilCodec.SimpleEncoder internalEncoder;
+
     public RenderableFtlFormElementsBuilder(final VisualTheme visualTheme, 
final RequestHandler requestHandler,
                                             final HttpServletRequest request, 
final HttpServletResponse response,
                                             final StaticContentUrlProvider 
staticContentUrlProvider) {
@@ -86,6 +93,7 @@ public final class RenderableFtlFormElementsBuilder {
         this.request = request;
         this.response = response;
         this.staticContentUrlProvider = staticContentUrlProvider;
+        this.internalEncoder = UtilCodec.getEncoder("string");
     }
 
     public RenderableFtl tooltip(final Map<String, Object> context, final 
ModelFormField modelFormField) {
@@ -627,7 +635,7 @@ public final class RenderableFtlFormElementsBuilder {
         }
 
         macroCallBuilder.stringParameter("value",
-                modelFormField.getEntry(context, 
dateFindField.getDefaultValue(context)))
+                        modelFormField.getEntry(context, 
dateFindField.getDefaultValue(context)))
                 .stringParameter("value2", modelFormField.getEntry(context));
 
         if (context.containsKey("parameters")) {
@@ -882,6 +890,105 @@ public final class RenderableFtlFormElementsBuilder {
         return macroCallBuilder.build();
     }
 
+    public RenderableFtl dropDownField(final Map<String, Object> context,
+                                       final ModelFormField.DropDownField 
dropDownField,
+                                       final boolean javaScriptEnabled) {
+
+        final var builder = 
RenderableFtlMacroCall.builder().name("renderDropDownField");
+
+        final ModelFormField modelFormField = 
dropDownField.getModelFormField();
+        final ModelForm modelForm = modelFormField.getModelForm();
+        final var currentValue = modelFormField.getEntry(context);
+        final var autoComplete = dropDownField.getAutoComplete();
+        final var textSizeOptional = dropDownField.getTextSize();
+
+        applyCommonStyling(modelFormField, context, builder);
+
+        builder
+                .stringParameter("name", 
modelFormField.getParameterName(context))
+                .stringParameter("id", 
modelFormField.getCurrentContainerId(context))
+                .stringParameter("formName", modelForm.getName())
+                .stringParameter("size", dropDownField.getSize())
+                .booleanParameter("multiple", dropDownField.getAllowMultiple())
+                .stringParameter("currentValue", currentValue)
+                .stringParameter("conditionGroup", 
modelFormField.getConditionGroup())
+                .booleanParameter("disabled", 
modelFormField.getDisabled(context))
+                .booleanParameter("ajaxEnabled", autoComplete != null && 
javaScriptEnabled)
+                .stringParameter("noCurrentSelectedKey", 
dropDownField.getNoCurrentSelectedKey(context))
+                .stringParameter("tabindex", modelFormField.getTabindex())
+                .booleanParameter("allowEmpty", dropDownField.getAllowEmpty())
+                .stringParameter("dDFCurrent", dropDownField.getCurrent())
+                .booleanParameter("placeCurrentValueAsFirstOption",
+                        "first-in-list".equals(dropDownField.getCurrent()));
+
+        final var event = modelFormField.getEvent();
+        final var action = modelFormField.getAction(context);
+        if (event != null && action != null) {
+            builder.stringParameter("event", event).stringParameter("action", 
action);
+        }
+
+        final var allOptionValues = dropDownField.getAllOptionValues(context, 
WidgetWorker.getDelegator(context));
+        final var explicitDescription =
+                // Populate explicitDescription with the description from the 
option associated with the current value.
+                allOptionValues.stream()
+                .filter(optionValue -> 
optionValue.getKey().equals(currentValue))
+                .map(ModelFormField.OptionValue::getDescription)
+                .findFirst()
+
+                // If no matching option is found, use the current description 
from the field.
+                .or(() -> 
Optional.ofNullable(dropDownField.getCurrentDescription(context)))
+                .filter(UtilValidate::isNotEmpty)
+
+                // If no description has been found, fall back to the 
description determined by the ModelFormField.
+                .or(() -> 
Optional.of(ModelFormField.FieldInfoWithOptions.getDescriptionForOptionKey(currentValue,
+                        allOptionValues)))
+
+                // Truncate and encode the description as needed.
+                .map(description -> encode(truncate(description, 
textSizeOptional), modelFormField, context));
+
+        builder.stringParameter("explicitDescription", 
explicitDescription.orElse(""));
+
+        // Take the field's current value and convert it to a list containing 
a single item.
+        // If the field allows multiple values, the current value is expected 
to be a string encoded list of values
+        // which it will be converted to a list of strings.
+        final List<String> currentValuesList = 
(UtilValidate.isNotEmpty(currentValue) && dropDownField.getAllowMultiple())
+                        ? (currentValue.startsWith("[")
+                            ? StringUtil.toList(currentValue)
+                            : 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());
+
+        builder.objectParameter("options", optionsList);
+
+        int otherFieldSize = dropDownField.getOtherFieldSize();
+        if (otherFieldSize > 0) {
+            var otherFieldName = dropDownField.getParameterNameOther(context);
+
+            var dataMap = modelFormField.getMap(context);
+            if (dataMap == null) {
+                dataMap = context;
+            }
+            var otherValueObj = dataMap.get(otherFieldName);
+            var otherValue = (otherValueObj == null) ? "" : 
otherValueObj.toString();
+
+            builder
+                    .stringParameter("otherFieldName", otherFieldName)
+                    .stringParameter("otherValue", otherValue)
+                    .intParameter("otherFieldSize", otherFieldSize);
+        }
+
+        return builder.build();
+    }
+
     /**
      * Create an ajaxXxxx JavaScript CSV string from a list of UpdateArea 
objects. See
      * <code>OfbizUtil.js</code>.
@@ -980,7 +1087,7 @@ public final class RenderableFtlFormElementsBuilder {
         return wholeFormContext;
     }
 
-    private boolean shouldApplyRequiredField(ModelFormField modelFormField) {
+    private static boolean shouldApplyRequiredField(ModelFormField 
modelFormField) {
         return ("single".equals(modelFormField.getModelForm().getType())
                 || "upload".equals(modelFormField.getModelForm().getType()))
                 && modelFormField.getRequiredField();
@@ -1004,4 +1111,51 @@ public final class RenderableFtlFormElementsBuilder {
     private String pathAsContentUrl(final String path) {
         return staticContentUrlProvider.pathAsContentUrlString(path);
     }
+
+    private String encode(String value, ModelFormField modelFormField, 
Map<String, Object> context) {
+        if (UtilValidate.isEmpty(value)) {
+            return value;
+        }
+        UtilCodec.SimpleEncoder encoder = (UtilCodec.SimpleEncoder) 
context.get("simpleEncoder");
+        if (modelFormField.getEncodeOutput() && encoder != null) {
+            value = encoder.encode(value);
+        } else {
+            value = internalEncoder.encode(value);
+        }
+        return value;
+    }
+
+    private String truncate(String value, int maxCharacterLength) {
+        if (maxCharacterLength > 8 && value.length() > maxCharacterLength) {
+            return value.substring(0, maxCharacterLength - 8) + "..." + 
value.substring(value.length() - 5);
+        }
+        return value;
+    }
+
+    private String truncate(String value, Optional<Integer> 
maxCharacterLengthOptional) {
+        return maxCharacterLengthOptional
+                .map(maxCharacterLength -> truncate(value, maxCharacterLength))
+                .orElse(value);
+    }
+
+    private static void applyCommonStyling(final ModelFormField 
modelFormField, final Map<String, Object> context,
+                                           final RenderableFtlMacroCallBuilder 
builder) {
+        final var classNames = new ArrayList<String>();
+        if (UtilValidate.isNotEmpty(modelFormField.getWidgetStyle())) {
+            classNames.add(modelFormField.getWidgetStyle());
+            if (modelFormField.shouldBeRed(context)) {
+                builder.stringParameter("alert", "true");
+            }
+        }
+
+        if (shouldApplyRequiredField(modelFormField)) {
+            var requiredStyle = modelFormField.getRequiredFieldStyle();
+            if (UtilValidate.isEmpty(requiredStyle)) {
+                requiredStyle = "required";
+            }
+            classNames.add(requiredStyle);
+        }
+
+        builder.stringParameter("className", String.join(" ", classNames));
+    }
 }
diff --git 
a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/model/Option.java
 
b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/model/Option.java
new file mode 100644
index 0000000000..b1a8190cae
--- /dev/null
+++ 
b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/model/Option.java
@@ -0,0 +1,25 @@
+/*
+ * 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;
+
+/**
+ * Record representing an option in a drop-down (select) list.
+ */
+public record Option(String key, String description, boolean selected) {
+}
diff --git 
a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlMacroCall.java
 
b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlMacroCall.java
index 7ef5674724..e00a9c93aa 100644
--- 
a/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlMacroCall.java
+++ 
b/framework/widget/src/main/java/org/apache/ofbiz/widget/renderer/macro/renderable/RenderableFtlMacroCall.java
@@ -81,6 +81,10 @@ public final class RenderableFtlMacroCall implements 
RenderableFtl {
             return parameter(parameterName, parameterValue);
         }
 
+        public RenderableFtlMacroCallBuilder objectParameter(final String 
parameterName, final Object parameterValue) {
+            return parameter(parameterName, parameterValue);
+        }
+
         public RenderableFtlMacroCall build() {
             return new RenderableFtlMacroCall(name, parameters);
         }
diff --git 
a/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/MacroFormRendererTest.java
 
b/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/MacroFormRendererTest.java
index 44fa5e3802..2b22108a60 100644
--- 
a/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/MacroFormRendererTest.java
+++ 
b/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/MacroFormRendererTest.java
@@ -255,29 +255,6 @@ public class MacroFormRendererTest {
         genericTooltipRenderedVerification();
     }
 
-    @Test
-    public void dropDownMacroRendered(@Mocked ModelFormField.DropDownField 
dropDownField) throws IOException {
-        final List<ModelFormField.OptionValue> optionValues = ImmutableList.of(
-                new ModelFormField.OptionValue("KEY1", "DESC1"),
-                new ModelFormField.OptionValue("KEY2", "DESC2"));
-
-        new Expectations() {
-            {
-                modelFormField.getEntry(withNotNull());
-                result = "KEY2";
-
-                dropDownField.getAllOptionValues(withNotNull(), (Delegator) 
any);
-                result = optionValues;
-            }
-        };
-
-        macroFormRenderer.renderDropDownField(appendable, ImmutableMap.of(), 
dropDownField);
-        assertAndGetMacroString("renderDropDownField", ImmutableMap.of(
-                "currentValue", "KEY2",
-                "options", 
ImmutableList.of("{'key':'KEY1','description':'DESC1'}",
-                        "{'key':'KEY2','description':'DESC2'}")));
-    }
-
     @Test
     public void checkFieldMacroRendered(@Mocked ModelFormField.CheckField 
checkField) throws IOException {
         final List<ModelFormField.OptionValue> optionValues = ImmutableList.of(
@@ -311,8 +288,6 @@ public class MacroFormRendererTest {
 
                 checkField.getModelFormField().getAttributeName();
                 result = "FieldName";
-
-
             }
         };
 
diff --git a/themes/common-theme/template/macro/CsvFormMacroLibrary.ftl 
b/themes/common-theme/template/macro/CsvFormMacroLibrary.ftl
index fbeb149d3f..8b95d8d86e 100644
--- a/themes/common-theme/template/macro/CsvFormMacroLibrary.ftl
+++ b/themes/common-theme/template/macro/CsvFormMacroLibrary.ftl
@@ -30,7 +30,10 @@ under the License.
 
 <#macro renderDateTimeField name className timeDropdownParamName 
defaultDateTimeString localizedIconTitle timeHourName timeMinutesName ampmName 
compositeType alert=false isTimeType=false isDateType=false amSelected=false 
pmSelected=false timeDropdown="" classString="" isTwelveHour=false hour1="" 
hour2="" minutes=0 shortDateInput="" title="" value="" size="" maxlength="" 
id="" formName="" mask="" event="" action="" step="" timeValues="" tabindex="" 
disabled=false isXMLHttpRequest=false><@ [...]
 
-<#macro renderDropDownField name className alert id formName action 
explicitDescription options fieldName otherFieldName otherValue otherFieldSize 
ajaxEnabled ajaxOptions frequency minChars choices autoSelect partialSearch 
partialChars ignoreCase fullSearch conditionGroup="" tabindex="" multiple="" 
event="" size="" firstInList="" currentValue="" allowEmpty="" dDFCurrent="" 
noCurrentSelectedKey="" disabled=false>
+<#macro renderDropDownField name className id formName explicitDescription 
options ajaxEnabled
+        otherFieldName="" otherValue="" otherFieldSize=""
+        alert="" conditionGroup="" tabindex="" multiple=false event="" size="" 
placeCurrentValueAsFirstOption=false
+        currentValue="" allowEmpty=false dDFCurrent="" noCurrentSelectedKey="" 
disabled=false action="">
 <@renderField explicitDescription />
 </#macro>
 
diff --git a/themes/common-theme/template/macro/FoFormMacroLibrary.ftl 
b/themes/common-theme/template/macro/FoFormMacroLibrary.ftl
index e3ee3f9b9c..15cc3c43f2 100644
--- a/themes/common-theme/template/macro/FoFormMacroLibrary.ftl
+++ b/themes/common-theme/template/macro/FoFormMacroLibrary.ftl
@@ -57,7 +57,10 @@ under the License.
 
 <#macro renderDateTimeField name className timeDropdownParamName 
defaultDateTimeString localizedIconTitle timeHourName timeMinutesName ampmName 
compositeType alert=false isTimeType=false isDateType=false amSelected=false 
pmSelected=false timeDropdown="" classString="" isTwelveHour=false hour1="" 
hour2="" minutes=0 shortDateInput="" title="" value="" size="" maxlength="" 
id="" formName="" mask="" event="" action="" step="" timeValues="" tabindex="" 
disabled=false isXMLHttpRequest=false><@ [...]
 
-<#macro renderDropDownField name className alert id formName action 
explicitDescription options fieldName otherFieldName otherValue otherFieldSize 
ajaxEnabled ajaxOptions frequency minChars choices autoSelect partialSearch 
partialChars ignoreCase fullSearch conditionGroup="" tabindex="" multiple="" 
event="" size="" firstInList="" currentValue="" allowEmpty="" dDFCurrent="" 
noCurrentSelectedKey="" disabled=false>
+<#macro renderDropDownField name className id formName explicitDescription 
options ajaxEnabled
+        otherFieldName="" otherValue="" otherFieldSize=""
+        alert="" conditionGroup="" tabindex="" multiple=false event="" size="" 
placeCurrentValueAsFirstOption=false
+        currentValue="" allowEmpty=false dDFCurrent="" noCurrentSelectedKey="" 
disabled=false action="">
 <@makeBlock "" explicitDescription />
 </#macro>
 
diff --git a/themes/common-theme/template/macro/HtmlFormMacroLibrary.ftl 
b/themes/common-theme/template/macro/HtmlFormMacroLibrary.ftl
index 0bf3d23a5c..13e4b54ad7 100644
--- a/themes/common-theme/template/macro/HtmlFormMacroLibrary.ftl
+++ b/themes/common-theme/template/macro/HtmlFormMacroLibrary.ftl
@@ -156,15 +156,17 @@ under the License.
   </span>
 </#macro>
 
-<#macro renderDropDownField name className alert id formName action 
explicitDescription options fieldName otherFieldName otherValue otherFieldSize 
ajaxEnabled ajaxOptions frequency minChars choices autoSelect partialSearch 
partialChars ignoreCase fullSearch conditionGroup="" tabindex="" multiple="" 
event="" size="" firstInList="" currentValue="" allowEmpty="" dDFCurrent="" 
noCurrentSelectedKey="" disabled=false>
+<#macro renderDropDownField name className id formName explicitDescription 
options ajaxEnabled
+        otherFieldName="" otherValue="" otherFieldSize=""
+        alert="" conditionGroup="" tabindex="" multiple=false event="" size="" 
placeCurrentValueAsFirstOption=false
+        currentValue="" allowEmpty=false dDFCurrent="" noCurrentSelectedKey="" 
disabled=false action="">
   <#if conditionGroup?has_content>
     <input type="hidden" name="${name}_grp" value="${conditionGroup}"/>
   </#if>
   <span class="ui-widget">
-    <select name="${name?default("")}<#rt/>"
+    <select name="${name?default("")}"
       <@renderClass className alert /> <@renderDisabled disabled />
       <#if id?has_content> id="${id}"</#if>
-      <#if multiple?has_content> multiple="multiple"</#if>
       <#if ajaxEnabled> class="autoCompleteDropDown"</#if>
       <#if event?has_content> ${event}="${action}"</#if>
       <#if size?has_content> size="${size}"</#if>
@@ -174,19 +176,31 @@ under the License.
         data-other-field-value='${otherValue?js_string}'
         data-other-field-size='${otherFieldSize}'
       </#if>>
-      <#if firstInList?has_content && currentValue?has_content && 
!multiple?has_content>
-        <option selected="selected" 
value="${currentValue}">${explicitDescription?replace("&#x5c;&#x27;","&#x27;")}</option><#rt/><#--
 replace("&#x5c;&#x27;","&#x27;") related to OFBIZ-6504 -->
+      <#if placeCurrentValueAsFirstOption && currentValue?has_content && 
!multiple>
+        <option selected="selected" 
value="${currentValue}">${explicitDescription}</option><#rt/>
       </#if>
-      <#if allowEmpty?has_content && allowEmpty=="Y">
+      <#if allowEmpty>
         <option value="">&nbsp;</option>
-      <#elseif allowEmpty=="N" && !options?has_content>
+      <#elseif !options?has_content>
           <option value="">&nbsp;</option>
       </#if>
       <#list options as item>
-        <#if multiple?has_content>
-          <option<#if currentValue?has_content && item.selected?has_content> 
selected="${item.selected}" <#elseif !currentValue?has_content && 
noCurrentSelectedKey?has_content && noCurrentSelectedKey == item.key> 
selected="selected" </#if> 
value="${item.key}">${item.description?replace("&#x5c;&#x27;","&#x27;")}</option><#rt/>
 <#-- replace("&#x5c;&#x27;","&#x27;") related to OFBIZ-6504 -->
+        <#if multiple>
+          <option
+            <#if currentValue?has_content && item.selected()>
+              selected
+            <#elseif !currentValue?has_content && 
noCurrentSelectedKey?has_content && noCurrentSelectedKey == item.key()>
+              selected
+            </#if>
+            value="${item.key()}">${item.description()}</option><#rt/>
         <#else>
-          <option<#if currentValue?has_content && currentValue == item.key && 
dDFCurrent?has_content && "selected" == dDFCurrent> selected="selected"<#elseif 
!currentValue?has_content && noCurrentSelectedKey?has_content && 
noCurrentSelectedKey == item.key> selected="selected"</#if> 
value="${item.key}">${item.description?replace("&#x5c;&#x27;","&#x27;")}</option><#rt/>
 <#-- replace("&#x5c;&#x27;","&#x27;") related to OFBIZ-6504 -->
+          <option
+            <#if currentValue?has_content && currentValue == item.key() && 
dDFCurrent?has_content && "selected" == dDFCurrent>
+              selected
+            <#elseif !currentValue?has_content && 
noCurrentSelectedKey?has_content && noCurrentSelectedKey == item.key()>
+              selected
+            </#if>
+            value="${item.key()}">${item.description()}</option><#rt/>
         </#if>
       </#list>
     </select>
diff --git a/themes/common-theme/template/macro/TextFormMacroLibrary.ftl 
b/themes/common-theme/template/macro/TextFormMacroLibrary.ftl
index db08b78be6..f9124a4e40 100644
--- a/themes/common-theme/template/macro/TextFormMacroLibrary.ftl
+++ b/themes/common-theme/template/macro/TextFormMacroLibrary.ftl
@@ -30,14 +30,11 @@ under the License.
 
 <#macro renderDateTimeField name className alert title value size maxlength id 
dateType shortDateInput timeDropdownParamName defaultDateTimeString 
localizedIconTitle timeDropdown timeHourName classString hour1 hour2 
timeMinutesName minutes isTwelveHour ampmName amSelected pmSelected 
compositeType formName mask="" event="" action="" step="" timeValues="" 
tabindex="" disabled="" isXMLHttpRequest=""><@renderField value /></#macro>
 
-<#macro renderDropDownField name className alert id multiple formName 
otherFieldName event action size firstInList currentValue explicitDescription 
allowEmpty options fieldName otherFieldName otherValue otherFieldSize 
dDFCurrent ajaxEnabled noCurrentSelectedKey ajaxOptions frequency minChars 
choices autoSelect partialSearch partialChars ignoreCase fullSearch 
conditionGroup tabindex disabled>
-<#if currentValue?has_content && firstInList?has_content>
+<#macro renderDropDownField name className id formName explicitDescription 
options ajaxEnabled
+        otherFieldName="" otherValue="" otherFieldSize=""
+        alert="" conditionGroup="" tabindex="" multiple=false event="" size="" 
placeCurrentValueAsFirstOption=false
+        currentValue="" allowEmpty=false dDFCurrent="" noCurrentSelectedKey="" 
disabled=false action="">
 <@renderField explicitDescription />
-<#else>
-<#list options as item>
-<@renderField item.description />
-</#list>
-</#if>
 </#macro>
 
 <#macro renderTooltip tooltip tooltipStyle></#macro>
diff --git a/themes/common-theme/template/macro/XlsFormMacroLibrary.ftl 
b/themes/common-theme/template/macro/XlsFormMacroLibrary.ftl
index c0954c6523..c8872e2f67 100644
--- a/themes/common-theme/template/macro/XlsFormMacroLibrary.ftl
+++ b/themes/common-theme/template/macro/XlsFormMacroLibrary.ftl
@@ -40,7 +40,12 @@ under the License.
 <#else><@renderItemField value "dtf" className/></#if>
 </#macro>
 
-<#macro renderDropDownField name className alert id multiple formName 
otherFieldName event action size firstInList currentValue explicitDescription 
allowEmpty options fieldName otherFieldName otherValue otherFieldSize 
dDFCurrent ajaxEnabled noCurrentSelectedKey ajaxOptions frequency minChars 
choices autoSelect partialSearch partialChars ignoreCase fullSearch 
conditionGroup tabindex disabled><@renderItemField explicitDescription "txf" 
className/></#macro>
+<#macro renderDropDownField name className id formName explicitDescription 
options ajaxEnabled
+        otherFieldName="" otherValue="" otherFieldSize=""
+        alert="" conditionGroup="" tabindex="" multiple=false event="" size="" 
placeCurrentValueAsFirstOption=false
+        currentValue="" allowEmpty=false dDFCurrent="" noCurrentSelectedKey="" 
disabled=false action="">
+<@renderItemField explicitDescription "txf" className/>
+</#macro>
 
 <#macro renderCheckField items className alert id currentValue name event 
action conditionGroup tabindex disabled allChecked=""><@renderItemField 
currentValue "txf" className/></#macro>
 
diff --git a/themes/common-theme/template/macro/XmlFormMacroLibrary.ftl 
b/themes/common-theme/template/macro/XmlFormMacroLibrary.ftl
index 78f03c612f..5678840d5b 100644
--- a/themes/common-theme/template/macro/XmlFormMacroLibrary.ftl
+++ b/themes/common-theme/template/macro/XmlFormMacroLibrary.ftl
@@ -46,7 +46,10 @@ under the License.
 
 <#macro renderDateTimeField name className timeDropdownParamName 
defaultDateTimeString localizedIconTitle timeHourName timeMinutesName ampmName 
compositeType alert=false isTimeType=false isDateType=false amSelected=false 
pmSelected=false timeDropdown="" classString="" isTwelveHour=false hour1="" 
hour2="" minutes=0 shortDateInput="" title="" value="" size="" maxlength="" 
id="" formName="" mask="" event="" action="" step="" timeValues="" tabindex="" 
disabled=false isXMLHttpRequest=false><@ [...]
 
-<#macro renderDropDownField name className alert id multiple formName 
otherFieldName event action size firstInList currentValue explicitDescription 
allowEmpty options fieldName otherFieldName otherValue otherFieldSize 
dDFCurrent ajaxEnabled noCurrentSelectedKey ajaxOptions frequency minChars 
choices autoSelect partialSearch partialChars ignoreCase fullSearch 
conditionGroup tabindex disabled>
+<#macro renderDropDownField name className id formName explicitDescription 
options ajaxEnabled
+        otherFieldName="" otherValue="" otherFieldSize=""
+        alert="" conditionGroup="" tabindex="" multiple=false event="" size="" 
placeCurrentValueAsFirstOption=false
+        currentValue="" allowEmpty=false dDFCurrent="" noCurrentSelectedKey="" 
disabled=false action="">
 </#macro>
 
 <#macro renderCheckField items className alert id currentValue name event 
action conditionGroup tabindex disabled allChecked=""></#macro>


Reply via email to