ZK8 Wizard Example - Part 3 final"

From Documentation
m (correct highlight (via JWB))
 
(17 intermediate revisions by one other user not shown)
Line 13: Line 13:
  
 
This chapter will show how the ZK features '''Form Binding''' and '''Form Validation''' can be applied to the previously existing wizard (Part 2) with little impact to the overall code.
 
This chapter will show how the ZK features '''Form Binding''' and '''Form Validation''' can be applied to the previously existing wizard (Part 2) with little impact to the overall code.
[https://github.com/cor3000/zk-wizard-example/commit/part-3#diff-part-2]
+
[https://github.com/zkoss-demo/zk-wizard-example/commit/part-3#diff-part-2]
  
 
<gflash width="502" height="534">Order-wizard_with_validation.swf</gflash>
 
<gflash width="502" height="534">Order-wizard_with_validation.swf</gflash>
 +
 +
The video above shows the resulting wizard including the validation.
  
 
== Add the Form Binding ==
 
== Add the Form Binding ==
Line 22: Line 24:
 
This will wrap the original Order object into a [http://blog.zkoss.org/index.php/2015/02/03/zk8-new-form-binding-approach/ Form Proxy] ('''new in ZK 8''') acting as a cache to allow validation before updates are propagated to the original object.
 
This will wrap the original Order object into a [http://blog.zkoss.org/index.php/2015/02/03/zk8-new-form-binding-approach/ Form Proxy] ('''new in ZK 8''') acting as a cache to allow validation before updates are propagated to the original object.
  
The form proxy object is then passed as the "order" parameter into the wizard, leaving the wizard unaware of the changes.
+
The form proxy object is then passed as the "order" parameter into the wizard, leaving the inner wizard and it's pages unaware of the added Form Binding.
  
;/wizardexample/src/main/webapp/order.zul [https://github.com/cor3000/zk-wizard-example/blob/part-3/src/main/webapp/order.zul]
+
;/wizardexample/src/main/webapp/order.zul [https://github.com/zkoss-demo/zk-wizard-example/blob/part-3/src/main/webapp/order.zul]
  
<source lang="xml" high="3,5">
+
<source lang="xml" highlight="3,5">
 
<wizard wizardModel="@init(vm.wizardModel)" wrapperTemplate="formWrapper">
 
<wizard wizardModel="@init(vm.wizardModel)" wrapperTemplate="formWrapper">
 
<template name="formWrapper">
 
<template name="formWrapper">
Line 43: Line 45:
  
 
Also the wizard template was slightly changed to allow adding the form binding using an injected wrapperTemplate.  
 
Also the wizard template was slightly changed to allow adding the form binding using an injected wrapperTemplate.  
This preserves the option to use the wizard without form binding.
+
This preserves the option to use the wizard without form binding - by using the "defaultWizardContentWrapper".
  
;/wizardexample/src/main/webapp/WEB-INF/zul/template/wizard/wizard.zul [https://github.com/cor3000/zk-wizard-example/blob/part-3/src/main/webapp/WEB-INF/zul/template/wizard/wizard.zul]
+
;/wizardexample/src/main/webapp/WEB-INF/zul/template/wizard/wizard.zul [https://github.com/zkoss-demo/zk-wizard-example/blob/part-3/src/main/webapp/WEB-INF/zul/template/wizard/wizard.zul]
  
<source lang="xml" high="2,5,6">
+
<source lang="xml" highlight="2,5,6">
 
  <window border="normal" title="@load(wizardVM.currentStep.title)" ... >
 
  <window border="normal" title="@load(wizardVM.currentStep.title)" ... >
 
<sh:apply template="@init(empty wrapperTemplate ? 'defaultWizardContentWrapper' : wrapperTemplate)"/>
 
<sh:apply template="@init(empty wrapperTemplate ? 'defaultWizardContentWrapper' : wrapperTemplate)"/>
Line 66: Line 68:
  
 
For example calculated (or read only) fields which are derived from other fields should be annotated '''@Transient'''.
 
For example calculated (or read only) fields which are derived from other fields should be annotated '''@Transient'''.
Those getters will not be intercepted by the form proxy keep performing their original calculations whenever executed.
+
Those getters will not be intercepted by the form proxy and keep performing their original calculations whenever executed.
  
;zk.example.order.api.Basket [https://github.com/cor3000/zk-wizard-example/blob/part-3/src/main/java/zk/example/order/api/Basket.java#L28]
+
;zk.example.order.api.Basket [https://github.com/zkoss-demo/zk-wizard-example/blob/part-3/src/main/java/zk/example/order/api/Basket.java#L28]
 
:here '''totalPrice''' and '''totalItems''' are based on getItems()/getItemPrice()/getQuantity() but not cached by the form proxy
 
:here '''totalPrice''' and '''totalItems''' are based on getItems()/getItemPrice()/getQuantity() but not cached by the form proxy
  
<source lang="java" high="1,8">
+
<source lang="java" highlight="1,8">
 
@Transient
 
@Transient
 
public BigDecimal getTotalPrice() {
 
public BigDecimal getTotalPrice() {
Line 88: Line 90:
  
 
One limitation of the proxy mechanism is that a class (to be proxied) requires a zero-argument constructor.  
 
One limitation of the proxy mechanism is that a class (to be proxied) requires a zero-argument constructor.  
For this case or if you intend to use a class in an immutable way (and not create a nested form proxy object) you can annotate a field getter with '''@Immutable'''.
+
For this case or if you intend to use a class in an immutable way (and not create a nested form proxy objects) you can annotate a field getter with '''@Immutable'''.
Which means it can still have a setter to replace the whole object, but the object itself is no longer wrapped by a form proxy and changes (if possible) are written directly to the real object.
+
Which means it can still have a setter to replace the whole object, but the object itself is no longer wrapped by a form proxy and none of its original methods are proxied.
  
A good example is the class java.math.BigDecimal which is neither final nor immutable by definition but in many cases treated as immutable.
+
A good example is the class java.math.BigDecimal which is neither final nor immutable by definition but in many cases it is useful to treat it like immutable.
  
;zk.example.order.api.BasketItem [https://github.com/cor3000/zk-wizard-example/blob/part-3/src/main/java/zk/example/order/api/BasketItem.java#L42]
+
;zk.example.order.api.BasketItem [https://github.com/zkoss-demo/zk-wizard-example/blob/part-3/src/main/java/zk/example/order/api/BasketItem.java#L42]
 
:another example for @Transient and one for '''@Immutable''' (means only setting a new unitPrice is handled by the form proxy, while calling unitPrice.setScale(...) is not intercepted at all)
 
:another example for @Transient and one for '''@Immutable''' (means only setting a new unitPrice is handled by the form proxy, while calling unitPrice.setScale(...) is not intercepted at all)
<source lang="java" high="6">
+
<source lang="java" highlight="6">
 
@Transient
 
@Transient
 
public BigDecimal getItemPrice() {
 
public BigDecimal getItemPrice() {
Line 111: Line 113:
  
 
== Enable Validation ==
 
== Enable Validation ==
By itself the form binding is not very useful in this example. It would simply delay saving the input values to the real object until the next button is clicked.
+
By itself the form binding is not very useful in this example. It would simply postpone saving the input values to the real object until the "next"-button is clicked.
In combination with form '''validation''' things get more interesting.
+
 
 +
The combination with '''Form Validation''' makes things a little more interesting. In this example I use the '''[http://books.zkoss.org/zk-mvvm-book/8.0/data_binding/validator.html#form-bean-validator formBeanValidator]''', which is a predefined validator leveraging the [http://beanvalidation.org/1.0/spec/ JSR-303 Bean Validation API].
 +
 
 +
(It is still possible to implement your own form validator(s) [http://books.zkoss.org/zk-mvvm-book/8.0/data_binding/validator.html#dependent-property-validator-in-form-binding] without additional 3rd party libraries.)
  
<source lang="xml" high="2">
+
<source lang="xml" highlight="2">
 
<div form="@id('orderForm') @load(vm.order) @save(vm.order, before=vm.wizardModel.nextCommand)
 
<div form="@id('orderForm') @load(vm.order) @save(vm.order, before=vm.wizardModel.nextCommand)
 
@validator('formBeanValidator', prefix='p_', groups=wizardModel.currentStep.validationGroups)">
 
@validator('formBeanValidator', prefix='p_', groups=wizardModel.currentStep.validationGroups)">
 
</source>
 
</source>
 +
* '''prefix='p_' ''': defines the prefix for the resulting validation messages
 +
* '''groups=wizardModel.currentStep.validationGroups''': defines the varying validation groups to be checked for each step
  
In this example I use the '''[http://books.zkoss.org/zk-mvvm-book/8.0/data_binding/validator.html#form-bean-validator formBeanValidator]''', which is a predifined validator leveraging the [http://beanvalidation.org/1.0/spec/ JSR-303 Bean Validation API].
+
To provide an implementation of the JSR-303 Bean Validation I chose the "hibernate-validator" dependency, which can be used separately from the hibernate ORM mapping framework.
 
 
(It is still possible to implement your own form validator [http://books.zkoss.org/zk-mvvm-book/8.0/data_binding/validator.html#dependent-property-validator-in-form-binding].)
 
  
;/wizardexample/pom.xml [https://github.com/cor3000/zk-wizard-example/blob/part-3/pom.xml#L70]
+
;/wizardexample/pom.xml [https://github.com/zkoss-demo/zk-wizard-example/blob/part-3/pom.xml#L70]
:the hibernate-validator dependency provides an implementation of JSR-303, which can be used separately from the hibernate ORM mapping framework
+
:add the dependency
<source lang="xml" high="2">
+
<source lang="xml" highlight="2">
 
<dependency>
 
<dependency>
 
<groupId>org.hibernate</groupId>
 
<groupId>org.hibernate</groupId>
Line 133: Line 138:
 
</source>
 
</source>
  
This API is well documented and can be applied simply using [http://docs.jboss.org/hibernate/stable/validator/reference/en-US/html/chapter-bean-constraints.html#table-spec-constraints various constraint annotations].
+
This API is well documented and can be applied using [http://docs.jboss.org/hibernate/stable/validator/reference/en-US/html/chapter-bean-constraints.html#table-spec-constraints various constraint annotations].
  
;zk.example.order.api.CreditCard [https://github.com/cor3000/zk-wizard-example/blob/part-3/src/main/java/zk/example/order/api/CreditCard.java#L23]
+
Below the CreditCard class which adds several constraints to its fields.
<source lang="java" high="1,2">
+
Here I show the validation constraints for the '''number''' field.
 +
 
 +
;zk.example.order.api.CreditCard [https://github.com/zkoss-demo/zk-wizard-example/blob/part-3/src/main/java/zk/example/order/api/CreditCard.java#L23]
 +
<source lang="java" highlight="1,2">
 
@NotNull(groups={PaymentGroup.class, Default.class}, message="{field.empty}")
 
@NotNull(groups={PaymentGroup.class, Default.class}, message="{field.empty}")
 
@Size(min=16, groups={PaymentGroup.class, Default.class}, message="{creditCard.number.size}")
 
@Size(min=16, groups={PaymentGroup.class, Default.class}, message="{creditCard.number.size}")
Line 144: Line 152:
 
</source>
 
</source>
  
* '''Line 1:''' mandatory field
+
* '''Line 1:''' @NotNull meaning it may not be empty - making it a mandatory field
* '''Line 2:''' validate the credit card length to be exactly 16
+
* '''Line 2:''' @Size defining the required length of 16 characters
 +
 
 +
The annotation properties "groups" and "message" are explained further in the following sections.
  
=== Outputting validation messages ===
+
=== Output validation messages ===
  
 
Validation messages can be rendered directly in a zul file using the [http://books.zkoss.org/zk-mvvm-book/8.0/data_binding/validator.html#validation-message-holder validation messages holder] (here called '''vmsgs''').
 
Validation messages can be rendered directly in a zul file using the [http://books.zkoss.org/zk-mvvm-book/8.0/data_binding/validator.html#validation-message-holder validation messages holder] (here called '''vmsgs''').
;/wizardexample/src/main/webapp/WEB-INF/zul/order/steps/basket.zul [https://github.com/cor3000/zk-wizard-example/blob/part-3/src/main/webapp/WEB-INF/zul/order/steps/basket.zul#L66]
+
;/wizardexample/src/main/webapp/WEB-INF/zul/order/steps/basket.zul [https://github.com/zkoss-demo/zk-wizard-example/blob/part-3/src/main/webapp/WEB-INF/zul/order/steps/basket.zul#L66]
  
<source lang="xml" high="2">
+
<source lang="xml" highlight="2">
 
<sh:if test="@load(!empty vmsgs['p_basket.items'])">
 
<sh:if test="@load(!empty vmsgs['p_basket.items'])">
 
<label style="color: red" value="@load(vmsgs['p_basket.items'])"/>
 
<label style="color: red" value="@load(vmsgs['p_basket.items'])"/>
 
</sh:if>
 
</sh:if>
 
</source>
 
</source>
 +
*'''Line 2:''' uses the prefix 'p_' as specified above - that's how the validation message for the validated property "basket.items" can be retrieved using vmsgs['p_basket.items']
  
 
For ease of use I also enhanced the formRow template to provide a consistent way of displaying error messages on form fields.
 
For ease of use I also enhanced the formRow template to provide a consistent way of displaying error messages on form fields.
 
/wizardexample/src/main/webapp/WEB-INF/zul/template/wizard/formRow.zul
 
/wizardexample/src/main/webapp/WEB-INF/zul/template/wizard/formRow.zul
([https://github.com/cor3000/zk-wizard-example/commit/part-3?diff=split#diff-ba096286d873f9053d45f297c391b8a4 here the changes to formRow.zul in a diff view])
+
([https://github.com/zkoss-demo/zk-wizard-example/commit/part-3?diff=split#diff-ba096286d873f9053d45f297c391b8a4 here the changes to formRow.zul in a diff view])
  
 
The the error message is passed into the formRow template by reference as the '''error''' parameter.
 
The the error message is passed into the formRow template by reference as the '''error''' parameter.
  
;/wizardexample/src/main/webapp/WEB-INF/zul/order/steps/shippingAddress.zul [https://github.com/cor3000/zk-wizard-example/blob/part-3/src/main/webapp/WEB-INF/zul/order/steps/shippingAddress.zul#L12]
+
;/wizardexample/src/main/webapp/WEB-INF/zul/order/steps/shippingAddress.zul [https://github.com/zkoss-demo/zk-wizard-example/blob/part-3/src/main/webapp/WEB-INF/zul/order/steps/shippingAddress.zul#L12]
  
<source lang="xml" high="3,6,9">
+
<source lang="xml" highlight="3,6,9">
 
<formRow type="textbox" label="@init(i18n:nls('order.shippingAddress.street'))"  
 
<formRow type="textbox" label="@init(i18n:nls('order.shippingAddress.street'))"  
 
value="@ref(order.shippingAddress.street)"
 
value="@ref(order.shippingAddress.street)"
Line 183: Line 194:
 
The validation of the final step can be done using the '''Default''' group to re-validate the whole object before submitting the order.
 
The validation of the final step can be done using the '''Default''' group to re-validate the whole object before submitting the order.
  
Here for the wizard the groups are dynamically applied based on the current step.
+
Here for the wizard the groups are dynamically applied based on the current step of the wizard model.
  
<source lang="xml" high="">
+
<source lang="xml" highlight="">
 
@validator('formBeanValidator', prefix='p_', groups=wizardModel.currentStep.validationGroups)">
 
@validator('formBeanValidator', prefix='p_', groups=wizardModel.currentStep.validationGroups)">
 
</source>
 
</source>
  
Creating a [https://github.com/cor3000/zk-wizard-example/blob/part-3/src/main/java/zk/example/order/ValidatingWizardStep.java#L7 ValidatingWizardStep] (extending [https://github.com/cor3000/zk-wizard-example/blob/part-3/src/main/java/zk/example/wizard/model/WizardStep.java WizardStep]) conveniently holds the additional validation group for each step.
+
Finally, creating a [https://github.com/zkoss-demo/zk-wizard-example/blob/part-3/src/main/java/zk/example/order/ValidatingWizardStep.java#L7 ValidatingWizardStep] (extending [https://github.com/zkoss-demo/zk-wizard-example/blob/part-3/src/main/java/zk/example/wizard/model/WizardStep.java WizardStep]) conveniently holds the additional validation group for each step.
  
;zk.example.order.OrderViewModel [https://github.com/cor3000/zk-wizard-example/blob/part-3/src/main/java/zk/example/order/OrderViewModel.java#L63]
+
;zk.example.order.OrderViewModel [https://github.com/zkoss-demo/zk-wizard-example/blob/part-3/src/main/java/zk/example/order/OrderViewModel.java#L63]
:The validation groups are defined when creating a new step ([https://github.com/cor3000/zk-wizard-example/tree/part-3/src/main/java/zk/example/order/api/validation BasketGroup, ShippingGroup, PaymentGroup])
+
:The validation groups are defined when creating a new step ([https://github.com/zkoss-demo/zk-wizard-example/tree/part-3/src/main/java/zk/example/order/api/validation BasketGroup, ShippingGroup, PaymentGroup])
<source lang="java" high="6">
+
<source lang="java" highlight="6">
 
private void initWizardModel() {
 
private void initWizardModel() {
 
List<WizardStep> availableSteps = Arrays.asList(
 
List<WizardStep> availableSteps = Arrays.asList(
Line 204: Line 215:
 
...
 
...
 
</source>
 
</source>
* '''Line 6:''' the Confirmation step uses the '''Default''' group to validate the whole form object again before the final submit
+
* '''Line 6:''' the "Confirmation"-step uses the '''Default''' group to validate the whole form object again before the final submit
  
 
=== I18N ===
 
=== I18N ===
  
I18N of validation messages are held in /wizardexample/src/main/resources/ValidationMessages.properties [https://github.com/cor3000/zk-wizard-example/blob/part-3/src/main/resources/ValidationMessages.properties]
+
I18N of validation messages are held in /wizardexample/src/main/resources/ValidationMessages.properties [https://github.com/zkoss-demo/zk-wizard-example/blob/part-3/src/main/resources/ValidationMessages.properties]
  
 
= Summary =
 
= Summary =
  
As shown above conditional validation can be applied to the whole wizard while leaving major parts of the code untouched. The shadow elements and templates ensure a consistent look and feel throughout the different pages.
+
As shown above conditional validation can be applied to the whole wizard while leaving major parts of the code untouched. The shadow elements and templates ensure a consistent look and feel throughout the different pages, when displaying validation messages.
  
 
The Form Proxy mechanism preserves the object type of the form object making it transparently usable in each Step while only the surrounding Wizard knows about the validation logic.
 
The Form Proxy mechanism preserves the object type of the form object making it transparently usable in each Step while only the surrounding Wizard knows about the validation logic.
Line 220: Line 231:
  
 
== Download ==
 
== Download ==
* The source code for this article can be found in [https://github.com/cor3000/zk-wizard-example/tree/part-3 github (branch: part-3)].
+
* The source code for this article can be found in [https://github.com/zkoss-demo/zk-wizard-example/tree/part-3 github (branch: part-3)].
  
 
== Running the Example ==
 
== Running the Example ==

Latest revision as of 04:15, 20 January 2022

Documentationobertwenzel
obertwenzel

Author
Robert Wenzel, Engineer, Potix Corporation
Date
January 2016
Version
ZK 8.0

Introduction

This chapter will show how the ZK features Form Binding and Form Validation can be applied to the previously existing wizard (Part 2) with little impact to the overall code. [1]

The video above shows the resulting wizard including the validation.

Add the Form Binding

In order.zul the wizard content is surrounded by a div element initializing the form binding (same syntax as in ZK 6.5 or 7). This will wrap the original Order object into a Form Proxy (new in ZK 8) acting as a cache to allow validation before updates are propagated to the original object.

The form proxy object is then passed as the "order" parameter into the wizard, leaving the inner wizard and it's pages unaware of the added Form Binding.

/wizardexample/src/main/webapp/order.zul [2]
	<wizard wizardModel="@init(vm.wizardModel)" wrapperTemplate="formWrapper">
		<template name="formWrapper">
			<div form="@id('orderForm') @load(vm.order) @save(vm.order, before=vm.wizardModel.nextCommand)
					@validator('formBeanValidator', prefix='p_', groups=wizardModel.currentStep.validationGroups)">
				<sh:apply template="wizardContent" order="@init(orderForm)" savedOrder="@init(vm.order)"/>
			</div>
		</template>
	</wizard>
  • Line 3: initialize the form proxy and define the save command (save after completing a step before switching to the next step)
  • Line 5: passing both the form proxy order, and the real savedOrder (updated after succesful validation) as parameters

Wrap the wizardContainer

Also the wizard template was slightly changed to allow adding the form binding using an injected wrapperTemplate. This preserves the option to use the wizard without form binding - by using the "defaultWizardContentWrapper".

/wizardexample/src/main/webapp/WEB-INF/zul/template/wizard/wizard.zul [3]
 	<window border="normal" title="@load(wizardVM.currentStep.title)" ... >
		<sh:apply template="@init(empty wrapperTemplate ? 'defaultWizardContentWrapper' : wrapperTemplate)"/>
  	</window>
  
	<template name="defaultWizardContentWrapper">
		<sh:apply template="wizardContent"/>
	</template>

  	<template name="wizardContent">
	   ...
 	</template>

Enable form binding in the model classes

Since the form binding in ZK 8 is based on a Proxy Object mechanism the Order related classes need some annotations to give the Proxy mechanism the additional information how to create the Proxy and which methods to ignore.

For example calculated (or read only) fields which are derived from other fields should be annotated @Transient. Those getters will not be intercepted by the form proxy and keep performing their original calculations whenever executed.

zk.example.order.api.Basket [4]
here totalPrice and totalItems are based on getItems()/getItemPrice()/getQuantity() but not cached by the form proxy
	@Transient
	public BigDecimal getTotalPrice() {
		return this.getItems().stream()
				.map(BasketItem::getItemPrice)
				.reduce(BigDecimal.ZERO, BigDecimal::add);
	}

	@Transient
	public int getTotalItems() {
		return this.getItems().stream()
				.mapToInt(BasketItem::getQuantity)
				.sum();
	}

One limitation of the proxy mechanism is that a class (to be proxied) requires a zero-argument constructor. For this case or if you intend to use a class in an immutable way (and not create a nested form proxy objects) you can annotate a field getter with @Immutable. Which means it can still have a setter to replace the whole object, but the object itself is no longer wrapped by a form proxy and none of its original methods are proxied.

A good example is the class java.math.BigDecimal which is neither final nor immutable by definition but in many cases it is useful to treat it like immutable.

zk.example.order.api.BasketItem [5]
another example for @Transient and one for @Immutable (means only setting a new unitPrice is handled by the form proxy, while calling unitPrice.setScale(...) is not intercepted at all)
	@Transient
	public BigDecimal getItemPrice() {
		return getUnitPrice().multiply(BigDecimal.valueOf(getQuantity()));
	}
	...
	@Immutable
	public BigDecimal getUnitPrice() {
		return unitPrice;
	}
	public void setUnitPrice(BigDecimal unitPrice) {
		this.unitPrice = unitPrice;
	}

Enable Validation

By itself the form binding is not very useful in this example. It would simply postpone saving the input values to the real object until the "next"-button is clicked.

The combination with Form Validation makes things a little more interesting. In this example I use the formBeanValidator, which is a predefined validator leveraging the JSR-303 Bean Validation API.

(It is still possible to implement your own form validator(s) [6] without additional 3rd party libraries.)

	<div form="@id('orderForm') @load(vm.order) @save(vm.order, before=vm.wizardModel.nextCommand)
			@validator('formBeanValidator', prefix='p_', groups=wizardModel.currentStep.validationGroups)">
  • prefix='p_' : defines the prefix for the resulting validation messages
  • groups=wizardModel.currentStep.validationGroups: defines the varying validation groups to be checked for each step

To provide an implementation of the JSR-303 Bean Validation I chose the "hibernate-validator" dependency, which can be used separately from the hibernate ORM mapping framework.

/wizardexample/pom.xml [7]
add the dependency
	<dependency>
		<groupId>org.hibernate</groupId>
		<artifactId>hibernate-validator</artifactId>
		<version>5.2.1.Final</version>
	</dependency>

This API is well documented and can be applied using various constraint annotations.

Below the CreditCard class which adds several constraints to its fields. Here I show the validation constraints for the number field.

zk.example.order.api.CreditCard [8]
	@NotNull(groups={PaymentGroup.class, Default.class}, message="{field.empty}")
	@Size(min=16, groups={PaymentGroup.class, Default.class}, message="{creditCard.number.size}")
	public String getNumber() {
		return number;
	}
  • Line 1: @NotNull meaning it may not be empty - making it a mandatory field
  • Line 2: @Size defining the required length of 16 characters

The annotation properties "groups" and "message" are explained further in the following sections.

Output validation messages

Validation messages can be rendered directly in a zul file using the validation messages holder (here called vmsgs).

/wizardexample/src/main/webapp/WEB-INF/zul/order/steps/basket.zul [9]
	<sh:if test="@load(!empty vmsgs['p_basket.items'])">
		<label style="color: red" value="@load(vmsgs['p_basket.items'])"/>
	</sh:if>
  • Line 2: uses the prefix 'p_' as specified above - that's how the validation message for the validated property "basket.items" can be retrieved using vmsgs['p_basket.items']

For ease of use I also enhanced the formRow template to provide a consistent way of displaying error messages on form fields. /wizardexample/src/main/webapp/WEB-INF/zul/template/wizard/formRow.zul (here the changes to formRow.zul in a diff view)

The the error message is passed into the formRow template by reference as the error parameter.

/wizardexample/src/main/webapp/WEB-INF/zul/order/steps/shippingAddress.zul [10]
			<formRow type="textbox" label="@init(i18n:nls('order.shippingAddress.street'))" 
				value="@ref(order.shippingAddress.street)"
				error="@ref(vmsgs['p_shippingAddress.street'])"/> 
			<formRow type="textbox" label="@init(i18n:nls('order.shippingAddress.city'))" 
				value="@ref(order.shippingAddress.city)" 
				error="@ref(vmsgs['p_shippingAddress.city'])"/> 
			<formRow type="textbox" label="@init(i18n:nls('order.shippingAddress.zipCode'))" 
				value="@ref(order.shippingAddress.zipCode)"
				error="@ref(vmsgs['p_shippingAddress.zipCode'])"/>

Using Validation Groups

The Bean validation API also supports validation groups, which allow partial validation of the Order object, for a single step. The validation of the final step can be done using the Default group to re-validate the whole object before submitting the order.

Here for the wizard the groups are dynamically applied based on the current step of the wizard model.

	@validator('formBeanValidator', prefix='p_', groups=wizardModel.currentStep.validationGroups)">

Finally, creating a ValidatingWizardStep (extending WizardStep) conveniently holds the additional validation group for each step.

zk.example.order.OrderViewModel [11]
The validation groups are defined when creating a new step (BasketGroup, ShippingGroup, PaymentGroup)
	private void initWizardModel() {
		List<WizardStep> availableSteps = Arrays.asList(
				wizardStep(BASKET, BasketGroup.class),
				wizardStep(SHIPPING_ADDRESS, ShippingGroup.class),
				wizardStep(PAYMENT, PaymentGroup.class),
				wizardStep(CONFIRMATION, Default.class)
					.withBeforeNextHandler(this::sendOrder)
					.withNextLabel(NlsFunctions.nls("order.confirmation.button.sendNow")),
				...
  • Line 6: the "Confirmation"-step uses the Default group to validate the whole form object again before the final submit

I18N

I18N of validation messages are held in /wizardexample/src/main/resources/ValidationMessages.properties [12]

Summary

As shown above conditional validation can be applied to the whole wizard while leaving major parts of the code untouched. The shadow elements and templates ensure a consistent look and feel throughout the different pages, when displaying validation messages.

The Form Proxy mechanism preserves the object type of the form object making it transparently usable in each Step while only the surrounding Wizard knows about the validation logic.

Now as things are working nicely and user input is validated in a flexible / pluggable manner. Lets concentrate on the nice things and add different (responsive) Layout in Part 4

Download

Running the Example

Checkout part-3

   git checkout part-3

The example war file can be built with maven:

   mvn clean package

Execute using jetty:

   mvn jetty:run

Then access the overview page http://localhost:8080/wizardexample/order.zul


Comments



Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.