Difference between revisions of "Macro Components"

From Documentation
(Created page with '{{ZKDevelopersGuidePageHeader}} == Macro Components == There are two ways to implement a component. One is to implement a class deriving from the <tt>org.zkoss.zk.ui.AbstractCom…')
 
m (correct highlight (via JWB))
 
(4 intermediate revisions by 3 users not shown)
Line 1: Line 1:
 +
#REDIRECT [[ZK_Developer's_Reference/UI_Composing/Macro_Component]]
 +
 
{{ZKDevelopersGuidePageHeader}}
 
{{ZKDevelopersGuidePageHeader}}
  
 
== Macro Components ==
 
== Macro Components ==
There are two ways to implement a component. One is to implement a class deriving from the <tt>org.zkoss.zk.ui.AbstractComponent</tt> class. The other is to implement it by use of other components.
+
There are two ways to implement a component. One is to implement a class deriving from the <code>org.zkoss.zk.ui.AbstractComponent</code> class. The other is to implement it by use of other components.
  
 
The former one is more flexible. It requires deeper understanding of ZK, so it is usually done by component developers. It is discussed in the '''Component Development Guide'''.
 
The former one is more flexible. It requires deeper understanding of ZK, so it is usually done by component developers. It is discussed in the '''Component Development Guide'''.
Line 22: Line 24:
 
All you need to do is to prepare a ZUML page that describes what the component consists of. In other words, the page is a template of the macro.
 
All you need to do is to prepare a ZUML page that describes what the component consists of. In other words, the page is a template of the macro.
  
For example, assume we want to pack a label and a text box as a macro component. Then we could create page, say <tt>/WEB-INF/macros/username.zul</tt>, as follows.
+
For example, assume we want to pack a label and a text box as a macro component. Then we could create page, say <code>/WEB-INF/macros/username.zul</code>, as follows.
  
 
<source lang="xml" >
 
<source lang="xml" >
Line 41: Line 43:
 
</source>
 
</source>
  
As shown, you have to declare the name (the <tt>name</tt> attribute) and the URI of the page (the <tt>macroURI</tt> attribute).
+
As shown, you have to declare the name (the <code>name</code> attribute) and the URI of the page (the <code>macroURI</code> attribute).
  
 
==== Other Properties ====
 
==== Other Properties ====
In additions to the <tt>name</tt>, <tt>macroURI</tt> and <tt>class</tt><ref>The class attribute will be discussed later.</ref> attributes, you can specify a list of initial properties that will be used to initialize a component when it is instantiated.
+
In additions to the <code>name</code>, <code>macroURI</code> and <code>class</code><ref>The class attribute will be discussed later.</ref> attributes, you can specify a list of initial properties that will be used to initialize a component when it is instantiated.
  
 
<source lang="xml" >
 
<source lang="xml" >
Line 84: Line 86:
 
</source>
 
</source>
  
All these properties specified are stored in a map that is then passed to the template via a variable called <tt>arg</tt>. Then, in the template, you could access these properties as follows.
+
All these properties specified are stored in a map that is then passed to the template via a variable called <code>arg</code>. Then, in the template, you could access these properties as follows.
  
 
<source lang="xml" >
 
<source lang="xml" >
Line 92: Line 94:
 
</source>
 
</source>
  
'''Note''': <tt>arg</tt> is available only when rendering the macro page. To access in the event listener, you have to use <tt>getDynamicProperty</tt> instead. Refer to the '''Provide Additional Methods''' section for more details.
+
'''Note''': <code>arg</code> is available only when rendering the macro page. To access in the event listener, you have to use <code>getDynamicProperty</code> instead. Refer to the '''Provide Additional Methods''' section for more details.
  
 
==== arg.includer ====
 
==== arg.includer ====
In additions to the specified properties (a.k.a., attributes), a property called <tt>arg.includer</tt> is always passed to represent the parent of the components defined in a macro template.
+
In additions to the specified properties (a.k.a., attributes), a property called <code>arg.includer</code> is always passed to represent the parent of the components defined in a macro template.
  
