The Final Grid
This article is out of date, please refer to http://books.zkoss.org/zkessentials-book/master/ for more up to date information.
The fully implemented Product View will look something like this, as shown in the below:
In the previous section, we saw how the renderer assigns UI components (for example, Image and Label) into rows of a Grid for UI presentation.
ProductViewCtrl.java - Rendering Rows
public void doAfterCompose(Component comp) throws Exception {
...
prodGrid.setRowRenderer(new RowRenderer() {
public void render(Row row, Object data) throws Exception {
final Product prod = (Product)data;
Image img = new Image(prod.getImgPath());
img.setWidth("70px");
img.setHeight("70px");
img.setParent(row);
new Label(prod.getName()).setParent(row);
new Label(""+prod.getPrice()).setParent(row);
new Label(""+prod.getQuantity()).setParent(row);
new Label(YYYY_MM_DD_hh_ss.format(prod.getCreateDate())).setParent(row);
initOperation(prod).setParent(row);
}
...
});
}
The last column contains a Cell component (returned by the method initOperation) which allows end users to ADD items to be purchased. In this section, we'll look into the implementation of this functionality which fulfills the requirements for the Product View Grid.
ProductViewCtrl.java - Implementation for "Adding" Items to Purchase
private Cell initOperation(final Product prod){
Cell cell = new Cell();
final Spinner spinner = new Spinner(1);
spinner.setConstraint("min 1 max "+prod.getQuantity());
spinner.setParent(cell);
Button button = new Button("add");
button.setImage("/image/ShoppingCart-16x16.png");
button.setParent(cell);
final Label errorLb = new Label();
errorLb.setParent(cell);
button.addEventListener(ON_CLICK, new EventListener() {
public void onEvent(Event event) throws Exception {
ShoppingCartCtrl ctrl = ShoppingCartViewCtrl.getInstance(desktop);
try{
ctrl.addItem(prod, spinner.getValue());
errorLb.setValue("");
}catch(WrongValueException e){
errorLb.setValue(e.getMessage());
}
}
});
return cell;
}
The Cell Component
In the initOperation method, Spinner and Button are created programmatically to allow end users to select and submit the quantity of the items to be purchased. The Cell is set as the parent component to these components, serving as a container.
Setting Constraints for an Input UI Component
All the ZK components implementing the InputElement interface can call the InputElement.setConstraint(String constr) method to set an upper and lower bound on the allowable input value. For instance, value selected for the Spinner component is only meaningful if it's between 1 and the quantity remained in stock for each product; hence, we could call the setConstraint to impose the allowable range:
spinner.setConstraint("min 1 max "+prod.getQuantity());
This convenient method offers us an easy way to impose a simple validation mechanism for the input components such as Textbox, Intbox, Datebox, and Combobox.
Registering an Event Listener
When users click the "add" button, we need to verify whether the quantity from all orders combined does not exceed the quantity in stock for each product. For the button to be created here programmatically, we'll need to add an event listener manually, as oppose to making button declaration in ZUL and rely on the GenericForwardComposer facilitate this process.
private Cell initOperation(final Product prod){
...
Button button = new Button("add");
button.addEventListener(ON_CLICK, new EventListener() {
public void onEvent(Event event) throws Exception {
//event handling code here...
});
return cell;
}
Utilizing Session Scope Attributes
The tasks needed to be done in the button's event handling is adding the purchased product to the shopping cart in the quantity that's specified. Since a shopping cart instance should pervade a user's session, it makes sense to save, or retrieve it as a session attribute:
ShoppingCartViewCtrl.java
private static ShoppingCart getShoppingCart(Session session){
ShoppingCart cart =
(ShoppingCart) session.getAttribute("ShoppingCart");
if(cart==null){
session.setAttribute("ShoppingCart", cart = new ShoppingCart());
}
return cart;
}
So that when we try to add an item, along with its quantity, we could get the same shopping cart instance for the session and validate whether the addition is allowed. Note that session is an implicit object instantiated by ZK. ShoppingCartViewCtrl.java
public void addItem(Product prod, int amount) {
try{
getShoppingCart(session).add(prod, amount, new AddToCartCallback() {
//validation code
});
refresh();
}catch(OverQuantityException e){
throw new WrongValueException(e);
}
}
The ShoppingCartView class is a controller where we access the single shopping cart instance across a session and renders the shopping cart data in the Shopping Cart View. Since only one single controller instance ShoppingCartViewCtrl is needed per desktop, we save and retrieve the ShoppingCartViewCtrl instance in a desktop scope. ShoppingCartViewCtrl.java
public static ShoppingCartCtrl getInstance(Desktop desktop){
return (ShoppingCartCtrl) desktop.getAttribute(KEY_SHOPPING_CART);
}
With the ShoppingCart instance stored as a session attribute, and the ShoppingCartViewCtrl instance stored in desktop scope, they are made easily accessible to us in the button's event handling code.
private Cell initOperation(final Product prod){
...
button.addEventListener(ON_CLICK, new EventListener() {
public void onEvent(Event event) throws Exception {
ShoppingCartCtrl ctrl = ShoppingCartViewCtrl.getInstance(desktop);
try{
ctrl.addItem(prod, spinner.getValue());
errorLb.setValue("");
}catch(WrongValueException e){
errorLb.setValue(e.getMessage());
}
}
});
return cell;
}