DZone
Thanks for visiting DZone today,
Edit Profile
  • Manage Email Subscriptions
  • How to Post to DZone
  • Article Submission Guidelines
Sign Out View Profile
  • Post an Article
  • Manage My Drafts
Over 2 million developers have joined DZone.
Log In / Join
Refcards Trend Reports Events Over 2 million developers have joined DZone. Join Today! Thanks for visiting DZone today,
Edit Profile Manage Email Subscriptions Moderation Admin Console How to Post to DZone Article Submission Guidelines
View Profile
Sign Out
Refcards
Trend Reports
Events
Zones
Culture and Methodologies Agile Career Development Methodologies Team Management
Data Engineering AI/ML Big Data Data Databases IoT
Software Design and Architecture Cloud Architecture Containers Integration Microservices Performance Security
Coding Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones AWS Cloud
by AWS Developer Relations
Culture and Methodologies
Agile Career Development Methodologies Team Management
Data Engineering
AI/ML Big Data Data Databases IoT
Software Design and Architecture
Cloud Architecture Containers Integration Microservices Performance Security
Coding
Frameworks Java JavaScript Languages Tools
Testing, Deployment, and Maintenance
Deployment DevOps and CI/CD Maintenance Monitoring and Observability Testing, Tools, and Frameworks
Partner Zones
AWS Cloud
by AWS Developer Relations
The Latest "Software Integration: The Intersection of APIs, Microservices, and Cloud-Based Systems" Trend Report
Get the report
  1. DZone
  2. Data Engineering
  3. Databases
  4. Tapestry & AjaxFormLoop

Tapestry & AjaxFormLoop

Taha Siddiqi user avatar by
Taha Siddiqi
·
Aug. 01, 11 · Interview
Like (0)
Save
Tweet
Share
5.46K Views

Join the DZone community and get the full member experience.

Join For Free

Tapestry mailing list has a constant flow of newbie questions related to AjaxFormLoop component. This is a very powerful component but with some limitations that must be understood before using it.

AjaxFormLoop allows, in a limited way, dynamic addition of components to a form. These components are laid out inside the AjaxFormLoop. Each time the ‘Add New’ link is clicked, addRow event is triggered. This event requires the event handler to return a new ‘value’ bean. A new row is added to the loop with the given set of components and these components if form fields are bound to the newly instantiated bean.

During rendering, a hidden field is inserted into each row and its value is set to the string coercion of loop value parameter. On submit this value is coerced back to the value. The whole coercion part is handled by the ValueEncoder parameter (“encoder”)

A simple example is show below

public class SimpleLoop
{
   @Property
   @Persist
   private List<Foo> foos;

   @SuppressWarnings("unused")
   @Property
   private Foo foo;

   void onActivate()
   {
      if(foos == null)
      {
         foos = new ArrayList<Foo>();
      }

   }

   void setupRender(){
      foos.removeAll(Collections.singleton(null));
   }
   public ValueEncoder<Foo> getEncoder(){
      return new ValueEncoder<Foo>()
      {

         public String toClient(Foo foo)
         {
            return String.valueOf(foos.indexOf(foo));
         }

         public Foo toValue(String clientValue)
         {
            return foos.get(Integer.parseInt(clientValue));
         }

      };
   }

   Object onAddRow()
   {
      Foo newFoo = new Foo();
      foos.add(newFoo);
      return newFoo;
   }

   void onRemoveRow(Foo newFoo)
   {
      foos.set(foos.indexOf(newFoo), null);
   }

   void onSuccess()
   {
      foos.removeAll(Collections.singleton(null));
   }

}
<html xmlns:t='http://tapestry.apache.org/schema/tapestry_5_1_0.xsd'>

   <body>

      <t:if test='foos'>${foos}</t:if>

      <form t:type='form'>
         <div t:type='ajaxformloop' t:source='foos' value='foo' encoder='encoder'>
            <label t:type='label' t:for='bar'></label>:
            <input t:type='textfield' t:id='bar' t:value='foo.bar'/>
            <t:removerowlink>remove</t:removerowlink>
            <br />

         </div>
         <input type='submit' t:type='submit' value='Submit' />
      </form>

   </body>

