blob: a5104e164d1e26b722b3ddd8ec393d1b922c783f [file] [log] [blame]
/*******************************************************************************
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you under the Apache License, Version 2.0 (the
* "License"); you may not use this file except in compliance
* with the License. You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing,
* software distributed under the License is distributed on an
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
* KIND, either express or implied. See the License for the
* specific language governing permissions and limitations
* under the License.
*******************************************************************************/
package org.apache.ofbiz.widget.renderer;
import java.io.IOException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.NoSuchElementException;
import java.util.Set;
import java.util.TreeMap;
import java.util.TreeSet;
import org.apache.ofbiz.base.util.Debug;
import org.apache.ofbiz.base.util.UtilGenerics;
import org.apache.ofbiz.base.util.UtilMisc;
import org.apache.ofbiz.base.util.UtilValidate;
import org.apache.ofbiz.base.util.collections.MapStack;
import org.apache.ofbiz.base.util.string.FlexibleStringExpander;
import org.apache.ofbiz.entity.GenericEntity;
import org.apache.ofbiz.entity.GenericEntityException;
import org.apache.ofbiz.entity.util.EntityListIterator;
import org.apache.ofbiz.widget.WidgetWorker;
import org.apache.ofbiz.widget.model.AbstractModelAction;
import org.apache.ofbiz.widget.model.FieldInfo;
import org.apache.ofbiz.widget.model.ModelForm;
import org.apache.ofbiz.widget.model.ModelForm.FieldGroup;
import org.apache.ofbiz.widget.model.ModelForm.FieldGroupBase;
import org.apache.ofbiz.widget.model.ModelFormField;
import org.apache.ofbiz.widget.model.ModelGrid;
/**
* A form rendering engine.
*
*/
public class FormRenderer {
/*
* ----------------------------------------------------------------------- *
* DEVELOPERS PLEASE READ
* ----------------------------------------------------------------------- *
*
* An instance of this class is created by each thread for each form that
* is rendered. If you need to keep track of things while rendering, then
* this is the place to do it. In other words, feel free to modify this
* object's state (except for the final fields of course).
*
*/
public static final String module = FormRenderer.class.getName();
public static String getCurrentContainerId(ModelForm modelForm, Map<String, Object> context) {
Locale locale = UtilMisc.ensureLocale(context.get("locale"));
String retVal = FlexibleStringExpander.expandString(modelForm.getContainerId(), context, locale);
Integer itemIndex = (Integer) context.get("itemIndex");
if (itemIndex != null/* && "list".equals(modelForm.getType())*/) {
if (UtilValidate.isNotEmpty(context.get("parentItemIndex"))) {
return retVal + context.get("parentItemIndex") + modelForm.getItemIndexSeparator() + itemIndex.intValue();
}
return retVal + modelForm.getItemIndexSeparator() + itemIndex.intValue();
}
return retVal;
}
public static String getCurrentFormName(ModelForm modelForm, Map<String, Object> context) {
Integer itemIndex = (Integer) context.get("itemIndex");
String formName = (String) context.get("formName");
if (UtilValidate.isEmpty(formName)) {
formName = modelForm.getName();
}
if (itemIndex != null && "list".equals(modelForm.getType())) {
return formName + modelForm.getItemIndexSeparator() + itemIndex.intValue();
} else {
return formName;
}
}
public static String getFocusFieldName(ModelForm modelForm, Map<String, Object> context) {
String focusFieldName = (String) context.get(modelForm.getName().concat(".focusFieldName"));
if (focusFieldName == null) {
return "";
}
return focusFieldName;
}
private final ModelForm modelForm;
private final FormStringRenderer formStringRenderer;
private String focusFieldName;
public FormRenderer(ModelForm modelForm, FormStringRenderer formStringRenderer) {
this.modelForm = modelForm;
this.formStringRenderer = formStringRenderer;
this.focusFieldName = modelForm.getFocusFieldName();
}
private Collection<List<ModelFormField>> getFieldListsByPosition(List<ModelFormField> modelFormFieldList) {
Map<Integer, List<ModelFormField>> fieldsByPosition = new TreeMap<Integer, List<ModelFormField>>();
for (ModelFormField modelFormField : modelFormFieldList) {
Integer position = Integer.valueOf(modelFormField.getPosition());
List<ModelFormField> fieldListByPosition = fieldsByPosition.get(position);
if (fieldListByPosition == null) {
fieldListByPosition = new LinkedList<ModelFormField>();
fieldsByPosition.put(position, fieldListByPosition);
}
fieldListByPosition.add(modelFormField);
}
return fieldsByPosition.values();
}
public String getFocusFieldName() {
return focusFieldName;
}
private List<ModelFormField> getHiddenIgnoredFields(Map<String, Object> context, Set<String> alreadyRendered,
List<ModelFormField> fieldList, int position) {
/*
* Method does not reference internal state - should be moved to another class.
*/
List<ModelFormField> hiddenIgnoredFieldList = new LinkedList<ModelFormField>();
for (ModelFormField modelFormField : fieldList) {
// with position == -1 then gets all the hidden fields
if (position != -1 && modelFormField.getPosition() != position) {
continue;
}
FieldInfo fieldInfo = modelFormField.getFieldInfo();
// render hidden/ignored field widget
switch (fieldInfo.getFieldType()) {
case FieldInfo.HIDDEN:
case FieldInfo.IGNORED:
if (modelFormField.shouldUse(context)) {
hiddenIgnoredFieldList.add(modelFormField);
if (alreadyRendered != null)
alreadyRendered.add(modelFormField.getName());
}
break;
case FieldInfo.DISPLAY:
case FieldInfo.DISPLAY_ENTITY:
ModelFormField.DisplayField displayField = (ModelFormField.DisplayField) fieldInfo;
if (displayField.getAlsoHidden() && modelFormField.shouldUse(context)) {
hiddenIgnoredFieldList.add(modelFormField);
// don't add to already rendered here, or the display won't ger rendered: if (alreadyRendered != null) alreadyRendered.add(modelFormField.getName());
}
break;
case FieldInfo.HYPERLINK:
ModelFormField.HyperlinkField hyperlinkField = (ModelFormField.HyperlinkField) fieldInfo;
if (hyperlinkField.getAlsoHidden() && modelFormField.shouldUse(context)) {
hiddenIgnoredFieldList.add(modelFormField);
// don't add to already rendered here, or the hyperlink won't ger rendered: if (alreadyRendered != null) alreadyRendered.add(modelFormField.getName());
}
break;
}
}
return hiddenIgnoredFieldList;
}
private List<FieldGroupBase> getInbetweenList(FieldGroup startFieldGroup, FieldGroup endFieldGroup) {
List<FieldGroupBase> inbetweenList = new ArrayList<FieldGroupBase>();
boolean firstFound = false;
String startFieldGroupId = null;
String endFieldGroupId = null;
if (endFieldGroup != null) {
endFieldGroupId = endFieldGroup.getId();
}
if (startFieldGroup == null) {
firstFound = true;
} else {
startFieldGroupId = startFieldGroup.getId();
}
Iterator<FieldGroupBase> iter = modelForm.getFieldGroupList().iterator();
while (iter.hasNext()) {
FieldGroupBase obj = iter.next();
if (obj instanceof ModelForm.Banner) {
if (firstFound)
inbetweenList.add(obj);
} else {
FieldGroup fieldGroup = (FieldGroup) obj;
String fieldGroupId = fieldGroup.getId();
if (!firstFound) {
if (fieldGroupId.equals(startFieldGroupId)) {
firstFound = true;
continue;
}
}
if (firstFound) {
if (fieldGroupId.equals(endFieldGroupId)) {
break;
} else {
inbetweenList.add(fieldGroup);
}
}
}
}
return inbetweenList;
}
/**
* Renders this form to a writer, as defined with the
* FormStringRenderer implementation.
*
* @param writer The Writer that the form text will be written to
* @param context Map containing the form context; the following are
* reserved words in this context: parameters (Map), isError (Boolean),
* itemIndex (Integer, for lists only, otherwise null), formName
* (String, optional alternate name for form, defaults to the
* value of the name attribute)
*/
public void render(Appendable writer, Map<String, Object> context) throws Exception {
// increment the paginator, only for list and multi forms
if (modelForm instanceof ModelGrid) {
WidgetWorker.incrementPaginatorNumber(context);
}
// Populate the viewSize and viewIndex so they are available for use during form actions
context.put("viewIndex", Paginator.getViewIndex(modelForm, context));
context.put("viewSize", Paginator.getViewSize(modelForm, context));
modelForm.runFormActions(context);
// if this is a list form, don't use Request Parameters
if (modelForm instanceof ModelGrid) {
context.put("useRequestParameters", Boolean.FALSE);
}
// find the highest position number to get the max positions used
int positions = 1;
for (ModelFormField modelFormField : modelForm.getFieldList()) {
int curPos = modelFormField.getPosition();
if (curPos > positions) {
positions = curPos;
}
FieldInfo currentFieldInfo = modelFormField.getFieldInfo();
if (currentFieldInfo == null) {
throw new IllegalArgumentException(
"Error rendering form, a field has no FieldInfo, ie no sub-element for the type of field for field named: "
+ modelFormField.getName());
}
}
if ("single".equals(modelForm.getType())) {
this.renderSingleFormString(writer, context, positions);
} else if ("list".equals(modelForm.getType())) {
this.renderListFormString(writer, context, positions);
} else if ("multi".equals(modelForm.getType())) {
this.renderMultiFormString(writer, context, positions);
} else if ("upload".equals(modelForm.getType())) {
this.renderSingleFormString(writer, context, positions);
} else {
if (UtilValidate.isEmpty(modelForm.getType())) {
throw new IllegalArgumentException("The form 'type' tag is missing or empty on the form with the name "
+ modelForm.getName());
} else {
throw new IllegalArgumentException("The form type " + modelForm.getType()
+ " is not supported for form with name " + modelForm.getName());
}
}
}
private int renderHeaderRow(Appendable writer, Map<String, Object> context)
throws IOException {
int maxNumOfColumns = 0;
// We will render one title/column for all the fields with the same name
// in this model: we can have more fields with the same name when use-when
// conditions are used or when a form is extended or when the fields are
// automatically retrieved by a service or entity definition.
List<ModelFormField> tempFieldList = new LinkedList<ModelFormField>();
tempFieldList.addAll(modelForm.getFieldList());
for (int j = 0; j < tempFieldList.size(); j++) {
ModelFormField modelFormField = tempFieldList.get(j);
for (int i = j + 1; i < tempFieldList.size(); i++) {
ModelFormField curField = tempFieldList.get(i);
if (curField.getName() != null && curField.getName().equals(modelFormField.getName())) {
tempFieldList.remove(i--);
}
}
}
// ===========================
// Preprocessing
// ===========================
// We get a sorted (by position, ascending) set of lists;
// each list contains all the fields with that position.
Collection<List<ModelFormField>> fieldListsByPosition = this.getFieldListsByPosition(tempFieldList);
List<Map<String, List<ModelFormField>>> fieldRowsByPosition = new LinkedList<Map<String, List<ModelFormField>>>(); // this list will contain maps, each one containing the list of fields for a position
for (List<ModelFormField> mainFieldList : fieldListsByPosition) {
int numOfColumns = 0;
List<ModelFormField> innerDisplayHyperlinkFieldsBegin = new LinkedList<ModelFormField>();
List<ModelFormField> innerFormFields = new LinkedList<ModelFormField>();
List<ModelFormField> innerDisplayHyperlinkFieldsEnd = new LinkedList<ModelFormField>();
// render title for each field, except hidden & ignored, etc
// start by rendering all display and hyperlink fields, until we
//get to a field that should go into the form cell, then render
//the form cell with all non-display and non-hyperlink fields, then
//do a start after the first form input field and
//render all display and hyperlink fields after the form
// prepare the two lists of display and hyperlink fields
// the fields in the first list will be rendered as columns before the
// combined column for the input fields; the fields in the second list
// will be rendered as columns after it
boolean inputFieldFound = false;
for (ModelFormField modelFormField : mainFieldList) {
FieldInfo fieldInfo = modelFormField.getFieldInfo();
// if the field's title is explicitly set to "" (title="") then
// the header is not created for it; this is useful for position list
// where one line can be rendered with more than one row, and we
// only want to display the title header for the main row
String modelFormFieldTitle = modelFormField.getTitle(context);
if ("".equals(modelFormFieldTitle)) {
continue;
}
// don't do any header for hidden or ignored fields
if (fieldInfo.getFieldType() == FieldInfo.HIDDEN
|| fieldInfo.getFieldType() == FieldInfo.IGNORED) {
continue;
}
if (modelFormField.shouldIgnore(context)) {
continue;
}
if (FieldInfo.isInputFieldType(fieldInfo.getFieldType())) {
inputFieldFound = true;
continue;
}
// separate into two lists the display/hyperlink fields found before and after the first input fields
if (!inputFieldFound) {
innerDisplayHyperlinkFieldsBegin.add(modelFormField);
} else {
innerDisplayHyperlinkFieldsEnd.add(modelFormField);
}
numOfColumns++;
}
// prepare the combined title for the column that will contain the form/input fields
for (ModelFormField modelFormField : mainFieldList) {
FieldInfo fieldInfo = modelFormField.getFieldInfo();
// don't do any header for hidden or ignored fields
if (fieldInfo.getFieldType() == FieldInfo.HIDDEN
|| fieldInfo.getFieldType() == FieldInfo.IGNORED) {
continue;
}
// skip all of the display/hyperlink fields
if (!FieldInfo.isInputFieldType(fieldInfo.getFieldType())) {
continue;
}
innerFormFields.add(modelFormField);
}
if (innerFormFields.size() > 0) {
numOfColumns++;
}
if (maxNumOfColumns < numOfColumns) {
maxNumOfColumns = numOfColumns;
}
Map<String, List<ModelFormField>> fieldRow = UtilMisc.toMap("displayBefore", innerDisplayHyperlinkFieldsBegin,
"inputFields", innerFormFields, "displayAfter", innerDisplayHyperlinkFieldsEnd, "mainFieldList",
mainFieldList);
fieldRowsByPosition.add(fieldRow);
}
// ===========================
// Rendering
// ===========================
formStringRenderer.renderFormatHeaderOpen(writer, context, modelForm);
for (Map<String, List<ModelFormField>> listsMap : fieldRowsByPosition) {
List<ModelFormField> innerDisplayHyperlinkFieldsBegin = listsMap.get("displayBefore");
List<ModelFormField> innerFormFields = listsMap.get("inputFields");
List<ModelFormField> innerDisplayHyperlinkFieldsEnd = listsMap.get("displayAfter");
List<ModelFormField> mainFieldList = listsMap.get("mainFieldList");
int numOfCells = innerDisplayHyperlinkFieldsBegin.size() + innerDisplayHyperlinkFieldsEnd.size()
+ (innerFormFields.size() > 0 ? 1 : 0);
int numOfColumnsToSpan = maxNumOfColumns - numOfCells + 1;
if (numOfColumnsToSpan < 1) {
numOfColumnsToSpan = 1;
}
if (numOfCells > 0) {
formStringRenderer.renderFormatHeaderRowOpen(writer, context, modelForm);
if (modelForm.getGroupColumns()) {
Iterator<ModelFormField> innerDisplayHyperlinkFieldsBeginIt = innerDisplayHyperlinkFieldsBegin.iterator();
while (innerDisplayHyperlinkFieldsBeginIt.hasNext()) {
ModelFormField modelFormField = innerDisplayHyperlinkFieldsBeginIt.next();
// span columns only if this is the last column in the row (not just in this first list)
if (innerDisplayHyperlinkFieldsBeginIt.hasNext() || numOfCells > innerDisplayHyperlinkFieldsBegin.size()) {
formStringRenderer.renderFormatHeaderRowCellOpen(writer, context, modelForm, modelFormField, 1);
} else {
formStringRenderer.renderFormatHeaderRowCellOpen(writer, context, modelForm, modelFormField,
numOfColumnsToSpan);
}
formStringRenderer.renderFieldTitle(writer, context, modelFormField);
formStringRenderer.renderFormatHeaderRowCellClose(writer, context, modelForm, modelFormField);
}
if (innerFormFields.size() > 0) {
// TODO: manage colspan
formStringRenderer.renderFormatHeaderRowFormCellOpen(writer, context, modelForm);
Iterator<ModelFormField> innerFormFieldsIt = innerFormFields.iterator();
while (innerFormFieldsIt.hasNext()) {
ModelFormField modelFormField = innerFormFieldsIt.next();
if (modelForm.getSeparateColumns() || modelFormField.getSeparateColumn()) {
formStringRenderer.renderFormatItemRowCellOpen(writer, context, modelForm, modelFormField, 1);
}
// render title (unless this is a submit or a reset field)
formStringRenderer.renderFieldTitle(writer, context, modelFormField);
if (modelForm.getSeparateColumns() || modelFormField.getSeparateColumn()) {
formStringRenderer.renderFormatItemRowCellClose(writer, context, modelForm, modelFormField);
}
if (innerFormFieldsIt.hasNext()) {
// TODO: determine somehow if this is the last one... how?
if (!modelForm.getSeparateColumns() && !modelFormField.getSeparateColumn()) {
formStringRenderer.renderFormatHeaderRowFormCellTitleSeparator(writer, context, modelForm,
modelFormField, false);
}
}
}
formStringRenderer.renderFormatHeaderRowFormCellClose(writer, context, modelForm);
}
Iterator<ModelFormField> innerDisplayHyperlinkFieldsEndIt = innerDisplayHyperlinkFieldsEnd.iterator();
while (innerDisplayHyperlinkFieldsEndIt.hasNext()) {
ModelFormField modelFormField = innerDisplayHyperlinkFieldsEndIt.next();
// span columns only if this is the last column in the row (not just in this first list)
if (innerDisplayHyperlinkFieldsEndIt.hasNext() || numOfCells > innerDisplayHyperlinkFieldsEnd.size()) {
formStringRenderer.renderFormatHeaderRowCellOpen(writer, context, modelForm, modelFormField, 1);
} else {
formStringRenderer.renderFormatHeaderRowCellOpen(writer, context, modelForm, modelFormField,
numOfColumnsToSpan);
}
formStringRenderer.renderFieldTitle(writer, context, modelFormField);
formStringRenderer.renderFormatHeaderRowCellClose(writer, context, modelForm, modelFormField);
}
} else {
Iterator<ModelFormField> mainFieldListIter = mainFieldList.iterator();
while (mainFieldListIter.hasNext()) {
ModelFormField modelFormField = mainFieldListIter.next();
// don't do any header for hidden or ignored fields
FieldInfo fieldInfo = modelFormField.getFieldInfo();
if (fieldInfo.getFieldType() == FieldInfo.HIDDEN
|| fieldInfo.getFieldType() == FieldInfo.IGNORED) {
continue;
}
// span columns only if this is the last column in the row (not just in this first list)
if (mainFieldListIter.hasNext() || numOfCells > mainFieldList.size()) {
formStringRenderer.renderFormatHeaderRowCellOpen(writer, context, modelForm, modelFormField, 1);
} else {
formStringRenderer.renderFormatHeaderRowCellOpen(writer, context, modelForm, modelFormField,
numOfColumnsToSpan);
}
formStringRenderer.renderFieldTitle(writer, context, modelFormField);
formStringRenderer.renderFormatHeaderRowCellClose(writer, context, modelForm, modelFormField);
}
}
formStringRenderer.renderFormatHeaderRowClose(writer, context, modelForm);
}
}
formStringRenderer.renderFormatHeaderClose(writer, context, modelForm);
return maxNumOfColumns;
}
private void renderHiddenIgnoredFields(Appendable writer, Map<String, Object> context, FormStringRenderer formStringRenderer,
List<ModelFormField> fieldList) throws IOException {
for (ModelFormField modelFormField : fieldList) {
FieldInfo fieldInfo = modelFormField.getFieldInfo();
// render hidden/ignored field widget
switch (fieldInfo.getFieldType()) {
case FieldInfo.HIDDEN:
case FieldInfo.IGNORED:
modelFormField.renderFieldString(writer, context, formStringRenderer);
break;
case FieldInfo.DISPLAY:
case FieldInfo.DISPLAY_ENTITY:
case FieldInfo.HYPERLINK:
formStringRenderer.renderHiddenField(writer, context, modelFormField, modelFormField.getEntry(context));
break;
}
}
}
// The fields in the three lists, usually created in the preprocessing phase
// of the renderItemRows method are rendered: this will create a visual representation
// of one row (corresponding to one position).
private void renderItemRow(Appendable writer, Map<String, Object> localContext, FormStringRenderer formStringRenderer,
boolean formPerItem, List<ModelFormField> hiddenIgnoredFieldList,
List<ModelFormField> innerDisplayHyperlinkFieldsBegin, List<ModelFormField> innerFormFields,
List<ModelFormField> innerDisplayHyperlinkFieldsEnd, List<ModelFormField> mainFieldList, int position,
int numOfColumns) throws IOException {
int numOfCells = innerDisplayHyperlinkFieldsBegin.size() + innerDisplayHyperlinkFieldsEnd.size()
+ (innerFormFields.size() > 0 ? 1 : 0);
int numOfColumnsToSpan = numOfColumns - numOfCells + 1;
if (numOfColumnsToSpan < 1) {
numOfColumnsToSpan = 1;
}
// render row formatting open
formStringRenderer.renderFormatItemRowOpen(writer, localContext, modelForm);
Iterator<ModelFormField> innerDisplayHyperlinkFieldsBeginIter = innerDisplayHyperlinkFieldsBegin.iterator();
Map<String, Integer> fieldCount = new HashMap<String, Integer>();
while (innerDisplayHyperlinkFieldsBeginIter.hasNext()) {
ModelFormField modelFormField = innerDisplayHyperlinkFieldsBeginIter.next();
if (fieldCount.containsKey(modelFormField.getFieldName())) {
fieldCount.put(modelFormField.getFieldName(), fieldCount.get(modelFormField.getFieldName()) + 1);
} else {
fieldCount.put(modelFormField.getFieldName(), 1);
}
}
if (modelForm.getGroupColumns()) {
// do the first part of display and hyperlink fields
Iterator<ModelFormField> innerDisplayHyperlinkFieldIter = innerDisplayHyperlinkFieldsBegin.iterator();
while (innerDisplayHyperlinkFieldIter.hasNext()) {
boolean cellOpen = false;
ModelFormField modelFormField = innerDisplayHyperlinkFieldIter.next();
if(modelFormField.shouldIgnore(localContext)) {
continue;
}
// span columns only if this is the last column in the row (not just in this first list)
if (fieldCount.get(modelFormField.getName()) < 2) {
if ((innerDisplayHyperlinkFieldIter.hasNext() || numOfCells > innerDisplayHyperlinkFieldsBegin.size())) {
formStringRenderer.renderFormatItemRowCellOpen(writer, localContext, modelForm, modelFormField, 1);
} else {
formStringRenderer.renderFormatItemRowCellOpen(writer, localContext, modelForm, modelFormField,
numOfColumnsToSpan);
}
cellOpen = true;
}
if ((!"list".equals(modelForm.getType()) && !"multi".equals(modelForm.getType()))
|| modelFormField.shouldUse(localContext)) {
if ((fieldCount.get(modelFormField.getName()) > 1)) {
if ((innerDisplayHyperlinkFieldIter.hasNext() || numOfCells > innerDisplayHyperlinkFieldsBegin.size())) {
formStringRenderer.renderFormatItemRowCellOpen(writer, localContext, modelForm, modelFormField, 1);
} else {
formStringRenderer.renderFormatItemRowCellOpen(writer, localContext, modelForm, modelFormField,
numOfColumnsToSpan);
}
cellOpen = true;
}
modelFormField.renderFieldString(writer, localContext, formStringRenderer);
}
if (cellOpen) {
formStringRenderer.renderFormatItemRowCellClose(writer, localContext, modelForm, modelFormField);
}
}
// The form cell is rendered only if there is at least an input field
if (innerFormFields.size() > 0) {
// render the "form" cell
formStringRenderer.renderFormatItemRowFormCellOpen(writer, localContext, modelForm); // TODO: colspan
if (formPerItem) {
formStringRenderer.renderFormOpen(writer, localContext, modelForm);
}
// do all of the hidden fields...
this.renderHiddenIgnoredFields(writer, localContext, formStringRenderer, hiddenIgnoredFieldList);
Iterator<ModelFormField> innerFormFieldIter = innerFormFields.iterator();
while (innerFormFieldIter.hasNext()) {
ModelFormField modelFormField = innerFormFieldIter.next();
if (modelForm.getSeparateColumns() || modelFormField.getSeparateColumn()) {
formStringRenderer.renderFormatItemRowCellOpen(writer, localContext, modelForm, modelFormField, 1);
}
// render field widget
if ((!"list".equals(modelForm.getType()) && !"multi".equals(modelForm.getType()))
|| modelFormField.shouldUse(localContext)) {
modelFormField.renderFieldString(writer, localContext, formStringRenderer);
}
if (modelForm.getSeparateColumns() || modelFormField.getSeparateColumn()) {
formStringRenderer.renderFormatItemRowCellClose(writer, localContext, modelForm, modelFormField);
}
}
if (formPerItem) {
formStringRenderer.renderFormClose(writer, localContext, modelForm);
}
formStringRenderer.renderFormatItemRowFormCellClose(writer, localContext, modelForm);
}
// render the rest of the display/hyperlink fields
innerDisplayHyperlinkFieldIter = innerDisplayHyperlinkFieldsEnd.iterator();
while (innerDisplayHyperlinkFieldIter.hasNext()) {
ModelFormField modelFormField = innerDisplayHyperlinkFieldIter.next();
// span columns only if this is the last column in the row
if (innerDisplayHyperlinkFieldIter.hasNext()) {
formStringRenderer.renderFormatItemRowCellOpen(writer, localContext, modelForm, modelFormField, 1);
} else {
formStringRenderer.renderFormatItemRowCellOpen(writer, localContext, modelForm, modelFormField,
numOfColumnsToSpan);
}
if ((!"list".equals(modelForm.getType()) && !"multi".equals(modelForm.getType()))
|| modelFormField.shouldUse(localContext)) {
modelFormField.renderFieldString(writer, localContext, formStringRenderer);
}
formStringRenderer.renderFormatItemRowCellClose(writer, localContext, modelForm, modelFormField);
}
} else {
// do all of the hidden fields...
this.renderHiddenIgnoredFields(writer, localContext, formStringRenderer, hiddenIgnoredFieldList);
Iterator<ModelFormField> mainFieldIter = mainFieldList.iterator();
while (mainFieldIter.hasNext()) {
ModelFormField modelFormField = mainFieldIter.next();
// don't do any header for hidden or ignored fields inside this loop
FieldInfo fieldInfo = modelFormField.getFieldInfo();
if (fieldInfo.getFieldType() == FieldInfo.HIDDEN
|| fieldInfo.getFieldType() == FieldInfo.IGNORED) {
continue;
}
// span columns only if this is the last column in the row
if (mainFieldIter.hasNext()) {
formStringRenderer.renderFormatItemRowCellOpen(writer, localContext, modelForm, modelFormField, 1);
} else {
formStringRenderer.renderFormatItemRowCellOpen(writer, localContext, modelForm, modelFormField,
numOfColumnsToSpan);
}
if ((!"list".equals(modelForm.getType()) && !"multi".equals(modelForm.getType()))
|| modelFormField.shouldUse(localContext)) {
modelFormField.renderFieldString(writer, localContext, formStringRenderer);
}
formStringRenderer.renderFormatItemRowCellClose(writer, localContext, modelForm, modelFormField);
}
}
// render row formatting close
formStringRenderer.renderFormatItemRowClose(writer, localContext, modelForm);
}
private void renderItemRows(Appendable writer, Map<String, Object> context, FormStringRenderer formStringRenderer,
boolean formPerItem, int numOfColumns) throws IOException {
String lookupName = modelForm.getListName();
if (UtilValidate.isEmpty(lookupName)) {
Debug.logError("No value for list or iterator name found.", module);
return;
}
Object obj = context.get(lookupName);
if (obj == null) {
if (Debug.verboseOn())
Debug.logVerbose("No object for list or iterator name [" + lookupName + "] found, so not rendering rows.", module);
return;
}
// if list is empty, do not render rows
Iterator<?> iter = null;
if (obj instanceof Iterator<?>) {
iter = (Iterator<?>) obj;
} else if (obj instanceof List<?>) {
iter = ((List<?>) obj).listIterator();
}
// set low and high index
Paginator.getListLimits(modelForm, context, obj);
int listSize = ((Integer) context.get("listSize")).intValue();
int lowIndex = ((Integer) context.get("lowIndex")).intValue();
int highIndex = ((Integer) context.get("highIndex")).intValue();
// we're passed a subset of the list, so use (0, viewSize) range
if (modelForm.isOverridenListSize()) {
lowIndex = 0;
highIndex = ((Integer) context.get("viewSize")).intValue();
}
if (iter != null) {
// render item rows
if (UtilValidate.isNotEmpty(context.get("itemIndex"))) {
if (UtilValidate.isNotEmpty(context.get("parentItemIndex"))) {
context.put("parentItemIndex", context.get("parentItemIndex") + modelForm.getItemIndexSeparator() + context.get("itemIndex"));
} else {
context.put("parentItemIndex", modelForm.getItemIndexSeparator() + context.get("itemIndex"));
}
}
int itemIndex = -1;
Object item = null;
context.put("wholeFormContext", context);
Map<String, Object> previousItem = new HashMap<String, Object>();
while ((item = safeNext(iter)) != null) {
itemIndex++;
if (itemIndex >= highIndex) {
break;
}
// TODO: this is a bad design, for EntityListIterators we should skip to the lowIndex and go from there, MUCH more efficient...
if (itemIndex < lowIndex) {
continue;
}
Map<String, Object> itemMap = UtilGenerics.checkMap(item);
MapStack<String> localContext = MapStack.create(context);
if (UtilValidate.isNotEmpty(modelForm.getListEntryName())) {
localContext.put(modelForm.getListEntryName(), item);
} else {
if (itemMap instanceof GenericEntity) {
// Rendering code might try to modify the GenericEntity instance,
// so we make a copy of it.
Map<String, Object> genericEntityClone = UtilGenerics.cast(((GenericEntity) itemMap).clone());
localContext.push(genericEntityClone);
} else {
localContext.push(itemMap);
}
}
localContext.push();
localContext.put("previousItem", previousItem);
previousItem = new HashMap<String, Object>();
previousItem.putAll(itemMap);
AbstractModelAction.runSubActions(modelForm.getRowActions(), localContext);
localContext.put("itemIndex", Integer.valueOf(itemIndex - lowIndex));
if (UtilValidate.isNotEmpty(context.get("renderFormSeqNumber"))) {
localContext.put("formUniqueId", "_" + context.get("renderFormSeqNumber"));
}
if (Debug.verboseOn())
Debug.logVerbose("In form got another row, context is: " + localContext, module);
// Check to see if there is a field, same name and same use-when (could come from extended form)
List<ModelFormField> tempFieldList = new LinkedList<ModelFormField>();
tempFieldList.addAll(modelForm.getFieldList());
for (int j = 0; j < tempFieldList.size(); j++) {
ModelFormField modelFormField = tempFieldList.get(j);
if (!modelFormField.isUseWhenEmpty()) {
boolean shouldUse1 = modelFormField.shouldUse(localContext);
for (int i = j + 1; i < tempFieldList.size(); i++) {
ModelFormField curField = tempFieldList.get(i);
if (curField.getName() != null && curField.getName().equals(modelFormField.getName())) {
boolean shouldUse2 = curField.shouldUse(localContext);
if (shouldUse1 == shouldUse2) {
tempFieldList.remove(i--);
}
} else {
continue;
}
}
}
}
// Each single item is rendered in one or more rows if its fields have
// different "position" attributes. All the fields with the same position
// are rendered in the same row.
// The default position is 1, and represents the main row:
// it contains the fields that are in the list header (columns).
// The positions lower than 1 are rendered in rows before the main one;
// positions higher than 1 are rendered after the main one.
// We get a sorted (by position, ascending) set of lists;
// each list contains all the fields with that position.
Collection<List<ModelFormField>> fieldListsByPosition = this.getFieldListsByPosition(tempFieldList);
//List hiddenIgnoredFieldList = getHiddenIgnoredFields(localContext, null, tempFieldList);
for (List<ModelFormField> fieldListByPosition : fieldListsByPosition) {
// For each position (the subset of fields with the same position attribute)
// we have two phases: preprocessing and rendering
List<ModelFormField> innerDisplayHyperlinkFieldsBegin = new LinkedList<ModelFormField>();
List<ModelFormField> innerFormFields = new LinkedList<ModelFormField>();
List<ModelFormField> innerDisplayHyperlinkFieldsEnd = new LinkedList<ModelFormField>();
// Preprocessing:
// all the form fields are evaluated and the ones that will
// appear in the form are put into three separate lists:
// - hyperlink fields that will appear at the beginning of the row
// - fields of other types
// - hyperlink fields that will appear at the end of the row
Iterator<ModelFormField> innerDisplayHyperlinkFieldIter = fieldListByPosition.iterator();
int currentPosition = 1;
while (innerDisplayHyperlinkFieldIter.hasNext()) {
ModelFormField modelFormField = innerDisplayHyperlinkFieldIter.next();
FieldInfo fieldInfo = modelFormField.getFieldInfo();
// don't do any header for hidden or ignored fields
if (fieldInfo.getFieldType() == FieldInfo.HIDDEN
|| fieldInfo.getFieldType() == FieldInfo.IGNORED) {
continue;
}
if (FieldInfo.isInputFieldType(fieldInfo.getFieldType())) {
// okay, now do the form cell
break;
}
// if this is a list or multi form don't skip here because we don't want to skip the table cell, will skip the actual field later
if (!"list".equals(modelForm.getType()) && !"multi".equals(modelForm.getType())
&& !modelFormField.shouldUse(localContext)) {
continue;
}
innerDisplayHyperlinkFieldsBegin.add(modelFormField);
currentPosition = modelFormField.getPosition();
}
Iterator<ModelFormField> innerFormFieldIter = fieldListByPosition.iterator();
while (innerFormFieldIter.hasNext()) {
ModelFormField modelFormField = innerFormFieldIter.next();
FieldInfo fieldInfo = modelFormField.getFieldInfo();
// don't do any header for hidden or ignored fields
if (fieldInfo.getFieldType() == FieldInfo.HIDDEN
|| fieldInfo.getFieldType() == FieldInfo.IGNORED) {
continue;
}
// skip all of the display/hyperlink fields
if (!FieldInfo.isInputFieldType(fieldInfo.getFieldType())) {
continue;
}
// if this is a list or multi form don't skip here because we don't want to skip the table cell, will skip the actual field later
if (!"list".equals(modelForm.getType()) && !"multi".equals(modelForm.getType())
&& !modelFormField.shouldUse(localContext)) {
continue;
}
innerFormFields.add(modelFormField);
currentPosition = modelFormField.getPosition();
}
while (innerDisplayHyperlinkFieldIter.hasNext()) {
ModelFormField modelFormField = innerDisplayHyperlinkFieldIter.next();
FieldInfo fieldInfo = modelFormField.getFieldInfo();
// don't do any header for hidden or ignored fields
if (fieldInfo.getFieldType() == FieldInfo.HIDDEN
|| fieldInfo.getFieldType() == FieldInfo.IGNORED) {
continue;
}
// skip all non-display and non-hyperlink fields
if (FieldInfo.isInputFieldType(fieldInfo.getFieldType())) {
continue;
}
// if this is a list or multi form don't skip here because we don't want to skip the table cell, will skip the actual field later
if (!"list".equals(modelForm.getType()) && !"multi".equals(modelForm.getType())
&& !modelFormField.shouldUse(localContext)) {
continue;
}
innerDisplayHyperlinkFieldsEnd.add(modelFormField);
currentPosition = modelFormField.getPosition();
}
List<ModelFormField> hiddenIgnoredFieldList = getHiddenIgnoredFields(localContext, null, tempFieldList,
currentPosition);
// Rendering:
// the fields in the three lists created in the preprocessing phase
// are now rendered: this will create a visual representation
// of one row (for the current position).
if (innerDisplayHyperlinkFieldsBegin.size() > 0 || innerFormFields.size() > 0
|| innerDisplayHyperlinkFieldsEnd.size() > 0) {
this.renderItemRow(writer, localContext, formStringRenderer, formPerItem, hiddenIgnoredFieldList,
innerDisplayHyperlinkFieldsBegin, innerFormFields, innerDisplayHyperlinkFieldsEnd,
fieldListByPosition, currentPosition, numOfColumns);
}
} // iteration on positions
} // iteration on items
// reduce the highIndex if number of items falls short
if ((itemIndex + 1) < highIndex) {
highIndex = itemIndex + 1;
// if list size is overridden, use full listSize
context.put("highIndex", Integer.valueOf(modelForm.isOverridenListSize() ? listSize : highIndex));
}
context.put("actualPageSize", Integer.valueOf(highIndex - lowIndex));
if (iter instanceof EntityListIterator) {
try {
((EntityListIterator) iter).close();
} catch (GenericEntityException e) {
Debug.logError(e, "Error closing list form render EntityListIterator: " + e.toString(), module);
}
}
}
}
private void renderListFormString(Appendable writer, Map<String, Object> context,
int positions) throws IOException {
// render list/tabular type forms
// prepare the items iterator and compute the pagination parameters
Paginator.preparePager(modelForm, context);
// render formatting wrapper open
formStringRenderer.renderFormatListWrapperOpen(writer, context, modelForm);
int numOfColumns = 0;
// ===== render header row =====
if (!modelForm.getHideHeader()) {
numOfColumns = this.renderHeaderRow(writer, context);
}
// ===== render the item rows =====
this.renderItemRows(writer, context, formStringRenderer, true, numOfColumns);
// render formatting wrapper close
formStringRenderer.renderFormatListWrapperClose(writer, context, modelForm);
}
private void renderMultiFormString(Appendable writer, Map<String, Object> context,
int positions) throws IOException {
if (!modelForm.getSkipStart()) {
formStringRenderer.renderFormOpen(writer, context, modelForm);
}
// prepare the items iterator and compute the pagination parameters
Paginator.preparePager(modelForm, context);
// render formatting wrapper open
formStringRenderer.renderFormatListWrapperOpen(writer, context, modelForm);
int numOfColumns = 0;
// ===== render header row =====
if (!modelForm.getHideHeader()) {
numOfColumns = this.renderHeaderRow(writer, context);
}
// ===== render the item rows =====
this.renderItemRows(writer, context, formStringRenderer, false, numOfColumns);
formStringRenderer.renderFormatListWrapperClose(writer, context, modelForm);
if (!modelForm.getSkipEnd()) {
formStringRenderer.renderMultiFormClose(writer, context, modelForm);
}
}
private void renderSingleFormString(Appendable writer, Map<String, Object> context,
int positions) throws IOException {
List<ModelFormField> tempFieldList = new LinkedList<ModelFormField>();
tempFieldList.addAll(modelForm.getFieldList());
// Check to see if there is a field, same name and same use-when (could come from extended form)
for (int j = 0; j < tempFieldList.size(); j++) {
ModelFormField modelFormField = tempFieldList.get(j);
if (modelForm.getUseWhenFields().contains(modelFormField.getName())) {
boolean shouldUse1 = modelFormField.shouldUse(context);
for (int i = j + 1; i < tempFieldList.size(); i++) {
ModelFormField curField = tempFieldList.get(i);
if (curField.getName() != null && curField.getName().equals(modelFormField.getName())) {
boolean shouldUse2 = curField.shouldUse(context);
if (shouldUse1 == shouldUse2) {
tempFieldList.remove(i--);
}
} else {
continue;
}
}
}
}
Set<String> alreadyRendered = new TreeSet<String>();
FieldGroup lastFieldGroup = null;
// render form open
if (!modelForm.getSkipStart())
formStringRenderer.renderFormOpen(writer, context, modelForm);
// render all hidden & ignored fields
List<ModelFormField> hiddenIgnoredFieldList = this.getHiddenIgnoredFields(context, alreadyRendered, tempFieldList, -1);
this.renderHiddenIgnoredFields(writer, context, formStringRenderer, hiddenIgnoredFieldList);
// render formatting wrapper open
// This should be covered by fieldGroup.renderStartString
//formStringRenderer.renderFormatSingleWrapperOpen(writer, context, this);
// render each field row, except hidden & ignored rows
Iterator<ModelFormField> fieldIter = tempFieldList.iterator();
ModelFormField lastFormField = null;
ModelFormField currentFormField = null;
ModelFormField nextFormField = null;
if (fieldIter.hasNext()) {
currentFormField = fieldIter.next();
}
if (fieldIter.hasNext()) {
nextFormField = fieldIter.next();
}
FieldGroup currentFieldGroup = null;
String currentFieldGroupName = null;
String lastFieldGroupName = null;
if (currentFormField != null) {
currentFieldGroup = (FieldGroup) modelForm.getFieldGroupMap().get(currentFormField.getFieldName());
if (currentFieldGroup == null) {
currentFieldGroup = modelForm.getDefaultFieldGroup();
}
if (currentFieldGroup != null) {
currentFieldGroupName = currentFieldGroup.getId();
}
}
boolean isFirstPass = true;
boolean haveRenderedOpenFieldRow = false;
while (currentFormField != null) {
// do the check/get next stuff at the beginning so we can still use the continue stuff easily
// don't do it on the first pass though...
if (isFirstPass) {
isFirstPass = false;
List<FieldGroupBase> inbetweenList = getInbetweenList(lastFieldGroup, currentFieldGroup);
for (FieldGroupBase obj : inbetweenList) {
if (obj instanceof ModelForm.Banner) {
((ModelForm.Banner) obj).renderString(writer, context, formStringRenderer);
}
}
if (currentFieldGroup != null && (lastFieldGroup == null || !lastFieldGroupName.equals(currentFieldGroupName))) {
currentFieldGroup.renderStartString(writer, context, formStringRenderer);
lastFieldGroup = currentFieldGroup;
}
} else {
if (fieldIter.hasNext()) {
// at least two loops left
lastFormField = currentFormField;
currentFormField = nextFormField;
nextFormField = fieldIter.next();
} else if (nextFormField != null) {
// okay, just one loop left
lastFormField = currentFormField;
currentFormField = nextFormField;
nextFormField = null;
} else {
// at the end...
lastFormField = currentFormField;
currentFormField = null;
// nextFormField is already null
break;
}
currentFieldGroup = null;
if (currentFormField != null) {
currentFieldGroup = (FieldGroup) modelForm.getFieldGroupMap().get(currentFormField.getName());
}
if (currentFieldGroup == null) {
currentFieldGroup = modelForm.getDefaultFieldGroup();
}
currentFieldGroupName = currentFieldGroup.getId();
if (lastFieldGroup != null) {
lastFieldGroupName = lastFieldGroup.getId();
if (!lastFieldGroupName.equals(currentFieldGroupName)) {
if (haveRenderedOpenFieldRow) {
formStringRenderer.renderFormatFieldRowClose(writer, context, modelForm);
haveRenderedOpenFieldRow = false;
}
lastFieldGroup.renderEndString(writer, context, formStringRenderer);
List<FieldGroupBase> inbetweenList = getInbetweenList(lastFieldGroup, currentFieldGroup);
for (FieldGroupBase obj : inbetweenList) {
if (obj instanceof ModelForm.Banner) {
((ModelForm.Banner) obj).renderString(writer, context, formStringRenderer);
}
}
}
}
if (lastFieldGroup == null || !lastFieldGroupName.equals(currentFieldGroupName)) {
currentFieldGroup.renderStartString(writer, context, formStringRenderer);
lastFieldGroup = currentFieldGroup;
}
}
FieldInfo fieldInfo = currentFormField.getFieldInfo();
if (fieldInfo.getFieldType() == FieldInfo.HIDDEN
|| fieldInfo.getFieldType() == FieldInfo.IGNORED) {
continue;
}
if (alreadyRendered.contains(currentFormField.getName())) {
continue;
}
//Debug.logInfo("In single form evaluating use-when for field " + currentFormField.getName() + ": " + currentFormField.getUseWhen(), module);
if (!currentFormField.shouldUse(context)) {
if (UtilValidate.isNotEmpty(lastFormField)) {
currentFormField = lastFormField;
}
continue;
}
alreadyRendered.add(currentFormField.getName());
if (focusFieldName.isEmpty()) {
if (fieldInfo.getFieldType() != FieldInfo.DISPLAY && fieldInfo.getFieldType() != FieldInfo.HIDDEN
&& fieldInfo.getFieldType() != FieldInfo.DISPLAY_ENTITY
&& fieldInfo.getFieldType() != FieldInfo.IGNORED
&& fieldInfo.getFieldType() != FieldInfo.IMAGE) {
focusFieldName = currentFormField.getName();
context.put(modelForm.getName().concat(".focusFieldName"), focusFieldName);
}
}
boolean stayingOnRow = false;
if (lastFormField != null) {
if (lastFormField.getPosition() >= currentFormField.getPosition()) {
// moving to next row
stayingOnRow = false;
} else {
// staying on same row
stayingOnRow = true;
}
}
int positionSpan = 1;
Integer nextPositionInRow = null;
if (nextFormField != null) {
if (nextFormField.getPosition() > currentFormField.getPosition()) {
positionSpan = nextFormField.getPosition() - currentFormField.getPosition() - 1;
nextPositionInRow = Integer.valueOf(nextFormField.getPosition());
} else {
positionSpan = positions - currentFormField.getPosition();
if (!stayingOnRow && nextFormField.getPosition() > 1) {
// TODO: here is a weird case where it is setup such
//that the first position(s) in the row are skipped
// not sure what to do about this right now...
}
}
}
if (stayingOnRow) {
// no spacer cell, might add later though...
//formStringRenderer.renderFormatFieldRowSpacerCell(writer, context, currentFormField);
} else {
if (haveRenderedOpenFieldRow) {
// render row formatting close
formStringRenderer.renderFormatFieldRowClose(writer, context, modelForm);
haveRenderedOpenFieldRow = false;
}
// render row formatting open
formStringRenderer.renderFormatFieldRowOpen(writer, context, modelForm);
haveRenderedOpenFieldRow = true;
}
//
// It must be a row open before rendering a field. If not, open it
//
if (!haveRenderedOpenFieldRow) {
formStringRenderer.renderFormatFieldRowOpen(writer, context, modelForm);
haveRenderedOpenFieldRow = true;
}
// render title formatting open
formStringRenderer.renderFormatFieldRowTitleCellOpen(writer, context, currentFormField);
// render title (unless this is a submit or a reset field)
if (fieldInfo.getFieldType() != FieldInfo.SUBMIT
&& fieldInfo.getFieldType() != FieldInfo.RESET) {
formStringRenderer.renderFieldTitle(writer, context, currentFormField);
} else {
formStringRenderer.renderFormatEmptySpace(writer, context, modelForm);
}
// render title formatting close
formStringRenderer.renderFormatFieldRowTitleCellClose(writer, context, currentFormField);
// render separator
formStringRenderer.renderFormatFieldRowSpacerCell(writer, context, currentFormField);
// render widget formatting open
formStringRenderer.renderFormatFieldRowWidgetCellOpen(writer, context, currentFormField, positions, positionSpan,
nextPositionInRow);
// render widget
currentFormField.renderFieldString(writer, context, formStringRenderer);
// render widget formatting close
formStringRenderer.renderFormatFieldRowWidgetCellClose(writer, context, currentFormField, positions, positionSpan,
nextPositionInRow);
}
// render row formatting close after the end if needed
if (haveRenderedOpenFieldRow) {
formStringRenderer.renderFormatFieldRowClose(writer, context, modelForm);
}
if (lastFieldGroup != null) {
lastFieldGroup.renderEndString(writer, context, formStringRenderer);
}
// render formatting wrapper close
// should be handled by renderEndString
//formStringRenderer.renderFormatSingleWrapperClose(writer, context, this);
// render form close
if (!modelForm.getSkipEnd())
formStringRenderer.renderFormClose(writer, context, modelForm);
}
private static <X> X safeNext(Iterator<X> iterator) {
try {
return iterator.next();
} catch (NoSuchElementException e) {
return null;
}
}
}