If a regular macro is created, <tt>arg.includer</tt> is the macro component itself. If an inline macro is created, <tt>arg.includer</tt> is the parent component, if any. Refer to the '''Inline Macros''' section for more information.
+
If a regular macro is created, <code>arg.includer</code> is the macro component itself. If an inline macro is created, <code>arg.includer</code> is the parent component, if any. Refer to the '''Inline Macros''' section for more information.
  
In the above example, <tt>arg.includer</tt> represents the regular macro component, <tt><username who="John"/></tt>, and is the parent of <tt><hbox></tt> (defined in <tt>username.zul</tt>).
+
In the above example, <code>arg.includer</code> represents the regular macro component, <code><username who="John"/></code>, and is the parent of <code><hbox></code> (defined in <code>username.zul</code>).
  
 
== Inline Macros ==
 
== Inline Macros ==
There are two kinds of macro components: inline<ref>Inline macro components are added since ZK 2.3.</ref> and regular. By default, regular macros are assumed. To specify inline macros, you have to specify <tt>inline="true"</tt> in the component directive.
+
There are two kinds of macro components: inline<ref>Inline macro components are added since ZK 2.3.</ref> and regular. By default, regular macros are assumed. To specify inline macros, you have to specify <code>inline="true"</code> in the component directive.
  
 
An inline macro behaves like ''inline-expansion''. ZK doesn't create a macro component if an inline macro is encountered. Rather, it inline-expands the components defined in the macro URI. In other words, it works as if you type the content of the inline macro directly to the target page.
 
An inline macro behaves like ''inline-expansion''. ZK doesn't create a macro component if an inline macro is encountered. Rather, it inline-expands the components defined in the macro URI. In other words, it works as if you type the content of the inline macro directly to the target page.
 
+
<references/>
  
 
use.zul: (target page)
 
use.zul: (target page)
Line 140: Line 142:
 
</source>
 
</source>
  
All properties, including <tt>id</tt>, are passed to the inline macro.
+
All properties, including <code>id</code>, are passed to the inline macro.
  
 
On the other hand, ZK will create a real component (called a macro component) to represent the regular macro. That is, the macro component is created as the parent of the components that are defined in the macro.
 
On the other hand, ZK will create a real component (called a macro component) to represent the regular macro. That is, the macro component is created as the parent of the components that are defined in the macro.
  
Inline macros are easier to integrate into sophisticated pages. For example, you ''cannot'' use ''regular'' components in the previous example since <tt>rows</tt> accepts only <tt>row</tt>, not macro components. It is easier to access to all components defined in a macro since they are in the same ID space. It also means the developers must be aware of the implementation to avoid name conflicts.
+
Inline macros are easier to integrate into sophisticated pages. For example, you ''cannot'' use ''regular'' components in the previous example since <code>rows</code> accepts only <code>row</code>, not macro components. It is easier to access to all components defined in a macro since they are in the same ID space. It also means the developers must be aware of the implementation to avoid name conflicts.
  
 
Regular macros allow the component developers to provide additional API and hide the implementation from the component users. Each regular macro component is an ID space owner, so there is no name conflicts. The users of regular macros usually assume nothing about the implementation. Rather, they access via the well-defined API.
 
Regular macros allow the component developers to provide additional API and hide the implementation from the component users. Each regular macro component is an ID space owner, so there is no name conflicts. The users of regular macros usually assume nothing about the implementation. Rather, they access via the well-defined API.
  
 
=== An Example ===
 
=== An Example ===
<tt>inline.zul</tt>: (the macro definition)
+
<code>inline.zul</code>: (the macro definition)
  
 
<source lang="xml" >
 
<source lang="xml" >
Line 158: Line 160:
 
</source>
 
</source>
  
<tt>useinline.zul</tt>: (the target page)
+
<code>useinline.zul</code>: (the target page)
  
 
<source lang="xml" >
 
