This example demonstrates how Tapestry can handle CRUD (Create, Review, Update, Delete) with ease.
It uses the Grid, BeanEditor, and BeanDisplay components which are especially great for prototyping.

IdFirst NameLast NameRegionStart DateAction
3 11 km West Coast Feb 28, 2007 Review Update Delete
5 87888Poop De scoop East Coast Jul 7, 2007 Review Update Delete
1 Acme2 456 East Coast Jun 19, 1952 Review Update Delete
2 Mary dgzfgdf East Coast Feb 29, 2008 Review Update Delete
4 yuno 3clover West Coast Feb 12, 2008 Review Update Delete

The source for IPersonFinderServiceLocal, IPersonManagerServiceLocal, and @EJB is shown in the Session Beans and @EJB examples.

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN" "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<!-- We need a doctype to allow us to use special characters like &nbsp; 
     We use a "strict" DTD to make IE follow the alignment rules. -->
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_4.xsd" xmlns:p="tapestry:parameter">
<body class="container">
    <h3>Easy CRUD</h3>
    <noscript class="js-recommended">

    This example demonstrates how Tapestry can handle CRUD (Create, Review, Update, Delete) with ease.<br/>
    It uses the Grid, BeanEditor, and BeanDisplay components which are especially great for prototyping.

    <div class="eg">
        <t:pagelink page="together/easycrud/person/PersonCreate">Create...</t:pagelink><br/><br/>
        <t:if test="errorMessage">
            <div class="alert alert-danger">
        <t:grid source="persons" row="person" include="id,firstName,lastName,region,startDate" add="action">
                <t:pagelink page="together/easycrud/person/PersonReview" context="person.id">Review</t:pagelink>
                <t:pagelink page="together/easycrud/person/PersonUpdate" context="person.id">Update</t:pagelink>
                <t:eventlink event="Delete" context="[person.id,person.version]" 
                    t:mixins="Confirm" Confirm.message="Delete ${person.firstName} ${person.lastName}?">Delete</t:eventlink>

    <t:pagelink page="Index">Home</t:pagelink><br/><br/>
    The source for IPersonFinderServiceLocal, IPersonManagerServiceLocal, and @EJB is shown in the Session Beans and @EJB examples.<br/><br/>

        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/together/easycrud/Persons.tml"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/together/easycrud/Persons.java"/>
        <t:sourcecodetab src="/web/src/main/resources/META-INF/assets/css/examples/css/js.css"/>
        <t:sourcecodetab src="/business/src/main/java/jumpstart/business/domain/person/Person.java"/>
        <t:sourcecodetab src="/business/src/main/java/jumpstart/business/domain/person/Regions.java"/>

package jumpstart.web.pages.together.easycrud;

import java.util.List;

import javax.ejb.EJB;

import jumpstart.business.domain.person.Person;
import jumpstart.business.domain.person.iface.IPersonFinderServiceLocal;
import jumpstart.business.domain.person.iface.IPersonManagerServiceLocal;
import jumpstart.util.ExceptionUtil;

import org.apache.tapestry5.PersistenceConstants;
import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.annotations.Persist;
import org.apache.tapestry5.annotations.Property;

@Import(stylesheet = "css/examples/js.css")
public class Persons {

    private final String demoModeStr = System.getProperty("jumpstart.demo-mode");
    static private final int MAX_RESULTS = 30;

    // Screen fields

    private List<Person> persons;

    private Person person;

    private String errorMessage;

    // Generally useful bits and pieces

    private IPersonFinderServiceLocal personFinderService;

    private IPersonManagerServiceLocal personManagerService;

    // The code

    void setupRender() {
        persons = personFinderService.findPersons(MAX_RESULTS);

    void onDelete(Long id, Integer version) {

        if (demoModeStr != null && demoModeStr.equals("true")) {
            errorMessage = "Sorry, but this function is not allowed in Demo mode.";

        try {
            personManagerService.deletePerson(id, version);
        catch (Exception e) {
            // Display the cause. In a real system we would try harder to get a user-friendly message.
            errorMessage = ExceptionUtil.getRootCauseMessage(e);

The file was not found. Path given was /WEB-INF/sourcecode/web/src/main/resources/META-INF/assets/css/examples/css/js.css

package jumpstart.business.domain.person;

import java.io.Serializable;
import java.util.Date;

import javax.persistence.Column;
import javax.persistence.Entity;
import javax.persistence.EnumType;
import javax.persistence.Enumerated;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.PrePersist;
import javax.persistence.PreUpdate;
import javax.persistence.Temporal;
import javax.persistence.TemporalType;
import javax.persistence.Version;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

 * The Person entity.
public class Person implements Serializable {

    @GeneratedValue(strategy = GenerationType.AUTO)
    @Column(nullable = false)
    private Long id;

    @Column(nullable = false)
    private Integer version;

    @Column(length = 10, nullable = false)
    @Size(max = 10)
    private String firstName;

    @Column(length = 10, nullable = false)
    @Size(max = 10)
    private String lastName;
    private Regions region;

    private Date startDate;

    public String toString() {
        final String DIVIDER = ", ";
        StringBuilder buf = new StringBuilder();
        buf.append(this.getClass().getSimpleName() + ": ");
        buf.append("id=" + id + DIVIDER);
        buf.append("version=" + version + DIVIDER);
        buf.append("firstName=" + firstName + DIVIDER);
        buf.append("lastName=" + lastName + DIVIDER);
        buf.append("region=" + region + DIVIDER);
        buf.append("startDate=" + startDate);
        return buf.toString();

    // Default constructor is required by JPA.
    public Person() {

    public Person(String firstName, String lastName, Regions region, Date startDate) {
        this.firstName = firstName;
        this.lastName = lastName;
        this.region = region;
        this.startDate = startDate;

    // The need for an equals() method is discussed at http://www.hibernate.org/109.html
    public boolean equals(Object obj) {
        return (obj == this) || (obj instanceof Person) && id != null && id.equals(((Person) obj).getId());

    // The need for a hashCode() method is discussed at http://www.hibernate.org/109.html

    public int hashCode() {
        return id == null ? super.hashCode() : id.hashCode();

    public void validate() throws ValidationException {


    public Long getId() {
        return id;

    public Integer getVersion() {
        return version;

    public void setVersion(Integer version) {
        this.version = version;

    public String getFirstName() {
        return firstName;

    public void setFirstName(String firstName) {
        this.firstName = firstName;

    public String getLastName() {
        return lastName;

    public void setLastName(String lastName) {
        this.lastName = lastName;

    public Regions getRegion() {
        return region;

    public void setRegion(Regions region) {
        this.region = region;

    public Date getStartDate() {
        return startDate;

    public void setStartDate(Date startDate) {
        this.startDate = startDate;


package jumpstart.business.domain.person;

public enum Regions {