You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@ofbiz.apache.org by ad...@apache.org on 2014/11/03 13:42:03 UTC

svn commit: r1636339 [2/3] - in /ofbiz/trunk/framework/widget/src/org/ofbiz/widget: form/FormFactory.java form/MacroFormRenderer.java form/ModelForm.java form/ModelFormAction.java html/HtmlFormRenderer.java tree/ModelTree.java

Modified: ofbiz/trunk/framework/widget/src/org/ofbiz/widget/form/ModelForm.java
URL: http://svn.apache.org/viewvc/ofbiz/trunk/framework/widget/src/org/ofbiz/widget/form/ModelForm.java?rev=1636339&r1=1636338&r2=1636339&view=diff
==============================================================================
--- ofbiz/trunk/framework/widget/src/org/ofbiz/widget/form/ModelForm.java (original)
+++ ofbiz/trunk/framework/widget/src/org/ofbiz/widget/form/ModelForm.java Mon Nov  3 12:42:03 2014
@@ -21,6 +21,7 @@ package org.ofbiz.widget.form;
 import java.io.IOException;
 import java.util.ArrayList;
 import java.util.Collection;
+import java.util.Collections;
 import java.util.HashMap;
 import java.util.HashSet;
 import java.util.Iterator;
@@ -32,6 +33,7 @@ import java.util.NoSuchElementException;
 import java.util.Set;
 import java.util.TreeMap;
 import java.util.TreeSet;
+import java.util.concurrent.atomic.AtomicInteger;
 
 import org.ofbiz.base.util.BshUtil;
 import org.ofbiz.base.util.Debug;
