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
commit cc6d9d5d05c02f1b7e530121b76e22def767e0da Author: Mridul Pathak <[email protected]> AuthorDate: Fri Oct 16 17:02:48 2026 +0530 Improved: MacroFormRenderer builds the find, lookup, pagination, file, password, image and banner macro calls with RenderableFtlFormElementsBuilder (OFBIZ-13681) MacroFormRenderer built the macro calls of the text find, range find, lookup, file, password and image fields, of the pagination and of the banner as text and compiled them. RenderableFtlFormElementsBuilder now creates these macro calls, so that FtlWriter passes their values as data: the target parameters of the lookup field are a list instead of a hash literal of quoted strings. The macro signatures and the output are unchanged, except that values containing a quote, a backslash or a [...] --- .../widget/renderer/macro/MacroFormRenderer.java | 820 +-------------------- .../macro/RenderableFtlFormElementsBuilder.java | 469 ++++++++++++ .../renderer/macro/MacroFormRendererTest.java | 196 ++--- .../RenderableFtlFormElementsBuilderTest.java | 318 ++++++++ 4 files changed, 853 insertions(+), 950 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 c3f8144f3e..94ee7be6e6 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 @@ -21,28 +21,22 @@ package org.apache.ofbiz.widget.renderer.macro; import java.io.IOException; import java.io.StringWriter; import java.io.UnsupportedEncodingException; -import java.util.HashSet; import java.util.Iterator; -import java.util.LinkedList; import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.Set; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; -import jakarta.servlet.http.HttpSession; import org.apache.ofbiz.base.util.Debug; import org.apache.ofbiz.base.util.UtilCodec; import org.apache.ofbiz.base.util.UtilGenerics; import org.apache.ofbiz.base.util.UtilHttp; -import org.apache.ofbiz.base.util.UtilMisc; import org.apache.ofbiz.base.util.UtilProperties; import org.apache.ofbiz.base.util.UtilValidate; import org.apache.ofbiz.base.util.string.FlexibleStringExpander; import org.apache.ofbiz.entity.Delegator; -import org.apache.ofbiz.security.CsrfUtil; import org.apache.ofbiz.webapp.control.RequestHandler; import org.apache.ofbiz.webapp.taglib.ContentUrlTag; import org.apache.ofbiz.widget.WidgetWorker; @@ -76,9 +70,7 @@ import org.apache.ofbiz.widget.model.ModelScreenWidget; import org.apache.ofbiz.widget.model.ModelSingleForm; import org.apache.ofbiz.widget.model.ModelWidget; import org.apache.ofbiz.widget.model.ThemeFactory; -import org.apache.ofbiz.widget.renderer.FormRenderer; import org.apache.ofbiz.widget.renderer.FormStringRenderer; -import org.apache.ofbiz.widget.renderer.Paginator; import org.apache.ofbiz.widget.renderer.UtilHelpText; import org.apache.ofbiz.widget.renderer.VisualTheme; import org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtl; @@ -209,8 +201,8 @@ public final class MacroFormRenderer implements FormStringRenderer { throws IOException { this.request.setAttribute("image", hyperlinkField.getImageLocation(context)); ModelFormField modelFormField = hyperlinkField.getModelFormField(); - String encodedAlternate = encode(hyperlinkField.getAlternate(context), modelFormField, context); - String encodedImageTitle = encode(hyperlinkField.getImageTitle(context), modelFormField, context); + String encodedAlternate = encodeForMacro(hyperlinkField.getAlternate(context), modelFormField, context); + String encodedImageTitle = encodeForMacro(hyperlinkField.getImageTitle(context), modelFormField, context); this.request.setAttribute("alternate", encodedAlternate); this.request.setAttribute("imageTitle", encodedImageTitle); this.request.setAttribute("descriptionSize", hyperlinkField.getSize()); @@ -617,188 +609,14 @@ public final class MacroFormRenderer implements FormStringRenderer { @Override public void renderTextFindField(Appendable writer, Map<String, Object> context, TextFindField textFindField) throws IOException { - ModelFormField modelFormField = textFindField.getModelFormField(); - String defaultOption = textFindField.getDefaultOption(context); - String conditionGroup = modelFormField.getConditionGroup(); - String className = ""; - String alert = "false"; - String opEquals = ""; - String opBeginsWith = ""; - String opContains = ""; - String opIsEmpty = ""; - String opNotEqual = ""; - String name = modelFormField.getParameterName(context); - String size = Integer.toString(textFindField.getSize()); - String maxlength = ""; - String autocomplete = ""; - if (UtilValidate.isNotEmpty(modelFormField.getWidgetStyle())) { - className = modelFormField.getWidgetStyle(); - if (modelFormField.shouldBeRed(context)) { - alert = "true"; - } - } - Locale locale = (Locale) context.get("locale"); - if (!textFindField.getHideOptions()) { - opEquals = UtilProperties.getMessage("conditionalUiLabels", "equals", locale); - opBeginsWith = UtilProperties.getMessage("conditionalUiLabels", "begins_with", locale); - opContains = UtilProperties.getMessage("conditionalUiLabels", "contains", locale); - opIsEmpty = UtilProperties.getMessage("conditionalUiLabels", "is_empty", locale); - opNotEqual = UtilProperties.getMessage("conditionalUiLabels", "not_equal", locale); - } - String value = modelFormField.getEntry(context, textFindField.getDefaultValue(context)); - if (value == null) { - value = ""; - } - if (textFindField.getMaxlength() != null) { - maxlength = textFindField.getMaxlength().toString(); - } - if (!textFindField.getClientAutocompleteField()) { - autocomplete = "off"; - } - String titleStyle = ""; - if (UtilValidate.isNotEmpty(modelFormField.getTitleStyle())) { - titleStyle = modelFormField.getTitleStyle(); - } - String ignoreCase = UtilProperties.getMessage("conditionalUiLabels", "ignore_case", locale); - boolean ignCase = textFindField.getIgnoreCase(context); - boolean hideIgnoreCase = textFindField.getHideIgnoreCase(); - String tabindex = modelFormField.getTabindex(); - boolean disabled = modelFormField.getDisabled(context); - StringWriter sr = new StringWriter(); - sr.append("<@renderTextFindField "); - sr.append(" name=\""); - sr.append(name); - sr.append("\" value=\""); - sr.append(value); - sr.append("\" defaultOption=\""); - sr.append(defaultOption); - sr.append("\" opEquals=\""); - sr.append(opEquals); - sr.append("\" opBeginsWith=\""); - sr.append(opBeginsWith); - sr.append("\" opContains=\""); - sr.append(opContains); - sr.append("\" opIsEmpty=\""); - sr.append(opIsEmpty); - sr.append("\" opNotEqual=\""); - sr.append(opNotEqual); - sr.append("\" className=\""); - sr.append(className); - sr.append("\" alert=\""); - sr.append(alert); - sr.append("\" size=\""); - sr.append(size); - sr.append("\" maxlength=\""); - sr.append(maxlength); - sr.append("\" autocomplete=\""); - sr.append(autocomplete); - sr.append("\" titleStyle=\""); - sr.append(titleStyle); - sr.append("\" hideIgnoreCase="); - sr.append(Boolean.toString(hideIgnoreCase)); - sr.append(" ignCase="); - sr.append(Boolean.toString(ignCase)); - sr.append(" ignoreCase=\""); - sr.append(ignoreCase); - sr.append("\" tabindex=\""); - sr.append(tabindex); - sr.append("\" conditionGroup=\""); - sr.append(conditionGroup); - sr.append("\" disabled="); - sr.append(Boolean.toString(disabled)); - sr.append(" />"); - executeMacro(writer, sr.toString()); - this.appendTooltip(writer, context, modelFormField); + writeFtlElement(writer, renderableFtlFormElementsBuilder.textFindField(context, textFindField)); + this.appendTooltip(writer, context, textFindField.getModelFormField()); } @Override public void renderRangeFindField(Appendable writer, Map<String, Object> context, RangeFindField rangeFindField) throws IOException { - ModelFormField modelFormField = rangeFindField.getModelFormField(); - Locale locale = (Locale) context.get("locale"); - String opEquals = UtilProperties.getMessage("conditionalUiLabels", "equals", locale); - String opGreaterThan = UtilProperties.getMessage("conditionalUiLabels", "greater_than", locale); - String opGreaterThanEquals = UtilProperties.getMessage("conditionalUiLabels", "greater_than_equals", locale); - String opLessThan = UtilProperties.getMessage("conditionalUiLabels", "less_than", locale); - String opLessThanEquals = UtilProperties.getMessage("conditionalUiLabels", "less_than_equals", locale); - String conditionGroup = modelFormField.getConditionGroup(); - String className = ""; - String alert = "false"; - if (UtilValidate.isNotEmpty(modelFormField.getWidgetStyle())) { - className = modelFormField.getWidgetStyle(); - if (modelFormField.shouldBeRed(context)) { - alert = "true"; - } - } - String name = modelFormField.getParameterName(context); - String size = Integer.toString(rangeFindField.getSize()); - String value = modelFormField.getEntry(context, rangeFindField.getDefaultValue(context)); - if (value == null) { - value = ""; - } - Integer maxlength = rangeFindField.getMaxlength(); - String autocomplete = ""; - - if (!rangeFindField.getClientAutocompleteField()) { - autocomplete = "off"; - } - String titleStyle = modelFormField.getTitleStyle(); - - if (titleStyle == null) { - titleStyle = ""; - } - String defaultOptionFrom = rangeFindField.getDefaultOptionFrom(); - String value2 = modelFormField.getEntry(context); - if (value2 == null) { - value2 = ""; - } - String defaultOptionThru = rangeFindField.getDefaultOptionThru(); - String tabindex = modelFormField.getTabindex(); - boolean disabled = modelFormField.getDisabled(context); - StringWriter sr = new StringWriter(); - sr.append("<@renderRangeFindField "); - sr.append(" className=\""); - sr.append(className); - sr.append("\" alert=\""); - sr.append(alert); - sr.append("\" name=\""); - sr.append(name); - sr.append("\" value=\""); - sr.append(value); - sr.append("\" size=\""); - sr.append(size); - sr.append("\" maxlength=\""); - if (maxlength != null) { - sr.append(Integer.toString(maxlength)); - } - sr.append("\" autocomplete=\""); - sr.append(autocomplete); - sr.append("\" titleStyle=\""); - sr.append(titleStyle); - sr.append("\" defaultOptionFrom=\""); - sr.append(defaultOptionFrom); - sr.append("\" opEquals=\""); - sr.append(opEquals); - sr.append("\" opGreaterThan=\""); - sr.append(opGreaterThan); - sr.append("\" opGreaterThanEquals=\""); - sr.append(opGreaterThanEquals); - sr.append("\" opLessThan=\""); - sr.append(opLessThan); - sr.append("\" opLessThanEquals=\""); - sr.append(opLessThanEquals); - sr.append("\" value2=\""); - sr.append(value2); - sr.append("\" defaultOptionThru=\""); - sr.append(defaultOptionThru); - 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.rangeFindField(context, rangeFindField)); + this.appendTooltip(writer, context, rangeFindField.getModelFormField()); } @Override @@ -819,560 +637,28 @@ public final class MacroFormRenderer implements FormStringRenderer { @Override public void renderLookupField(Appendable writer, Map<String, Object> context, LookupField lookupField) throws IOException { - ModelFormField modelFormField = lookupField.getModelFormField(); - String lookupFieldFormName = lookupField.getFormName(context); - String conditionGroup = modelFormField.getConditionGroup(); - String className = ""; - String alert = "false"; - 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 name = modelFormField.getParameterName(context); - String value = modelFormField.getEntry(context, lookupField.getDefaultValue(context)); - if (value == null) { - value = ""; - } - String size = Integer.toString(lookupField.getSize()); - Integer maxlength = lookupField.getMaxlength(); - String id = modelFormField.getCurrentContainerId(context); - List<ModelForm.UpdateArea> updateAreas = modelFormField.getOnChangeUpdateAreas(); - //add default ajax auto completer to all lookup fields - if (UtilValidate.isEmpty(updateAreas) && UtilValidate.isNotEmpty(lookupFieldFormName)) { - String autoCompleterTarget = null; - if (lookupFieldFormName.indexOf('?') == -1) { - autoCompleterTarget = lookupFieldFormName + "?"; - } else { - autoCompleterTarget = lookupFieldFormName + "&amp;"; - } - autoCompleterTarget = autoCompleterTarget + "ajaxLookup=Y"; - updateAreas = new LinkedList<>(); - updateAreas.add(ModelForm.UpdateArea.literal("change", id, autoCompleterTarget)); - } - boolean ajaxEnabled = UtilValidate.isNotEmpty(updateAreas) && this.javaScriptEnabled; - String autocomplete = ""; - if (!lookupField.getClientAutocompleteField() || !ajaxEnabled) { - autocomplete = "off"; - } - String event = modelFormField.getEvent(); - String action = modelFormField.getAction(context); - boolean readonly = lookupField.getReadonly(); - // add lookup pop-up button - String descriptionFieldName = lookupField.getDescriptionFieldName(); - ModelForm modelForm = modelFormField.getModelForm(); - String formName = modelFormField.getParentFormName(); - if (UtilValidate.isEmpty(formName)) { - formName = FormRenderer.getCurrentFormName(modelForm, context); - } - StringBuilder targetParameterIter = new StringBuilder(); - StringBuilder imgSrc = new StringBuilder(); - // FIXME: refactor using the StringUtils methods - List<String> targetParameterList = lookupField.getTargetParameterList(context); - targetParameterIter.append("["); - for (String targetParameter : targetParameterList) { - if (targetParameterIter.length() > 1) { - targetParameterIter.append(","); - } - targetParameterIter.append("'"); - targetParameterIter.append(targetParameter); - targetParameterIter.append("'"); - } - targetParameterIter.append("]"); - this.appendContentUrl(imgSrc, "/images/fieldlookup.gif"); - String ajaxUrl = ""; - if (ajaxEnabled) { - ajaxUrl = MacroCommonRenderer.createAjaxParamsFromUpdateAreas(updateAreas, null, modelForm, "", context); - } - String lookupPresentation = lookupField.getLookupPresentation(); - if (UtilValidate.isEmpty(lookupPresentation)) { - lookupPresentation = ""; - } - String lookupHeight = lookupField.getLookupHeight(); - String lookupWidth = lookupField.getLookupWidth(); - String lookupPosition = lookupField.getLookupPosition(); - String fadeBackground = lookupField.getFadeBackground(); - if (UtilValidate.isEmpty(fadeBackground)) { - fadeBackground = "false"; - } - Boolean isInitiallyCollapsed = lookupField.getInitiallyCollapsed(); - String clearText = ""; - Map<String, Object> uiLabelMap = UtilGenerics.cast(context.get("uiLabelMap")); - if (uiLabelMap != null) { - clearText = (String) uiLabelMap.get("CommonClear"); - } else { - Debug.logWarning("Could not find uiLabelMap in context", MODULE); - } - Boolean showDescription = lookupField.getShowDescription(); - if (showDescription == null) { - showDescription = "Y".equals(visualTheme.getModelTheme().getLookupShowDescription()); - } - // lastViewName, used by lookup to remember the real last view name; read only from session (set by RequestHandler) to prevent user input - String lastViewName = (String) request.getSession().getAttribute("_LAST_VIEW_NAME_"); - if (UtilValidate.isEmpty(lastViewName)) { - lastViewName = ""; - } - lastViewName = UtilHttp.getEncodedParameter(lastViewName); - String tabindex = modelFormField.getTabindex(); - boolean disabled = modelFormField.getDisabled(context); - StringWriter sr = new StringWriter(); - sr.append("<@renderLookupField "); - sr.append(" className=\""); - sr.append(className); - sr.append("\" alert=\""); - sr.append(alert); - sr.append("\" name=\""); - sr.append(name); - sr.append("\" value=\""); - sr.append(value); - sr.append("\" size=\""); - sr.append(size); - sr.append("\" maxlength=\""); - sr.append((maxlength != null ? Integer.toString(maxlength) : "")); - 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("\" readonly="); - sr.append(Boolean.toString(readonly)); - sr.append(" autocomplete=\""); - sr.append(autocomplete); - sr.append("\" descriptionFieldName=\""); - sr.append(descriptionFieldName); - sr.append("\" formName=\""); - sr.append(formName); - sr.append("\" fieldFormName=\""); - sr.append(lookupFieldFormName); - sr.append("\" targetParameterIter="); - sr.append(targetParameterIter.toString()); - sr.append(" imgSrc=\""); - sr.append(imgSrc.toString()); - sr.append("\" ajaxUrl=\""); - sr.append(ajaxUrl); - sr.append("\" ajaxEnabled="); - sr.append(Boolean.toString(ajaxEnabled)); - sr.append(" presentation=\""); - sr.append(lookupPresentation); - if (UtilValidate.isNotEmpty(lookupHeight)) { - sr.append("\" height=\""); - sr.append(lookupHeight); - } - if (UtilValidate.isNotEmpty(lookupWidth)) { - sr.append("\" width=\""); - sr.append(lookupWidth); - } - if (UtilValidate.isNotEmpty(lookupPosition)) { - sr.append("\" position=\""); - sr.append(lookupPosition); - } - sr.append("\" fadeBackground=\""); - sr.append(fadeBackground); - sr.append("\" clearText=\""); - sr.append(clearText); - sr.append("\" showDescription=\""); - sr.append(Boolean.toString(showDescription)); - sr.append("\" initiallyCollapsed=\""); - sr.append(Boolean.toString(isInitiallyCollapsed)); - sr.append("\" lastViewName=\""); - sr.append(lastViewName); - sr.append("\" conditionGroup=\""); - sr.append(conditionGroup); - sr.append("\" tabindex=\""); - sr.append(tabindex); - sr.append("\" disabled="); - sr.append(Boolean.toString(disabled)); - sr.append(" delegatorName=\""); - sr.append(((HttpSession) context.get("session")).getAttribute("delegatorName").toString()); - sr.append("\" />"); - executeMacro(writer, sr.toString()); + final ModelFormField modelFormField = lookupField.getModelFormField(); + writeFtlElement(writer, renderableFtlFormElementsBuilder.lookupField(context, lookupField, this.javaScriptEnabled)); this.addAsterisks(writer, context, modelFormField); this.makeHyperlinkString(writer, lookupField.getSubHyperlink(), context); this.appendTooltip(writer, context, modelFormField); } public void renderNextPrev(Appendable writer, Map<String, Object> context, ModelForm modelForm) { - boolean ajaxEnabled = false; - List<ModelForm.UpdateArea> updateAreas = modelForm.getOnPaginateUpdateAreas(); - String targetService = modelForm.getPaginateTarget(context); - if (this.javaScriptEnabled) { - if (UtilValidate.isNotEmpty(updateAreas)) { - ajaxEnabled = true; - } - } - if (targetService == null) { - targetService = "${targetService}"; - } - if (UtilValidate.isEmpty(targetService) && updateAreas == null) { - Debug.logWarning("Cannot paginate because TargetService is empty for the form: " + modelForm.getName(), MODULE); - return; - } - // get the parameterized pagination index and size fields - int paginatorNumber = WidgetWorker.getPaginatorNumber(context); - String viewIndexParam = modelForm.getMultiPaginateIndexField(context); - String viewSizeParam = modelForm.getMultiPaginateSizeField(context); - int viewIndex = Paginator.getViewIndex(modelForm, context); - int viewSize = Paginator.getViewSize(modelForm, context); - int listSize = Paginator.getListSize(context); - int lowIndex = Paginator.getLowIndex(context); - int highIndex = Paginator.getHighIndex(context); - int actualPageSize = Paginator.getActualPageSize(context); - // needed for the "Page" and "rows" labels - Map<String, String> uiLabelMap = UtilGenerics.cast(context.get("uiLabelMap")); - String pageLabel = ""; - String commonDisplaying = ""; - if (uiLabelMap == null) { - Debug.logWarning("Could not find uiLabelMap in context", MODULE); - } else { - pageLabel = uiLabelMap.get("CommonPage"); - Map<String, Integer> messageMap = UtilMisc.toMap("lowCount", lowIndex + 1, "highCount", lowIndex + actualPageSize, "total", listSize); - commonDisplaying = UtilProperties.getMessage("CommonUiLabels", "CommonDisplaying", messageMap, (Locale) context.get("locale")); - } - // for legacy support, the viewSizeParam is VIEW_SIZE and viewIndexParam is VIEW_INDEX when the fields are "viewSize" and "viewIndex" - if (("viewIndex" + "_" + paginatorNumber).equals(viewIndexParam)) { - viewIndexParam = "VIEW_INDEX" + "_" + paginatorNumber; - } - if (("viewSize" + "_" + paginatorNumber).equals(viewSizeParam)) { - viewSizeParam = "VIEW_SIZE" + "_" + paginatorNumber; - } - String str = (String) context.get("_QBESTRING_"); - - // refresh any csrf token in the query string for pagination - String tokenValue = CsrfUtil.generateTokenForNonAjax(request, targetService); - str = CsrfUtil.addOrUpdateTokenInQueryString(str, tokenValue); - - // strip legacy viewIndex/viewSize params from the query string - String queryString = UtilHttp.stripViewParamsFromQueryString(str, "" + paginatorNumber); - // strip parameterized index/size params from the query string - Set<String> paramNames = new HashSet<>(); - paramNames.add(viewIndexParam); - paramNames.add(viewSizeParam); - queryString = UtilHttp.stripNamedParamsFromQueryString(queryString, paramNames); - String anchor = ""; - String paginateAnchor = modelForm.getPaginateTargetAnchor(); - if (UtilValidate.isNotEmpty(paginateAnchor)) { - anchor = "#" + paginateAnchor; - } - // Create separate url path String and request parameters String, - // add viewIndex/viewSize parameters to request parameter String - String urlPath = UtilHttp.removeQueryStringFromTarget(targetService); - String prepLinkText = UtilHttp.getQueryStringFromTarget(targetService); - String prepLinkSizeText; - if (UtilValidate.isNotEmpty(queryString)) { - queryString = UtilHttp.encodeAmpersands(queryString); - } - if (prepLinkText.indexOf('?') < 0) { - prepLinkText += "?"; - } else if (!prepLinkText.endsWith("?")) { - prepLinkText += "&"; - } - if (UtilValidate.isNotEmpty(queryString) && !"null".equals(queryString)) { - prepLinkText += queryString + "&"; - } - prepLinkSizeText = prepLinkText + viewSizeParam + "='+this.value+'" + "&" + viewIndexParam + "=0"; - prepLinkText += viewSizeParam + "=" + viewSize + "&" + viewIndexParam + "="; - if (ajaxEnabled) { - // Prepare params for prototype.js - prepLinkText = prepLinkText.replace("?", ""); - prepLinkText = prepLinkText.replace("&", "&"); - } - String linkText; - String paginateStyle = modelForm.getPaginateStyle(); - String paginateFirstStyle = modelForm.getPaginateFirstStyle(); - String paginateFirstLabel = modelForm.getPaginateFirstLabel(context); - String firstUrl = ""; - String ajaxFirstUrl = ""; - String paginatePreviousStyle = modelForm.getPaginatePreviousStyle(); - String paginatePreviousLabel = modelForm.getPaginatePreviousLabel(context); - String previousUrl = ""; - String ajaxPreviousUrl = ""; - String selectUrl = ""; - String ajaxSelectUrl = ""; - String paginateViewSizeLabel = modelForm.getPaginateViewSizeLabel(context); - String selectSizeUrl = ""; - String ajaxSelectSizeUrl = ""; - String paginateNextStyle = modelForm.getPaginateNextStyle(); - String paginateNextLabel = modelForm.getPaginateNextLabel(context); - String nextUrl = ""; - String ajaxNextUrl = ""; - String paginateLastStyle = modelForm.getPaginateLastStyle(); - String paginateLastLabel = modelForm.getPaginateLastLabel(context); - String lastUrl = ""; - String ajaxLastUrl = ""; - if (viewIndex > 0) { - if (ajaxEnabled) { - ajaxFirstUrl = MacroCommonRenderer.createAjaxParamsFromUpdateAreas(updateAreas, null, modelForm, prepLinkText + 0 + anchor, context); - } else { - linkText = prepLinkText + 0 + anchor; - firstUrl = rh.makeLink(this.request, this.response, urlPath + linkText); - } - } - if (viewIndex > 0) { - if (ajaxEnabled) { - ajaxPreviousUrl = MacroCommonRenderer.createAjaxParamsFromUpdateAreas(updateAreas, null, modelForm, - prepLinkText + (viewIndex - 1) + anchor, context); - } else { - linkText = prepLinkText + (viewIndex - 1) + anchor; - previousUrl = rh.makeLink(this.request, this.response, urlPath + linkText); - } - } - // Page select dropdown - if (listSize > 0 && this.javaScriptEnabled) { - if (ajaxEnabled) { - ajaxSelectUrl = MacroCommonRenderer.createAjaxParamsFromUpdateAreas(updateAreas, null, modelForm, - prepLinkText + "' + (this.value - 1) + '", context); - } else { - linkText = prepLinkText; - if (linkText.startsWith("/")) { - linkText = linkText.substring(1); - } - selectUrl = rh.makeLink(this.request, this.response, urlPath + linkText); - } - } - // Next button - if (highIndex < listSize) { - if (ajaxEnabled) { - ajaxNextUrl = MacroCommonRenderer.createAjaxParamsFromUpdateAreas(updateAreas, null, - modelForm, prepLinkText + (viewIndex + 1) + anchor, context); - } else { - linkText = prepLinkText + (viewIndex + 1) + anchor; - nextUrl = rh.makeLink(this.request, this.response, urlPath + linkText); - } - } - // Last button - if (highIndex < listSize) { - int lastIndex = UtilMisc.getViewLastIndex(listSize, viewSize); - if (ajaxEnabled) { - ajaxLastUrl = MacroCommonRenderer.createAjaxParamsFromUpdateAreas(updateAreas, null, - modelForm, prepLinkText + lastIndex + anchor, context); - } else { - linkText = prepLinkText + lastIndex + anchor; - lastUrl = rh.makeLink(this.request, this.response, urlPath + linkText); - } - } - // Page size select dropdown - if (listSize > 0 && this.javaScriptEnabled) { - if (ajaxEnabled) { - ajaxSelectSizeUrl = MacroCommonRenderer.createAjaxParamsFromUpdateAreas(updateAreas, null, - modelForm, prepLinkSizeText + anchor, context); - } else { - linkText = prepLinkSizeText; - if (linkText.startsWith("/")) { - linkText = linkText.substring(1); - } - selectSizeUrl = rh.makeLink(this.request, this.response, urlPath + linkText); - } - } - StringWriter sr = new StringWriter(); - sr.append("<@renderNextPrev "); - sr.append(" paginateStyle=\""); - sr.append(paginateStyle); - sr.append("\" paginateFirstStyle=\""); - sr.append(paginateFirstStyle); - sr.append("\" viewIndex="); - sr.append(Integer.toString(viewIndex)); - sr.append(" highIndex="); - sr.append(Integer.toString(highIndex)); - sr.append(" listSize="); - sr.append(Integer.toString(listSize)); - sr.append(" viewSize="); - sr.append(Integer.toString(viewSize)); - sr.append(" ajaxEnabled="); - sr.append(Boolean.toString(ajaxEnabled)); - sr.append(" javaScriptEnabled="); - sr.append(Boolean.toString(javaScriptEnabled)); - sr.append(" ajaxFirstUrl=\""); - sr.append(ajaxFirstUrl); - sr.append("\" ajaxFirstUrl=\""); - sr.append(ajaxFirstUrl); - sr.append("\" ajaxFirstUrl=\""); - sr.append(ajaxFirstUrl); - sr.append("\" firstUrl=\""); - sr.append(firstUrl); - sr.append("\" paginateFirstLabel=\""); - sr.append(paginateFirstLabel); - sr.append("\" paginatePreviousStyle=\""); - sr.append(paginatePreviousStyle); - sr.append("\" ajaxPreviousUrl=\""); - sr.append(ajaxPreviousUrl); - sr.append("\" previousUrl=\""); - sr.append(previousUrl); - sr.append("\" paginatePreviousLabel=\""); - sr.append(paginatePreviousLabel); - sr.append("\" pageLabel=\""); - sr.append(pageLabel); - sr.append("\" ajaxSelectUrl=\""); - sr.append(ajaxSelectUrl); - sr.append("\" selectUrl=\""); - sr.append(selectUrl); - sr.append("\" ajaxSelectSizeUrl=\""); - sr.append(ajaxSelectSizeUrl); - sr.append("\" selectSizeUrl=\""); - sr.append(selectSizeUrl); - sr.append("\" commonDisplaying=\""); - sr.append(commonDisplaying); - sr.append("\" paginateNextStyle=\""); - sr.append(paginateNextStyle); - sr.append("\" ajaxNextUrl=\""); - sr.append(ajaxNextUrl); - sr.append("\" nextUrl=\""); - sr.append(nextUrl); - sr.append("\" paginateNextLabel=\""); - sr.append(paginateNextLabel); - sr.append("\" paginateLastStyle=\""); - sr.append(paginateLastStyle); - sr.append("\" ajaxLastUrl=\""); - sr.append(ajaxLastUrl); - sr.append("\" lastUrl=\""); - sr.append(lastUrl); - sr.append("\" paginateLastLabel=\""); - sr.append(paginateLastLabel); - sr.append("\" paginateViewSizeLabel=\""); - sr.append(paginateViewSizeLabel); - sr.append("\" />"); - executeMacro(writer, sr.toString()); + writeFtlElement(writer, renderableFtlFormElementsBuilder.nextPrev(context, modelForm, this.javaScriptEnabled)); } @Override public void renderFileField(Appendable writer, Map<String, Object> context, FileField textField) throws IOException { - ModelFormField modelFormField = textField.getModelFormField(); - String className = ""; - String alert = "false"; - String name = modelFormField.getParameterName(context); - String value = modelFormField.getEntry(context, textField.getDefaultValue(context)); - String size = Integer.toString(textField.getSize()); - String maxlength = ""; - String autocomplete = ""; - if (UtilValidate.isNotEmpty(modelFormField.getWidgetStyle())) { - className = modelFormField.getWidgetStyle(); - if (modelFormField.shouldBeRed(context)) { - alert = "true"; - } - } - if (UtilValidate.isEmpty(value)) { - value = ""; - } - if (textField.getMaxlength() != null) { - maxlength = textField.getMaxlength().toString(); - } - if (!textField.getClientAutocompleteField()) { - autocomplete = "off"; - } - String tabindex = modelFormField.getTabindex(); - boolean disabled = modelFormField.getDisabled(context); - StringWriter sr = new StringWriter(); - sr.append("<@renderFileField "); - sr.append(" className=\""); - sr.append(className); - sr.append("\" alert=\""); - sr.append(alert); - sr.append("\" name=\""); - sr.append(name); - sr.append("\" value=\""); - sr.append(value); - sr.append("\" size=\""); - sr.append(size); - sr.append("\" maxlength=\""); - sr.append(maxlength); - sr.append("\" autocomplete=\""); - sr.append(autocomplete); - sr.append("\" tabindex=\""); - sr.append(tabindex); - sr.append("\" disabled="); - sr.append(Boolean.toString(disabled)); - sr.append(" />"); - executeMacro(writer, sr.toString()); + writeFtlElement(writer, renderableFtlFormElementsBuilder.fileField(context, textField)); this.makeHyperlinkString(writer, textField.getSubHyperlink(), context); - this.appendTooltip(writer, context, modelFormField); + this.appendTooltip(writer, context, textField.getModelFormField()); } @Override public void renderPasswordField(Appendable writer, Map<String, Object> context, PasswordField passwordField) throws IOException { - ModelFormField modelFormField = passwordField.getModelFormField(); - String className = ""; - String alert = "false"; - String name = modelFormField.getParameterName(context); - String size = Integer.toString(passwordField.getSize()); - String maxlength = ""; - String id = modelFormField.getCurrentContainerId(context); - boolean disabled = modelFormField.getDisabled(context); - String autocomplete = ""; - if (UtilValidate.isNotEmpty(modelFormField.getWidgetStyle())) { - className = modelFormField.getWidgetStyle(); - if (modelFormField.shouldBeRed(context)) { - alert = "true"; - } - } - String value = modelFormField.getEntry(context, passwordField.getDefaultValue(context)); - if (value == null) { - value = ""; - } - if (passwordField.getMaxlength() != null) { - maxlength = passwordField.getMaxlength().toString(); - } - if (id == null) { - id = ""; - } - if (!passwordField.getClientAutocompleteField()) { - autocomplete = "off"; - } - - //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 tabindex = modelFormField.getTabindex(); - StringWriter sr = new StringWriter(); - sr.append("<@renderPasswordField "); - sr.append(" className=\""); - sr.append(className); - sr.append("\" alert=\""); - sr.append(alert); - sr.append("\" name=\""); - sr.append(name); - sr.append("\" value=\""); - sr.append(value); - sr.append("\" size=\""); - sr.append(size); - sr.append("\" maxlength=\""); - sr.append(maxlength); - sr.append("\" id=\""); - sr.append(id); - sr.append("\" autocomplete=\""); - sr.append(autocomplete); - sr.append("\" tabindex=\""); - sr.append(tabindex); - sr.append("\" disabled="); - sr.append(Boolean.toString(disabled)); - sr.append(" />"); - executeMacro(writer, sr.toString()); + final ModelFormField modelFormField = passwordField.getModelFormField(); + writeFtlElement(writer, renderableFtlFormElementsBuilder.passwordField(context, passwordField)); this.addAsterisks(writer, context, modelFormField); this.makeHyperlinkString(writer, passwordField.getSubHyperlink(), context); this.appendTooltip(writer, context, modelFormField); @@ -1380,47 +666,9 @@ public final class MacroFormRenderer implements FormStringRenderer { @Override public void renderImageField(Appendable writer, Map<String, Object> context, ImageField imageField) throws IOException { - ModelFormField modelFormField = imageField.getModelFormField(); - String value = modelFormField.getEntry(context, imageField.getValue(context)); - String description = imageField.getDescription(context); - String alternate = imageField.getAlternate(context); - String style = imageField.getStyle(context); - if (UtilValidate.isEmpty(description)) { - description = imageField.getModelFormField().getTitle(context); - } - if (UtilValidate.isEmpty(alternate)) { - alternate = description; - } - if (UtilValidate.isNotEmpty(value)) { - if (!value.startsWith("http")) { - StringBuilder buffer = new StringBuilder(); - ContentUrlTag.appendContentPrefix(request, buffer); - buffer.append(value); - value = buffer.toString(); - } - } else if (value == null) { - value = ""; - } - String event = modelFormField.getEvent(); - String action = modelFormField.getAction(context); - StringWriter sr = new StringWriter(); - sr.append("<@renderImageField "); - sr.append(" value=\""); - sr.append(value); - sr.append("\" description=\""); - sr.append(encode(description, modelFormField, context)); - sr.append("\" alternate=\""); - sr.append(encode(alternate, modelFormField, context)); - sr.append("\" style=\""); - sr.append(style); - sr.append("\" event=\""); - sr.append(event == null ? "" : event); - sr.append("\" action=\""); - sr.append(action == null ? "" : action); - sr.append("\" />"); - executeMacro(writer, sr.toString()); + writeFtlElement(writer, renderableFtlFormElementsBuilder.imageField(context, imageField)); this.makeHyperlinkString(writer, imageField.getSubHyperlink(), context); - this.appendTooltip(writer, context, modelFormField); + this.appendTooltip(writer, context, imageField.getModelFormField()); } @Override @@ -1437,43 +685,7 @@ public final class MacroFormRenderer implements FormStringRenderer { @Override public void renderBanner(Appendable writer, Map<String, Object> context, ModelForm.Banner banner) throws IOException { - String style = banner.getStyle(context); - String leftStyle = banner.getLeftTextStyle(context); - if (UtilValidate.isEmpty(leftStyle)) { - leftStyle = style; - } - String rightStyle = banner.getRightTextStyle(context); - if (UtilValidate.isEmpty(rightStyle)) { - rightStyle = style; - } - String leftText = banner.getLeftText(context); - if (leftText == null) { - leftText = ""; - } - String text = banner.getText(context); - if (text == null) { - text = ""; - } - String rightText = banner.getRightText(context); - if (rightText == null) { - rightText = ""; - } - StringWriter sr = new StringWriter(); - sr.append("<@renderBanner "); - sr.append(" style=\""); - sr.append(style); - sr.append("\" leftStyle=\""); - sr.append(leftStyle); - sr.append("\" rightStyle=\""); - sr.append(rightStyle); - sr.append("\" leftText=\""); - sr.append(leftText); - sr.append("\" text=\""); - sr.append(text); - sr.append("\" rightText=\""); - sr.append(rightText); - sr.append("\" />"); - executeMacro(writer, sr.toString()); + writeFtlElement(writer, renderableFtlFormElementsBuilder.banner(context, banner)); } /** 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 539ad1d401..b83a0766e0 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 @@ -19,10 +19,12 @@ package org.apache.ofbiz.widget.renderer.macro; import java.io.StringWriter; +import java.io.UnsupportedEncodingException; import java.net.URI; import java.sql.Timestamp; import java.util.ArrayList; import java.util.Collections; +import java.util.HashSet; import java.util.Iterator; import java.util.List; import java.util.Locale; @@ -1876,6 +1878,473 @@ public final class RenderableFtlFormElementsBuilder { .build(); } + /** + * Creates the macro call that renders a text find field. + * @param context the context + * @param textFindField the text find field + * @return the RenderableFtl + */ + public RenderableFtl textFindField(final Map<String, Object> context, final ModelFormField.TextFindField textFindField) { + final ModelFormField modelFormField = textFindField.getModelFormField(); + final Locale locale = (Locale) context.get("locale"); + String opEquals = ""; + String opBeginsWith = ""; + String opContains = ""; + String opIsEmpty = ""; + String opNotEqual = ""; + if (!textFindField.getHideOptions()) { + opEquals = UtilProperties.getMessage("conditionalUiLabels", "equals", locale); + opBeginsWith = UtilProperties.getMessage("conditionalUiLabels", "begins_with", locale); + opContains = UtilProperties.getMessage("conditionalUiLabels", "contains", locale); + opIsEmpty = UtilProperties.getMessage("conditionalUiLabels", "is_empty", locale); + opNotEqual = UtilProperties.getMessage("conditionalUiLabels", "not_equal", locale); + } + final String value = modelFormField.getEntry(context, textFindField.getDefaultValue(context)); + final var builder = RenderableFtlMacroCall.builder().name("renderTextFindField"); + applyWidgetStyleAndAlert(modelFormField, context, builder); + return builder + .stringParameter("name", nonNull(modelFormField.getParameterName(context))) + .stringParameter("value", nonNull(value)) + .stringParameter("defaultOption", nonNull(textFindField.getDefaultOption(context))) + .stringParameter("opEquals", nonNull(opEquals)) + .stringParameter("opBeginsWith", nonNull(opBeginsWith)) + .stringParameter("opContains", nonNull(opContains)) + .stringParameter("opIsEmpty", nonNull(opIsEmpty)) + .stringParameter("opNotEqual", nonNull(opNotEqual)) + .stringParameter("size", Integer.toString(textFindField.getSize())) + .stringParameter("maxlength", textFindField.getMaxlength() == null ? "" : textFindField.getMaxlength().toString()) + .stringParameter("autocomplete", textFindField.getClientAutocompleteField() ? "" : "off") + .stringParameter("titleStyle", nonNull(modelFormField.getTitleStyle())) + .booleanParameter("hideIgnoreCase", textFindField.getHideIgnoreCase()) + .booleanParameter("ignCase", textFindField.getIgnoreCase(context)) + .stringParameter("ignoreCase", nonNull(UtilProperties.getMessage("conditionalUiLabels", "ignore_case", locale))) + .stringParameter("tabindex", nonNull(modelFormField.getTabindex())) + .stringParameter("conditionGroup", nonNull(modelFormField.getConditionGroup())) + .booleanParameter("disabled", modelFormField.getDisabled(context)) + .build(); + } + + /** + * Creates the macro call that renders a range find field. + * @param context the context + * @param rangeFindField the range find field + * @return the RenderableFtl + */ + public RenderableFtl rangeFindField(final Map<String, Object> context, final ModelFormField.RangeFindField rangeFindField) { + final ModelFormField modelFormField = rangeFindField.getModelFormField(); + final Locale locale = (Locale) context.get("locale"); + final String value = modelFormField.getEntry(context, rangeFindField.getDefaultValue(context)); + final String value2 = modelFormField.getEntry(context); + final Integer maxlength = rangeFindField.getMaxlength(); + final var builder = RenderableFtlMacroCall.builder().name("renderRangeFindField"); + applyWidgetStyleAndAlert(modelFormField, context, builder); + return builder + .stringParameter("name", nonNull(modelFormField.getParameterName(context))) + .stringParameter("value", nonNull(value)) + .stringParameter("size", Integer.toString(rangeFindField.getSize())) + .stringParameter("maxlength", maxlength == null ? "" : Integer.toString(maxlength)) + .stringParameter("autocomplete", rangeFindField.getClientAutocompleteField() ? "" : "off") + .stringParameter("titleStyle", nonNull(modelFormField.getTitleStyle())) + .stringParameter("defaultOptionFrom", nonNull(rangeFindField.getDefaultOptionFrom())) + .stringParameter("opEquals", nonNull(UtilProperties.getMessage("conditionalUiLabels", "equals", locale))) + .stringParameter("opGreaterThan", nonNull(UtilProperties.getMessage("conditionalUiLabels", "greater_than", locale))) + .stringParameter("opGreaterThanEquals", + nonNull(UtilProperties.getMessage("conditionalUiLabels", "greater_than_equals", locale))) + .stringParameter("opLessThan", nonNull(UtilProperties.getMessage("conditionalUiLabels", "less_than", locale))) + .stringParameter("opLessThanEquals", nonNull(UtilProperties.getMessage("conditionalUiLabels", "less_than_equals", locale))) + .stringParameter("value2", nonNull(value2)) + .stringParameter("defaultOptionThru", nonNull(rangeFindField.getDefaultOptionThru())) + .stringParameter("conditionGroup", nonNull(modelFormField.getConditionGroup())) + .stringParameter("tabindex", nonNull(modelFormField.getTabindex())) + .booleanParameter("disabled", modelFormField.getDisabled(context)) + .build(); + } + + /** + * Creates the macro call that renders a lookup field. + * @param context the context + * @param lookupField the lookup field + * @param javaScriptEnabled whether JavaScript is enabled in the browser of the user + * @return the RenderableFtl + * @throws UnsupportedEncodingException if the last view name cannot be encoded + */ + public RenderableFtl lookupField(final Map<String, Object> context, final ModelFormField.LookupField lookupField, + final boolean javaScriptEnabled) throws UnsupportedEncodingException { + final ModelFormField modelFormField = lookupField.getModelFormField(); + final String lookupFieldFormName = lookupField.getFormName(context); + final String id = modelFormField.getCurrentContainerId(context); + List<ModelForm.UpdateArea> updateAreas = modelFormField.getOnChangeUpdateAreas(); + //add default ajax auto completer to all lookup fields + if (UtilValidate.isEmpty(updateAreas) && UtilValidate.isNotEmpty(lookupFieldFormName)) { + final String autoCompleterTarget = (lookupFieldFormName.indexOf('?') == -1 ? lookupFieldFormName + "?" + : lookupFieldFormName + "&amp;") + "ajaxLookup=Y"; + updateAreas = new ArrayList<>(); + updateAreas.add(ModelForm.UpdateArea.literal("change", id, autoCompleterTarget)); + } + final boolean ajaxEnabled = UtilValidate.isNotEmpty(updateAreas) && javaScriptEnabled; + final ModelForm modelForm = modelFormField.getModelForm(); + String formName = modelFormField.getParentFormName(); + if (UtilValidate.isEmpty(formName)) { + formName = FormRenderer.getCurrentFormName(modelForm, context); + } + final String ajaxUrl = ajaxEnabled ? MacroCommonRenderer.createAjaxParamsFromUpdateAreas(updateAreas, null, modelForm, "", context) : ""; + String clearText = ""; + final Map<String, Object> uiLabelMap = UtilGenerics.cast(context.get("uiLabelMap")); + if (uiLabelMap != null) { + clearText = (String) uiLabelMap.get("CommonClear"); + } else { + Debug.logWarning("Could not find uiLabelMap in context", MODULE); + } + Boolean showDescription = lookupField.getShowDescription(); + if (showDescription == null) { + showDescription = "Y".equals(visualTheme.getModelTheme().getLookupShowDescription()); + } + // lastViewName, used by lookup to remember the real last view name; read only from session (set by RequestHandler) to prevent user input + String lastViewName = (String) request.getSession().getAttribute("_LAST_VIEW_NAME_"); + if (UtilValidate.isEmpty(lastViewName)) { + lastViewName = ""; + } + lastViewName = UtilHttp.getEncodedParameter(lastViewName); + + String className = nonNull(modelFormField.getWidgetStyle()); + final String alert = UtilValidate.isNotEmpty(className) && modelFormField.shouldBeRed(context) ? "true" : "false"; + className = withRequiredStyle(modelFormField, className); + final Integer maxlength = lookupField.getMaxlength(); + final String fadeBackground = lookupField.getFadeBackground(); + final var builder = RenderableFtlMacroCall.builder() + .name("renderLookupField") + .stringParameter("className", className) + .stringParameter("alert", alert) + .stringParameter("name", nonNull(modelFormField.getParameterName(context))) + .stringParameter("value", nonNull(modelFormField.getEntry(context, lookupField.getDefaultValue(context)))) + .stringParameter("size", Integer.toString(lookupField.getSize())) + .stringParameter("maxlength", maxlength == null ? "" : Integer.toString(maxlength)) + .stringParameter("id", nonNull(id)) + .stringParameter("event", nonNull(modelFormField.getEvent())) + .stringParameter("action", nonNull(modelFormField.getAction(context))) + .booleanParameter("readonly", lookupField.getReadonly()) + .stringParameter("autocomplete", !lookupField.getClientAutocompleteField() || !ajaxEnabled ? "off" : "") + .stringParameter("descriptionFieldName", nonNull(lookupField.getDescriptionFieldName())) + .stringParameter("formName", nonNull(formName)) + .stringParameter("fieldFormName", nonNull(lookupFieldFormName)) + .objectParameter("targetParameterIter", new ArrayList<>(lookupField.getTargetParameterList(context))) + .stringParameter("imgSrc", pathAsContentUrl("/images/fieldlookup.gif")) + .stringParameter("ajaxUrl", ajaxUrl) + .booleanParameter("ajaxEnabled", ajaxEnabled) + .stringParameter("presentation", nonNull(lookupField.getLookupPresentation())); + if (UtilValidate.isNotEmpty(lookupField.getLookupHeight())) { + builder.stringParameter("height", lookupField.getLookupHeight()); + } + if (UtilValidate.isNotEmpty(lookupField.getLookupWidth())) { + builder.stringParameter("width", lookupField.getLookupWidth()); + } + if (UtilValidate.isNotEmpty(lookupField.getLookupPosition())) { + builder.stringParameter("position", lookupField.getLookupPosition()); + } + return builder + .stringParameter("fadeBackground", UtilValidate.isEmpty(fadeBackground) ? "false" : fadeBackground) + .stringParameter("clearText", nonNull(clearText)) + .stringParameter("showDescription", Boolean.toString(showDescription)) + .stringParameter("initiallyCollapsed", Boolean.toString(lookupField.getInitiallyCollapsed())) + .stringParameter("lastViewName", lastViewName) + .stringParameter("conditionGroup", nonNull(modelFormField.getConditionGroup())) + .stringParameter("tabindex", nonNull(modelFormField.getTabindex())) + .booleanParameter("disabled", modelFormField.getDisabled(context)) + .stringParameter("delegatorName", ((HttpSession) context.get("session")).getAttribute("delegatorName").toString()) + .build(); + } + + /** + * Creates the macro call that renders the pagination of a list form. + * @param context the context + * @param modelForm the form + * @param javaScriptEnabled whether JavaScript is enabled in the browser of the user + * @return the RenderableFtl, which renders nothing if the form cannot paginate + */ + public RenderableFtl nextPrev(final Map<String, Object> context, final ModelForm modelForm, final boolean javaScriptEnabled) { + boolean ajaxEnabled = false; + final List<ModelForm.UpdateArea> updateAreas = modelForm.getOnPaginateUpdateAreas(); + String targetService = modelForm.getPaginateTarget(context); + if (javaScriptEnabled && UtilValidate.isNotEmpty(updateAreas)) { + ajaxEnabled = true; + } + if (targetService == null) { + targetService = "${targetService}"; + } + if (UtilValidate.isEmpty(targetService) && updateAreas == null) { + Debug.logWarning("Cannot paginate because TargetService is empty for the form: " + modelForm.getName(), MODULE); + return RenderableFtlNoop.INSTANCE; + } + // get the parameterized pagination index and size fields + final int paginatorNumber = WidgetWorker.getPaginatorNumber(context); + String viewIndexParam = modelForm.getMultiPaginateIndexField(context); + String viewSizeParam = modelForm.getMultiPaginateSizeField(context); + final int viewIndex = Paginator.getViewIndex(modelForm, context); + final int viewSize = Paginator.getViewSize(modelForm, context); + final int listSize = Paginator.getListSize(context); + final int lowIndex = Paginator.getLowIndex(context); + final int highIndex = Paginator.getHighIndex(context); + final int actualPageSize = Paginator.getActualPageSize(context); + // needed for the "Page" and "rows" labels + final Map<String, String> uiLabelMap = UtilGenerics.cast(context.get("uiLabelMap")); + String pageLabel = ""; + String commonDisplaying = ""; + if (uiLabelMap == null) { + Debug.logWarning("Could not find uiLabelMap in context", MODULE); + } else { + pageLabel = uiLabelMap.get("CommonPage"); + final Map<String, Integer> messageMap = UtilMisc.toMap("lowCount", lowIndex + 1, "highCount", lowIndex + actualPageSize, + "total", listSize); + commonDisplaying = UtilProperties.getMessage("CommonUiLabels", "CommonDisplaying", messageMap, (Locale) context.get("locale")); + } + // for legacy support, the viewSizeParam is VIEW_SIZE and viewIndexParam is VIEW_INDEX when the fields are "viewSize" and "viewIndex" + if (("viewIndex" + "_" + paginatorNumber).equals(viewIndexParam)) { + viewIndexParam = "VIEW_INDEX" + "_" + paginatorNumber; + } + if (("viewSize" + "_" + paginatorNumber).equals(viewSizeParam)) { + viewSizeParam = "VIEW_SIZE" + "_" + paginatorNumber; + } + String str = (String) context.get("_QBESTRING_"); + + // refresh any csrf token in the query string for pagination + final String tokenValue = CsrfUtil.generateTokenForNonAjax(request, targetService); + str = CsrfUtil.addOrUpdateTokenInQueryString(str, tokenValue); + + // strip legacy viewIndex/viewSize params from the query string + String queryString = UtilHttp.stripViewParamsFromQueryString(str, "" + paginatorNumber); + // strip parameterized index/size params from the query string + final Set<String> paramNames = new HashSet<>(); + paramNames.add(viewIndexParam); + paramNames.add(viewSizeParam); + queryString = UtilHttp.stripNamedParamsFromQueryString(queryString, paramNames); + String anchor = ""; + final String paginateAnchor = modelForm.getPaginateTargetAnchor(); + if (UtilValidate.isNotEmpty(paginateAnchor)) { + anchor = "#" + paginateAnchor; + } + // Create separate url path String and request parameters String, + // add viewIndex/viewSize parameters to request parameter String + final String urlPath = UtilHttp.removeQueryStringFromTarget(targetService); + String prepLinkText = UtilHttp.getQueryStringFromTarget(targetService); + if (UtilValidate.isNotEmpty(queryString)) { + queryString = UtilHttp.encodeAmpersands(queryString); + } + if (prepLinkText.indexOf('?') < 0) { + prepLinkText += "?"; + } else if (!prepLinkText.endsWith("?")) { + prepLinkText += "&"; + } + if (UtilValidate.isNotEmpty(queryString) && !"null".equals(queryString)) { + prepLinkText += queryString + "&"; + } + final String prepLinkSizeText = prepLinkText + viewSizeParam + "='+this.value+'" + "&" + viewIndexParam + "=0"; + prepLinkText += viewSizeParam + "=" + viewSize + "&" + viewIndexParam + "="; + if (ajaxEnabled) { + // Prepare params for prototype.js + prepLinkText = prepLinkText.replace("?", ""); + prepLinkText = prepLinkText.replace("&", "&"); + } + String firstUrl = ""; + String ajaxFirstUrl = ""; + String previousUrl = ""; + String ajaxPreviousUrl = ""; + String selectUrl = ""; + String ajaxSelectUrl = ""; + String selectSizeUrl = ""; + String ajaxSelectSizeUrl = ""; + String nextUrl = ""; + String ajaxNextUrl = ""; + String lastUrl = ""; + String ajaxLastUrl = ""; + if (viewIndex > 0) { + if (ajaxEnabled) { + ajaxFirstUrl = MacroCommonRenderer.createAjaxParamsFromUpdateAreas(updateAreas, null, modelForm, prepLinkText + 0 + anchor, context); + ajaxPreviousUrl = MacroCommonRenderer.createAjaxParamsFromUpdateAreas(updateAreas, null, modelForm, + prepLinkText + (viewIndex - 1) + anchor, context); + } else { + firstUrl = requestHandler.makeLink(request, response, urlPath + prepLinkText + 0 + anchor); + previousUrl = requestHandler.makeLink(request, response, urlPath + prepLinkText + (viewIndex - 1) + anchor); + } + } + // Page select dropdown + if (listSize > 0 && javaScriptEnabled) { + if (ajaxEnabled) { + ajaxSelectUrl = MacroCommonRenderer.createAjaxParamsFromUpdateAreas(updateAreas, null, modelForm, + prepLinkText + "' + (this.value - 1) + '", context); + } else { + final String linkText = prepLinkText.startsWith("/") ? prepLinkText.substring(1) : prepLinkText; + selectUrl = requestHandler.makeLink(request, response, urlPath + linkText); + } + } + if (highIndex < listSize) { + // Next button and last button + final int lastIndex = UtilMisc.getViewLastIndex(listSize, viewSize); + if (ajaxEnabled) { + ajaxNextUrl = MacroCommonRenderer.createAjaxParamsFromUpdateAreas(updateAreas, null, + modelForm, prepLinkText + (viewIndex + 1) + anchor, context); + ajaxLastUrl = MacroCommonRenderer.createAjaxParamsFromUpdateAreas(updateAreas, null, + modelForm, prepLinkText + lastIndex + anchor, context); + } else { + nextUrl = requestHandler.makeLink(request, response, urlPath + prepLinkText + (viewIndex + 1) + anchor); + lastUrl = requestHandler.makeLink(request, response, urlPath + prepLinkText + lastIndex + anchor); + } + } + // Page size select dropdown + if (listSize > 0 && javaScriptEnabled) { + if (ajaxEnabled) { + ajaxSelectSizeUrl = MacroCommonRenderer.createAjaxParamsFromUpdateAreas(updateAreas, null, + modelForm, prepLinkSizeText + anchor, context); + } else { + final String linkText = prepLinkSizeText.startsWith("/") ? prepLinkSizeText.substring(1) : prepLinkSizeText; + selectSizeUrl = requestHandler.makeLink(request, response, urlPath + linkText); + } + } + return RenderableFtlMacroCall.builder() + .name("renderNextPrev") + .stringParameter("paginateStyle", nonNull(modelForm.getPaginateStyle())) + .stringParameter("paginateFirstStyle", nonNull(modelForm.getPaginateFirstStyle())) + .intParameter("viewIndex", viewIndex) + .intParameter("highIndex", highIndex) + .intParameter("listSize", listSize) + .intParameter("viewSize", viewSize) + .booleanParameter("ajaxEnabled", ajaxEnabled) + .booleanParameter("javaScriptEnabled", javaScriptEnabled) + .stringParameter("ajaxFirstUrl", ajaxFirstUrl) + .stringParameter("firstUrl", nonNull(firstUrl)) + .stringParameter("paginateFirstLabel", nonNull(modelForm.getPaginateFirstLabel(context))) + .stringParameter("paginatePreviousStyle", nonNull(modelForm.getPaginatePreviousStyle())) + .stringParameter("ajaxPreviousUrl", ajaxPreviousUrl) + .stringParameter("previousUrl", nonNull(previousUrl)) + .stringParameter("paginatePreviousLabel", nonNull(modelForm.getPaginatePreviousLabel(context))) + .stringParameter("pageLabel", nonNull(pageLabel)) + .stringParameter("ajaxSelectUrl", ajaxSelectUrl) + .stringParameter("selectUrl", nonNull(selectUrl)) + .stringParameter("ajaxSelectSizeUrl", ajaxSelectSizeUrl) + .stringParameter("selectSizeUrl", nonNull(selectSizeUrl)) + .stringParameter("commonDisplaying", nonNull(commonDisplaying)) + .stringParameter("paginateNextStyle", nonNull(modelForm.getPaginateNextStyle())) + .stringParameter("ajaxNextUrl", ajaxNextUrl) + .stringParameter("nextUrl", nonNull(nextUrl)) + .stringParameter("paginateNextLabel", nonNull(modelForm.getPaginateNextLabel(context))) + .stringParameter("paginateLastStyle", nonNull(modelForm.getPaginateLastStyle())) + .stringParameter("ajaxLastUrl", ajaxLastUrl) + .stringParameter("lastUrl", nonNull(lastUrl)) + .stringParameter("paginateLastLabel", nonNull(modelForm.getPaginateLastLabel(context))) + .stringParameter("paginateViewSizeLabel", nonNull(modelForm.getPaginateViewSizeLabel(context))) + .build(); + } + + /** + * Creates the macro call that renders a file field. + * @param context the context + * @param fileField the file field + * @return the RenderableFtl + */ + public RenderableFtl fileField(final Map<String, Object> context, final ModelFormField.FileField fileField) { + final ModelFormField modelFormField = fileField.getModelFormField(); + final var builder = RenderableFtlMacroCall.builder().name("renderFileField"); + applyWidgetStyleAndAlert(modelFormField, context, builder); + return builder + .stringParameter("name", nonNull(modelFormField.getParameterName(context))) + .stringParameter("value", nonNull(modelFormField.getEntry(context, fileField.getDefaultValue(context)))) + .stringParameter("size", Integer.toString(fileField.getSize())) + .stringParameter("maxlength", fileField.getMaxlength() == null ? "" : fileField.getMaxlength().toString()) + .stringParameter("autocomplete", fileField.getClientAutocompleteField() ? "" : "off") + .stringParameter("tabindex", nonNull(modelFormField.getTabindex())) + .booleanParameter("disabled", modelFormField.getDisabled(context)) + .build(); + } + + /** + * Creates the macro call that renders a password field. + * @param context the context + * @param passwordField the password field + * @return the RenderableFtl + */ + public RenderableFtl passwordField(final Map<String, Object> context, final ModelFormField.PasswordField passwordField) { + final ModelFormField modelFormField = passwordField.getModelFormField(); + String className = nonNull(modelFormField.getWidgetStyle()); + final String alert = UtilValidate.isNotEmpty(className) && modelFormField.shouldBeRed(context) ? "true" : "false"; + className = withRequiredStyle(modelFormField, className); + return RenderableFtlMacroCall.builder() + .name("renderPasswordField") + .stringParameter("className", className) + .stringParameter("alert", alert) + .stringParameter("name", nonNull(modelFormField.getParameterName(context))) + .stringParameter("value", nonNull(modelFormField.getEntry(context, passwordField.getDefaultValue(context)))) + .stringParameter("size", Integer.toString(passwordField.getSize())) + .stringParameter("maxlength", passwordField.getMaxlength() == null ? "" : passwordField.getMaxlength().toString()) + .stringParameter("id", nonNull(modelFormField.getCurrentContainerId(context))) + .stringParameter("autocomplete", passwordField.getClientAutocompleteField() ? "" : "off") + .stringParameter("tabindex", nonNull(modelFormField.getTabindex())) + .booleanParameter("disabled", modelFormField.getDisabled(context)) + .build(); + } + + /** + * Creates the macro call that renders an image field. + * @param context the context + * @param imageField the image field + * @return the RenderableFtl + */ + public RenderableFtl imageField(final Map<String, Object> context, final ModelFormField.ImageField imageField) { + final ModelFormField modelFormField = imageField.getModelFormField(); + String value = modelFormField.getEntry(context, imageField.getValue(context)); + String description = imageField.getDescription(context); + String alternate = imageField.getAlternate(context); + if (UtilValidate.isEmpty(description)) { + description = modelFormField.getTitle(context); + } + if (UtilValidate.isEmpty(alternate)) { + alternate = description; + } + if (UtilValidate.isNotEmpty(value) && !value.startsWith("http")) { + value = pathAsContentUrl(value); + } + return RenderableFtlMacroCall.builder() + .name("renderImageField") + .stringParameter("value", nonNull(value)) + .stringParameter("description", nonNull(encode(description, modelFormField, context))) + .stringParameter("alternate", nonNull(encode(alternate, modelFormField, context))) + .stringParameter("style", nonNull(imageField.getStyle(context))) + .stringParameter("event", nonNull(modelFormField.getEvent())) + .stringParameter("action", nonNull(modelFormField.getAction(context))) + .build(); + } + + /** + * Creates the macro call that renders a banner. + * @param context the context + * @param banner the banner + * @return the RenderableFtl + */ + public RenderableFtl banner(final Map<String, Object> context, final ModelForm.Banner banner) { + final String style = banner.getStyle(context); + final String leftStyle = banner.getLeftTextStyle(context); + final String rightStyle = banner.getRightTextStyle(context); + return RenderableFtlMacroCall.builder() + .name("renderBanner") + .stringParameter("style", nonNull(style)) + .stringParameter("leftStyle", nonNull(UtilValidate.isEmpty(leftStyle) ? style : leftStyle)) + .stringParameter("rightStyle", nonNull(UtilValidate.isEmpty(rightStyle) ? style : rightStyle)) + .stringParameter("leftText", nonNull(banner.getLeftText(context))) + .stringParameter("text", nonNull(banner.getText(context))) + .stringParameter("rightText", nonNull(banner.getRightText(context))) + .build(); + } + + // the class name of a field that is required in a single form gets the required style first + private static String withRequiredStyle(final ModelFormField modelFormField, final String className) { + if (!shouldApplyRequiredField(modelFormField)) { + return className; + } + String requiredStyle = modelFormField.getRequiredFieldStyle(); + if (UtilValidate.isEmpty(requiredStyle)) { + requiredStyle = "required"; + } + return UtilValidate.isEmpty(className) ? requiredStyle : requiredStyle + " " + className; + } + private static RenderableFtl macroWithoutParameters(final String macroName) { return RenderableFtlMacroCall.builder().name(macroName).build(); } 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 fe7e566970..911992ff25 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 @@ -19,11 +19,7 @@ package org.apache.ofbiz.widget.renderer.macro; import static org.hamcrest.MatcherAssert.assertThat; -import static org.hamcrest.Matchers.containsString; import static org.hamcrest.Matchers.equalTo; -import static org.hamcrest.Matchers.empty; -import static org.hamcrest.Matchers.not; -import static org.hamcrest.Matchers.startsWith; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyBoolean; import static org.mockito.ArgumentMatchers.anyMap; @@ -35,7 +31,6 @@ import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.CALLS_REAL_METHODS; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; -import static org.mockito.Mockito.atLeastOnce; import static org.mockito.Mockito.inOrder; import static org.mockito.Mockito.mock; import static org.mockito.Mockito.mockStatic; @@ -46,10 +41,8 @@ import static org.mockito.Mockito.when; import java.io.IOException; import java.io.StringWriter; import java.util.HashMap; -import java.util.List; import java.util.Locale; import java.util.Map; -import java.util.stream.Collectors; import jakarta.servlet.http.HttpServletRequest; import jakarta.servlet.http.HttpServletResponse; @@ -72,7 +65,6 @@ import org.hamcrest.Matchers; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; -import org.mockito.ArgumentCaptor; import org.mockito.InOrder; import org.mockito.MockedStatic; @@ -491,125 +483,84 @@ public final class MacroFormRendererTest { @Test public void textFindFieldMacroRendered() throws IOException { - final ModelFormField localModelFormField = mock(ModelFormField.class); final ModelFormField.TextFindField textFindField = mock(ModelFormField.TextFindField.class); - when(textFindField.getModelFormField()).thenReturn(localModelFormField); - when(textFindField.getHideOptions()).thenReturn(true); - when(localModelFormField.getWidgetStyle()).thenReturn("WIDGETSTYLE"); - when(localModelFormField.shouldBeRed(notNull())).thenReturn(true); - when(localModelFormField.getParameterName(notNull())).thenReturn("FIELDNAME"); + when(renderableFtlFormElementsBuilder.textFindField(notNull(), eq(textFindField))).thenReturn(genericMacroCall); + genericTooltipRenderedExpectation(textFindField); + + macroFormRenderer.renderTextFindField(appendable, ImmutableMap.of(), textFindField); - ImmutableMap<String, Object> context = ImmutableMap.of(); - macroFormRenderer.renderTextFindField(appendable, context, textFindField); - assertAndGetMacroString("renderTextFindField", ImmutableMap.of( - "name", "FIELDNAME", - "className", "WIDGETSTYLE", - "alert", "true")); + genericSingleMacroRenderedVerification(); + genericTooltipRenderedVerification(); } @Test public void rangeFindFieldMacroRendered() throws IOException { - final ModelFormField localModelFormField = mock(ModelFormField.class); final ModelFormField.RangeFindField rangeFindField = mock(ModelFormField.RangeFindField.class); - when(rangeFindField.getModelFormField()).thenReturn(localModelFormField); - when(localModelFormField.getWidgetStyle()).thenReturn("WIDGETSTYLE"); - when(rangeFindField.getDefaultValue(notNull())).thenReturn("AAA"); - when(localModelFormField.getEntry(notNull(), eq("AAA"))).thenReturn("AAA"); - when(localModelFormField.getEntry(notNull())).thenReturn("BBB"); - when(localModelFormField.shouldBeRed(notNull())).thenReturn(true); - when(localModelFormField.getParameterName(notNull())).thenReturn("FIELDNAME"); + when(renderableFtlFormElementsBuilder.rangeFindField(notNull(), eq(rangeFindField))).thenReturn(genericMacroCall); + genericTooltipRenderedExpectation(rangeFindField); + + macroFormRenderer.renderRangeFindField(appendable, ImmutableMap.of(), rangeFindField); - ImmutableMap<String, Object> context = ImmutableMap.of(); - macroFormRenderer.renderRangeFindField(appendable, context, rangeFindField); - assertAndGetMacroString("renderRangeFindField", ImmutableMap.of( - "name", "FIELDNAME", - "className", "WIDGETSTYLE", - "alert", "true", - "value", "AAA", - "value2", "BBB")); + genericSingleMacroRenderedVerification(); + genericTooltipRenderedVerification(); } @Test public void lookupFieldMacroRendered() throws IOException { - // Deep stubs: renderLookupField()'s shouldApplyRequiredField() call cascades through - // localModelFormField.getModelForm().getType() unconditionally. - final ModelFormField localModelFormField = mock(ModelFormField.class, RETURNS_DEEP_STUBS); final ModelFormField.LookupField lookupField = mock(ModelFormField.LookupField.class); - when(httpSession.getAttribute("delegatorName")).thenReturn("delegator"); + when(renderableFtlFormElementsBuilder.lookupField(notNull(), eq(lookupField), eq(true))).thenReturn(genericMacroCall); + genericTooltipRenderedExpectation(lookupField); - when(lookupField.getModelFormField()).thenReturn(localModelFormField); - when(localModelFormField.getEntry(notNull(), isNull())).thenReturn("VALUE"); - when(localModelFormField.getParameterName(notNull())).thenReturn("FIELDNAME"); - when(localModelFormField.getCurrentContainerId(notNull())).thenReturn("CONTAINERID"); + macroFormRenderer.renderLookupField(appendable, ImmutableMap.of(), lookupField); - ImmutableMap<String, Object> context = ImmutableMap.of("session", httpSession); - macroFormRenderer.renderLookupField(appendable, context, lookupField); - assertAndGetMacroString("renderLookupField", ImmutableMap.of( - "name", "FIELDNAME", - "value", "VALUE", - "id", "CONTAINERID")); + genericSingleMacroRenderedVerification(); + genericTooltipRenderedVerification(); } @Test public void renderNextPrevMacroRendered() throws IOException { final ModelForm modelForm = mock(ModelForm.class); - final String targetService = ""; // Leave empty to avoid CSRF token generation. - final String paginateIndexField = "PAGINATE_INDEX"; - final String paginateSizeField = "PAGINATE_SIZE"; - - when(modelForm.getPaginateTarget(notNull())).thenReturn(targetService); - when(modelForm.getMultiPaginateIndexField(notNull())).thenReturn(paginateIndexField); + when(renderableFtlFormElementsBuilder.nextPrev(notNull(), eq(modelForm), eq(true))).thenReturn(genericMacroCall); - Map<String, Object> context = new HashMap<>(); - context.put("session", httpSession); - context.put(paginateIndexField, 0); - context.put(paginateSizeField, 30); - macroFormRenderer.renderNextPrev(appendable, context, modelForm); + macroFormRenderer.renderNextPrev(appendable, new HashMap<>(), modelForm); - assertAndGetMacroString("renderNextPrev"); + genericSingleMacroRenderedVerification(); } @Test public void fileFieldMacroRendered() throws IOException { final ModelFormField.FileField fileField = mock(ModelFormField.FileField.class); - when(fileField.getModelFormField()).thenReturn(modelFormField); - when(modelFormField.getParameterName(notNull())).thenReturn("FIELDNAME"); - when(modelFormField.getEntry(notNull(), isNull())).thenReturn("VALUE"); - when(modelFormField.getWidgetStyle()).thenReturn("WIDGETSTYLE"); + when(renderableFtlFormElementsBuilder.fileField(notNull(), eq(fileField))).thenReturn(genericMacroCall); + genericTooltipRenderedExpectation(fileField); macroFormRenderer.renderFileField(appendable, ImmutableMap.of(), fileField); - assertAndGetMacroString("renderFileField", ImmutableMap.of( - "name", "FIELDNAME", - "value", "VALUE", - "className", "WIDGETSTYLE")); + genericSingleMacroRenderedVerification(); + genericTooltipRenderedVerification(); } @Test public void passwordFieldMacroRendered() throws IOException { final ModelFormField.PasswordField passwordField = mock(ModelFormField.PasswordField.class); - when(passwordField.getModelFormField()).thenReturn(modelFormField); - when(modelFormField.getParameterName(notNull())).thenReturn("FIELDNAME"); - when(modelFormField.getEntry(notNull(), isNull())).thenReturn("VALUE"); - when(modelFormField.getWidgetStyle()).thenReturn("WIDGETSTYLE"); + when(renderableFtlFormElementsBuilder.passwordField(notNull(), eq(passwordField))).thenReturn(genericMacroCall); + genericTooltipRenderedExpectation(passwordField); macroFormRenderer.renderPasswordField(appendable, ImmutableMap.of(), passwordField); - assertAndGetMacroString("renderPasswordField", ImmutableMap.of( - "name", "FIELDNAME", - "value", "VALUE", - "className", "WIDGETSTYLE")); + genericSingleMacroRenderedVerification(); + genericTooltipRenderedVerification(); } @Test public void imageFieldMacroRendered() throws IOException { final ModelFormField.ImageField imageField = mock(ModelFormField.ImageField.class); - when(imageField.getModelFormField()).thenReturn(modelFormField); - when(modelFormField.getEntry(notNull(), isNull())).thenReturn("VALUE"); + when(renderableFtlFormElementsBuilder.imageField(notNull(), eq(imageField))).thenReturn(genericMacroCall); + genericTooltipRenderedExpectation(imageField); macroFormRenderer.renderImageField(appendable, ImmutableMap.of(), imageField); - assertAndGetMacroString("renderImageField", ImmutableMap.of("value", "VALUE")); + genericSingleMacroRenderedVerification(); + genericTooltipRenderedVerification(); } @Test @@ -681,21 +632,13 @@ public final class MacroFormRendererTest { } @Test - public void renderNextPrevUsesQueryString() throws IOException { - final ModelForm modelForm = mock(ModelForm.class); - final String targetService = ""; // Leave empty to avoid CSRF token generation. - final String qbeString = "field1=value1&field2=value2+with+spaces"; - final String linkFromQbeString = "LinkFromQBEString"; + public void bannerMacroRendered() throws IOException { + final ModelForm.Banner banner = mock(ModelForm.Banner.class); + when(renderableFtlFormElementsBuilder.banner(notNull(), eq(banner))).thenReturn(genericMacroCall); - when(modelForm.getPaginateTarget(notNull())).thenReturn(targetService); - when(requestHandler.makeLink(notNull(), notNull(), contains(qbeString))).thenReturn(linkFromQbeString); - - final Map<String, Object> context = new HashMap<>(); - context.put("_QBESTRING_", qbeString); - context.put("listSize", 100); - macroFormRenderer.renderNextPrev(appendable, context, modelForm); + macroFormRenderer.renderBanner(appendable, ImmutableMap.of(), banner); - assertAndGetMacroString("renderNextPrev", ImmutableMap.of("nextUrl", linkFromQbeString)); + genericSingleMacroRenderedVerification(); } @Test @@ -735,6 +678,19 @@ public final class MacroFormRendererTest { genericTooltipRenderedVerification(); } + @Test + public void hyperlinkFieldPassesTheAlternateAndTheImageTitleWithoutEscapedQuotes() throws IOException { + final ModelFormField.HyperlinkField hyperlinkField = mock(ModelFormField.HyperlinkField.class); + when(hyperlinkField.getModelFormField()).thenReturn(modelFormField); + when(hyperlinkField.getAlternate(notNull())).thenReturn("say \"hi\""); + when(hyperlinkField.getImageTitle(notNull())).thenReturn("a \"title\""); + + macroFormRenderer.renderHyperlinkField(appendable, new HashMap<>(), hyperlinkField); + + verify(request).setAttribute("alternate", "say \"hi\""); + verify(request).setAttribute("imageTitle", "a \"title\""); + } + @Test public void fieldTitleWithHeaderLinkRendersHyperlinkTitle() throws IOException { when(modelFormField.getTitle(notNull())).thenReturn("FIELDTITLE"); @@ -767,46 +723,6 @@ public final class MacroFormRendererTest { genericTooltipRenderedVerification(); } - private String assertAndGetMacroString(final String expectedName) { - return assertAndGetMacroString(expectedName, ImmutableMap.of()); - } - - private String assertAndGetMacroString(final String expectedName, final Map<String, Object> expectedAttributes) { - ArgumentCaptor<String> macroCaptor = ArgumentCaptor.forClass(String.class); - verify(ftlWriter, atLeastOnce()).processFtlString(notNull(), isNull(), macroCaptor.capture()); - - // The captor accumulates every processFtlString() call made on this mock since the test - // began, not just those since the last check - unlike JMockit's per-block capture, so a test - // that renders more than one macro (or calls a render method twice) must look at the most - // recent invocation, not the first, to see the one this specific call actually produced. - List<String> macros = macroCaptor.getAllValues(); - assertThat(macros, not(empty())); - final String macro = macros.get(macros.size() - 1); - assertThat(macro, startsWith("<@" + expectedName)); - - expectedAttributes.forEach((name, value) -> assertMacroAttribute(macro, name, value)); - - return macro; - } - - private void assertMacroAttribute(final String macro, final String attributeName, final Object attributeValue) { - if (attributeValue instanceof Number) { - assertThat(macro, containsString(attributeName + "=" + attributeValue)); - } else if (attributeValue instanceof List<?>) { - final String valueString = ((List<?>) attributeValue).stream() - .map(Object::toString) - .collect(Collectors.joining(",", "[", "]")); - assertThat(macro, containsString(attributeName + "=" + valueString)); - } else if (attributeValue instanceof Boolean) { - assertThat(macro, containsString(attributeName + "=" + attributeValue)); - } else if (attributeValue instanceof FreemarkerRawString) { - final String valueString = ((FreemarkerRawString) attributeValue).getRawString(); - assertThat(macro, containsString(attributeName + "=\"" + valueString + "\"")); - } else { - assertThat(macro, containsString(attributeName + "=\"" + attributeValue + "\"")); - } - } - /** * Assert that the generic MacroCall instance is passed to the macro executor. This is used for simple renderings * where MacroFormRenderer has FormMacroCallBuilder to construct a MacroCall and then passes it straight to the @@ -828,16 +744,4 @@ public final class MacroFormRendererTest { private void genericSubHyperlinkRenderedVerification() { verify(ftlWriter).processFtl(appendable, null, genericHyperlinkMacroCall); } - - static class FreemarkerRawString { - private final String rawString; - - FreemarkerRawString(final String rawString) { - this.rawString = rawString; - } - - public String getRawString() { - return rawString; - } - } } 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 8389332225..a5d0e296ca 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 @@ -20,6 +20,8 @@ package org.apache.ofbiz.widget.renderer.macro; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; +import org.apache.ofbiz.base.util.UtilCodec; +import org.apache.ofbiz.base.util.UtilProperties; import org.apache.ofbiz.webapp.control.ConfigXMLReader; import org.apache.ofbiz.webapp.control.RequestHandler; import org.apache.ofbiz.widget.content.StaticContentUrlProvider; @@ -32,6 +34,7 @@ import org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtlMacroCall; import org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtl; import org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtlNoop; import org.apache.ofbiz.widget.renderer.macro.renderable.RenderableFtlString; +import org.mockito.MockedStatic; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; @@ -49,6 +52,11 @@ 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.anyMap; +import static org.mockito.ArgumentMatchers.anyString; +import static org.mockito.ArgumentMatchers.contains; +import static org.mockito.Mockito.CALLS_REAL_METHODS; +import static org.mockito.Mockito.mockStatic; import static org.mockito.ArgumentMatchers.notNull; import static org.mockito.ArgumentMatchers.nullable; import static org.mockito.Mockito.RETURNS_DEEP_STUBS; @@ -1064,4 +1072,314 @@ public final class RenderableFtlFormElementsBuilderTest { assertThat(((RenderableFtlString) link).getFtlString().contains("A ${description}"), equalTo(true)); assertThat(renderableFtlFormElementsBuilder.postMultiFormContent(context) instanceof RenderableFtlString, equalTo(true)); } + + private static MockedStatic<UtilProperties> labelsReturningTheirKey() { + final MockedStatic<UtilProperties> utilProperties = mockStatic(UtilProperties.class, CALLS_REAL_METHODS); + utilProperties.when(() -> UtilProperties.getMessage(anyString(), anyString(), nullable(Locale.class))) + .thenAnswer(invocation -> invocation.<String>getArgument(1) + "_MESSAGE"); + return utilProperties; + } + + @Test + public void textFindFieldPassesTheValuesAsTheyAre() { + final ModelFormField.TextFindField textFindField = mock(ModelFormField.TextFindField.class); + when(textFindField.getModelFormField()).thenReturn(modelFormField); + when(textFindField.getDefaultOption(notNull())).thenReturn("contains"); + when(textFindField.getSize()).thenReturn(20); + when(textFindField.getMaxlength()).thenReturn(5); + when(textFindField.getClientAutocompleteField()).thenReturn(true); + when(modelFormField.getEntry(notNull(), nullable(String.class))).thenReturn("say \"hi\" ${1+1}"); + when(modelFormField.getParameterName(notNull())).thenReturn("FIELDNAME"); + + try (MockedStatic<UtilProperties> ignored = labelsReturningTheirKey()) { + assertThat(renderableFtlFormElementsBuilder.textFindField(new HashMap<>(), textFindField), + MacroCallMatcher.hasNameAndParameters("renderTextFindField", + MacroCallParameterMatcher.hasNameAndStringValue("name", "FIELDNAME"), + MacroCallParameterMatcher.hasNameAndStringValue("value", "say \"hi\" ${1+1}"), + MacroCallParameterMatcher.hasNameAndStringValue("defaultOption", "contains"), + MacroCallParameterMatcher.hasNameAndStringValue("opEquals", "equals_MESSAGE"), + MacroCallParameterMatcher.hasNameAndStringValue("size", "20"), + MacroCallParameterMatcher.hasNameAndStringValue("maxlength", "5"), + MacroCallParameterMatcher.hasNameAndStringValue("autocomplete", ""), + MacroCallParameterMatcher.hasNameAndStringValue("ignoreCase", "ignore_case_MESSAGE"))); + } + } + + @Test + public void textFindFieldWithHiddenOptionsHasNoOperatorLabels() { + final ModelFormField.TextFindField textFindField = mock(ModelFormField.TextFindField.class); + when(textFindField.getModelFormField()).thenReturn(modelFormField); + when(textFindField.getHideOptions()).thenReturn(true); + when(textFindField.getMaxlength()).thenReturn(null); + + try (MockedStatic<UtilProperties> ignored = labelsReturningTheirKey()) { + assertThat(renderableFtlFormElementsBuilder.textFindField(new HashMap<>(), textFindField), + MacroCallMatcher.hasNameAndParameters("renderTextFindField", + MacroCallParameterMatcher.hasNameAndStringValue("opEquals", ""), + MacroCallParameterMatcher.hasNameAndStringValue("opNotEqual", ""), + MacroCallParameterMatcher.hasNameAndStringValue("maxlength", ""), + MacroCallParameterMatcher.hasNameAndStringValue("autocomplete", "off"))); + } + } + + @Test + public void rangeFindFieldSetsBothValues() { + final ModelFormField.RangeFindField rangeFindField = mock(ModelFormField.RangeFindField.class); + when(rangeFindField.getModelFormField()).thenReturn(modelFormField); + when(rangeFindField.getMaxlength()).thenReturn(null); + when(rangeFindField.getDefaultOptionFrom()).thenReturn("greaterThan"); + when(rangeFindField.getDefaultOptionThru()).thenReturn("lessThan"); + when(modelFormField.getEntry(notNull(), nullable(String.class))).thenReturn("AAA ${1+1}"); + when(modelFormField.getEntry(notNull())).thenReturn("BBB"); + + try (MockedStatic<UtilProperties> ignored = labelsReturningTheirKey()) { + assertThat(renderableFtlFormElementsBuilder.rangeFindField(new HashMap<>(), rangeFindField), + MacroCallMatcher.hasNameAndParameters("renderRangeFindField", + MacroCallParameterMatcher.hasNameAndStringValue("value", "AAA ${1+1}"), + MacroCallParameterMatcher.hasNameAndStringValue("value2", "BBB"), + MacroCallParameterMatcher.hasNameAndStringValue("maxlength", ""), + MacroCallParameterMatcher.hasNameAndStringValue("defaultOptionFrom", "greaterThan"), + MacroCallParameterMatcher.hasNameAndStringValue("defaultOptionThru", "lessThan"), + MacroCallParameterMatcher.hasNameAndStringValue("opGreaterThanEquals", "greater_than_equals_MESSAGE"))); + } + } + + @Test + public void lookupFieldPassesTheTargetParametersAsAList() throws Exception { + final ModelFormField.LookupField lookupField = mock(ModelFormField.LookupField.class); + when(lookupField.getModelFormField()).thenReturn(modelFormField); + when(lookupField.getFormName(notNull())).thenReturn("LookupForm"); + when(lookupField.getTargetParameterList(notNull())).thenReturn(List.of("it's", "a\"b")); + when(lookupField.getLookupPresentation()).thenReturn(null); + when(lookupField.getLookupHeight()).thenReturn("300"); + when(modelFormField.getEntry(notNull(), nullable(String.class))).thenReturn("VALUE"); + when(modelFormField.getCurrentContainerId(notNull())).thenReturn("CONTAINERID"); + when(modelFormField.getParameterName(notNull())).thenReturn("FIELDNAME"); + when(httpSession.getAttribute("delegatorName")).thenReturn("delegator"); + when(staticContentUrlProvider.pathAsContentUrlString("/images/fieldlookup.gif")).thenReturn("PREFIX/images/fieldlookup.gif"); + final Map<String, Object> context = new HashMap<>(); + context.put("session", httpSession); + + final RenderableFtlMacroCall macroCall = macroCall(renderableFtlFormElementsBuilder.lookupField(context, lookupField, false)); + assertThat(macroCall, MacroCallMatcher.hasNameAndParameters("renderLookupField", + MacroCallParameterMatcher.hasNameAndStringValue("name", "FIELDNAME"), + MacroCallParameterMatcher.hasNameAndStringValue("value", "VALUE"), + MacroCallParameterMatcher.hasNameAndStringValue("id", "CONTAINERID"), + MacroCallParameterMatcher.hasNameAndStringValue("imgSrc", "PREFIX/images/fieldlookup.gif"), + MacroCallParameterMatcher.hasNameAndStringValue("presentation", ""), + MacroCallParameterMatcher.hasNameAndStringValue("height", "300"), + MacroCallParameterMatcher.hasNameAndStringValue("ajaxUrl", ""), + MacroCallParameterMatcher.hasNameAndBooleanValue("ajaxEnabled", false), + MacroCallParameterMatcher.hasNameAndStringValue("delegatorName", "delegator"))); + assertThat(macroCall.getParameters().get("targetParameterIter"), equalTo(List.of("it's", "a\"b"))); + assertThat(macroCall.getParameters(), not(hasKey("width"))); + assertThat(macroCall.getParameters(), not(hasKey("position"))); + } + + @Test + public void lookupFieldOfARequiredFieldGetsTheRequiredStyleFirst() throws Exception { + final ModelFormField.LookupField lookupField = mock(ModelFormField.LookupField.class); + when(lookupField.getModelFormField()).thenReturn(modelFormField); + when(modelFormField.getWidgetStyle()).thenReturn("WIDGETSTYLE"); + when(modelFormField.shouldBeRed(notNull())).thenReturn(true); + when(modelFormField.getModelForm().getType()).thenReturn("single"); + when(modelFormField.getRequiredField()).thenReturn(true); + when(modelFormField.getRequiredFieldStyle()).thenReturn("REQ"); + when(httpSession.getAttribute("delegatorName")).thenReturn("delegator"); + final Map<String, Object> context = new HashMap<>(); + context.put("session", httpSession); + + assertThat(renderableFtlFormElementsBuilder.lookupField(context, lookupField, false), + MacroCallMatcher.hasNameAndParameters("renderLookupField", + MacroCallParameterMatcher.hasNameAndStringValue("className", "REQ WIDGETSTYLE"), + MacroCallParameterMatcher.hasNameAndStringValue("alert", "true"))); + } + + private ModelForm paginatedForm() { + final ModelForm modelForm = mock(ModelForm.class); + when(modelForm.getPaginateTarget(notNull())).thenReturn(""); // empty to avoid the generation of a CSRF token + when(modelForm.getMultiPaginateIndexField(notNull())).thenReturn("PAGINATE_INDEX"); + when(modelForm.getMultiPaginateSizeField(notNull())).thenReturn("PAGINATE_SIZE"); + return modelForm; + } + + private static Map<String, Object> paginationContext(final String queryString) { + final Map<String, Object> context = new HashMap<>(); + context.put("PAGINATE_INDEX", 0); + context.put("PAGINATE_SIZE", 30); + context.put("listSize", 100); + context.put("_QBESTRING_", queryString); + return context; + } + + @Test + public void nextPrevUsesTheQueryStringOfTheListForTheLinks() { + final ModelForm modelForm = paginatedForm(); + final String queryString = "field1=value1&field2=value2+with+spaces"; + when(requestHandler.makeLink(notNull(), notNull(), contains(queryString))).thenReturn("LinkFromQBEString"); + + final RenderableFtlMacroCall macroCall = macroCall( + renderableFtlFormElementsBuilder.nextPrev(paginationContext(queryString), modelForm, true)); + assertThat(macroCall, MacroCallMatcher.hasNameAndParameters("renderNextPrev", + MacroCallParameterMatcher.hasNameAndStringValue("nextUrl", "LinkFromQBEString"), + MacroCallParameterMatcher.hasNameAndStringValue("lastUrl", "LinkFromQBEString"), + MacroCallParameterMatcher.hasNameAndStringValue("selectUrl", "LinkFromQBEString"), + MacroCallParameterMatcher.hasNameAndStringValue("firstUrl", ""), + MacroCallParameterMatcher.hasNameAndIntegerValue("viewSize", 30), + MacroCallParameterMatcher.hasNameAndIntegerValue("listSize", 100), + MacroCallParameterMatcher.hasNameAndBooleanValue("ajaxEnabled", false), + MacroCallParameterMatcher.hasNameAndBooleanValue("javaScriptEnabled", true))); + } + + @Test + public void nextPrevWithoutJavaScriptHasNoSelectLinks() { + final ModelForm modelForm = paginatedForm(); + when(requestHandler.makeLink(notNull(), notNull(), anyString())).thenReturn("LINK"); + + assertThat(renderableFtlFormElementsBuilder.nextPrev(paginationContext(""), modelForm, false), + MacroCallMatcher.hasNameAndParameters("renderNextPrev", + MacroCallParameterMatcher.hasNameAndStringValue("nextUrl", "LINK"), + MacroCallParameterMatcher.hasNameAndStringValue("selectUrl", ""), + MacroCallParameterMatcher.hasNameAndStringValue("selectSizeUrl", ""), + MacroCallParameterMatcher.hasNameAndBooleanValue("javaScriptEnabled", false))); + } + + @Test + public void nextPrevSetsTheLabels() { + final ModelForm modelForm = paginatedForm(); + when(modelForm.getPaginateNextLabel(notNull())).thenReturn("Next ${label}"); + final Map<String, Object> context = paginationContext(""); + context.put("uiLabelMap", Map.of("CommonPage", "PAGE")); + + try (MockedStatic<UtilProperties> utilProperties = mockStatic(UtilProperties.class, CALLS_REAL_METHODS)) { + utilProperties.when(() -> UtilProperties.getMessage(anyString(), anyString(), anyMap(), nullable(Locale.class))) + .thenReturn("DISPLAYING"); + assertThat(renderableFtlFormElementsBuilder.nextPrev(context, modelForm, false), + MacroCallMatcher.hasNameAndParameters("renderNextPrev", + MacroCallParameterMatcher.hasNameAndStringValue("pageLabel", "PAGE"), + MacroCallParameterMatcher.hasNameAndStringValue("commonDisplaying", "DISPLAYING"), + MacroCallParameterMatcher.hasNameAndStringValue("paginateNextLabel", "Next ${label}"))); + } + } + + @Test + public void nextPrevRendersNothingWhenThereIsNothingToPaginate() { + final ModelForm modelForm = paginatedForm(); + when(modelForm.getOnPaginateUpdateAreas()).thenReturn(null); + + assertThat(renderableFtlFormElementsBuilder.nextPrev(paginationContext(""), modelForm, true), equalTo(RenderableFtlNoop.INSTANCE)); + } + + @Test + public void fileFieldPassesTheValueAsItIs() { + final ModelFormField.FileField fileField = mock(ModelFormField.FileField.class); + when(fileField.getModelFormField()).thenReturn(modelFormField); + when(fileField.getSize()).thenReturn(30); + when(fileField.getMaxlength()).thenReturn(null); + when(modelFormField.getParameterName(notNull())).thenReturn("FIELDNAME"); + when(modelFormField.getEntry(notNull(), nullable(String.class))).thenReturn("it's \"VALUE\" ${1+1}"); + when(modelFormField.getWidgetStyle()).thenReturn("WIDGETSTYLE"); + + assertThat(renderableFtlFormElementsBuilder.fileField(new HashMap<>(), fileField), + MacroCallMatcher.hasNameAndParameters("renderFileField", + MacroCallParameterMatcher.hasNameAndStringValue("name", "FIELDNAME"), + MacroCallParameterMatcher.hasNameAndStringValue("value", "it's \"VALUE\" ${1+1}"), + MacroCallParameterMatcher.hasNameAndStringValue("className", "WIDGETSTYLE"), + MacroCallParameterMatcher.hasNameAndStringValue("size", "30"), + MacroCallParameterMatcher.hasNameAndStringValue("maxlength", ""))); + } + + @Test + public void passwordFieldOfARequiredFieldGetsTheRequiredStyleFirst() { + final ModelFormField.PasswordField passwordField = mock(ModelFormField.PasswordField.class); + when(passwordField.getModelFormField()).thenReturn(modelFormField); + when(modelFormField.getWidgetStyle()).thenReturn("WIDGETSTYLE"); + when(modelFormField.shouldBeRed(notNull())).thenReturn(true); + when(modelFormField.getModelForm().getType()).thenReturn("single"); + when(modelFormField.getRequiredField()).thenReturn(true); + when(modelFormField.getRequiredFieldStyle()).thenReturn("REQ"); + when(modelFormField.getCurrentContainerId(notNull())).thenReturn(null); + + assertThat(renderableFtlFormElementsBuilder.passwordField(new HashMap<>(), passwordField), + MacroCallMatcher.hasNameAndParameters("renderPasswordField", + MacroCallParameterMatcher.hasNameAndStringValue("className", "REQ WIDGETSTYLE"), + MacroCallParameterMatcher.hasNameAndStringValue("alert", "true"), + MacroCallParameterMatcher.hasNameAndStringValue("id", ""))); + } + + @Test + public void passwordFieldWithoutRequiredStyleUsesTheDefaultRequiredStyle() { + final ModelFormField.PasswordField passwordField = mock(ModelFormField.PasswordField.class); + when(passwordField.getModelFormField()).thenReturn(modelFormField); + when(modelFormField.getModelForm().getType()).thenReturn("upload"); + when(modelFormField.getRequiredField()).thenReturn(true); + when(modelFormField.getRequiredFieldStyle()).thenReturn(""); + + assertThat(renderableFtlFormElementsBuilder.passwordField(new HashMap<>(), passwordField), + MacroCallMatcher.hasNameAndParameters("renderPasswordField", + MacroCallParameterMatcher.hasNameAndStringValue("className", "required"), + MacroCallParameterMatcher.hasNameAndStringValue("alert", "false"))); + } + + @Test + public void imageFieldPrefixesTheValueAndPassesTheTextsAsTheyAre() { + final ModelFormField.ImageField imageField = mock(ModelFormField.ImageField.class); + when(imageField.getModelFormField()).thenReturn(modelFormField); + when(imageField.getDescription(notNull())).thenReturn("say \"hi\" ${1+1}"); + when(imageField.getStyle(notNull())).thenReturn("STYLE"); + when(modelFormField.getEntry(notNull(), nullable(String.class))).thenReturn("img.png"); + when(staticContentUrlProvider.pathAsContentUrlString("img.png")).thenReturn("PREFIX/img.png"); + + assertThat(renderableFtlFormElementsBuilder.imageField(new HashMap<>(), imageField), + MacroCallMatcher.hasNameAndParameters("renderImageField", + MacroCallParameterMatcher.hasNameAndStringValue("value", "PREFIX/img.png"), + MacroCallParameterMatcher.hasNameAndStringValue("description", "say \"hi\" ${1+1}"), + // without an alternate the description is used + MacroCallParameterMatcher.hasNameAndStringValue("alternate", "say \"hi\" ${1+1}"), + MacroCallParameterMatcher.hasNameAndStringValue("style", "STYLE"))); + } + + @Test + public void imageFieldKeepsAnAbsoluteValueAndUsesTheTitleWithoutDescription() { + final ModelFormField.ImageField imageField = mock(ModelFormField.ImageField.class); + when(imageField.getModelFormField()).thenReturn(modelFormField); + when(modelFormField.getEntry(notNull(), nullable(String.class))).thenReturn("http://host/img.png"); + when(modelFormField.getTitle(notNull())).thenReturn("TITLE"); + + assertThat(renderableFtlFormElementsBuilder.imageField(new HashMap<>(), imageField), + MacroCallMatcher.hasNameAndParameters("renderImageField", + MacroCallParameterMatcher.hasNameAndStringValue("value", "http://host/img.png"), + MacroCallParameterMatcher.hasNameAndStringValue("description", "TITLE"))); + } + + @Test + public void imageFieldEncodesTheTextsOnlyOnceWithTheEncoderOfTheContext() { + final ModelFormField.ImageField imageField = mock(ModelFormField.ImageField.class); + when(imageField.getModelFormField()).thenReturn(modelFormField); + when(imageField.getDescription(notNull())).thenReturn("<b>"); + when(modelFormField.getEncodeOutput()).thenReturn(true); + final Map<String, Object> context = new HashMap<>(); + context.put("simpleEncoder", UtilCodec.getEncoder("html")); + + assertThat(renderableFtlFormElementsBuilder.imageField(context, imageField), + MacroCallMatcher.hasNameAndParameters("renderImageField", + MacroCallParameterMatcher.hasNameAndStringValue("description", "<b>"))); + } + + @Test + public void bannerUsesTheStyleWhenThereIsNoStyleForASide() { + final ModelForm.Banner banner = mock(ModelForm.Banner.class); + when(banner.getStyle(notNull())).thenReturn("STYLE"); + when(banner.getRightTextStyle(notNull())).thenReturn("RIGHTSTYLE"); + when(banner.getText(notNull())).thenReturn("TEXT ${1+1}"); + + assertThat(renderableFtlFormElementsBuilder.banner(new HashMap<>(), banner), + MacroCallMatcher.hasNameAndParameters("renderBanner", + MacroCallParameterMatcher.hasNameAndStringValue("style", "STYLE"), + MacroCallParameterMatcher.hasNameAndStringValue("leftStyle", "STYLE"), + MacroCallParameterMatcher.hasNameAndStringValue("rightStyle", "RIGHTSTYLE"), + MacroCallParameterMatcher.hasNameAndStringValue("leftText", ""), + MacroCallParameterMatcher.hasNameAndStringValue("text", "TEXT ${1+1}"), + MacroCallParameterMatcher.hasNameAndStringValue("rightText", ""))); + } }