<source lang="xml" >
Line 185: Line 187:
  
 
=== Macro Components and The ID Space ===
 
=== Macro Components and The ID Space ===
Like <tt>window</tt>, a macro component is an ID space owner. In other words, it is free to use whatever identifiers to identify components inside the page implementing a macro component (a.k.a., child components of the macro component). They won't conflict with components defined in the same page with the macro component.
+
Like <code>window</code>, a macro component is an ID space owner. In other words, it is free to use whatever identifiers to identify components inside the page implementing a macro component (a.k.a., child components of the macro component). They won't conflict with components defined in the same page with the macro component.
  
 
For example, assume we have a macro defined as follows.
 
For example, assume we have a macro defined as follows.
Line 214: Line 216:
 
Why? Like any ID space owner, the macro component itself is in the same ID space with its child components. There are two alternative solutions:
 
Why? Like any ID space owner, the macro component itself is in the same ID space with its child components. There are two alternative solutions:
  
1. Use a special prefix for the identifiers of child components of a macro component. For example, <tt>"mc_who"</tt> instead of <tt>"who"</tt>.  
+
1. Use a special prefix for the identifiers of child components of a macro component. For example, <code>"mc_who"</code> instead of <code>"who"</code>.  
  
 
<source lang="xml" >
 
<source lang="xml" >
Line 222: Line 224:
 
</source>
 
</source>
  
2. Use the <tt>window</tt> component to create an additional ID space.  
+
2. Use the <code>window</code> component to create an additional ID space.  
  
 
<source lang="xml" >
 
<source lang="xml" >
Line 235: Line 237:
  
 
==== Access Child Components From the Outside ====
 
==== Access Child Components From the Outside ====
Like other ID space owner, you can access its child component by use of two <tt>getFellow</tt> method invocations or org.zkoss.zk.ui.Path.
+
Like other ID space owner, you can access its child component by use of two <code>getFellow</code> method invocations or org.zkoss.zk.ui.Path.
  
For example, assume you have a macro component whose ID is called <tt>"username"</tt>, and then you can access the <tt>textbox</tt> as follows.
+
For example, assume you have a macro component whose ID is called <code>"username"</code>, and then you can access the <code>textbox</code> as follows.
  
 
<source lang="java" >
 
<source lang="java" >
Line 247: Line 249:
 
Macro components work as inline-expansion. Thus, like other components, a child component (of a macro component) can access any variable defined in the parent's ID space.
 
Macro components work as inline-expansion. Thus, like other components, a child component (of a macro component) can access any variable defined in the parent's ID space.
  
For example, <tt>username</tt>'s child component can access <tt>v</tt> directly.
+
For example, <code>username</code>'s child component can access <code>v</code> directly.
  
 
<source lang="xml" >
 
<source lang="xml" >
Line 267: Line 269:
  
 
=== Provide Additional Methods ===
 
=== Provide Additional Methods ===
A macro component implements the <tt>org.zkoss.zk.ui.ext.DynamicPropertied</tt> interface, so you can access its properties by use of the <tt>getDynamicProperty</tt> methods as follows.
+
A macro component implements the <code>org.zkoss.zk.ui.ext.DynamicPropertied</code> interface, so you can access its properties by use of the <code>getDynamicProperty</code> methods as follows.
  
 
<source lang="xml" >
 
<source lang="xml" >
Line 274: Line 276:
 
</source>
 
</source>
  
Obviously, using <tt>DynamicPropertied </tt>is tedious. Worse of all, the macro's child components won't be changed if you use <tt>setDynamicProperty</tt> to change a property. For example, the following codes still show <tt>John</tt> as the username, not <tt>Mary</tt>.
+
Obviously, using <code>DynamicPropertied </code>is tedious. Worse of all, the macro's child components won't be changed if you use <code>setDynamicProperty</code> to change a property. For example, the following codes still show <code>John</code> as the username, not <code>Mary</code>.
  
 
<source lang="xml" >
 
<source lang="xml" >
Line 283: Line 285:
 