@@ -64,114 +66,36 @@ import bsh.EvalError;
 import bsh.Interpreter;
 
 /**
- * Widget Library - Form model class
+ * Models the <form> element.
+ * 
+ * @see <code>widget-form.xsd</code>
  */
 @SuppressWarnings("serial")
 public class ModelForm extends ModelWidget {
 
-    public static final String module = ModelForm.class.getName();
-    public static final String DEFAULT_FORM_RESULT_LIST_NAME = "defaultFormResultList";
-
-    protected String formLocation;
-    protected String parentFormName;
-    protected String parentFormLocation;
-    protected ModelForm parentModelForm;
-    protected String type;
-    protected FlexibleStringExpander target;
-    protected String targetType;
-    protected String containerId;
-    protected String containerStyle;
-    protected String focusFieldName;
-    protected String title;
-    protected String tooltip;
-    protected String listName;
-    protected String listEntryName;
-    protected FlexibleMapAccessor<Map<String, ? extends Object>> defaultMapName;
-    protected String defaultEntityName;
-    protected String defaultServiceName;
-    protected String formTitleAreaStyle;
-    protected String formWidgetAreaStyle;
-    protected String defaultTitleAreaStyle;
-    protected String defaultWidgetAreaStyle;
-    protected String defaultTitleStyle;
-    protected String defaultWidgetStyle;
-    protected String defaultTooltipStyle;
-    protected String itemIndexSeparator;
-    protected FlexibleStringExpander paginate;
-    protected FlexibleStringExpander paginateTarget;
-    protected FlexibleStringExpander paginateIndexField;
-    protected FlexibleStringExpander paginateSizeField;
-    protected FlexibleStringExpander overrideListSize;
-    protected FlexibleStringExpander paginateFirstLabel;
-    protected FlexibleStringExpander paginatePreviousLabel;
-    protected FlexibleStringExpander paginateNextLabel;
-    protected FlexibleStringExpander paginateLastLabel;
-    protected FlexibleStringExpander paginateViewSizeLabel;
-    protected String paginateTargetAnchor;
-    protected String paginateStyle;
-    protected boolean separateColumns = false;
-    protected boolean groupColumns = true;
-    protected boolean useRowSubmit = false;
-    protected FlexibleStringExpander targetWindowExdr;
-    protected String defaultRequiredFieldStyle;
-    protected String defaultSortFieldStyle;
-    protected String defaultSortFieldAscStyle;
-    protected String defaultSortFieldDescStyle;
-    protected String oddRowStyle;
-    protected String evenRowStyle;
-    protected String defaultTableStyle;
-    protected String headerRowStyle;
-    protected boolean skipStart = false;
-    protected boolean skipEnd = false;
-    protected boolean hideHeader = false;
-    protected boolean overridenListSize = false;
-    protected boolean clientAutocompleteFields = true;
-
-    protected List<AltTarget> altTargets = new ArrayList<AltTarget>();
-    protected List<AutoFieldsService> autoFieldsServices = new ArrayList<AutoFieldsService>();
-    protected List<AutoFieldsEntity> autoFieldsEntities = new ArrayList<AutoFieldsEntity>();
-    protected List<String> lastOrderFields = new ArrayList<String>();
-    protected List<SortField> sortOrderFields = new ArrayList<SortField>();
-    protected List<AltRowStyle> altRowStyles = new ArrayList<AltRowStyle>();
-
-    /** This List will contain one copy of each field for each field name in the order
-     * they were encountered in the service, entity, or form definition; field definitions
-     * with constraints will also be in this list but may appear multiple times for the same
-     * field name.
-     *
-     * When rendering the form the order in this list should be following and it should not be
-     * necessary to use the Map. The Map is used when loading the form definition to keep the
-     * list clean and implement the override features for field definitions.
-     */
-    protected List<ModelFormField> fieldList = new ArrayList<ModelFormField>();
-
-    /** This Map is keyed with the field name and has a ModelFormField for the value.
-     */
-    protected Map<String, ModelFormField> fieldMap = new HashMap<String, ModelFormField>();
-
-    /** Keeps track of conditional fields to help ensure that only one is rendered
-     */
-    protected Set<String> useWhenFields = new HashSet<String>();
-
-    /** This is a list of FieldGroups in the order they were created.
-     * Can also include Banner objects.
-     */
-    protected List<FieldGroupBase> fieldGroupList = new ArrayList<FieldGroupBase>();
-
-    /** This Map is keyed with the field name and has a FieldGroup for the value.
-     * Can also include Banner objects.
-     */
-    protected Map<String, FieldGroupBase> fieldGroupMap = new HashMap<String, FieldGroupBase>();
-
-    /** This field group will be the "catch-all" group for fields that are not
-     *  included in an explicit field-group.
+    /*
+     * ----------------------------------------------------------------------- *
+     *                     DEVELOPERS PLEASE READ
+     * ----------------------------------------------------------------------- *
+     * 
+     * This model is intended to be a read-only data structure that represents
+     * an XML element. Outside of object construction, the class should not
+     * have any behaviors. All behavior should be contained in model visitors.
+     * 
+     * Instances of this class will be shared by multiple threads - therefore
+     * it is immutable. DO NOT CHANGE THE OBJECT'S STATE AT RUN TIME!
+     * 
+     * BE VERY CAREFUL when implementing "extends" - parent form collections
+     * must be added to child collections, not replace them. In other words,
+     * do not assign parent collection fields to child collection fields.
+     * 
      */
-    protected FieldGroup defaultFieldGroup;
 
+    public static final String module = ModelForm.class.getName();
+    public static final String DEFAULT_FORM_RESULT_LIST_NAME = "defaultFormResultList";
     /** Pagination settings and defaults. */
     public static int DEFAULT_PAGE_SIZE = 10;
     public static int MAX_PAGE_SIZE = 10000;
-    protected int defaultViewSize = DEFAULT_PAGE_SIZE;
     public static String DEFAULT_PAG_INDEX_FIELD = "viewIndex";
     public static String DEFAULT_PAG_SIZE_FIELD = "viewSize";
     public static String DEFAULT_PAG_STYLE = "nav-pager";
@@ -179,418 +103,706 @@ public class ModelForm extends ModelWidg
     public static String DEFAULT_PAG_PREV_STYLE = "nav-previous";
     public static String DEFAULT_PAG_NEXT_STYLE = "nav-next";
     public static String DEFAULT_PAG_LAST_STYLE = "nav-last";
-
     /** Sort field default styles. */
     public static String DEFAULT_SORT_FIELD_STYLE = "sort-order";
     public static String DEFAULT_SORT_FIELD_ASC_STYLE = "sort-order-asc";
     public static String DEFAULT_SORT_FIELD_DESC_STYLE = "sort-order-desc";
-
-    protected List<ModelWidgetAction> actions;
-    protected List<ModelWidgetAction> rowActions;
-    protected FlexibleStringExpander rowCountExdr;
-    protected List<ModelFormField> multiSubmitFields = new ArrayList<ModelFormField>();
-    protected int rowCount = 0;
-    private String sortFieldParameterName = "sortField";
-
-    /** On Submit areas to be updated. */
-    protected List<UpdateArea> onSubmitUpdateAreas;
+    private final List<ModelWidgetAction> actions;
+    private final List<AltRowStyle> altRowStyles;
+    private final List<AltTarget> altTargets;
+    private final List<AutoFieldsEntity> autoFieldsEntities;
+    private final List<AutoFieldsService> autoFieldsServices;
+    private final boolean clientAutocompleteFields;
+    private final String containerId;
+    private final String containerStyle;
+    private final String defaultEntityName;
+    /** This field group will be the "catch-all" group for fields that are not
+     *  included in an explicit field-group.
+     */
+    private final FieldGroup defaultFieldGroup;
+    private final FlexibleMapAccessor<Map<String, ? extends Object>> defaultMapName;
+    private final String defaultRequiredFieldStyle;
+    private final String defaultServiceName;
+    private final String defaultSortFieldAscStyle;
+    private final String defaultSortFieldDescStyle;
+    private final String defaultSortFieldStyle;
+    private final String defaultTableStyle;
+    private final String defaultTitleAreaStyle;
+    private final String defaultTitleStyle;
+    private final String defaultTooltipStyle;
+    private final int defaultViewSize;
+    private final String defaultWidgetAreaStyle;
+    private final String defaultWidgetStyle;
+    private final String evenRowStyle;
+    /** This is a list of FieldGroups in the order they were created.
+     * Can also include Banner objects.
+     */
+    private final List<FieldGroupBase> fieldGroupList;
+    /** This Map is keyed with the field name and has a FieldGroup for the value.
+     * Can also include Banner objects.
+     */
+    private final Map<String, FieldGroupBase> fieldGroupMap;
+    /** This List will contain one copy of each field for each field name in the order
+     * they were encountered in the service, entity, or form definition; field definitions
+     * with constraints will also be in this list but may appear multiple times for the same
+     * field name.
+     *
+     * When rendering the form the order in this list should be following and it should not be
+     * necessary to use the Map. The Map is used when loading the form definition to keep the
+     * list clean and implement the override features for field definitions.
+     */
+    private final List<ModelFormField> fieldList;
+    private final String focusFieldName;
+    private final String formLocation;
+    private final String formTitleAreaStyle;
+    private final String formWidgetAreaStyle;
+    private final boolean groupColumns;
+    private final String headerRowStyle;
+    private final boolean hideHeader;
+    private final String itemIndexSeparator;
+    private final List<String> lastOrderFields;
+    private final String listEntryName;
+    private final String listName;
+    private final List<ModelFormField> multiSubmitFields;
+    private final String oddRowStyle;
     /** On Paginate areas to be updated. */
-    protected List<UpdateArea> onPaginateUpdateAreas;
+    private final List<UpdateArea> onPaginateUpdateAreas;
     /** On Sort Column areas to be updated. */
-    protected List<UpdateArea> onSortColumnUpdateAreas;
-
-    // ===== CONSTRUCTORS =====
+    private final List<UpdateArea> onSortColumnUpdateAreas;
+    /** On Submit areas to be updated. */
+    private final List<UpdateArea> onSubmitUpdateAreas;
+    private final FlexibleStringExpander overrideListSize;
+    private final FlexibleStringExpander paginate;
+    private final FlexibleStringExpander paginateFirstLabel;
+    private final FlexibleStringExpander paginateIndexField;
+    private final FlexibleStringExpander paginateLastLabel;
+    private final FlexibleStringExpander paginateNextLabel;
+    private final FlexibleStringExpander paginatePreviousLabel;
+    private final FlexibleStringExpander paginateSizeField;
+    private final String paginateStyle;
+    private final FlexibleStringExpander paginateTarget;
+    private final String paginateTargetAnchor;
+    private final FlexibleStringExpander paginateViewSizeLabel;
+    private final ModelForm parentModelForm;
+    private final List<ModelWidgetAction> rowActions;
+    private final FlexibleStringExpander rowCountExdr;
+    private final boolean separateColumns;
+    private final boolean skipEnd;
+    private final boolean skipStart;
+    private final String sortFieldParameterName;
+    private final List<SortField> sortOrderFields;
+    private final FlexibleStringExpander target;
+    private final String targetType;
+    private final FlexibleStringExpander targetWindowExdr;
+    private final String title;
+    private final String tooltip;
+    private final String type;
+    private final boolean useRowSubmit;
+    /** Keeps track of conditional fields to help ensure that only one is rendered
+     */
+    private final Set<String> useWhenFields;
 
     /** XML Constructor */
-    public ModelForm(Element formElement, ModelReader entityModelReader, DispatchContext dispatchContext) {
+    public ModelForm(Element formElement, String formLocation, ModelReader entityModelReader, DispatchContext dispatchContext) {
         super(formElement);
-        try {
-            initForm(formElement, entityModelReader, dispatchContext);
-        } catch (RuntimeException e) {
-            Debug.logError(e, "Error parsing form [" + formElement.getAttribute("name") + "]: " + e.toString(), module);
-            throw e;
+        this.formLocation = formLocation;
+        parentModelForm = getParentForm(formElement, entityModelReader, dispatchContext);
+        int defaultViewSizeInt = DEFAULT_PAGE_SIZE;
+        if (parentModelForm != null) {
+            defaultViewSizeInt = parentModelForm.defaultViewSize;
+        } else {
+            defaultViewSizeInt = UtilProperties.getPropertyAsInteger("widget.properties", "widget.form.defaultViewSize",
+                    defaultViewSizeInt);
         }
-    }
-
-    public String getTarget() {
-        return target.getOriginal();
-    }
-
-    public List<AltTarget> getAltTargets() {
-        return altTargets;
-    }
-
-    public List<ModelWidgetAction> getActions() {
-        return actions;
-    }
-
-    public List<ModelWidgetAction> getRowActions() {
-        return rowActions;
-    }
-
-    public List<AutoFieldsEntity> getAutoFieldsEntities() {
-        return autoFieldsEntities;
-    }
-
-    public List<AutoFieldsService> getAutoFieldsServices() {
-        return autoFieldsServices;
-    }
-
-    public void initForm(Element formElement, ModelReader entityModelReader, DispatchContext dispatchContext) {
-
-        setDefaultViewSize(UtilProperties.getPropertyValue("widget.properties", "widget.form.defaultViewSize"));
-        // check if there is a parent form to inherit from
-        String parentResource = formElement.getAttribute("extends-resource");
-        String parentForm = formElement.getAttribute("extends");
-        if (parentForm.length() > 0) {
-            ModelForm parent = null;
-            // check if we have a resource name (part of the string before the ?)
-            if (parentResource.length() > 0) {
-                try {
-                    parent = FormFactory.getFormFromLocation(parentResource, parentForm, entityModelReader, dispatchContext);
-                    this.parentFormName = parentForm;
-                    this.parentFormLocation = parentResource;
-                } catch (Exception e) {
-                    Debug.logError(e, "Failed to load parent form definition '" + parentForm + "' at resource '" + parentResource + "'", module);
-                }
-            } else if (!parentForm.equals(formElement.getAttribute("name"))) {
-                // try to find a form definition in the same file
-                Element rootElement = formElement.getOwnerDocument().getDocumentElement();
-                List<? extends Element> formElements = UtilXml.childElementList(rootElement, "form");
-                //Uncomment below to add support for abstract forms
-                //formElements.addAll(UtilXml.childElementList(rootElement, "abstract-form"));
-                for (Element formElementEntry: formElements) {
-                    if (formElementEntry.getAttribute("name").equals(parentForm)) {
-                        parent = new ModelForm(formElementEntry, entityModelReader, dispatchContext);
-                        break;
-                    }
-                }
-                if (parent == null) {
-                    Debug.logError("Failed to find parent form definition '" + parentForm + "' in same document.", module);
-                } else {
-                    this.parentFormName = parentForm;
-                }
-            } else {
-                Debug.logError("Recursive form definition found for '" + formElement.getAttribute("name") + ".'", module);
-            }
-
-            if (parent != null) {
-                this.parentModelForm = parent;
-                this.type = parent.type;
-                this.target = parent.target;
-                this.containerId = parent.containerId;
-                this.containerStyle = parent.containerStyle;
-                this.focusFieldName = parent.focusFieldName;
-                this.title = parent.title;
-                this.tooltip = parent.tooltip;
-                this.listName = parent.listName;
-                this.listEntryName = parent.listEntryName;
-                this.tooltip = parent.tooltip;
-                this.defaultEntityName = parent.defaultEntityName;
-                this.defaultServiceName = parent.defaultServiceName;
-                this.formTitleAreaStyle = parent.formTitleAreaStyle;
-                this.formWidgetAreaStyle = parent.formWidgetAreaStyle;
-                this.defaultTitleAreaStyle = parent.defaultTitleAreaStyle;
-                this.defaultWidgetAreaStyle = parent.defaultWidgetAreaStyle;
-                this.oddRowStyle = parent.oddRowStyle;
-                this.evenRowStyle = parent.evenRowStyle;
-                this.defaultTableStyle = parent.defaultTableStyle;
-                this.headerRowStyle = parent.headerRowStyle;
-                this.defaultTitleStyle = parent.defaultTitleStyle;
-                this.defaultWidgetStyle = parent.defaultWidgetStyle;
-                this.defaultTooltipStyle = parent.defaultTooltipStyle;
-                this.itemIndexSeparator = parent.itemIndexSeparator;
-                this.separateColumns = parent.separateColumns;
-                this.groupColumns = parent.groupColumns;
-                this.targetType = parent.targetType;
-                this.defaultMapName = parent.defaultMapName;
-                this.targetWindowExdr = parent.targetWindowExdr;
-                this.hideHeader = parent.hideHeader;
-                this.clientAutocompleteFields = parent.clientAutocompleteFields;
-                this.paginateTarget = parent.paginateTarget;
-
-                this.altTargets.addAll(parent.altTargets);
-                this.actions = parent.actions;
-                this.rowActions = parent.rowActions;
-                this.defaultViewSize = parent.defaultViewSize;
-                this.onSubmitUpdateAreas = parent.onSubmitUpdateAreas;
-                this.onPaginateUpdateAreas = parent.onPaginateUpdateAreas;
-                this.onSortColumnUpdateAreas = parent.onSortColumnUpdateAreas;
-                this.altRowStyles = parent.altRowStyles;
-
-                this.useWhenFields = parent.useWhenFields;
-
-                // Create this fieldList/Map from clones of parent's
-                for (ModelFormField parentChildField: parent.fieldList) {
-                    ModelFormField childField = new ModelFormField(this);
-                    childField.mergeOverrideModelFormField(parentChildField);
-                    this.fieldList.add(childField);
-                    this.fieldMap.put(childField.getName(), childField);
-                }
-
-                this.fieldGroupMap = parent.fieldGroupMap;
-                this.fieldGroupList = parent.fieldGroupList;
-                this.lastOrderFields = parent.lastOrderFields;
-                this.sortFieldParameterName = parent.sortFieldParameterName;
-
+        if (formElement.hasAttribute("view-size")) {
+            try {
+                defaultViewSizeInt = Integer.valueOf(formElement.getAttribute("view-size"));
+            } catch (NumberFormatException e) {
             }
         }
-
-        if (this.type == null || formElement.hasAttribute("type")) {
-            this.type = formElement.getAttribute("type");
-        }
-        if (this.target == null || formElement.hasAttribute("target")) {
-            setTarget(formElement.getAttribute("target"));
-        }
-        if (this.targetWindowExdr == null || formElement.hasAttribute("target-window")) {
-            setTargetWindow(formElement.getAttribute("target-window"));
-        }
-        if (this.containerId == null || formElement.hasAttribute("id")) {
-            this.containerId = formElement.getAttribute("id");
+        this.defaultViewSize = defaultViewSizeInt;
+        String type = null;
+        if (formElement.hasAttribute("type")) {
+            type = formElement.getAttribute("type");
+        } else {
+            if (parentModelForm != null) {
+                type = parentModelForm.type;
+            }
         }
-        if (this.containerStyle == null || formElement.hasAttribute("style")) {
-            this.containerStyle = formElement.getAttribute("style");
+        this.type = type;
+        FlexibleStringExpander target = FlexibleStringExpander.getInstance(formElement.getAttribute("target"));
+        if (target.isEmpty() && parentModelForm != null) {
+            target = parentModelForm.target;
+        }
+        this.target = target;
+        String containerId = null;
+        if (formElement.hasAttribute("id")) {
+            containerId = formElement.getAttribute("id");
+        } else {
+            if (parentModelForm != null) {
+                containerId = parentModelForm.containerId;
+            }
         }
-        if (this.focusFieldName == null || formElement.hasAttribute("focus-field-name")) {
-            this.focusFieldName = formElement.getAttribute("focus-field-name");
+        this.containerId = containerId;
+        String containerStyle = "";
+        if (formElement.hasAttribute("style")) {
+            containerStyle = formElement.getAttribute("style");
+        } else {
+            if (parentModelForm != null) {
+                containerStyle = parentModelForm.containerStyle;
+            }
         }
-        if (this.title == null || formElement.hasAttribute("title")) {
-            this.title = formElement.getAttribute("title");
+        this.containerStyle = containerStyle;
+        String title = null;
+        if (formElement.hasAttribute("title")) {
+            title = formElement.getAttribute("title");
+        } else {
+            if (parentModelForm != null) {
+                title = parentModelForm.title;
+            }
         }
-        if (this.tooltip == null || formElement.hasAttribute("tooltip")) {
-            this.tooltip = formElement.getAttribute("tooltip");
+        this.title = title;
+        String tooltip = null;
+        if (formElement.hasAttribute("tooltip")) {
+            tooltip = formElement.getAttribute("tooltip");
+        } else {
+            if (parentModelForm != null) {
+                tooltip = parentModelForm.tooltip;
+            }
         }
-        if (this.listName == null || formElement.hasAttribute("list-name")) {
-            this.listName = formElement.getAttribute("list-name");
+        this.tooltip = tooltip;
+        String listName = DEFAULT_FORM_RESULT_LIST_NAME;
+        if (formElement.hasAttribute("list-name")) {
+            listName = formElement.getAttribute("list-name");
+        } else {
+            if (parentModelForm != null) {
+                listName = parentModelForm.listName;
+            }
         }
-        // if no list-name then look in the list-iterator-name; this is deprecated but we'll look at it anyway
-        if (UtilValidate.isEmpty(this.listName) && formElement.hasAttribute("list-iterator-name")) {
-            this.listName = formElement.getAttribute("list-iterator-name");
+        this.listName = listName;
+        String listEntryName = null;
+        if (formElement.hasAttribute("list-entry-name")) {
+            listEntryName = formElement.getAttribute("list-entry-name");
+        } else {
+            if (parentModelForm != null) {
+                listEntryName = parentModelForm.listEntryName;
+            }
         }
-        if (this.listEntryName == null || formElement.hasAttribute("list-entry-name")) {
-            this.listEntryName = formElement.getAttribute("list-entry-name");
+        this.listEntryName = listEntryName;
+        String defaultEntityName = null;
+        if (formElement.hasAttribute("default-entity-name")) {
+            defaultEntityName = formElement.getAttribute("default-entity-name");
+        } else {
+            if (parentModelForm != null) {
+                defaultEntityName = parentModelForm.defaultEntityName;
+            }
         }
-        if (this.defaultMapName == null || formElement.hasAttribute("default-map-name")) {
-            this.setDefaultMapName(formElement.getAttribute("default-map-name"));
+        this.defaultEntityName = defaultEntityName;
+        String defaultServiceName = null;
+        if (formElement.hasAttribute("default-service-name")) {
+            defaultServiceName = formElement.getAttribute("default-service-name");
+        } else {
+            if (parentModelForm != null) {
+                defaultServiceName = parentModelForm.defaultServiceName;
+            }
         }
-        if (this.defaultServiceName == null || formElement.hasAttribute("default-service-name")) {
-            this.defaultServiceName = formElement.getAttribute("default-service-name");
+        this.defaultServiceName = defaultServiceName;
+        String formTitleAreaStyle = "";
+        if (formElement.hasAttribute("form-title-area-style")) {
+            formTitleAreaStyle = formElement.getAttribute("form-title-area-style");
+        } else {
+            if (parentModelForm != null) {
+                formTitleAreaStyle = parentModelForm.formTitleAreaStyle;
+            }
         }
-        if (this.defaultEntityName == null || formElement.hasAttribute("default-entity-name")) {
-            this.defaultEntityName = formElement.getAttribute("default-entity-name");
+        this.formTitleAreaStyle = formTitleAreaStyle;
+        String formWidgetAreaStyle = "";
+        if (formElement.hasAttribute("form-widget-area-style")) {
+            formWidgetAreaStyle = formElement.getAttribute("form-widget-area-style");
+        } else {
+            if (parentModelForm != null) {
+                formWidgetAreaStyle = parentModelForm.formWidgetAreaStyle;
+            }
         }
-
-        if (this.formTitleAreaStyle == null || formElement.hasAttribute("form-title-area-style")) {
-            this.formTitleAreaStyle = formElement.getAttribute("form-title-area-style");
+        this.formWidgetAreaStyle = formWidgetAreaStyle;
+        String defaultTitleAreaStyle = "";
+        if (formElement.hasAttribute("default-title-area-style")) {
+            defaultTitleAreaStyle = formElement.getAttribute("default-title-area-style");
+        } else {
+            if (parentModelForm != null) {
+                defaultTitleAreaStyle = parentModelForm.defaultTitleAreaStyle;
+            }
         }
-        if (this.formWidgetAreaStyle == null || formElement.hasAttribute("form-widget-area-style")) {
-            this.formWidgetAreaStyle = formElement.getAttribute("form-widget-area-style");
+        this.defaultTitleAreaStyle = defaultTitleAreaStyle;
+        String defaultWidgetAreaStyle = "";
+        if (formElement.hasAttribute("default-widget-area-style")) {
+            defaultWidgetAreaStyle = formElement.getAttribute("default-widget-area-style");
+        } else {
+            if (parentModelForm != null) {
+                defaultWidgetAreaStyle = parentModelForm.defaultWidgetAreaStyle;
+            }
         }
-
-        if (this.defaultTitleAreaStyle == null || formElement.hasAttribute("default-title-area-style")) {
-            this.defaultTitleAreaStyle = formElement.getAttribute("default-title-area-style");
+        this.defaultWidgetAreaStyle = defaultWidgetAreaStyle;
+        String oddRowStyle = "";
+        if (formElement.hasAttribute("odd-row-style")) {
+            oddRowStyle = formElement.getAttribute("odd-row-style");
+        } else {
+            if (parentModelForm != null) {
+                oddRowStyle = parentModelForm.oddRowStyle;
+            }
         }
-        if (this.defaultWidgetAreaStyle == null || formElement.hasAttribute("default-widget-area-style")) {
-            this.defaultWidgetAreaStyle = formElement.getAttribute("default-widget-area-style");
+        this.oddRowStyle = oddRowStyle;
+        String evenRowStyle = "";
+        if (formElement.hasAttribute("even-row-style")) {
+            evenRowStyle = formElement.getAttribute("even-row-style");
+        } else {
+            if (parentModelForm != null) {
+                evenRowStyle = parentModelForm.evenRowStyle;
+            }
         }
-        if (this.oddRowStyle == null || formElement.hasAttribute("odd-row-style")) {
-            this.oddRowStyle = formElement.getAttribute("odd-row-style");
+        this.evenRowStyle = evenRowStyle;
+        String defaultTableStyle = "";
+        if (formElement.hasAttribute("default-table-style")) {
+            defaultTableStyle = formElement.getAttribute("default-table-style");
+        } else {
+            if (parentModelForm != null) {
+                defaultTableStyle = parentModelForm.defaultTableStyle;
+            }
         }
-        if (this.evenRowStyle == null || formElement.hasAttribute("even-row-style")) {
-            this.evenRowStyle = formElement.getAttribute("even-row-style");
+        this.defaultTableStyle = defaultTableStyle;
+        String headerRowStyle = "";
+        if (formElement.hasAttribute("header-row-style")) {
+            headerRowStyle = formElement.getAttribute("header-row-style");
+        } else {
+            if (parentModelForm != null) {
+                headerRowStyle = parentModelForm.headerRowStyle;
+            }
         }
-        if (this.defaultTableStyle == null || formElement.hasAttribute("default-table-style")) {
-            this.defaultTableStyle = formElement.getAttribute("default-table-style");
+        this.headerRowStyle = headerRowStyle;
+        String defaultTitleStyle = "";
+        if (formElement.hasAttribute("default-title-style")) {
+            defaultTitleStyle = formElement.getAttribute("default-title-style");
+        } else {
+            if (parentModelForm != null) {
+                defaultTitleStyle = parentModelForm.defaultTitleStyle;
+            }
         }
-        if (this.headerRowStyle == null || formElement.hasAttribute("header-row-style")) {
-            this.headerRowStyle = formElement.getAttribute("header-row-style");
+        this.defaultTitleStyle = defaultTitleStyle;
+        String defaultWidgetStyle = "";
+        if (formElement.hasAttribute("default-widget-style")) {
+            defaultWidgetStyle = formElement.getAttribute("default-widget-style");
+        } else {
+            if (parentModelForm != null) {
+                defaultWidgetStyle = parentModelForm.defaultWidgetStyle;
+            }
         }
-        if (this.defaultTitleStyle == null || formElement.hasAttribute("header-row-style")) {
-            this.defaultTitleStyle = formElement.getAttribute("default-title-style");
+        this.defaultWidgetStyle = defaultWidgetStyle;
+        String defaultTooltipStyle = "";
+        if (formElement.hasAttribute("default-tooltip-style")) {
+            defaultTooltipStyle = formElement.getAttribute("default-tooltip-style");
+        } else {
+            if (parentModelForm != null) {
+                defaultTooltipStyle = parentModelForm.defaultTooltipStyle;
+            }
         }
-        if (this.defaultWidgetStyle == null || formElement.hasAttribute("default-widget-style")) {
-            this.defaultWidgetStyle = formElement.getAttribute("default-widget-style");
+        this.defaultTooltipStyle = defaultTooltipStyle;
+        String itemIndexSeparator = null;
+        if (formElement.hasAttribute("item-index-separator")) {
+            itemIndexSeparator = formElement.getAttribute("item-index-separator");
+        } else {
+            if (parentModelForm != null) {
+                itemIndexSeparator = parentModelForm.itemIndexSeparator;
+            }
         }
-        if (this.defaultTooltipStyle == null || formElement.hasAttribute("default-tooltip-style")) {
-            this.defaultTooltipStyle = formElement.getAttribute("default-tooltip-style");
+        this.itemIndexSeparator = itemIndexSeparator;
+        boolean separateColumns = false;
+        if (formElement.hasAttribute("separate-columns")) {
+            separateColumns = "true".equals(formElement.getAttribute("separate-columns"));
+        } else {
+            if (parentModelForm != null) {
+                separateColumns = parentModelForm.separateColumns;
+            }
         }
-        if (this.itemIndexSeparator == null || formElement.hasAttribute("item-index-separator")) {
-            this.itemIndexSeparator = formElement.getAttribute("item-index-separator");
+        this.separateColumns = separateColumns;
+        boolean groupColumns = false;
+        if (formElement.hasAttribute("group-columns")) {
+            groupColumns = !"false".equals(formElement.getAttribute("group-columns"));
+        } else {
+            if (parentModelForm != null) {
+                groupColumns = parentModelForm.groupColumns;
+            }
         }
-        if (this.targetType == null || formElement.hasAttribute("target-type")) {
-            this.targetType = formElement.getAttribute("target-type");
+        this.groupColumns = groupColumns;
+        String targetType = null;
+        if (formElement.hasAttribute("target-type")) {
+            targetType = formElement.getAttribute("target-type");
+        } else {
+            if (parentModelForm != null) {
+                targetType = parentModelForm.targetType;
+            }
         }
-        if (this.defaultRequiredFieldStyle == null || formElement.hasAttribute("default-required-field-style")) {
-            this.defaultRequiredFieldStyle = formElement.getAttribute("default-required-field-style");
+        this.targetType = targetType;
+        FlexibleMapAccessor<Map<String, ? extends Object>> defaultMapName = FlexibleMapAccessor.getInstance(formElement
+                .getAttribute("default-map-name"));
+        if (defaultMapName.isEmpty() && parentModelForm != null) {
+            defaultMapName = parentModelForm.defaultMapName;
+        }
+        this.defaultMapName = defaultMapName;
+        FlexibleStringExpander targetWindowExdr = FlexibleStringExpander.getInstance(formElement.getAttribute("target-window"));
+        if (targetWindowExdr.isEmpty() && parentModelForm != null) {
+            targetWindowExdr = parentModelForm.targetWindowExdr;
+        }
+        this.targetWindowExdr = targetWindowExdr;
+        boolean hideHeader = false;
+        if (formElement.hasAttribute("hide-header")) {
+            hideHeader = "true".equals(formElement.getAttribute("hide-header"));
+        } else {
+            if (parentModelForm != null) {
+                hideHeader = parentModelForm.hideHeader;
+            }
         }
-        if (this.defaultSortFieldStyle == null || formElement.hasAttribute("default-sort-field-style")) {
-            this.defaultSortFieldStyle = formElement.getAttribute("default-sort-field-style");
+        this.hideHeader = hideHeader;
+        boolean clientAutocompleteFields = true;
+        if (formElement.hasAttribute("client-autocomplete-fields")) {
+            clientAutocompleteFields = !"false".equals(formElement.getAttribute("client-autocomplete-fields"));
+        } else {
+            if (parentModelForm != null) {
+                clientAutocompleteFields = parentModelForm.clientAutocompleteFields;
+            }
         }
-        if (this.defaultSortFieldAscStyle == null || formElement.hasAttribute("default-sort-field-asc-style")) {
-            this.defaultSortFieldAscStyle = formElement.getAttribute("default-sort-field-asc-style");
+        this.clientAutocompleteFields = clientAutocompleteFields;
+        FlexibleStringExpander paginateTarget = FlexibleStringExpander.getInstance(formElement.getAttribute("paginate-target"));
+        if (paginateTarget.isEmpty() && parentModelForm != null) {
+            paginateTarget = parentModelForm.paginateTarget;
+        }
+        this.paginateTarget = paginateTarget;
+        ArrayList<AltTarget> altTargets = new ArrayList<AltTarget>();
+        if (parentModelForm != null) {
+            altTargets.addAll(parentModelForm.altTargets);
+        }
+        for (Element altTargetElement : UtilXml.childElementList(formElement, "alt-target")) {
+            altTargets.add(new AltTarget(altTargetElement));
+        }
+        altTargets.trimToSize();
+        this.altTargets = Collections.unmodifiableList(altTargets);
+        ArrayList<ModelWidgetAction> actions = new ArrayList<ModelWidgetAction>();
+        if (parentModelForm != null) {
+            actions.addAll(parentModelForm.actions);
         }
-        if (this.defaultSortFieldDescStyle == null || formElement.hasAttribute("default-sort-field-desc-style")) {
-            this.defaultSortFieldDescStyle = formElement.getAttribute("default-sort-field-desc-style");
+        Element actionsElement = UtilXml.firstChildElement(formElement, "actions");
+        if (actionsElement != null) {
+            actions.addAll(ModelFormAction.readSubActions(this, actionsElement));
         }
-
-        // pagination settings
-        if (this.paginateTarget == null || formElement.hasAttribute("paginate-target")) {
-            setPaginateTarget(formElement.getAttribute("paginate-target"));
+        actions.trimToSize();
+        this.actions = Collections.unmodifiableList(actions);
+        ArrayList<ModelWidgetAction> rowActions = new ArrayList<ModelWidgetAction>();
+        if (parentModelForm != null) {
+            rowActions.addAll(parentModelForm.rowActions);
         }
-        if (this.paginateTargetAnchor == null || formElement.hasAttribute("paginate-target-anchor")) {
-            this.paginateTargetAnchor = formElement.getAttribute("paginate-target-anchor");
+        Element rowActionsElement = UtilXml.firstChildElement(formElement, "row-actions");
+        if (rowActionsElement != null) {
+            rowActions.addAll(ModelFormAction.readSubActions(this, rowActionsElement));
         }
-        if (this.paginateIndexField == null || formElement.hasAttribute("paginate-index-field")) {
-            setPaginateIndexField(formElement.getAttribute("paginate-index-field"));
+        rowActions.trimToSize();
+        this.rowActions = Collections.unmodifiableList(rowActions);
+        ArrayList<UpdateArea> onPaginateUpdateAreas = new ArrayList<UpdateArea>();
+        ArrayList<UpdateArea> onSubmitUpdateAreas = new ArrayList<UpdateArea>();
+        ArrayList<UpdateArea> onSortColumnUpdateAreas = new ArrayList<UpdateArea>();
+        if (parentModelForm != null) {
+            onPaginateUpdateAreas.addAll(parentModelForm.onPaginateUpdateAreas);
+            onSubmitUpdateAreas.addAll(parentModelForm.onSubmitUpdateAreas);
+            onSortColumnUpdateAreas.addAll(parentModelForm.onSortColumnUpdateAreas);
         }
-        if (this.paginateSizeField == null || formElement.hasAttribute("paginate-size-field")) {
-            setPaginateSizeField(formElement.getAttribute("paginate-size-field"));
+        for (Element updateAreaElement : UtilXml.childElementList(formElement, "on-event-update-area")) {
+            UpdateArea updateArea = new UpdateArea(updateAreaElement, defaultServiceName, defaultEntityName);
+            if ("paginate".equals(updateArea.getEventType())) {
+                int index = onPaginateUpdateAreas.indexOf(updateArea);
+                if (index != -1) {
+                    if (!updateArea.areaTarget.isEmpty()) {
+                        onPaginateUpdateAreas.set(index, updateArea);
+                    } else {
+                        // blank target indicates a removing override
+                        onPaginateUpdateAreas.remove(index);
+                    }
+                } else {
+                    onPaginateUpdateAreas.add(updateArea);
+                }
+            } else if ("submit".equals(updateArea.getEventType())) {
+                int index = onSubmitUpdateAreas.indexOf(updateArea);
+                if (index != -1) {
+                    onSubmitUpdateAreas.set(index, updateArea);
+                } else {
+                    onSubmitUpdateAreas.add(updateArea);
+                }
+            } else if ("sort-column".equals(updateArea.getEventType())) {
+                int index = onSortColumnUpdateAreas.indexOf(updateArea);
+                if (index != -1) {
+                    if (!updateArea.areaTarget.isEmpty()) {
+                        onSortColumnUpdateAreas.set(index, updateArea);
+                    } else {
+                        // blank target indicates a removing override
+                        onSortColumnUpdateAreas.remove(index);
+                    }
+                } else {
+                    onSortColumnUpdateAreas.add(updateArea);
+                }
+            }
         }
-        if (this.overrideListSize == null || formElement.hasAttribute("override-list-size")) {
-            this.overrideListSize = FlexibleStringExpander.getInstance(formElement.getAttribute("override-list-size"));
+        onPaginateUpdateAreas.trimToSize();
+        this.onPaginateUpdateAreas = Collections.unmodifiableList(onPaginateUpdateAreas);
+        onSubmitUpdateAreas.trimToSize();
+        this.onSubmitUpdateAreas = Collections.unmodifiableList(onSubmitUpdateAreas);
+        onSortColumnUpdateAreas.trimToSize();
+        this.onSortColumnUpdateAreas = Collections.unmodifiableList(onSortColumnUpdateAreas);
+        ArrayList<AltRowStyle> altRowStyles = new ArrayList<AltRowStyle>();
+        if (parentModelForm != null) {
+            altRowStyles.addAll(parentModelForm.altRowStyles);
         }
-        if (this.paginateFirstLabel == null || formElement.hasAttribute("paginate-first-label")) {
-            this.paginateFirstLabel = FlexibleStringExpander.getInstance(formElement.getAttribute("paginate-first-label"));
+        for (Element altRowStyleElement : UtilXml.childElementList(formElement, "alt-row-style")) {
+            AltRowStyle altRowStyle = new AltRowStyle(altRowStyleElement);
+            altRowStyles.add(altRowStyle);
         }
-        if (this.paginatePreviousLabel == null || formElement.hasAttribute("paginate-previous-label")) {
-            this.paginatePreviousLabel = FlexibleStringExpander.getInstance(formElement.getAttribute("paginate-previous-label"));
+        altRowStyles.trimToSize();
+        this.altRowStyles = Collections.unmodifiableList(altRowStyles);
+        Set<String> useWhenFields = new HashSet<String>();
+        if (parentModelForm != null) {
+            useWhenFields.addAll(parentModelForm.useWhenFields);
+        }
+        ArrayList<ModelFormField> fieldList = new ArrayList<ModelFormField>();
+        Map<String, ModelFormField> fieldMap = new HashMap<String, ModelFormField>();
+        if (parentModelForm != null) {
+            // Create this fieldList/Map from clones of parentModelForm's
+            for (ModelFormField parentChildField : parentModelForm.fieldList) {
+                ModelFormField childField = new ModelFormField(this);
+                childField.mergeOverrideModelFormField(parentChildField);
+                fieldList.add(childField);
+                fieldMap.put(childField.getName(), childField);
+            }
+        }
+        Map<String, FieldGroupBase> fieldGroupMap = new HashMap<String, FieldGroupBase>();
+        if (parentModelForm != null) {
+            fieldGroupMap.putAll(parentModelForm.fieldGroupMap);
+        }
+        ArrayList<FieldGroupBase> fieldGroupList = new ArrayList<FieldGroupBase>();
+        if (parentModelForm != null) {
+            fieldGroupList.addAll(parentModelForm.fieldGroupList);
+        }
+        ArrayList<String> lastOrderFields = new ArrayList<String>();
+        if (parentModelForm != null) {
+            lastOrderFields.addAll(parentModelForm.lastOrderFields);
+        }
+        String sortFieldParameterName = "sortField";
+        if (formElement.hasAttribute("sort-field-parameter-name")) {
+            sortFieldParameterName = formElement.getAttribute("sort-field-parameter-name");
+        } else {
+            if (parentModelForm != null) {
+                sortFieldParameterName = parentModelForm.targetType;
+            }
         }
-        if (this.paginateNextLabel == null || formElement.hasAttribute("paginate-next-label")) {
-            this.paginateNextLabel = FlexibleStringExpander.getInstance(formElement.getAttribute("paginate-next-label"));
+        this.sortFieldParameterName = sortFieldParameterName;
+        String defaultRequiredFieldStyle = "";
+        if (formElement.hasAttribute("default-required-field-style")) {
+            defaultRequiredFieldStyle = formElement.getAttribute("default-required-field-style");
+        } else {
+            if (parentModelForm != null) {
+                defaultRequiredFieldStyle = parentModelForm.defaultRequiredFieldStyle;
+            }
         }
-        if (this.paginateLastLabel == null || formElement.hasAttribute("paginate-last-label")) {
-            this.paginateLastLabel = FlexibleStringExpander.getInstance(formElement.getAttribute("paginate-last-label"));
+        this.defaultRequiredFieldStyle = defaultRequiredFieldStyle;
+        String defaultSortFieldStyle = DEFAULT_SORT_FIELD_STYLE;
+        if (formElement.hasAttribute("default-sort-field-style")) {
+            defaultSortFieldStyle = formElement.getAttribute("default-sort-field-style");
+        } else {
+            if (parentModelForm != null) {
+                defaultSortFieldStyle = parentModelForm.defaultSortFieldStyle;
+            }
         }
-        if (this.paginateViewSizeLabel == null || formElement.hasAttribute("paginate-viewsize-label")) {
-            this.paginateViewSizeLabel = FlexibleStringExpander.getInstance(formElement.getAttribute("paginate-viewsize-label"));
+        this.defaultSortFieldStyle = defaultSortFieldStyle;
+        String defaultSortFieldAscStyle = DEFAULT_SORT_FIELD_ASC_STYLE;
+        if (formElement.hasAttribute("default-sort-field-asc-style")) {
+            defaultSortFieldAscStyle = formElement.getAttribute("default-sort-field-asc-style");
+        } else {
+            if (parentModelForm != null) {
+                defaultSortFieldAscStyle = parentModelForm.defaultSortFieldAscStyle;
+            }
         }
-        if (this.paginateStyle == null || formElement.hasAttribute("paginate-style")) {
-            setPaginateStyle(formElement.getAttribute("paginate-style"));
+        this.defaultSortFieldAscStyle = defaultSortFieldAscStyle;
+        String defaultSortFieldDescStyle = DEFAULT_SORT_FIELD_DESC_STYLE;
+        if (formElement.hasAttribute("default-sort-field-desc-style")) {
+            defaultSortFieldDescStyle = formElement.getAttribute("default-sort-field-desc-style");
+        } else {
+            if (parentModelForm != null) {
+                defaultSortFieldDescStyle = parentModelForm.defaultSortFieldDescStyle;
+            }
         }
-        if (this.paginate == null || formElement.hasAttribute("paginate")) {
-            this.paginate = FlexibleStringExpander.getInstance(formElement.getAttribute("paginate"));
+        this.defaultSortFieldDescStyle = defaultSortFieldDescStyle;
+        String paginateTargetAnchor = null;
+        if (formElement.hasAttribute("paginate-target-anchor")) {
+            paginateTargetAnchor = formElement.getAttribute("paginate-target-anchor");
+        } else {
+            if (parentModelForm != null) {
+                paginateTargetAnchor = parentModelForm.paginateTargetAnchor;
+            }
         }
-        String sortFieldParameterName = formElement.getAttribute("sort-field-parameter-name");
-        if (!sortFieldParameterName.isEmpty()) {
-            this.sortFieldParameterName = sortFieldParameterName;
+        this.paginateTargetAnchor = paginateTargetAnchor;
+        FlexibleStringExpander paginateIndexField = FlexibleStringExpander.getInstance(formElement
+                .getAttribute("paginate-index-field"));
+        if (paginateIndexField.isEmpty() && parentModelForm != null) {
+            paginateIndexField = parentModelForm.paginateIndexField;
+        }
+        this.paginateIndexField = paginateIndexField;
+        FlexibleStringExpander paginateSizeField = FlexibleStringExpander.getInstance(formElement
+                .getAttribute("paginate-size-field"));
+        if (paginateSizeField.isEmpty() && parentModelForm != null) {
+            paginateSizeField = parentModelForm.paginateSizeField;
+        }
+        this.paginateSizeField = paginateSizeField;
+        FlexibleStringExpander overrideListSize = FlexibleStringExpander.getInstance(formElement
+                .getAttribute("override-list-size"));
+        if (overrideListSize.isEmpty() && parentModelForm != null) {
+            overrideListSize = parentModelForm.overrideListSize;
+        }
+        this.overrideListSize = overrideListSize;
+        FlexibleStringExpander paginateFirstLabel = FlexibleStringExpander.getInstance(formElement
+                .getAttribute("paginate-first-label"));
+        if (paginateFirstLabel.isEmpty() && parentModelForm != null) {
+            paginateFirstLabel = parentModelForm.paginateFirstLabel;
+        }
+        this.paginateFirstLabel = paginateFirstLabel;
+        FlexibleStringExpander paginatePreviousLabel = FlexibleStringExpander.getInstance(formElement
+                .getAttribute("paginate-previous-label"));
+        if (paginatePreviousLabel.isEmpty() && parentModelForm != null) {
+            paginatePreviousLabel = parentModelForm.paginatePreviousLabel;
+        }
+        this.paginatePreviousLabel = paginatePreviousLabel;
+        FlexibleStringExpander paginateNextLabel = FlexibleStringExpander.getInstance(formElement
+                .getAttribute("paginate-next-label"));
+        if (paginateNextLabel.isEmpty() && parentModelForm != null) {
+            paginateNextLabel = parentModelForm.paginateNextLabel;
+        }
+        this.paginateNextLabel = paginateNextLabel;
+        FlexibleStringExpander paginateLastLabel = FlexibleStringExpander.getInstance(formElement
+                .getAttribute("paginate-last-label"));
+        if (paginateLastLabel.isEmpty() && parentModelForm != null) {
+            paginateLastLabel = parentModelForm.paginateLastLabel;
+        }
+        this.paginateLastLabel = paginateLastLabel;
+        FlexibleStringExpander paginateViewSizeLabel = FlexibleStringExpander.getInstance(formElement
+                .getAttribute("paginate-viewsize-label"));
+        if (paginateViewSizeLabel.isEmpty() && parentModelForm != null) {
+            paginateViewSizeLabel = parentModelForm.paginateViewSizeLabel;
+        }
+        this.paginateViewSizeLabel = paginateViewSizeLabel;
+        String paginateStyle = DEFAULT_PAG_STYLE;
+        if (formElement.hasAttribute("paginate-style")) {
+            paginateStyle = formElement.getAttribute("paginate-style");
+        } else {
+            if (parentModelForm != null) {
+                paginateStyle = parentModelForm.paginateStyle;
+            }
         }
-        this.skipStart = "true".equals(formElement.getAttribute("skip-start"));
-        this.skipEnd = "true".equals(formElement.getAttribute("skip-end"));
-        this.hideHeader = "true".equals(formElement.getAttribute("hide-header"));
-        this.clientAutocompleteFields = !"false".equals(formElement.getAttribute("client-autocomplete-fields"));
-        if (formElement.hasAttribute("separate-columns")) {
-            String sepColumns = formElement.getAttribute("separate-columns");
-            if (sepColumns != null && sepColumns.equalsIgnoreCase("true"))
-                separateColumns = true;
+        this.paginateStyle = paginateStyle;
+        FlexibleStringExpander paginate = FlexibleStringExpander.getInstance(formElement.getAttribute("paginate"));
+        if (paginate.isEmpty() && parentModelForm != null) {
+            paginate = parentModelForm.paginate;
+        }
+        this.paginate = paginate;
+        boolean skipStart = false;
+        if (formElement.hasAttribute("skip-start")) {
+            skipStart = "true".equals(formElement.getAttribute("skip-start"));
+        } else {
+            if (parentModelForm != null) {
+                skipStart = parentModelForm.skipStart;
+            }
         }
-        if (formElement.hasAttribute("group-columns")) {
-            String groupColumnsStr = formElement.getAttribute("group-columns");
-            if (groupColumnsStr != null && groupColumnsStr.equalsIgnoreCase("false"))
-                groupColumns = false;
+        this.skipStart = skipStart;
+        boolean skipEnd = false;
+        if (formElement.hasAttribute("skip-end")) {
+            skipEnd = "true".equals(formElement.getAttribute("skip-end"));
+        } else {
+            if (parentModelForm != null) {
+                skipEnd = parentModelForm.skipEnd;
+            }
         }
+        this.skipEnd = skipEnd;
+        boolean useRowSubmit = false;
         if (formElement.hasAttribute("use-row-submit")) {
-            String rowSubmit = formElement.getAttribute("use-row-submit");
-            if (rowSubmit != null && rowSubmit.equalsIgnoreCase("true"))
-                useRowSubmit = true;
-        }
-        if (formElement.hasAttribute("view-size")) {
-            setDefaultViewSize(formElement.getAttribute("view-size"));
-        }
-        if (this.rowCountExdr == null || formElement.hasAttribute("row-count")) {
-            this.rowCountExdr = FlexibleStringExpander.getInstance(formElement.getAttribute("row-count"));
-        }
-
-        //alt-row-styles
-        for (Element altRowStyleElement : UtilXml.childElementList(formElement, "alt-row-style")) {
-            AltRowStyle altRowStyle = new AltRowStyle(altRowStyleElement);
-            this.altRowStyles.add(altRowStyle);
-        }
-
-        // alt-target
-        for (Element altTargetElement: UtilXml.childElementList(formElement, "alt-target")) {
-            AltTarget altTarget = new AltTarget(altTargetElement);
-            this.addAltTarget(altTarget);
-        }
-
-        // on-event-update-area
-        for (Element updateAreaElement : UtilXml.childElementList(formElement, "on-event-update-area")) {
-            UpdateArea updateArea = new UpdateArea(updateAreaElement);
-            this.addOnEventUpdateArea(updateArea);
+            useRowSubmit = "true".equals(formElement.getAttribute("use-row-submit"));
+        } else {
+            if (parentModelForm != null) {
+                useRowSubmit = parentModelForm.useRowSubmit;
+            }
         }
-        //propagate defaultEntityName on updateAreas
-        if (UtilValidate.isNotEmpty(this.defaultEntityName)) this.setDefaultEntityNameOnUpdateAreas();
-        if (UtilValidate.isNotEmpty(this.defaultServiceName)) this.setDefaultServiceNameOnUpdateAreas();
-
-        // auto-fields-service
-        for (Element autoFieldsServiceElement: UtilXml.childElementList(formElement, "auto-fields-service")) {
+        this.useRowSubmit = useRowSubmit;
+        FlexibleStringExpander rowCountExdr = FlexibleStringExpander.getInstance(formElement.getAttribute("row-count"));
+        if (rowCountExdr.isEmpty() && parentModelForm != null) {
+            rowCountExdr = parentModelForm.rowCountExdr;
+        }
+        this.rowCountExdr = paginate;
+        ArrayList<ModelFormField> multiSubmitFields = new ArrayList<ModelFormField>();
+        ArrayList<AutoFieldsService> autoFieldsServices = new ArrayList<AutoFieldsService>();
+        ArrayList<AutoFieldsEntity> autoFieldsEntities = new ArrayList<AutoFieldsEntity>();
+        ArrayList<SortField> sortOrderFields = new ArrayList<SortField>();
+        this.defaultFieldGroup = new FieldGroup(null, this, sortOrderFields, fieldGroupMap);
+        for (Element autoFieldsServiceElement : UtilXml.childElementList(formElement, "auto-fields-service")) {
             AutoFieldsService autoFieldsService = new AutoFieldsService(autoFieldsServiceElement);
-            this.addAutoFieldsFromService(autoFieldsService, entityModelReader, dispatchContext);
+            autoFieldsServices.add(autoFieldsService);
+            addAutoFieldsFromService(autoFieldsService, entityModelReader, dispatchContext, useWhenFields, fieldList, fieldMap);
         }
-
-        // auto-fields-entity
-        for (Element autoFieldsEntityElement: UtilXml.childElementList(formElement, "auto-fields-entity")) {
+        for (Element autoFieldsEntityElement : UtilXml.childElementList(formElement, "auto-fields-entity")) {
             AutoFieldsEntity autoFieldsEntity = new AutoFieldsEntity(autoFieldsEntityElement);
-            this.addAutoFieldsFromEntity(autoFieldsEntity, entityModelReader);
+            autoFieldsEntities.add(autoFieldsEntity);
+            addAutoFieldsFromEntity(autoFieldsEntity, entityModelReader, useWhenFields, fieldList, fieldMap);
         }
-
-        // read in add field defs, add/override one by one using the fieldList and fieldMap
         String thisType = this.getType();
-        for (Element fieldElement: UtilXml.childElementList(formElement, "field")) {
+        for (Element fieldElement : UtilXml.childElementList(formElement, "field")) {
             ModelFormField modelFormField = new ModelFormField(fieldElement, this, entityModelReader, dispatchContext);
             ModelFormField.FieldInfo fieldInfo = modelFormField.getFieldInfo();
             if (thisType.equals("multi") && fieldInfo instanceof ModelFormField.SubmitField) {
-               multiSubmitFields.add(modelFormField);
+                multiSubmitFields.add(modelFormField);
             } else {
-                modelFormField = this.addUpdateField(modelFormField);
+                modelFormField = addUpdateField(modelFormField, useWhenFields, fieldList, fieldMap);
             }
         }
-
-        // Create the default field group
-        defaultFieldGroup = new FieldGroup(null, this);
         // get the sort-order
         Element sortOrderElement = UtilXml.firstChildElement(formElement, "sort-order");
         if (sortOrderElement != null) {
-            FieldGroup lastFieldGroup = new FieldGroup(null, this);
-            this.fieldGroupList.add(lastFieldGroup);
+            FieldGroup lastFieldGroup = new FieldGroup(null, this, sortOrderFields, fieldGroupMap);
+            fieldGroupList.add(lastFieldGroup);
             // read in sort-field
-            for (Element sortFieldElement: UtilXml.childElementList(sortOrderElement)) {
+            for (Element sortFieldElement : UtilXml.childElementList(sortOrderElement)) {
                 String tagName = sortFieldElement.getTagName();
                 if (tagName.equals("sort-field")) {
                     String fieldName = sortFieldElement.getAttribute("name");
                     String position = sortFieldElement.getAttribute("position");
-                    this.sortOrderFields.add(new SortField(fieldName, position));
-                    this.fieldGroupMap.put(fieldName, lastFieldGroup);
+                    sortOrderFields.add(new SortField(fieldName, position));
+                    fieldGroupMap.put(fieldName, lastFieldGroup);
                 } else if (tagName.equals("last-field")) {
                     String fieldName = sortFieldElement.getAttribute("name");
-                    this.fieldGroupMap.put(fieldName, lastFieldGroup);
-                    this.lastOrderFields.add(fieldName);
+                    fieldGroupMap.put(fieldName, lastFieldGroup);
+                    lastOrderFields.add(fieldName);
                 } else if (tagName.equals("banner")) {
-                    Banner thisBanner = new Banner(sortFieldElement, this);
-                    this.fieldGroupList.add(thisBanner);
-
-                    lastFieldGroup = new FieldGroup(null, this);
-                    this.fieldGroupList.add(lastFieldGroup);
+                    Banner thisBanner = new Banner(sortFieldElement);
+                    fieldGroupList.add(thisBanner);
+                    lastFieldGroup = new FieldGroup(null, this, sortOrderFields, fieldGroupMap);
+                    fieldGroupList.add(lastFieldGroup);
                 } else if (tagName.equals("field-group")) {
-                    FieldGroup thisFieldGroup = new FieldGroup(sortFieldElement, this);
-                    this.fieldGroupList.add(thisFieldGroup);
-
-                    lastFieldGroup = new FieldGroup(null, this);
-                    this.fieldGroupList.add(lastFieldGroup);
+                    FieldGroup thisFieldGroup = new FieldGroup(sortFieldElement, this, sortOrderFields, fieldGroupMap);
+                    fieldGroupList.add(thisFieldGroup);
+                    lastFieldGroup = new FieldGroup(null, this, sortOrderFields, fieldGroupMap);
+                    fieldGroupList.add(lastFieldGroup);
                 }
             }
         }
-
-        // reorder fields according to sort order
         if (sortOrderFields.size() > 0) {
-            List<ModelFormField> sortedFields = new LinkedList<ModelFormField>();
-            for (SortField sortField: this.sortOrderFields) {
+            ArrayList<ModelFormField> sortedFields = new ArrayList<ModelFormField>();
+            for (SortField sortField : sortOrderFields) {
                 String fieldName = sortField.getFieldName();
                 if (UtilValidate.isEmpty(fieldName)) {
                     continue;
                 }
-
                 // get all fields with the given name from the existing list and put them in the sorted list
-                Iterator<ModelFormField> fieldIter = this.fieldList.iterator();
+                Iterator<ModelFormField> fieldIter = fieldList.iterator();
                 while (fieldIter.hasNext()) {
                     ModelFormField modelFormField = fieldIter.next();
                     if (fieldName.equals(modelFormField.getName())) {
@@ -604,19 +816,18 @@ public class ModelForm extends ModelWidg
                 }
             }
             // now add all of the rest of the fields from fieldList, ie those that were not explicitly listed in the sort order
-            sortedFields.addAll(this.fieldList);
+            sortedFields.addAll(fieldList);
             // sortedFields all done, set fieldList
-            this.fieldList = sortedFields;
+            fieldList = sortedFields;
         }
-
-        if (UtilValidate.isNotEmpty(this.lastOrderFields)) {
+        if (UtilValidate.isNotEmpty(lastOrderFields)) {
             List<ModelFormField> lastedFields = new LinkedList<ModelFormField>();
-            for (String fieldName: this.lastOrderFields) {
+            for (String fieldName : lastOrderFields) {
                 if (UtilValidate.isEmpty(fieldName)) {
                     continue;
                 }
-             // get all fields with the given name from the existing list and put them in the lasted list
-                Iterator<ModelFormField> fieldIter = this.fieldList.iterator();
+                // get all fields with the given name from the existing list and put them in the lasted list
+                Iterator<ModelFormField> fieldIter = fieldList.iterator();
                 while (fieldIter.hasNext()) {
                     ModelFormField modelFormField = fieldIter.next();
                     if (fieldName.equals(modelFormField.getName())) {
@@ -627,145 +838,89 @@ public class ModelForm extends ModelWidg
                 }
             }
             //now put all lastedFields at the field list end
-            this.fieldList.addAll(lastedFields);
-        }
-
-        // read all actions under the "actions" element
-        Element actionsElement = UtilXml.firstChildElement(formElement, "actions");
-        if (actionsElement != null) {
-            this.actions = ModelFormAction.readSubActions(this, actionsElement);
+            fieldList.addAll(lastedFields);
         }
-
-        // read all actions under the "row-actions" element
-        Element rowActionsElement = UtilXml.firstChildElement(formElement, "row-actions");
-        if (rowActionsElement != null) {
-            this.rowActions = ModelFormAction.readSubActions(this, rowActionsElement);
+        this.useWhenFields = Collections.unmodifiableSet(useWhenFields);
+        fieldList.trimToSize();
+        this.fieldList = Collections.unmodifiableList(fieldList);
+        this.fieldGroupMap = Collections.unmodifiableMap(fieldGroupMap);
+        fieldGroupList.trimToSize();
+        this.fieldGroupList = Collections.unmodifiableList(fieldGroupList);
+        lastOrderFields.trimToSize();
+        this.lastOrderFields = Collections.unmodifiableList(lastOrderFields);
+        multiSubmitFields.trimToSize();
+        this.multiSubmitFields = Collections.unmodifiableList(multiSubmitFields);
+        autoFieldsServices.trimToSize();
+        this.autoFieldsServices = Collections.unmodifiableList(autoFieldsServices);
+        autoFieldsEntities.trimToSize();
+        this.autoFieldsEntities = Collections.unmodifiableList(autoFieldsEntities);
+        sortOrderFields.trimToSize();
+        this.sortOrderFields = Collections.unmodifiableList(sortOrderFields);
+        String focusFieldName = null;
+        if (formElement.hasAttribute("focus-field-name")) {
+            focusFieldName = formElement.getAttribute("focus-field-name");
+        } else {
+            if (parentModelForm != null) {
+                focusFieldName = parentModelForm.focusFieldName;
+            }
+            // TODO: Set this automatically if not specified
         }
+        this.focusFieldName = focusFieldName;
     }
 
-    /**
-     * add/override modelFormField using the fieldList and fieldMap
-     *
-     * @return The same ModelFormField, or if merged with an existing field, the existing field.
-     */
-    public ModelFormField addUpdateField(ModelFormField modelFormField) {
-        if (!modelFormField.isUseWhenEmpty() || useWhenFields.contains(modelFormField.getName())) {
-            useWhenFields.add(modelFormField.getName());
-            // is a conditional field, add to the List but don't worry about the Map
-            //for adding to list, see if there is another field with that name in the list and if so, put it before that one
-            boolean inserted = false;
-            for (int i = 0; i < this.fieldList.size(); i++) {
-                ModelFormField curField = this.fieldList.get(i);
-                if (curField.getName() != null && curField.getName().equals(modelFormField.getName())) {
-                    this.fieldList.add(i, modelFormField);
-                    inserted = true;
-                    break;
-                }
-            }
-            if (!inserted) {
-                this.fieldList.add(modelFormField);
-            }
-            return modelFormField;
-        } else {
-
-            // not a conditional field, see if a named field exists in Map
-            ModelFormField existingField = this.fieldMap.get(modelFormField.getName());
-            if (existingField != null) {
-                // does exist, update the field by doing a merge/override
-                existingField.mergeOverrideModelFormField(modelFormField);
-                return existingField;
-            } else {
-                // does not exist, add to List and Map
-                this.fieldList.add(modelFormField);
-                this.fieldMap.put(modelFormField.getName(), modelFormField);
-                return modelFormField;
-            }
-        }
-    }
-
-    public void addAltTarget(AltTarget altTarget) {
-        int index = altTargets.indexOf(altTarget);
-        if (index != -1) {
-            altTargets.set(index, altTarget);
-        } else {
-            altTargets.add(altTarget);
-        }
-    }
-
-    public void addOnEventUpdateArea(UpdateArea updateArea) {
-        // Event types are sorted as a convenience
-        // for the rendering classes
-        if ("paginate".equals(updateArea.getEventType())) {
-            addOnPaginateUpdateArea(updateArea);
-        } else if ("submit".equals(updateArea.getEventType())) {
-            addOnSubmitUpdateArea(updateArea);
-        } else if ("sort-column".equals(updateArea.getEventType())) {
-            addOnSortColumnUpdateArea(updateArea);
-        }
+    @Override
+    public void accept(ModelWidgetVisitor visitor) {
+        visitor.visit(this);
     }
 
-    protected void addOnSubmitUpdateArea(UpdateArea updateArea) {
-        if (onSubmitUpdateAreas == null) {
-            onSubmitUpdateAreas = new ArrayList<UpdateArea>();
+    private void addAutoFieldsFromEntity(AutoFieldsEntity autoFieldsEntity, ModelReader entityModelReader,
+            Set<String> useWhenFields, List<ModelFormField> fieldList, Map<String, ModelFormField> fieldMap) {
+        // read entity def and auto-create fields
+        ModelEntity modelEntity = null;
+        try {
+            modelEntity = entityModelReader.getModelEntity(autoFieldsEntity.entityName);
+        } catch (GenericEntityException e) {
+            Debug.logError(e, module);
         }
-        int index = onSubmitUpdateAreas.indexOf(updateArea);
-        if (index != -1) {
-            onSubmitUpdateAreas.set(index, updateArea);
-        } else {
-            onSubmitUpdateAreas.add(updateArea);
+        if (modelEntity == null) {
+            throw new IllegalArgumentException("Error finding Entity with name " + autoFieldsEntity.entityName
+                    + " for auto-fields-entity in a form widget");
         }
-    }
 
-    protected void addOnPaginateUpdateArea(UpdateArea updateArea) {
-        if (onPaginateUpdateAreas == null) {
-            onPaginateUpdateAreas = new ArrayList<UpdateArea>();
-        }
-        int index = onPaginateUpdateAreas.indexOf(updateArea);
-        if (index != -1) {
-            if (UtilValidate.isNotEmpty(updateArea.areaTarget)) {
-                onPaginateUpdateAreas.set(index, updateArea);
-            } else {
-                // blank target indicates a removing override
-                onPaginateUpdateAreas.remove(index);
+        Iterator<ModelField> modelFieldIter = modelEntity.getFieldsIterator();
+        while (modelFieldIter.hasNext()) {
+            ModelField modelField = modelFieldIter.next();
+            if (modelField.getIsAutoCreatedInternal()) {
+                // don't ever auto-add these, should only be added if explicitly referenced
+                continue;
             }
-        } else {
-            onPaginateUpdateAreas.add(updateArea);
-        }
-    }
-
-    protected void addOnSortColumnUpdateArea(UpdateArea updateArea) {
-        if (onSortColumnUpdateAreas == null) {
-            onSortColumnUpdateAreas = new ArrayList<UpdateArea>();
-        }
-        int index = onSortColumnUpdateAreas.indexOf(updateArea);
-        if (index != -1) {
-            if (UtilValidate.isNotEmpty(updateArea.areaTarget)) {
-                onSortColumnUpdateAreas.set(index, updateArea);
-            } else {
-                // blank target indicates a removing override
-                onSortColumnUpdateAreas.remove(index);
+            ModelFormField modelFormField = this.addFieldFromEntityField(modelEntity, modelField,
+                    autoFieldsEntity.defaultFieldType, autoFieldsEntity.defaultPosition, useWhenFields, fieldList, fieldMap);
+            if (UtilValidate.isNotEmpty(autoFieldsEntity.mapName)) {
+                modelFormField.setMapName(autoFieldsEntity.mapName);
             }
-        } else {
-            onSortColumnUpdateAreas.add(updateArea);
         }
     }
 
-    private void addAutoFieldsFromService(AutoFieldsService autoFieldsService, ModelReader entityModelReader, DispatchContext dispatchContext) {
-        autoFieldsServices.add(autoFieldsService);
+    private void addAutoFieldsFromService(AutoFieldsService autoFieldsService, ModelReader entityModelReader,
+            DispatchContext dispatchContext, Set<String> useWhenFields, List<ModelFormField> fieldList,
+            Map<String, ModelFormField> fieldMap) {
 
         // read service def and auto-create fields
         ModelService modelService = null;
         try {
             modelService = dispatchContext.getModelService(autoFieldsService.serviceName);
         } catch (GenericServiceException e) {
-            String errmsg = "Error finding Service with name " + autoFieldsService.serviceName + " for auto-fields-service in a form widget";
+            String errmsg = "Error finding Service with name " + autoFieldsService.serviceName
+                    + " for auto-fields-service in a form widget";
             Debug.logError(e, errmsg, module);
             throw new IllegalArgumentException(errmsg);
         }
 
-        for (ModelParam modelParam: modelService.getInModelParamList()) {
+        for (ModelParam modelParam : modelService.getInModelParamList()) {
             // skip auto params that the service engine populates...
-            if ("userLogin".equals(modelParam.name) || "locale".equals(modelParam.name) || "timeZone".equals(modelParam.name) || "login.username".equals(modelParam.name) || "login.password".equals(modelParam.name)) {
+            if ("userLogin".equals(modelParam.name) || "locale".equals(modelParam.name) || "timeZone".equals(modelParam.name)
+                    || "login.username".equals(modelParam.name) || "login.password".equals(modelParam.name)) {
                 continue;
             }
             if (modelParam.formDisplay) {
@@ -777,7 +932,9 @@ public class ModelForm extends ModelWidg
                             ModelField modelField = modelEntity.getField(modelParam.fieldName);
                             if (modelField != null) {
                                 // okay, populate using the entity field info...
-                                ModelFormField modelFormField = this.addFieldFromEntityField(modelEntity, modelField, autoFieldsService.defaultFieldType, autoFieldsService.defaultPosition);
+                                ModelFormField modelFormField = addFieldFromEntityField(modelEntity, modelField,
+                                        autoFieldsService.defaultFieldType, autoFieldsService.defaultPosition, useWhenFields,
+                                        fieldList, fieldMap);
                                 if (UtilValidate.isNotEmpty(autoFieldsService.mapName)) {
                                     modelFormField.setMapName(autoFieldsService.mapName);
                                 }
@@ -791,7 +948,9 @@ public class ModelForm extends ModelWidg
                     }
                 }
 
-                ModelFormField modelFormField = this.addFieldFromServiceParam(modelService, modelParam, autoFieldsService.defaultFieldType, autoFieldsService.defaultPosition);
+                ModelFormField modelFormField = this
+                        .addFieldFromServiceParam(modelService, modelParam, autoFieldsService.defaultFieldType,
+                                autoFieldsService.defaultPosition, useWhenFields, fieldList, fieldMap);
                 if (UtilValidate.isNotEmpty(autoFieldsService.mapName)) {
                     modelFormField.setMapName(autoFieldsService.mapName);
                 }
@@ -799,7 +958,20 @@ public class ModelForm extends ModelWidg
         }
     }
 
-    private ModelFormField addFieldFromServiceParam(ModelService modelService, ModelParam modelParam, String defaultFieldType, int defaultPosition) {
+    private ModelFormField addFieldFromEntityField(ModelEntity modelEntity, ModelField modelField, String defaultFieldType,
+            int defaultPosition, Set<String> useWhenFields, List<ModelFormField> fieldList, Map<String, ModelFormField> fieldMap) {
+        // create field def from entity field def
+        ModelFormField newFormField = new ModelFormField(this);
+        newFormField.setName(modelField.getName());
+        newFormField.setEntityName(modelEntity.getEntityName());
+        newFormField.setFieldName(modelField.getName());
+        newFormField.induceFieldInfoFromEntityField(modelEntity, modelField, defaultFieldType);
+        newFormField.setPosition(defaultPosition);
+        return this.addUpdateField(newFormField, useWhenFields, fieldList, fieldMap);
+    }
+
+    private ModelFormField addFieldFromServiceParam(ModelService modelService, ModelParam modelParam, String defaultFieldType,
+            int defaultPosition, Set<String> useWhenFields, List<ModelFormField> fieldList, Map<String, ModelFormField> fieldMap) {
         // create field def from service param def
         ModelFormField newFormField = new ModelFormField(this);
         newFormField.setName(modelParam.name);
@@ -809,2168 +981,2041 @@ public class ModelForm extends ModelWidg
         newFormField.setRequiredField(!modelParam.optional);
         newFormField.induceFieldInfoFromServiceParam(modelService, modelParam, defaultFieldType);
         newFormField.setPosition(defaultPosition);
-        return this.addUpdateField(newFormField);
+        return this.addUpdateField(newFormField, useWhenFields, fieldList, fieldMap);
     }
 
-    private void addAutoFieldsFromEntity(AutoFieldsEntity autoFieldsEntity, ModelReader entityModelReader) {
-        autoFieldsEntities.add(autoFieldsEntity);
-        // read entity def and auto-create fields
-        ModelEntity modelEntity = null;
-        try {
-            modelEntity = entityModelReader.getModelEntity(autoFieldsEntity.entityName);
-        } catch (GenericEntityException e) {
-            Debug.logError(e, module);
-        }
-        if (modelEntity == null) {
-            throw new IllegalArgumentException("Error finding Entity with name " + autoFieldsEntity.entityName + " for auto-fields-entity in a form widget");
-        }
-
-        Iterator<ModelField> modelFieldIter = modelEntity.getFieldsIterator();
-        while (modelFieldIter.hasNext()) {
-            ModelField modelField = modelFieldIter.next();
-            if (modelField.getIsAutoCreatedInternal()) {
-                // don't ever auto-add these, should only be added if explicitly referenced
-                continue;
+    /**
+     * add/override modelFormField using the fieldList and fieldMap
+     *
+     * @return The same ModelFormField, or if merged with an existing field, the existing field.
+     */
+    private ModelFormField addUpdateField(ModelFormField modelFormField, Set<String> useWhenFields,
+            List<ModelFormField> fieldList, Map<String, ModelFormField> fieldMap) {
+        if (!modelFormField.isUseWhenEmpty() || useWhenFields.contains(modelFormField.getName())) {
+            useWhenFields.add(modelFormField.getName());
+            // is a conditional field, add to the List but don't worry about the Map
+            //for adding to list, see if there is another field with that name in the list and if so, put it before that one
+            boolean inserted = false;
+            for (int i = 0; i < fieldList.size(); i++) {
+                ModelFormField curField = fieldList.get(i);
+                if (curField.getName() != null && curField.getName().equals(modelFormField.getName())) {
+                    fieldList.add(i, modelFormField);
+                    inserted = true;
+                    break;
+                }
             }
-            ModelFormField modelFormField = this.addFieldFromEntityField(modelEntity, modelField, autoFieldsEntity.defaultFieldType, autoFieldsEntity.defaultPosition);
-            if (UtilValidate.isNotEmpty(autoFieldsEntity.mapName)) {
-                modelFormField.setMapName(autoFieldsEntity.mapName);
+            if (!inserted) {
+                fieldList.add(modelFormField);
+            }
+            return modelFormField;
+        } else {
+            // not a conditional field, see if a named field exists in Map
+            ModelFormField existingField = fieldMap.get(modelFormField.getName());
+            if (existingField != null) {
+                // does exist, update the field by doing a merge/override
+                existingField.mergeOverrideModelFormField(modelFormField);
+                return existingField;
+            } else {
+                // does not exist, add to List and Map
+                fieldList.add(modelFormField);
+                fieldMap.put(modelFormField.getName(), modelFormField);
+                return modelFormField;
             }
         }
     }
 
-    private ModelFormField addFieldFromEntityField(ModelEntity modelEntity, ModelField modelField, String defaultFieldType, int defaultPosition) {
-        // create field def from entity field def
-        ModelFormField newFormField = new ModelFormField(this);
-        newFormField.setName(modelField.getName());
-        newFormField.setEntityName(modelEntity.getEntityName());
-        newFormField.setFieldName(modelField.getName());
-        newFormField.induceFieldInfoFromEntityField(modelEntity, modelField, defaultFieldType);
-        newFormField.setPosition(defaultPosition);
-        return this.addUpdateField(newFormField);
+    public List<ModelWidgetAction> getActions() {
+        return actions;
     }
 
-    public void runFormActions(Map<String, Object> context) {
-        ModelWidgetAction.runSubActions(this.actions, context);
+    public int getActualPageSize(Map<String, Object> context) {
+        Integer value = (Integer) context.get("actualPageSize");
+        return value != null ? value.intValue() : (getHighIndex(context) - getLowIndex(context));
     }
 
-    /**
-     * Renders this form to a String, i.e. in a text format, 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), bshInterpreter,
-     *   formName (String, optional alternate name for form, defaults to the
-     *   value of the name attribute)
-     * @param formStringRenderer An implementation of the FormStringRenderer
-     *   interface that is responsible for the actual text generation for
-     *   different form elements; implementing your own makes it possible to
-     *   use the same form definitions for many types of form UIs
-     */
-    public void renderFormString(Appendable writer, Map<String, Object> context, FormStringRenderer formStringRenderer) throws IOException {
-        //  increment the paginator, only for list and multi forms
-        if ("list".equals(this.type) || "multi".equals(this.type)) {
-            WidgetWorker.incrementPaginatorNumber(context);
-        }
+    public List<AltRowStyle> getAltRowStyles() {
+        return altRowStyles;
+    }
 
-        //if pagination is disabled, update the defualt view size
-        if (!getPaginate(context)) {
-            setDefaultViewSize(ModelForm.MAX_PAGE_SIZE);
-        }
+    public List<AltTarget> getAltTargets() {
+        return altTargets;
+    }
 
-        // Populate the viewSize and viewIndex so they are available for use during form actions
-        context.put("viewIndex", this.getViewIndex(context));
-        context.put("viewSize", this.getViewSize(context));
+    public List<AutoFieldsEntity> getAutoFieldsEntities() {
+        return autoFieldsEntities;
+    }
 
-        runFormActions(context);
+    public List<AutoFieldsService> getAutoFieldsServices() {
+        return autoFieldsServices;
+    }
 
-        // if this is a list form, don't useRequestParameters
-        if ("list".equals(this.type) || "multi".equals(this.type)) {
-            context.put("useRequestParameters", Boolean.FALSE);
+    @Override
+    public String getBoundaryCommentName() {
+        return formLocation + "#" + getName();
+    }
+
+    public Interpreter getBshInterpreter(Map<String, Object> context) throws EvalError {
+        Interpreter bsh = (Interpreter) context.get("bshInterpreter");
+        if (bsh == null) {
+            bsh = BshUtil.makeInterpreter(context);
+            context.put("bshInterpreter", bsh);
         }
+        return bsh;
+    }
 
-        // find the highest position number to get the max positions used
-        int positions = 1;
-        for (ModelFormField modelFormField: this.fieldList) {
-            int curPos = modelFormField.getPosition();
-            if (curPos > positions) {
-                positions = curPos;
-            }
-            ModelFormField.FieldInfo currentFieldInfo = modelFormField.getFieldInfo();
-            if (currentFieldInfo != null) {
-                ModelFormField fieldInfoFormField = currentFieldInfo.getModelFormField();
-                if (fieldInfoFormField != null) {
-                    fieldInfoFormField.setModelForm(this);
-                }
-            } else {
-                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());
-            }
-       }
+    public boolean getClientAutocompleteFields() {
+        return this.clientAutocompleteFields;
+    }
 
-        if ("single".equals(this.type)) {
-            this.renderSingleFormString(writer, context, formStringRenderer, positions);
-        } else if ("list".equals(this.type)) {
-            this.renderListFormString(writer, context, formStringRenderer, positions);
-        } else if ("multi".equals(this.type)) {
-            this.renderMultiFormString(writer, context, formStringRenderer, positions);
-        } else if ("upload".equals(this.type)) {
-            this.renderSingleFormString(writer, context, formStringRenderer, positions);
+    public String getContainerId() {
+        // use the name if there is no id
+        if (UtilValidate.isNotEmpty(this.containerId)) {
+            return this.containerId;
         } else {
-            if (UtilValidate.isEmpty(this.getType())) {
-                throw new IllegalArgumentException("The form 'type' tag is missing or empty on the form with the name " + this.getName());
-            } else {
-                throw new IllegalArgumentException("The form type " + this.getType() + " is not supported for form with name " + this.getName());
-            }
+            return this.getName();
         }
     }
 
-    public void renderSingleFormString(Appendable writer, Map<String, Object> context, FormStringRenderer formStringRenderer, int positions) throws IOException {
-        List<ModelFormField> tempFieldList = new LinkedList<ModelFormField>();
-        tempFieldList.addAll(this.fieldList);
+    public String getContainerStyle() {
+        return this.containerStyle;
+    }
 
-        // 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 (this.useWhenFields.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;
-                    }
-                }
-            }
-        }
+    public String getCurrentContainerId(Map<String, Object> context) {
+        Locale locale = UtilMisc.ensureLocale(context.get("locale"));
 
-        Set<String> alreadyRendered = new TreeSet<String>();
-        FieldGroup lastFieldGroup = null;
-        // render form open
-        if (!skipStart) formStringRenderer.renderFormOpen(writer, context, this);
+        String retVal = FlexibleStringExpander.expandString(this.getContainerId(), context, locale);
 
-        // render all hidden & ignored fields
-        List<ModelFormField> hiddenIgnoredFieldList = this.getHiddenIgnoredFields(context, alreadyRendered, tempFieldList, -1);
-        this.renderHiddenIgnoredFields(writer, context, formStringRenderer, hiddenIgnoredFieldList);

[... 4029 lines stripped ...]