You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@click.apache.org by sa...@apache.org on 2010/05/01 16:44:26 UTC

svn commit: r940042 - in /click/trunk/click/framework/src/org/apache/click: Page.java control/AbstractContainer.java control/Container.java control/FieldSet.java control/Form.java control/Panel.java util/ContainerUtils.java

Author: sabob
Date: Sat May  1 14:44:26 2010
New Revision: 940042

URL: http://svn.apache.org/viewvc?rev=940042&view=rev
Log:
Added support for replacing a control if another control already exists with matching name. CLK-666

Modified:
    click/trunk/click/framework/src/org/apache/click/Page.java
    click/trunk/click/framework/src/org/apache/click/control/AbstractContainer.java
    click/trunk/click/framework/src/org/apache/click/control/Container.java
    click/trunk/click/framework/src/org/apache/click/control/FieldSet.java
    click/trunk/click/framework/src/org/apache/click/control/Form.java
    click/trunk/click/framework/src/org/apache/click/control/Panel.java
    click/trunk/click/framework/src/org/apache/click/util/ContainerUtils.java

Modified: click/trunk/click/framework/src/org/apache/click/Page.java
URL: http://svn.apache.org/viewvc/click/trunk/click/framework/src/org/apache/click/Page.java?rev=940042&r1=940041&r2=940042&view=diff
==============================================================================
--- click/trunk/click/framework/src/org/apache/click/Page.java (original)
+++ click/trunk/click/framework/src/org/apache/click/Page.java Sat May  1 14:44:26 2010
@@ -359,13 +359,17 @@ public class Page implements Serializabl
     // Public Methods ---------------------------------------------------------
 
     /**
-     * Add the control to the page. The control will be added to the pages model
-     * using the controls name as the key. The Controls parent property will
+     * Add the control to the page. The control will be added to the page model
+     * using the control name as the key. The Controls parent property will
      * also be set to the page instance.
-     *
-     * @param control the control to add
-     * @throws IllegalArgumentException if the control is null, or if the name
-     *      of the control is not defined
+     * <p/>
+     * <b>Please note</b>: if the page contains a control with the same name as
+     * the given control, that control will be replaced by the given control.
+     * If a control has no name defined it cannot be replaced.
+     *
+     * @param control the control to add to the page
+     * @throws IllegalArgumentException if the control is null or if the name
+     * of the control is not defined
      */
     public void addControl(Control control) {
         if (control == null) {
@@ -376,6 +380,14 @@ public class Page implements Serializabl
                 + control.getClass());
         }
 
+        // Check if page already contains a named value
+        Object currentValue = getModel().get(control.getName());
+        if (currentValue != null && currentValue instanceof Control) {
+            Control currentControl = (Control) currentValue;
+            replaceControl(currentControl, control);
+            return;
+        }
+
         // Note: set parent first as setParent might veto further processing
         control.setParent(this);
 
@@ -831,11 +843,14 @@ public class Page implements Serializabl
 
     /**
      * Add the named object value to the Pages model map.
+     * <p/>
+     * <b>Please note</b>: if the Page contains an object with a matching name,
+     * that object will be replaced by the given value.
      *
      * @param name the key name of the object to add
      * @param value the object to add
      * @throws IllegalArgumentException if the name or value parameters are
-     * null, or if there is already a named value in the model
+     * null
      */
     public void addModel(String name, Object value) {
         if (name == null) {
@@ -848,13 +863,8 @@ public class Page implements Serializabl
                     + "to " + getClass().getName() + " model";
             throw new IllegalArgumentException(msg);
         }
-        if (getModel().containsKey(name)) {
-            String msg = getClass().getName() + " model already contains "
-                    + "value named " + name;
-            throw new IllegalArgumentException(msg);
-        } else {
-            getModel().put(name, value);
-        }
+
+        getModel().put(name, value);
     }
 
     /**
@@ -1190,7 +1200,7 @@ public class Page implements Serializabl
      * @param location the path to redirect the request to
      * @param params the map of request parameter name and value pairs
      */
