Introduction
Macro component is a useful technique to modularize complex ZUL pages. In most use cases, macro component consists of several fixed components. However, you may want to append more components inside a macro component based on some action. This blog will show how you can add additional components to a wired component inside a macro component. Here we will use a breadcrumb macro component as an example and dynamically create components inside it.
Sample Scenario
In this sample, there is a side bar menu with a tree component, the breadcrumb component will change the path according to the treeitem clicked.
Breadcrumb macro component
First, create a breadcrumb.zul like the following to define a macro component.
<div style="width: 675px; overflow: auto;">
<pre class="brush:xml"><zk>
<hlayout id="m_hlayout" hflex="1" height="20px"
sclass="breadcrumb" spacing="10px">
</hlayout>
</zk></pre>
</div>
Main zul page
Then, use the macro component in the main zul page.
<div style="width: 675px; overflow: auto;">
<pre class="brush:xml; highlight: [1, 21]"><?component name="breadcrumb" macroURI="/WEB-INF/macro/breadcrumb.zul" class="demo.macro.BreadCrumb"?>
<zk xmlns:n="native">
<div vflex="1">
<borderlayout hflex="1" vflex="1" apply="org.zkoss.bind.BindComposer" viewModel="@id('vm') @init('demo.macro.MacroSampleVM')">
<north border="none">
<div>
<label value="Blog Title" />
</div>
</north>
<east width="280px" border="none">
<div>
<tree model="@bind(vm.menuModel)">
<template name="model" var="node">
<treeitem label="@load(node.data.label)" onClick="@command('loadArticle', node=node)" />
</template>
</tree>
</div>
</east>
<center autoscroll="true" border="none">
<vlayout>
<breadcrumb hflex="1" path="@bind(vm.path)" />
<vlayout>
article list
</vlayout>
</vlayout>
</center>
</borderlayout>
</div>
</zk></pre>
</div>
View Model
In View Model, register a click event to command loadArticle and notify the path change to the macro component.
<div style="width: 675px; overflow: auto;">
<pre class="brush:xml"><zk>
<hlayout id="m_hlayout" hflex="1" height="20px"
sclass="breadcrumb" spacing="10px">
</hlayout>
</zk></pre>
</div>
Breadcrumb macro component class
Finally, define setPath API in BreadCrumb.java to add a new component inside the macro component.
<div style="width: 675px; overflow: hidden;">
<pre class="brush: java; highlight: [11, 16]">public class BreadCrumb extends HtmlMacroComponent {
@Wire
private Hlayout m_hlayout;
//other member fields
public BreadCrumb() {
compose();
}
public TreeNode<ArticleLabel> getPath() {
return _node;
}
public void setPath(TreeNode<ArticleLabel> node) {
_node = node;
_paths.clear();
m_hlayout.getChildren().clear();
generatePaths(node);
//create components here and set parent to wired Hlayout
A[] links = new A[_paths.size() - 1];
for (int i = links.length - 1; i >= 0; i--) {
TreeNode<ArticleLabel> n = _paths.pollLast();
String label = n.getData().getLabel();
links[i] = new A(label);
links[i].setParent(m_hlayout);
}
_paths.clear();
}
private LinkedList<TreeNode<ArticleLabel>> generatePaths(TreeNode<ArticleLabel> node) {
//skip
}
}</pre>
</div>
Communicate internal components with View Model
As we can see from the sample above, we can create components inside a macro component by using a click event defined in View Model. Here we will show how to register an event on a newly added component and pass it onto View Model.
First, add a forward event to the newly added component, the target component is the macro component itself.
<div style="width: 675px; overflow: hidden;">
<pre class="brush: java; highlight: 11">public class BreadCrumb extends HtmlMacroComponent {
//omitted
public void setPath(TreeNode<ArticleLabel> node) {
//...
A[] links = new A[_paths.size() - 1];
for (int i = links.length - 1; i >= 0; i--) {
TreeNode<ArticleLabel> n = _paths.pollLast();
String label = n.getData().getLabel();
links[i] = new A(label);
links[i].setParent(m_hlayout);
links[i].addForward(Events.ON_CLICK, this, "onPathClick", n);
}
//...
}
}</pre>
</div>
Then, we can listen to onPathClick event on zul and bind with MVVM.
<div style="width: 675px; overflow: hidden;">
<pre class="brush: xml; highlight: 4"><!-- omitted -->
<vlayout>
<breadcrumb hflex="1" path="@bind(vm.path)"
onPathClick="@command('loadArticle', node=event.data)" />
<vlayout>
article list
</vlayout>
</vlayout>
<!-- omitted --></pre>
</div>
Download
You can download the complete source code for this example from git.
More information about macro component