MVVM in ZK6: Work with Spring
Hawk Chen, Engineer, Potix Corporation
February 08, 2012
ZK 6
Foreword
In the previous article: MVVM in ZK 6 - Design CRUD page by MVVM pattern, Dennis introduced us to how we can build an order management application. Users can create, read, modify orders and delete using the confirmation dialog. However, in larger projects, it is popular to adopt Spring framework as an application infrastructure. This article continues the story. The goal is to build an application with same functions but using Spring to manage objects, including ViewModel, validator, converter and even validation message. We also utilize JPA as the persistence layer in our example application.
Declare Spring bean
We will declare OrderService (service layer), and OrderDao as the singleton bean.
Prepare a ViewModel
Although we adopt Spring framework in this example, we don't make the ViewModel as a Spring bean for 2 reasons;
- None of Spring's scopes matches correctly with ViewModel's lifecycle
- There will be two variables referenced to the same ViewModel in which one is created by @id, and another by Spring bean's name
These two variables will confuse developers
Therefore, We use ViewModel like the original way and activate Spring variable resolver.
order3.zul
<?variable-resolver class="org.zkoss.zkplus.spring.DelegatingVariableResolver"?>
<window title="Order Management" border="normal" width="600px" apply="org.zkoss.bind.BindComposer"
viewModel="@id('vm') @init('org.zkoss.bind.examples.spring.order.viewmodel.OrderVM')" >
As ViewModel is not a Spring bean, we can't retrieve an object of OrderService by Spring's autowire, however, we can use ZK's @WireVariable to achieve the same result.
public class OrderVM {
//the order list
List<Order> orders;
@WireVariable
OrderService orderService;
}
Converter as a Singleton Bean
Originally, we access converters through OrderVM. To increase reusability of a converter, we create an independent class for each converter inside in OrderVM and declare them as beans. Thus, any ZUL can easily utilize a converter without OrderVM and eliminate those getter methods for converters.
DateFormatConverter.java
@org.springframework.stereotype.Component("dateFormatConverter")
public class DateFormatConverter implements Converter {
public Object coerceToUi(Object val, Component comp, BindContext ctx) {
//user sets format in annotation of binding or args when calling binder.addPropertyBinding()
final String format = (String) ctx.getConverterArg("format");
if(format==null) throw new NullPointerException("format attribute not found");
final Date date = (Date) val;
return date == null ? null : new SimpleDateFormat(format).format(date);
}
public Object coerceToBean(Object val, Component comp, BindContext ctx) {
final String format = (String) ctx.getConverterArg("format");
if(format==null) throw new NullPointerException("format attribute not found");
final String date = (String) val;
try {
return date == null ? null : new SimpleDateFormat(format).parse(date);
} catch (ParseException e) {
throw UiException.Aide.wrap(e);
}
}
}
Converters have no dependency with other shorter-lived bean and are stateless, so declare them as singleton beans. This converter accepts an argument with key "format" as a pattern to format the Date value.
order3.zul
<listitem >
<listcell label="@bind(item.id)"/>
<listcell label="@bind(item.quantity)"/>
<listcell label="@bind(item.price) @converter(numberFormatConverter, format='###,##0.00')"/>
<listcell label="@bind(item.creationDate) @converter(dateFormatConverter, format='yyyy/MM/dd')"/>
<listcell label="@bind(item.shippingDate) @converter(dateFormatConverter, format='yyyy/MM/dd')"/>
</listitem>
Validator as a Singleton Bean
Validators are similar to converters, we extract each validator inside a ViewModel as an independent class
ShippingDateValidator.java
@Component("shippingDateValidator")
public class ShippingDateValidator extends AbstractValidator{
public void validate(ValidationContext ctx) {
Date shipping = (Date)ctx.getProperty().getValue();//the main property
Date creation = (Date)ctx.getProperties("creationDate")[0].getValue();//the collected
//multiple fields dependent validation, shipping date have to large than creation more than 3 days.
if(!isDayAfter(creation,shipping,3)){
addInvalidMessage(ctx, "must large than creation date at least 3 days");
}
}
static public boolean isDayAfter(Date date, Date laterDay , int day) {
if(date==null) return false;
if(laterDay==null) return false;
Calendar cal = Calendar.getInstance();
Calendar lc = Calendar.getInstance();
cal.setTime(date);
lc.setTime(laterDay);
int cy = cal.get(Calendar.YEAR);
int ly = lc.get(Calendar.YEAR);
int cd = cal.get(Calendar.DAY_OF_YEAR);
int ld = lc.get(Calendar.DAY_OF_YEAR);
return (ly*365+ld)-(cy*365+cd) >= day;
}
}
We declare validators as singleton beans because they are stateless. This validator validates one field (shippDate) upon another field (creationDate) and stores message into the validation message holder.
order3.zul
<datebox
value="@load(vm.selected.shippingDate) @save(vm.selected.shippingDate, before='saveOrder') @validator(shippingDateValidator)"/>
We can therefore use them in ZK bind expression without prefix “vm.” because we retrieve them by ZK's DelegatingVariableResolver . Any ZUL can access a validator through its bean name only instead of ViewModel.
Package Structure
The final package structure looks like:
Every reusable element is separated to an independent class in multiple packages.
LiveDemo
Summary
ZK MVVM pattern is very suitable to cooperate with Spring framework. All objects used on ZUL can be accessed through a consistent mechanism provided by Spring, thus increasing those objects’ reusability and reducing developer’s burden.
Download
zkbind-springdemo (war with source code)
See Also
- Envisage ZK 6: The Next Generation Data Binding System
- Hello ZK MVVM
- MVVM in ZK 6 - Design your first MVVM page
- MVVM in ZK 6 - Design CRUD page by MVVM pattern
Comments
Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License. |