内容


使用 jQuery、ZK 和 Java 代码的融合来增强 Ajax 开发

从服务器端和客户端编程快速获益

Comments

简介

jQuery 推广在 Cascading Style Sheets (CSS) 语法中使用选择器来检索 Document Object Model (DOM) 元素以及对这些元素进行操作。使用 jQuery 有很多优势,其中之一是,您不再有用 JavaScript 解析复杂 DOM 树的负担了。

ZK 是一个事件驱动的、基于组件的 Ajax 框架,因其使用 Java 语言来编程和与 Java EE 技术的集成而出名。它带来了 Ajax 桌面编程体验。对于预期的 HTML,ZK UI 可能被使用标记和一个表达式语言(EL)拼凑起来。

本文探讨了,服务器端代码和 jQuery 的结合如何帮助您应对企业 Ajax 应用程序开发中的挑战。jQuery 和 ZK 结合在一起可以整合技术,帮助您轻松高效地提供丰富且反应灵敏的 UI 体验。

下载 本文使用的样例代码。

Ajax 应用程序开发挑战

以下是企业级 Ajax 应用程序开发人员通常会遇到的挑战:

复杂性
Ajax 比较复杂。在 Ajax 通信模式下,当 DOM 中的元素单独更新时,web 页面不需要整体刷新。

在企业级应用程序中,从服务器端到客户端编组复杂的业务逻辑和手工处理每个 Ajax 调用需要付出艰辛的努力。

服务器端编程
服务器端 Ajax 解决方案不能利用客户端资源来构建一个更敏感的用户体验、离线功能、或者向客户端发布计算和数据存储。

服务器端解决方案不减少服务器端的内存占用,而且还增加了应用程序的可伸缩性。

客户端编程
客户端编程进一步提出了安全考虑和开发成本。业务逻辑和敏感数据暴露在浏览器上,使应用程序易受攻击。

客户端编程涉及大量 JavaScript 实现,如果您习惯于使用 Java EE 技术进行应用程序开发,可能会是一个挑战。

大数据
当 UI 层必须在一个视图上呈现许多数据时,另一个常见问题将会出现。例如,考虑一个常规 HTML 表格。当数据源变得很大时,您可能需要以下之一:
  • 冒险超载浏览器缓存或通过预加载大数据设置减缓流量。
  • 每当用户发出该请求时,手工在服务器端实现一种机制,来在视图上呈现感兴趣数据的一个子集。

为了消除这些障碍,一个可行的解决方案是使用一个框架,其中客户端和服务器状态是动态同步的。您可以在服务端利用所有安全的 Java EE 技术,而使用客户端计算功能计算响应性和进行 UI 控制。

ZK 的服务器和客户端融合架构

ZK 的创新架构提供了完全最大化服务器和客户端可用的所有技术的方法。您可以直接访问客户端小部件,只要您想。本小节主要介绍 ZK 服务器和客户端融合架构的一些优势。

ZK Loader 和 ZK AU Engine

ZK Loader 通过以下步骤为 URL 请求提供服务:

  • 在请求页面创建 UI 组件
  • 在组件上加载必要数据
  • 在 HTML 页面上呈现这些数据
  • 将其连同 ZK 的 Client Engine 发送回客户端

ZK Asynchronous Update (AU) Engine 和 Client Engine 处理 Ajax 通信,为您屏蔽掉 Ajax 调用处理的复杂性。图 1 显示 ZK Loader、ZK AU Engine 和 ZK Client Engine。

图 1. ZK 融合架构概述
展示 URL 和 ZK 从客户端引擎到服务器的请求、以及服务器返回一个 html 页面和 ZK 响应。
展示 URL 和 ZK 从客户端引擎到服务器的请求、以及服务器返回一个 html 页面和 ZK 响应。

解决 Ajax 复杂性

ZK 的 Client Engine 和 AU Engine 充当 pitcher 和 catcher 的角色,ZK 的事件队列依次传输将要处理的事件。 结果是异步事件处理被简化的就像桌面编程一样。

利用服务器端技术

因为服务器在 JVM 中包含一组 ZK 组件,相应的 ZK 小部件集作为 JavaScript 对象驻留在浏览器中。页面状态在两端保持同步。任何处理业务逻辑或敏感数据的事件处理保留在服务器端。当事件处理完成时,服务器端 ZK 组件状态的修改值反过来只会影响客户端 ZK 小部件。不会有业务逻辑暴露在客户端。

利用客户端技术

ZK 小部件是用 jQuery 编写的客户端 JavaScript 对象。因此,继承了 jQuery 的所有行为。由于在客户端和服务器端都有一个组件的完整表述及其状态,那么您可以选择在客户端或者服务器端设计应用程序。图 2 是一个示例。

图 2. ZK 客户端技术
图中箭头显示从 DOM 、可选客户端应用程序和 ZK 客户端应用程序指向 ZK Ajax 小部件,以及从 ZK Ajax 小部件指向它们。
图中箭头显示从 DOM 、可选客户端应用程序和 ZK 客户端应用程序指向 ZK Ajax 小部件,以及从 ZK Ajax 小部件指向它们。

处理大量数据

有时候,您需要解决在客户端处理大量数据的问题。ZK 用户显示数据记录的网格、列表框和组件有一个按需加载的特性,除非视图需要,数据不能在浏览器端呈现。这也有一个分页特性,这样大量数据就可以分布在多个页面上。有了这些特性,呈现大量数据的复杂性大大减少了。

使用 ZK 解决问题

要研究 ZK 的服务器和客户端融合架构,您需要简单了解一下样例:股票查看器应用程序,用两种方式:使用一个纯服务器驱动解决方案做为参考,以及在服务器和客户端分别进行事件处理。

股票查看器应用程序将读取一个 XML 格式的历史股票记录,并以列表格式和线性图显示数据。应用程序将包括一个自动完成搜索的搜索框,比如 Google Suggest。图 3 显示了该应用程序。

图 3. 股票查看应用程序
屏幕显示股票个数、最高价、开盘价、最低价和收盘价,以及交易量
屏幕显示股票个数、最高价、开盘价、最低价和收盘价,以及交易量

使用 ZK MVC 的服务器端解决方案

对于服务器驱动的方法,该样例适应 ZK 的 Model-View-Controller (MVC) 方法。

模型
该模型是由 3 个 Java 类、2 个数据对象和一个 DAO 对象构成。Stock 对象相当于多个 Price 对象。
	public class Stock {
	private int _id;
	private String _name;
	private List<Price> _priceItems = new ArrayList<Price>();
....
//getter and setter methods
}
	
	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
}

StockDAO 类解析 data.xml 中的历史股票数据,并在内存中创建 StockPrice 对象。每个对象被添加到 “stocks” 链表中。

             public class StockDAO {
             private List<Stock> stocks = new LinkedList<Stock>();
             public List findAll() {...} // returns all stock items in data.xml
		public Stock getStock(int id) {...}
}
视图
该视图是使用 ZK 的标记语言实现的。主页面 index.zul 被使用边界布局分段,西边部分包含搜索框和股票名列表,中间部分含有充当 price.zul 占位符的 include 元素。每个股票的数据记录在网格部分展示,并在 price.zul 的制图组件中绘制。

index.zul 清单如清单 1 所示:

  • 第 1 行,init 标记通知 ZK Loader 初始化 ZK 的 Data Binding Manager,以便数据源能动态绑定到 UI 组件,反之亦然。
  • 第 2 行,apply="StockController" 表示来自默认包的 StockController 将充当 borderlayout 组件的控制器。
  • 第 12 行,使用从 StockController 类的 getStocks() 方法获取的数据模型将 model="@{main$composer.stocks}" 转换成设定的 listbox
  • 第 14 至 16 行,listitem 组件为每个股票对象而创建,其名称被指定给 listcell 标签。
