[ Jeff Liu is an engineer with Potix Corporation, who are creators of the ZK framework ]
On Feb 18th, 2005 Jesse James Garrett [1] first introduced the term “AJAX” in his essay “Ajax: A New Approach to Web Applications.” Since then, Ajax has become a very popular topic in web application development communities. In the past 2 years, numerous Ajax frameworks have been built for the development community. These frameworks vary from pure JavaScript frameworks, such as Yahoo! UI (YUI), to Flash based ones such as Adobe AIR (Adobe Integrated Runtime). But, one important concept is often ignored by the media and general audience: are the frameworks server-centric or client-centric? This article attempts to clarify some misconception with real ZK and GWT examples.
In short, the main difference between server-centric and client-centric is where the application runs. In a server-centric framework, the application is hosted on the application server and all processing is done on the server. The client browser is only used for data presentation.
In contrast, applications that do all of their processing on the client side, such as GWT, use JavaScript running in the client browser.
Figure 1 - Server-Centric vs. Client-Centric
At the first glance, developers will consider that these 2 frameworks are similar: both allow the developer to use Java, and they both, support AJAX user interface components. Probe a little more deeply into the mechanism behind both frameworks and you will see that ZK and GWT work in an entirely different fashion. Generally GWT compiles Java code into JavaScript allowing the application to run in the client browser rather than on the server's. Applications built of GWT interact with the server only when data retrieval is necessary. ZK framework uses a different approach from GWT: the application runs on the server and ZK takes care of the presentation layer.
In the following section a simple Google Maps Locator application will be implemented by ZK and GWT in order to demonstrate the difference between the frameworks. Assuming that you are coding a Google Maps Locator for a client, the requirements might contain:
Let us assume you have already implemented a magical Java class which is called ocator.?It will take a string as input and return the required information. The only work left is implementing the UI and data retrieval parts. Let see code from both frameworks in real action.
Gmap.zul:
<?xml version="1.0" encoding="utf-8"?>
<zk>
<script src="http://maps.google.com/maps?file=api&v=2&key=KEY"
type="text/javascript">
</script>
<zscript>
import com.macroselfian.gps.Locator;
public void locate(){
String location = tb.getValue();
double[] pos = Locator.locate(location);
mymap.panTo(pos[0], pos[1]);
mymap.setZoom(16);
ginfo.setOpen(true);
ginfo.setLat(pos[0]);
ginfo.setLng(pos[1]);
ginfo.setContent(Locator.getInfo(location));
mymap.openInfo(ginfo);
}
</zscript>
<window>
<div align="center">
<separator spacing="50px" />
<vbox>
<label value="Macroselfian Google Map Locator"/>
<hbox width="600px">
<textbox width="550px" id="tb"/>
<button width="50px" onClick="locate();" label="Search"/>
</hbox>
<gmaps id="mymap" zoom="16" ...>
<ginfo id="ginfo"/>
</gmaps>
</vbox>
</div>
</window>
</zk>
Client Side Files:
Server Side Files:
Code Snippet:
Map.java:
?br />public class Map implements EntryPoint {
?
public void onModuleLoad() {
?br /> mapWidget = new GMap2Widget("400", "600");
gmaps = mapWidget.getGmap();
gmaps.addControl(GControl.GMapTypeControl());
gmaps.addControl(GControl.GLargeMapControl());
gmaps.setZoom(16);
VerticalPanel vPanel = new VerticalPanel();
HorizontalPanel hPanel = new HorizontalPanel();
hPanel.add(location);
hPanel.add(search);
vPanel.add(title);
vPanel.add(hPanel);
vPanel.add(mapWidget);
RootPanel.get().add(vPanel);
}
public class ClickListenerImpl implements ClickListener{
public void onClick(Widget widget) {
LocationGeneratorAsync async =
LocationGenerator.Util.getInstance();
async.locate(location.getText(), new LocationCallback());
}
}
public class LocationCallback implements AsyncCallback {
public void onFailure(Throwable error) {
response.setText("Ops..!");
}
public void onSuccess(Object resp) {
LocatorData data = (LocatorData)resp;
double lat = data.getLat();
double lng= data.getLng();
String inf = data.getInfo();
Label info = new Label(inf);
info.setStyleName("map-info");
GLatLng pos = new GLatLng(lat,lng);
gmaps.setCenter(pos);
gmaps.openInfoWindow(pos,info);
}
}
}
LocationGenerator.java:
public interface LocationGenerator extends RemoteService {
public static final String SERVICE_URI = "/locationgenerator";
public static class Util {
public static LocationGeneratorAsync getInstance() {
LocationGeneratorAsync instance =
(LocationGeneratorAsync)GWT.create(LocationGenerator.class);
ServiceDefTarget target = (ServiceDefTarget) instance;
target.setServiceEntryPoint(GWT.getModuleBaseURL()SERVICE_URI);
return instance;
}
}
public LocatorData locate(String location);
}
LocationGeneratorAsync.java:
public interface LocationGeneratorAsync {
public void locate(String location, AsyncCallback callback);
}
LocatorData.java:
public class LocatorData implements IsSerializable {
private double _lng;
private double _lat;
private String _info;
private String _title;
public LocatorData(double lat, double lng, String info,? {
_lng = lng;
_lat = lat;
_info = info;
_title = title;
}
?br />}
LocationGeneraotrImpl.java:
public class LocationGeneratorImpl extends RemoteServiceServlet
implements LocationGenerator {
public LocatorData locate(String location) {
com.macroselfian.gps.Locator locator =
new com.macroselfian.gps.Locator(location);
LocatorData data = new
LocatorData(locator.getLat(),locator.getLng(),locator.getInfo(),
locator.getTitle());
return data;
}
}
Using the ZK framework, the component state is maintained by the server. One of the advantages of server-centric applications is that data retrieval and business logic processing are straight-forward. Since the component states are maintained on the server side, retrieving data or processing business logic requests require no extra work.
In contrast, a GWT-RPC call is required when GWT applications needs data from the server. In Map.java, async.locate(? is called when onClick is trigged, then a callback object, LocationCallback is required to process the returned data. If you have any JavaScript programming experience, you will soon find out that it is very similar to calling XMLHttpRequest functions in JavaScript. Up to this point you have probably figured out that by using ZK server-centric approach, developers don have to make an RPC call and handle the returned data manually.
Here is a sample code for achieve the same functionality by another client-centric framework, DWR.
Web.xml
<?xml version="1.0" encoding="UTF-8"?>
<web-app version="2.4" xmlns="http://java.sun.com/xml/ns/j2ee"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://java.sun.com/xml/ns/j2ee
http://java.sun.com/xml/ns/j2ee/web-app_2_4.xsd">
<servlet>
<servlet-name>dwr-invoker</servlet-name>
<servlet-class>
org.directwebremoting.servlet.DwrServlet
</servlet-class>
<init-param>
<param-name>debug</param-name>
<param-value>true</param-value>
</init-param>
</servlet>
<servlet-mapping>
<servlet-name>dwr-invoker</servlet-name>
<url-pattern>/dwr/*</url-pattern>
</servlet-mapping>
</web-app>
Map.htm
<!DOCTYPE html PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN"
"http://www.w3.org/TR/html4/loose.dtd">
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=ISO-8859-1">
<script type="text/javascript" src="dwr/engine.js"></script>
<script type="text/javascript" src="dwr/util.js"></script>
<script type='text/javascript' src="dwr/interface/locator.js"> </script>
<script src="http://maps.google.com/maps?file=api&v=2" type="text/javascript"></script>
<script type="text/javascript" src="http://www.google.com/jsapi?key=ABCDEFG"></script>
</head>
<body>
<script language="javascript" >
var lng, lat, map;
function goto(location){
locator.locate(location, callBackLat);
}
function callBackLat(data){
lat = data[0];
lng = data[1];
load();
}
function load(){
if (GBrowserIsCompatible()) {
var level = 16;
map = new GMap2(document.getElementById("map_canvas"));
map.setCenter(new GLatLng(lat, lng), level);
map.addControl(new GSmallMapControl());
map.addControl(new GMapTypeControl());
var point = new GLatLng(lat,lng);
setGmarker();
setGinfo();
}
}
function setGmarker(){
var point = new GLatLng(lat,lng);
map.addOverlay(new GMarker(point));
}
function setGinfo(){
locator.getInfo(location, callBackGinfo);
}
function callBackGinfo(data){
var point = new GLatLng(lat,lng);
span_element = document.createElement("span");
span_element.setAttribute("style","font-size:11px;font-family:verdana;");
txt_node = document.createTextNode(data);
span_element.appendChild(txt_node);
map.openInfoWindow(point,span_element);
}
</script>
<input id="location" type="textbox" onchange="goto(this.value);" />
<div id="map_canvas" style="width: 500px; height: 300px"></div>
</body>
</html>
Both GWT and DWR use the client-centric approach, but DWR is quite different from GWT. Compared to GWT, DWR requires less codes. However, as a result of its flexibility, developers have to handle the browser dependency issues. In short, DWR only builds the ridge?between the client and the server.
Every developer will encounter this in his/her career, displaying master and detail problem. For example, there is list of names when the user clicks on one of names; the details behind the name need to be displayed in the details panel. ZK comes with a very neat solution for this task: annotation data-binding. For more information about ZK annotation data-binding, please refer to Y-Grid Support Drag-Drop and DataBinding [6].
To achieve this task in GWT, developers need to make an RPC call, find which cell in the grid which needs updating and so on. It is not a bad idea, but you have to do all that work by yourself.
ygrid-databinding2.zul
<?init class="org.zkforge.yuiext.zkplus.databind.AnnotateDataBinderInit" ?>
<window xmlns:y="http://www.zkoss.org/2007/yui" width="500px">
<zscript src="Person.zs"/>
<y:grid height="200px" model="@{persons}" selectedItem="@{selected}">
<y:columns>
<y:column label="First Name"/>
<y:column label="Last Name"/>
<y:column label="Full Name"/>
</y:columns>
<y:rows>
<y:row self="@{each=person}">
<y:label value="@{person.firstName}"/>
<y:label value="@{person.lastName}"/>
<y:label value="@{person.fullName}"/>
</y:row>
</y:rows>
</y:grid>
<!-- show the detail of the selected person -->
<textbox value="@{selected.firstName}"/>
<textbox value="@{selected.lastName}"/>
<label value="@{selected.fullName}"/>
<zscript>
//init each person
setupPerson(Person person, int j) {
person.setFirstName("First "+j);
person.setLastName("Last "+j);
}
//prepare the example persons List
int count = 30;
List persons = new ArrayList();
for(int j= 0; j < count; ++j) {
Person personx = new Person();
if(j==0)
selected = personx;
setupPerson(personx, j);
persons.add(personx);
}
</zscript>
</window>
Client Side Code:
Server Side Code:
Code Snippet:
FooExt.java:
package test.gwtExt.client;
import com.google.gwt.core.client.EntryPoint;
?br />public class FooExt implements EntryPoint {
?br />
public void onModuleLoad() {
_response = new Label();
_first = new TextBox();
_first.setName("first");
_first.addChangeListener(new OnTextChangeListenerImpl());
_last = new TextBox();
_last.setName("last");
_last.addChangeListener(new OnTextChangeListenerImpl());
DataSourceGeneratorAsync async =
DataSourceGenerator.Util.getInstance();
async.getData(new DataCallback());
RootPanel.get().add(_first);
RootPanel.get().add(_last);
RootPanel.get().add(_response);
}
public class DataCallback implements AsyncCallback {
public void onFailure(Throwable error) {
?
}
public void onSuccess(Object resp) {
Object[][] data = (Object[][])resp;
createGrid(data);
}
}
public void createGrid(Object[][] data){
MemoryProxy proxy = new MemoryProxy(data);
_recordDef = new RecordDef(
new FieldDef[]{
new StringFieldDef("first"),
new StringFieldDef("last"),
new StringFieldDef("full")
}
);
ArrayReader reader = new ArrayReader(_recordDef);
Store store = new Store(proxy, reader);
store.load();
ColumnModel columnModel = new ColumnModel(new ColumnConfig[]{
new ColumnConfig() {
{
setHeader("First Name");
setWidth(160);
setSortable(true);
setLocked(false);
setDataIndex("first");
}
},
new ColumnConfig() {
{
setHeader("Last Name");
setWidth(100);
setSortable(true);
setDataIndex("last");
}
},
new ColumnConfig(){
{
setHeader("Full Name");
setWidth(260);
setRenderer(new Renderer(){
public String render(?;
_grid = new Grid("grid-example1", "460px", "300px", store, ?;
_grid.addGridRowListener(new RowClickListener());
_grid.render();
}
public class RowClickListener implements GridRowListener{
public void onRowClick(Grid grid, int rowIndex, EventObject e) {
FieldDef[] fs = _recordDef.getFields();
Record record = grid.getStore().getAt(rowIndex);
_first.setText(record.getAsString(fs[0].getName()));
_last.setText(record.getAsString(fs[1].getName()));
_selectedRow = rowIndex;
}
?br />
}
public class OnTextChangeListenerImpl implements ChangeListener {
public void onChange(Widget widget) {
/*
* TODO: Modify data in server side by RPC
*/
TextBox target = (TextBox)widget;
Record r = _grid.getStore().getAt(_selectedRow);
if(target.getName().equals("first")){
r.set("first", target.getText());
}else if(target.getName().equals("last")){
r.set("last",target.getText());
}
}
}
}
DataSourceGenerator.java:
public interface DataSourceGenerator extends RemoteService {
public static final String SERVICE_URI = "/datasourcegenerator";
public static class Util {
public static DataSourceGeneratorAsync getInstance() {
DataSourceGeneratorAsync instance =
(DataSourceGeneratorAsync)GWT.create(DataSourceGenerator.class);
ServiceDefTarget target = (ServiceDefTarget) instance;
target.setServiceEntryPoint(GWT.getModuleBaseURL() + SERVICE_URI);
return instance;
}
}
public String[][] getData();
}
DataSourceGeneratorAsync.java:
public interface DataSourceGeneratorAsync {
public void getData(AsyncCallback callback);
}
DataSourceGeneratorImpl.java:
public class DataSourceGeneratorImpl extends RemoteServiceServlet
implements DataSourceGenerator {
public String[][] getData() {
org.zkoss.demo.DataSource ds = new org.zkoss.demo.DataSource();
return ds.getData();
}
}
Ygrid-livedata.zul:
<window xmlns:y="http://www.zkoss.org/2007/yui" title="Y-Grid Live Data"
width="200px" border="normal">
<zscript><![CDATA[
String[] data = new String[200];
for(int j=0; j < data.length; ++j) {
data[j] = "option "+j;
}
ListModel strset = new SimpleListModel(data);
]]></zscript>
<y:grid height="200px" model="${strset}">
<y:columns>
<y:column label="options"/>
</y:columns>
</y:grid>
</window>
More articles about ZK Mobile
More aritcles about ZK Android
To me, both of them are great tools to use. The real question is what kind of problem are you trying to solve?
Find the right tool for the right job. For heavy data access applications and projects that require a higher level of security, I prefer the server-centric approach. If the application requires fancy client side actions and less server requests, the client-centric approach could be my choice.
Links:
[1] http://en.wikipedia.org/wiki/Jesse_James_Garrett
[2] http://blogs.pathf.com/agileajax/2006/06/welcome_to_zk_w.html
[3] http://www.zkoss.org/doc/architecture.dsp
[4] http://www.zkoss.org/doc/ZK-wp-prodovw.pdf
[5] http://www.zkoss.org/doc/
[6] http://www.zkoss.org/smalltalks/yuiextz-0.5.2/
[7] http://www.zkoss.org/smalltalks/livedata/livedataforgrid.dsp
[8] http://www.zkoss.org/smalltalks/zkMobTwitter/
[9] http://www.zkoss.org/smalltalks/zkmobcal/
[10] http://www.zkoss.org/smalltalks/zkmob/zkmob.dsp
[11] http://www.zkoss.org/smalltalks/zkAndroid3/
[12] http://www.zkoss.org/smalltalks/zkAndroid2/
[13] http://www.zkoss.org/smalltalks/zkAndroid/index.dsp