ZK 5.0 and Server+Client Fusion
Robbie Cheng, Technology Evangelist, Potix Corporation
July 14, 2009
ZK 5.0
Introduction
Since ZK 5, developers can not only leverage the ease of development of the server-centric architecture, but also the full controllability of client-side programming. It's developer's choice to implement a function using either server-side or client side development according to their requirements.
In this article, we will demonstrate two different approaches, pure server-centric, and client/server development to implement a real-world application.
Resources
Download ZK 5 Live Demo Documentation
The Application: ZK Finance
ZK finance is real-world application; users can look up the historical price of stocks, and see the result in a table, and a chart. Here is the snapshot of this application, including a search box on the left panel, and a table, and chart on the right panel.
Pure Server-centric approach
First of all, we use the pure server-centric approach to implement this application using MVC (Model-View-Controller) pattern.
Model
There are two objects of this application, including Stock, and Price. The relationship between Stock and Price is one-to-many. Each stock has more than one Price objects to represent its historical price.
Stock.java
public class Stock {
private int _id;
private String _name;
private List _priceItems = new ArrayList();
....
getter and setter methods
}
Price.java
public class Price {
private String _date;
private double _open;
private double _high;
private double _low;
private double _close;
private int _volumn;
....
getter and setter methods
}
Moreover, we create a DAO object which is responsible for providing data of stock. StockDAO.java
public class StockDAO {
private List stocks = new LinkedList();
public Stock getStock(int id) {}
public List findAll() {}
}
View
We provide a search function, and show the search result in a Listbox.
index.zul
<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit"?>
<borderlayout id="main" apply="StockController">
<west title="ZK Finance" size="250px" flex="true"
splittable="true" minsize="210" maxsize="500" collapsible="true">
<panel>
<toolbar>
<label value="Search:" />
<textbox id="searchBox" ctrlKeys="#down#up"
focus="true" sclass="demo-search-inp" />
</toolbar>
<panelchildren>
<listbox id="itemList" model="@{main$composer.stocks}"
fixedLayout="true" vflex="true">
<listitem self="@{each='stock'}" value="@{stock}">
<listcell label="@{stock.name}" />
</listitem>
</listbox>
</panelchildren>
</panel>
</west>
<center>
<include id="detail"/>
</center>
</borderlayout>
Regarding historical price of a stock, we use Grid, and line Chart to show the result.
price.zul
<?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit"?>
<window id="main2" apply="PriceController">
<grid id="history" model="@{main2$PriceController.prices}" >
<columns menupopup="auto">
<column label="Date" />
<column label="Open" />
<column label="High" />
<column label="Low" />
<column label="Close" />
<column label="Volumn" />
</columns>
<rows>
<row self="@{each='price'}">
<label value="@{price.date}"/>
<label value="@{price.open}"/>
<label value="@{price.high}"/>
<label value="@{price.low}"/>
<label value="@{price.close}"/>
<label value="@{price.volumn}"/>
</row>
</rows>
</grid>
<chart id="line" width="500" height="250" type="line"
fgAlpha="128" model="@{main2$PriceController.cateModel}"/>
</window>
Controller
Search function
In StockController.java, we register onChanging event listener on Textbox component to get user’s input instantly, and then list all stocks that match the query in the Listbox component.
StockController.java
public class StockController extends GenericForwardComposer {
Textbox searchBox;
Listbox itemList;
Include detail;
StockDAO dao = new StockDAO();
private static String DETAIL_URL = "price.zul";
public void onCreate$main(){
itemList.setSelectedIndex(0);
Events.postEvent(new Event(Events.ON_SELECT, itemList));
}
public void onSelect$itemList(){
int id = ((Stock)itemList.getSelectedItem().getValue()).getId();
detail.setSrc(DETAIL_URL + "?id=" + id);
}
public void onChanging$searchBox(InputEvent event) {
String key = event.getValue();
LinkedList item = new LinkedList();
List items = dao.findAll();
if (key.trim().length() != 0) {
for (Iterator iterator = items.iterator(); iterator.hasNext();) {
Stock st = (Stock) iterator.next();
if (st.getName().toLowerCase()
.indexOf(key.toLowerCase()) != -1)
item.add(st);
}
itemList.setModel(new ListModelList(item));
} else itemList.setModel(new ListModelList(items));
}
public List getStocks(){
return dao.findAll();
}
}
Once the user clicks any Stock in the result, we register onSelect event listener to show the historical prices by reloading the price.zul page. onSelect$itemList()
Showing the result in table and chart
To show the historical prices of certain stock, in PriceController.java, we get prices data from StockDAO, and create required data model for chart component.
PriceController.java
public class PriceController extends GenericAutowireComposer {
private StockDAO dao = new StockDAO();
private CategoryModel cateModel;
private List items;
public PriceController() {
init();
}
public void init() {
//get stock id
int id = Integer.parseInt((String) Executions.getCurrent().getParameter("id"));
Stock stock = dao.getStock(id);
items = stock.getPriceItems();
//create category model for chart
cateModel = new SimpleCategoryModel();
for (Iterator iterator = items.iterator(); iterator.hasNext();) {
Price price = (Price) iterator.next();
cateModel.setValue(stock.getName(), price.getDate(), price.getClose());
}
}
public List getPrices(){
return items;
}
public CategoryModel getCateModel() {
return cateModel;
}
}
Server+client Fusion approach
In addition to the pure server-centric approach, we will illustrate a hybrid approach here. Let us say we want to improve the responsiveness of the search, then we can move the corresponding code to the client. In the following paragraphs, we use search function as an example.
Modification of existing code
To implement search function at client side, first of all, we have to load the stock data to client. We use an include component to load the data source, data.xml, from the server.
<include id="data" src="data.xml" comment="true"/>
Secondly, at the server side, the control code (StockController.java) shall be removed from index.zul since we will implement this function at client side.
<borderlayout id="main">
In addition, we no longer generate the stock list at the server-side so we remove code related to generating the search result, too.
<listbox id="list" rows="10" width="300px">
Client side programming
To implement the search function at client side, we create an update function in listbox where it generates stock items according to user’s input.
<zk xmlns:w="http://www.zkoss.org/2005/zk/client">
<script src="/zkau/web/js/zk.xml.wpd"/>
....
<listbox id="list" rows="10" width="300px">
<attribute w:name="update"><![CDATA[
(function () {
var data;
function loadData(w) {
var xmlDoc = zk.xml.Utl.parseXML(jq(w).html().replace(/<!--|-->/g, '').trim()),
ids = xmlDoc.getElementsByTagName("id"),
labels = xmlDoc.getElementsByTagName("name");
data = [];
jq(ids).each(function (i) {
data.push({id: this.firstChild.nodeValue, label: labels[i].firstChild.nodeValue});
});
}
function createItems (listbox, data) {
if (!data.length) return;
jq(data).each(function () {
listbox.appendChild(new zul.sel.Listitem({label: this.label, uuid: this.id}));
});
}
return function (txt) {
txt = txt || '';
if (!data) loadData(this.$f('data'));
this.clear();
createItems(this, jq.grep(data, function (item) {
return item.label.toLowerCase().indexOf(txt.toLowerCase()) != -1;
}));
this.stripe();
};
})()
]]></attribute>
</listbox>
- zk.xml.Utl.parseXML() is defined inside
zk/src/archive/web/js/zk/xml/utl.js
. To use it, we have to include its source by<script src="/zkau/web/js/zk.xml.wpd"/>
- $f (id), a way to get fellow component. It is the same as getFellow(id), i.e., an alias of getFellow.
- http://www.zkoss.org/2005/zk/client is the so-called client namespace that tells ZK engines to generate the code at the client-side instead of server-side.
Then, we register onChanging event listener on textbox to invoke the update function of listbox at clien-side, too. Notice that we have to specify the client namespace (w:), since the listener is running at the client.
<textbox id="inp" w:onChanging="this.$f('list').update(event.data.value)"/>
Besides, if we would like to show all stock items in the beginning, we have to invoke update function of listbox component at initialization by registering bind_ function to do the initialization.
<listbox id="list" rows="10" width="300px">
<attribute w:name="bind_">
function (desktop, skipper, after) {
this.$bind_.apply(this, arguments);
var self = this;
after.push(function () {
self.update();
self.firstChild.setSelected(true);
});
}
</attribute>
....
</listbox>
Interacting with server-side components
So far, we’ve migrated the search function to client-side, the last mile is to communicate with server to update the historical prices of selected stock item. To make sure the onSelect event will be send to server instantly, we simply register the onSelect event at listbox component. Thus, once users click any stock item, the ZK engine will sends an onSelect event to server.
<listbox id="list" onSelect="" rows="10" width="300px">
....
Next, to handle the onSelect event directly at server, we could implement a service at server side; then, we can update the content of historical price accordingly.
<listbox id="list" onSelect="" rows="10" width="300px">
....
</listbox>
<include id="content" src="price.zul?id=1"/>
<zscript>
public class MyService implements org.zkoss.zk.au.AuService {
public boolean service(org.zkoss.zk.au.AuRequest request, boolean everError) {
final String cmd = request.getCommand();
if (cmd.equals(Events.ON_SELECT)) {
String uuid = ((List)request.getData().get("items")).get(0);
System.out.println("selected:" + uuid);
content.setSrc("price.zul?id=" + uuid);
return true;
}
return false;
}
}
list.setAuService(new MyService());
</zscript>
Summary
In above paragraphs, we demonstrate two different approached to implement the same application using either pure server-side, or a hybrid approach. Whether to use server-centric or client-centric development are developers’ choices, ZK 5 points out a new way to implement Rich Internet Application that developers can leverage both ease of development, and controllability at the same time.
Download
The application could be downloaded here.
See Also
- ZK5: Client Computing with ZUML
- Client Namespace
- ZK 5.0 and jQuery
- ZK 5.0 and jQuery part 2
- ZK Selector -- Under construction. Support jQuery selector for ZK widget.
- Zk.Widget -- See the usage of $f(), $n(), bind_, init_, zk.widget.$()
- Client Side Actions -- Deprecated since ZK5
Copyright © Potix Corp. This article is licensed under GNU Free Documentation License. |