清单 1. index.zul
1. <?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit"?>
2. <borderlayout id="main" apply="StockController">
3.  	<west title="ZK Finance" size="250px" flex="true"
4. 		splittable="true" minsize="210" maxsize="500" collapsible="true">
5. 		<panel>
6. 			<toolbar>
7. 				<label value="Search:" />
8. 				<textbox id="searchBox" ctrlKeys="#down#up"
9. 					focus="true" sclass="demo-search-inp" />
10. 			</toolbar>
11. 			<panelchildren>
12. 				<listbox id="itemList" model="@{main$composer.stocks}"
13. 					fixedLayout="true" vflex="true">
14. 					<listitem self="@{each='stock'}" value="@{stock}">
15. 						<listcell label="@{stock.name}" />
16. 					</listitem>
17. 				</listbox>
18. 			</panelchildren>
19. 		</panel>
20. 	</west>
21. 	<center>
22. 		<include id="detail"/>
23. 	</center>
24. </borderlayout>

price.zul 清单如清单 2 所示:

  • 第 1 行,ZK 的 Data Binding Manager 依旧在此被初始化。
  • 第 2 行,apply="PriceController" 表示来自默认包的 PriceController 类将充当窗口组件的控制器。
  • 第 3 行,使用从 PriceController 类的 getPrices() 方法获取的数据模型将 model="@{main$composer.stocks}" 转换成设定网格。
  • 第 13 至 20 行,为每个 price 对象创建一个行组件,其值被指定给一个标签。
清单 2. price.zul
1. <?init class="org.zkoss.zkplus.databind.AnnotateDataBinderInit"?>
2. <window id="main2" apply="PriceController">
3. 	<grid id="history" model="@{main2$PriceController.prices}" >
4. 		<columns menupopup="auto">
5. 			<column label="Date" />
6. 			<column label="Open" />
7. 			<column label="High" />
8. 			<column label="Low" />
9. 			<column label="Close" />
10. 			<column label="Volumn" />
11.  		</columns>
12. 		<rows>
13. 			<row self="@{each='price'}">
14. 				<label value="@{price.date}"/>
15. 				<label value="@{price.open}"/>
16. 				<label value="@{price.high}"/>
17. 				<label value="@{price.low}"/>
18. 				<label value="@{price.close}"/>
19. 				<label value="@{price.volumn}"/>
20. 			</row>
21. 		</rows>
22. 	</grid>
23. 	<chart id="line" width="500" height="250" type="line"
24. 		fgAlpha="128" model="@{main2$PriceController.cateModel}"/>
25. </window>
控制器
控制器是从 ZK 的 rg.zkoss.zk.ui.util.GenericForwardComposer 扩展的类,使用 UI 组件自动连接数据对象。

StockController 类中,它控制 index.zul 的 borderlayout 组件中的视图:

  • 第 8 行,含有 id=mainborderlayout 是用一个 onCreate 事件监听器注册的,设置了将在视图中显示的默认股票详情。
  • 第 13 行,含有 id=itemList 的列表框是用一个 onSelect 事件监听器注册的,获取在 listbox 中选中的 listitem,然后设置带有 id=detail 的组件 — 页面资源用于展示股票数据和折线图。
  • 第 18 行,带有 id=searchBox 的文本框是用 onChanging 监听器注册的,循环遍历股票项,返回一个和用户输入匹配的股票列表,每次文本框的值都有所改变。有争论的事件扔给 org.zkoss.zk.ui.event.InputEvent,用户的输入分配给字符串 key
  • 第 34 行,getStocks() 方法返回 data. xml 中列出的所有股票项。
清单 3. StockController.java
1. public class StockController extends GenericForwardComposer {	
2. 	Textbox searchBox;
3. 	Listbox itemList;
4. 	Include detail;
5. 	StockDAO dao = new StockDAO();
6. 	private static String DETAIL_URL = "price.zul";
7.  
8. 	public void onCreate$main(){		
9. 		itemList.setSelectedIndex(0);		
10. 		Events.postEvent(new Event(Events.ON_SELECT, itemList));
11. 	}
12. 
13. 	public void onSelect$itemList(){
14. 		int id = ((Stock)itemList.getSelectedItem().getValue()).getId();
15. 		detail.setSrc(DETAIL_URL + "?id=" + id);
16. 	}	
17.  
18. 	public void onChanging$searchBox(InputEvent event) {
19. 		String key = event.getValue();
20. 		LinkedList item = new LinkedList();
21. 		List items = dao.findAll();
22.  
23. 		if (key.trim().length() != 0) {
24. 			for (Iterator iterator = items.iterator(); iterator.hasNext();) {
25. 				Stock st = (Stock) iterator.next();
26. 				if (st.getName().toLowerCase()
27. 						.indexOf(key.toLowerCase()) != -1)
28. 					item.add(st);
29. 			}
30. 			itemList.setModel(new ListModelList(item));
31. 		} else itemList.setModel(new ListModelList(items));
32. 	}
33.  
34. 	public List getStocks(){
35. 		return dao.findAll();
36. 	}
37. }