</html>

The value encoder here uses the index as key. Usually a ValueEncoder is based on an entity’s primary key.

Now the real confusion about AjaxFormLoop. What if there is an ajax component within a row. If an ajax component makes an ajax request, that request is going to find the ‘value’ bean to be null as it is not persisted. What if we persist it ? If we persist, that will result in another problem. During an ajax request, the loop is not iterated again and so the persisted value is the last value that was read during rendering. All the components are temporarily bound to this last value. Therefore, persisting is a bad idea. The only way you can get around this problem is to not trust the ‘value’ field and instead use context in the ajax call. The context can then be used to get the actual value (e.g. fetching it from the database, in which case the context will be the primary key).

Here is an example where each row contains an eventlink with context set to a unique key. This key is then used for uniquely identify foo.

/**
 * AjaxFormLoop with ajax updates.
 */
public class LoopWithAjaxUpdates
{
   @Persist
   @Property
   private List<Foo> foos;

   @Property
   private Foo foo;

   @InjectComponent
   private Zone zone;

   void onActivate()
   {
      if(foos == null)
      {
         foos = new ArrayList<Foo>();
      }

   }

   public String getUniqueZoneId()
   {
      return "zone_" + foos.indexOf(foo);
   }

   public int getId()
   {
      return foos.indexOf(foo);
   }

   public ValueEncoder<Foo> getEncoder()
   {
      return new ValueEncoder<Foo>()
      {

         public String toClient(Foo foo)
         {
            return String.valueOf(foos.indexOf(foo));
         }

         public Foo toValue(String clientValue)
         {
            return foos.get(Integer.parseInt(clientValue));
         }

      };
   }

   Object onAddRow()
   {
      Foo newFoo = new Foo();
      foos.add(newFoo);
      return newFoo;
   }

   void onRemoveRow(Foo newFoo)
   {
      foos.set(foos.indexOf(newFoo), null);
   }

   void onSuccess()
   {
      foos.removeAll(Collections.singleton(null));
   }

   Object onZoneUpdate(int index)
   {
      foo = foos.get(index);
      return zone.getBody();
   }
}

 

<html xmlns:t='http://tapestry.apache.org/schema/tapestry_5_1_0.xsd'>

   <body>

      <t:if test='foos'>${foos}</t:if>

      <form t:type='form'>

         <div t:type='ajaxformloop' t:source='foos' value='foo' encoder='encoder'>

            <label t:type='label' t:for='bar'></label> :
            <input t:type='textfield' t:id='bar' t:value='foo.bar' />
            <a href='#' t:type='eventlink' t:event='zoneupdate'
                t:context='id' t:zone='${uniqueZoneId}' >update</a> |
            <t:removerowlink>remove</t:removerowlink> <
            <span t:type='zone' t:id='zone' id='${uniqueZoneId}'>
                 ${foo.bar}</span> >
            <br />

         </div>

         <input type='submit' t:type='submit' value='Submit' />
      </form>

   </body>

</html>

From http://tawus.wordpress.com/2011/07/26/tapestry-ajaxformloop/

Database

Opinions expressed by DZone contributors are their own.

Popular on DZone

  • REST vs. Messaging for Microservices
  • Cloud Performance Engineering
  • Building a Real-Time App With Spring Boot, Cassandra, Pulsar, React, and Hilla
  • How To Choose the Right Streaming Database

Comments

Partner Resources

X

ABOUT US

  • About DZone
  • Send feedback
  • Careers
  • Sitemap

ADVERTISE

  • Advertise with DZone

CONTRIBUTE ON DZONE

  • Article Submission Guidelines
  • Become a Contributor
  • Visit the Writers' Zone

LEGAL

  • Terms of Service
  • Privacy Policy

CONTACT US

  • 600 Park Offices Drive
  • Suite 300
  • Durham, NC 27709
  • support@dzone.com
  • +1 (919) 678-0300

Let's be friends: