forked from seam2/jboss-seam
-
Notifications
You must be signed in to change notification settings - Fork 0
/
Validation.xml
executable file
·320 lines (249 loc) · 10.1 KB
/
Validation.xml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
<chapter id="validation">
<title>JSF form validation in Seam</title>
<para>
In plain JSF, validation is defined in the view:
</para>
<programlisting role="XHTML"><![CDATA[<h:form>
<h:messages/>
<div>
Country:
<h:inputText value="#{location.country}" required="true">
<my:validateCountry/>
</h:inputText>
</div>
<div>
Zip code:
<h:inputText value="#{location.zip}" required="true">
<my:validateZip/>
</h:inputText>
</div>
<h:commandButton/>
</h:form>]]></programlisting>
<para>
In practice, this approach usually violates DRY, since most
"validation" actually enforces constraints that are part of
the data model, and exist all the way down to the database
schema definition. Seam provides support for model-based
constraints defined using Bean Validation.
</para>
<para>
Let's start by defining our constraints, on our
<literal>Location</literal> class:
</para>
<programlisting role="JAVA"><![CDATA[public class Location {
private String country;
private String zip;
@NotNull
@Size(max=30)
public String getCountry() { return country; }
public void setCountry(String c) { country = c; }
@NotNull
@Size(max=6)
@Pattern("^\d*$")
public String getZip() { return zip; }
public void setZip(String z) { zip = z; }
}]]></programlisting>
<para>
Well, that's a decent first cut, but in practice it might be
more elegant to use custom constraints instead of the ones
built into Bean Validation:
</para>
<programlisting role="JAVA"><![CDATA[public class Location {
private String country;
private String zip;
@NotNull
@Country
public String getCountry() { return country; }
public void setCountry(String c) { country = c; }
@NotNull
@ZipCode
public String getZip() { return zip; }
public void setZip(String z) { zip = z; }
}]]></programlisting>
<para>
Whichever route we take, we no longer need to specify the
type of validation to be used in the JSF page. Instead, we
can use <literal><s:validate></literal> to validate
against the constraint defined on the model object.
</para>
<programlisting role="XHTML"><![CDATA[<h:form>
<h:messages/>
<div>
Country:
<h:inputText value="#{location.country}" required="true">
<s:validate/>
</h:inputText>
</div>
<div>
Zip code:
<h:inputText value="#{location.zip}" required="true">
<s:validate/>
</h:inputText>
</div>
<h:commandButton/>
</h:form>]]></programlisting>
<para>
<emphasis>Note:</emphasis> specifying <literal>@NotNull</literal>
on the model does <emphasis>not</emphasis> eliminate the requirement
for <literal>required="true"</literal> to appear on the control!
This is due to a limitation of the JSF validation architecture.
</para>
<para>
This approach <emphasis>defines</emphasis> constraints on the model, and
<emphasis>presents</emphasis> constraint violations in the view — a
significantly better design.
</para>
<para>
However, it is not much less verbose than what we started with,
so let's try <literal><s:validateAll></literal>:
</para>
<programlisting role="XHTML"><![CDATA[<h:form>
<h:messages/>
<s:validateAll>
<div>
Country:
<h:inputText value="#{location.country}" required="true"/>
</div>
<div>
Zip code:
<h:inputText value="#{location.zip}" required="true"/>
</div>
<h:commandButton/>
</s:validateAll>
</h:form>]]></programlisting>
<para>
This tag simply adds an <literal><s:validate></literal>
to every input in the form. For a large form, it can save a lot
of typing!
</para>
<para>
Now we need to do something about displaying feedback to the
user when validation fails. Currently we are displaying all
messages at the top of the form. In order for the user to correlate the message with an input, you need to define a label using the standard <literal>label</literal> attribute on the input component.
</para>
<programlisting role="XHTML"><![CDATA[<h:inputText value="#{location.zip}" required="true" label="Zip:">
<s:validate/>
</h:inputText>]]></programlisting>
<para>
You can then inject this value into the message string using
the placeholder {0} (the first and only parameter passed to a
JSF message for a Bean Validation restriction). See the
internationalization section for more information regarding
where to define these messages.
</para>
<programlisting>validator.length={0} length must be between {min} and {max}</programlisting>
<para>
What we would really like to do, though, is display the message
next to the field with the error (this is possible in plain
JSF), highlight the field and label (this is not possible) and,
for good measure, display some image next to the field (also
not possible). We also want to display a little colored
asterisk next to the label for each required form field. Using
this approach, the identifying label is not necessary.
</para>
<para>
That's quite a lot of functionality we need for each field
of our form. We wouldn't want to have to specify highlighting
and the layout of the image, message and input field for every
field on the form. So, instead, we'll specify the common
layout in a facelets template:
</para>
<programlisting role="XHTML"><![CDATA[<ui:composition xmlns="http://www.w3.org/1999/xhtml"
xmlns:ui="http://java.sun.com/jsf/facelets"
xmlns:h="http://java.sun.com/jsf/html"
xmlns:f="http://java.sun.com/jsf/core"
xmlns:s="http://jboss.org/schema/seam/taglib">
<div>
<s:label styleClass="#{invalid?'error':''}">
<ui:insert name="label"/>
<s:span styleClass="required" rendered="#{required}">*</s:span>
</s:label>
<span class="#{invalid?'error':''}">
<h:graphicImage value="/img/error.gif" rendered="#{invalid}"/>
<s:validateAll>
<ui:insert/>
</s:validateAll>
</span>
<s:message styleClass="error"/>
</div>
</ui:composition>]]></programlisting>
<para>
We can include this template for each of our form fields using
<literal><s:decorate></literal>.
</para>
<programlisting role="XHTML"><![CDATA[<h:form>
<h:messages globalOnly="true"/>
<s:decorate template="edit.xhtml">
<ui:define name="label">Country:</ui:define>
<h:inputText value="#{location.country}" required="true"/>
</s:decorate>
<s:decorate template="edit.xhtml">
<ui:define name="label">Zip code:</ui:define>
<h:inputText value="#{location.zip}" required="true"/>
</s:decorate>
<h:commandButton/>
</h:form>]]></programlisting>
<para>
Finally, we can use RichFaces Ajax to display validation messages as the user
is navigating around the form:
</para>
<programlisting role="XHTML"><![CDATA[<h:form>
<h:messages globalOnly="true"/>
<s:decorate id="countryDecoration" template="edit.xhtml">
<ui:define name="label">Country:</ui:define>
<h:inputText value="#{location.country}" required="true">
<a:ajax event="blur" render="countryDecoration" bypassUpdates="true"/>
</h:inputText>
</s:decorate>
<s:decorate id="zipDecoration" template="edit.xhtml">
<ui:define name="label">Zip code:</ui:define>
<h:inputText value="#{location.zip}" required="true">
<a:ajax event="blur" render="zipDecoration" bypassUpdates="true"/>
</h:inputText>
</s:decorate>
<h:commandButton/>
</h:form>]]></programlisting>
<para>
It's better style to define explicit ids for
important controls on the page, especially if you want to do
automated testing for the UI, using some toolkit like
Selenium. If you don't provide explicit ids, JSF will generate
them, but the generated values will change if you change
anything on the page.
</para>
<programlisting role="XHTML"><![CDATA[<h:form id="form">
<h:messages globalOnly="true"/>
<s:decorate id="countryDecoration" template="edit.xhtml">
<ui:define name="label">Country:</ui:define>
<h:inputText id="country" value="#{location.country}" required="true">
<a:ajax event="blur" render="countryDecoration" bypassUpdates="true"/>
</h:inputText>
</s:decorate>
<s:decorate id="zipDecoration" template="edit.xhtml">
<ui:define name="label">Zip code:</ui:define>
<h:inputText id="zip" value="#{location.zip}" required="true">
<a:ajax event="blur" render="zipDecoration" bypassUpdates="true"/>
</h:inputText>
</s:decorate>
<h:commandButton/>
</h:form>]]></programlisting>
<para>
And what if you want to specify a different message to be
displayed when validation fails? You can use the Seam message
bundle (and all it's goodies like el expressions inside the message,
and per-view message bundles) with the Bean Validation:
</para>
<programlisting role="JAVA"><![CDATA[public class Location {
private String name;
private String zip;
// Getters and setters for name
@NotNull
@Size(max=6)
@ZipCode(message="#{messages['location.zipCode.invalid']}")
public String getZip() { return zip; }
public void setZip(String z) { zip = z; }
}]]></programlisting>
<programlisting>
location.zipCode.invalid = The zip code is not valid for #{location.name}
</programlisting>
</chapter>