PriceController 类控制网格中的视图和 price.zul 中的制图组件:

  • 第 14 行,附加到 price.zul 请求的 id 参数被捕获。org.zkoss.zk.ui.Executions 类是一个非 Ajax 请求的包装器。
  • 第 21 行,线性图模型的值被设置。
  • 第 25 行,getPrices() 方法被声明,因此在 price.zul 中可通过网格组件中的 EL 调用。这可以正常运行,因为 PriceController 类扩展了 org.zkoss.zk.ui.util.GenericAutowireComposer,它使用数据对象连接 UI 组件。
清单 4. PriceController
1. public class PriceController extends GenericAutowireComposer {
2. 
3. 	private StockDAO dao = new StockDAO();
4. 	private CategoryModel cateModel;			
5. 	private List items;
6.  
7. 	public PriceController() {
8. 		init();
9. 	}
10. 
11.	public void init() {
12. 		//get stock id
13. 	   	int id = Integer.parseInt((String) 
14. 		Executions.getCurrent().getParameter("id"));		
15. 		Stock stock = dao.getStock(id);
16. 		items = stock.getPriceItems();		
17. 		//create category model for chart
18. 		cateModel = new SimpleCategoryModel();
19. 		for (Iterator iterator = items.iterator(); iterator.hasNext();) {
20. 			Price price = (Price) iterator.next();
21. 			cateModel.setValue(stock.getName(), price.getDate(), 
22. 		Price.getClose());
23. 		}
24. 	}
25. 	public List getPrices(){
26. 		return items;
27. 	}
28. 	public CategoryModel getCateModel() {
29. 		return cateModel;
30. 	}	
31. }

在此例中,需要实现搜索框功能的异步调用和图表中的动态数据更新是完全由 ZK 的 Client Engine 和 AU Engine 处理的。指令在服务器上执行,客户端仅仅接收 UI 组件和数据模型的更新。

服务器和客户端融合解决方案

我们来看看使用服务器和客户端融合方法的情况,用同一个样例应用程序。客户端实现最明显的候选者是搜索框,其中响应性是优先考虑的,不单是股票项名称,还有他们对应数据都需要解析和展示。在这种情况下,StockController 类不再需要,因为事件处理搜索框只在客户端实现。示例修改 index.zul 并将其重命名为 hybrid.zul。

