Composable Composers - Creating a Popup Reference

From Documentation
DocumentationSmall Talks2019JulyComposable Composers - Creating a Popup Reference
Composable Composers - Creating a Popup Reference

Author
Robert Wenzel, Engineer, Potix Corporation
Date
June, 2019
Version
ZK 8.6.2

Introduction

Sometimes you want to implement a common/recurring feature for components (e.g. a discardable initial help overlay or image preview overlay, a recurring visual effect, remember some kind of contextual data, fire an occasional custom event ... I could go on).

You can of course extend existing components, adding a new property (getter and setter) and implement the functionality on that component, extend/define the component-definition in a lang-addon.xml.

This often leads to multiple specialized implementations of

or <label> component in separate classes.

Instead of extending each class separately - bloating up the component class hierarchy - you can implement and apply such a behavior (or multiple) on demand.

(Un)fortunately Java's uses a static type system, so you often read the mantra: "Favor Composition over Inheritance!"

Is this even possible in a ZK application?

Which feature could we use for Composition?

... a Composer ... maybe?

Composers as Re-Usable Behaviors

For these scenarios ZK provides Composers which are called after component creation. The more traditionally used composers types are GenericForwardComposer, SelectorComposer (since ZK 6) and BindComposer (for MVVM since ZK 6). Their purpose is to ease integration of application logic and convenient access to components. Technically they could be used to implement any custom component behavior, however they are quite "heavy" - running complex wiring, annotation scanning and binding behavior (which is the price for abstraction and convenience). All these composers implement the Composer [LINK] interface, providing the most light-weight extension point - a simple method call.

public interface Composer<T extends Component> {
	public void doAfterCompose(T comp) throws Exception;
}

That's it. A single method, no fields, no side effects, no overhead, just a single method called after the component (and children) have been created => ready to apply our plug-in-behavior.

Closable for any Component

Panel, Tab and Window have a unique behavior and event triggered by the close button. When clicking the 'close' icon, they detach themselves after firing an onClose event. In the 'onClose' listener it is possible to prevent the detaching by calling 'event.stopPropagation' so that the default listener is never reached and the component remains on the page. This is useful. In case prior validation failed or the component should be reused - so instead of detaching simply making it invisible.

A general-purpose-closable-behavior can be implemented without extending any existing component.

public class Closeable implements Composer {

    public final static Closeable instance = new Closeable();

    @Override
    public void doAfterCompose(Component comp) throws Exception {
        comp.addEventListener(Events.ON_CLICK,
                e -> Events.postEvent(Events.ON_CLOSE, comp, null));
        comp.addEventListener(-1000, Events.ON_CLOSE,
                e -> comp.detach());
    }
}
  • "onClick" listener (reacting to user clicks on that component) which triggers an "onClose" event (don't detach immediately)
  • "onClose" event listener (with a lower priority, so that it can be intercepted by calling event.stopPropagation)

Mentionable is the static field instance. Since the Closable composer is stateless a single instance can be reused as many times as needed saving additional memory.

Inside a zul file the usage may look like this.

<?import zk.example.Closeable?>
<zk>
    <div apply="your.package.ApplicationComposer">
        <label value="click to detach" apply="zk.example.Closeable"/> <!-- creates a new instance -->
        <separator/>
        <label value="click to detach" apply="${Closeable.instance}"/> <!-- use the shared instance -->
        <separator/>
        <div id="someInformation" apply="${Closeable.instance}"/>
            Please click this area away to accept our terms and conditions.
            Listed below:
            <grid>...</grid>
        </div>
    </div>
<zk/>

Your ApplicationComposer can now add event an listener for the "onClose" event:

e.g. when used in a SelectorComposer you can then prevent the closing based on your application logic. Or update the UI in other places when the information div is closed.

    @Listen("onClose=#someInformation")
    public void closeSomeInformation() {
        if (Math.random() < 0.5) {
            event.stopPropagation();
        } else {
            Clients.showNotification("Bad luck, Please click again to remove!");
        }
    }

In anticipation of your next questions - here a quick Q&A:

Q: Where's the component.setApply() method?

A: There isn't one.

Q: How do I set the Closable composer from Java code?

A: You don't have to. Simply call doAfterCompose from your ApplicationComposer's doAfterCompose method.

public class ApplicationComposer extends SelectorComposer<Div> {
    @Wire
    private Div someInformation;

    @Override
    public void doAfterCompose(Div comp) {
        super.doAfterCompose(comp);
        Closable.instance.doAfterCompose(someInformation);
        //if you prefer implement your own static helper method
        //Closable.applyTo(someInformation);
    }
}

Q: What about MVVM?

A: Just add a command-binding -> `onClose="@command('handleSomeinfoClosing', event=event)"`.

A Hover-To-Toggle-a-CSS-Class behavior

A completely unrelated behavior might be to add a css class when the user hovers a certain component. (I know this can be done purely using the pseudo class :hover) However for this example we chose to do this, obviously you can execute more complicated client side code.

public class HoverToggleClass implements Composer<Component> {
    private String cssClass;

    static Map<String, HoverToggleClass> cache = new ConcurrentHashMap<>();

    public HoverToggleClass(String cssClass) {
        this.cssClass = cssClass;
    }

    public static HoverToggleClass instanceFor(String cssClass) {
        return cache.computeIfAbsent(cssClass, HoverToggleClass::new);
    }

    @Override
    public void doAfterCompose(Component comp) throws Exception {
        comp.setWidgetListener("onMouseOver", String.format("jq(this).toggleClass('%s', true);", cssClass));
        comp.setWidgetListener("onMouseOut", String.format("jq(this).toggleClass('%s', false);", cssClass));
    }
}
  • doAfterCompose adds to client side listeners: to toggle the desired css class only at client side (sending no event to the server)

More interesting is the Constructor using an argument, which naturally prevents instantiation without a parameter.

    <!-- So the following won't work: -->
    <label apply="zk.example.HoverToggleClass" value="hover me"/>

    <!-- Neither will this -->
    <label apply="zk.example.HoverToggleClass('myHoverClass')" value="hover me"/>

One Possible way to instantiate this Composer is:

<?taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c"?>
<zk>
    <label apply="${c:new1('zk.example.HoverToggleClass', 'myHoverClass')}" value="hover me"/>
</zk>

Still we can do better, by calling a static factory method, returning cached instances for the same css-class, instead of creating a new instance every time.

<?import zk.example.HoverToggleClass?>
<zk>
    <label apply="${HoverToggleClass.instanceFor('myHoverClass')}" value="hover me" />
</zk>

HoverToggleClass caches previously created instances, in a Map to maximize reuse. The line return cache.computeIfAbsent(cssClass, HoverToggleClass::new); lazily creates the Map entry for that cssClass on demand. (Of course this only works if you UI uses finite number of css classes) - entries are cached, but also reused forever.)

Applying Multiple Composers

You might be already familiar with the following syntax to apply 2 or more Composers at a time:

    <div apply="my.package.ComposerA, my.package.ComposerB" />

Our 2 independent behaviors from above can also be used simultaneously on the same element. But need a slightly different syntax in order to provide the cached instances. The EL3 syntax for collection literals [...] comes to the rescue [LINK https://docs.oracle.com/javaee/7/tutorial/jsf-el004.htm]:

    <div apply="${[Closeable.instance, HoverToggleClass.instanceFor('myHoverClass')]}">
         onClose='Clients.log("closed by user user click")'>
        multiple composers: hover to toggle css, click to detach
    </div>

Popup Reference (A More useful example)

Popup Reference

Summary

Comments



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