</source>
 
</source>
  
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<ref>On the other hand, the child components included by the <tt>include</tt> component is created in the rendering phase. In addition, all child components are removed and created each time the <tt>include</tt> component is invalidated.</ref>. Thus, the invocation to <tt>setDynamicProperty</tt> affects only the properties stored in a macro component (which you can retrieve with <tt>getDynamicProperties</tt>). The content of <tt>textbox</tt> remains intact.
+
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<ref>On the other hand, the child components included by the <code>include</code> component is created in the rendering phase. In addition, all child components are removed and created each time the <code>include</code> component is invalidated.</ref>. Thus, the invocation to <code>setDynamicProperty</code> affects only the properties stored in a macro component (which you can retrieve with <code>getDynamicProperties</code>). The content of <code>textbox</code> remains intact.
  
Thus, it is better to provide a method, say <tt>setWho</tt>, 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 <tt>class</tt> attribute of the component directive.
+
Thus, it is better to provide a method, say <code>setWho</code>, 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 <code>class</code> attribute of the component directive.
  
'''Tip''': To ''recreate'' child components with the current properties, you can use the <tt>recreate</tt> method. It actually detaches all child components, and then create them again.
+
'''Tip''': To ''recreate'' child components with the current properties, you can use the <code>recreate</code> 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.
 
There are two ways to implement a class. The details are described in the following sections.
 
+
<references/>
 
==== Provide Additional Methods in Java ====
 
==== Provide Additional Methods in Java ====
 
It takes two steps to provide additional methods for a macro component.
 
It takes two steps to provide additional methods for a macro component.
  
1. Implement a class by extending from the <tt>org.zkoss.zk.ui.HtmlMacroComponent</tt> class.
+
1. Implement a class by extending from the <code>org.zkoss.zk.ui.HtmlMacroComponent</code> class.
  
 
  //Username.java
 
  //Username.java
Line 315: Line 317:
 
</source>
 
</source>
  
* As depicted above, you have to call <tt>setDynamicProperty</tt> in <tt>setWho</tt>, because <tt>${arg.who}</tt> is referenced in the macro page (<tt>${arg.who}</tt>), which is used when a macro component are creating its child components.
+
* As depicted above, you have to call <code>setDynamicProperty</code> in <code>setWho</code>, because <code>${arg.who}</code> is referenced in the macro page (<code>${arg.who}</code>), which is used when a macro component are creating its child components.
* Since the <tt>setWho</tt> method might be called before a macro component creates its children, you have to check whether <tt>mc_who</tt> exists.
+
* Since the <code>setWho</code> method might be called before a macro component creates its children, you have to check whether <code>mc_who</code> exists.
* Since <tt>mc_who</tt>'s <tt>setValue</tt> is called, both the content and the visual presentation at the client are updated automatically, when <tt>setWho</tt> is called.
+
* Since <code>mc_who</code>'s <code>setValue</code> is called, both the content and the visual presentation at the client are updated automatically, when <code>setWho</code> is called.
  
2. Declare the class in the macro declaration with the <tt>class</tt> attribute.  
+
2. Declare the class in the macro declaration with the <code>class</code> attribute.  
  
 
<source lang="xml" >
 
<source lang="xml" >
Line 327: Line 329:
  
 
==== Provide Additional Methods in zscript ====
 
==== Provide Additional Methods in zscript ====
In addition to implementing with a Java file, you can implement the Java class(es) in <tt>zscript</tt>. 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.  
+
In addition to implementing with a Java file, you can implement the Java class(es) in <code>zscript</code>. 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 <tt>zscript</tt>.
+
It takes a few steps to implement a Java class in <code>zscript</code>.
  
1. You have to prepare a zscript file, say <tt>/zs/username.zs</tt>, for the class to implement. Notice that you can put any number of classes and functions in the same <tt>zscript</tt> file.
+
1. You have to prepare a zscript file, say <code>/zs/username.zs</code>, for the class to implement. Notice that you can put any number of classes and functions in the same <code>zscript</code> file.
  
 
  //username.zs
 
  //username.zs