hybrid.zul 清单如下所示,注意下列 ZK 指令:

  • ZK 的客户端名称空间必须被声明为:
    <zk xmlns:w="http://www.zkoss.org/2005/zk/client">
    (用于小部件的名称空间 w 是一个 ZK 协议。)
  • 第 2 行,apply="StockController" 属性是从 borderlayout 中取出的,因为展示股票名的搜索框和列表框将直接在客户端更新。
  • 第 8 行,textboxonChanging 事件现在被在客户端使用注解 w: 声明。
  • 第 9 行,您可以获取 listbox 并调用其 update() 方法。在 Java 中,this.$f(‘list') 表达式等于 inp.getFellow("list"),这可以得到带有 id="list"listbox 控件。
  • 第 13 行,来自类 zk.Widgetbind_ callback 方法被重写来运行 update() 方法,在 listbox 被绑定到 DOM 树之后,设置列表第一项作为选中项。
  • 第 28 行,zUtl.parseXML() 是一个实用程序方法,用于解析 XML 文档。jq() 在 ZK 中被用作 jQuery 选择器符号,以至于 jQuery 的 $() 符号仍然可用。
  • 第 38 行,使用其标签值和 ID 集合为每个股票项创建一个新的 listitem 小部件,并附加到 listbox 上作为它的子部件。
  • 第 56 至 70 行,listboxonSelect 事件是通过在服务器端为异步请求实现一个服务方法直接进行处理的。该示例为被选中的 listitem 检索 uuid 值,并将其附加到 price.zul 页面的请求中,这例展示了股票数据和线性表。
清单 5. hybrid.zul
1. <zk xmlns:w="http://www.zkoss.org/2005/zk/client">
2. <borderlayout id="main">
3. 	<west title="ZK Finance" size="250px" flex="true"
4. 	splittable="true" minsize="210" maxsize="500" collapsible="true">
5. 	<panel>
6. 		<panelchildren>
7. 			<textbox id="inp">
8. 			<attribute w:name="onChanging">
9. 				this.$f('list').update(event.data.value);
10. 	 		</attribute>
11. 			</textbox>
12. 			<listbox id="list" onSelect="" rows="10" width="300px">
13. 			<attribute w:name="bind_">
14. 			function (desktop, skipper, after) {
15. 				this.$bind_.apply(this, arguments);		
16. 				var self = this;
17. 				after.push(function () {
18. 				self.update();
19. 				self.firstChild.setSelected(true);
20. 				});
21. 				}
22. 			</attribute>
23. 			<attribute w:name="update"><![CDATA[
24. 
25. 			(function () {
26. 			var data;
27. 			function loadData(w) {
28. 	var xmlDoc = zk.xml.Utl.parseXML(jq(w).html().replace(/<!--|-->/g,'').trim()),
29. 			ids = xmlDoc.getElementsByTagName("id"),
30. 			labels = xmlDoc.getElementsByTagName("name");
31.  			data = [];
32. 			jq(ids).each(function (i) {
33. 			data.push({id: this.firstChild.nodeValue, label:
34. 			labels[i].firstChild.nodeValue});
35. 			});
36. 			}
37. 
38. 			function createItems (listbox, data) {
39. 			if (!data.length) return;
40. 			jq(data).each(function () {
41. 	listbox.appendChild(new zul.sel.Listitem({label: this.label, uuid: this.id}));
42. 			});
43. 			}
44. 			return function (txt) {
45. 				txt = txt || '';
46. 				if (!data) loadData(this.$f('data'));
47. 				this.clear();
48. 				createItems(this, jq.grep(data, function (item) {
49. 			return item.label.toLowerCase().indexOf(txt.toLowerCase()) != -1;
50. 			}));
51. 			this.stripe();
52. 			};
53. 			})()
54. 			]]></attribute>
55. 			</listbox>
56. 	<zscript>
57. 	public class MyService implements org.zkoss.zk.au.AuService {
58. 	public boolean service(org.zkoss.zk.au.AuRequest request, boolean everError) {
59. 		final String cmd = request.getCommand();
60. 		if (cmd.equals(Events.ON_SELECT)) {
61. 		String uuid = ((List)request.getData().get("items")).get(0);
62. 		System.out.println("selected:" + uuid);
63. 		content.setSrc("price.zul?id=" + uuid);
64. 		return true;
65. 		}
66. 		return false;
67. 		}
68. 		}
69. 	list.setAuService(new MyService());
70. </zscript>
71. <include id="data" src="data.xml" comment="true"/>
72. </panelchildren>
73. </panel>
74. </west>
75. <center>
76. 	<include id="content" src="price.zul?id=1"/>
77. </center>
78. </borderlayout>
79. </zk>

您可以 下载 样例代码。注意在搜索框的敏感性方面的改进。

结束语

在本文中,您学习了如何通过利用客户端和服务器端编程的优势在 ZK 中改进您的 Ajax 开发。ZK 在两端动态同步应用程序状态。您可以享受 Java EE 技术的生产效率,同时仍然可以选择实现客户端功能,您的实现方法变得依赖于您构建的企业级应用程序的需求,而不依赖于开发框架的优势或制约。


下载资源


相关主题


评论

添加或订阅评论,请先登录注册

static.content.url=http://www.ibm.com/developerworks/js/artrating/
SITE_ID=10
Zone=Web development, Java technology
ArticleID=765333
ArticleTitle=使用 jQuery、ZK 和 Java 代码的融合来增强 Ajax 开发
publish-date=10122011