ZK5.0和客户端+服务器端相结合的编程方式
译注:我对JSF比较熟悉,最近想研究一些其它的事件驱动的基于组件的WEB层框架,继Wicket和GWT之后,我开始仔细学习了一下ZK,虽然之前说的那几种框架也各有很多的优点,但ZK还是给了我很强的冲击。肤浅的总结一下:
我现在还在学习,上面只是粗浅的认识,下面是关于ZK5的一篇文章,我照着文章几分钟就做出了一个还挺漂亮的示例,现在把它翻译一下。 |
简介
从ZK 5开始,开发者不仅能(继续)享受使用服务器为中心的便利开发方式,而且还能使用客户端编程进行全面的控制。二者可以按需选择。这篇文章中将为您分别演示使用服务器为中心的模式和客户端/服务器混合模式来编写一个真实的应用。
应用: ZK Finance(ZK 财经)
ZK finance 是一个真实的应用,用户可以查找一个股票的历史价格,并通过一个表格和图表来显示结果。下面是一个界面图,在左面板上有一个搜索框,右边是显示结果的表格和图表。
纯粹的服务器处理方式
首先我们使用服务器端编程来实现这个应用,我们使用MVC (Model-View-Controller) 模式。
模型
这里有两个对象——股票和价格。它们是一对多的关系,一支股票有多个历史价格。
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
}
然后我们创建一个DAO对象来负责提供股票数据,StockDAO.java
public class StockDAO {
private List stocks = new LinkedList();
public Stock getStock(int id) {}
public List findAll() {}
}
View
提供一个搜索功能在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>
我们使用一个数据表格和图表来显示价格
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
搜索功能
在StockController.java中我们给TextBox注册一个onChanging事件的监听器,当用户输入时我们搜索股票并把结果显示在ListBox中
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();
}
}
当用户点击任何一支股票,onSelect事件被触发,onSelect$itemList()这个监听方法被执行,然后重新加载price.zul,显示这支股票的历史价格。
在表格和图中显示股票的历史价格
在PriceController.java中我们得到股票的历史价格的数据并为图表构建一个合适的模型。
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混合模式
除了上面的纯粹的服务器编程方式,我们再介绍一种混合模式。 我们想提高搜索的响应速度,那可以把相应的代码移到客户端。下面我们用搜索功能做一个例子。
修改现有的代码
为了在客户端实现搜索功能,首先我们要加载股票数据到客户端,我们使用一个include组件从服务器端加载data.xml。
<include id="data" src="data.xml" comment="true"/>
其次在服务器端的控制器(StockController.java) 可以不要了,它的功能将在客户端实现。
<borderlayout id="main">
另外我们不再在服务器端生成股票列表了,把这部分相关的服务器端代码也除去。
<listbox id="list" rows="10" width="300px">
客户端编程
为了在客户端实现搜索功能,我们为listbox 创建一个更新方法来生成用户搜索的股票项目。
<zk xmlns:w="http://www.zkoss.org/2005/zk/client"> .... <listbox id="list" rows="10" width="300px"> <attribute w:name="update"><![CDATA[ (function () { var data; function loadData(w) { var xmlDoc = zUtl.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>
- $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.
然后我们为textbox注册一个客户端的onChange监听方法,这个将方法调用listbox的update方法去更新listbox。注意我们要声明一个命名空间(w: ),因为这个监听器是在客户端的。
<textbox id="inp" w:onChanging="this.$f('list').update(event.data.value)"/>
此外,我们还要在一开始显示所有的股票,这需要调用注册一个bind_方法来实现初始化,这个方法将调用listbox的update方法。
<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>
和服务器端交互
最后,还需要和服务器端交换,用户点击一支股票,需要告诉服务器更新历史价格的表格和图表。在listbox上指定onSelect事件,这样一旦用户点击了一个股票,ZK引擎将给服务器发送一个onSelect事件。
<listbox id="list" onSelect="" rows="10" width="300px">
接下来,为了在服务器端处理onSelect事件, 我们在服务器端实现一个service,通过它我们可以实现股票历史价格的更新:
<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>
总结
在上文中,我们演示了两种不同的实现方式——纯粹的服务器编程和客户端/服务器混合编程——来实现同一个应用。无论使用哪一种,ZK 5 提供了一种新的方式来平衡易于开发和更多的可控性。
译注:最后上传两张IDE中的截图吧,我用的是ZK3.6.2,客户端/服务器端混合编程的方式没有去做。