ZK8 Wizard Example - Part 3 final

From Documentation
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]

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 enable wrapping the wizardContent the wizard template was slightly changed to allow an injected wrapperTemplate to apply the form logic preserving the possibility 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 should be annotated @Transient. Those getters will not be intercepted by the form proxy to perform their original calculation whenever executed.

zk.example.order.api.Basket [4]
here the totalPrice and totalItems are aggregated iterating over the whole collection returned by getItems() but not cached by the form proxy to be always up to date
	@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 @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]
shows another example for @Transient and one for @Immutable (means only replacing the unitPrice 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 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 LINK ME which is a predifined validator leveraging the JSR-303 Bean Validation API LINK ME It is still possible to implement your own form validator LINK ME.

/wizardexample/pom.xml [6]
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 annotations LINK ME.

zk.example.order.api.CreditCard [7]
	@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 validationMessages LINK ME object (here called vmsgs).

/wizardexample/src/main/webapp/WEB-INF/zul/order/steps/basket.zul [8]
	<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 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 [9]

before:

			<formRow type="textbox" label="@init(i18n:nls('order.shippingAddress.street'))" value="@ref(order.shippingAddress.street)"/> 
			<formRow type="textbox" label="@init(i18n:nls('order.shippingAddress.city'))" value="@ref(order.shippingAddress.city)"/> 
			<formRow type="textbox" label="@init(i18n:nls('order.shippingAddress.zipCode'))" value="@ref(order.shippingAddress.zipCode)"/>

after:

			<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 allows validation groups, which are applied dynamically based on the current wizard step.

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

Validation groups allow partial validation of the Order object. The validation of the final step can be done using the Default group to revalidate the whole object before submitting the order.

Creating a ValidatingWizardStep (extends WizardStep) conveniently holds the additional validation group(s) required for each step.

zk.example.order.OrderViewModel [10]
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 5: the last 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 [11]

Summary

Lets add a different 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.