Overview | Package | Class | Use | Tree | Deprecated | Index | Help
PREV CLASS NEXT CLASS | FRAMES NO FRAMES
SUMMARY: NESTED | FIELD | CONSTR | METHOD | DETAIL: FIELD | CONSTR | METHOD

## org.apache.tapestry5.corelib.components
Class AjaxFormLoop

    java.lang.Object
      org.apache.tapestry5.corelib.components.AjaxFormLoop

    @Events(value={"addRow","removeRow"})
    public class AjaxFormLoop

extends java.lang.Object

A special form of the `Loop` component that adds Ajax support to handle adding new rows and removing existing rows dynamically.

This component expects that the values being iterated over are entities that can be identified via a `ValueEncoder`, therefore you must either bind the "encoder" parameter to a ValueEncoder or use an entity type for the "value" parameter for which Tapestry can provide a ValueEncoder automatically.

Works with `AddRowLink` and `RemoveRowLink` components.

The addRow event will receive the context specified by the context parameter.

The removeRow event will receive the client-side value for the row being iterated.

See Also:
  `EventConstants.ADD_ROW`, `EventConstants.REMOVE_ROW`, `AddRowLink`, `RemoveRowLink`, `Loop`, `FormInjector`

Component Parameters
Name | Description | Type | Flags | Default | Default Prefix | Since
--- | --- | --- | --- | --- | --- | ---
addRow | A block to render after the loop as the body of the org.apache.tapestry5.corelib.components.FormInjector. This typically contains a org.apache.tapestry5.corelib.components.AddRowLink. | org.apache.tapestry5.Block |  | block:defaultAddRow | literal |
context | The context for the form loop (optional parameter). This list of values will be converted into strings and included in the URI. The strings will be coerced back to whatever their values are and made available to event handler methods. | Object |  |  | prop |
element | The element to render for each iteration of the loop. The default comes from the template, or "div" if the template did not specify an element. | String |  |  | literal |
encoder | A ValueEncoder used to convert server-side objects (provided by the "source" parameter) into unique client-side strings (typically IDs) and back. Note: this parameter may be OMITTED if Tapestry is configured to provide a ValueEncoder automatically for the type of property bound to the "value" parameter. | org.apache.tapestry5.ValueEncoder | Required, Not Null |  | prop |
show | Name of a function on the client-side Tapestry.ElementEffect object that is invoked to make added content visible. This is used with the org.apache.tapestry5.corelib.components.FormInjector component, when adding a new row to the loop. Leaving as null uses the default function, "highlight". | String |  |  | literal |
source | The objects to iterate over (passed to the internal Loop component). | Iterable | Required |  | prop |
value | The current value from the source. | Object | Required |  | prop |

Events:

addRow
removeRow

