ZK10 Preview: Using the new and light Client MVVM"

From Documentation
 
(25 intermediate revisions by 3 users not shown)
Line 1: Line 1:
 
{{Template:Smalltalk_Author|
 
{{Template:Smalltalk_Author|
 
|author=James Chu, Engineer, Potix Corporation
 
|author=James Chu, Engineer, Potix Corporation
|date=May 19, 2022
+
|date=May 26, 2022 (last update Oct 19, 2023)
|version=10.0.0.FL.20220425-Eval
+
|version=10.0.0-Beta or later
 
}}
 
}}
  
Line 18: Line 18:
 
Now, let's take a look at how we can enable the Client MVVM.
 
Now, let's take a look at how we can enable the Client MVVM.
  
== Setup ==
+
== Setting up Client MVVM ==
 
There are two ways to use the client MVVM, either use it against a specific ViewModel, or, set it globally. Either way, you have to first include the jar file and add the listener.
 
There are two ways to use the client MVVM, either use it against a specific ViewModel, or, set it globally. Either way, you have to first include the jar file and add the listener.
  
=== Step 0 ===  
+
=== Step 1 ===  
Use ZK 10 EE-Eval freshly, and include the '''zephyr''' jar file.
+
Use ZK 10 EE-Eval freshly, and include the [https://mavensync.zkoss.org/eval/org/zkoss/zk/client-bind/ '''client-bind'''] jar file.
  
=== Step 1 ===
+
=== Step 2 ===
 
Add the listener for Client MVVM. (in zk.xml)
 
Add the listener for Client MVVM. (in zk.xml)
 
<source lang='xml'>
 
<source lang='xml'>
 
<listener>​
 
<listener>​
     <listener-class>org.zkoss.zephyrex.bind.BinderPropertiesRenderer</listener-class>​
+
     <listener-class>org.zkoss.clientbind.BinderPropertiesRenderer</listener-class>​
 
</listener>
 
</listener>
 
</source>
 
</source>
  
=== Step 2 ===
+
=== Step 3 ===
 
==== Option 1: Apply to a ViewModel ====  
 
==== Option 1: Apply to a ViewModel ====  
  
Line 39: Line 39:
 
For example: (in ZUL File)
 
For example: (in ZUL File)
 
<source lang='xml'>
 
<source lang='xml'>
<div apply="org.zkoss.zephyrex.bind.ClientBindComposer" viewModel="..." >
+
<div apply="org.zkoss.clientbind.ClientBindComposer" viewModel="..." >
 
     <!-- other components -->
 
     <!-- other components -->
 
</div>
 
</div>
Line 46: Line 46:
 
==== Option 2: Set it globally ====  
 
==== Option 2: Set it globally ====  
  
We can change the [https://www.zkoss.org/wiki/ZK_Configuration_Reference/zk.xml/The_Library_Properties/org.zkoss.bind.defaultComposer.class default auto-applied BindComposer]:
+
We can change the [https://www.zkoss.org/wiki/ZK_Configuration_Reference/zk.xml/The_Library_Properties/org.zkoss.bind.defaultComposer.class default auto-applied org.zkoss.bind.BindComposer]:
  
 
(in zk.xml)
 
(in zk.xml)
Line 52: Line 52:
 
<library-property>​
 
<library-property>​
 
   <name>org.zkoss.bind.defaultComposer.class</name>​
 
   <name>org.zkoss.bind.defaultComposer.class</name>​
   <value>org.zkoss.zephyrex.bind.ClientBindComposer</value>​
+
   <value>org.zkoss.clientbind.ClientBindComposer</value>​
 
</library-property>
 
</library-property>
 
</source>
 
</source>
Line 70: Line 70:
 
With client MVVM, the MVVM mechanism is now done at the client-side instead of the server-side, there are several limitations and differences compared to the server-side MVVM.
 
With client MVVM, the MVVM mechanism is now done at the client-side instead of the server-side, there are several limitations and differences compared to the server-side MVVM.
  
=== 1. EL expressions not supported ===
+
=== 1. EL expression is not supported ===
 
EL expression - ${expr} and EL3 are not supported. For Example the following expressions are not supported:
 
EL expression - ${expr} and EL3 are not supported. For Example the following expressions are not supported:
 
<source lang='xml'>
 
<source lang='xml'>
 
<label value="${expr}" />
 
<label value="${expr}" />
 
<label value="@load(('Hi, ' += vm.person.firstName += ' ' += vm.person.lastName))​" />
 
<label value="@load(('Hi, ' += vm.person.firstName += ' ' += vm.person.lastName))​" />
<label value="@load((vm.names.stream().filter(x -> x.contains(vm.filter)).toList())) />
+
<label value="@load((vm.names.stream().filter(x -> x.contains(vm.filter)).toList()))" />
 
</source>
 
</source>
  
Line 89: Line 89:
 
</source>
 
</source>
  
* [http://books.zkoss.org/zk-mvvm-book/9.5/advanced/wire_components.html @Wire]​ and [http://books.zkoss.org/zk-mvvm-book/9.5/syntax/selectorparam.html @SelectorParam] are not supported.
+
* [https://www.zkoss.org/wiki/ZK_Developer's_Reference/Event_Handling/Event_Listening @Listen], [http://books.zkoss.org/zk-mvvm-book/9.5/advanced/wire_components.html @Wire], [https://books.zkoss.org/zk-mvvm-book/9.5/advanced/wire_components.html @WireVariable]​ and [http://books.zkoss.org/zk-mvvm-book/9.5/syntax/selectorparam.html @SelectorParam] are not supported.
 +
 
 +
* You cannot use ​[http://books.zkoss.org/zk-mvvm-book/9.5/syntax/bindingparam.html @BindingParam] and [http://books.zkoss.org/zk-mvvm-book/9.5/syntax/contextparam.html @ContextParam] to get components.
 +
<source lang='java'>
 +
@Command
 +
public void commandA(@BindingParam Component otherComponent,
 +
                    @ContextParam(ContextType.COMPONENT) Component targetComponent​​) {
 +
    // do something here.
 +
}
 +
</source>
  
* You cannot use ​[http://books.zkoss.org/zk-mvvm-book/9.5/syntax/bindingparam.html @BindingParam] to get components.
+
Note that even though you cannot get the full component, you '''CAN''' still use @BindingParam to retrieve values and other "ContextType"s.
 +
<source lang='xml'>
 +
<button label="click it" onClick="@command('commandB', foo='something', bar='somethingelse')" />
 +
</source>
 
<source lang='java'>
 
<source lang='java'>
 
@Command
 
@Command
public void commandA(@BindingParam Component comp) {
+
public void commandB(@BindingParam("foo") String foo,
 +
                    @ContextParam(ContextType.PAGE) Page page,
 +
                    @ContextParam(ContextType.DESKTOP) Desktop desktop) {
 
     // do something here.
 
     // do something here.
 
}
 
}
Line 102: Line 116:
 
<source lang='java'>
 
<source lang='java'>
 
@Command
 
@Command
public void commandB(@ContextParam(ContextType.TRIGGER_EVENT) MouseEvent event, @ContextParam(ContextType.COMPONENT) Component component​​) {
+
public void commandB(@ContextParam(ContextType.TRIGGER_EVENT) MouseEvent event) {
     // event.getTarget() and component is null
+
     // event.getTarget() is null.
 +
    // event.getPageX()/event.getPageY()/.. are still available.
 
}
 
}
  
Line 112: Line 127:
 
</source>
 
</source>
  
=== 3. Deferred Binding no longer needed ===   
+
=== 3. Getter Method should be pure in View Model ===
 +
To write those data in the view model to the client side, client MVVM depends on the getter methods to retrieve data.
 +
 
 +
For example, the return value of a getter method should not be always a "new" object.
 +
 
 +
=== 4. Deferred Binding is no longer supported ===   
 
Deferred Binding is no longer supported. This feature is to avoid unnecessary AU requests (data updates). Since client MVVM does those bindings on the client-side, it doesn't require such an update.
 
Deferred Binding is no longer supported. This feature is to avoid unnecessary AU requests (data updates). Since client MVVM does those bindings on the client-side, it doesn't require such an update.
  
Line 121: Line 141:
 
</source>
 
</source>
  
=== 4. SmarNotifyChange always on ===
+
=== 5. SmartNotifyChange always on ===
 
SmartNotifyChange is always enabled in Client MVVM​, which means that the properties will only update (reload) when the value of the expression is changed.
 
SmartNotifyChange is always enabled in Client MVVM​, which means that the properties will only update (reload) when the value of the expression is changed.
  
=== 5. Conditional Binding works differently ===  
+
=== 6. Conditional Binding works differently ===  
 
Conditional Binding works differently when the command updates the value without doing "NotifyChange".
 
Conditional Binding works differently when the command updates the value without doing "NotifyChange".
 
For example:
 
For example:
Line 142: Line 162:
  
 
In client MVVM, the value will remain "123". If you intend to see "123changed" you will need to do NotifyChange.
 
In client MVVM, the value will remain "123". If you intend to see "123changed" you will need to do NotifyChange.
 +
 +
=== 7. AnnotateDataBinder and Calling Binder API are no longer supported ===
 +
AnnotateDataBinder is the old ZK binding in zkplus module.
 +
And the Binder API is for server MVVM.
 +
For example:
 +
<source lang='xml'>
 +
<window id="myWin" viewModel="@id('vm') @init('...')">
 +
    <button id="changeNameBtn" label="change name">
 +
<attribute name="onClick"><![CDATA[
 +
myWin$composer.getViewModel().setName("...");
 +
myWin$composer.getBinder().loadComponent(myWin, true);
 +
]]></attribute>
 +
</button>
 +
</window>
 +
</source>
 +
 +
Or using custom Binder
 +
<source lang='xml'>
 +
<window viewModel="..." binder="@id('mybinder') @init(vm.binder)">
 +
</window>
 +
</source>
 +
 +
=== 8. More Information ===
 +
For migration, you can use [https://blog.zkoss.org/2023/08/01/zk-10-preview-introducing-zk-client-mvvm-linter/ ZK Client MVVM Linter] to check your ZK MVVM project.
  
 
== Debugging Tips ==
 
== Debugging Tips ==

Latest revision as of 08:46, 19 October 2023

DocumentationSmall Talks2022MayZK10 Preview: Using the new and light Client MVVM
ZK10 Preview: Using the new and light Client MVVM

Author
James Chu, Engineer, Potix Corporation
Date
May 26, 2022 (last update Oct 19, 2023)
Version
10.0.0-Beta or later

Introduction

One of the major enhancements of ZK 10 is the Client MVVM. Without creating lots of binding objects on the server-side, client MVVM helps to reduce the memory footprint significantly. You can view the performance testing result in this article ZK10_Preview:_30x_Lighter_and_300x_Faster_with_Client_MVVM.

When we use ZK (server) MVVM, we start from a ZUL file and apply "BindComposer" to a component. It will create a binder, data bindings, and initialize ViewModel instances on the server-side. The binder is the kernel of MVVM and acts as a bridge to transfer data and events between the View and ViewModel. See more detail here.

ZK MVVM Mechanism


The new Client MVVM also starts from a ZUL and a ViewModel, and the binding information will be passed to the client-side with ZK widgets. When the widgets are created and rendered on the browser, the bindings will also be created at the same time.

ZK 10 Client MVVM Mechanism


As you can see, the binding now happens on the client-side. However, from an application developer's point of view, this change is transparent, and it is something ZK takes care of for you. You'll still be building the MVVM application in the same way, referencing our ZK MVVM guide, except for a few differences which we will cover in the following sections.

Now, let's take a look at how we can enable the Client MVVM.

Setting up Client MVVM

There are two ways to use the client MVVM, either use it against a specific ViewModel, or, set it globally. Either way, you have to first include the jar file and add the listener.

Step 1

Use ZK 10 EE-Eval freshly, and include the client-bind jar file.

Step 2

Add the listener for Client MVVM. (in zk.xml)

<listener><listener-class>org.zkoss.clientbind.BinderPropertiesRenderer</listener-class></listener>

Step 3

Option 1: Apply to a ViewModel

Apply "org.zkoss.zephyrex.bind.ClientBindComposer" in ZUL.

For example: (in ZUL File)

<div apply="org.zkoss.clientbind.ClientBindComposer" viewModel="..." >
    <!-- other components -->
</div>

Option 2: Set it globally

We can change the default auto-applied org.zkoss.bind.BindComposer:

(in zk.xml)

<library-property><name>org.zkoss.bind.defaultComposer.class</name><value>org.zkoss.clientbind.ClientBindComposer</value></library-property>

(in ZUL File)

<div viewModel="..." >
    <!-- other components -->
</div>

If you use the global method, the ClientBindComposer will be applied automatically. If you wish to use a different composer (ex. the traditional ZK MVVM) on a specific VM, you can specify the "apply" attribute on that VM with the corresponding composer, and the manually specified apply will have the priority.

Once it is correctly configured, we can start to enjoy the benefits of client MVVM!

Restrictions and Differences

With client MVVM, the MVVM mechanism is now done at the client-side instead of the server-side, there are several limitations and differences compared to the server-side MVVM.

1. EL expression is not supported

EL expression - ${expr} and EL3 are not supported. For Example the following expressions are not supported:

<label value="${expr}" />
<label value="@load(('Hi, ' += vm.person.firstName += ' ' += vm.person.lastName))​" />
<label value="@load((vm.names.stream().filter(x -> x.contains(vm.filter)).toList()))" />

2. Follow the MVVM pattern

The main feature of MVVM is to decouple the UI and non-UI code, which means components should be controlled in MVVM. Developers should avoid controlling components directly. Even though we have this guideline, there are cases where developers have been accessing the components directly, and they use to work in ZK MVVM.

But now, when you apply client MVVM, ZK will update all the binding evaluation results into widget properties. This means the server-side does not have the full and most up-to-date information. Therefore, you cannot, and should no longer access and control those child components as a server-side component. You have to stick to the MVVM pattern.

  • Traversing components, like using "self" and ".parent" are not supported.
<button onClick="@command('delete', index=self.parent.parent.index)"/>
@Command
public void commandA(@BindingParam Component otherComponent,
                     @ContextParam(ContextType.COMPONENT) Component targetComponent​​) {
    // do something here.
}

Note that even though you cannot get the full component, you CAN still use @BindingParam to retrieve values and other "ContextType"s.

<button label="click it" onClick="@command('commandB', foo='something', bar='somethingelse')" />
@Command
public void commandB(@BindingParam("foo") String foo,
                     @ContextParam(ContextType.PAGE) Page page,
                     @ContextParam(ContextType.DESKTOP) Desktop desktop) {
    // do something here.
}
  • Component related API might return null (no component on the server-side).
@Command
public void commandB(@ContextParam(ContextType.TRIGGER_EVENT) MouseEvent event) {
    // event.getTarget() is null.
    // event.getPageX()/event.getPageY()/.. are still available.
}

// the component is null
org.zkoss.bind.Converter#coerceToUi(B beanProp, C component, BindContext ctx);
org.zkoss.bind.Converter#coerceToBean(U compAttr, C component, BindContext ctx);

3. Getter Method should be pure in View Model

To write those data in the view model to the client side, client MVVM depends on the getter methods to retrieve data.

For example, the return value of a getter method should not be always a "new" object.

4. Deferred Binding is no longer supported

Deferred Binding is no longer supported. This feature is to avoid unnecessary AU requests (data updates). Since client MVVM does those bindings on the client-side, it doesn't require such an update.

<textbox value="@bind(vm.text1)">
    <custom-attributes org.zkoss.bind.event.deferPost="false"/>
</textbox>

5. SmartNotifyChange always on

SmartNotifyChange is always enabled in Client MVVM​, which means that the properties will only update (reload) when the value of the expression is changed.

6. Conditional Binding works differently

Conditional Binding works differently when the command updates the value without doing "NotifyChange". For example:

<label value="@load(vm.text, after='doChange')" />
<button label="Do Change" onClick="@command('doChange')" />
public String text = "123";
@Command
public void doChange() {
    this.text += "changed";
}

In ZK MVVM, the value of Label will be "123changed" after the button - "Do Change" is clicked.

In client MVVM, the value will remain "123". If you intend to see "123changed" you will need to do NotifyChange.

7. AnnotateDataBinder and Calling Binder API are no longer supported

AnnotateDataBinder is the old ZK binding in zkplus module. And the Binder API is for server MVVM. For example:

<window id="myWin" viewModel="@id('vm') @init('...')">
    <button id="changeNameBtn" label="change name">
		<attribute name="onClick"><![CDATA[
		myWin$composer.getViewModel().setName("...");
		myWin$composer.getBinder().loadComponent(myWin, true);
	]]></attribute>
	</button>
</window>

Or using custom Binder

<window viewModel="..." binder="@id('mybinder') @init(vm.binder)">
</window>

8. More Information

For migration, you can use ZK Client MVVM Linter to check your ZK MVVM project.

Debugging Tips

Unsupported/incompatible usages mentioned in the previous section will be reported as system logs during the application startup, remember to check the logs when running into issues using the client MVVM.

Upgrade Tips

  • Upgrading your ZK MVVM project to Client MVVM is straightforward if you do not run across any of the unsupported usages mentioned above.
  • Client MVVM can be applied to a specific VM if you are not ready to use it globally.
  • You may wish to update your code and remove the incompatibility if the performance/memory benefit client MVVM brings is important in your use case.
  • Client MVVM is a pluggable feature, if you are not ready to use Client MVVM, no worries, you can still upgrade to ZK 10 and use the existing ZK MVVM.

Summary

The new ZK 10's Client MVVM makes ZK application lighter and faster. In some in-house test cases where a large number of components are used, we are seeing 30 to 300 times improvements. We hope it also greatly improves the performance and usability of your project.

Try it out today and let us know what you think!

For more ZK 10 articles, visit here.


Comments



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