First Name![]() | Last Name![]() |
|---|---|
| My1 | Momma |
| rarara | kumar |
| Standa1 | Bandaa |
| test | cdf |
| xwang | Spoon12 |
mixins. Tapestry gives this package
special treatment.
<!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
We use a "strict" DTD to make IE follow the alignment rules. -->
<html xmlns:t="http://tapestry.apache.org/schema/tapestry_5_3.xsd">
<head>
<link rel="stylesheet" type="text/css" href="${context:css/examples/js.css}"/>
<style type="text/css">
form { background-color: #eee; padding: 14px; }
.error-label { color: red; }
.required-field { border-right-color: orange; }
.error-field { border-color: red; }
.error-msg { color: red; }
</style>
</head>
<body>
<h1>AJAX Validators (1)</h1>
<noscript class="js-required">
${message:javascript_required}
</noscript>
Let's say you need a validator that behaves like any other client-side validator except that it asks the server to do the validation,
for example to validate that a username or email address is unique.<br/><br/>
Well, here we present a reusable <strong>mixin</strong> built for the job. It's called <strong>AjaxValidator</strong> and
we use it here to validate that the first and last names are unique.
<div class="eg">
<form t:type="CustomForm" t:id="inputs">
<t:errors/>
<table>
<tr>
<td><t:label for="firstName"/>:</td>
<td><input t:type="TextField" t:id="firstName" t:validate="required, maxlength=10" size="10"
t:mixins="ajaxValidator"/></td>
<td>(required, maxLength=10, new name only)</td>
</tr>
<tr>
<th></th>
<td colspan="2"><t:customerror for="firstName"/></td>
</tr>
<tr>
<td><t:label for="lastName"/>:</td>
<td><input t:type="TextField" t:id="lastName" t:validate="required, maxLength=10" size="10"
t:mixins="ajaxValidator"/></td>
<td>(required, maxLength=10, new name only)</td>
</tr>
<tr>
<th></th>
<td colspan="2"><t:customerror for="lastName"/></td>
</tr>
<tr>
<td></td>
<td><input type="submit" value="Display"/></td>
<td></td>
</tr>
</table>
</form><br/>
Here are the names already in use:<br/><br/>
<table t:type="grid" t:source="persons" t:row="var:person" include="firstName,lastName">[Grid here]</table>
</div>
Mixin location is important. Mixins must be put in a package called <code>mixins</code>. Tapestry gives this package
special treatment.<br/><br/>
References:
<a href="http://tapestry.apache.org/component-mixins.html">Component Mixins</a>,
<a href="http://tapestry.apache.org/ajax-and-zones.html">Ajax and Zones</a>,
<a href="http://tapestry.apache.org/5.3.7/apidocs/org/apache/tapestry5/services/Request.html">Request</a>.<br/><br/>
<a t:type="pagelink" t:page="Index" href="#">Home</a><br/><br/>
The source for PersonFinderService, @EJB handling, etc. is shown in the @EJB example.<br/>
The source for CustomForm and CustomError is shown in the No Validation Bubbles example.<br/><br/>
<t:sourcecodedisplay src="/web/src/main/java/jumpstart/web/pages/examples/ajax/AjaxValidators1.tml"/>
<t:sourcecodedisplay src="/web/src/main/java/jumpstart/web/pages/examples/ajax/AjaxValidators1.java"/>
<t:sourcecodedisplay src="/web/src/main/java/jumpstart/web/css/examples/js.css"/>
<t:sourcecodedisplay src="/web/src/main/java/jumpstart/web/mixins/AjaxValidator.java"/>
<t:sourcecodedisplay src="/web/src/main/java/jumpstart/web/mixins/AjaxValidator.js"/>
</body>
</html>
package jumpstart.web.pages.examples.ajax;
import java.util.List;
import javax.ejb.EJB;
import jumpstart.business.domain.person.Person;
import jumpstart.business.domain.person.iface.IPersonFinderServiceLocal;
import org.apache.tapestry5.annotations.InjectPage;
import org.apache.tapestry5.annotations.Property;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.json.JSONObject;
import org.apache.tapestry5.services.Request;
public class AjaxValidators1 {
static private final int MAX_RESULTS = 30;
// Screen fields
@Property
private String firstName;
@Property
private String lastName;
@Property
private List<Person> persons;
// Other pages
@InjectPage
private AjaxValidators2 page2;
// Useful bits and pieces
@Inject
private Request request;
@EJB
private IPersonFinderServiceLocal personFinderService;
// The code
void setupRender() {
persons = personFinderService.findPersons(MAX_RESULTS);
}
JSONObject onAjaxValidateFromFirstName() {
String firstName = request.getParameter("param");
try {
validateFirstNameIsUnique(firstName);
}
catch (Exception e) {
return new JSONObject().put("error", e.getMessage());
}
return new JSONObject();
}
JSONObject onAjaxValidateFromLastName() {
String lastName = request.getParameter("param");
try {
validateLastNameIsUnique(lastName);
}
catch (Exception e) {
return new JSONObject().put("error", e.getMessage());
}
return new JSONObject();
}
Object onSuccess() {
page2.set(firstName, lastName);
return page2;
}
void validateFirstNameIsUnique(String firstName) throws Exception {
if (firstName != null) {
List<Person> persons = personFinderService.findPersonsByFirstName(firstName);
if (persons.size() > 0) {
throw new Exception("The name is not available.");
}
}
}
void validateLastNameIsUnique(String lastName) throws Exception {
if (lastName != null) {
List<Person> persons = personFinderService.findPersonsByLastName(lastName);
if (persons.size() > 0) {
throw new Exception("The name is not available.");
}
}
}
}
body { font-family: 'Helvetica Neue', Helvetica, Arial, sans-serif; font-size: 13px; font-weight: normal; color: #333;
line-height: 17px; }
h1 { font-size: 26px; line-height: 20px; } /* For IE 7 */
form { margin: 0; }
.eg { margin: 20px 0; padding: 20px; color: #888;
border: 1px solid #ddd; border-radius: 4px; -webkit-border-radius: 4px; -mox-border-radius: 4px; }
a { text-decoration: none; color: #3D69B6; }
a:hover { text-decoration: underline; }
/* For javascript examples. */
.js-required { color: red; display: block; margin-bottom: 14px; }
.js-recommended { color: red; display: block; margin-bottom: 14px; }
.grid { border-collapse: collapse; border-spacing: 0; border: 1px solid #dddddd; font-size: 13px; }
.grid tr.odd { background-color: #f8f8f8; }
.grid tr:hover { background-color: #eeeeee; }
.grid th { padding: 3px 5px; text-align: left; width: 130px; border: 1px solid #dddddd;
font-weight: normal; background-color: #eeeeee;
filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#fbfbfb', endColorstr='#e4e4e4'); /* for IE */
background: -webkit-gradient(linear, left top, left bottom, from(#fbfbfb), to(#e4e4e4)); /* for webkit browsers */
background: -moz-linear-gradient(top, #fbfbfb, #e4e4e4); /* for firefox 3.6+ */ }
.grid td { padding: 3px 5px; text-align:left; }
/**
* A simple mixin for attaching javascript that invokes a listener in the component via AJAX.
* Based on http://tinybits.blogspot.com/2010/03/new-and-better-zoneupdater.html
* and http://tinybits.blogspot.com/2009/05/simple-onevent-mixin.html.
*/
package jumpstart.web.mixins;
import org.apache.tapestry5.ClientElement;
import org.apache.tapestry5.ComponentResources;
import org.apache.tapestry5.annotations.Import;
import org.apache.tapestry5.annotations.InjectContainer;
import org.apache.tapestry5.ioc.annotations.Inject;
import org.apache.tapestry5.json.JSONObject;
import org.apache.tapestry5.services.javascript.JavaScriptSupport;
// The @Import tells Tapestry to put a link to the file in the head of the page so that the browser will pull it in.
@Import(library = "AjaxValidator.js")
public class AjaxValidator {
// Useful bits and pieces
@Inject
private ComponentResources componentResources;
@Inject
private JavaScriptSupport javaScriptSupport;
/**
* The element we attach ourselves to
*/
@InjectContainer
private ClientElement clientElement;
// The code
void afterRender() {
// Tell the Tapestry.Initializer to do the initializing of an AjaxValidator, which it will do when the DOM has
// been fully loaded.
JSONObject spec = new JSONObject();
spec.put("elementId", clientElement.getClientId());
spec.put("listenerURI", componentResources.createEventLink("ajaxValidate").toAbsoluteURI());
javaScriptSupport.addInitializerCall("ajaxValidator", spec);
}
}
// A class that invokes a listener in the component via AJAX.
// Based on http://tinybits.blogspot.com/2010/03/new-and-better-zoneupdater.html
// and http://tinybits.blogspot.com/2009/05/simple-onevent-mixin.html
// and tapestry.js.
AjaxValidator = Class.create( {
initialize : function(spec) {
this.field = $(spec.elementId);
this.listenerURI = spec.listenerURI;
// Set up a listener that validates the field - asynchronously in the server - on change of focus
document.observe(Tapestry.FOCUS_CHANGE_EVENT, function(event) {
// If changing focus *within the same form* then perform validation.
// Note that Tapestry.currentFocusField does not change
// until after the FOCUS_CHANGE_EVENT notification.
if (Tapestry.currentFocusField == this.field && this.field.form == event.memo.form) {
this.asyncValidateInServer();
}
}.bindAsEventListener(this) );
},
asyncValidateInServer : function() {
var value = this.field.value;
var listenerURIWithValue = this.listenerURI;
if (value) {
listenerURIWithValue = addQueryStringParameter(listenerURIWithValue, 'param', value);
new Ajax.Request(listenerURIWithValue, {
method: 'get',
onFailure: function(t) {
alert('Error communication with the server: ' + t.responseText.stripTags());
},
onException: function(t, exception) {
alert('Error communication with the server: ' + exception.message);
},
onSuccess: function(t) {
if (t.responseJSON.error) {
this.field.showValidationMessage(t.responseJSON.error);
}
}.bind(this)
});
}
}
} )
function addQueryStringParameter(url, name, value) {
if (url.indexOf('?') < 0) {
url += '?'
} else {
url += '&';
}
value = escape(value);
url += name + '=' + value;
return url;
}
// Extend the Tapestry.Initializer with a static method that instantiates an AjaxValidator.
Tapestry.Initializer.ajaxValidator = function(spec) {
new AjaxValidator(spec);
}