Forms Explained (1)

Here is a blow-by-blow explanation of the previous example.
On submit, Form does client-side validation that the TextFields satisfy @NotNull and @Size(max=10). If it passes validation, then...

Form sends a component event request. It's an HTTP POST with a URL similar to http://thehost/jumpstart/examples/input/forms1.names and this is how the server reacts:

Q: Why would Tapestry redirect the browser to Page2 instead of just rendering and returning Page2?
A: Tapestry uses the Post/Redirect/Get pattern. The benefit of Post/Redirect/Get is that when the next page shows, its URL will NOT be the URL that triggered the event. Instead, it will be a URL that is safe to reload, or bookmark, or return to with the Back button. The cost is 2 trips to the server, but it's worth it.

Q: Why do we use the long method name onValidateFromNames() instead of just onValidate()?
A; To distinguish between validate bubbled up from Form and validate bubbled up from each TextField (firstName and lastName). We explore this more in the example The Validate Event.

References: .form-horizontal, .col-, Form, TextField, Submit, Forms and Validation, Bean Validation, Page Navigation, Component Events.

Home


<!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>Forms Explained (1)</h3>
    
    Here is a blow-by-blow explanation of the previous example.
    
    <div class="eg">
        <t:form t:id="names" class="form-horizontal">
            <div class="form-group">
                <t:label for="firstName" class="col-sm-2"/>
                <div class="col-sm-4">
                    <t:textfield t:id="firstName"/>
                </div>
            </div>
            <div class="form-group">
                <t:label for="lastName" class="col-sm-2"/>
                <div class="col-sm-4">
                    <t:textfield t:id="lastName"/>
                </div>
            </div>
            <div class="form-group">
                <div class="col-sm-4 col-sm-offset-2">
                    <t:submit/>
                </div>
            </div>
        </t:form>
     </div>

    On submit, Form does <em>client-side validation</em> that the TextFields satisfy <code>@NotNull</code> and 
    <code>@Size(max=10)</code>. If it passes validation, then...<br/><br/>
     
    Form sends a <strong>component event request</strong>. It's an HTTP POST with a URL similar to 
    <code>http://thehost/jumpstart/examples/input/forms1.names</code> and this is how the server reacts:<br/><br/>

    <ul>
        <li>Tapestry activates the page <em>examples/input/forms1</em>, and notifies the component <em>names</em>, which is a Form.</li>
        <li>Form "bubbles up" a series of events: <em>prepareForSubmit</em>, <em>validate</em>, <em>success</em> (or <em>failure</em>), etc.</li>   
        <li>This page has handlers for two events: <code>onValidateFromNames()</code> and <code>onSuccess()</code>.</li>
        <li>Before <code>onValidateFromNames()</code> is triggered, Tapestry validates the TextFields satisfy <code>@NotNull</code> and <code>@Size(max=10)</code>.</li>
        <li>If <code>onValidateFromNames()</code> records an error, then the next event from the form will be <em>failure</em>, not <em>success</em>.</li>
        <li><code>onSuccess()</code> returns a page object, <code>Page2</code>. 
            Tapestry generates a <strong>page render request</strong> to Page2, and redirects the browser to it.</li>
        <li>We don't have an <code>onFailure()</code>, so Tapestry will return the current page, complete with error messages.</li>
    </ul>
    
    Q: Why would Tapestry <strong>redirect</strong> the browser to Page2 instead of just rendering and returning Page2?<br/>
    A: Tapestry uses the <a href="http://en.wikipedia.org/wiki/Post/Redirect/Get">Post/Redirect/Get</a> pattern. 
    The benefit of Post/Redirect/Get is that when the next page shows, its URL will NOT be the URL that triggered the event.  
    Instead, it will be a URL that is safe to reload, or bookmark, or return to with the Back button. 
    The cost is 2 trips to the server, but it's worth it.<br/><br/>

    Q: Why do we use the long method name <code>onValidateFromNames()</code> instead of just <code>onValidate()</code>? <br/>
    A; To distinguish between <em>validate</em> bubbled up from Form and <em>validate</em> bubbled up from each TextField 
    (firstName and lastName). We explore this more in the example The Validate Event.<br/><br/>
    
    References:
    <a href="http://getbootstrap.com/css/#forms-horizontal">.form-horizontal</a>, 
    <a href="http://getbootstrap.com/css/#grid">.col-</a>, 
    <a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/corelib/components/Form.html">Form</a>, 
    <a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/corelib/components/TextField.html">TextField</a>, 
    <a href="http://tapestry.apache.org/5.4/apidocs/org/apache/tapestry5/corelib/components/Submit.html">Submit</a>, 
    <a href="http://tapestry.apache.org/forms-and-validation.html">Forms and Validation</a>, 
    <a href="http://tapestry.apache.org/bean-validation.html">Bean Validation</a>,
    <a href="http://tapestry.apache.org/page-navigation.html">Page Navigation</a>, 
    <a href="http://tapestry.apache.org/component-events.html">Component Events</a>.<br/><br/>

    <t:pagelink t:page="Index">Home</t:pagelink><br/><br/>

    <t:tabgroup>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/input/FormsExplained1.tml"/>
        <t:sourcecodetab src="/web/src/main/java/jumpstart/web/pages/examples/input/FormsExplained1.java"/>
        <t:sourcecodetab src="/web/src/main/resources/META-INF/assets/css/examples/plain.css"/>
    </t:tabgroup>
</body>
</html>


package jumpstart.web.pages.examples.input;

import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;

import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.annotations.InjectComponent;
import org.apache.tapestry5.annotations.InjectPage;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.corelib.components.Form;
import org.apache.tapestry5.corelib.components.TextField;

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

    // Screen fields

    @Property
    @NotNull
    @Size(max = 10)
    private String firstName;

    @Property
    @NotNull
    @Size(max = 10)
    private String lastName;

    // Other pages

    @InjectPage
    private FormsExplained2 page2;

    // Generally useful bits and pieces

    @InjectComponent("names")
    private Form form;

    @InjectComponent("firstName")
    private TextField firstNameField;

    // The code

    void onValidateFromNames() {

        // Note, this method is triggered even if server-side validation has already found error(s).

        if (firstName != null && firstName.equals("Acme")) {
            form.recordError(firstNameField, "First Name must not be Acme.");
        }
    }

    Object onSuccess() {
        page2.set(firstName, lastName);
        return page2;
    }
}


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