tapestry-commits mailing list archives

Site index · List index
Message view « Date » · « Thread »
Top « Date » · « Thread »
From conflue...@apache.org
Subject [CONF] Apache Tapestry > Forms
Date Mon, 11 Oct 2010 11:37:00 GMT
<html>
<head>
    <base href="https://cwiki.apache.org/confluence">
            <link rel="stylesheet" href="/confluence/s/1810/9/12/_/styles/combined.css?spaceKey=TAPESTRY&amp;forWysiwyg=true"
type="text/css">
    </head>
<body style="background: white;" bgcolor="white" class="email-body">
<div id="pageContent">
<div id="notificationFormat">
<div class="wiki-content">
<div class="email">
    <h2><a href="https://cwiki.apache.org/confluence/display/TAPESTRY/Forms">Forms</a></h2>
    <h4>Page <b>edited</b> by             <a href="https://cwiki.apache.org/confluence/display/~ccordenier">Christophe
Cordenier</a>
    </h4>
        <br/>
                         <h4>Changes (7)</h4>
                                 
    
<div id="page-diffs">
            <table class="diff" cellpadding="0" cellspacing="0">
            <tr><td class="diff-snipped" >...<br></td></tr>
            <tr><td class="diff-unchanged" >}{noformat} <br> <br></td></tr>
            <tr><td class="diff-changed-lines" >So ... why is the class named
