Ch06
Implement ToDoZK
Always start out with a larger pot than what you think you need. -- Julia Child
Now, we will discuss how to solve some issues doesn't happen in pure ZK environment but occurred in JSP + ZK architecture.
Mummy Pythian: To prove I am not a goof-off guy, I already finished basic value object and data access object. In
Sidebar
area, because the relation of milestone and workspace is similar to tree, I intend to implement withTree
component. So the ZKDefaultTreeModel
ofSidebar
is done too.
Good job, but... sorry to tell you that useTree
inSidebar
is not a good idea...
Shake Spear: It look fine, eh?
Mummy Pythian: Exactly! It's a pity we don't use the tree model, I work so hard!
We will talk about it [http:// later], now we continue using it.
Mummy Pythian: That's ok. Conqueror and I rewrite the layout in [Ch.4] toindex.jsp
. Thesidebar.zul
andcardview.zul
inTaskViewer
is done too. But I don't know how to controlContent
area to showcardview.zul
?
Great! These will help us with follow discuss.
Control Content
Area
First we answer the question: "Why Content
area is empty? How can I control it?" According to the requirement, the Content
area will show static content and RIA (ZK block) belong to which item the end use click.
Hence, we need a mechanism to change Content
area's content. By intuition, we can write a ZUL file like this:
<!-- content.zul -->
<include src="about.jsp" />
And the Content
area in index.jsp
will be:
<jsp:include page="content.zul" />
Thus, control the src
we can specitfy the content. This solution is feasible and very powerful if you want more detailed control, but in [ToDoZK] it is a little long-winded.
The other solution is use AJAX to load specify URL and use it's content to replace HTML element. It look so complicated, but we can use jQuery function of ZK client engine easily:
jq('#ID').load(URL);
Then we call the Java method Clients.evalJavaScript()
, let client side execute jQuery method. Hence, we can change the content when end user click the item in Sidebar
. Wrap them in a utility method:
public static void changeContent(String id, String url) {
Clients.evalJavaScript("jq('#" + id + "').load('"+url+"');");
}
Generally speaking, there is a hypothesis to use this utility: browser must load ZK client engine before -- that says the browser must be showing or have shown the ZK block.
Shake Spear: When I tested the result, I found there are some
<meta>
tags in the DOM structure ofContent
area. Are they regular or correct?Conqueror Clipper: Yeah, I found them too, but I ignore because they don't influence the layout. Another strange thing is... why the
Sidebar
area won't occur these tags?
Let ZUL don't generate complete HTML structure
Basically ZK will generate complete HTML structure when process a ZUL request, that says ZK will generate tags like <html>
, <head>
, <body>
and so on. When we load a ZUL file with AJAX method, it will produce unnecessary, inaccurate DOM structure.
To prevent this situation, one of the solutions is add zk.redrawCtrl=page
to query string of URL, like this:
changeContent("content", "cardview.zul?zk.redrawCtrl=page");
the other way is add <custom-attribute>
in ZUL:
<!-- in cardview.zul -->
<zk>
<custom-attributes scope="request" org.zkoss.zk.ui.page.redrawCtrl="page"/>
<!-- skip -->
</zk>
With this setting, ZK will not generate complete HTML structure. For general purpose, we recommend the second solution.
If ZUL is included by <jsp:include>
or ZK Include
component, ZK will determine and skip unnecessary HTML structure without set redrawCtrl
. That's why there are no <head>
and <meta>
tags in Sidebar
area.
Mummy Pythian: I am glad at these solutions, they make my job easier! So I implement
UserStatus
withuserStatus.zul
, and the other task viewertreeview.zul
is also done. But how canContent
area do corresponding action when end user want change view inUserStatus
?All you need is global command.
Communication between ZK block
We design there is an avatar image in userStatus.zul
, when end user click avatar image will show Radiogroup
and can change the task view style. After the value of Radiogroup
changed, system must trigger sidebar.zul
to do corresponding action. Because they are two different ZUL files, we must use global command to communicate. First, set the onCheck
attribute of Radiogroup
in userStatus.zul
:
<radiogroup id="view" onCheck='@global-command("viewChange", type=self.selectedItem.value)' />
Second, add a method named viewChange
in view model of sidebar.zul
:
@GlobalCommand
public void viewChange(@BindingParam("type")int value){
//call changeContent()
}
Check the item of Radiogroup
will trigger onCheck
, thus it will occure a global command. This global command doesn't specify receive target, every active view model which has viewChange
method with @GlobalCommand
will receive this event. The value
argument with @BindingParam
will get the type
argument of global command. If you want know more detail, please refer to ZK document.
By the way, although we don't need it in [ToDoZK]. If TaskViewer
area need to receive global command, we must use the session scope global command. Every component declared view model must set binder
attribute like this:
<div apply="org.zkoss.bind.BindComposer" viewModel='@id("vm") @init("org.zkoss.todoZK.viewmodel.UserStatusVM")'
binder='@init(queueScope="session")'>
The reason is cardview.zul
and userStatus.zul
we include by jQuery are not in the same Desktop
. The default queueScope
is Desktop
, so global command published by userStatus.zul
can't be receive in cardview.zul
. We must set the Session
scope, thus even in different Desktop
still can receive the event. As the same reason, all of userStatus.zul
and cardview.zul
must set binder
attribute.
To System Architect
If your project doesn't use MVVM, it's a little trouble to handle this issue, but not a problem. In nutshell, the fundamental of global command is EventQueue, MVVM wrap details. You must create EventQueue, publish/subscribe event on your hand. Hence you can do any MVVM feature.
For more implement detail of EventQueue, please refer to ZK document.
Shake Spear: Cool! There seems to be no problems with JSP + ZK architecture.
Mummy Pythian: Wait a minute. How about the ZK bookmark mechanism? Cat it still work fine?
You are a lazy guy, Mummy Pythian. Why do you test it first by yourself......
Bookmark mechanism
ZK Bookmark mechanism can control browser's history and listen history changes. Because it access the location object of browser, every ZUL (even is included by index.jsp
) can operate bookmark normally.
In [ToDoZK], before change the content of Content
area, we set corresponding bookmark:
Executions.getCurrent().getDesktop().setBookmark(BOOKMARK_STRING);
If Content
will show task viewer, we use query string style to set BOOKMARK_STRING
, for example: ws=WORKSPACE_ID
.
We gather the code of control Content
area in sidebar.zul
, so we put the code which process bookmark change in sidebar.zul
too. Add onBookmarkChange
attribute to component of sidebar.zul
:
<div apply="org.zkoss.bind.BindComposer" viewModel='@id("vm") @init("org.zkoss.todoZK.viewmodel.SidebarVM")'
onBookmarkChange='@command("bookmarkChange", evnt=event)'>
In view model, bookmarkChange
method must identify current bookmark then change the Content
area. The bookmarkChange
will look like:
@Command
public void bookmarkChange(@BindingParam("evnt") BookmarkEvent evnt) {
String bookmark = evnt.getBookmark();
if (bookmark.equals("document")) {
//call changeContent()
} else if (bookmark.startsWith("ws=")) {
String workspaceId = bookmark.substring(3);
//call changeContent()
}
//and more......
}
In a word, ZK bookmark mechanism is the same as usual in JSP + ZK architecture.
To System Architect
In HTML5 specification, there is an alternative of Bookmark mechanism:
pushState()
of History API. It is more powerful and flexible than Bookmark, but some browsers have not support this feature yet (Include IE with no doubt). ZK also has a addon ZKPushState to integratepushState()
. For more instruction, please refer to ZK blog. The logic is similar to Bookmark, we must replace the code of parse bookmark value by
- Parse URL at initiation.
- Fetch value from
PopupStateEvent.getState()
when browser's history changed.