Indexed Properties and Validation

Download Report

Transcript Indexed Properties and Validation

Indexed Properties and
Validation
James Turner
Director of Software Development
Benefit Systems, Inc.
Who Am I
• Director of Software Development, Benefit
Systems, Inc.
• Author: MySQL and JSP Web Applications
• Co-Author: Struts Kick Start
• Forthcoming Author: JSF Kick Start
• 2 Year Struts User
• Committer, Struts & Jakarta Commons
• Release Manager, Validator 1.0
To be Covered
• Using Indexed Properties in Forms
– Simple arrays of strings
– Arrays of beans
• The Validation Framework
– Review
– Adding new Validations
– Validating Indexed Properties
– Using requiredif
Using Indexed Property Forms
• Typically used in “master-detail” type
forms.
• For example, a purchase order form
(quantity, part #, description, price)
• Or in an enrollment form.
• There are two fundemental ways to do this,
one is handled better by Struts than the
other.
How Everybody Does it First
<form-beans>
<form-bean name="plainIndexedForm"
type="org.apache.struts.validator.DynaValidatorForm">
<form-property name="lastName"
type="java.lang.String[]" size="10"/>
</form-bean>
</form-bean>
• Note the use of the new “size=“ feature.
• The problem is, the native Struts indexed
property support isn’t set up to handle this.
What You Have to End Up Doing
<HEAD><TITLE>Example of Indexed Property Form With Plain String
Indexes</TITLE></HEAD>
<H1>Example of Indexed Property Form With Plain String
Indexes</H1>
<html:form action="/handlePlainIndexedProperty">
<html:errors/>
<c:forEach var="lastName" varStatus="status"
items="${plainIndexedForm.map.lastName}" >
<c:out value="${status.index + 1}"/>
<html-el:text property="lastName[${status.index}]"/><BR>
</c:forEach>
<html:submit/>
</html:form>
The Problem
• If you use indexed=“true”, you won’t get
what you expect.
• For example, if you did:
<html:text property=“lastName” indexed=“true”/>
What you’d end up with is:
org.apache.struts.taglib.html.BEAN[0].lastName
Not
org.apache.struts.taglib.html.BEAN.lastName[0]
The More Complex Case
• In master-detail, you typically have
multiple fields associate with a single line.
<form-bean name="plainMultivarIndexedForm“
type="org.apache.struts.validator.DynaValidatorForm">
<form-property name="lastName"
type="java.lang.String[]" size="10"/>
<form-property name="firstName"
type="java.lang.String[]" size="10"/>
<form-property name="middleName"
type="java.lang.String[]" size="10"/>
</form-bean>
Which Leads to the Ugly…
<HEAD><TITLE>Example of Indexed Property Form With Plain String
Indexes</TITLE></HEAD>
<H1>Example of Indexed Property Form With Plain String Indexes</H1>
<html:form action="/handlePlainIndexedProperty">
<html:errors/>
<c:forEach var="lastName" varStatus="status"
items="${plainIndexedForm.map.lastName}" >
<c:out value="${status.index + 1}"/>
<html-el:text property="lastName[${status.index}]"/>
<html-el:text property=“firstName[${status.index}]"/>
<html-el:text property=“ssn[${status.index}]"/><BR>
</c:forEach>
<html:submit/>
</html:form>
Thankfully..
• There’s a better way, and one that is
architecturally cleaner too.
• Define a bean that holds the values for
one line:
• Then you can use the indexed property
correctly.
public class Person {
private String lastName;
private String firstName;
private String ssn;
private String gender;
public String getLastName() {
return lastName;
}
public void setLastName(String lastName) {
this.lastName = lastName;
}
public String getFirstName() {
return firstName;
}
public void setFirstName(String firstName) {
this.firstName = firstName;
}
public String getSsn() {
return ssn;
}
public void setSsn(String ssn) {
this.ssn = ssn;
}
public String getGender() {
return gender;
}
public void setGender(String gender) {
this.gender = gender;
}
}
The Corresponding JSP
<HEAD><TITLE>Example of Indexed Property Form With Bean String
Indexes</TITLE></HEAD>
<H1>Example of Indexed Property Form With Bean String Indexes</H1>
<html:form action="/handleBeanIndexedProperty">
<html:errors/>
<c:forEach var="person" varStatus="status"
items="${beanMultivarIndexedForm.map.person}" >
<c:out value="${status.index + 1}"/>
<html:text name="person" property="lastName" indexed="true"/>
<html:text name="person" property="firstName" indexed="true"/>
<html:text name="person" property="ssn" indexed="true"/> <BR>
</c:forEach>
<html:submit/>
</html:form>
Important to Note
• The name of the iteration var (either via
logic:iterate or c:forEach) must match the
property name of the array in the form.
• As of 1.1RC1, you can use either JSTL or
Struts tag iteration with the indexed
attribute of an HTML form tag.
Quick Validator Review
• The validator framwork offers an
alternative to using the validate() method
of the ActionForm.
• Also the only way to do pre-Action
validation of DynaForms.
• Adding new validations is fairly easy...
Adding a New Validation
• Step one: Write a method of a class which
takes some of the following items in it’s
method signature
– Object – The field value being validated
– ValidatorAction – The rule being run
– Field – The field definition from the XML file
– ActionErrors – As in the ActionForm
– Validator – A handle to the actual validator
– HttpServletRequest – The Request
For Example:
public static boolean validateNameNotFred(Object bean,
ValidatorAction va,
Field field,
ActionErrors errors,
Validator validator,
HttpServletRequest request) {
String value = null;
if (isString(bean)) {
value = (String) bean;
} else {
value = ValidatorUtil.getValueAsString(bean, field.getProperty());
}
if (value.equals(“Fred”) return false;
return true;
}
Now Register It
• Usually put in validation.xml, but can go into any
validation ruleset that the validator loads.
<global>
<validator name=“isnotfred" msg="errors.notFred"
classname=“demoValidator"
method="validateNameNotFred"
methodParams="java.lang.Object,
org.apache.commons.validator.ValidatorAction,
org.apache.commons.validator.Field,
org.apache.struts.action.ActionErrors,
org.apache.commons.validator.Validator,
javax.servlet.http.HttpServletRequest"/>
</global>
Validating Simple Array of Strings
<form name="plainIndexedForm">
<field property="lastName"
indexedListProperty="lastName“
depends="required">
<arg0 key=“label.lastName"/>
</field>
</form>
Ok, So It Doesn’t Make Sense
• You shouldn’t have to specify both
property and indexedListProperty, but you
do.
• As written, if you don’t fill in all 10 last
names, you will get an error.
It Makes a Little More Sense for
Bean Arrays
<form name="beanMultivarIndexedForm">
<field property="lastName" indexedListProperty="person"
depends="required">
<arg0 key="label.lastName"/>
</field>
<field property="firstName" indexedListProperty="person"
depends="required">
<arg0 key="label.firstName"/>
</field>
<field property="ssn" indexedListProperty="person"
depends="required">
<arg0 key="label.ssn"/>
</field>
</form>
Unfortunately, Not Quite Right
• In most cases with master-detail, you don’t
want to require that all the lines get filled in.
• Instead, you want to require that if part of
a line is filled in, the entire line is filled in.
• Enter: requiredif
requiredif: A Simple Case
<field property="pregnancyTest" depends="requiredif">
<arg0 key="medicalStatusForm.pregnancyTest.label"/>
<var> <var-name>field[0]</var-name> <var-value>sex</var-value> </var>
<var> <var-name>fieldTest[0]</var-name>
<var-value>EQUAL</var-value>
</var>
<var> <var-name>fieldValue[0]</var-name> <var-value>F</var-value> </var>
<var> <var-name>field[1]</var-name> <var-value>sex</var-value> </var>
<var> <var-name>fieldTest[1]</var-name>
<var-value>EQUAL</var-value> </var>
<var> <var-name>fieldValue[1]</var-name> <var-value>f</var-value></var>
<var> <var-name>fieldJoin</var-name> <var-value>OR</var-value> </var>
</field>
Notes on requiredif
1. For Now, doesn’t generate Javascript
validation (and may never because of
complexity)
2. Change was made between Beta 1 and
RC1, variable names are now
camelCase rather than dashed.
Where requiredif Becomes A
Lifesaver
• Let’s define a set of requiredif rules using the
fieldIndexed parameter. If lastName is not null,
firstname is required.
• The fieldIndexed param says that you should
apply the same index to this property as the
property being checked.
• In other words, if you’re checking
person[10].lastName, and the test is on
firstName with fieldIndexed set true, the value of
person[10].firstName will be checked.
A requiredif example
<form name="beanMultivarIndexedForm">
<field property="lastName" indexedListProperty="person" depends="">
<arg0 key="label.lastName"/>
</field>
<field property="firstName" indexedListProperty="person" depends="requiredif">
<arg0 key="label.firstName"/>
<var>
<var-name>field[0]</var-name>
<var-value>lastName</var-value>
</var>
<var>
<var-name>fieldIndexed[0]</var-name>
<var-value>true</var-value>
</var>
<var>
<var-name>fieldTest[0]</var-name>
<var-value>NOTNULL</var-value>
</var>
</field>
A Look Ahead
• requiredif is powerful but UGLY
• So I wrote something better, but
unfortunately too late for 1.1.
• It will go in right after 1.1 for the 1.2
release.
• It’s called validwhen
• And it looks like this:
Something to Make You Drool
<field property="firstName"
indexedListProperty="dependents"
depends="validwhen">
<arg0 key="dependentlistForm.firstName.label"/>
<var>
<var-name>test</var-name>
<var-value>((dependents[].lastName == null) or
(dependents[].firstName != null))
</var-value>
</var>
</field>
Q&A