<document>
    <body>
        <section name="Examples">

            <p>
                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.
            </p>

            <p>
                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.
            </p>


            <p>
                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.
            </p>

            <img src="ajaxformloop.png"/>


            <subsection name="Person.java">
                <source><![CDATA[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;
    }
}
]]></source>
            </subsection>

            <subsection name="PhoneType.java">
                <source><![CDATA[package org.example.addressbook.entities;

public enum PhoneType
{
    HOME, OFFICE, MOBILE, FAX, PAGER
}
]]></source>
            </subsection>

            <subsection name="Phone.java">
                <source><![CDATA[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;
    }
}
]]></source>
                <p>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.
                </p>
            </subsection>

            <subsection name="Edit.tml">
                <source><![CDATA[<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>]]></source>

                <p>
                    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.
                </p>

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

                <p>
                    The AjaxFormLoop provides a default row for adding additional data rows.
                </p>


            </subsection>

            <subsection name="Edit.java">
                <source><![CDATA[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);
  }
}
]]></source>
            </subsection>

            <p>
                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).
            </p>

            <p>
                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.
            </p>

            <p>
            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.
            </p>

        </section>


    </body>
</document>