Line 352: Line 354:
 
</source>
 
</source>
  
2. Use the <tt>init</tt> directive to load the <tt>zscript</tt> file, and then declare the component  
+
2. Use the <code>init</code> directive to load the <code>zscript</code> file, and then declare the component  
  
 
<source lang="xml" >
 
<source lang="xml" >
Line 360: Line 362:
 
</source>
 
</source>
 
 
The implementation class (<tt>mypack.Username</tt> in the previous example) is resolved as late as the macro component is really used, so it is also OK to use the <tt>zscript</tt> element to evaluate the <tt>zscript</tt> file.
+
The implementation class (<code>mypack.Username</code> in the previous example) is resolved as late as the macro component is really used, so it is also OK to use the <code>zscript</code> element to evaluate the <code>zscript</code> file.
  
 
<source lang="xml" >
 
<source lang="xml" >
Line 371: Line 373:
 
</source>
 
</source>
  
Though subjective, the <tt>init</tt> directive is more readable.
+
Though subjective, the <code>init</code> directive is more readable.
  
 
==== Override the Implementation Class When Instantiation ====
 
==== Override the Implementation Class When Instantiation ====
Like any other component, you can use the <tt>use</tt> attribute to override the class used to implement a macro component for any particular instance.
+
Like any other component, you can use the <code>use</code> attribute to override the class used to implement a macro component for any particular instance.
  
 
<source lang="xml" >
 
<source lang="xml" >
Line 385: Line 387:
 
</source>
 
</source>
  
Of course, you have to provide the implementation of <tt>another.MyAnohterUsername</tt> in the above example. Once again the class can be implemented with separate Java file, or by use of <tt>zscript</tt>.
+
Of course, you have to provide the implementation of <code>another.MyAnohterUsername</code> in the above example. Once again the class can be implemented with separate Java file, or by use of <code>zscript</code>.
  
 
==== Create a Macro Component Manually ====
 
==== Create a Macro Component Manually ====
To create a macro component manually, you have to invoke the <tt>afterCompose</tt> method after all the initialization as follows.
+
To create a macro component manually, you have to invoke the <code>afterCompose</code> method after all the initialization as follows.
  
 
<source lang="java" >
 
<source lang="java" >
Line 399: Line 401:
 
</source>
 
</source>
  
'''Note''': The <tt>getComponentDefinition</tt> method is used to look up the component definitions defined in a page.
+
'''Note''': The <code>getComponentDefinition</code> 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.
 
If you implement a class, say Username, for the macro, then you can do as follow.
Line 410: Line 412:
 
</source>
 
</source>
  
== See Also ==
 
From ZK Forum:
 
