Uploaded image for project: 'Tapestry 5'
  1. Tapestry 5
  2. TAP5-280

Fields injected by AjaxFormLoop sometimes have incorrect values after submits where validation fails.

    XMLWordPrintableJSON

Details

    • Bug
    • Status: Closed
    • Major
    • Resolution: Abandoned
    • 5.0.15
    • None
    • tapestry-core

    Description

      Using the below .tml, .java. and .js files:
      1. Click Add Group.
      2. Add values "a" and "1" in the upper group, "c" and "3" in the lower group.
      3. Click Save.
      4. Add a row in the upper group and enter "b" and "b".
      5. Click Save.

      ==> Values "c" and "3" are copied into the last row of the upper group (validation has failed).

      The only values ever lost/copied over are values of rows that were inserted via the addRowLink. Upon failure of validation, it looks as though the ValidationTracker is trying to look up the values for the new rows using the control id from the render phase, but the submission had "mangled"/"uniquified" control ids (with :?????? added) for all the inputs, so that is how these values are stored in the ValidationTracker.

      AjaxFormLoopTest.tml:

      <html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
      <head/>
      <body>
      <h1>Nested AjaxFormLoop Test</h1>
      <t:form t:id="formLoopTestForm" t:clientValidation="false">
      <div t:type="AjaxFormLoop"
      t:id="outerFormLoop"
      t:source="outerList"
      t:value="tempOuter"
      t:encoder="outerEncoder"
      >
      <table style="background-color: #ece9d8; margin: 2px 0;">
      <tbody>
      <tr>
      <th>String</th>
      <th>Number</th>
      </tr>
      <tr t:type="AjaxFormLoop"
      t:id="innerFormLoop"
      t:source="tempOuter.list"
      t:value="tempInner"
      t:context="tempOuter.id"
      t:encoder="innerEncoder"
      >
      <td><t:textField t:value="tempInner.string"/></td>
      <td><t:textField t:value="tempInner.number"/></td>
      <t:parameter name="addRow">
      <td colspan="2">
      <t:addRowLink>Add Row</t:addRowLink>
      </td>
      </t:parameter>
      </tr>
      </tbody>
      </table>
      <t:parameter name="addRow">
      <t:addRowLink>Add Group</t:addRowLink>
      </t:parameter>
      </div>

      <br/>

      <t:submit t:id="save" value="Save"/>
      <t:actionLink t:id="clear">Clear</t:actionLink>
      </t:form>
      </body>
      </html>

      AjaxFormLoopTest.java:

      @IncludeJavaScriptLibrary("context:javascript/script.js")
      public class AjaxFormLoopTest {
      @Persist
      @Property
      private List<Outer> outerList;

      @Property
      private Outer tempOuter;

      @Property
      private Inner tempInner;

      void beginRender() {
      if (outerList == null)

      { outerList = new ArrayList<Outer>(); Outer outer = new Outer(0); outerList.add(outer); }

      }

      Object onAddRowFromOuterFormLoop()

      { int nextId = outerList.size() + 1; Outer outer = new Outer(nextId); outerList.add(outer); return outer; }

      Object onAddRowFromInnerFormLoop(Integer outerId)

      { Outer outer = getOuterById(outerId); List<Inner> list = outer.getList(); Inner inner = new Inner(); list.add(inner); inner.setOuter(outer); return inner; }

      void onActionFromClear()

      { outerList = null; }

      ////////////////////////////////
      // Encoders
      ////////////////////////////////

      private final PrimaryKeyEncoder<Integer, Outer> outerEncoder =
      new PrimaryKeyEncoder<Integer, Outer>() {
      public void prepareForKeys(List arg0) {}
      public Integer toKey(Outer outer)

      { return outer.getId(); }


      public Outer toValue(Integer key)

      { Outer ret = getOuterById(key); if (ret == null) throw new RuntimeException("outerEncoder could not retreive item for key:" + key); return ret; }

      };
      public PrimaryKeyEncoder<Integer, Outer> getOuterEncoder()

      { return outerEncoder; }

      private final PrimaryKeyEncoder<EncoderKey, Inner> innerEncoder =
      new PrimaryKeyEncoder<EncoderKey, Inner>() {
      public void prepareForKeys(List arg0) {}
      public EncoderKey toKey(Inner inner)

      { Outer outer = inner.getOuter(); return new EncoderKey(outer.getId(), outer.getList().indexOf(inner)); }


      public Inner toValue(EncoderKey key) {
      Inner ret = null;

      Outer outer = getOuterById(key.getGroupId());
      if (outer != null)

      { ret = outer.getList().get(key.getIndex()); }

      if (ret == null) throw new RuntimeException("innerEncoder could not retreive item for key:" + key);
      return ret;
      }
      };
      public PrimaryKeyEncoder<EncoderKey, Inner> getInnerEncoder()

      { return innerEncoder; }

      ////////////////////////////////
      // Helpers
      ////////////////////////////////
      private Outer getOuterById(Integer outerId) {
      Outer outer = null;
      for (Outer o : outerList) {
      if (o.getId().equals(outerId))

      { outer = o; break; }

      }

      return outer;
      }

      // Referenced classes
      public static class Outer {
      private Integer id;
      private List<Inner> list;

      public Outer(Integer id)

      { this.id = id; list = new ArrayList<Inner>(); Inner inner = new Inner(); inner.setOuter(this); list.add(inner); }

      public Integer getId()

      { return id; }

      public void setId(Integer id)

      { this.id = id; }

      public List<Inner> getList()

      { return list; }

      public void setList(List<Inner> list)

      { this.list = list; }

      }

      public static class Inner {
      private String string;
      private Integer number;
      private Outer outer;

      public String getString()

      { return string; }

      public void setString(String string)

      { this.string = string; }

      public Integer getNumber()

      { return number; }

      public void setNumber(Integer number)

      { this.number = number; }

      public Outer getOuter()

      { return outer; }

      public void setOuter(Outer outer)

      { this.outer = outer; }

      }

      public static class EncoderKey implements Serializable{
      private static final long serialVersionUID = 1L;

      private final Integer groupId;
      private final Integer index;

      public final static String SEPARATOR = "_";

      public EncoderKey(Integer q, Integer i)

      { this.groupId = q; this.index = i; }

      public Integer getGroupId()

      { return groupId; }

      public Integer getIndex()

      { return index; }

      @Override
      public boolean equals(Object obj){
      if (!(obj instanceof EncoderKey))

      { return super.equals(obj); }

      EncoderKey other = (EncoderKey)obj;
      return this.groupId.equals(other.groupId) && this.index.equals(other.index);
      }

      @Override
      public int hashCode()

      { return this.groupId * this.index * 31; }

      public int compareTo(EncoderKey other)

      { int test = this.groupId.compareTo(other.groupId); return test != 0 ? test : this.index.compareTo(other.index); }

      public String toString()

      { return "Group: " + groupId + " Index: " + index; }

      public String encode()

      { return this.getGroupId()+EncoderKey.SEPARATOR+this.getIndex(); }

      }
      }

      script.js:

      /** Override to prevent client-side validation. */
      Tapestry.Initializer.validate = function (field, specs)

      { return; }

      ;

      Attachments

        1. MyAjaxFormLoop.java
          14 kB
          Robson Miranda

        Issue Links

          Activity

            People

              Unassigned Unassigned
              shawn.brownfield Shawn Brownfield
              Votes:
              3 Vote for this issue
              Watchers:
              5 Start watching this issue

              Dates

                Created:
                Updated:
                Resolved: