zkzkoss

Issue Integrating Child Components in Custom ZK Component


I'm developing a ButtonGroup custom component in ZK to house multiple button links, and I'm running into an issue where child components aren't rendering inside the intended container within a macro component.

    <button-group>
        <n:a href="forgot-password" class="text-xs text-blue-500 hover:underline">Forgot Password?</n:a>
        <n:a href="register" class="text-xs text-blue-500 hover:underline">Register</n:a>
    </button-group>

Goal: I want the <n:a> tags to render inside a specific division (childrenHost) of the macro template.

macro component setup:

    <zk xmlns:n="not the link because no karma -_-">
        <div class="flex flex-row gap-1">
            <n:p>Hello</n:p>
            <n:div id="childrenHost" class="flex flex-row gap-1">
                <!-- Target for dynamic child append -->
            </n:div>
        </div>
    </zk>

Java Component Setup:

    package nl.example.org.webui.components;
    
    import org.zkoss.zk.ui.HtmlMacroComponent;
    import org.zkoss.zk.ui.Component;
    
    public class ButtonGroup extends HtmlMacroComponent {
        public ButtonGroup() {
            setMacroURI("/components/form/buttonGroup.zul");
            compose();
        }
    
        @Override
        public void afterCompose() {
            super.afterCompose();
            // Attempts to append children to 'childrenHost'
        }
    }

Despite various methods attempted, such as manipulating the component hierarchy programmatically in afterCompose, the children still render outside the specified childrenHost div.

Has anyone encountered a similar challenge, or does anyone have insights on how to ensure child components integrate correctly into a designated part of a macro component? Any help or suggestions would be highly appreciated!


Solution

  • The main issue with this structure is that HtmlMacroComponent will only instantiate the content of the template URI provided to it if there are no existing components already created as children of that component.

    Note: by default the macro-component acts as a tag

    Also you shouldn't call compose during constructor. Compose should be called during the composition phase, when the component is already in the UI.

    The result is that you have either workflows:

    So, since your goal is to insert content into a host inside of the macro content, you shouldn't pass the children directly from the outer page, but as a template instead. Then you build that template in the compose method of the macro component.

    Just wire your host component to use it as target for the built components. This way, you control when and were the children components are created and added to the actual component structure.

    See below:

    on the outer page:

    <div>
        <mymacro>
            <template name="macroContent">
                <n:div class="foo">
                    <label value="bar"/>
                </n:div>
            </template>
        </mymacro>
    </div>
    

    In the macro component template:

    <span sclass="mymacro">
        <textbox value="myMacroContent1"/>
        <div id="mymacrohost" sclass="mymacrohost"></div>
        <textbox value="myMacroContent2"/>
    </span>
    

    in the macro component class

    @Wire("#mymacrohost")
    Div mymacrohost;
    
    @Override
    protected void compose() {
        super.compose();
        Template macroContent = Templates.lookup(this, "macroContent");
        macroContent.create(mymacrohost, null, null, null); //composer and resolve null, but can pass if needed.
    }