-    public void setRedirect(String location, Map<String, Object> params) {
+    public void setRedirect(String location, Map<String, ? extends Object> params) {
         Context context = getContext();
         if (StringUtils.isNotBlank(location)) {
             if (location.charAt(0) == '/') {
@@ -1260,7 +1270,9 @@ public class Page implements Serializabl
      * @throws IllegalArgumentException if the Page Class is not configured
      * with a unique path
      */
-    public void setRedirect(Class<? extends Page> pageClass, Map<String, Object> params) {
+    public void setRedirect(Class<? extends Page> pageClass,
+        Map<String, ? extends Object> params) {
+
         String target = getContext().getPagePath(pageClass);
 
         // If page class maps to a jsp, convert to htm which allows ClickServlet
@@ -1336,4 +1348,39 @@ public class Page implements Serializabl
         this.template = template;
     }
 
+    // Private methods --------------------------------------------------------
+
+    /**
+     * Replace the current control with the new control.
+     *
+     * @param currentControl the control currently contained in the page
+     * @param newControl the control to replace the current control container in
+     * the page
+     *
+     * @throws IllegalStateException if the currentControl is not contained in
+     * the page
+     */
+    private void replaceControl(Control currentControl, Control newControl) {
+
+        // Current control and new control are referencing the same object
+        // so we exit early
+        if (currentControl == newControl) {
+            return;
+        }
+
+        int controlIndex = getControls().indexOf(currentControl);
+        if (controlIndex == -1) {
+            throw new IllegalStateException("Cannot replace the given control"
+                + " because it is not present in the page");
+        }
+
+        // Note: set parent first since setParent might veto further processing
+        newControl.setParent(this);
+        currentControl.setParent(null);
+
+        // Set control to current control index
+        getControls().set(controlIndex, newControl);
+
+        addModel(newControl.getName(), newControl);
+    }
 }

Modified: click/trunk/click/framework/src/org/apache/click/control/AbstractContainer.java
URL: http://svn.apache.org/viewvc/click/trunk/click/framework/src/org/apache/click/control/AbstractContainer.java?rev=940042&r1=940041&r2=940042&view=diff
==============================================================================
--- click/trunk/click/framework/src/org/apache/click/control/AbstractContainer.java (original)
+++ click/trunk/click/framework/src/org/apache/click/control/AbstractContainer.java Sat May  1 14:44:26 2010
@@ -81,11 +81,15 @@ public abstract class AbstractContainer 
 
     /**
      * @see org.apache.click.control.Container#add(org.apache.click.Control).
+     * <p/>
+     * <b>Please note</b>: if the container contains a control with the same name
+     * as the given control, that control will be
+     * {@link #replace(org.apache.click.Control, org.apache.click.Control) replaced}
+     * by the given control. If a control has no name defined it cannot be replaced.
      *
      * @param control the control to add to the container
      * @return the control that was added to the container
-     * @throws IllegalArgumentException if the control is null or the container
-     * already contains a control with the same name
+     * @throws IllegalArgumentException if the control is null
      */
     public Control add(Control control) {
         return insert(control, getControls().size());
@@ -95,7 +99,12 @@ public abstract class AbstractContainer 
      * Add the control to the container at the specified index, and return the
      * added instance.
      * <p/>
-     * <b>Please note</b> if the specified control already has a parent assigned,
+     * <b>Please note</b>: if the container contains a control with the same name
+     * as the given control, that control will be
+     * {@link #replace(org.apache.click.Control, org.apache.click.Control) replaced}
+     * by the given control. If a control has no name defined it cannot be replaced.
+     * <p/>
+     * <b>Also note</b> if the specified control already has a parent assigned,
      * it will automatically be removed from that parent and inserted into this
      * container.
      *
@@ -106,13 +115,33 @@ public abstract class AbstractContainer 
      * @return the control that was added to the container
      *
      * @throws IllegalArgumentException if the control is null or if the control
-     * and container is the same instance or the container already contains
-     * a control with the same name or if a Field name is not defined
+     * and container is the same instance
      *
      * @throws IndexOutOfBoundsException if index is out of range
      * <tt>(index &lt; 0 || index &gt; getControls().size())</tt>
      */
     public Control insert(Control control, int index) {
+        // Check if panel already contains the control
+        String controlName = control.getName();
+        if (controlName != null) {
+            // Check if container already contains the control
+            Control currentControl = getControlMap().get(control.getName());
+
+            // If container already contains the control do a replace
+            if (currentControl != null) {
+
+                // Current control and new control are referencing the same object
+                // so we exit early
+                if (currentControl == control) {
+                    return control;
+                }
+
+                // If the two controls are different objects, we remove the current
+                // control and add the given control
+                return replace(currentControl, control);
+            }
+        }
+
         return ContainerUtils.insert(this, control, index, getControlMap());
     }
 
@@ -128,6 +157,28 @@ public abstract class AbstractContainer 
     }
 
     /**
+     * Replace the control in the container at the specified index, and return
+     * the newly added control.
+     *
+     * @see org.apache.click.control.Container#replace(org.apache.click.Control, org.apache.click.Control)
+     *
+     * @param currentControl the control currently contained in the container
+     * @param newControl the control to replace the current control contained in
+     * the container
+     * @return the new control that replaced the current control
+     *
+     * @throws IllegalArgumentException if the currentControl or newControl is
+     * null
+     * @throws IllegalStateException if the currentControl is not contained in
+     * the container
+     */
+    public Control replace(Control currentControl, Control newControl) {
+        int controlIndex = getControls().indexOf(currentControl);
+        return ContainerUtils.replace(this, currentControl, newControl,
+            controlIndex, getControlMap());
+    }
+
+    /**
      * @see org.apache.click.control.Container#getControls().
      *
      * @return the sequential list of controls held by the container

Modified: click/trunk/click/framework/src/org/apache/click/control/Container.java
URL: http://svn.apache.org/viewvc/click/trunk/click/framework/src/org/apache/click/control/Container.java?rev=940042&r1=940041&r2=940042&view=diff
==============================================================================
--- click/trunk/click/framework/src/org/apache/click/control/Container.java (original)
+++ click/trunk/click/framework/src/org/apache/click/control/Container.java Sat May  1 14:44:26 2010
@@ -58,6 +58,17 @@ public interface Container extends Contr
     Control insert(Control control, int index);
 
     /**
+     * Replace the current control with the new control, and return the newly
+     * added control.
+     *
+     * @param currentControl the control currently contained in the container
+     * @param newControl the control to replace the current control contained in
+     * the container
+     * @return the new control that replaced the current control
+     */
+    Control replace(Control currentControl, Control newControl);
+
+    /**
      * Remove the given control from the container, returning true if the
      * control was found in the container and removed, or false if the control
      * was not found.

Modified: click/trunk/click/framework/src/org/apache/click/control/FieldSet.java
URL: http://svn.apache.org/viewvc/click/trunk/click/framework/src/org/apache/click/control/FieldSet.java?rev=940042&r1=940041&r2=940042&view=diff
==============================================================================
--- click/trunk/click/framework/src/org/apache/click/control/FieldSet.java (original)
+++ click/trunk/click/framework/src/org/apache/click/control/FieldSet.java Sat May  1 14:44:26 2010
@@ -165,6 +165,11 @@ public class FieldSet extends Field impl
      * Add a Field to the FieldSet at the specified index and return the added
      * instance.
      * <p/>
+     * <b>Please note</b>: if the FieldSet contains a control with the same name
+     * as the given control, that control will be
+     * {@link #replace(org.apache.click.Control, org.apache.click.Control) replaced}
+     * by the given control. If a control has no name defined it cannot be replaced.
+     * <p/>
      * Controls can be retrieved from the Map {@link #getControlMap() controlMap}
      * where the key is the Control name and value is the Control instance.
      * <p/>
@@ -185,10 +190,30 @@ public class FieldSet extends Field impl
      * @param index the index at which the control is to be inserted
      * @return the control that was added to the FieldSet
      * @throws IllegalArgumentException if the control is null, the Field's name
-     * is not defined, the container already contains a control with the same
-     * name or if the control is neither a Field nor FieldSet
+     * is not defined or if the control is neither a Field nor FieldSet
      */
     public Control insert(Control control, int index) {
+        // Check if container already contains the control
+        String controlName = control.getName();
+        if (controlName != null) {
+            Control currentControl = getControlMap().get(controlName);
+
+            // If container already contains the control do a replace
+            if (currentControl != null
+                && !(control instanceof Label)) {
+
+                // Current control and new control are referencing the same object
+                // so we exit early
+                if (currentControl == control) {
+                    return control;
+                }
+
+                // If the two controls are different objects, we remove the current
+                // control and add the given control
+                return replace(currentControl, control);
+            }
+        }
+
         ContainerUtils.insert(this, control, index, getControlMap());
 
         if (control instanceof Field) {
@@ -219,8 +244,70 @@ public class FieldSet extends Field impl
     }
 
     /**
+     * Replace the current control with the new control.
+     *
+     * @param currentControl the current control container in the fieldset
+     * @param newControl the control to replace the current control
+     * @return the new control that replaced the current control
+     *
+     * @throws IllegalArgumentException if the currentControl or newControl is
+     * null
+     * @throws IllegalStateException if the currentControl is not contained in
+     * the fieldset
+     */
+    @Override
+    public Control replace(Control currentControl, Control newControl) {
+        // Current and new control is the same instance - exit early
+        if (currentControl == newControl) {
+            return newControl;
+        }
+
+        int controlIndex = getControls().indexOf(currentControl);
+        Control result = ContainerUtils.replace(this, currentControl, newControl,
+            controlIndex, getControlMap());
+
+        if (newControl instanceof Field) {
+            Field field = (Field) newControl;
+
+            // Replace field in fieldList for fast access
+            if (!(field instanceof Button)) {
+                int fieldIndex = getFieldList().indexOf(currentControl);
+                getFieldList().set(fieldIndex, field);
+            }
+
+            // Set parent form
+            Form form = getForm();
+            field.setForm(form);
+
+            if (currentControl instanceof Field) {
+                // Remove form reference from current control
+                ((Field) currentControl).setForm(null);
+            }
+
+            if (form != null && form.getDefaultFieldSize() > 0) {
+                if (field instanceof TextField) {
+                    ((TextField) field).setSize(form.getDefaultFieldSize());
+
+                } else if (field instanceof FileField) {
+                    ((FileField) field).setSize(form.getDefaultFieldSize());
+
+                } else if (field instanceof TextArea) {
+                    ((TextArea) field).setCols(form.getDefaultFieldSize());
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /**
      * Add a Control to the fieldset and return the added instance.
      * <p/>
+     * <b>Please note</b>: if the FieldSet contains a control with the same name
+     * as the given control, that control will be
+     * {@link #replace(org.apache.click.Control, org.apache.click.Control) replaced}
+     * by the given control. If a control has no name defined it cannot be replaced.
+     * <p/>
      * Controls can be retrieved from the Map {@link #getControlMap() controlMap}
      * where the key is the Control name and value is the Control instance.
      * <p/>
@@ -234,8 +321,7 @@ public class FieldSet extends Field impl
      * @return the control that was added to the container
      *
      * @throws IllegalArgumentException if the control is null, the Field's name
-     * is not defined, the container already contains a control with the same
-     * name or if the control is neither a Field nor FieldSet
+     * is not defined or if the control is neither a Field nor FieldSet
      */
     public Control add(Control control) {
         return insert(control, getControls().size());
@@ -244,6 +330,11 @@ public class FieldSet extends Field impl
     /**
      * Add the field to the fieldSet, and set the fields form property.
      * <p/>
+     * <b>Please note</b>: if the FieldSet contains a control with the same name
+     * as the given control, that control will be
+     * {@link #replace(org.apache.click.Control, org.apache.click.Control) replaced}
+     * by the given control. If a control has no name defined it cannot be replaced.
+     * <p/>
      * Fields can be retrieved from the Map {@link #getFields() fields} where
      * the key is the Field name and value is the Field instance.
      * <p/>
@@ -253,9 +344,8 @@ public class FieldSet extends Field impl
      *
      * @param field the field to add to the fieldSet
      * @return the field added to this fieldSet
-     * @throws IllegalArgumentException if the field is null, the field name
-     * is not defined or the fieldSet already contains a control with the same
-     * name
+     * @throws IllegalArgumentException if the field is null or the field name
+     * is not defined
      */
     public Field add(Field field) {
         add((Control) field);
@@ -265,6 +355,11 @@ public class FieldSet extends Field impl
     /**
      * Add the field to the fieldset and specify the field width in columns.
      * <p/>
+     * <b>Please note</b>: if the FieldSet contains a control with the same name
+     * as the given control, that control will be
+     * {@link #replace(org.apache.click.Control, org.apache.click.Control) replaced}
+     * by the given control. If a control has no name defined it cannot be replaced.
+     * <p/>
      * Fields can be retrieved from the Map {@link #getFields() fields} where
      * the key is the Field name and value is the Field instance.
      * <p/>
@@ -276,8 +371,7 @@ public class FieldSet extends Field impl
      * @param width the width of the field in table columns
      * @return the field added to this fieldset
      * @throws IllegalArgumentException if the field is null, field name is
-     * not defined, field is a Button or HiddenField, the fieldset already
-     * contains a field with the same name or the width &lt; 1
+     * not defined, field is a Button or HiddenField or the width &lt; 1
      */
     public Field add(Field field, int width) {
         add((Control) field, width);
@@ -287,6 +381,11 @@ public class FieldSet extends Field impl
     /**
      * Add the control to the fieldset and specify the control's width in columns.
      * <p/>
+     * <b>Please note</b>: if the FieldSet contains a control with the same name
+     * as the given control, that control will be
+     * {@link #replace(org.apache.click.Control, org.apache.click.Control) replaced}
+     * by the given control. If a control has no name defined it cannot be replaced.
+     * <p/>
      * Controls can be retrieved from the Map {@link #getControlMap() controlMap}
      * where the key is the Control name and value is the Control instance.
      * <p/>
@@ -298,8 +397,7 @@ public class FieldSet extends Field impl
      * @param width the width of the control in table columns
      * @return the control added to this fieldSet
      * @throws IllegalArgumentException if the control is null, control is a
-     * Button or HiddenField, the fieldSet already contains a control with the
-     * same name or the width &lt; 1
+     * Button or HiddenField or the width &lt; 1
      */
     public Control add(Control control, int width) {
         if (control instanceof Button || control instanceof HiddenField) {
@@ -376,7 +474,7 @@ public class FieldSet extends Field impl
      *
      * @return the sequential list of controls held by the container
      */
-    public List getControls() {
+    public List<Control> getControls() {
         if (controls == null) {
             controls = new ArrayList();
         }
@@ -867,7 +965,7 @@ public class FieldSet extends Field impl
      *
      * @return the map of controls
      */
-    protected Map getControlMap() {
+    protected Map<String, Control> getControlMap() {
         if (controlMap == null) {
             controlMap = new HashMap();
         }

Modified: click/trunk/click/framework/src/org/apache/click/control/Form.java
URL: http://svn.apache.org/viewvc/click/trunk/click/framework/src/org/apache/click/control/Form.java?rev=940042&r1=940041&r2=940042&view=diff
==============================================================================
--- click/trunk/click/framework/src/org/apache/click/control/Form.java (original)
+++ click/trunk/click/framework/src/org/apache/click/control/Form.java Sat May  1 14:44:26 2010
@@ -697,6 +697,11 @@ public class Form extends AbstractContai
      * Add the control to the form at the specified index, and return the
      * added instance.
      * <p/>
+     * <b>Please note</b>: if the form contains a control with the same name as
+     * the given control, that control will be
+     * {@link #replace(org.apache.click.Control, org.apache.click.Control) replaced}
+     * by the given control. If a control has no name defined it cannot be replaced.
+     * <p/>
      * Controls can be retrieved from the Map {@link #getControlMap() controlMap}
      * where the key is the Control name and value is the Control instance.
      * <p/>
@@ -711,11 +716,6 @@ public class Form extends AbstractContai
      * <b>Please note</b> if the specified control already has a parent assigned,
      * it will automatically be removed from that parent and inserted into the
      * form.
-     * <p/>
-     * <b>Also note:</b> Form automatically adds hidden fields to preserve
-     * state across requests. Be aware of this when using <tt>insert</tt> as the
-     * hidden fields might influence the position of the Control you insert.
-     * <em>This restriction might be removed in a future version of Click.</em>
      *
      * @see Container#insert(org.apache.click.Control, int)
      *
@@ -724,8 +724,7 @@ public class Form extends AbstractContai
      * @return the control that was added to the container
      *
      * @throws IllegalArgumentException if the control is null or if the control
-     * and container is the same instance or the container already contains
-     * a control with the same name or if the Field name is not defined
+     * and container is the same instance or if the Field name is not defined
      *
      * @throws IndexOutOfBoundsException if index is out of range
      * <tt>(index &lt; 0 || index &gt; getControls().size())</tt>
@@ -733,10 +732,31 @@ public class Form extends AbstractContai
     @Override
     public Control insert(Control control, int index) {
 
+        // Check if container already contains the control
+        String controlName = control.getName();
+        if (controlName != null) {
+            Control currentControl = getControlMap().get(controlName);
+
+            // If container already contains the control do a replace
+            if (currentControl != null
+                && !(control instanceof Label)) {
+
+                // Current control and new control are referencing the same object
+                // so we exit early
+                if (currentControl == control) {
+                    return control;
+                }
+
+                // If the two controls are different objects, replace the current
+                // control with the given control
+                return replace(currentControl, control);
+            }
+        }
+
         // Adjust index for hidden fields added by Form. CLK-447
         int realIndex = Math.min(index, getControls().size() - insertIndexOffset);
 
-        super.insert(control, realIndex);
+        ContainerUtils.insert(this, control, realIndex, getControlMap());
 
         if (control instanceof Field) {
             Field field = (Field) control;
@@ -772,6 +792,11 @@ public class Form extends AbstractContai
     /**
      * Add a Control to the form and return the added instance.
      * <p/>
+     * <b>Please note</b>: if the form contains a control with the same name as
+     * the given control, that control will be
+     * {@link #replace(org.apache.click.Control, org.apache.click.Control) replaced}
+     * by the given control. If a control has no name defined it cannot be replaced.
+     * <p/>
      * Controls can be retrieved from the Map {@link #getControlMap() controlMap}
      * where the key is the Control name and value is the Control instance.
      * <p/>
@@ -796,6 +821,11 @@ public class Form extends AbstractContai
     /**
      * Add the field to the form, and set the fields form property.
      * <p/>
+     * <b>Please note</b>: if the form contains a control with the same name as
+     * the given control, that control will be
+     * {@link #replace(org.apache.click.Control, org.apache.click.Control) replaced}
+     * by the given control. If a control has no name defined it cannot be replaced.
+     * <p/>
      * Fields can be retrieved from the Map {@link #getFields() fields} where
      * the key is the Field name and value is the Field instance.
      * <p/>
@@ -817,6 +847,11 @@ public class Form extends AbstractContai
     /**
      * Add the field to the form and specify the field's width in columns.
      * <p/>
+     * <b>Please note</b>: if the form contains a control with the same name as
+     * the given control, that control will be
+     * {@link #replace(org.apache.click.Control, org.apache.click.Control) replaced}
+     * by the given control. If a control has no name defined it cannot be replaced.
+     * <p/>
      * Fields can be retrieved from the Map {@link #getFields() fields} where
      * the key is the Field name and value is the Field instance.
      * <p/>
@@ -841,6 +876,11 @@ public class Form extends AbstractContai
     /**
      * Add the control to the form and specify the control's width in columns.
      * <p/>
+     * <b>Please note</b>: if the form contains a control with the same name as
+     * the given control, that control will be
+     * {@link #replace(org.apache.click.Control, org.apache.click.Control) replaced}
+     * by the given control. If a control has no name defined it cannot be replaced.
+     * <p/>
      * Controls can be retrieved from the Map {@link #getControlMap() controlMap}
      * where the key is the Control name and value is the Control instance.
      * <p/>
@@ -875,6 +915,71 @@ public class Form extends AbstractContai
     }
 
     /**
+     * Replace the control in the form at the specified index, and return
+     * the newly added control.
+     *
+     * @see org.apache.click.control.Container#replace(org.apache.click.Control, org.apache.click.Control)
+     *
+     * @param currentControl the control currently contained in the form
+     * @param newControl the control to replace the current control contained in
+     * the form
+     * @return the new control that replaced the current control
+     *
+     * @throws IllegalArgumentException if the currentControl or newControl is
+     * null
+     * @throws IllegalStateException if the currentControl is not contained in
+     * the form
+     */
+    @Override
+    public Control replace(Control currentControl, Control newControl) {
+        // Current and new control is the same instance - exit early
+        if (currentControl == newControl) {
+            return newControl;
+        }
+
+        int controlIndex = getControls().indexOf(currentControl);
+        Control result = ContainerUtils.replace(this, currentControl, newControl,
+            controlIndex, getControlMap());
+
+        if (newControl instanceof Field) {
+            Field field = (Field) newControl;
+
+            if (field instanceof Button) {
+                // Replace field in buttonList for fast access
+                int buttonIndex = getButtonList().indexOf(currentControl);
+                getButtonList().set(buttonIndex, (Button) field);
+
+            } else {
+                // Replace field in fieldList for fast access
+                int fieldIndex = getFieldList().indexOf(currentControl);
+                getFieldList().set(fieldIndex, field);
+            }
+
+            // Set parent form
+            field.setForm(this);
+
+            if (currentControl instanceof Field) {
+                // Remove form reference from current control
+                ((Field) currentControl).setForm(null);
+            }
+
+            if (getDefaultFieldSize() > 0) {
+                if (field instanceof TextField) {
+                    ((TextField) field).setSize(getDefaultFieldSize());
+
+                } else if (field instanceof FileField) {
+                    ((FileField) field).setSize(getDefaultFieldSize());
+
+                } else if (field instanceof TextArea) {
+                    ((TextArea) field).setCols(getDefaultFieldSize());
+                }
+            }
+        }
+
+        return result;
+    }
+
+    /**
      * @see Container#remove(org.apache.click.Control)
      *
      * @param control the control to remove from the container

Modified: click/trunk/click/framework/src/org/apache/click/control/Panel.java
URL: http://svn.apache.org/viewvc/click/trunk/click/framework/src/org/apache/click/control/Panel.java?rev=940042&r1=940041&r2=940042&view=diff
==============================================================================
--- click/trunk/click/framework/src/org/apache/click/control/Panel.java (original)
+++ click/trunk/click/framework/src/org/apache/click/control/Panel.java Sat May  1 14:44:26 2010
@@ -30,6 +30,7 @@ import org.apache.click.Context;
 import org.apache.click.Control;
 import org.apache.click.Page;
 import org.apache.click.util.ClickUtils;
+import org.apache.click.util.ContainerUtils;
 import org.apache.click.util.Format;
 import org.apache.click.util.HtmlStringBuffer;
 import org.apache.click.util.SessionMap;
@@ -314,9 +315,8 @@ public class Panel extends AbstractConta
      *
      * @param control the control to add to the container
      * @return the control that was added to the container
-     * @throws IllegalArgumentException if the control is null, if the name
-     *     of the control is not defined or the container already contains a
-     *     control with the same name
+     * @throws IllegalArgumentException if the control is null or if the name
+     *     of the control is not defined
      */
     public Control addControl(Control control) {
         return add(control);
@@ -325,21 +325,22 @@ public class Panel extends AbstractConta
     /**
      * Add the control to the panel and return the specified control.
      * <p/>
+     * <b>Please note</b>: if the Panel contains a control with the same name as
+     * the given control, that control will be
+     * {@link #replace(org.apache.click.Control, org.apache.click.Control) replaced}
+     * by the given control. If a control has no name defined it cannot be replaced.
+     * <p/>
      * In addition to the requirements specified by
-     * {@link Container#add(org.apache.click.Control)}, note
-     * the following:
+     * {@link Container#add(org.apache.click.Control)}, note the following:
      * <ul>
      *  <li>
-     *   The control's name must be set when adding to a panel.
-     *  </li>
-     *  <li>
-     *   The control will be added to the Panel model using the controls name as
-     *   the key and can be accessed through {@link #getModel()}. This allows
-     *   one to reference the control in the Panels template.
+     *   If the control name is defined, it will be added to the Panel
+     *   {@link #getModel() model} using the control name as the key. The control
+     *   can be referenced via it's name from the Panel template.
      *  </li>
      *  <li>
      *   If the specified control is an <tt>instanceof</tt> a Panel, it will
-     *   also be added to a list of panels and can be accessed through
+     *   be added to the list of panels and can be accessed through
      *   {@link #getPanels()}.
      *  </li>
      * </ul>
@@ -347,15 +348,34 @@ public class Panel extends AbstractConta
      *
      * @param control the control to add to the container
      * @return the control that was added to the container
-     * @throws IllegalArgumentException if the control is null or the container
-     *     already contains a control with the same name
+     * @throws IllegalArgumentException if the control is null
      */
     @Override
-    public Control add(Control control) {
-        super.add(control);
-
+    public Control insert(Control control, int index) {
+        // Check if panel already contains the control
         String controlName = control.getName();
         if (controlName != null) {
+            // Check if container already contains the control
+            Control currentControl = getControlMap().get(controlName);
+
+            // If container already contains the control do a replace
+            if (currentControl != null) {
+
+                // Current control and new control are referencing the same object
+                // so we exit early
+                if (currentControl == control) {
+                    return control;
+                }
+
+                // If the two controls are different objects, we remove the current
+                // control and add the given control
+                return replace(currentControl, control);
+            }
+        }
+
+        ContainerUtils.insert(this, control, index, getControlMap());
+
+        if (controlName != null) {
             // If controls name is set, add control to the model
             addModel(controlName, control);
         }
@@ -368,6 +388,43 @@ public class Panel extends AbstractConta
     }
 
     /**
+     * Replace the current control with the new control.
+     *
+     * @param currentControl the current control container in the panel
+     * @param newControl the control to replace the current control
+     * @return the new control that replaced the current control
+     *
+     * @throws IllegalArgumentException if the currentControl or newControl is
+     * null
+     * @throws IllegalStateException if the currentControl is not contained in
+     * the panel
+     */
+    @Override
+    public Control replace(Control currentControl, Control newControl) {
+        // Current and new control is the same instance - exit early
+        if (currentControl == newControl) {
+            return newControl;
+        }
+
+        int controlIndex = getControls().indexOf(currentControl);
+        Control result = ContainerUtils.replace(this, currentControl, newControl,
+            controlIndex, getControlMap());
+
+        String controlName = newControl.getName();
+        if (controlName != null) {
+            // If controls name is set, add control to the model
+            addModel(controlName, newControl);
+        }
+
+        if (newControl instanceof Panel) {
+            int panelIndex = getPanels().indexOf(currentControl);
+            getPanels().set(panelIndex, (Panel) newControl);
+        }
+
+        return result;
+    }
+
+    /**
      * @see #remove(org.apache.click.Control)
      *
      * @deprecated use {@link #remove(org.apache.click.Control)} instead
@@ -445,7 +502,7 @@ public class Panel extends AbstractConta
      * Set the panel active flag. The active property is normally managed and
      * set by Panel containers.
      *
-     * <b>Please note</b>: inactive panels do not have their page events
+     * <b>Please note</b>: inactive panels do not have their events
      * ({@link #onInit()}, {@link #onProcess()}, {@link #onRender()}) processed.
      *
      * @param active the active flag
@@ -550,11 +607,14 @@ public class Panel extends AbstractConta
 
     /**
      * Add the named object value to the Panels model map.
+     * <p/>
+     * <b>Please note</b>: if the Panel contains an object with a matching name,
+     * that object will be replaced by the given value.
      *
      * @param name the key name of the object to add
      * @param value the object to add
      * @throws IllegalArgumentException if the name or value parameters are
-     * null, or if there is already a named value in the model
+     * null
      */
     public void addModel(String name, Object value) {
         if (name == null) {
@@ -567,13 +627,7 @@ public class Panel extends AbstractConta
                          + "to " + getClass().getName() + " model";
             throw new IllegalArgumentException(msg);
         }
-        if (getModel().containsKey(name)) {
-            String msg = getClass().getName() + " model already contains "
-                         + "value named " + name;
-            throw new IllegalArgumentException(msg);
-        } else {
-            getModel().put(name, value);
-        }
+        getModel().put(name, value);
     }
 
     /**

Modified: click/trunk/click/framework/src/org/apache/click/util/ContainerUtils.java
URL: http://svn.apache.org/viewvc/click/trunk/click/framework/src/org/apache/click/util/ContainerUtils.java?rev=940042&r1=940041&r2=940042&view=diff
==============================================================================
--- click/trunk/click/framework/src/org/apache/click/util/ContainerUtils.java (original)
+++ click/trunk/click/framework/src/org/apache/click/util/ContainerUtils.java Sat May  1 14:44:26 2010
@@ -479,9 +479,13 @@ public class ContainerUtils {
      * Add the given control to the container at the specified index, and return
      * the added instance.
      * <p/>
-     * <b>Please note</b> if the specified control already has a parent assigned,
-     * it will automatically be removed from that parent and inserted into the
-     * container.
+     * <b>Please note</b>: an exception is raised if the container contains a
+     * control with the same name as the given control. It is the responsibility
+     * of the caller to replace existing controls.
+     * <p/>
+     * <b>Also note</b> if the specified control already has a parent assigned,
+     * it will automatically be removed from that parent and inserted as a child
+     * of the container instead.
      * <p/>
      * This method is useful for developers needing to implement the
      * {@link org.apache.click.control.Container} interface but cannot for one
@@ -503,8 +507,7 @@ public class ContainerUtils {
      *     }
      *
      *     ...
-     * }
-     * </pre>
+     * } </pre>
      *
      * @param container the container to insert the given control into
      * @param control the control to add to the container
@@ -513,8 +516,7 @@ public class ContainerUtils {
      * @return the control that was added to the container
      *
      * @throws IllegalArgumentException if the control is null or if the control
-     * and container is the same instance or the container already contains
-     * a control with the same name or if a Field name is not defined
+     * and container is the same instance
      *
      * @throws IndexOutOfBoundsException if index is out of range
      * <tt>(index &lt; 0 || index &gt; container.getControls().size())</tt>
@@ -573,6 +575,113 @@ public class ContainerUtils {
     }
 
     /**
+     * Replace the current control in the container at the specified index, and
+     * return the newly added control.
+     * <p/>
+     * <b>Please note</b> if the new control already has a parent assigned,
+     * it will automatically be removed from that parent and inserted as a child
+     * of the container instead.
+     * <p/>
+     * This method is useful for developers needing to implement the
+     * {@link org.apache.click.control.Container} interface but cannot for one
+     * reason or another extend from {@link org.apache.click.control.AbstractContainer}.
+     * For example if the Container already extends from an existing <tt>Control</tt>
+     * such as a <tt>Field</tt>, it won't be possible to extend
+     * <tt>AbstractContainer</tt> as well. In such scenarios instead of
+     * reimplementing {@link org.apache.click.control.Container#replace(org.apache.click.Control, org.apache.click.Control) replace},
+     * one can delegate to this method.
+     * <p/>
+     * For example, a custom Container that extends <tt>Field</tt> and
+     * implements <tt>Container</tt> could implement the <tt>replace</tt> method
+     * as follows:
+     *
+     * <pre class="prettyprint">
+     * public class MyContainer extends Field implements Container {
+     *
+     *     public Control replace(Control currentControl, Control newControl) {
+     *         int controlIndex = getControls().indexOf(currentControl);
+     *         return ContainerUtils.replace(this, currentControl, newControl,
+     *             controlIndex, getControlMap());
+     *     }
+     *
+     *     ...
+     * } </pre>
+     *
+     * @param container the container to insert the new control into
+     * @param currentControl the control currently contained in the container
+     * @param newControl the control to replace the current control contained in
+     * the container
+     * @param controlIndex the index of the current control in the container
+     * @param controlMap the container's map of controls keyed on control name
+     * @return the new control that replaced the current control
+     *
+     * @throws IllegalArgumentException if the currentControl or newControl is
+     * null
+     * @throws IllegalStateException if the controlIndex = -1
+     */
+    public static Control replace(Container container, Control currentControl,
+        Control newControl, int controlIndex, Map<String, Control> controlMap) {
+
+        // Pre conditions start
+
+        // Current and new control is the same instance - exit early
+        if (currentControl == newControl) {
+            return newControl;
+        }
+
+        if (currentControl == null) {
+            throw new IllegalArgumentException("Null current control parameter");
+        }
+        if (newControl == null) {
+            throw new IllegalArgumentException("Null new control parameter");
+        }
+
+        if (controlIndex == -1) {
+            throw new IllegalStateException("Cannot replace the given control"
+                + " because it is not present in the container");
+        }
+
+        // Pre conditions end
+
+        // Check if control already has parent
+        // If parent references the given container, there is no need to remove it
+        Object currentParent = newControl.getParent();
+        if (currentParent != null && currentParent != container) {
+
+            // Remove new control from parent Page or Container
+            if (currentParent instanceof Page) {
+                ((Page) currentParent).removeControl(newControl);
+
+            } else if (currentParent instanceof Container) {
+                ((Container) currentParent).remove(newControl);
+            }
+
+            // Create warning message to users that the parent has been reset
+            logParentReset(container, newControl, currentParent);
+        }
+
+        // Note: set parent first since setParent might veto further processing
+        newControl.setParent(container);
+        currentControl.setParent(null);
+
+        // Replace currentControl with newControl
+        container.getControls().set(controlIndex, newControl);
+
+        // Update controlMap
+        String controlName = newControl.getName();
+        if (controlName != null) {
+            controlMap.put(controlName, newControl);
+        } else {
+            controlName = currentControl.getName();
+
+            if (controlName != null) {
+                controlMap.remove(controlName);
+            }
+        }
+        return newControl;
+    }
+
+    /**
      * Remove the given control from the container, returning <tt>true</tt> if
      * the control was found in the container and removed, or <tt>false</tt> if
      * the control was not found.
@@ -597,8 +706,7 @@ public class ContainerUtils {
      *     }
      *
      *     ...
-     * }
-     * </pre>
+     * } </pre>
      *
      * @param container the container to remove the given control from
      * @param control the control to remove from the container