Total Control CRUD

This example is like Easy CRUD but shows how CRUD can be done "by hand", ie. without using Tapestry's Grid, BeanEditor, and BeanDisplay components.
For example, instead of using the Grid component we use the Loop and Output components in a normal HTML table.
Create...

Id First Name Last Name Region Start Date Actions
5 Dishy Spon East Coast May 7, 2008 Review Update Delete
1 Humptywqd Dumptyasd East Coast May 25, 2018 Review Update Delete
3 Jack Sprat West Coast Feb 28, 2007 Review Update Delete
4 Jill Spill West Coast Feb 21, 2008 Review Update Delete
2 Mary Contrary East Coast Feb 24, 2008 Review Update Delete
Home

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">
<body class="container">
    <h3>Total Control CRUD</h3>
    
    This example is like <em>Easy CRUD</em> but shows how CRUD can be done "by hand", ie. without using Tapestry's Grid, BeanEditor, and BeanDisplay components.<br/>
    For example, instead of using the Grid component we use the Loop and Output components in a normal HTML table.

    <div class="eg">
        <t:pagelink page="together/totalcontrolcrud/person/PersonCreate">Create...</t:pagelink><br/><br/>
        
        <t:if test="errorMessage">
            <div class="alert alert-danger">
                ${errorMessage}
            </div>
        </t:if>
    
        <table class="table table-bordered table-striped table-hover table-condensed">
            <thead>
                <tr>
                    <th>Id</th>
                    <th>First Name</th>
                    <th>Last Name</th>
                    <th>Region</th>
                    <th>Start Date</th>
                    <th>Actions</th>
                </tr>
            </thead>
            <tbody>
                <tr t:type="Loop" t:source="persons" t:value="person" class="prop:evenodd.next">
                    <td>${person.id}</td>
                    <td>${person.firstName}</td>
                    <td>${person.lastName}</td>
                    <td>${personRegion}</td>
                    <td><t:output value="person.startDate" format="dateFormat"/></td>
                    <td>
                        <t:pagelink page="together/totalcontrolcrud/person/PersonReview" context="person.id">Review</t:pagelink>
                        <t:pagelink page="together/totalcontrolcrud/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>
                    </td>
                </tr>
            </tbody>
        </table>
    </div>

    <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:tabgroup>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/together/totalcontrolcrud/Persons.tml"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/together/totalcontrolcrud/Persons.properties"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/together/totalcontrolcrud/Persons.java"/>
        <t:sourcecodetab src="/web/src/main/resources/META-INF/assets/css/examples/plain.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"/>
    </t:tabgroup>
</body>
</html>


## These enum conversions could be moved to the central message properties file called app.properties
## The structure we've chosen (enum class name, dot, enum value) is the same as expected by the Select component.
Regions.EAST_COAST=East Coast
Regions.WEST_COAST=West Coast


package jumpstart.web.pages.together.totalcontrolcrud;

import java.text.DateFormat;
import java.text.Format;
import java.util.List;
import java.util.Locale;

import javax.ejb.EJB;

import jumpstart.business.domain.person.Person;
import jumpstart.business.domain.person.Regions;
import jumpstart.business.domain.person.iface.IPersonFinderServiceLocal;
import jumpstart.business.domain.person.iface.IPersonManagerServiceLocal;
import jumpstart.util.ExceptionUtil;
import jumpstart.web.commons.EvenOdd;

import org.apache.tapestry5.PersistenceConstants;
import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.annotations.Persist;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.ioc.Messages;
import org.apache.tapestry5.ioc.annotations.Inject;

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

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

    // Screen fields

    @Property
    private List<Person> persons;

    @Property
    private Person person;

    @Property
    @Persist(PersistenceConstants.FLASH)
    private String errorMessage;

    @Property
    private EvenOdd evenOdd;

    // Generally useful bits and pieces

    @EJB
    private IPersonFinderServiceLocal personFinderService;

    @EJB
    private IPersonManagerServiceLocal personManagerService;

    @Inject
    private Messages messages;

    @Inject
    private Locale currentLocale;

    // The code

    void setupRender() {
        persons = personFinderService.findPersons(MAX_RESULTS);
        evenOdd = new EvenOdd();
    }

    void onDelete(Long id, Integer version) {
        
        if (demoModeStr != null && demoModeStr.equals("true")) {
            errorMessage = "Sorry, but this function is not allowed in Demo mode.";
            return;
        }

        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);
        }
    }

    public String getPersonRegion() {
        return messages.get(Regions.class.getSimpleName() + "." + person.getRegion().name());
    }

    public Format getDateFormat() {
        return DateFormat.getDateInstance(DateFormat.MEDIUM, currentLocale);
    }
}


.eg {
                margin: 20px 0;
                padding: 14px;
                border: 1px solid #ddd;
                border-radius: 6px;
                -webkit-border-radius: 6px;
                -mox-border-radius: 6px;
}


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.
 */
@Entity
@SuppressWarnings("serial")
public class Person implements Serializable {

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

    @Version
    @Column(nullable = false)
    private Integer version;

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

    @Column(length = 10, nullable = false)
    @NotNull
    @Size(max = 10)
    private String lastName;
    
    @Enumerated(EnumType.STRING)
    @NotNull
    private Regions region;

    @Temporal(TemporalType.DATE)
    @NotNull
    private Date startDate;

    public String toString() {
        final String DIVIDER = ", ";
        
        StringBuilder buf = new StringBuilder();
        buf.append(this.getClass().getSimpleName() + ": ");
        buf.append("[");
        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);
        buf.append("]");
        return buf.toString();
    }

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

    public Person(String firstName, String lastName, Regions region, Date startDate) {
        super();
        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
    
    @Override
    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

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

    @PrePersist
    @PreUpdate
    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 {
    EAST_COAST, WEST_COAST;
}