ZK8 Wizard Example - Part 3 final
Robert Wenzel, Engineer, Potix Corporation
January 2016
ZK 8.0
Introduction
- Part 1 - Defining the Wizard
- Part 2 - Order Wizard (a more complex example)
- Part 3 - Form Handling and Input Validation (You are here)
- Part 4 - Styling the wizard (with Bootstrap)
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]
Add form the binding
In order.zul the wizard content is surrounded by a div element initializing the form binding. This will wrap the original Order object into a form proxy 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.
- /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
To 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.
- /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 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 object) 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.
A good example is the class java.math.BigDecimal which is neither final nor immutable by definition but in many cases treated as 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 delay saving the input values to the real object until the next button is clicked. In combination with form validation things get more interesting.
<div form="@id('orderForm') @load(vm.order) @save(vm.order, before=vm.wizardModel.nextCommand)
@validator('formBeanValidator', prefix='p_', groups=wizardModel.currentStep.validationGroups)">
In this example I use the formBeanValidator, which is a predifined validator leveraging the JSR-303 Bean Validation API.
(It is still possible to implement your own form validator [6].)
- /wizardexample/pom.xml [7]
- the hibernate-validator dependency provides an implementation of JSR-303, which can be used separately from the hibernate ORM mapping framework
<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 simply using various constraint annotations.
- 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: mandatory field
- Line 2: validate the credit card length to be exactly 16
Outputting 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>
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.
@validator('formBeanValidator', prefix='p_', groups=wizardModel.currentStep.validationGroups)">
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.
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.
The Form Proxy mechanism preserves the 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
- The source code for this article can be found in github (branch: part-3).
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. |