*[http://www.zkoss.org/forum/index.zul#path%3DlistComment%3BdiscussionId%3D5932%3BcategoryId%3D14%3B Fellow component not found for included zul file]
 
*[http://www.zkoss.org/forum/index.zul#path%3DlistComment%3BdiscussionId%3D6717%3BcategoryId%3D14%3B Cant create HtmlMacroComponent with java]
 
 
Frum User Blog:
 
*[http://felipecypriano.com/blog/2010/01/15/handling-events-on-zk-macro-components/ Handling events on ZK macro components] -- By Felipe Cypriano
 
  
 
{{ ZKDevelopersGuidePageFooter}}
 
{{ ZKDevelopersGuidePageFooter}}

Latest revision as of 02:58, 20 January 2022

Documentationacro Components
acro Components


Stop.png This documentation is for an older version of ZK. For the latest one, please click here.


Macro Components

There are two ways to implement a component. One is to implement a class deriving from the org.zkoss.zk.ui.AbstractComponent class. The other is to implement it by use of other components.

The former one is more flexible. It requires deeper understanding of ZK, so it is usually done by component developers. It is discussed in the Component Development Guide.

On the other hand, implementing a new component by use of other components is straightforward. It works like composition, macro expansion, or inline replacement. For sake of convenience, we call this kind of components as macro components., while the others are called primitive components.

Tip: a macro component is no different from a primitive component from application developer's viewpoint, except how it is implemented.

Three Steps to Use Macro Components

It takes three steps to use macro components as follows.

  1. Implements a macro component by a ZUML page.
  2. Declare the macro component in the page that is going to use it.
  3. Use the macro components, which is no difference that other components.

Tip: In addition to define a macro component in page, you can put its definition into a language addon such all pages are able to access the macro component.

Step 1. The Implementation

All you need to do is to prepare a ZUML page that describes what the component consists of. In other words, the page is a template of the macro.

For example, assume we want to pack a label and a text box as a macro component. Then we could create page, say /WEB-INF/macros/username.zul, as follows.

<hbox>
	Username: <textbox/>
</hbox>

It is done!

The ZUML page implementing a macro component is the same as any other pages, so any ZUML page can be used as a macro component.

Step 2. The Declaration

Before instantiating a macro component, you have to declare first. One of simplest way to declare is to use the component directives.

<?component name="username" macroURI="/WEB-INF/macros/username.zul"?>

As shown, you have to declare the name (the name attribute) and the URI of the page (the macroURI attribute).

Other Properties

In additions to the name, macroURI and class[1] attributes, you can specify a list of initial properties that will be used to initialize a component when it is instantiated.

<?component name="mycomp" macroURI="/macros/mycomp.zul"
   myprop="myval" another="anotherval"?>

Therefore,

<mycomp/>

is equivalent to

<mycomp myprop="myval1" another="anotherval"/>
  1. The class attribute will be discussed later.

Step 3. The Use

The use of a macro component is no different than others.

<window>
	<username/>
</window>

Pass Properties

Like an ordinary component, you can specify properties (a.k.a., attributes) when using a macro component as follows.

<?component name="username" macroURI="/WEB-INF/macros/username.zul"?>
<window>
	<username who="John"/>
</window>

All these properties specified are stored in a map that is then passed to the template via a variable called arg. Then, in the template, you could access these properties as follows.

<hbox>
	Username: <textbox value="${arg.who}"/>
</hbox>

Note: arg is available only when rendering the macro page. To access in the event listener, you have to use getDynamicProperty instead. Refer to the Provide Additional Methods section for more details.

arg.includer

In additions to the specified properties (a.k.a., attributes), a property called arg.includer is always passed to represent the parent of the components defined in a macro template.

If a regular macro is created, arg.includer is the macro component itself. If an inline macro is created, arg.includer is the parent component, if any. Refer to the Inline Macros section for more information.

In the above example, arg.includer represents the regular macro component, <username who="John"/>, and is the parent of <hbox> (defined in username.zul).

Inline Macros

There are two kinds of macro components: inline[1] and regular. By default, regular macros are assumed. To specify inline macros, you have to specify inline="true" in the component directive.

An inline macro behaves like inline-expansion. ZK doesn't create a macro component if an inline macro is encountered. Rather, it inline-expands the components defined in the macro URI. In other words, it works as if you type the content of the inline macro directly to the target page.

  1. Inline macro components are added since ZK 2.3.

use.zul: (target page)

<?component name="username" inline="true" macroURI="username.zul"?>
<grid>
	<rows>
		<username id="ua" name="John"/>
	</rows>
</grid>

username.zul: (macro definition)

<row>
	Username
	<textbox id="${arg.id}" value="${arg.name}"/>
</row>

Equivalent page:

<grid>
	<rows>
		<row>
			Username
			<textbox id="ua" value="John"/>
		</row>
	</rows>
</grid>

All properties, including id, are passed to the inline macro.

On the other hand, ZK will create a real component (called a macro component) to represent the regular macro. That is, the macro component is created as the parent of the components that are defined in the macro.

Inline macros are easier to integrate into sophisticated pages. For example, you cannot use regular components in the previous example since rows accepts only row, not macro components. It is easier to access to all components defined in a macro since they are in the same ID space. It also means the developers must be aware of the implementation to avoid name conflicts.

Regular macros allow the component developers to provide additional API and hide the implementation from the component users. Each regular macro component is an ID space owner, so there is no name conflicts. The users of regular macros usually assume nothing about the implementation. Rather, they access via the well-defined API.

An Example

inline.zul: (the macro definition)

<row>
	<textbox value="${arg.col1}"/>
	<textbox value="${arg.col2}"/>
</row>

useinline.zul: (the target page)

<?component    name="myrow"    macroURI="inline.zul"  inline="true"?>
<window    title="Test of inline macros"    border="normal">
	<zscript><![CDATA[
		import    org.zkoss.util.Pair;
		List  infos = new LinkedList();
		for(int j     = 0;j    <10;++j){
			infos.add(new Pair("A" + j, "B" +j));
		}
	]]>
	</zscript>
	<grid>
		<rows>
			<myrow    col1="${each.x}"    col2="${each.y}"    forEach="${infos}"/>
		</rows>
	</grid>
</window>

Regular Macros

ZK created a real component (called a macro component) to represent the regular macro as described in the previous section.

For sake of convenience, when we talk about macro components in this section, we mean the regular macro components.

Macro Components and The ID Space

Like window, a macro component is an ID space owner. In other words, it is free to use whatever identifiers to identify components inside the page implementing a macro component (a.k.a., child components of the macro component). They won't conflict with components defined in the same page with the macro component.

For example, assume we have a macro defined as follows.

<hbox>
	Username: <textbox id="who" value="${arg.who}"/>
</hbox>

Then, the following codes work correctly.

<?component name="username" macroURI="/WEB-INF/macros/username.zul"?>
<zk>
	<username/>
	<button id="who"/> <!-- no conflict because it is in a different ID space -->
</zk>

However, the following codes don't work.

<?component name="username" macroURI="/WEB-INF/macros/username.zul"?>
<username id="who"/>

Why? Like any ID space owner, the macro component itself is in the same ID space with its child components. There are two alternative solutions:

1. Use a special prefix for the identifiers of child components of a macro component. For example, "mc_who" instead of "who".

<hbox>
	Username: <textbox id="mc_who" value="${arg.who}"/>
</hbox>

2. Use the window component to create an additional ID space.

<window>
	<hbox>
		Username: <textbox id="who" value="${arg.who}"/>
	</hbox>
</window>

The first solution is suggested, if applicable, due to the simplicity.

Access Child Components From the Outside

Like other ID space owner, you can access its child component by use of two getFellow method invocations or org.zkoss.zk.ui.Path.

For example, assume you have a macro component whose ID is called "username", and then you can access the textbox as follows.

comp.getFellow("username").getFellow("mc_who");
new Path("/username/mc_who");

Access Variables Defined in the Ancestors

Macro components work as inline-expansion. Thus, like other components, a child component (of a macro component) can access any variable defined in the parent's ID space.

For example, username's child component can access v directly.

<zscript>
	String v = "something";
</zscript>
<username/>

However, it is not recommended to utilize such visibility because it might limit where a macro can be used.

Change macroURI At the Runtime

You can change the macro URI dynamically as follows.

<username id="ua"/>
<button onClick="ua.setMacroURI(&quot;another.zul&quot;)"/>

Provide Additional Methods

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(&quot;who&quot;))"/>

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[1]. 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.

  1. 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.

Provide Additional Methods in Java

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;

import org.zkoss.zk.ui.HtmlMacroComponent;
import org.zkoss.zul.Textbox;

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)getDynamicProperty("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"?>

Provide Additional Methods in zscript

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;

import org.zkoss.zk.ui.HtmlMacroComponent;
import org.zkoss.zul.Textbox;

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)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.

Override the Implementation Class When Instantiation

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.

Create a Macro Component Manually

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, null);
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();



Last Update : 2022/01/20

Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.