&quot;CreateAddress&quot; and not simply &quot;Create&quot;? Actually, we
could have named it &quot;Create&quot;, and the application would still work, but
the longer _class_ name is equally valid. Tapestry noticed the redundancy in the class name:
<span class="diff-deleted-words"style="color:#999;background-color:#fdd;text-decoration:line-through;">}}org.apache.tapestry5.tutorial.pages.{{{_}address{_}}}.Create{{_Address_</span>
<span class="diff-added-words"style="background-color: #dfd;">org.apache.tapestry5.tutorial.pages.{{{_}address{_}}}.Create{_}Address_</span>
and just stripped it out. <br></td></tr>
            <tr><td class="diff-unchanged" > <br></td></tr>
            <tr><td class="diff-changed-lines" >Eventually, your application will
probably have more entities: perhaps you&#39;ll have a &quot;user/Create&quot;
page and a &quot;payment/Create&quot; page and an &quot;account/Create&quot;
page. Now, you _could_ have a bunch of different classes named <span class="diff-deleted-words"style="color:#999;background-color:#fdd;text-decoration:line-through;">}}Create{{</span>
<span class="diff-added-words"style="background-color: #dfd;">Create</span> spread
across a number of different packages. That&#39;s legal Java, but it isn&#39;t ideal.
You may find yourself accidentally editing the Java code for creating an Account when your
really want to be <span class="diff-changed-words">edit<span class="diff-deleted-chars"style="color:#999;background-color:#fdd;text-decoration:line-through;">t</span>ing</span>
the code for creating a Payment. <br></td></tr>
            <tr><td class="diff-unchanged" > <br></td></tr>
            <tr><td class="diff-changed-lines" >Tapestry is encouraging you to
use a more descriptive name: <span class="diff-deleted-words"style="color:#999;background-color:#fdd;text-decoration:line-through;">}}Create{{_Address_</span>
<span class="diff-added-words"style="background-color: #dfd;">Create{_}Address_</span>
not just <span class="diff-deleted-words"style="color:#999;background-color:#fdd;text-decoration:line-through;">}}Create{{,</span>
<span class="diff-added-words"style="background-color: #dfd;">Create,</span> but
it isn&#39;t making you pay the cost (in terms of longer, uglier URLs). The URL to access
the page will still be [http://localhost:8080/tutorial1/address/create]. <br></td></tr>
            <tr><td class="diff-unchanged" > <br>Another note: Index pages
work in folders as well. A class named org.apache.tapestry5.tutorial.pages.address.AddressIndex
would be given the name &quot;address/Index&quot;. However, Tapestry has special rules
for pages named &quot;Index&quot; and the render URL would be [http://localhost:8080/tutorial1/address/]
. In other words, you can place Index pages in any folder and Tapestry will build a short
URL for that page ... and you _don&#39;t_ have to keep naming the classes Index (it&#39;s
confusing to have many classes with the same name, even across multiple packages); instead,
you can name each index page after the package that contains it. Tapestry users a smart _convention_
to keep it all straight and generate short, to the point URLs. <br></td></tr>
            <tr><td class="diff-snipped" >...<br></td></tr>
            <tr><td class="diff-unchanged" >  private Address address{noformat}
<br> <br></td></tr>
            <tr><td class="diff-changed-lines" >When you refresh the page, you&#39;ll
see the following: <span class="diff-deleted-words"style="color:#999;background-color:#fdd;text-decoration:line-through;">!address-v1.png|border=1,width=760,height=439!</span>
<br></td></tr>
            <tr><td class="diff-unchanged" > <br></td></tr>
            <tr><td class="diff-added-lines" style="background-color: #dfd;">!address-v1.png|border=1,width=760,height=439!
<br> <br></td></tr>
            <tr><td class="diff-unchanged" >Initial version of the create address
form <br> <br></td></tr>
            <tr><td class="diff-snipped" >...<br></td></tr>
            <tr><td class="diff-unchanged" >zip-regexp-message=Zip Codes are five
or nine digits.  Example: 02134 or 90125-1655.{noformat} <br> <br></td></tr>
            <tr><td class="diff-changed-lines" >Refresh the page and submit again:
<span class="diff-deleted-words"style="color:#999;background-color:#fdd;text-decoration:line-through;">!address-v8.png|border=1,width=760,height=482!</span>
<br></td></tr>
            <tr><td class="diff-unchanged" > <br></td></tr>
            <tr><td class="diff-added-lines" style="background-color: #dfd;">
!address-v8.png|border=1,width=760,height=482! <br></td></tr>
            <tr><td class="diff-unchanged" > <br> <br></td></tr>
            <tr><td class="diff-snipped" >...<br></td></tr>
        </table>
</div>                            <h4>Full Content</h4>
                    <div class="notificationGreySide">
        <h2><a name="Forms-Chapter4%3AFormsinTapestry"></a>Chapter 4: Forms
in Tapestry</h2>

<p>In the previous chapters, we saw how Tapestry can handle simple links, even links
that pass information in the URL. In this chapter, we'll see how Tapestry can do the same,
and quite a bit more, for HTML forms.</p>

<p>Form support in Tapestry is deep and rich, more than can be covered in a single chapter.
However, we can show the basics, including some very common development patterns. To get started,
let's create a simple address book application.</p>

<p>We'll start with the entity data, a simple object to store the information we'll
need. These classes go in an }}entities{{ sub-package. Unlike the use of the }}pages{{ sub-package
(for page component classes), this is not enforced by Tapestry; it's just a convention (but
as we'll see shortly, a handy one).</p>

<p><b>src/main/java/org/apache/tapestry5/tutorial/entities/Address.java:</b></p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent
panelContent">
<pre>package org.apache.tapestry5.tutorial.entities;

import org.apache.tapestry5.tutorial.data.Honorific;

public class Address
{
  private Honorific honorific;

  private String firstName;

  private String lastName;

  private String street1;

  private String street2;

  private String city;

  private String state;

  private String zip;

  private String email;

  private String phone;

  public String getCity()
  {
    return city;
  }

  public String getEmail()
  {
    return email;
  }

  public String getFirstName()
  {
    return firstName;
  }

  public Honorific getHonorific()
  {
    return honorific;
  }

  public String getLastName()
  {
    return lastName;
  }

  public String getPhone()
  {
    return phone;
  }

  public String getState()
  {
    return state;
  }

  public String getStreet1()
  {
    return street1;
  }

  public String getStreet2()
  {
    return street2;
  }

  public String getZip()
  {
    return zip;
  }

  public void setCity(String city)
  {
    this.city = city;
  }

  public void setEmail(String email)
  {
    this.email = email;
  }

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

  public void setHonorific(Honorific honorific)
  {
    this.honorific = honorific;
  }

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

  public void setPhone(String phone)
  {
    this.phone = phone;
  }

  public void setState(String state)
  {
    this.state = state;
  }

  public void setStreet1(String street1)
  {
    this.street1 = street1;
  }

  public void setStreet2(String street2)
  {
    this.street2 = street2;
  }

  public void setZip(String zip)
  {
    this.zip = zip;
  }
}</pre>
</div></div>

<p>It's just a collection of getter and setter methods. We also need to define the enum
type, Honorific:</p>

<p><b>src/main/java/org/apache/tapestry5/tutorial/data/Honorific.java:</b></p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent
panelContent">
<pre>package org.apache.tapestry5.tutorial.data;

public enum Honorific
{
  MR, MRS, MISS, DR
}</pre>
</div></div>

<h3><a name="Forms-AddressPages"></a>Address Pages</h3>

<p>We're probably going to create a few pages related to addresses: pages for creating
them, for editing them, for searching and listing them. We'll create a sub-folder, address,
to hold them. Let's get started on the first of these pages, "address/Create" (that's the
real name, including the slash &#8212; we'll see in a minute how that maps to classes
and templates).</p>

<p>First, we'll update the Index.tml template, to create a link for creating a new page:</p>

<p><b>src/main/webapp/Index.tml:</b></p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent
panelContent">
<pre>    &lt;h1&gt;Address Book&lt;/h1&gt;

    &lt;ul&gt;
      &lt;li&gt;&lt;t:pagelink page="address/create"&gt;Create new address&lt;/t:pagelink&gt;&lt;/li&gt;
    &lt;/ul&gt;</pre>
</div></div>

<p>Now we need the page, let's start with an empty shell, just to test our navigation.</p>

<p><b>src/main/webapp/address/CreateAddress.tml:</b></p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent
panelContent">
<pre>&lt;html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_1_0.xsd"&gt;
  &lt;head&gt;
    &lt;title&gt;Create New Address&lt;/title&gt;
  &lt;/head&gt;
  &lt;body&gt;

    &lt;h1&gt;Create New Address&lt;/h1&gt;

    &lt;em&gt;coming soon ...&lt;/em&gt;

  &lt;/body&gt;
&lt;/html&gt;</pre>
</div></div>

<p>And the corresponding class:</p>

<p><b>src/main/java/org/apache/tapestry5/tutorial1/pages/address/CreateAddress.java:</b></p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent
panelContent">
<pre>package org.apache.tapestry5.tutorial.pages.address;

public class CreateAddress
{

}</pre>
</div></div>

<p>So ... why is the class named "CreateAddress" and not simply "Create"? Actually,
we could have named it "Create", and the application would still work, but the longer <em>class</em>
name is equally valid. Tapestry noticed the redundancy in the class name: org.apache.tapestry5.tutorial.pages.<tt><em>address</em></tt>.Create<em>Address</em>
and just stripped it out.</p>

<p>Eventually, your application will probably have more entities: perhaps you'll have
a "user/Create" page and a "payment/Create" page and an "account/Create" page. Now, you <em>could</em>
have a bunch of different classes named Create spread across a number of different packages.
That's legal Java, but it isn't ideal. You may find yourself accidentally editing the Java
code for creating an Account when your really want to be editing the code for creating a Payment.</p>

<p>Tapestry is encouraging you to use a more descriptive name: Create<em>Address</em>
not just Create, but it isn't making you pay the cost (in terms of longer, uglier URLs). The
URL to access the page will still be <a href="http://localhost:8080/tutorial1/address/create"
class="external-link" rel="nofollow">http://localhost:8080/tutorial1/address/create</a>.</p>

<p>Another note: Index pages work in folders as well. A class named org.apache.tapestry5.tutorial.pages.address.AddressIndex
would be given the name "address/Index". However, Tapestry has special rules for pages named
"Index" and the render URL would be <a href="http://localhost:8080/tutorial1/address/"
class="external-link" rel="nofollow">http://localhost:8080/tutorial1/address/</a>
. In other words, you can place Index pages in any folder and Tapestry will build a short
URL for that page ... and you <em>don't</em> have to keep naming the classes Index
(it's confusing to have many classes with the same name, even across multiple packages); instead,
you can name each index page after the package that contains it. Tapestry users a smart <em>convention</em>
to keep it all straight and generate short, to the point URLs.</p>

<h3><a name="Forms-UsingtheBeanEditFormcomponent"></a>Using the BeanEditForm
component</h3>

<p>Time to start putting together the logic for this form. In fact, let's use a magic
trick ... the BeanEditForm component. This component can analyze a class and create an editor
UI for it all in one go. Let's give it a try.</p>

<p>Add the following to the CreateAddress template (replacing the "coming soon ..."
message):</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent
panelContent">
<pre>  &lt;t:beaneditform object="address"/&gt;</pre>
</div></div>

<p>And match that up with a property in the CreateAddress class:</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent
panelContent">
<pre>  @Property
  private Address address</pre>
</div></div>

<p>When you refresh the page, you'll see the following:</p>

<p><span class="image-wrap" style=""><img src="/confluence/download/attachments/23340431/address-v1.png?version=1&amp;modificationDate=1286796817000"
height="439" width="760" style="border: 1px solid black" /></span></p>

<p>Initial version of the create address form</p>

<p>&#95;There have been minor changes to the default CSS since this screenshot was
taken; for example, the labels are a bit wider now. In addition, the Honorific field (being
optional) would include a blank option, rather than the first real selection, "Mr".&#95;</p>

<p>Tapestry's done quite a bit of work here. It has created a form that includes a field
for each property. Further, its seen that the honorific property is an enumerated type, and
presented that as a drop-down list.</p>

<p>In addition, Tapestry has converted the property names ("city", "email", "firstName")
to user presentable labels ("City", "Email", "First Name"). In fact, these are &lt;label&gt;
elements, so clicking a label will move the cursor into the corresponding field.</p>

<p>This is an awesome start; it's a presentable interface, quite nice in fact for a
few minute's work. But it's far from perfect; let's get started with some customizations.</p>

<h3><a name="Forms-Changingfieldorder"></a>Changing field order</h3>

<p>It looks like the fields are being displayed in alphabetical order, ("city" first,
"zip" last). That's not quite the reality, however: If you check the listing for the Address
class, you'll see that the getter and setter methods are in alphabetical order (care of Eclipse,
which generated all those methods from the fields).</p>

<p>The BeanEditForm works in the order in which the <em>getter methods</em>
are defined in the class. Let's reorder them into a more reasonable order:</p>

<ul>
	<li>honorific</li>
	<li>firstName</li>
	<li>lastName</li>
	<li>street1</li>
	<li>street2</li>
	<li>city</li>
	<li>state</li>
	<li>zip</li>
	<li>email</li>
	<li>phone<br/>
(This is also the order of in which the fields are defined.)</li>
</ul>


<p>Because Address is not a component class, it is necessary to restart Jetty to see
the effects of these changes.</p>

<p>Once Jetty is restarted, hit the browser's refresh button to see the fields in the
correct order:</p>


<p><span class="image-wrap" style=""><img src="/confluence/download/attachments/23340431/address-v2.png?version=1&amp;modificationDate=1286796818000"
height="439" width="760" style="border: 1px solid black" /></span><br/>
Create address form with fields in proper order</p>

<h3><a name="Forms-Customizinglabels"></a>Customizing labels</h3>

<p>Tapestry makes it pretty easy to customize the labels used on the fields. It's just
a matter of creating a <em>message catalog</em> for the page.</p>

<p>In Tapestry, every page and component may have its own message catalog. This is a
standard Java properties file, and it is named the same as the page or component class, with
a ".properties" extension. A message catalog consists of a series of lines, each line is a
message key and a message value separated with an equals sign.</p>

<p>All it takes is to create a message entry with a particular name: the name of the
property suffixed with "-label". As elsewhere, Tapestry is forgiving of case.</p>

<p><b>src/main/resources/org/apache/tapestry5/tutorial/pages/address/CreateAddress.properties:</b></p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent
panelContent">
<pre>street1-label=Street 1
street2-label=Street 2
email-label=E-Mail
zip-label=Zip Code
phone-label=Phone Number</pre>
</div></div>

<p>Since this is a <em>new</em> file (and not a change to an existing file),
you may have to restart Jetty to force Tapestry to pick up the change.</p>


<p><span class="image-wrap" style=""><img src="/confluence/download/attachments/23340431/address-v3.png?version=1&amp;modificationDate=1286796818000"
height="446" width="760" style="border: 1px solid black" /></span><br/>
Create Address form with field labels corrected</p>

<p>We can also customize the options in the drop down list. All we have to do is add
some more entries to the message catalog matching the enum names to the desired labels. Update
CreateAddress.properties and add:</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent
panelContent">
<pre>MR=Mr.
MRS=Mrs.
DR=Dr.</pre>
</div></div>

<p>Notice that we don't have to include an option for MISS, because that is converted
to "Miss" anyway. You might just want to include it for sake of consistency ... the point
is, each option label is searched for seperately.</p>

<p>Lastly, the default label on the submit button is "Create/Update" (BeanEditForm doesn't
know how it is being used). Let's change that to "Create Address".</p>

<p>That button is a component within the BeanEditForm component. It's not a property,
so we can't just put a message into the message catalog, the way we can with the fields. Fortunately,
the BeanEditForm component includes a parameter expressly for re-labelling the button. Simply
change the CreateAddress component template:</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent
panelContent">
<pre>  &lt;t:beaneditform submitlabel="Create Address" object="address"/&gt;</pre>
</div></div>

<p>The default for the submitlabel parameter is "Create/Update", but here we're overriding
that default to a specific value.</p>

<p>The final result shows the reformatting and relabeling:</p>


<p><span class="image-wrap" style=""><img src="/confluence/download/attachments/23340431/address-v5.png?version=1&amp;modificationDate=1286796818000"
style="border: 1px solid black" /></span><br/>
Create Address form with proper labels</p>

<p>Before continuing on to validation, a side note about message catalogs. Message catalogs
are not just for re-labeling fields and options; we'll see in later chapters how message catalogs
are used in the context of localization and internationalization.</p>

<p>Instead of putting the label for the submit button directly inside the template,
we're going to provide a reference to the label; the actual label will go in the message catalog.</p>

<p>In Tapestry, when binding a parameter, the value you provide may include a prefix.
The prefix guides Tapestry in how to interpret the rest of the the parameter value ... is
it the name of a property? The id of a component? A message key? Most fields have a default
prefix, usually "prop:", that is used when you fail to provide one (this helps to make the
templates as terse as possible).</p>

<p>Here we want to reference a message from the catalog, so we use the "message:" prefix:</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent
panelContent">
<pre>  &lt;t:beaneditform submitlabel="message:submit-label" object="address"/&gt;</pre>
</div></div>

<p>And then define the submit-label key in the message catalog:</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent
panelContent">
<pre>submit-label=Create Address</pre>
</div></div>

<p>At then end of the day, the exact same HTML is sent to the client, regardless of
whether you include the label text directly in the template, or indirectly in the message
catalog. In the long term, the latter approach will work better if you later chose to internationalize
your application.</p>

<h3><a name="Forms-AddingValidation"></a>Adding Validation</h3>

<p>Before we worry about storing the Address object, we should make sure that the user
provides reasonable values. For example,several of the fields should be required, and phone
numbers and email address have specific formats.</p>

<p>The BeanEditForm checks for a Tapestry-specific annotation, @org.apache.tapestry5.beaneditor.Validate,
on the getter <em>or</em> setter method of each property.</p>

<p>Update the getter methods for the lastName, firstName, street1, city, state and zip
fields, adding a @Validate annotation to each:</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent
panelContent">
<pre>  @Validate("required")
  public String getFirstName()
  {
    return firstName;
  }</pre>
</div></div>

<p>What is that string, "required"? That's how you specify the desired validation. It
is a series of names that identify what type of validation is desired. A number of validators
are built in, such as "required", "minLength" and "maxLength". As elsewhere, Tapestry is case
insensitive.</p>

<p>You can apply multiple validations, by seperating the validator names with commas.
Some validators can be configured (with an equals sign). Thus you might say "required,minLength=5"
for a field that must be specified, and must be at least five characters long.</p>

<p>Restart the application, and refresh your browser, then hit the submit button. <span
class="image-wrap" style=""><img src="/confluence/download/attachments/23340431/address-v6.png?version=1&amp;modificationDate=1286796818000"
height="482" width="760" style="border: 1px solid black" /></span></p>


<p>Form with client side validations visible</p>

<p>This is a shot just after hitting the submit button; all the fields have been validated
and pop-up error bubbles are displayed. This looks a bit cluttered, but all the bubbles, except
for the one for the focus field (the field the user is actively typing into), will fade out
after a moment. As you tab from field to field, Tapestry will validate your input and briefly
display the error bubble. And <em>all</em> of this is taking place on the client
side, without any communication with the application.</p>

<p>Each field in error has been highlighted (it's a bit subtle) and marked with a red
"X". Further, the label for each of the fields has also been highlighted in red, to even more
clearly identify what's in error. The cursor has also been moved to the first field that's
in error.</p>

<p>Once all the errors are corrected, and the form does submit, all validations are
performed on the server side as well (just in case the client has JavaScript disabled).</p>

<p>So ... how about some more interesting validation than just "required or not". Tapestry
has built in support for validating based on field length and several variations of field
value, including regular expressions. Zip codes are pretty easy to express as a regular expression.</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent
panelContent">
<pre>  @Validate("required,regexp=\\d{5}(-\\d{4})?")
  public String getZip()
  {
    return zip;
  }    </pre>
</div></div>

<p>Let's give it a try; restart the application and enter an "abc" for the zip code.</p>


<p><span class="image-wrap" style=""><img src="/confluence/download/attachments/23340431/address-v7.png?version=1&amp;modificationDate=1286796818000"
height="482" width="760" style="border: 1px solid black" /></span><br/>
Regexp validation</p>

<p>This is what you'll see after typing "abc" and tabbing out of the field, then tabbing
back in. It's a little hard to capture all the animation effects in a still photo.</p>

<p>In any case, that's the right validation behavior, but it's the wrong message. Your
users are not going to know or care about regular expressions.</p>

<p>Fortunately, it's easy to customize validation messages. All we need to know is the
name of the property ("zip") and the name of the validator ("regexp"). We can then put an
entry into the CreateAddress message catalog:</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent
panelContent">
<pre>zip-regexp-message=Zip Codes are five or nine digits.  Example: 02134 or 90125-1655.</pre>
</div></div>

<p>Refresh the page and submit again:</p>

<p> <span class="image-wrap" style=""><img src="/confluence/download/attachments/23340431/address-v8.png?version=1&amp;modificationDate=1286796818000"
height="482" width="760" style="border: 1px solid black" /></span></p>


<p>Regexp validation with corrected message</p>

<p>This trick isn't limited to just the regexp validator, it works equally well with
<em>any</em> validator.</p>

<p>Let's go one step further. Turns out, we can move the regexp pattern to the message
catalog as well. If you only provide the name of the validator in the @Validate annotation,
Tapestry will search the containing page's message catalog of the constraint value, as well
as the validation message. The constraint value for the regexp validator is the regular expression
to match against.</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent
panelContent">
<pre>  @Validate("required,regexp")
  public String getZip()
  {
    return zip;
  }</pre>
</div></div>

<p>Now, just put the regular expression into the CreateAddress message catalog:</p>

<div class="preformatted panel" style="border-width: 1px;"><div class="preformattedContent
panelContent">
<pre>zip-regexp=\\d{5}(-\\d{4})?
zip-regexp-message=Zip Codes are five or nine digits.  Example: 02134 or 90125-1655.</pre>
</div></div>

<p>After a restart you'll see the ... the same behavior. But when we start creating
more complicated regular expressions, it'll be much, much nicer to put them in the message
catalog rather than inside the annotation value. And inside the message catalog, you can change
and tweak the regular expressions without having to restart the application each time.</p>

<p>We could go a bit further here, adding more regular expression validation for phone
numbers and e-mail addresses. We're also far from done in terms of further customizations
of the BeanEditForm component.</p>

<p>By now you are likely curious about what happens <em>after</em> the form
submits successfully (without validation errors), so that's what we'll focus on next.</p>

<p><a href="/confluence/pages/createpage.action?spaceKey=TAPESTRY&amp;title=Forms2&amp;linkCreation=true&amp;fromPageId=23340431"
class="createlink">Continue on to Chapter 5: Forms in Tapestry, Part Two</a></p>
    </div>
        <div id="commentsSection" class="wiki-content pageSection">
        <div style="float: right;">
            <a href="https://cwiki.apache.org/confluence/users/viewnotifications.action"
class="grey">Change Notification Preferences</a>
        </div>
        <a href="https://cwiki.apache.org/confluence/display/TAPESTRY/Forms">View Online</a>
        |
        <a href="https://cwiki.apache.org/confluence/pages/diffpagesbyversion.action?pageId=23340431&revisedVersion=2&originalVersion=1">View
Changes</a>
            </div>
</div>
</div>
</div>
</div>
</body>
</html>

Mime
View raw message