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

mridulpathak 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 35d68ab0e8 Improved: MacroFormRenderer builds the check, radio, 
submit, reset, hidden and field title macro calls with 
RenderableFtlFormElementsBuilder (OFBIZ-13679)
35d68ab0e8 is described below

commit 35d68ab0e80984dfbe9d90d967449c23e4aebb2c
Author: Mridul Pathak <[email protected]>
AuthorDate: Fri Oct 16 11:13:13 2026 +0530

    Improved: MacroFormRenderer builds the check, radio, submit, reset, hidden 
and field title macro calls with RenderableFtlFormElementsBuilder (OFBIZ-13679)
    
    MacroFormRenderer built the macro calls of the check, radio, submit, reset 
and hidden fields, of the field titles and of the sort field as text and 
compiled them. RenderableFtlFormElementsBuilder now creates these macro calls, 
so that FtlWriter passes their values as data: the options of the check and 
radio fields are lists of maps instead of hash literals, and the quotes that 
the form fields escaped for a FreeMarker string literal are no longer escaped. 
The macro signatures and the o [...]
---
 .../widget/renderer/macro/MacroFormRenderer.java   | 396 ++-------------------
 .../macro/RenderableFtlFormElementsBuilder.java    | 280 ++++++++++++++-
 .../renderer/macro/MacroFormRendererTest.java      | 117 +++---
 .../RenderableFtlFormElementsBuilderTest.java      | 181 ++++++++++
 4 files changed, 538 insertions(+), 436 deletions(-)

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 cc28442ca6..e1cb37ba1f 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
@@ -22,7 +22,6 @@ import java.io.IOException;
 import java.io.StringWriter;
 import java.io.UnsupportedEncodingException;
 import java.net.URI;
-import java.util.ArrayList;
 import java.util.HashSet;
 import java.util.Iterator;
 import java.util.LinkedList;
@@ -175,6 +174,17 @@ public final class MacroFormRenderer implements 
FormStringRenderer {
         return value;
     }
 
