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.

<zk>
    <hlayout id="m_hlayout" hflex="1" height="20px"
        sclass="breadcrumb" spacing="10px">
    </hlayout>
</zk>

Main zul page
Then, use the macro component in the main zul page.

<?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>

View Model
In View Model, register a click event to command loadArticle and notify the path change to the macro component.

public class MacroSampleVM {
    private DefaultTreeModel<ArticleLabel> menuModel;
    private TreeNode<ArticleLabel> path;
    public MacroSampleVM() {
        // generate tree
        TreeNode<ArticleLabel> root = new DefaultTreeNode<ArticleLabel>(null, generateYears());
        menuModel = new DefaultTreeModel<ArticleLabel>(root);
    }
    @Command("loadArticle")
    @NotifyChange("path")
    public void loadArticle(@BindingParam("node") DefaultTreeNode<ArticleLabel> node) {
        path = node;
    }
}

Breadcrumb macro component class
Finally, define setPath API in BreadCrumb.java to add a new component inside the macro component.

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
    }
}

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.

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);
        }
        //...
    }
}

Then, we can listen to onPathClick event on zul and bind with MVVM.

<!-- omitted -->
<vlayout>
    <breadcrumb hflex="1" path="@bind(vm.path)" 
        onPathClick="@command('loadArticle', node=event.data)" />
    <vlayout>
        article list
    </vlayout>
</vlayout>
<!-- omitted -->

Demo

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