译注:我对JSF比较熟悉,最近想研究一些其它的事件驱动的基于组件的WEB层框架,继Wicket和GWT之后,我开始仔细学习了一下ZK,虽然之前说的那几种框架也各有很多的优点,但ZK还是给了我很强的冲击。肤浅的总结一下:
我现在还在学习,上面只是粗浅的认识,下面是关于ZK5的一篇文章,我照着文章几分钟就做出了一个还挺漂亮的示例,现在把它翻译一下。 |
从ZK 5开始,开发者不仅能(继续)享受使用服务器为中心的便利开发方式,而且还能使用客户端编程进行全面的控制。二者可以按需选择。这篇文章中将为您分别演示使用服务器为中心的模式和客户端/服务器混合模式来编写一个真实的应用。
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() {} }
提供一个搜索功能在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>
在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; } }
除了上面的纯粹的服务器编程方式,我们再介绍一种混合模式。 我们想提高搜索的响应速度,那可以把相应的代码移到客户端。下面我们用搜索功能做一个例子。
为了在客户端实现搜索功能,首先我们要加载股票数据到客户端,我们使用一个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>
然后我们为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,客户端/服务器端混合编程的方式没有去做。