+    private String encodeForMacro(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);
+        }
+        return value;
+    }
+
     public void renderLabel(Appendable writer, Map<String, Object> context, 
ModelScreenWidget.Label label) {
         final RenderableFtl renderableFtl = 
renderableFtlFormElementsBuilder.label(context, label);
         writeFtlElement(writer, renderableFtl);
@@ -282,294 +292,26 @@ public final class MacroFormRenderer implements 
FormStringRenderer {
 
     @Override
     public void renderCheckField(Appendable writer, Map<String, Object> 
context, CheckField checkField) throws IOException {
-        ModelFormField modelFormField = checkField.getModelFormField();
-        String currentValue = modelFormField.getEntry(context);
-        String conditionGroup = modelFormField.getConditionGroup();
-        Boolean allChecked = checkField.isAllChecked(context);
-        boolean disabled = modelFormField.getDisabled(context);
-        String id = modelFormField.getCurrentContainerId(context);
-        String className = "";
-        String alert = "false";
-        String name = modelFormField.getParameterName(context);
-        String event = modelFormField.getEvent();
-        String action = modelFormField.getAction(context);
-        StringBuilder items = new StringBuilder();
-        String checkBox = checkField.getModelFormField().getAttributeName();
-        List<String> checkedByDefault = new ArrayList<String>();
-
-        if (context.containsKey(checkBox) && context.get(checkBox) != null
-                && !context.get(checkBox).getClass().equals(String.class)) {
-            checkedByDefault = context.containsKey(checkBox) ? 
StringUtil.toList(context.get(checkBox).toString())
-                    : List.of();
-        }
-
-        if (UtilValidate.isNotEmpty(modelFormField.getWidgetStyle())) {
-            className = modelFormField.getWidgetStyle();
-            if (modelFormField.shouldBeRed(context)) {
-                alert = "true";
-            }
-        }
-        String tabindex = modelFormField.getTabindex();
-
-        List<String> currentValueList = null;
-        if (UtilValidate.isNotEmpty(currentValue)) {
-            if (currentValue.startsWith("[")) {
-                currentValueList = StringUtil.toList(currentValue);
-            } else {
-                currentValueList = UtilMisc.toList(currentValue);
-            }
-        }
-
-        List<ModelFormField.OptionValue> allOptionValues = 
checkField.getAllOptionValues(context, WidgetWorker.getDelegator(context));
-        items.append("[");
-        for (ModelFormField.OptionValue optionValue : allOptionValues) {
-            boolean checked;
-
-            if (UtilValidate.isNotEmpty(currentValueList)) {
-                checked = currentValueList.contains(optionValue.getKey());
-            } else {
-                if (UtilValidate.isNotEmpty(checkedByDefault)) {
-                    checked = checkedByDefault.contains(optionValue.getKey());
-                } else checked = allChecked;
-            }
-            String data = String.format(
-                    "{'value':'%s', 'description':'%s', 'checked':'%s'}",
-                    optionValue.getKey(),
-                    encode(optionValue.getDescription(), modelFormField, 
context),
-                    checked);
-            items.append(data);
-            items.append(",");
-        }
-        if (items.length() > 0) {
-            items.deleteCharAt(items.length() - 1);
-        }
-        items.append("]");
-        StringWriter sr = new StringWriter();
-        sr.append("<@renderCheckField ");
-        sr.append("items=");
-        sr.append(items.toString());
-        sr.append(" className=\"");
-        sr.append(className);
-        sr.append("\" alert=\"");
-        sr.append(alert);
-        sr.append("\" id=\"");
-        sr.append(id);
-        sr.append("\" conditionGroup=\"");
-        sr.append(conditionGroup);
-        sr.append("\" currentValue=\"");
-        sr.append(currentValue);
-        sr.append("\" name=\"");
-        sr.append(name);
-        sr.append("\" event=\"");
-        if (event != null) {
-            sr.append(event);
-        }
-        sr.append("\" action=\"");
-        if (action != null) {
-            sr.append(action);
-        }
-        sr.append("\" tabindex=\"");
-        sr.append(tabindex);
-        sr.append("\" disabled=");
-        sr.append(Boolean.toString(disabled));
-        sr.append(" />");
-        executeMacro(writer, sr.toString());
-        this.appendTooltip(writer, context, modelFormField);
+        writeFtlElement(writer, 
renderableFtlFormElementsBuilder.checkField(context, checkField));
+        this.appendTooltip(writer, context, checkField.getModelFormField());
     }
 
     @Override
     public void renderRadioField(Appendable writer, Map<String, Object> 
context, RadioField radioField) throws IOException {
-        ModelFormField modelFormField = radioField.getModelFormField();
-        List<ModelFormField.OptionValue> allOptionValues = 
radioField.getAllOptionValues(context, WidgetWorker.getDelegator(context));
-        String currentValue = modelFormField.getEntry(context);
-        String conditionGroup = modelFormField.getConditionGroup();
-        boolean disabled = modelFormField.getDisabled(context);
-        String className = "";
-        String alert = "false";
-        String name = modelFormField.getParameterName(context);
-        String event = modelFormField.getEvent();
-        String action = modelFormField.getAction(context);
-        StringBuilder items = new StringBuilder();
-        if (UtilValidate.isNotEmpty(modelFormField.getWidgetStyle())) {
-            className = modelFormField.getWidgetStyle();
-            if (modelFormField.shouldBeRed(context)) {
-                alert = "true";
-            }
-        }
-        String noCurrentSelectedKey = 
radioField.getNoCurrentSelectedKey(context);
-        String tabindex = modelFormField.getTabindex();
-        items.append("[");
-        for (ModelFormField.OptionValue optionValue : allOptionValues) {
-            if (items.length() > 1) {
-                items.append(",");
-            }
-            items.append("{'key':'");
-            items.append(optionValue.getKey());
-            items.append("', 'description':'" + 
encode(optionValue.getDescription(), modelFormField, context));
-            items.append("'}");
-        }
-        items.append("]");
-        StringWriter sr = new StringWriter();
-        sr.append("<@renderRadioField ");
-        sr.append("items=");
-        sr.append(items.toString());
-        sr.append(" className=\"");
-        sr.append(className);
-        sr.append("\" alert=\"");
-        sr.append(alert);
-        sr.append("\" currentValue=\"");
-        sr.append(currentValue);
-        sr.append("\" noCurrentSelectedKey=\"");
-        sr.append(noCurrentSelectedKey);
-        sr.append("\" name=\"");
-        sr.append(name);
-        sr.append("\" event=\"");
-        if (event != null) {
-            sr.append(event);
-        }
-        sr.append("\" action=\"");
-        if (action != null) {
-            sr.append(action);
-        }
-        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());
-        this.appendTooltip(writer, context, modelFormField);
+        writeFtlElement(writer, 
renderableFtlFormElementsBuilder.radioField(context, radioField));
+        this.appendTooltip(writer, context, radioField.getModelFormField());
     }
 
     @Override
     public void renderSubmitField(Appendable writer, Map<String, Object> 
context, SubmitField submitField) throws IOException {
-        ModelFormField modelFormField = submitField.getModelFormField();
-        ModelForm modelForm = modelFormField.getModelForm();
-        String id = modelFormField.getCurrentContainerId(context);
-        String event = modelFormField.getEvent();
-        String action = modelFormField.getAction(context);
-        String title = modelFormField.getTitle(context);
-        String name = modelFormField.getParameterName(context);
-        String buttonType = submitField.getButtonType();
-        String formName = FormRenderer.getCurrentFormName(modelForm, context);
-        String imgSrc = submitField.getImageLocation(context);
-        String confirmation = submitField.getConfirmation(context);
-        String className = "";
-        String alert = "false";
-        if (UtilValidate.isNotEmpty(modelFormField.getWidgetStyle())) {
-            className = modelFormField.getWidgetStyle();
-            if (modelFormField.shouldBeRed(context)) {
-                alert = "true";
-            }
-        }
-        String formId = FormRenderer.getCurrentContainerId(modelForm, context);
-        List<ModelForm.UpdateArea> updateAreas = new LinkedList<>();
-        List<ModelForm.UpdateArea> onSubmitUpdateAreas = 
modelForm.getOnSubmitUpdateAreas();
-        if (UtilValidate.isNotEmpty(onSubmitUpdateAreas)) {
-            updateAreas.addAll(onSubmitUpdateAreas);
-        }
-
-        // Retrieve on click event for submit field
-        List<ModelForm.UpdateArea> onClickUpdateAreas = 
modelFormField.getOnClickUpdateAreas();
-        if (UtilValidate.isNotEmpty(onClickUpdateAreas)) {
-            updateAreas.addAll(onClickUpdateAreas);
-        }
-
-        // This is here for backwards compatibility. Use on-event-update-area
-        // elements instead.
-        String backgroundSubmitRefreshTarget = 
submitField.getBackgroundSubmitRefreshTarget(context);
-        ModelForm.UpdateArea jwtCallback = 
ModelForm.UpdateArea.fromJwtToken(context);
-        if (UtilValidate.isNotEmpty(backgroundSubmitRefreshTarget)) {
-            updateAreas.add(ModelForm.UpdateArea.literal("submit", formId, 
backgroundSubmitRefreshTarget));
-        }
-
-        // In context a callback is present and no other update area to call 
after the submit, so trigger it.
-        if (UtilValidate.isEmpty(updateAreas) && jwtCallback != null && 
!submitField.getPropagateCallback()) {
-            updateAreas = UtilMisc.toList(jwtCallback);
-        }
-        boolean ajaxEnabled = UtilValidate.isNotEmpty(updateAreas) && 
this.javaScriptEnabled;
-        String ajaxUrl = "";
-        if (ajaxEnabled) {
-            Map<String, Object> extraParams = 
CommonWidgetModels.propagateCallbackInParameterMap(context,
-                    submitField.getPropagateCallback(), jwtCallback);
-            ajaxUrl = 
MacroCommonRenderer.createAjaxParamsFromUpdateAreas(updateAreas, extraParams, 
modelForm, "", context);
-        }
-        String tabindex = modelFormField.getTabindex();
-        boolean disabled = modelFormField.getDisabled(context);
-        StringWriter sr = new StringWriter();
-        sr.append("<@renderSubmitField ");
-        sr.append("buttonType=\"");
-        sr.append(buttonType);
-        sr.append("\" className=\"");
-        sr.append(className);
-        sr.append("\" alert=\"");
-        sr.append(alert);
-        sr.append("\" formName=\"");
-        sr.append(formName);
-        sr.append("\" title=\"");
-        sr.append(encode(title, modelFormField, context));
-        sr.append("\" name=\"");
-        sr.append(name);
-        sr.append("\" id=\"");
-        sr.append(id);
-        sr.append("\" event=\"");
-        if (event != null) {
-            sr.append(event);
-        }
-        sr.append("\" action=\"");
-        if (action != null) {
-            sr.append(action);
-        }
-        sr.append("\" imgSrc=\"");
-        sr.append(imgSrc);
-        sr.append("\" containerId=\"");
-        if (ajaxEnabled) {
-            sr.append(formId);
-        }
-        sr.append("\" confirmation =\"");
-        sr.append(confirmation);
-        sr.append("\" ajaxUrl=\"");
-        if (ajaxEnabled) {
-            sr.append(ajaxUrl);
-        }
-        sr.append("\" tabindex=\"");
-        sr.append(tabindex);
-        sr.append("\" disabled=");
-        sr.append(Boolean.toString(disabled));
-        sr.append(" closeOnSubmit=\"");
-        sr.append(String.valueOf(!submitField.getPropagateCallback()));
-        sr.append("\" />");
-        executeMacro(writer, sr.toString());
-        this.appendTooltip(writer, context, modelFormField);
+        writeFtlElement(writer, 
renderableFtlFormElementsBuilder.submitField(context, submitField, 
this.javaScriptEnabled));
+        this.appendTooltip(writer, context, submitField.getModelFormField());
     }
 
     @Override
     public void renderResetField(Appendable writer, Map<String, Object> 
context, ResetField resetField) throws IOException {
-        ModelFormField modelFormField = resetField.getModelFormField();
-        String name = modelFormField.getParameterName(context);
-        String className = "";
-        String alert = "false";
-        if (UtilValidate.isNotEmpty(modelFormField.getWidgetStyle())) {
-            className = modelFormField.getWidgetStyle();
-            if (modelFormField.shouldBeRed(context)) {
-                alert = "true";
-            }
-        }
-        String title = modelFormField.getTitle(context);
-        StringWriter sr = new StringWriter();
-        sr.append("<@renderResetField ");
-        sr.append(" className=\"");
-        sr.append(className);
-        sr.append("\" alert=\"");
-        sr.append(alert);
-        sr.append("\" name=\"");
-        sr.append(name);
-        sr.append("\" title=\"");
-        sr.append(title);
-        sr.append("\" />");
-        executeMacro(writer, sr.toString());
-        this.appendTooltip(writer, context, modelFormField);
+        writeFtlElement(writer, 
renderableFtlFormElementsBuilder.resetField(context, resetField));
+        this.appendTooltip(writer, context, resetField.getModelFormField());
     }
 
     @Override
@@ -581,34 +323,7 @@ public final class MacroFormRenderer implements 
FormStringRenderer {
 
     @Override
     public void renderHiddenField(Appendable writer, Map<String, Object> 
context, ModelFormField modelFormField, String value) throws IOException {
-        String name = modelFormField.getParameterName(context);
-        String action = modelFormField.getAction(context);
-        String conditionGroup = modelFormField.getConditionGroup();
-        String event = modelFormField.getEvent();
-        String id = modelFormField.getCurrentContainerId(context);
-        boolean disabled = modelFormField.getDisabled(context);
-        StringWriter sr = new StringWriter();
-        sr.append("<@renderHiddenField ");
-        sr.append(" name=\"");
-        sr.append(name);
-        sr.append("\" conditionGroup=\"");
-        sr.append(conditionGroup);
-        sr.append("\" value=\"");
-        sr.append(value);
-        sr.append("\" id=\"");
-        sr.append(id);
-        sr.append("\" event=\"");
-        if (event != null) {
-            sr.append(event);
-        }
-        sr.append("\" action=\"");
-        if (action != null) {
-            sr.append(action);
-        }
-        sr.append("\" disabled=");
-        sr.append(Boolean.toString(disabled));
-        sr.append(" />");
-        executeMacro(writer, sr.toString());
+        writeFtlElement(writer, 
renderableFtlFormElementsBuilder.hiddenField(context, modelFormField, value));
     }
 
     @Override
@@ -623,12 +338,13 @@ public final class MacroFormRenderer implements 
FormStringRenderer {
         String titleText = modelFormField.getTitle(context);
         String style = modelFormField.getTitleStyle();
         String id = modelFormField.getCurrentContainerId(context);
-        StringBuilder sb = new StringBuilder();
+        String title = "";
         if (UtilValidate.isNotEmpty(titleText)) {
             if (" ".equals(titleText)) {
-                executeMacro(writer, "<@renderFormatEmptySpace />");
+                writeFtlElement(writer, 
renderableFtlFormElementsBuilder.formatEmptySpace());
             } else {
                 titleText = UtilHttp.encodeAmpersands(titleText);
+                final String encodedTitleText = encodeForMacro(titleText, 
modelFormField, context);
                 titleText = encode(titleText, modelFormField, context);
                 if (UtilValidate.isNotEmpty(modelFormField.getHeaderLink())) {
                     StringBuilder targetBuffer = new StringBuilder();
@@ -643,32 +359,20 @@ public final class MacroFormRenderer implements 
FormStringRenderer {
                     StringWriter sr = new StringWriter();
                     makeHyperlinkString(sr, 
modelFormField.getHeaderLinkStyle(), targetType, targetBuffer.toString(), null, 
titleText, "",
                             modelFormField, this.request, this.response, 
context, "");
-                    String title = sr.toString().replace("\"", "\'");
-                    sr = new StringWriter();
-                    sr.append("<@renderHyperlinkTitle ");
-                    sr.append(" name=\"");
-                    sr.append(modelFormField.getModelForm().getName());
-                    sr.append("\" title=\"");
-                    sr.append(MacroCommonRenderer.escapeFtlString(title));
-                    sr.append("\" />");
-                    executeMacro(writer, sr.toString());
+                    String hyperlinkTitle = sr.toString().replace("\"", "\'");
+                    writeFtlElement(writer, 
renderableFtlFormElementsBuilder.hyperlinkTitle(modelFormField.getModelForm().getName(),
+                            hyperlinkTitle, false));
                 } else if (modelFormField.isSortField()) {
-                    renderSortField(writer, context, modelFormField, 
titleText);
+                    renderSortField(writer, context, modelFormField, 
encodedTitleText);
                 } else if (modelFormField.isRowSubmit()) {
-                    StringWriter sr = new StringWriter();
-                    sr.append("<@renderHyperlinkTitle ");
-                    sr.append(" name=\"");
-                    sr.append(modelFormField.getModelForm().getName());
-                    sr.append("\" title=\"");
-                    sr.append(titleText);
-                    sr.append("\" showSelectAll=\"Y\"/>");
-                    executeMacro(writer, sr.toString());
+                    writeFtlElement(writer, 
renderableFtlFormElementsBuilder.hyperlinkTitle(modelFormField.getModelForm().getName(),
+                            encodedTitleText, true));
                 } else {
-                    sb.append(titleText);
+                    title = encodedTitleText;
                 }
             }
         }
-        if (!sb.toString().isEmpty()) {
+        if (!title.isEmpty()) {
             //check for required field style on single forms
             if (shouldApplyRequiredField(modelFormField)) {
                 String requiredStyle = modelFormField.getRequiredFieldStyle();
@@ -676,10 +380,7 @@ public final class MacroFormRenderer implements 
FormStringRenderer {
                     style = requiredStyle;
                 }
             }
-            StringWriter sr = new StringWriter();
-            sr.append("<@renderFieldTitle ");
-            sr.append(" style=\"");
-            sr.append(style);
+            String fieldHelpText = null;
             String displayHelpText = UtilProperties.getPropertyValue("widget", 
"widget.form.displayhelpText");
             if ("Y".equals(displayHelpText)) {
                 Delegator delegator = WidgetWorker.getDelegator(context);
@@ -687,22 +388,9 @@ public final class MacroFormRenderer implements 
FormStringRenderer {
                 String entityName = modelFormField.getEntityName();
                 String fieldName = modelFormField.getFieldName();
                 String helpText = 
UtilHelpText.getEntityFieldDescription(entityName, fieldName, delegator, 
locale);
-
-                sr.append("\" fieldHelpText=\"");
-                sr.append(MacroCommonRenderer.escapeFtlString(helpText));
-            }
-            sr.append("\" title=\"");
-            sr.append(sb.toString());
-            if (UtilValidate.isNotEmpty(id)) {
-                sr.append("\" id=\"");
-                sr.append(id);
-                sr.append("_title");
-                // Render "for"
-                sr.append("\" for=\"");
-                sr.append(id);
+                fieldHelpText = helpText == null ? "" : helpText;
             }
-            sr.append("\" />");
-            executeMacro(writer, sr.toString());
+            writeFtlElement(writer, 
renderableFtlFormElementsBuilder.fieldTitle(style, title, id, fieldHelpText));
         }
     }
 
@@ -2157,22 +1845,8 @@ public final class MacroFormRenderer implements 
FormStringRenderer {
                 linkUrl = rh.makeLink(this.request, this.response, 
urlPath.concat(UtilCodec.encodeUrl(newQueryString, context)));
             }
         }
-        StringWriter sr = new StringWriter();
-        sr.append("<@renderSortField ");
-        sr.append(" style=\"");
-        sr.append(sortFieldStyle);
-        sr.append("\" title=\"");
-        sr.append(titleText);
-        sr.append("\" linkUrl=\"");
-        sr.append(linkUrl);
-        sr.append("\" ajaxEnabled=");
-        sr.append(Boolean.toString(ajaxEnabled));
         String tooltip = modelFormField.getSortFieldHelpText(context);
-        if (!tooltip.isEmpty()) {
-            sr.append(" tooltip=\"").append(tooltip).append("\"");
-        }
-        sr.append(" />");
-        executeMacro(writer, sr.toString());
+        writeFtlElement(writer, 
renderableFtlFormElementsBuilder.sortField(sortFieldStyle, titleText, linkUrl, 
ajaxEnabled, tooltip));
     }
 
     private void appendTooltip(Appendable writer, Map<String, Object> context, 
ModelFormField 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 1afb043407..d5171cf636 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
@@ -53,6 +53,7 @@ import 
org.apache.ofbiz.base.util.string.FlexibleStringExpander;
 import org.apache.ofbiz.webapp.control.RequestHandler;
 import org.apache.ofbiz.widget.WidgetWorker;
 import org.apache.ofbiz.widget.content.StaticContentUrlProvider;
+import org.apache.ofbiz.widget.model.CommonWidgetModels;
 import org.apache.ofbiz.widget.model.ModelForm;
 import org.apache.ofbiz.widget.model.ModelFormField;
 import org.apache.ofbiz.widget.model.ModelFormField.ContainerField;
@@ -85,8 +86,6 @@ 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) {
@@ -95,7 +94,6 @@ 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) {
@@ -1277,8 +1275,6 @@ public final class RenderableFtlFormElementsBuilder {
         UtilCodec.SimpleEncoder encoder = (UtilCodec.SimpleEncoder) 
context.get("simpleEncoder");
         if (modelFormField.getEncodeOutput() && encoder != null) {
             value = encoder.encode(value);
-        } else {
-            value = internalEncoder.encode(value);
         }
         return value;
     }
@@ -1296,6 +1292,280 @@ public final class RenderableFtlFormElementsBuilder {
                 .orElse(value);
     }
 
+    /**
+     * Creates the macro call that renders the check boxes of a check field.
+     * @param context the context
+     * @param checkField the check field
+     * @return the RenderableFtl
+     */
+    public RenderableFtl checkField(final Map<String, Object> context, final 
ModelFormField.CheckField checkField) {
+        final ModelFormField modelFormField = checkField.getModelFormField();
+        final String currentValue = modelFormField.getEntry(context);
+        final Boolean allChecked = checkField.isAllChecked(context);
+        final String checkBox = modelFormField.getAttributeName();
+
+        List<String> checkedByDefault = Collections.emptyList();
+        final Object checkBoxValue = context.get(checkBox);
+        if (checkBoxValue != null && 
!checkBoxValue.getClass().equals(String.class)) {
+            checkedByDefault = StringUtil.toList(checkBoxValue.toString());
+        }
+
+        List<String> currentValueList = null;
+        if (UtilValidate.isNotEmpty(currentValue)) {
+            currentValueList = currentValue.startsWith("[") ? 
StringUtil.toList(currentValue) : UtilMisc.toList(currentValue);
+        }
+
+        final List<Map<String, String>> items = new ArrayList<>();
+        for (final ModelFormField.OptionValue optionValue : 
checkField.getAllOptionValues(context, WidgetWorker.getDelegator(context))) {
+            final boolean checked;
+            if (UtilValidate.isNotEmpty(currentValueList)) {
+                checked = currentValueList.contains(optionValue.getKey());
+            } else if (UtilValidate.isNotEmpty(checkedByDefault)) {
+                checked = checkedByDefault.contains(optionValue.getKey());
+            } else {
+                checked = allChecked;
+            }
+            items.add(Map.of(
+                    "value", nonNull(optionValue.getKey()),
+                    "description", 
nonNull(encode(optionValue.getDescription(), modelFormField, context)),
+                    "checked", String.valueOf(checked)));
+        }
+
+        final var builder = 
RenderableFtlMacroCall.builder().name("renderCheckField");
+        applyWidgetStyleAndAlert(modelFormField, context, builder);
+        return builder
+                .objectParameter("items", items)
+                .stringParameter("id", 
nonNull(modelFormField.getCurrentContainerId(context)))
+                .stringParameter("conditionGroup", 
nonNull(modelFormField.getConditionGroup()))
+                .stringParameter("currentValue", nonNull(currentValue))
+                .stringParameter("name", 
nonNull(modelFormField.getParameterName(context)))
+                .stringParameter("event", nonNull(modelFormField.getEvent()))
+                .stringParameter("action", 
nonNull(modelFormField.getAction(context)))
+                .stringParameter("tabindex", 
nonNull(modelFormField.getTabindex()))
+                .booleanParameter("disabled", 
modelFormField.getDisabled(context))
+                .build();
+    }
+
+    /**
+     * Creates the macro call that renders the radio buttons of a radio field.
+     * @param context the context
+     * @param radioField the radio field
+     * @return the RenderableFtl
+     */
+    public RenderableFtl radioField(final Map<String, Object> context, final 
ModelFormField.RadioField radioField) {
+        final ModelFormField modelFormField = radioField.getModelFormField();
+
+        final List<Map<String, String>> items = new ArrayList<>();
+        for (final ModelFormField.OptionValue optionValue : 
radioField.getAllOptionValues(context, WidgetWorker.getDelegator(context))) {
+            items.add(Map.of(
+                    "key", nonNull(optionValue.getKey()),
+                    "description", 
nonNull(encode(optionValue.getDescription(), modelFormField, context))));
+        }
+
+        final var builder = 
RenderableFtlMacroCall.builder().name("renderRadioField");
+        applyWidgetStyleAndAlert(modelFormField, context, builder);
+        return builder
+                .objectParameter("items", items)
+                .stringParameter("currentValue", 
nonNull(modelFormField.getEntry(context)))
+                .stringParameter("noCurrentSelectedKey", 
nonNull(radioField.getNoCurrentSelectedKey(context)))
+                .stringParameter("name", 
nonNull(modelFormField.getParameterName(context)))
+                .stringParameter("event", nonNull(modelFormField.getEvent()))
+                .stringParameter("action", 
nonNull(modelFormField.getAction(context)))
+                .stringParameter("conditionGroup", 
nonNull(modelFormField.getConditionGroup()))
+                .stringParameter("tabindex", 
nonNull(modelFormField.getTabindex()))
+                .booleanParameter("disabled", 
modelFormField.getDisabled(context))
+                .build();
+    }
+
+    /**
+     * Creates the macro call that renders a submit button.
+     * @param context the context
+     * @param submitField the submit field
+     * @param javaScriptEnabled whether JavaScript is enabled in the browser 
of the user
+     * @return the RenderableFtl
+     */
+    public RenderableFtl submitField(final Map<String, Object> context, final 
ModelFormField.SubmitField submitField,
+                                     final boolean javaScriptEnabled) {
+        final ModelFormField modelFormField = submitField.getModelFormField();
+        final ModelForm modelForm = modelFormField.getModelForm();
+        final String formId = FormRenderer.getCurrentContainerId(modelForm, 
context);
+
+        List<ModelForm.UpdateArea> updateAreas = new ArrayList<>();
+        final List<ModelForm.UpdateArea> onSubmitUpdateAreas = 
modelForm.getOnSubmitUpdateAreas();
+        if (UtilValidate.isNotEmpty(onSubmitUpdateAreas)) {
+            updateAreas.addAll(onSubmitUpdateAreas);
+        }
+
+        // Retrieve on click event for submit field
+        final List<ModelForm.UpdateArea> onClickUpdateAreas = 
modelFormField.getOnClickUpdateAreas();
+        if (UtilValidate.isNotEmpty(onClickUpdateAreas)) {
+            updateAreas.addAll(onClickUpdateAreas);
+        }
+
+        // This is here for backwards compatibility. Use on-event-update-area 
elements instead.
+        final String backgroundSubmitRefreshTarget = 
submitField.getBackgroundSubmitRefreshTarget(context);
+        final ModelForm.UpdateArea jwtCallback = 
ModelForm.UpdateArea.fromJwtToken(context);
+        if (UtilValidate.isNotEmpty(backgroundSubmitRefreshTarget)) {
+            updateAreas.add(ModelForm.UpdateArea.literal("submit", formId, 
backgroundSubmitRefreshTarget));
+        }
+
+        // In context a callback is present and no other update area to call 
after the submit, so trigger it.
+        if (UtilValidate.isEmpty(updateAreas) && jwtCallback != null && 
!submitField.getPropagateCallback()) {
+            updateAreas = UtilMisc.toList(jwtCallback);
+        }
+        final boolean ajaxEnabled = UtilValidate.isNotEmpty(updateAreas) && 
javaScriptEnabled;
+        String ajaxUrl = "";
+        if (ajaxEnabled) {
+            final Map<String, Object> extraParams = 
CommonWidgetModels.propagateCallbackInParameterMap(context,
+                    submitField.getPropagateCallback(), jwtCallback);
+            ajaxUrl = 
MacroCommonRenderer.createAjaxParamsFromUpdateAreas(updateAreas, extraParams, 
modelForm, "", context);
+        }
+
+        final var builder = 
RenderableFtlMacroCall.builder().name("renderSubmitField");
+        applyWidgetStyleAndAlert(modelFormField, context, builder);
+        return builder
+                .stringParameter("buttonType", 
nonNull(submitField.getButtonType()))
+                .stringParameter("formName", 
nonNull(FormRenderer.getCurrentFormName(modelForm, context)))
+                .stringParameter("title", 
nonNull(encode(modelFormField.getTitle(context), modelFormField, context)))
+                .stringParameter("name", 
nonNull(modelFormField.getParameterName(context)))
+                .stringParameter("id", 
nonNull(modelFormField.getCurrentContainerId(context)))
+                .stringParameter("event", nonNull(modelFormField.getEvent()))
+                .stringParameter("action", 
nonNull(modelFormField.getAction(context)))
+                .stringParameter("imgSrc", 
nonNull(submitField.getImageLocation(context)))
+                .stringParameter("containerId", ajaxEnabled ? nonNull(formId) 
: "")
+                .stringParameter("confirmation", 
nonNull(submitField.getConfirmation(context)))
+                .stringParameter("ajaxUrl", ajaxEnabled ? nonNull(ajaxUrl) : 
"")
+                .stringParameter("tabindex", 
nonNull(modelFormField.getTabindex()))
+                .booleanParameter("disabled", 
modelFormField.getDisabled(context))
+                .stringParameter("closeOnSubmit", 
String.valueOf(!submitField.getPropagateCallback()))
+                .build();
+    }
+
+    /**
+     * Creates the macro call that renders a reset button.
+     * @param context the context
+     * @param resetField the reset field
+     * @return the RenderableFtl
+     */
+    public RenderableFtl resetField(final Map<String, Object> context, final 
ModelFormField.ResetField resetField) {
+        final ModelFormField modelFormField = resetField.getModelFormField();
+        final var builder = 
RenderableFtlMacroCall.builder().name("renderResetField");
+        applyWidgetStyleAndAlert(modelFormField, context, builder);
+        return builder
+                .stringParameter("name", 
nonNull(modelFormField.getParameterName(context)))
+                .stringParameter("title", 
nonNull(modelFormField.getTitle(context)))
+                .build();
+    }
+
+    /**
+     * Creates the macro call that renders a hidden field.
+     * @param context the context
+     * @param modelFormField the form field
+     * @param value the value of the field, which may contain the quotes 
escaped for a FreeMarker string literal
+     * @return the RenderableFtl
+     */
+    public RenderableFtl hiddenField(final Map<String, Object> context, final 
ModelFormField modelFormField, final String value) {
+        return RenderableFtlMacroCall.builder()
+                .name("renderHiddenField")
+                .stringParameter("name", 
nonNull(modelFormField.getParameterName(context)))
+                .stringParameter("conditionGroup", 
nonNull(modelFormField.getConditionGroup()))
+                // the form fields escape the double quotes with a backslash 
because the value was a FreeMarker string literal
+                .stringParameter("value", nonNull(value).replace("\\\"", "\""))
+                .stringParameter("id", 
nonNull(modelFormField.getCurrentContainerId(context)))
+                .stringParameter("event", nonNull(modelFormField.getEvent()))
+                .stringParameter("action", 
nonNull(modelFormField.getAction(context)))
+                .booleanParameter("disabled", 
modelFormField.getDisabled(context))
+                .build();
+    }
+
+    /**
+     * Creates the macro call that renders an empty field title.
+     * @return the RenderableFtl
+     */
+    public RenderableFtl formatEmptySpace() {
+        return 
RenderableFtlMacroCall.builder().name("renderFormatEmptySpace").build();
+    }
+
+    /**
+     * Creates the macro call that renders a field title made of a link or of 
a select all check box.
+     * @param formName the name of the form
+     * @param title the title, which can contain HTML
+     * @param showSelectAll true to show the select all check box
+     * @return the RenderableFtl
+     */
+    public RenderableFtl hyperlinkTitle(final String formName, final String 
title, final boolean showSelectAll) {
+        final var builder = RenderableFtlMacroCall.builder()
+                .name("renderHyperlinkTitle")
+                .stringParameter("name", nonNull(formName))
+                .stringParameter("title", nonNull(title));
+        if (showSelectAll) {
+            builder.stringParameter("showSelectAll", "Y");
+        }
+        return builder.build();
+    }
+
+    /**
+     * Creates the macro call that renders a field title.
+     * @param style the style of the title
+     * @param title the title
+     * @param id the id of the field, or empty if the title has no id and does 
not refer to a field
+     * @param fieldHelpText the help text of the field, or null if there is 
none to show
+     * @return the RenderableFtl
+     */
+    public RenderableFtl fieldTitle(final String style, final String title, 
final String id, final String fieldHelpText) {
+        final var builder = RenderableFtlMacroCall.builder()
+                .name("renderFieldTitle")
+                .stringParameter("style", nonNull(style))
+                .stringParameter("title", nonNull(title));
+        if (fieldHelpText != null) {
+            builder.stringParameter("fieldHelpText", fieldHelpText);
+        }
+        if (UtilValidate.isNotEmpty(id)) {
+            builder.stringParameter("id", id + 
"_title").stringParameter("for", id);
+        }
+        return builder.build();
+    }
+
+    /**
+     * Creates the macro call that renders a field title which sorts the list 
when clicked.
+     * @param style the style of the link
+     * @param title the title
+     * @param linkUrl the url of the link
+     * @param ajaxEnabled true if the link updates areas of the page
+     * @param tooltip the tooltip, or empty if there is none
+     * @return the RenderableFtl
+     */
+    public RenderableFtl sortField(final String style, final String title, 
final String linkUrl, final boolean ajaxEnabled,
+                                   final String tooltip) {
+        final var builder = RenderableFtlMacroCall.builder()
+                .name("renderSortField")
+                .stringParameter("style", nonNull(style))
+                .stringParameter("title", nonNull(title))
+                .stringParameter("linkUrl", nonNull(linkUrl))
+                .booleanParameter("ajaxEnabled", ajaxEnabled);
+        if (UtilValidate.isNotEmpty(tooltip)) {
+            builder.stringParameter("tooltip", tooltip);
+        }
+        return builder.build();
+    }
+
+    private static String nonNull(final String value) {
+        return value == null ? "" : value;
+    }
+
+    private static void applyWidgetStyleAndAlert(final ModelFormField 
modelFormField, final Map<String, Object> context,
+                                                 final 
RenderableFtlMacroCallBuilder builder) {
+        String className = "";
+        String alert = "false";
+        if (UtilValidate.isNotEmpty(modelFormField.getWidgetStyle())) {
+            className = modelFormField.getWidgetStyle();
+            if (modelFormField.shouldBeRed(context)) {
+                alert = "true";
+            }
+        }
+        builder.stringParameter("className", 
className).stringParameter("alert", alert);
+    }
+
     private static void applyCommonStyling(final ModelFormField 
modelFormField, final Map<String, Object> context,
                                            final RenderableFtlMacroCallBuilder 
builder) {
         final var classNames = new ArrayList<String>();
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 40097f106c..ab1574b133 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
@@ -42,7 +42,6 @@ import static org.mockito.Mockito.when;
 
 import java.io.IOException;
 import java.io.StringWriter;
-import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
@@ -74,7 +73,6 @@ import org.junit.jupiter.api.Test;
 import org.mockito.ArgumentCaptor;
 import org.mockito.MockedStatic;
 
-import com.google.common.collect.ImmutableList;
 import com.google.common.collect.ImmutableMap;
 
 import freemarker.core.Environment;
@@ -265,92 +263,49 @@ public final class MacroFormRendererTest {
     @Test
     public void checkFieldMacroRendered() throws IOException {
         final ModelFormField.CheckField checkField = 
mock(ModelFormField.CheckField.class);
-        when(checkField.getModelFormField()).thenReturn(modelFormField);
-        final List<ModelFormField.OptionValue> optionValues = ImmutableList.of(
-                new ModelFormField.OptionValue("KEY1", "DESC1"),
-                new ModelFormField.OptionValue("KEY2", "DESC2"),
-                new ModelFormField.OptionValue("KEY3", "DESC3"),
-                new ModelFormField.OptionValue("KEY4", "DESC4"));
-
-        when(modelFormField.getEntry(notNull())).thenReturn("KEY2");
-        when(checkField.getAllOptionValues(notNull(), 
any())).thenReturn(optionValues);
+        when(renderableFtlFormElementsBuilder.checkField(notNull(), 
eq(checkField))).thenReturn(genericMacroCall);
+        genericTooltipRenderedExpectation(checkField);
 
         macroFormRenderer.renderCheckField(appendable, ImmutableMap.of(), 
checkField);
-        assertAndGetMacroString("renderCheckField", ImmutableMap.of(
-                "currentValue", "KEY2",
-                "items", ImmutableList.of(
-                        "{'value':'KEY1', 'description':'DESC1', 
'checked':'false'}",
-                        "{'value':'KEY2', 'description':'DESC2', 
'checked':'true'}",
-                        "{'value':'KEY3', 'description':'DESC3', 
'checked':'false'}",
-                        "{'value':'KEY4', 'description':'DESC4', 
'checked':'false'}")));
-
-        when(modelFormField.getEntry(notNull())).thenReturn("");
-        
when(checkField.getModelFormField().getAttributeName()).thenReturn("FieldName");
-
-        StringWriter writer = new StringWriter();
-        Map<String, Object> context = new HashMap<>();
-        List<String> fieldName = new ArrayList<>();
-        fieldName.add("KEY1");
-        fieldName.add("KEY3");
-        context.put("FieldName", fieldName);
-
-        try {
-            macroFormRenderer.renderCheckField(writer, context, checkField);
-        } catch (IOException e) {
-            e.printStackTrace();
-        }
-
-        assertAndGetMacroString("renderCheckField", ImmutableMap.of(
-                "items", ImmutableList.of(
-                        "{'value':'KEY1', 'description':'DESC1', 
'checked':'true'}",
-                        "{'value':'KEY2', 'description':'DESC2', 
'checked':'false'}",
-                        "{'value':'KEY3', 'description':'DESC3', 
'checked':'true'}",
-                        "{'value':'KEY4', 'description':'DESC4', 
'checked':'false'}")));
 
+        genericSingleMacroRenderedVerification();
+        genericTooltipRenderedVerification();
     }
 
     @Test
     public void radioFieldMacroRendered() throws IOException {
         final ModelFormField.RadioField radioField = 
mock(ModelFormField.RadioField.class);
-        when(radioField.getModelFormField()).thenReturn(modelFormField);
-        final List<ModelFormField.OptionValue> optionValues = ImmutableList.of(
-                new ModelFormField.OptionValue("KEY1", "DESC1"),
-                new ModelFormField.OptionValue("KEY2", "DESC2"));
-
-        when(modelFormField.getEntry(notNull())).thenReturn("KEY2");
-        when(radioField.getAllOptionValues(notNull(), 
any())).thenReturn(optionValues);
+        when(renderableFtlFormElementsBuilder.radioField(notNull(), 
eq(radioField))).thenReturn(genericMacroCall);
+        genericTooltipRenderedExpectation(radioField);
 
         macroFormRenderer.renderRadioField(appendable, ImmutableMap.of(), 
radioField);
-        assertAndGetMacroString("renderRadioField", ImmutableMap.of(
-                "currentValue", "KEY2",
-                "items", ImmutableList.of("{'key':'KEY1', 
'description':'DESC1'}",
-                        "{'key':'KEY2', 'description':'DESC2'}")));
+
+        genericSingleMacroRenderedVerification();
+        genericTooltipRenderedVerification();
     }
 
     @Test
     public void submitFieldMacroRendered() throws IOException {
         final ModelFormField.SubmitField submitField = 
mock(ModelFormField.SubmitField.class);
-        when(submitField.getModelFormField()).thenReturn(modelFormField);
-        when(modelFormField.getTitle(notNull())).thenReturn("BUTTONTITLE");
-        // renderSubmitField() does 
updateAreas.addAll(modelForm.getOnSubmitUpdateAreas()) unconditionally
-        // once the isNotEmpty() guard passes. JMockit's cascading defaults an 
unstubbed array-returning
-        // call to an empty array; Mockito's deep-stub default is null, and 
AbstractCollection.addAll()
-        // dereferences Collection.toArray() without a null-check - needs an 
explicit non-null list here.
-        
when(modelFormField.getModelForm().getOnSubmitUpdateAreas()).thenReturn(new 
ArrayList<>());
-        when(modelFormField.getOnClickUpdateAreas()).thenReturn(new 
ArrayList<>());
+        when(renderableFtlFormElementsBuilder.submitField(notNull(), 
eq(submitField), anyBoolean())).thenReturn(genericMacroCall);
+        genericTooltipRenderedExpectation(submitField);
 
         macroFormRenderer.renderSubmitField(appendable, ImmutableMap.of(), 
submitField);
-        assertAndGetMacroString("renderSubmitField", ImmutableMap.of("title", 
"BUTTONTITLE"));
+
+        genericSingleMacroRenderedVerification();
+        genericTooltipRenderedVerification();
     }
 
     @Test
     public void resetFieldMacroRendered() throws IOException {
         final ModelFormField.ResetField resetField = 
mock(ModelFormField.ResetField.class);
-        when(resetField.getModelFormField()).thenReturn(modelFormField);
-        when(modelFormField.getTitle(notNull())).thenReturn("BUTTONTITLE");
+        when(renderableFtlFormElementsBuilder.resetField(notNull(), 
eq(resetField))).thenReturn(genericMacroCall);
+        genericTooltipRenderedExpectation(resetField);
 
         macroFormRenderer.renderResetField(appendable, ImmutableMap.of(), 
resetField);
-        assertAndGetMacroString("renderResetField", ImmutableMap.of("title", 
"BUTTONTITLE"));
+
+        genericSingleMacroRenderedVerification();
+        genericTooltipRenderedVerification();
     }
 
     @Test
@@ -358,25 +313,44 @@ public final class MacroFormRendererTest {
         final ModelFormField.HiddenField hiddenField = 
mock(ModelFormField.HiddenField.class);
         when(hiddenField.getModelFormField()).thenReturn(modelFormField);
         when(hiddenField.getValue(notNull())).thenReturn("HIDDENVALUE");
+        when(renderableFtlFormElementsBuilder.hiddenField(notNull(), 
eq(modelFormField), eq("HIDDENVALUE"))).thenReturn(genericMacroCall);
 
         macroFormRenderer.renderHiddenField(appendable, ImmutableMap.of(), 
hiddenField);
-        assertAndGetMacroString("renderHiddenField", ImmutableMap.of("value", 
"HIDDENVALUE"));
+
+        genericSingleMacroRenderedVerification();
     }
 
     @Test
     public void emptyFieldTitleMacroRendered() throws IOException {
         when(modelFormField.getTitle(notNull())).thenReturn(" ");
+        
when(renderableFtlFormElementsBuilder.formatEmptySpace()).thenReturn(genericMacroCall);
 
         macroFormRenderer.renderFieldTitle(appendable, ImmutableMap.of(), 
modelFormField);
-        assertAndGetMacroString("renderFormatEmptySpace");
+
+        genericSingleMacroRenderedVerification();
     }
 
     @Test
     public void fieldTitleMacroRendered() throws IOException {
         when(modelFormField.getTitle(notNull())).thenReturn("FIELDTITLE");
+        
when(renderableFtlFormElementsBuilder.fieldTitle(nullable(String.class), 
eq("FIELDTITLE"), nullable(String.class),
+                nullable(String.class))).thenReturn(genericMacroCall);
+
+        macroFormRenderer.renderFieldTitle(appendable, ImmutableMap.of(), 
modelFormField);
+
+        genericSingleMacroRenderedVerification();
+    }
+
+    @Test
+    public void rowSubmitFieldTitleMacroRendered() throws IOException {
+        when(modelFormField.getTitle(notNull())).thenReturn("FIELDTITLE");
+        when(modelFormField.isRowSubmit()).thenReturn(true);
+        when(modelFormField.getModelForm().getName()).thenReturn("FORMNAME");
+        when(renderableFtlFormElementsBuilder.hyperlinkTitle("FORMNAME", 
"FIELDTITLE", true)).thenReturn(genericMacroCall);
 
         macroFormRenderer.renderFieldTitle(appendable, ImmutableMap.of(), 
modelFormField);
-        assertAndGetMacroString("renderFieldTitle", ImmutableMap.of("title", 
"FIELDTITLE"));
+
+        genericSingleMacroRenderedVerification();
     }
 
     @Test
@@ -635,11 +609,13 @@ public final class MacroFormRendererTest {
         when(modelFormField.getModelForm()).thenReturn(modelForm);
         
when(modelForm.getPaginateTarget(notNull())).thenReturn(paginateTarget);
         
when(modelFormField.getSortFieldHelpText(notNull())).thenReturn("HELPTEXT");
+        
when(renderableFtlFormElementsBuilder.sortField(nullable(String.class), 
eq("TITLE"), nullable(String.class), eq(false),
+                eq("HELPTEXT"))).thenReturn(genericMacroCall);
 
         final Map<String, Object> context = new HashMap<>();
         macroFormRenderer.renderSortField(appendable, context, modelFormField, 
"TITLE");
 
-        assertAndGetMacroString("renderSortField", ImmutableMap.of("title", 
"TITLE"));
+        genericSingleMacroRenderedVerification();
     }
 
     @Test
@@ -704,14 +680,15 @@ public final class MacroFormRendererTest {
         
when(modelForm.getPaginateTarget(notNull())).thenReturn(paginateTarget);
         when(requestHandler.makeLink(notNull(), notNull(), 
contains(qbeString))).thenReturn(linkFromQbeString);
         
when(modelFormField.getSortFieldHelpText(notNull())).thenReturn("HELPTEXT");
+        
when(renderableFtlFormElementsBuilder.sortField(nullable(String.class), eq(""), 
eq(linkFromQbeString), eq(false),
+                eq("HELPTEXT"))).thenReturn(genericMacroCall);
 
         final Map<String, Object> context = new HashMap<>();
         context.put("_QBESTRING_", qbeString);
         context.put("listSize", 100);
         macroFormRenderer.renderSortField(appendable, context, modelFormField, 
"");
 
-        assertAndGetMacroString("renderSortField", ImmutableMap.of(
-                "linkUrl", new FreemarkerRawString(linkFromQbeString)));
+        genericSingleMacroRenderedVerification();
     }
 
     @Test
diff --git 
a/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/RenderableFtlFormElementsBuilderTest.java
 
b/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/RenderableFtlFormElementsBuilderTest.java
index 35b3213e96..0bbb861ba3 100644
--- 
a/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/RenderableFtlFormElementsBuilderTest.java
+++ 
b/framework/widget/src/test/java/org/apache/ofbiz/widget/renderer/macro/RenderableFtlFormElementsBuilderTest.java
@@ -36,6 +36,7 @@ import org.junit.jupiter.api.Test;
 import jakarta.servlet.http.HttpServletRequest;
 import jakarta.servlet.http.HttpServletResponse;
 import jakarta.servlet.http.HttpSession;
+import java.util.ArrayList;
 import java.util.HashMap;
 import java.util.List;
 import java.util.Locale;
@@ -43,6 +44,9 @@ import java.util.Map;
 
 import static org.hamcrest.MatcherAssert.assertThat;
 import static org.hamcrest.Matchers.equalTo;
+import static org.hamcrest.Matchers.not;
+import static org.hamcrest.Matchers.hasKey;
+import static org.mockito.ArgumentMatchers.any;
 import static org.mockito.ArgumentMatchers.notNull;
 import static org.mockito.ArgumentMatchers.nullable;
 import static org.mockito.Mockito.RETURNS_DEEP_STUBS;
@@ -537,4 +541,181 @@ public final class RenderableFtlFormElementsBuilderTest {
         assertThat(renderableFtl, 
MacroCallMatcher.hasParameters(MacroCallParameterMatcher.hasNameAndStringValue("timePickerIncrement",
 "5")));
         assertThat(renderableFtl, 
MacroCallMatcher.hasParameters(MacroCallParameterMatcher.hasNameAndBooleanValue("timePickerSeconds",
 true)));
     }
+
+    private static List<ModelFormField.OptionValue> threeOptions() {
+        final List<ModelFormField.OptionValue> optionValues = new 
ArrayList<>();
+        optionValues.add(new ModelFormField.OptionValue("KEY1", "DESC1"));
+        optionValues.add(new ModelFormField.OptionValue("KEY2", "DESC2"));
+        optionValues.add(new ModelFormField.OptionValue("KEY3", "DESC3"));
+        return optionValues;
+    }
+
+    private static Map<String, String> checkItem(final String key, final 
String description, final boolean checked) {
+        return Map.of("value", key, "description", description, "checked", 
Boolean.toString(checked));
+    }
+
+    @Test
+    public void checkFieldChecksTheCurrentValue() {
+        final ModelFormField.CheckField checkField = 
mock(ModelFormField.CheckField.class);
+        when(checkField.getModelFormField()).thenReturn(modelFormField);
+        when(checkField.isAllChecked(notNull())).thenReturn(false);
+        when(checkField.getAllOptionValues(notNull(), 
any())).thenReturn(threeOptions());
+        when(modelFormField.getEntry(notNull())).thenReturn("KEY2");
+
+        final RenderableFtlMacroCall macroCall = (RenderableFtlMacroCall) 
renderableFtlFormElementsBuilder.checkField(new HashMap<>(), checkField);
+        assertThat(macroCall, 
MacroCallMatcher.hasNameAndParameters("renderCheckField",
+                
MacroCallParameterMatcher.hasNameAndStringValue("currentValue", "KEY2")));
+        assertThat(macroCall.getParameters().get("items"), equalTo(List.of(
+                checkItem("KEY1", "DESC1", false), checkItem("KEY2", "DESC2", 
true), checkItem("KEY3", "DESC3", false))));
+    }
+
+    @Test
+    public void 
checkFieldChecksTheValuesOfTheContextWhenThereIsNoCurrentValue() {
+        final ModelFormField.CheckField checkField = 
mock(ModelFormField.CheckField.class);
+        when(checkField.getModelFormField()).thenReturn(modelFormField);
+        when(checkField.isAllChecked(notNull())).thenReturn(false);
+        when(checkField.getAllOptionValues(notNull(), 
any())).thenReturn(threeOptions());
+        when(modelFormField.getEntry(notNull())).thenReturn("");
+        when(modelFormField.getAttributeName()).thenReturn("FieldName");
+
+        final Map<String, Object> context = new HashMap<>();
+        context.put("FieldName", List.of("KEY1", "KEY3"));
+
+        final RenderableFtlMacroCall macroCall = (RenderableFtlMacroCall) 
renderableFtlFormElementsBuilder.checkField(context, checkField);
+        assertThat(macroCall.getParameters().get("items"), equalTo(List.of(
+                checkItem("KEY1", "DESC1", true), checkItem("KEY2", "DESC2", 
false), checkItem("KEY3", "DESC3", true))));
+    }
+
+    @Test
+    public void checkFieldPassesKeysAndDescriptionsAsTheyAre() {
+        final ModelFormField.CheckField checkField = 
mock(ModelFormField.CheckField.class);
+        when(checkField.getModelFormField()).thenReturn(modelFormField);
+        when(checkField.isAllChecked(notNull())).thenReturn(false);
+        final List<ModelFormField.OptionValue> optionValues = new 
ArrayList<>();
+        optionValues.add(new ModelFormField.OptionValue("it's ${1+1}", "say 
\"hi\" \\ #{2}"));
+        when(checkField.getAllOptionValues(notNull(), 
any())).thenReturn(optionValues);
+        when(modelFormField.getEntry(notNull())).thenReturn("");
+
+        final RenderableFtlMacroCall macroCall = (RenderableFtlMacroCall) 
renderableFtlFormElementsBuilder.checkField(new HashMap<>(), checkField);
+        assertThat(macroCall.getParameters().get("items"), equalTo(List.of(
+                checkItem("it's ${1+1}", "say \"hi\" \\ #{2}", false))));
+    }
+
+    @Test
+    public void radioFieldSetsItemsCurrentValueAndNoCurrentSelectedKey() {
+        final ModelFormField.RadioField radioField = 
mock(ModelFormField.RadioField.class);
+        when(radioField.getModelFormField()).thenReturn(modelFormField);
+        when(radioField.getAllOptionValues(notNull(), 
any())).thenReturn(threeOptions());
+        
when(radioField.getNoCurrentSelectedKey(notNull())).thenReturn("NOKEY");
+        when(modelFormField.getEntry(notNull())).thenReturn("KEY2");
+
+        final RenderableFtlMacroCall macroCall = (RenderableFtlMacroCall) 
renderableFtlFormElementsBuilder.radioField(new HashMap<>(), radioField);
+        assertThat(macroCall, 
MacroCallMatcher.hasNameAndParameters("renderRadioField",
+                
MacroCallParameterMatcher.hasNameAndStringValue("currentValue", "KEY2"),
+                
MacroCallParameterMatcher.hasNameAndStringValue("noCurrentSelectedKey", 
"NOKEY")));
+        assertThat(macroCall.getParameters().get("items"), equalTo(List.of(
+                Map.of("key", "KEY1", "description", "DESC1"),
+                Map.of("key", "KEY2", "description", "DESC2"),
+                Map.of("key", "KEY3", "description", "DESC3"))));
+    }
+
+    @Test
+    public void submitFieldSetsTitleAndDoesNotUseAjaxWithoutUpdateAreas() {
+        final ModelFormField.SubmitField submitField = 
mock(ModelFormField.SubmitField.class);
+        when(submitField.getModelFormField()).thenReturn(modelFormField);
+        when(modelFormField.getTitle(notNull())).thenReturn("BUTTONTITLE");
+        
when(modelFormField.getModelForm().getOnSubmitUpdateAreas()).thenReturn(new 
ArrayList<>());
+        when(modelFormField.getOnClickUpdateAreas()).thenReturn(new 
ArrayList<>());
+
+        final RenderableFtl renderableFtl = 
renderableFtlFormElementsBuilder.submitField(new HashMap<>(), submitField, 
true);
+        assertThat(renderableFtl, 
MacroCallMatcher.hasNameAndParameters("renderSubmitField",
+                MacroCallParameterMatcher.hasNameAndStringValue("title", 
"BUTTONTITLE"),
+                MacroCallParameterMatcher.hasNameAndStringValue("containerId", 
""),
+                MacroCallParameterMatcher.hasNameAndStringValue("ajaxUrl", ""),
+                
MacroCallParameterMatcher.hasNameAndStringValue("closeOnSubmit", "true")));
+    }
+
+    @Test
+    public void resetFieldSetsTitleAndName() {
+        final ModelFormField.ResetField resetField = 
mock(ModelFormField.ResetField.class);
+        when(resetField.getModelFormField()).thenReturn(modelFormField);
+        when(modelFormField.getTitle(notNull())).thenReturn("BUTTONTITLE");
+        
when(modelFormField.getParameterName(notNull())).thenReturn("PARAMNAME");
+
+        final RenderableFtl renderableFtl = 
renderableFtlFormElementsBuilder.resetField(new HashMap<>(), resetField);
+        assertThat(renderableFtl, 
MacroCallMatcher.hasNameAndParameters("renderResetField",
+                MacroCallParameterMatcher.hasNameAndStringValue("title", 
"BUTTONTITLE"),
+                MacroCallParameterMatcher.hasNameAndStringValue("name", 
"PARAMNAME")));
+    }
+
+    @Test
+    public void hiddenFieldSetsTheValue() {
+        
when(modelFormField.getParameterName(notNull())).thenReturn("PARAMNAME");
+
+        final RenderableFtl renderableFtl = 
renderableFtlFormElementsBuilder.hiddenField(new HashMap<>(), modelFormField, 
"HIDDENVALUE");
+        assertThat(renderableFtl, 
MacroCallMatcher.hasNameAndParameters("renderHiddenField",
+                MacroCallParameterMatcher.hasNameAndStringValue("name", 
"PARAMNAME"),
+                MacroCallParameterMatcher.hasNameAndStringValue("value", 
"HIDDENVALUE")));
+    }
+
+    @Test
+    public void hiddenFieldRemovesTheBackslashOfTheEscapedQuotes() {
+        final RenderableFtl renderableFtl = 
renderableFtlFormElementsBuilder.hiddenField(new HashMap<>(), modelFormField, 
"say \\\"hi\\\"");
+        assertThat(renderableFtl, 
MacroCallMatcher.hasNameAndParameters("renderHiddenField",
+                MacroCallParameterMatcher.hasNameAndStringValue("value", "say 
\"hi\"")));
+    }
+
+    @Test
+    public void formatEmptySpaceHasNoParameters() {
+        final RenderableFtlMacroCall macroCall = (RenderableFtlMacroCall) 
renderableFtlFormElementsBuilder.formatEmptySpace();
+        assertThat(macroCall.getName(), equalTo("renderFormatEmptySpace"));
+        assertThat(macroCall.getParameters().isEmpty(), equalTo(true));
+    }
+
+    @Test
+    public void hyperlinkTitleShowsTheSelectAllOnlyWhenAsked() {
+        final RenderableFtlMacroCall withSelectAll = (RenderableFtlMacroCall) 
renderableFtlFormElementsBuilder.hyperlinkTitle("FORM", "TITLE", true);
+        assertThat(withSelectAll, 
MacroCallMatcher.hasNameAndParameters("renderHyperlinkTitle",
+                MacroCallParameterMatcher.hasNameAndStringValue("name", 
"FORM"),
+                MacroCallParameterMatcher.hasNameAndStringValue("title", 
"TITLE"),
+                
MacroCallParameterMatcher.hasNameAndStringValue("showSelectAll", "Y")));
+
+        final RenderableFtlMacroCall withoutSelectAll =
+                (RenderableFtlMacroCall) 
renderableFtlFormElementsBuilder.hyperlinkTitle("FORM", "TITLE", false);
+        assertThat(withoutSelectAll.getParameters(), 
not(hasKey("showSelectAll")));
+    }
+
+    @Test
+    public void fieldTitleSetsIdAndForOnlyWithAnId() {
+        final RenderableFtlMacroCall withId = (RenderableFtlMacroCall) 
renderableFtlFormElementsBuilder.fieldTitle("STYLE", "TITLE", "FIELDID", null);
+        assertThat(withId, 
MacroCallMatcher.hasNameAndParameters("renderFieldTitle",
+                MacroCallParameterMatcher.hasNameAndStringValue("style", 
"STYLE"),
+                MacroCallParameterMatcher.hasNameAndStringValue("title", 
"TITLE"),
+                MacroCallParameterMatcher.hasNameAndStringValue("id", 
"FIELDID_title"),
+                MacroCallParameterMatcher.hasNameAndStringValue("for", 
"FIELDID")));
+        assertThat(withId.getParameters(), not(hasKey("fieldHelpText")));
+
+        final RenderableFtlMacroCall withoutId = (RenderableFtlMacroCall) 
renderableFtlFormElementsBuilder.fieldTitle("STYLE", "TITLE", "", "HELP");
+        assertThat(withoutId.getParameters(), not(hasKey("id")));
+        assertThat(withoutId.getParameters(), not(hasKey("for")));
+        assertThat(withoutId, 
MacroCallMatcher.hasNameAndParameters("renderFieldTitle",
+                
MacroCallParameterMatcher.hasNameAndStringValue("fieldHelpText", "HELP")));
+    }
+
+    @Test
+    public void sortFieldSetsTheLinkAndOmitsAnEmptyTooltip() {
+        final RenderableFtlMacroCall macroCall =
+                (RenderableFtlMacroCall) 
renderableFtlFormElementsBuilder.sortField("STYLE", "TITLE", "/target?x=1", 
true, "");
+        assertThat(macroCall, 
MacroCallMatcher.hasNameAndParameters("renderSortField",
+                MacroCallParameterMatcher.hasNameAndStringValue("style", 
"STYLE"),
+                MacroCallParameterMatcher.hasNameAndStringValue("title", 
"TITLE"),
+                MacroCallParameterMatcher.hasNameAndStringValue("linkUrl", 
"/target?x=1"),
+                
MacroCallParameterMatcher.hasNameAndBooleanValue("ajaxEnabled", true)));
+        assertThat(macroCall.getParameters(), not(hasKey("tooltip")));
+
+        final RenderableFtlMacroCall withTooltip =
+                (RenderableFtlMacroCall) 
renderableFtlFormElementsBuilder.sortField("STYLE", "TITLE", "/target", false, 
"TIP");
+        assertThat(withTooltip, 
MacroCallMatcher.hasNameAndParameters("renderSortField",
+                MacroCallParameterMatcher.hasNameAndStringValue("tooltip", 
"TIP")));
+    }
 }

Reply via email to