Composable Composers - Creating a Popup Reference"

From Documentation
m (correct highlight (via JWB))
 
(24 intermediate revisions by 2 users not shown)
Line 1: Line 1:
 
{{Template:Smalltalk_Author|
 
{{Template:Smalltalk_Author|
 
|author=Robert Wenzel, Engineer, Potix Corporation
 
|author=Robert Wenzel, Engineer, Potix Corporation
|date=June, 2019
+
|date=July, 2019
 
|version=ZK 8.6.2
 
|version=ZK 8.6.2
 
}}
 
}}
Line 7: Line 7:
 
= Introduction =
 
= 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).
+
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 for a popup, 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.
 
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 <div> or <label> component in separate classes.  
+
This may lead to multiple specialized extensions of various component classes just for a small recurring feature. Instead of extending each class separately - bloating up the component class hierarchy - you can implement and apply such a behavior (or multiple) on demand.
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 unsatisfying recommendation ...
 
(Un)fortunately Java's uses a static type system, so you often read the unsatisfying recommendation ...
Line 26: Line 25:
 
'''Q:''' Which feature could be use for behavior ''Composition''?  
 
'''Q:''' Which feature could be use for behavior ''Composition''?  
  
'''A:''' ... a '''''Composer''''' ... maybe?
+
'''A:''' ... a '''''Composer''''' ... maybe? Let's cover some basics first!
  
 
= Composers as Re-Usable Behaviors  =
 
