ZK/How-Tos/Frequently Asked Questions

From Wikibooks, open books for an open world
< ZK‎ | How-Tos
Jump to navigation Jump to search

How to access resources in another application[edit | edit source]

1. Let us assume you have another application called app2. Then, you can access the resource by use of "~app2/your/resource/path".

For example,

<include src="~app2/main/foo.zul"/>

Notice that the Web container might prevent you from accessing other Web applications. In this case, org.zkoss.lang.SystemException(("Context not found or not visible to....") will be thrown. To enable it you have to configure the Web container properly.

2. In context.xml, specify crossContext="true" to the Context element:

<Context crossContext="true">

How to add a hyperlink?[edit | edit source]

Use toolbarbutton or button component and specify the href attribute. e.g. The following code would generate ZK - Simple and Rich hyperlink.

<toolbarbutton label="ZK - Simple and Rich" href="http://tw.yahoo.com"/>
<button label="Rich Internet Application" href="http://www.zkoss.org/zkdemo/userguide"/>

You can also mix xhtml and xul together so you can create normal hyperlinks e.g.

<window title="mix HTML demo" xmlns:h="http://www.w3.org/1999/xhtml">
<button label="zul hyperlink button" href="http://www.google.com/"/>
<h:a href="http://www.google.com">xhtml hyperlink</h:a>
</window>

see the demo and the developer guide for more examples.

How to convert AWT Image to ZK Image[edit | edit source]

import java.io.ByteArrayOutputStream;
import java.awt.image.RenderedImage;
import javax.imageio.ImageIO;
import org.zkoss.image.Image;
import org.zkoss.image.AImage;

Image encode(RenderedImage image) {
  ByteArrayOutputStream os = new ByteArrayOutputStream();
  ImageIO.write(image, "png", os);
  return new AImage("my-image.png", os.toByteArray());
}

Since 3.0.7 and 3.5.0, there is a utility class called org.zkoss.image.Images to simply the encoding job.

How to include the same page twice?[edit | edit source]

With the include component, you could include any page multiple times as follows.

<include src="/mypage.zul"/>
<include src="/mypage.zul"/>

However, if you want to access the component inside of them, you have to assign a unique identifier of the page being included. Here is what you can do.

<include src="/mypage.zul?pageId=first"/>
<include src="/mypage.zul?pageId=second"/>

In additions, in the page being include, i.e., mypage.zul in this example, you have to write

<?page id="${param.pageId}"?>

Then, you could access their component.

Path.getComponent('//first/asdf/'); 
Path.getComponent('//second/asdf/'); 

Notice that components are created as late as the Rendering phase, so you could access them only in the event listener for the following events:

<window>
<zscript><![CDATA[
        /**
         * in a real application we would use something like 
         *         List iterateOverMe = sessionScope.get("listToRender");
         */
        String[][] iterateOverMe = {
                {"99", "Fred Flintstone"}
                ,{"8", "Wilma Flintstone"}
                ,{"65", "Barney Rubble"}
                ,{"32", "Betty Rubble"}
        };
]]></zscript>
<tabbox mold="accordion">
<tabs>
 <!-- more realisticly my iterateOverMe would be a List of 
 pojos so that I can write ${each.label} -->
 <tab forEach="${iterateOverMe}" label="${each[1]}"/>
</tabs>
<tabpanels>
 <!-- more realisticly my iterateOverMe would be a List of 
 pojos so that I can write ${each.id} -->
 <tabpanel forEach="${iterateOverMe}" >
 <include src="/render-item.zul?pageId=${each[0]}"/>
 </tabpanel>
</tabpanels>
</tabbox>
</window>

In that page we pull in search-item.zul once for each object the search results list and we give the included page a pageId that is the identifier of the item that is to be rendered i.e. 99,8,65,32. Within render-item.zul:

<?page id="${param.pageId}"?>
<zscript>
 // Here we have to use param.pageId to locate the object that we will render
 Object itemToRender = ... // use param.pageId as the identifier to locate specific object to render at this time
</zscript>
<vbox>
 <button label="${itemToRender.label}"/>
</vbox>

In this file is included four separate times and param.pageId differs each time i.e. 99,8,65,32. Each time the page is called we use the param.pageId to find the business item to be rended.

How to initialize a component if extending it to a custom class?[edit | edit source]

Say, we extend Tabbox with MyTabbox as follows.

public class MyTabbox extends Tabbox {
...

Then, you could initialize it using two methods: the constructor and the onCreate event.

public class MyTabbox extends Tabbox {
  public MyTabbox() {
    ...//some init
  }
  public void onCreate() {
    ...//other init
  }
}

The constructor is called before members are assigned with initial values, while onCreate is processed after the whole page is loaded.

However, the onCreate event will not be sent if you create the component programmatically, instead of declaring in a ZUML page.

How to redirect to another page when processing an event?[edit | edit source]

Use the sendRedirect method of the Execution interface to ask the browser to redirect to another page. All updates to the current page/desktop are dropped.

void onClick() {
  if (some_condition)
    Executions.sendRedirect("another_page");
}

How to refresh inner pages only?[edit | edit source]

First, use include component and specifies the src attribute to include whatever page you want (ZK, JSP, JSF or whatever) inside a ZK page. Second, you can dynamically change it by changing the src attribute. e.g. The following code would change the inner page from hello.zul to byebye.zul when an end user press the Bye! button.

<include id="inner" src="hello.zul"/>
<button label="Bye!" onClick="inner.src = &quot;byebye.zul&quot;"/>

If you simply want to reload the same page(and not to change to another page), you have to set the src attribute to null first; then set the src attribute back to what it was. Because ZK optimizes operations, set same value to the same attribute would be deemed doing nothing. e.g. The following code would refresh the hello.zul page when an end user press the Reload button.

<include id="inner" src="hello.zul"/>
<button id="reload" label="Reload" onClick="String tmp=inner.src; inner.src=null; inner.src=tmp;"/>

If you want to programmatically call the reload button from the event handler of another button after doing some other work you can dispatch the current event to the button just by referring to it by id with

Events.sendEvent(reload,event)

as follows:

<button label="Do Lots Of Stuff Then Pass The Click To The Reload Button">
<attribute name="onClick">
{
 doLotsOfStuff();
 Events.sendEvent(reload, event);
} 
</attribute>
</button>

How to lazy load tabs to speed up the loading of a very large page?[edit | edit source]

Cread the following page and two other pages "tab1.zul" and "tab2.zul" in the same folder. In tab1.zul and tab2.zul put a <window> containing any zul that you would like (hint go to the demo page and click the "Try Me" buttons and cut-n-paste some large source).

<?page id="main-page"?>
<window id="main-window">
<tabbox width="400px">
	<attribute name="onSelect">{
		//alert("selected index:"+self.selectedIndex);
		if(self.selectedIndex > 0 ) {
			Include inc = (Include)Path.getComponent("//main-page/main-window/tab"+self.selectedIndex);
			inc.setSrc("tab"+self.selectedIndex+".zul");
		}
	}</attribute>
	<tabs>
		<tab label="Tab 0"/>
		<tab label="Tab 1"/>
		<tab label="Tab 2"/>
	</tabs>
	<tabpanels>
		<tabpanel>This is panel 0 and it does not have an include</tabpanel>
		<tabpanel>
			<include id="tab1" src=""/> 
		</tabpanel>
		<tabpanel>
			<include id="tab2" src=""/> 
		</tabpanel>
	</tabpanels>
</tabbox>
</window>

Only when you click on "Tab 1" and "Tab 2" are is the content of tab1.zul and tab2.zul loaded and their components created. To prove this to yourself comment out the line starting

inc.setSrc("tab"+self.selectedIndex+".zul");

then refresh the page and click on the tabs and you wont see any xul from tab1.zul or tab2.zul as these files will not be loaded after commenting out the event handler code that loads them.

How to specify line-break in Label's attribute[edit | edit source]

XML parses considers line breaks as regular whitespaces, so it won't work by using

<label value="line 1
line 2"/>

Instead, use the attribute element as follows:

<label multiline="true">
  <attribute name="value">line 1
line 2</attribute>
</label>

How to use "each" variable of "forEach" attribute in onXxx event handler?[edit | edit source]

Example1 (incorrect)[edit | edit source]

 <window title="Countries" border="normal" width="100%">
     <zscript><![CDATA[
         // Here we have an array of countries
         // We will generate a bunch of buttons per this array
         // When the button is click, an alert window will show the country name.
         
         String[] countries = {"China", "France", "Germany", "United Kindom", "United States"};
     ]]></zscript>
     
     <hbox>
          <!-- WARNING THIS IS INCORRECT USE "Example 3" -->
         <button label="${each}" forEach="${countries}" onClick="alert(${each})"/>
     </hbox>
 </window>

The Example1 is not correct. The EL expression cannot be used in onXxx() event handler. It will throws an exception "Attempt to access property on undefined variable or class name" when button is clicked because the alert(${each}) in onClick is directly interpreted as java codes.

Example2 (incorrect)[edit | edit source]

 <window title="Countries" border="normal" width="100%">
     <zscript><![CDATA[
         // Here we have an array of countries
         // We will generate a bunch of buttons per this array
         // When the button is click, an alert window will show the country name.
         
         String[] countries = {"China", "France", "Germany", "United Kindom", "United States"};
     ]]></zscript>
     
     <hbox>
          <!-- WARNING THIS IS INCORRECT USE "Example 3" -->
         <button label="${each}" forEach="${countries}" onClick="alert(each)"/>
     </hbox>
 </window>

Example2 rewrites alert(${each}) to alert(each). Because each is an implicit object, it should be ok to write it directly in onClick just like other implicit object. However, click the button would still throw an exception "Undefined argument: each". The reason is simple. While each is an implicit object, it is a temporary implicit object. It exists only when the zuml page is being evaluated. When the button is clicked, that original each has gone already.

Then how do we reference each variable of forEach attribute in onXxx() event handler? The key is to store the temporary each variable when the zuml page is evaluated and use that stored object when onXxx event is fired.

Example3 (correct)[edit | edit source]

<window title="Countries" border="normal" width="100%">
    <zscript><![CDATA[
        // Here we have an array of countries
        // We will generate a bunch of buttons per this array
        // When the button is click, an alert window will show the country name.
        
        String[] countries = {"China", "France", "Germany", "United Kindom", "United States"};
    ]]></zscript>
    
    <hbox>
        <button label="${each}" forEach="${countries}"
            onClick="alert(componentScope.get(&quot;country&quot;))">
            <custom-attributes country="${each}"/>
        </button>
    </hbox>
</window>

Example3 uses the button's custom-attributes map (componentScope) to store the temporary each object when the zuml page is evaluated and use that stored object later when the button is clicked.

How to make a Grid's row and column drag-n-drop?[edit | edit source]

By Bakoma


A Grid consists of Columns and Rows where each Columns has columns as the Columns' children. Similarly, Rows has rows as its children.

Here is a simple Grid with head columns and three rows:

        Name  Age Grade
        Mike  29    C 
        Todd  21    B
        Tony  37    A

The following codes will put the above into a Grid with the rows and columns draggable and droppable. Here are the steps:

  • Create Grid
  • Add Columns, and specify each column is draggable and droppable provided that they are of the same type of columns.
<columns>  
    <column label="Name" draggable="col" droppable="col" onDrop="move(event.dragged)"/>gunawan
    <column label="Age" draggable="col" droppable="col" onDrop="move(event.dragged)"/> 
    <column label="Grade" draggable="col" droppable="col" onDrop="move(event.dragged)"/> 
</columns>
  • Add Rows, and specify each row is draggable and droppable.
<rows>  
    <row draggable="row" droppable="row" onDrop="move(event.dragged)">  
        <label value="Mike" />  
        <label value="29" /> 
        <label value="C" /> 
    </row>  
    <row draggable="row" droppable="row" onDrop="move(event.dragged)">  
        <label value="Todd" />  
        <label value="21" /> 
        <label value="B" />
    </row>  
    <row draggable="row" droppable="row" onDrop="move(event.dragged)">  
        <label value="Tony" />  
        <label value="31" /> 
        <label value="A" /> 
    </row> 
</rows>
  • Implement the event handling function. Make sure to move the corresponding cells as well when moving the columns.
void move(Component dragged) { 
    if(dragged.getClass().getName().endsWith("Column")) { 
        int maxRows=dragged.getGrid().getRows().getChildren().size(); 
        int i= dragged.getParent().getChildren().indexOf(dragged); 
        int j= self.getParent().getChildren().indexOf(self); 
 
        //move celles for each row 
        for(int k=0; k < maxRows; k++) 
            self.getGrid().getCell(k,j).parent.insertBefore(self.getGrid()
              .getCell(k,i),self.getGrid().getCell(k,j)); 
   } 
 
    self.parent.insertBefore(dragged, self);  
} 
  • Put them together to get the complete codes:
<zk> 
<grid>  
  <columns>  
    <column label="Name" draggable="col" droppable="col" onDrop="move(event.dragged)"/> 
    <column label="Age" draggable="col" droppable="col" onDrop="move(event.dragged)"/> 
    <column label="Grade" draggable="col" droppable="col" onDrop="move(event.dragged)"/> 
  </columns> 
  <rows>  
    <row draggable="row" droppable="row" onDrop="move(event.dragged)">  
        <label value="Mike" />  
        <label value="29" /> 
        <label value="C" /> 
    </row>  
    <row draggable="row" droppable="row" onDrop="move(event.dragged)">  
        <label value="Todd" />  
        <label value="21" /> 
        <label value="B" />
    </row>  
    <row draggable="row" droppable="row" onDrop="move(event.dragged)">  
        <label value="Tony" />  
        <label value="31" /> 
        <label value="A" /> 
    </row> 
  </rows> 
</grid>  
<zscript><![CDATA[ 
  void move(Component dragged) { 
    if(dragged.getClass().getName().endsWith("Column")) { 
        int maxRows=dragged.getGrid().getRows().getChildren().size(); 
        int i= dragged.getParent().getChildren().indexOf(dragged); 
        int j= self.getParent().getChildren().indexOf(self); 
 
        //move celles for each row 
        for(int k=0; k < maxRows; k++) 
           self.getGrid().getCell(k,j).parent.insertBefore(self.getGrid()
              .getCell(k,i),self.getGrid().getCell(k,j)); 
   } 
 
    self.parent.insertBefore(dragged, self);  
  }  
]]></zscript> 
</zk>

How to generate ©?[edit | edit source]

<html>Copyright &amp;copy; Super Co.</html>

Explanation: &amp; is a way to pass & to the content property of the html component. And, the html component directly outputs the content property to the browser.

On the other hand, it won't work if the label component, since label will encode its content.

<label>Copyright &amp;copy; Super Co.</label>

How to set the forward property by program[edit | edit source]

org.zkoss.zk.ui.sys.ComponentsCtrl.applyForward(comp, "onClick");

How to enable logging in ZK[edit | edit source]

1. First, Set up the log configuration in your application's zk.xml file. I just used an empty string.

<zk>
    <log>
        <log-base></log-base>
    </log>
</zk>

2. Create an i3-log.conf configuration file and place it in the Tomcat's /conf folder. It should include the following lines.

org.zkoss.testlog=DEBUG
org.zkoss=ERROR

3. Remember to modify the logging.properties file in your Tomcat's /conf folder. The default ConsoleHandler's level is INFO, so you won't see any DEBUG message otherwise. java.util.logging.ConsoleHandler.level = INFO to java.util.logging.ConsoleHandler.level = FINEST 4. If you want to output specific messages from your program

private static final Log log = Log.lookup(org.zkoss.testlog.Foo.class);
if(log.debugable()){
    log.debug("a log for debug message");
}
if(log.infoable()){
    log.info("a log for info message");
}

From http://www.javaworld.com.tw/roller/atticcat/entry/2007_10_10_Using_log_in_zk