Introduction
Usually we will use template and binding data directly in listbox while using MVVM, but there are some issues such like the zul file and ViemModel might be tightly coupled by the event processing in template, or you cannot use template since some framework limitation, or your data cannot rendered by template well.
However, finally you make a decision that to use ListitemRenderer and you still need to process some events, instead of go through the 'binding vm via java code', you can also try add EventListener in ListitemRenderer and pass some custom event with required data to listbox then bind that custom event of Listbox to ViewModel.
This article will describe how to pass event to listbox from inner item.
Note: You can just ignore the 'template' and 'binding vm via java code' mentioned above, they are actually not so related to this article.
Pre-request
ZK Basic MVVM Pattern
http://ben-bai.blogspot.tw/2012/12/zk-basic-mvvm-pattern.html
ZK Build View in ZUL file or Java Code (the java code part)
http://ben-bai.blogspot.tw/2012/12/zk-build-view-in-zul-file-or-java-code.html
ZK Listening/Processing Event in ZUL Page or Java Code
http://ben-bai.blogspot.tw/2012/12/zk-listeningprocessing-event-in-zul.html
Pass Event to Other Component
http://ben-bai.blogspot.tw/2012/12/pass-event-to-other-component.html
ZK Listbox: Using ListitemRenderer to Render Complex Data in Listbox
http://ben-bai.blogspot.tw/2012/12/zk-listbox-using-listitemrenderer-to.html
The Program
index.zul
In the zul file, we get model from vm, assign renderer to listbox, and execute some command in vm wile the custom onOrderEvent event of listbox is triggered.
As you can see, this is a very simple View for MVVM.
<zk>
<!-- Tested with ZK 6.0.2 -->
<div apply="org.zkoss.bind.BindComposer"
viewModel="@id('vm') @init('test.viewmodel.TestVM')">
<hbox>
<!-- simply get itemModel from vm, use ItemRenderer
to render items and to preprocess inner event
and trigger orderItem event while the onOrderEvent
custom event -->
<listbox width="400px" model="@load(vm.itemModel)"
itemRenderer="test.renderer.ItemRenderer"
onOrderEvent="@command('orderItem')">
<listhead>
<listheader label="Item Name" />
<listheader label="Available Amount" />
<listheader label="Order Amount" />
<listheader label="Order Button" />
</listhead>
</listbox>
<!-- simply get orderModel from vm, use OrderRenderer
to render items and to preprocess inner event
and trigger cancelOrder event while the onOrderEvent
custom event -->
<listbox width="400px" model="@load(vm.orderModel)"
itemRenderer="test.renderer.OrderRenderer"
onOrderEvent="@command('cancelOrder')">
<listhead>
<listheader label="Item Name" />
<listheader label="Ordered Amount" />
<listheader label="Cancel Button" />
</listhead>
</listbox>
</hbox>
</div>
</zk>
TestVM.java
In the ViewModel, there is a Model ShoppingCart in it, and have some getter to provide model, and some command that will be triggered by OrderEvent.
As you can see, this is a very simple ViewModel for MVVM
package test.viewmodel;
import org.zkoss.bind.annotation.Command;
import org.zkoss.bind.annotation.ContextParam;
import org.zkoss.bind.annotation.ContextType;
import org.zkoss.bind.annotation.NotifyChange;
import org.zkoss.zul.ListModel;
import test.data.Item;
import test.data.Order;
import test.data.ShoppingCart;
import test.event.OrderEvent;
/**
* Tested with ZK 6.0.2
*
* The VM interact with the view
*
* @author benbai123
*
*/
public class TestVM {
ShoppingCart _cart = new ShoppingCart();
public ListModel<Item> getItemModel () {
return _cart.getItemModel();
}
public ListModel<Order> getOrderModel () {
return _cart.getOrderModel();
}
@Command
@NotifyChange({"itemModel", "orderModel"})
public void orderItem (
@ContextParam(ContextType.TRIGGER_EVENT) OrderEvent event) {
_cart.orderItem(event);
}
@Command
@NotifyChange({"itemModel", "orderModel"})
public void cancelOrder (
@ContextParam(ContextType.TRIGGER_EVENT) OrderEvent event) {
_cart.cancelOrder(event);
}
}
Item.java
Contains information of an item
package test.data;
/**
* Item class contains item name and amount
* @author benbai123
*
*/
public class Item {
private String _itemName;
private int _amount;
public Item (String itemName, int amount) {
_itemName = itemName;
_amount = amount;
}
public String getItemName () {
return _itemName;
}
public void setAmount (int amount) {
_amount = amount;
}
public int getAmount () {
return _amount;
}
}
Order.java
Contains information of an order.
package test.data;
/**
* Order class contains item (the Item class) and item index
* (item index received while order that item)
* @author benbai123
*
*/
public class Order {
private Item _item;
private int _itemIndex;
public Order (Item item, int itemIndex) {
_item = item;
_itemIndex = itemIndex;
}
public Item getItem () {
return _item;
}
public int getItemIndex () {
return _itemIndex;
}
}
ShoppingCart.java
Contains a list of item and a list of order, also handle the 'order item' and 'cancel order' in it.
package test.data;
import java.util.ArrayList;
import java.util.List;
import org.zkoss.zul.ListModel;
import org.zkoss.zul.ListModelList;
import test.event.OrderEvent;
/**
* The shoppingcart contains the item model and order model
* also handle the orderItem and cancelOrder here
*
* @author benbai123
*
*/
public class ShoppingCart {
private ListModelList<Item> _itemModel;
private ListModelList<Order> _orderModel;
public ListModel<Item> getItemModel () {
if (_itemModel == null) {
List<Item> items = new ArrayList<Item>();
items.add(new Item("Item One", 5));
items.add(new Item("Item Two", 3));
items.add(new Item("Item Three", 10));
_itemModel = new ListModelList<Item>(items);
}
return _itemModel;
}
public ListModel<Order> getOrderModel () {
if (_orderModel == null) {
_orderModel = new ListModelList<Order>(new ArrayList<Order>());
}
return _orderModel;
}
public void orderItem (OrderEvent event) {
addOrder (event.getItemName(), event.getAmount(), event.getItemIndex());
decreaseItemAmount (event.getItemIndex(), event.getAmount());
}
public void cancelOrder (OrderEvent event) {
removeOrder (event.getItemName());
increaseItemAmount (event.getItemIndex(), event.getAmount());
}
private void decreaseItemAmount (int index, int amount) {
Item item = _itemModel.getElementAt(index);
item.setAmount(item.getAmount() - amount);
}
private void increaseItemAmount (int index, int amount) {
Item item = _itemModel.getElementAt(index);
item.setAmount(item.getAmount() + amount);
}
private void addOrder (String itemName, int amount, int itemIndex) {
Order order = getOrderByName(itemName);
if (order != null) {
Item item = order.getItem();
item.setAmount(item.getAmount() + amount);
} else {
order = new Order(new Item(itemName, amount), itemIndex);
_orderModel.add(order);
}
}
private void removeOrder (String itemName) {
Order order = getOrderByName(itemName);;
if (order != null) {
_orderModel.remove(order);
}
}
private Order getOrderByName (String itemName) {
int size = _orderModel.getSize();
Order order;
for (int i = 0; i < size; i++) {
order = _orderModel.getElementAt(i);
if (itemName.equals(order.getItem().getItemName())) {
return order;
}
}
return null;
}
}
ItemRenderer.java
Renderer to render a list of Item.
The order button will process the onClick event itself, and pass the OrderEvent with required data to listbox as needed. This let us only need to face the event we are interested (the OrderEvent of the Item List) instead of face all button click then handle all situation in ViewModel.
package test.renderer;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zul.Button;
import org.zkoss.zul.Intbox;
import org.zkoss.zul.Listbox;
import org.zkoss.zul.Listcell;
import org.zkoss.zul.ListitemRenderer;
import org.zkoss.zul.Listitem;
import test.data.Item;
import test.event.OrderEvent;
/**
* Tested with ZK 6.0.2
*
* ItemRenderer render Item class to listitem
* @author benbai123
*
*/
public class ItemRenderer implements ListitemRenderer<Item> {
@SuppressWarnings({ "unchecked", "rawtypes" })
public void render (final Listitem listitem, final Item data, final int index) {
final Listbox lb = listitem.getListbox();
// item name and available amount
new Listcell(data.getItemName()).setParent(listitem);
new Listcell(data.getAmount() + "").setParent(listitem);
// intbox for input the amount to order
Listcell lc = new Listcell();
final Intbox intbox = new Intbox();
intbox.setParent(lc);
lc.setParent(listitem);
// button to perform the order action
lc = new Listcell();
Button orderBtn = new Button("Order Item");
orderBtn.addEventListener(Events.ON_CLICK, new EventListener() {
public void onEvent (Event event) {
// if the value is not grater zero or over the available amount
// show alert message
int amount = intbox.getValue() == null? 0 : intbox.getValue();
if (amount <= 0) {
Clients.alert(" positive value only");
} else if (amount > data.getAmount()) {
Clients.alert(" over available amount ");
} else {
// post an OrderEvent to listbox
// to inform order action with the
// item name and the amount to order
Item item = new Item(data.getItemName(), amount);
Events.postEvent(OrderEvent.getOrderEvent(lb, item, index));
}
}
});
orderBtn.setParent(lc);
lc.setParent(listitem);
}
}
OrderRenderer.java
Renderer to render a list of Order.
Similar to the order button, the cancel button will process the onClick event itself, and pass the OrderEvent with required data to listbox as needed.
package test.renderer;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.event.EventListener;
import org.zkoss.zk.ui.event.Events;
import org.zkoss.zul.Button;
import org.zkoss.zul.Listbox;
import org.zkoss.zul.Listcell;
import org.zkoss.zul.Listitem;
import org.zkoss.zul.ListitemRenderer;
import test.data.Item;
import test.data.Order;
import test.event.OrderEvent;
/**
* Tested with ZK 6.0.2
*
* OrderRenderer render Order class to listitem
* @author benbai123
*
*/
public class OrderRenderer implements ListitemRenderer<Order> {
@SuppressWarnings({ "unchecked", "rawtypes" })
public void render (final Listitem listitem, final Order data, final int index) {
final Listbox lb = listitem.getListbox();
// get item from order
Item item = data.getItem();
// item name and ordered amount
new Listcell(item.getItemName()).setParent(listitem);
new Listcell(item.getAmount() + "").setParent(listitem);
// button to perform the cancel action
Listcell lc = new Listcell();
Button cancelBtn = new Button("Cancel Order");
cancelBtn.addEventListener(Events.ON_CLICK, new EventListener() {
public void onEvent (Event event) {
// post an OrderEvent to listbox
// to inform cancel action with the
// item name and the ordered amount to restore
Events.postEvent(
OrderEvent.getOrderEvent(lb, data.getItem(), data.getItemIndex())
);
}
});
cancelBtn.setParent(lc);
lc.setParent(listitem);
}
}
OrderEvent.java
Contains the data of an order for both to order item or to cancel order.
package test.event;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
import test.data.Item;
/**
* Tested with ZK 6.0.2
* @author benbai123
*
*/
public class OrderEvent extends Event {
private static final long serialVersionUID = 3645653880934243558L;
private final String _itemName;
// itemIndex denotes the index to restore the amount
private final int _itemIndex;
private final int _amount;
public static OrderEvent getOrderEvent (Component target, Item data, int index) {
return new OrderEvent("onOrderEvent", target, data.getItemName(), index, data.getAmount());
}
public OrderEvent (String name, Component target, String itemName, int itemIndex, int amount) {
super(name, target);
_itemName = itemName;
_itemIndex = itemIndex;
_amount = amount;
}
public String getItemName () {
return _itemName;
}
public int getItemIndex () {
return _itemIndex;
}
public int getAmount () {
return _amount;
}
}
The Result
View demo online
http://screencast.com/t/XZNxVOzbs
Extended Reading
http://stackoverflow.com/questions/13818305/pure-java-binding-in-zk-listbox-with-itemrenderer
http://www.zkoss.org/forum/listComment/20938-Listbox-using-template-how-can-I-get-the-previous-row-so-can-use-in-logic-for-current-row
Download
Demo flash at github
https://github.com/benbai123/ZK_Practice/blob/master/demo_src/swf/Pattern/MVVM/handle_event_in_item_renderer_with_mvvm.swf
Full project at github
https://github.com/benbai123/ZK_Practice/tree/master/Pattern/MVVM/RendererWithMVVM