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

If you enjoyed this post, please consider leaving a comment or subscribing to the RSS feed to have future articles delivered to your feed reader.

Leave a Reply