A macro component implements the org.zkoss.zk.ui.ext.DynamicPropertied interface, so you can access its properties by use of the getDynamicProperty methods as follows.
<username id="ua" who="John"/> <button label="what?" onClick="alert(ua.getDynamicProperty("who"))"/>
Obviously, using DynamicPropertied is tedious. Worse of all, the macro's child components won't be changed if you use setDynamicProperty to change a property. For example, the following codes still show John as the username, not Mary.
<username id="ua" who="John"/> <zscript> ua.setDynamicProperty("who", "Mary"); </zscript>
Why? All child components of a macro component are created when the macro component is created, and they won't be changed unless you manipulate them manually[57]. Thus, the invocation to setDynamicProperty affects only the properties stored in a macro component (which you can retrieve with getDynamicProperties). The content of textbox remains intact.
Thus, it is better to provide a method, say setWho, to manipulate the macro component directly. To provide your own methods, you have to implement a class for the macro components, and then specify it in the class attribute of the component directive.
Tip: To recreate child components with the current properties, you can use the recreate method. It actually detaches all child components, and then create them again.
There are two ways to implement a class. The details are described in the following sections.
It takes two steps to provide additional methods for a macro component.
1. Implement a class by extending from the org.zkoss.zk.ui.HtmlMacroComponent class.
//Username.java package mypack; public class Username extends HtmlMacroComponent { public void setWho(String name) { setDynamicProperty("who", name); //arg.who requires it final Textbox tb = (Textbox)getFellow("mc_who"); if (tb != null) tb.setValue(name); //correct the child if available } public String getWho() { return (String)getDynamicaProperty("who"); } }
As depicted above, you have to call setDynamicProperty in setWho, because ${arg.who} is referenced in the macro page (${arg.who}), which is used when a macro component are creating its child components.
Since the setWho method might be called before a macro component creates its children, you have to check whether mc_who exists.
Since mc_who's setValue is called, both the content and the visual presentation at the client are updated automatically, when setWho is called.
2. Declare the class in the macro declaration with the class attribute.
<?component name="username" macroURI="/WEB-INF/macros/username.zul" class="mypack.Username"?>
In addition to implementing with a Java file, you can implement the Java class(es) in zscript. The advantage is that no compilation is required and you can modify its content dynamically (without re-deploying the Web application). The disadvantage is the performance downgrade and prone to typos.
It takes a few steps to implement a Java class in zscript.
1. You have to prepare a zscript file, say /zs/username.zs, for the class to implement. Notice that you can put any number of classes and functions in the same zscript file.
//username.zs package mypack; public class Username extends HtmlMacroComponent { public void setWho(String name) { setDynamicProperty("who", name); Textbox tb = getFellow("mc_who"); if (tb != null) tb.setValue(name); } public String getWho() { return getDynamicProperty("who"); } }
2. Use the init directive to load the zscript file, and then declare the component
<?init zscript="/zs/username.zs"?> <?component name="username" macroURI="/WEB-INF/macros/username.zul" class="mypack.Username"?>
The implementation class (mypack.Username in the previous example) is resolved as late as the macro component is really used, so it is also OK to use the zscript element to evaluate the zscript file.
<?component name="username" macroURI="/WEB-INF/macros/username.zul" class="mypack.Username"?> <zk> <zscript src="/zs/username.zs"/> <username/> </zk>
Though subjective, the init directive is more readable.
Like any other component, you can use the use attribute to override the class used to implement a macro component for any particular instance.
<?component name="username" macroURI="/WEB-INF/macros/username.zul" class="mypack.Username?> <username use="another.MyAnotherUsername/>
Of course, you have to provide the implementation of another.MyAnohterUsername in the above example. Once again the class can be implemented with separate Java file, or by use of zscript.
To create a macro component manually, you have to invoke the afterCompose method after all the initialization as follows.
HtmlMacroComponent ua = (HtmlMacroComponent) page.getComponentDefinition("username", false).newInstance(page); ua.setParent(wnd); ua.applyProperties(); //apply properties defined in the component definition ua.setDynamicProperty("who", "Joe"); ua.afterCompose(); //then the ZUML page is loaded and child components are created
Note: The getComponentDefinition method is used to look up the component definitions defined in a page.
If you implement a class, say Username, for the macro, then you can do as follow.
Username ua = new Username(); ua.setWho("Joe"); ua.setParent(wnd); ua.afterCompose();
[57] On the other hand, the child components included by the include component is created in the rendering phase. In addition, all child components are removed and created each time the include component is invalidated.