Ch06

From Documentation

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 with Tree component. So the ZK DefaultTreeModel of Sidebar is done too.
Good job, but... sorry to tell you that use Tree in Sidebar 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] to index.jsp. The sidebar.zul and cardview.zul in TaskViewer is done too. But I don't know how to control Content area to show cardview.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 of Content 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 with userStatus.zul, and the other task viewer treeview.zul is also done. But how can Content area do corresponding action when end user want change view in UserStatus?

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 integrate pushState(). 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.