Examples:

  AjaxFormLoop renders an extensible, editable list of entities. It is intended for use with Master/Detail relationships (such as between an Order and a LineItem, in an e-commerce application). It allows new detail objects to be added on the server side, with corresponding new user interface added to the client side. Likewise, existing server-side objects can be removed, and the corresponding user interface also removed.

  AjaxFormLoop is dependent on the ability to extract an identifier (a primary key) from objects when rendering, and then retrieve the full object in a later request, such as when the form is submitted. This aligns well with an Object Relational Mapping layer such as Hibernate.

  This example has an address book of Persons, each of which has multiple Phones. It is, in fact, implemented in terms of Hibernate, using the tapestry-hibernate module.

  ### Person.java

      package org.example.addressbook.entities;

      import org.apache.tapestry5.beaneditor.NonVisual;
      import org.apache.tapestry5.beaneditor.Validate;
      import org.apache.tapestry5.beaneditor.Width;
      import org.apache.tapestry5.ioc.internal.util.CollectionFactory;

      import javax.persistence.*;
      import java.util.List;

      @Entity
      public class Person
      {
          @Id
          @GeneratedValue(strategy = GenerationType.IDENTITY)
          @NonVisual
          private long id;

          . . .

          @OneToMany(mappedBy = "person", cascade = CascadeType.ALL)
          private List<Phone> phones = new ArrayList<Phone>();

          . . .

          public List<Phone> getPhones()
          {
              return phones;
          }

          public void setPhones(List<Phone> phones)
          {
              this.phones = phones;
          }
      }

  ### PhoneType.java

      package org.example.addressbook.entities;

      public enum PhoneType
      {
          HOME, OFFICE, MOBILE, FAX, PAGER
      }

  ### Phone.java

      package org.example.addressbook.entities;

      import org.apache.tapestry5.beaneditor.NonVisual;
      import org.apache.tapestry5.beaneditor.Validate;
      import org.apache.tapestry5.beaneditor.Width;

      import javax.persistence.*;

      @Entity
      public class Phone
      {
          @Id
          @GeneratedValue(strategy = GenerationType.IDENTITY)
          @NonVisual
          private long id;

          @ManyToOne(optional = false)
          private Person person;

          private PhoneType type;

          @Column(nullable = true, length = 20)
          @Width(20)
          @Validate("required,maxlength=20")
          private String number;

          public long getId()
          {
              return id;
          }

          public void setId(long id)
          {
              this.id = id;
          }

          public Person getPerson()
          {
              return person;
          }

          public void setPerson(Person person)
          {
              this.person = person;
          }

          public PhoneType getType()
          {
              return type;
          }

          public void setType(PhoneType type)
          {
              this.type = type;
          }

          public String getNumber()
          {
              return number;
          }

          public void setNumber(String number)
          {
              this.number = number;
          }
      }

  Notice that the number field is nullable but required. This is because, when creating a new Phone instance, we have no number to fill in. However, a number is expected, and the user interface enforces that.

  ### Edit.tml

      <html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_0_0.xsd">
        <body>
          <h1>Edit ${person.firstName} ${person.lastName}</h1>

          <t:form t:id="form">

            <t:errors/>

            <div class="t-beaneditor">

              <t:beaneditor t:id="person"/>

              <h2>Phones</h2>

              <div t:type="ajaxformloop" t:id="phones" source="person.phones" value="phone">
                <t:select t:id="type" value="phone.type"/>
                <t:textfield t:id="number" value="phone.number"/>

                |

                <t:removerowlink>remove</t:removerowlink>

              </div>

              <p>
                <input type="submit" value="Update"/>
              </p>
            </div>
          </t:form>

        </body>
      </html>

  Here we're editing the direct properties of the Person object and adding a section below to allow the phones for the person to be edited. The AjaxFormLoop looks much like a Loop component here, except we must provide a PrimaryKeyEncoder object.

  Each row provides a RemoveRowLink component that will remove that row (from the server side, then on the client side).

  The AjaxFormLoop provides a default row for adding additional data rows.

  ### Edit.java

      package org.example.addressbook.pages;

      import org.apache.tapestry5.PrimaryKeyEncoder;
      import org.apache.tapestry5.annotations.PageActivationContext;
      import org.apache.tapestry5.annotations.Property;
      import org.apache.tapestry5.hibernate.annotations.CommitAfter;
      import org.apache.tapestry5.ioc.annotations.Inject;
      import org.example.addressbook.entities.Person;
      import org.example.addressbook.entities.Phone;
      import org.hibernate.Session;

      import java.util.List;

      public class Edit
      {
        @PageActivationContext
        @Property
        private Person person;

        @Property
        private Phone phone;

        @Inject
        private Session session;

        @CommitAfter
        public Object onSuccess()
        {
          return Index.class;
        }

        @CommitAfter
        Object onAddRowFromPhones()
        {
          Phone phone = new Phone();

          person.getPhones().add(phone);
          phone.setPerson(person);

          return phone;
        }

        @CommitAfter
        void onRemoveRowFromPhones(Phone phone)
        {
          session.delete(phone);
        }
      }

  The onAddRowFromPhones() event handler method's job is to add a new Phone instance and connect it to the Person. The @CommitAfter annotation ensures that changes are saved to the database (including generating a primary key for the new Phone instance).

  The flip side is onRemoveRowFromPhones(), which is the event handler when removing a row. The event handler method is passed the Phone object to remove. Again, it is necessary to commit the Hibernate transaction.

  AjaxFormLoop has to determine how to store an id for each editable row (remember that the client side can only really store strings, not full Java objects); in some cases you will have to bind the encoder parameter to a ValueEncoder object that is specific for your data type. However, this is not necessary for any Hibernate entities, as Tapestry automatically provides th ValueEncoder.

Constructor Summary
`AjaxFormLoop()`

Method Summary

Methods inherited from class java.lang.Object
`clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait`

Constructor Detail

### AjaxFormLoop

    public AjaxFormLoop()

Overview | Package | Class | Use | Tree | Deprecated | Index | Help
PREV CLASS NEXT CLASS | FRAMES NO FRAMES
SUMMARY: NESTED | FIELD | CONSTR | METHOD | DETAIL: FIELD | CONSTR | METHOD

Copyright © 2003-2012 The Apache Software Foundation.