= 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 attachable, custom component behavior, however they are quite "heavy" - running complex wiring, annotation scanning and binding behavior (which is the price for abstraction and convenience).   
+
For these scenarios ZK provides Composers which are invoked after component creation. The more traditionally used composers types are <javadoc>org.zkoss.zk.ui.util.GenericForwardComposer</javadoc>, <javadoc>org.zkoss.zk.ui.select.SelectorComposer</javadoc> (since ZK 6) and <javadoc>org.zkoss.bind.BindComposer</javadoc> (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 attachable, custom component behavior, however they are quite "heavy" - running complex/expensive wiring, annotation scanning and binding behavior (which is the price for abstraction and convenience).   
  
Still all these composers implement the Composer [LINK] interface, providing the most light-weight extension point - a simple callback method.
+
All these composers implement the <javadoc>org.zkoss.zk.ui.util.Composer</javadoc> interface, providing the most light-weight extension point - a simple callback method.
  
 
<source lang="java">
 
<source lang="java">
Line 40: Line 39:
 
</source>
 
</source>
  
That's it. A single method. No fields, no side effects, no overhead. Just a single method invoked after the component (and children) have been created and attached => ready to apply our plug-in-behavior.
+
That's it - a single method. No fields, no side effects, (almost) no overhead. Just a single method invoked after the component (and children) have been created and attached => ready to apply our plug-in-behavior.
  
 
== Closable for any Component ==
 
== 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.
+
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 <code>event.stopPropagation()</code> so that the default listener is never reached and the component remains on the page. This is often useful, in case of validation failures or the component should be reused - i.e. instead of detaching simply making it invisible.
  
 
A general-purpose-closable-behavior can be implemented without extending any existing component.
 
A general-purpose-closable-behavior can be implemented without extending any existing component.
  
<source lang="java">
+
e.g. [https://github.com/zkoss-demo/zk-popup-reference/blob/master/src/main/java/zk/example/Closeable.java Closable.java]
 +
 
 +
<source lang="java" highlight="8">
 
public class Closeable implements Composer {
 
public class Closeable implements Composer {
  
Line 63: Line 64:
 
</source>
 
</source>
  
* "onClick" listener (reacting to user clicks on that component) which triggers an "onClose" event (don't detach immediately)
+
* "onClick" listener (reacting to user clicks on that component (you might decide to add the listener to only a specific child component), which triggers an "onClose" event (instead of detaching immediately)
* "onClose" event listener (with a lower priority, so that it can be intercepted by calling event.stopPropagation)
+
* "onClose" event listener (with a lower priority, so that it can be intercepted by calling <code>event.stopPropagation()</code>)
 +
 
 +
I'd like to mention the static field <code>instance</code> here. Since the Closable composer is stateless, a single instance can be reused as many times as needed, saving additional memory.
  
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.
+
[https://github.com/zkoss-demo/zk-popup-reference/blob/master/src/main/webapp/basics.zul#L5-L9 Basics.zul] demonstrates the basic usage.
  
Inside a zul file the usage may look like this.
+
A theoretical scenario ...
  
 
<source lang="xml">
 
<source lang="xml">
Line 74: Line 77:
 
<zk>
 
<zk>
 
     <div apply="your.package.ApplicationComposer">
 
     <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}"/>
 
         <div id="someInformation" apply="${Closeable.instance}"/>
             Please click this area away to accept our terms and conditions.
+
             Please click this area away to accept our terms and conditions.  
             Listed below:
+
             If you're lucky we won't bother you any longer.
 
             <grid>...</grid>
 
             <grid>...</grid>
 
         </div>
 
         </div>
Line 87: Line 87:
 
</source>
 
</source>
  
Besides the auto-closing behavior, your ApplicationComposer can now add an event listener for the "onClose" event, for use case specific actions.
+
In order to customize auto-closing behavior, your ApplicationComposer can now add an event "onClose"-listener, e.g. prevent the closing in 50% of the cases, to test his patience.
  
 
E.g. In a SelectorComposer you can then prevent the auto-closing based on your application logic or give user feedback when the information div is closed.
 
E.g. In a SelectorComposer you can then prevent the auto-closing based on your application logic or give user feedback when the information div is closed.
  
 
<source lang="java">
 
<source lang="java">
 +
public class ApplicationComposer extends SelectorComposer<Div> {
 +
    ...
 
     @Listen("onClose=#someInformation")
 
     @Listen("onClose=#someInformation")
 
     public void closeSomeInformation() {
 
     public void closeSomeInformation() {
Line 101: Line 103:
 
         }
 
         }
 
     }
 
     }
 +
    ...
 
</source>
 
</source>
  
Line 107: Line 110:
 
In anticipation of your next questions - here a quick Q&A:
 
In anticipation of your next questions - here a quick Q&A:
  
'''Q:''' ''I'm using Java. Where's the '' <code>component.setApply()</code> ''method?''
+
'''Q:''' ''I want to add this behavior dynamically. Where's the '' <code>component.setApply()</code> ''method?''
  
 
'''A:''' ''There isn't one.''
 
'''A:''' ''There isn't one.''
Line 113: Line 116:
 
'''Q:''' ''How do I set the Closable composer from Java code?''
 
'''Q:''' ''How do I set the Closable composer from Java code?''
  
'''A:''' ''You don't. Simply call doAfterCompose from your ApplicationComposer's doAfterCompose method.''
+
'''A:''' ''You don't. Simply call Closable.doAfterCompose from your ApplicationComposer's doAfterCompose method.''
  
 
<source lang="java">
 
<source lang="java">
Line 134: Line 137:
 
'''A:''' ''Just add a command-binding ->'' <code>onClose="@command('handleSomeinfoClosing', event=event)"</code>.
 
'''A:''' ''Just add a command-binding ->'' <code>onClose="@command('handleSomeinfoClosing', event=event)"</code>.
  
== A Hover-To-Toggle-a-CSS-Class Behavior ==
+
== 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.
+
A completely unrelated behavior is adding 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.
+
(I know this can be done purely using the pseudo class :hover). However for this example I chose to do this, obviously you can execute more complicated client side code ([https://github.com/zkoss-demo/zk-popup-reference/blob/master/src/main/java/zk/example/HoverToggleClass.java HoverToggleClass.java]).
  
<source lang="java">
+
<source lang="java" highlight="16,17">
 
public class HoverToggleClass implements Composer<Component> {
 
public class HoverToggleClass implements Composer<Component> {
 
     private String cssClass;
 
     private String cssClass;
Line 161: Line 164:
 
</source>
 
</source>
  
* doAfterCompose adds to client side listeners: to toggle the desired css class only at client side (sending no event to the server)
+
* doAfterCompose adds two client side listeners: to toggle the desired css class only at client side (this time, sending no event to the server, just client side)
  
 
More interesting is the Constructor using an argument, which naturally prevents instantiation without a parameter.
 
More interesting is the Constructor using an argument, which naturally prevents instantiation without a parameter.
Line 190: Line 193:
 
</zk>
 
</zk>
 
</source>
 
</source>
 +
 +
(also demonstrated in [https://github.com/zkoss-demo/zk-popup-reference/blob/master/src/main/webapp/basics.zul#L11-L12 basics.zul])
  
 
HoverToggleClass caches previously created instances, in a Map to maximize reuse. This happens in the line ...
 
HoverToggleClass caches previously created instances, in a Map to maximize reuse. This happens in the line ...
Line 195: Line 200:
 
return cache.computeIfAbsent(cssClass, HoverToggleClass::new);
 
return cache.computeIfAbsent(cssClass, HoverToggleClass::new);
 
</source>
 
</source>
... which lazily creates the Map entry for that cssClass on demand. (Of course this is only beneficial as long as the UI uses finite number of css classes - entries are cached forever, but also reused forever in return)
+
... which lazily creates the Map entry for that "cssClass" on demand. (Of course this is only beneficial as long as the UI uses finite number of css classes - entries are cached forever, but also reused forever in return).
  
 
== Applying Multiple Composers ==
 
== Applying Multiple Composers ==
  
You might be already familiar with the following syntax to apply 2 or more Composers at a time:
+
You might already be familiar with the following syntax, to apply 2 or more Composers at a time:
  
 
<source lang="xml">
 
<source lang="xml">
Line 205: Line 210:
 
</source>
 
</source>
  
Our 2 independent behaviors from above can also be used simultaneously on the same element.
+
This works as long our Composers don't need constructor arguments, or even factory methods need to be called.
But need a slightly different syntax in order to provide the cached instances.
+
Still our 2 independent behaviors from above can be used simultaneously on the same element.
 +
It just needs a slightly different syntax:
 
The [https://docs.oracle.com/javaee/7/tutorial/jsf-el004.htm EL 3.0 syntax for collection literals] - <code>${[a, b, c, ...]}</code> - comes to the rescue:
 
The [https://docs.oracle.com/javaee/7/tutorial/jsf-el004.htm EL 3.0 syntax for collection literals] - <code>${[a, b, c, ...]}</code> - comes to the rescue:
  
Line 215: Line 221:
 
     </div>
 
     </div>
 
</source>
 
</source>
 +
 +
(see [https://github.com/zkoss-demo/zk-popup-reference/blob/master/src/main/webapp/basics.zul#L14-L27 basics.zul])
  
 
= Popup Reference (A More useful example) =
 
= Popup Reference (A More useful example) =
  
 
Let's start with this trivial case:
 
Let's start with this trivial case:
(I know zscript is 'bad' - but ideal for demos and prototyping. I assume you already know how to put event listeners in java code, so please keep on doing that)
+
(I know zscript is 'bad' - but ideal for demos and prototyping. I assume you already know how to put event listeners in java code, so please keep on doing that!)
  
 
<source  lang="xml">
 
<source  lang="xml">
Line 238: Line 246:
 
</zk>
 
</zk>
 
</source>
 
</source>
 +
 +
The same <menupopup> is repeated multiple times for each textbox. While the components are completely identical, the difference lies in the '''onClick'''-listener. It's the same <code>xyz.setValue("")</code> method call only on a different object each time.
  
 
'''Q:''' Who even does that?
 
'''Q:''' Who even does that?
Line 249: Line 259:
 
Obviously we can't reduce the number of inputs required in our form, but what we can do is share the menu popup.
 
Obviously we can't reduce the number of inputs required in our form, but what we can do is share the menu popup.
  
<source  lang="xml" high="7">
+
<source  lang="xml" highlight="7">
 
<zk>
 
<zk>
 
     <textbox id="tb1" context="menu"/>
 
     <textbox id="tb1" context="menu"/>
Line 263: Line 273:
 
</source>
 
</source>
  
Most users get stuck at the three question marks. However the solution is quite simple, each time a menu popup opens/closes it triggers an <code>OpenEvent</code>-event, which delivers the referencing component.
+
Most users get stuck at the three question marks. However the solution is quite simple, each time a menu popup opens/closes it triggers an <javadoc>org.zkoss.zul.ui.event.OpenEvent</javadoc>, which delivers the referencing component. ''Adding more ZSCRIPT code.''
  
<source  lang="xml" high="7">
+
<source  lang="xml" highlight="7">
 
     <menupopup id="menu"  
 
     <menupopup id="menu"  
 
         onOpen='if(event.isOpen()) {
 
         onOpen='if(event.isOpen()) {
Line 277: Line 287:
 
</source>
 
</source>
  
Now that's more like it: the referencing component can be stored in a component scoped attribute. And is then available as a variable in child components.
+
Now the referencing component is stored in a component scoped attribute, making it available as an EL variable in child components.
What's left to do is to get rid of the inline script code. Of course we could implement this behavior in our composer.To avoid implementing it over and over again we can add a real java class implementation of the onOpen-event-listener explicitely or we can use the Composer interface mentioned above to apply the event listener automatically from a zul file - resulting in cleaner MVVM separation.
+
What's left to do is to get rid of the inline ZSCRIPT code. Of course we could implement this behavior in our application composer. To avoid mixing business logic and technical concerns repeatedly, we can implement a dedicated Composer registering the '''onOpen'''-event-listener automatically.
 +
 
 +
A simple initial implementation could be [https://github.com/zkoss-demo/zk-popup-reference/blob/master/src/main/java/zk/example/PopupReferenceSimple.java PopupReferenceSimple.java]
  
 
<source lang="java">
 
<source lang="java">
Line 295: Line 307:
 
</source>
 
</source>
  
<source  lang="xml" high="7">
+
And the equally simple usage in [https://github.com/zkoss-demo/zk-popup-reference/blob/master/src/main/webapp/popupReferenceSimple.zul popupReferenceSimple.zul]
 +
 
 +
<source  lang="xml" highlight="7">
 
     <div id="ctrl">
 
     <div id="ctrl">
 
         <textbox id="tb1" context="menu"/>
 
         <textbox id="tb1" context="menu"/>
Line 308: Line 322:
 
</source>
 
</source>
  
The inline onClick-event-listener can be replaced in MVC:
+
To clean up the inline '''onClick'''-event-listener can be replaced in Java (MVC) as follows.
  
 
<source lang="java">
 
<source lang="java">
Line 318: Line 332:
 
</source>
 
</source>
  
In MVVM this still leaves us with the textbox component leaking into our View Model:
+
In MVVM this still leaves us with the '''textbox''' component leaking into our View Model:
  
<source lang="xml" high="2">
+
<source lang="xml" highlight="2">
 
     <menupopup id="menu" apply="zk.example.PopupReferenceSimple">
 
     <menupopup id="menu" apply="zk.example.PopupReferenceSimple">
 
         <menuitem label="clear" onClick="@command('clearText', textbox=popupReference)"/>
 
         <menuitem label="clear" onClick="@command('clearText', textbox=popupReference)"/>
Line 326: Line 340:
 
</source>
 
</source>
  
In this simple case passing the component Id or a custom component attribute might be enough to identify the property in the ViewModel code.
+
In this simple case passing the component Id or a custom component attribute might be enough to identify the property in the View Model code.
  
 
<source lang="xml">
 
<source lang="xml">
 
     <menuitem label="clear"  
 
     <menuitem label="clear"  
 
             onClick="@command('clearText', textboxId=popupReference.id)"/>
 
             onClick="@command('clearText', textboxId=popupReference.id)"/>
   <!-- OR -->
+
   <!-- OR using another custom attribute -->
 
   <menuitem label="clear"  
 
   <menuitem label="clear"  
 
             onClick="@command('clearText', textboxId=popupReference.getAttribute('vmProperty'))"/>
 
             onClick="@command('clearText', textboxId=popupReference.getAttribute('vmProperty'))"/>
 
</source>
 
</source>
 +
 +
Specifying additional unique component IDs or adding more custom attributes doesn't really scale well. That's why the example doesn't end here and continues with a typical scenario where we have a shared context menu for listitems in a listbox - rendered using a ListModel.
  
 
== Listbox with shared Listitem Popup ==
 
== Listbox with shared Listitem Popup ==
  
As an idea I added a more versatile version using a mapper to supply a mapping from the popup reference component into a suitable value in your application code. '''LINK CODE'''
+
In this scenario we are only interested in the actual value of each listitem, ideally corresponding to the template variable <code>each</code> from the list model. Especially in MVVM we don't care about the component, just the raw data.
 +
As an solution I added a more versatile Composer - [https://github.com/zkoss-demo/zk-popup-reference/blob/master/src/main/java/zk/example/PopupReference.java PopupReference.java] - supporting an optional mapping to convert from the popup reference '''component''' into an arbitrary '''object''' suitable in the application code. (50+ lines of code make it clear, why it is desirable to implement such a behavior only once, and reuse it via composition).
  
This then allows seamless integration with a listbox (here displaying a list of Locales).
+
This now allows seamless MVVM integration with a listbox (here displaying a list of Locales).
It reuses the same context popup for all listitems. The view model will only receive the actual Locale object.
+
It reuses the same context popup for all listitems. The view model will only ever receive the actual Locale object.
  
<source lang="xml" high="21">
+
Let's look at [https://github.com/zkoss-demo/zk-popup-reference/blob/master/src/main/webapp/popupReferenceListbox.zul popupReferenceListbox.zul]:
 +
 
 +
<source lang="xml" highlight="21">
 
<?import zk.example.PopupReference?>
 
<?import zk.example.PopupReference?>
 
<zk>
 
<zk>
Line 368: Line 387:
 
<menuitem label="Menuitem 2" onClick="@command('contextClick2')" />
 
<menuitem label="Menuitem 2" onClick="@command('contextClick2')" />
 
</menupopup>
 
</menupopup>
 
+
              ...
 
</div>
 
</div>
 
</zk>
 
</zk>
Line 376: Line 395:
  
 
''Menuitem 1'' and ''Menuitem 2'' show alternative @Command bindings for the popupReference parameter as an explicit @BindingParam or an implicitly resolved @ScopeParam. Both ways avoid direct component access in the view model.
 
''Menuitem 1'' and ''Menuitem 2'' show alternative @Command bindings for the popupReference parameter as an explicit @BindingParam or an implicitly resolved @ScopeParam. Both ways avoid direct component access in the view model.
 +
 +
Extract from the view model class [https://github.com/zkoss-demo/zk-popup-reference/blob/master/src/main/java/zk/example/PopupReferenceListboxVM.java PopupReferenceListboxVM.java]
  
 
<source lang="java">
 
<source lang="java">
Line 389: Line 410:
 
</source>
 
</source>
  
For more usage examples please check out the source code. '''LINK ME'''
+
The code also contains an example how to create the Composer from java code (e.g. for more complext dynamic/conditional mapping) and still apply it in the zul file.
  
Worth mentioning is the fact that <code>PopupReference</code> adds an onOpen event listener implementing the <code>Deferrable</code> interface.
+
Worth mentioning is the fact that <code>PopupReference</code> adds an '''onOpen'''-event-listener implementing the <javadoc>org.zkoss.zk.ui.event.Deferrable</javadoc>-interface.
While this is optional the benefits are that the onOpen event is not sent to the server immediately. Instead they are ''deferred'', waiting for the onClick event followed by another onOpen event (open=false) in the same AuRequest. While this naturally reduces the number network round trips it avoids the loading animation of the popup and takes care of adding/removing the popupReference in the same request/response cycle.
+
While this is optional the benefits are that the onOpen-events are not sent to the server immediately. Instead they are ''deferred'', waiting for the menuitem's onClick event followed by another onOpen-event (open=false) in the same AuRequest. While this naturally reduces the number network round trips it avoids the (in this case unnecessary) loading animation of the popup and takes care of adding/removing the popupReference-attribute in the same request/response cycle.
  
Here the effective sequence of commands sent in a single websocket or ajax message:
+
Here the effective sequence of commands sent in a single websocket message or ajax request:
  
 
<source>
 
<source>
Line 410: Line 431:
 
= Summary =
 
= Summary =
  
'''Q:''' When can I use this new feature: ''Composers''?
+
Composers provide a flexible way to implement reusable component features and apply them to multiple components (or multiple composers to one component) - avoiding excessive component class hierarchies - promoting composition.
 +
 
 +
'''Q:''' When can I use this ''new'' feature: ''Composers''?
  
'''A:''' Actually the Composer Interface was added in ZK 3.0.0 - so practically everywhere :P
+
'''A:''' Actually the '''Composer''' interface was added in '''ZK 3.0.0''' - so practically available everywhere :P - if not, it's time to upgrade.
  
 
== Example Code ==
 
== Example Code ==
Available on github - checkout the README.md for running instructions. '''LINK ME'''
+
Available on [https://github.com/zkoss-demo/zk-popup-reference github] - checkout the [https://github.com/zkoss-demo/zk-popup-reference/blob/master/README.md README.md] for running instructions.
  
  

Latest revision as of 04:42, 20 January 2022

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

Author
Robert Wenzel, Engineer, Potix Corporation
Date
July, 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 for a popup, 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 may lead to multiple specialized extensions of various component classes just for a small recurring feature. 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 unsatisfying recommendation ...

A: Favor Composition over Inheritance!

Leading to more questions ...

Q: How would I do this without changing/recompiling ZK's provided component hierarchy?

Q: Is this even possible with ZK components?

Q: Which feature could be use for behavior Composition?

A: ... a Composer ... maybe? Let's cover some basics first!

Composers as Re-Usable Behaviors

For these scenarios ZK provides Composers which are invoked 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 attachable, custom component behavior, however they are quite "heavy" - running complex/expensive wiring, annotation scanning and binding behavior (which is the price for abstraction and convenience).

All these composers implement the Composer interface, providing the most light-weight extension point - a simple callback method.

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

That's it - a single method. No fields, no side effects, (almost) no overhead. Just a single method invoked after the component (and children) have been created and attached => 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 often useful, in case of validation failures or the component should be reused - i.e. instead of detaching simply making it invisible.

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

e.g. Closable.java

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 (you might decide to add the listener to only a specific child component), which triggers an "onClose" event (instead of detaching immediately)
  • "onClose" event listener (with a lower priority, so that it can be intercepted by calling event.stopPropagation())

I'd like to mention the static field instance here. Since the Closable composer is stateless, a single instance can be reused as many times as needed, saving additional memory.

Basics.zul demonstrates the basic usage.

A theoretical scenario ...

<?import zk.example.Closeable?>
<zk>
    <div apply="your.package.ApplicationComposer">
        ...
        <div id="someInformation" apply="${Closeable.instance}"/>
            Please click this area away to accept our terms and conditions. 
            If you're lucky we won't bother you any longer.
            <grid>...</grid>
        </div>
    </div>
<zk/>

In order to customize auto-closing behavior, your ApplicationComposer can now add an event "onClose"-listener, e.g. prevent the closing in 50% of the cases, to test his patience.

E.g. In a SelectorComposer you can then prevent the auto-closing based on your application logic or give user feedback when the information div is closed.

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

Tiny Q&A

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

Q: I want to add this behavior dynamically. 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. Simply call Closable.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 is adding 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 I chose to do this, obviously you can execute more complicated client side code (HoverToggleClass.java).

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 two client side listeners: to toggle the desired css class only at client side (this time, sending no event to the server, just client side)

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>

(also demonstrated in basics.zul)

HoverToggleClass caches previously created instances, in a Map to maximize reuse. This happens in the line ...

return cache.computeIfAbsent(cssClass, HoverToggleClass::new);

... which lazily creates the Map entry for that "cssClass" on demand. (Of course this is only beneficial as long as the UI uses finite number of css classes - entries are cached forever, but also reused forever in return).

Applying Multiple Composers

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

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

This works as long our Composers don't need constructor arguments, or even factory methods need to be called. Still our 2 independent behaviors from above can be used simultaneously on the same element. It just needs a slightly different syntax: The EL 3.0 syntax for collection literals - ${[a, b, c, ...]} - comes to the rescue:

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

(see basics.zul)

Popup Reference (A More useful example)

Let's start with this trivial case: (I know zscript is 'bad' - but ideal for demos and prototyping. I assume you already know how to put event listeners in java code, so please keep on doing that!)

<zk>
    <textbox id="tb1" context="menu1"/>
    <menupopup id="menu1">
        <menuitem label="clear" onClick='tb1.setValue("")'/>
        ... <!-- many options -->
    </menupopup>

    <textbox id="tb2" context="menu2"/>
    <menupopup id="menu2">
        <menuitem label="clear" onClick='tb2.setValue("")'/>
        ... <!-- the exact same options again -->
    </menupopup>

     ... <!-- repeat -->
</zk>

The same <menupopup> is repeated multiple times for each textbox. While the components are completely identical, the difference lies in the onClick-listener. It's the same xyz.setValue("") method call only on a different object each time.

Q: Who even does that?

A: Trust me I've seen this or something similar many times ... and that's why I decided to write this article in the first place.

Q: What about <templates>?

A: They might reduce the amount of code you have to write, still each template instance creates a new component.

Obviously we can't reduce the number of inputs required in our form, but what we can do is share the menu popup.

<zk>
    <textbox id="tb1" context="menu"/>
    <textbox id="tb2" context="menu"/>
    ... <!-- repeat -->

    <menupopup id="menu">
        <menuitem label="clear" onClick='???.setValue("")'/>
        ... <!-- many options, only once -->
    </menupopup>

</zk>

Most users get stuck at the three question marks. However the solution is quite simple, each time a menu popup opens/closes it triggers an OpenEvent, which delivers the referencing component. Adding more ZSCRIPT code.

    <menupopup id="menu" 
        onOpen='if(event.isOpen()) {
                    self.setAttribute("popupReference", event.getReference());
                } else {
                    self.removeAttribute("popupReference");
                }'>
        <menuitem label="clear" onClick='popupReference.setValue("")'/>
        ... <!-- many options, only once -->
    </menupopup>

Now the referencing component is stored in a component scoped attribute, making it available as an EL variable in child components. What's left to do is to get rid of the inline ZSCRIPT code. Of course we could implement this behavior in our application composer. To avoid mixing business logic and technical concerns repeatedly, we can implement a dedicated Composer registering the onOpen-event-listener automatically.

A simple initial implementation could be PopupReferenceSimple.java

public class PopupReferenceSimple<T extends Popup> implements Composer<T> {
    @Override
    public void doAfterCompose(T popup) {
        popup.addEventListener(Events.ON_OPEN, (OpenEvent event) -> {
            if (event.isOpen()) {
                popup.setAttribute("popupReference", event.getReference());
            } else {
                popup.removeAttribute("popupReference");
            }
        });
    }
}

And the equally simple usage in popupReferenceSimple.zul

    <div id="ctrl">
        <textbox id="tb1" context="menu"/>
        <textbox id="tb3" context="menu"/>
        <textbox id="tb4" context="menu"/>
        <textbox id="tb5" context="menu"/>

        <menupopup id="menu" apply="zk.example.PopupReferenceSimple">
            <menuitem id="menuitemClear" label="clear" onClick='popupReference.setValue("")'/>
        </menupopup>
    </div>

To clean up the inline onClick-event-listener can be replaced in Java (MVC) as follows.

    @Listen("onClick = #menuitemClear") 
    public void clearTextbox(Event event) {
        Textbox tb = (Textbox) event.getTarget().getAttribute("popupReference", true); //true - search in parent scopes
        tb.setValue("");
    }

In MVVM this still leaves us with the textbox component leaking into our View Model:

    <menupopup id="menu" apply="zk.example.PopupReferenceSimple">
        <menuitem label="clear" onClick="@command('clearText', textbox=popupReference)"/>
    </menupopup>

In this simple case passing the component Id or a custom component attribute might be enough to identify the property in the View Model code.

    <menuitem label="clear" 
             onClick="@command('clearText', textboxId=popupReference.id)"/>
   <!-- OR using another custom attribute -->
   <menuitem label="clear" 
             onClick="@command('clearText', textboxId=popupReference.getAttribute('vmProperty'))"/>

Specifying additional unique component IDs or adding more custom attributes doesn't really scale well. That's why the example doesn't end here and continues with a typical scenario where we have a shared context menu for listitems in a listbox - rendered using a ListModel.

Listbox with shared Listitem Popup

In this scenario we are only interested in the actual value of each listitem, ideally corresponding to the template variable each from the list model. Especially in MVVM we don't care about the component, just the raw data. As an solution I added a more versatile Composer - PopupReference.java - supporting an optional mapping to convert from the popup reference component into an arbitrary object suitable in the application code. (50+ lines of code make it clear, why it is desirable to implement such a behavior only once, and reuse it via composition).

This now allows seamless MVVM integration with a listbox (here displaying a list of Locales). It reuses the same context popup for all listitems. The view model will only ever receive the actual Locale object.

Let's look at popupReferenceListbox.zul:

<?import zk.example.PopupReference?>
<zk>
	<div viewModel="@id('vm') @init('zk.example.popupref.ListitemContextVM')">
		<listbox model="@init(vm.model)" checkmark="true" width="400px" onSelect="@command('handleSelection')">
			<custom-attributes org.zkoss.zul.listbox.rightSelect="false"/>
			<listhead>
				<listheader label="Locale"/>
				<listheader label="Language"/>
				<listheader label="Country"/>
			</listhead>
			<template name="model">
				<listitem context="menupopup1">
					<listcell label="${each}"/>
					<listcell label="${each.displayLanguage}"/>
					<listcell label="${each.displayCountry}"/>
				</listitem>
			</template>
		</listbox>

		<!-- PopupReference created in ZUL -->
		<menupopup id="menupopup1" apply="${PopupReference.forLambda(li -> li.value)}">
			<menuitem label="Menuitem 1" onClick="@command('contextClick1', contextLocale=popupReference)" />
			<menuitem label="Menuitem 2" onClick="@command('contextClick2')" />
		</menupopup>
               ...
	</div>
</zk>

The factory method PopupReference.forLambda leverages an EL 3 lambda expression to convert from a component (org.zkoss.zul.Listitem) to its actual model value (java.util.Locale).

Menuitem 1 and Menuitem 2 show alternative @Command bindings for the popupReference parameter as an explicit @BindingParam or an implicitly resolved @ScopeParam. Both ways avoid direct component access in the view model.

Extract from the view model class PopupReferenceListboxVM.java

    @Command
    public void contextClick1(@BindingParam("contextLocale") Locale contextLocale) {
        Clients.log("Menuitem 1: clicked for " + contextLocale);
    }

    @Command
    public void contextClick2(@ScopeParam(POPUP_REFERENCE_ATTR) Locale popupReference) {
        Clients.log("Menuitem 2: clicked for " + popupReference);
    }

The code also contains an example how to create the Composer from java code (e.g. for more complext dynamic/conditional mapping) and still apply it in the zul file.

Worth mentioning is the fact that PopupReference adds an onOpen-event-listener implementing the Deferrable-interface. While this is optional the benefits are that the onOpen-events are not sent to the server immediately. Instead they are deferred, waiting for the menuitem's onClick event followed by another onOpen-event (open=false) in the same AuRequest. While this naturally reduces the number network round trips it avoids the (in this case unnecessary) loading animation of the popup and takes care of adding/removing the popupReference-attribute in the same request/response cycle.

Here the effective sequence of commands sent in a single websocket message or ajax request:

cmd_0: "onOpen"
cmd_1: "onClick"
cmd_2: "onOpen"
data_0: "{"open":true,"reference":"cTwPr"}"
data_1: "{"pageX":283,"pageY":215,"which":1,"x":283,"y":215}"
data_2: "{"open":false}"
uuid_0: "cTwP6"
uuid_1: "cTwP8"
uuid_2: "cTwP6"

Summary

Composers provide a flexible way to implement reusable component features and apply them to multiple components (or multiple composers to one component) - avoiding excessive component class hierarchies - promoting composition.

Q: When can I use this new feature: Composers?

A: Actually the Composer interface was added in ZK 3.0.0 - so practically available everywhere :P - if not, it's time to upgrade.

Example Code

Available on github - checkout the README.md for running instructions.


Comments



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