Chapter 5: Handling User Input"

From Documentation
m (correct highlight (via JWB))
 
(9 intermediate revisions by 2 users not shown)
Line 34: Line 34:
  
 
'''chapter5/profile-mvc.zul'''
 
'''chapter5/profile-mvc.zul'''
<source lang='xml' high='4, 5, 6, 14, 21, 22, 29, 30, 31, 37, 43,44,45, 59'>
+
<source lang='xml' highlight='4, 5, 6, 14, 21, 22, 29, 30, 37, 43,44,45, 59'>
  
 
<?link rel="stylesheet" type="text/css" href="/style.css"?>
 
<?link rel="stylesheet" type="text/css" href="/style.css"?>
Line 104: Line 104:
 
* Line 6: [[ZK%20Component%20Reference/Layouts/Vlayout| ''Vlayout'']] is a light-weight layout component which arranges child components vertically without splitter, align, and pack support.
 
* Line 6: [[ZK%20Component%20Reference/Layouts/Vlayout| ''Vlayout'']] is a light-weight layout component which arranges child components vertically without splitter, align, and pack support.
 
* Line 14: [[ZK_Component_Reference/Supplementary/Cell| ''Cell'']]  is used inside ''Row'', ''Hbox'', or ''Vbox'' for fully controlling style and layout.
 
* Line 14: [[ZK_Component_Reference/Supplementary/Cell| ''Cell'']]  is used inside ''Row'', ''Hbox'', or ''Vbox'' for fully controlling style and layout.
* Line 21, 22, 29, 30, 31, 37: Specify <tt>constraint</tt> attribute of an input component can activate input validation feature and we will discuss it in later section.  
+
* Line 21, 22, 29, 30, 37: Specify <code>constraint</code> attribute of an input component can activate input validation feature and we will discuss it in later section.  
 
* Line 43: Some components have multiple molds, and each mold has its own style.
 
* Line 43: Some components have multiple molds, and each mold has its own style.
* Line 44: ''Template'' component can create its child components repeatedly upon the data model of parent component, ''Listbox'', and doesn't has a corresponding visual widget itself. The <tt>name</tt> attribute is required and has to be "model" in default case.
+
* Line 44: ''Template'' component can create its child components repeatedly upon the data model of parent component, ''Listbox'', and doesn't has a corresponding visual widget itself. The <code>name</code> attribute is required and has to be "model" in default case.
* Line 45: The <tt>${each}</tt> is an implicit variable that you can use without declaration inside ''Template'', and it represents an object of the data model list for each iteration when rendering. We use this variable at component attributes to reference the object's property with dot notation. In our example, we just set it at a ''Listitem'''s label.
+
* Line 45: The <code>${each}</code> is an implicit variable that you can use without declaration inside ''Template'', and it represents an object of the data model list for each iteration when rendering. We use this variable at component attributes to reference the object's property with dot notation. In our example, we just set it at a ''Listitem'''s label.
 
* Line 59: [[ZK%20Component%20Reference/Layouts/Hlayout| ''Hlayout'']], like ''Vlayout'', but arranges child components horizontally.
 
* Line 59: [[ZK%20Component%20Reference/Layouts/Hlayout| ''Hlayout'']], like ''Vlayout'', but arranges child components horizontally.
  
 
== User Input Validation ==  
 
== User Input Validation ==  
We can specify the <tt>constraint</tt> attribute of an input component with [[ZK Component Reference/Base Components/InputElement#Validation| constraint rule]] to activate its input validation feature and the feature can work without writing any code in a controller. For example:
+
We can specify the <code>constraint</code> attribute of an input component with [[ZK Component Reference/Base Components/InputElement#Validation| constraint rule]] to activate its input validation feature and the feature can work without writing any code in a controller. For example:
  
 
<source lang="xml">
 
<source lang="xml">
Line 150: Line 150:
  
 
'''Initialize data model for a ''Listbox'' '''
 
'''Initialize data model for a ''Listbox'' '''
<source lang="java" high='10,11'>
+
<source lang="java" highlight='10,11'>
  
 
public class ProfileViewController extends SelectorComposer<Component>{
 
public class ProfileViewController extends SelectorComposer<Component>{
Line 171: Line 171:
 
}
 
}
 
</source>
 
</source>
* Line 10: Create a <tt>ListModelList</tt> object with a list of <tt>String</tt>
+
* Line 10: Create a <code>ListModelList</code> object with a list of <code>String</code>
* Line 11: Provide prepared data model object to the component by <tt>setModel()</tt>.
+
* Line 11: Provide prepared data model object to the component by <code>setModel()</code>.
  
  
 
When a user visits this page, we want profile data to already appear in the form and ready to be modified. Hence, we should initialize those input components in a controller by loading saved data to input components.
 
When a user visits this page, we want profile data to already appear in the form and ready to be modified. Hence, we should initialize those input components in a controller by loading saved data to input components.
  
<source lang="java" high='4,17, 28, 33, 42,43,44,45,46,48'>
+
<source lang="java" highlight='4,17, 28, 33, 42,43,44,45,46,48'>
  
 
public class ProfileViewController extends SelectorComposer<Component>{
 
public class ProfileViewController extends SelectorComposer<Component>{
Line 235: Line 235:
 
* Line 28: Load saved data to input components to initialize the View, so we should call it after initializing country list.
 
* Line 28: Load saved data to input components to initialize the View, so we should call it after initializing country list.
 
* Line 33: This method reloads the saved data from service classes to input components.
 
* Line 33: This method reloads the saved data from service classes to input components.
* Line 42~46: Push saved user data to components by <tt>setValue()</tt>.
+
* Line 42~46: Push saved user data to components by <code>setValue()</code>.
* Line 48: Use <tt>ListModelList.addToSelection()</tt> to control the ''Listbox'''s selection,
+
* Line 48: Use <code>ListModelList.addToSelection()</code> to control the ''Listbox'''s selection,
  
 
== Save & Reload Data ==
 
== Save & Reload Data ==
Line 246: Line 246:
  
  
In this section, we will demonstrate a more flexible way to define an event listener in a controller with <tt>@Listen</tt> annotation instead of calling <tt>addEventListener()</tt> method (mentioned in chapter 4).
+
In this section, we will demonstrate a more flexible way to define an event listener in a controller with <code>@Listen</code> annotation instead of calling <code>addEventListener()</code> method (mentioned in chapter 4).
  
An event listener method should be public, have a void return type, and have either no parameter or one parameter of the specific event type (corresponding to the event listened) with <tt>@Listen</tt> in a controller. You should specify event listening rule in the annotation's element value. Then ZK will "wire" the method to the specified components for specified events. ZK provides various wiring selectors to specify in the annotation, please refer to [[ZK Developer%27s Reference/MVC/Controller/Wire Event Listeners]].
+
An event listener method should be public, have a void return type, and have either no parameter or one parameter of the specific event type (corresponding to the event listened) with <code>@Listen</code> in a controller. You should specify event listening rule in the annotation's element value. Then ZK will "wire" the method to the specified components for specified events. ZK provides various wiring selectors to specify in the annotation, please refer to [[ZK Developer%27s Reference/MVC/Controller/Wire Event Listeners]].
  
 
'''Listen "Save" button's clicking'''
 
'''Listen "Save" button's clicking'''
<source lang='java' high='3'>
+
<source lang='java' highlight='3'>
 
public class ProfileViewController extends SelectorComposer<Component>{
 
public class ProfileViewController extends SelectorComposer<Component>{
  
Line 261: Line 261:
 
}
 
}
 
</source>
 
</source>
* Line 3: The <tt>@Listen</tt> will make <tt>doSaveProfile()</tt> be invoked when a user clicks a component (<tt>onClick</tt>) whose id is "saveProfile" (<tt>#saveProfile</tt>).
+
* Line 3: The <code>@Listen</code> will make <code>doSaveProfile()</code> be invoked when a user clicks a component (<code>onClick</code>) whose id is "saveProfile" (<code>#saveProfile</code>).
  
  
We can manipulate components to change the presentation in the event listener. In <tt>doSaveProfile()</tt>, we get user's input from input components and save the data to a <tt>User</tt> object. Then show the notification to the client.
+
We can manipulate components to change the presentation in the event listener. In <code>doSaveProfile()</code>, we get user's input from input components and save the data to a <code>User</code> object. Then show the notification to the client.
  
 
'''Handle "Save" button's clicking'''
 
'''Handle "Save" button's clicking'''
<source lang='java' high=' 7, 14, 19, 28'>
+
<source lang='java' highlight=' 7, 14, 19, 28'>
 
public class ProfileViewController extends SelectorComposer<Component>{
 
public class ProfileViewController extends SelectorComposer<Component>{
  
Line 300: Line 300:
 
}
 
}
 
</source>
 
</source>
* Line 7: In this chapter's example, <tt>UserCredential</tt> is pre-defined to "Anonymous". We will write a real case in chapter 8.
+
* Line 7: In this chapter's example, <code>UserCredential</code> is pre-defined to "Anonymous". We will write a real case in chapter 8.
* Line 14: Get users input by calling <tt>getValue()</tt>.  
+
* Line 14: Get users input by calling <code>getValue()</code>.  
 
* Line 19: Get a user's selection for a ''Listbox'' from its model object.
 
* Line 19: Get a user's selection for a ''Listbox'' from its model object.
 
* Line 28: Show a notification box which is the most easy way to show a message to users.
 
* Line 28: Show a notification box which is the most easy way to show a message to users.
  
  
To wire the event listener for "Reload" button's is similar as previous one, and it pushes saved user data to components using <tt>setValue()</tt>.
+
To wire the event listener for "Reload" button's is similar as previous one, and it pushes saved user data to components using <code>setValue()</code>.
  
<source lang="java" high='21'>
+
<source lang="java" highlight='21'>
 
public class ProfileViewController extends SelectorComposer<Component>{
 
public class ProfileViewController extends SelectorComposer<Component>{
  
Line 366: Line 366:
  
 
'''Extracted from chapter5/profile-mvvm-property.zul'''
 
'''Extracted from chapter5/profile-mvvm-property.zul'''
<source lang="xml" high='41'>
+
<source lang="xml" highlight='41'>
 
<?link rel="stylesheet" type="text/css" href="/style.css"?>
 
<?link rel="stylesheet" type="text/css" href="/style.css"?>
 
<window border="normal" hflex="1" vflex="1" contentStyle="overflow:auto">
 
<window border="normal" hflex="1" vflex="1" contentStyle="overflow:auto">
Line 385: Line 385:
 
<cell sclass="row-title">Full Name :</cell>
 
<cell sclass="row-title">Full Name :</cell>
 
<cell>
 
<cell>
<textbox constraint="no empty: Plean enter your full name"  
+
<textbox  
 +
constraint="no empty: Plean enter your full name"  
 
width="200px"/>
 
width="200px"/>
 
</cell>
 
</cell>
Line 414: Line 415:
 
<row>
 
<row>
 
<cell sclass="row-title">Bio :</cell>
 
<cell sclass="row-title">Bio :</cell>
<cell><textbox multiline="true" hflex="1" height="200px" />
+
<cell>
 +
<textbox  
 +
multiline="true" hflex="1" height="200px" />
 
</cell>
 
</cell>
 
</row>
 
</row>
Line 427: Line 430:
 
</window>
 
</window>
 
</source>
 
</source>
* Line 41: You might notice that there is no EL expression <tt>${each}</tt> as we will use data binding to access it.
+
* Line 41: You might notice that there is no EL expression <code>${each}</code> as we will use data binding to access it.
  
 
== Create a ViewModel ==
 
== Create a ViewModel ==
Line 436: Line 439:
  
 
'''Define properties in a ViewModel'''
 
'''Define properties in a ViewModel'''
<source lang="java" high='4, 5, 8, 10, 14, 18'>
+
<source lang="java" highlight='4, 5, 8, 10, 14, 18'>
  
 
public class ProfileViewModel implements Serializable{
 
public class ProfileViewModel implements Serializable{
Line 470: Line 473:
 
* Line 8, 10: We should define current user profile data and its getter method to be displayed in the zul.
 
* Line 8, 10: We should define current user profile data and its getter method to be displayed in the zul.
 
* Line 14: ViewModel exposes its data by getter methods, it doesn't have to define a corresponding member variable. Hence we can expose country list by getting from the service class.
 
* Line 14: ViewModel exposes its data by getter methods, it doesn't have to define a corresponding member variable. Hence we can expose country list by getting from the service class.
* Line 18:  There is a marker annotation <tt>@Init</tt> for a method which should be at most one in each ViewModel and ZK will invoke this method after instantiating a ViewModel class. We should perform initialization in it, e.g. get user credential to initialize <tt>currentUser</tt>.
+
* Line 18:  There is a marker annotation <code>@Init</code> for a method which should be at most one in each ViewModel and ZK will invoke this method after instantiating a ViewModel class. We should perform initialization in it, e.g. get user credential to initialize <code>currentUser</code>.
  
  
Line 478: Line 481:
 
ViewModel also contains View's behaviors which are implemented by methods. We call such a method "Command" of the ViewModel. These methods usually manipulate data in the ViewModel, for example deleting an item. The View's behaviors are usually triggered by events from the View. The Data binding mechanism also supports binding an event to a ViewModel's command. Firing the component's event will trigger the execution of bound command that means invoking the corresponding command method.
 
ViewModel also contains View's behaviors which are implemented by methods. We call such a method "Command" of the ViewModel. These methods usually manipulate data in the ViewModel, for example deleting an item. The View's behaviors are usually triggered by events from the View. The Data binding mechanism also supports binding an event to a ViewModel's command. Firing the component's event will trigger the execution of bound command that means invoking the corresponding command method.
  
For ZK to recognize a command method in a ViewModel, you should apply annotation <tt>@Command</tt> to a command method. You could specify a command name which is the method's name by default if no specified. Our example has two behavior: "save" and "reload", so we define two command methods for each of them:
+
For ZK to recognize a command method in a ViewModel, you should apply annotation <code>@Command</code> to a command method. You could specify a command name which is the method's name by default if no specified. Our example has two behavior: "save" and "reload", so we define two command methods for each of them:
  
 
'''Define commands in a ViewModel'''  
 
'''Define commands in a ViewModel'''  
<source lang='java' high='4,5, 10'>
+
<source lang='java' highlight='4,5, 10'>
  
 
public class ProfileViewModel implements Serializable{
 
public class ProfileViewModel implements Serializable{
Line 499: Line 502:
 
}
 
}
 
</source>
 
</source>
* Line 4, 10: Annotate a method with <tt>@Command</tt> to make it become a command method, and it can be bound with data binding in a zul.
+
* Line 4, 10: Annotate a method with <code>@Command</code> to make it become a command method, and it can be bound with data binding in a zul.
* Line 5: Method name is the default command name if you don't specify in <tt>@Command</tt>. This method save the <tt>currentUser</tt> with a service class and show a notification.
+
* Line 5: Method name is the default command name if you don't specify in <code>@Command</code>. This method save the <code>currentUser</code> with a service class and show a notification.
  
  
Line 509: Line 512:
 
One property:
 
One property:
  
''' <tt> @NotifyChange("oneProperty") </tt>
+
''' <code> @NotifyChange("oneProperty") </code>
  
 
Multiple properties:
 
Multiple properties:
  
''' <tt> @NotifyChange({"property01","property02"}) </tt>
+
''' <code> @NotifyChange({"property01","property02"}) </code>
  
 
All properties in a ViewModel:
 
All properties in a ViewModel:
  
''' <tt> @NotifyChange("*") </tt>
+
''' <code> @NotifyChange("*") </code>
  
  
 
'''Define notification & commands in a ViewModel'''  
 
'''Define notification & commands in a ViewModel'''  
<source lang='java' high='5,12'>
+
<source lang='java' highlight='5,12'>
  
 
public class ProfileViewModel implements Serializable{
 
public class ProfileViewModel implements Serializable{
Line 541: Line 544:
 
}
 
}
 
</source>
 
</source>
* Line 5, 12: Notify which property change with <tt>@NotifyChange</tt> and zK will reload those attributes that are bound to <tt>currentUser</tt>.
+
* Line 5, 12: Notify which property change with <code>@NotifyChange</code> and zK will reload those attributes that are bound to <code>currentUser</code>.
  
 
=== Apply a ViewModel on a Component===
 
=== Apply a ViewModel on a Component===
  
Before data binding can work, we must apply a composer called '''org.zkoss.bind.BindComposer'''. It will create a '''binder''' for the ViewModel and instantiate the ViewModel's class. Then we should bind a ZK component to our ViewModel by setting its <tt>'''viewModel'''</tt> attribute with the ViewModel's id in <tt> @id </tt> and the ViewModel's full-qualified class name in <tt> @init </tt>.  The id is used to reference the ViewModel's properties, e.g. <tt>vm.name</tt>, whilst the full-qualified class name is used to instantiate the ViewModel object itself. So that component becomes the ''Root View Component'' for the ViewModel. All child components of this Root View Component can be bound to the same ViewModel and its properties, so we usually bind the root component of a page to a ViewModel.   
+
Before data binding can work, we must apply a composer called '''org.zkoss.bind.BindComposer'''. It will create a '''binder''' for the ViewModel and instantiate the ViewModel's class. Then we should bind a ZK component to our ViewModel by setting its <code>'''viewModel'''</code> attribute with the ViewModel's id in <code> @id </code> and the ViewModel's full-qualified class name in <code> @init </code>.  The id is used to reference the ViewModel's properties, e.g. <code>vm.name</code>, whilst the full-qualified class name is used to instantiate the ViewModel object itself. So that component becomes the ''Root View Component'' for the ViewModel. All child components of this Root View Component can be bound to the same ViewModel and its properties, so we usually bind the root component of a page to a ViewModel.   
  
<source lang='xml' high='1,2'>
+
<source lang='xml' highlight='1,2'>
  
 
<window apply="org.zkoss.bind.BindComposer"
 
<window apply="org.zkoss.bind.BindComposer"
Line 556: Line 559:
 
</source>
 
</source>
 
* Line 1: Under MVVM approach, the composer we apply is fixed '''org.zkoss.bind.BindComposer'''.
 
* Line 1: Under MVVM approach, the composer we apply is fixed '''org.zkoss.bind.BindComposer'''.
* Line 2: Specify ViewModel's id with <tt> @id </tt> and the its full-qualified class name in <tt> @init </tt> for the binder.
+
* Line 2: Specify ViewModel's id with <code> @id </code> and the its full-qualified class name in <code> @init </code> for the binder.
  
 
== Data Binding to ViewModel's Properties ==
 
== Data Binding to ViewModel's Properties ==
Line 564: Line 567:
 
[[File:Mvvm-databinding-role.png | center | 600px]]
 
[[File:Mvvm-databinding-role.png | center | 600px]]
  
Let's demonstrate how to make ''Listbox'' load a list of country name from the ViewModel. We have talked about the [[Tutorial/Chapter5:Handling User_Input#Initialize Profile Form| data model concept]]  in MVC approach section, and we also need to prepare a model object that are defined in one of our ViewModel's properties, <tt>countryList</tt>. You might find  <tt>getCountryList()</tt> return a <tt>List</tt> instead of a <tt>ListModelList</tt>, but don't worry. ZK will convert it automatically. We use <tt>@load</tt> to load a ViewModel's property to a component's attribute and <tt>@save</tt> to save an attribute value into a ViewModel's property (usually for an input component). If both loading and saving are required, we could use <tt>@bind</tt>.
+
Let's demonstrate how to make ''Listbox'' load a list of country name from the ViewModel. We have talked about the [[Tutorial/Chapter5:Handling User_Input#Initialize Profile Form| data model concept]]  in MVC approach section, and we also need to prepare a model object that are defined in one of our ViewModel's properties, <code>countryList</code>. You might find  <code>getCountryList()</code> return a <code>List</code> instead of a <code>ListModelList</code>, but don't worry. ZK will convert it automatically. We use <code>@load</code> to load a ViewModel's property to a component's attribute and <code>@save</code> to save an attribute value into a ViewModel's property (usually for an input component). If both loading and saving are required, we could use <code>@bind</code>.
  
<source lang="xml" high='3,4, 5'>
+
<source lang="xml" highlight='3,4, 5'>
 
...
 
...
<cell>
+
<cell>
<listbox model="@load(vm.countryList)" mold="select" width="200px">
+
<listbox model="@load(vm.countryList)" mold="select" width="200px">
<template name="model">
+
<template name="model">
<listitem label="@load(each)" />
+
<listitem label="@load(each)" />
</template>
+
</template>
</listbox>
+
</listbox>
</cell>
+
</cell>
 
...
 
...
 
</source>
 
</source>
* Line 3: We setup a load binding with <tt>@load</tt>. The <tt>vm</tt> is the ViewModel's id we specified at <tt>@id</tt> in previous section and the target property (<tt>countryList</tt>) can be referenced in dot notation.  
+
* Line 3: We setup a load binding with <code>@load</code>. The <code>vm</code> is the ViewModel's id we specified at <code>@id</code> in previous section and the target property (<code>countryList</code>) can be referenced in dot notation.  
 
* Line 4: ''Template'' component, we have explained in MVC approach section, can create its child components repeatedly upon the data model of parent component.
 
* Line 4: ''Template'' component, we have explained in MVC approach section, can create its child components repeatedly upon the data model of parent component.
* Line 5: The implicit variable <tt>each</tt> which you can use without declaration inside ''Template'' represents each object in the data model for each iterative rendering (It represents String object of a country name in this example). We use this variable to access objects of data model. In our example, we just make it as a ''Listitem'''s label.
+
* Line 5: The implicit variable <code>each</code> which you can use without declaration inside ''Template'' represents each object in the data model for each iterative rendering (It represents String object of a country name in this example). We use this variable to access objects of data model. In our example, we just make it as a ''Listitem'''s label.
  
  
In MVC approach, we have to call an input component's getter method (e.g. <tt>getValue()</tt> ) to collect user input. But in MVVM approach, ZK will save user input back to a ViewModel automatically. For example in the below zul, user input is saved automatically when you move the focus out of the ''Textbox''.
+
In MVC approach, we have to call an input component's getter method (e.g. <code>getValue()</code> ) to collect user input. But in MVVM approach, ZK will save user input back to a ViewModel automatically. For example in the below zul, user input is saved automatically when you move the focus out of the ''Textbox''.
  
 
<source lang='xml'>
 
<source lang='xml'>
<textbox value="@bind(vm.currentUser.fullName)" constraint="no empty: Plean enter your full name" width="200px"/>
+
<textbox value="@bind(vm.currentUser.fullName)"  
 +
constraint="no empty: Plean enter your full name" width="200px"/>
 
</source>
 
</source>
  
For the property <tt>currentUser</tt>, we want to both save user input back to the ViewModel and load value from the ViewModel, so we should use the <tt>@bind</tt> at <tt>value</tt> attribute. Notice that you can bind <tt>selectedItem</tt> to a property, then the user's selection can be saved automatically to the ViewModel.
+
For the property <code>currentUser</code>, we want to both save user input back to the ViewModel and load value from the ViewModel, so we should use the <code>@bind</code> at <code>value</code> attribute. Notice that you can bind <code>selectedItem</code> to a property, then the user's selection can be saved automatically to the ViewModel.
  
<source lang='xml' high='12,18, 24, 30, 40'>
+
<source lang='xml' highlight='12, 20, 28, 36, 47'>
 
...
 
...
 
<rows>
 
<rows>
Line 602: Line 606:
 
<cell sclass="row-title">Full Name :</cell>
 
<cell sclass="row-title">Full Name :</cell>
 
<cell>
 
<cell>
<textbox value="@bind(vm.currentUser.fullName)" constraint="no empty: Plean enter your full name" width="200px"/>
+
<textbox value="@bind(vm.currentUser.fullName)"  
 +
constraint="no empty: Plean enter your full name"
 +
width="200px"/>
 
</cell>
 
</cell>
 
</row>
 
</row>
Line 608: Line 614:
 
<cell sclass="row-title">Email :</cell>
 
<cell sclass="row-title">Email :</cell>
 
<cell>
 
<cell>
<textbox value="@bind(vm.currentUser.email)" constraint="/.+@.+\.[a-z]+/: Please enter an e-mail address" width="200px"/>
+
<textbox value="@bind(vm.currentUser.email)"  
 +
constraint="/.+@.+\.[a-z]+/: Please enter an e-mail address"  
 +
width="200px"/>
 
</cell>
 
</cell>
 
</row>
 
</row>
Line 614: Line 622:
 
<cell sclass="row-title">Birthday :</cell>
 
<cell sclass="row-title">Birthday :</cell>
 
<cell>
 
<cell>
<datebox value="@bind(vm.currentUser.birthday)" constraint="no future" width="200px"/>
+
<datebox value="@bind(vm.currentUser.birthday)"
 +
constraint="no future" width="200px"/>
 
</cell>
 
</cell>
 
</row>
 
</row>
Line 620: Line 629:
 
<cell sclass="row-title">Country :</cell>
 
<cell sclass="row-title">Country :</cell>
 
<cell>
 
<cell>
<listbox model="@load(vm.countryList)" selectedItem="@bind(vm.currentUser.country)" mold="select" width="200px">
+
<listbox model="@load(vm.countryList)"  
 +
selectedItem="@bind(vm.currentUser.country)"  
 +
mold="select" width="200px">
 
<template name="model">
 
<template name="model">
 
<listitem label="@load(each)" />
 
<listitem label="@load(each)" />
Line 630: Line 641:
 
<cell sclass="row-title">Bio :</cell>
 
<cell sclass="row-title">Bio :</cell>
 
<cell>
 
<cell>
<textbox value="@bind(vm.currentUser.bio)" multiline="true" hflex="1" height="200px" />
+
<textbox value="@bind(vm.currentUser.bio)"  
 +
multiline="true" hflex="1" height="200px" />
 
</cell>
 
</cell>
 
</row>
 
</row>
Line 637: Line 649:
 
...
 
...
 
</source>
 
</source>
* Line 12,18,24,40: Use <tt>@bind</tt> to save user input back to the ViewModel and load value from the ViewModel.
+
* Line 12, 20, 28, 47: Use <code>@bind</code> to save user input back to the ViewModel and load value from the ViewModel.
* Line 30: Bind <tt>selectedItem</tt> to <tt>vm.currentUser.country</tt> and the selected country will be saved to <tt>currentUser</tt>.
+
* Line 36: Bind <code>selectedItem</code> to <code>vm.currentUser.country</code> and the selected country will be saved to <code>currentUser</code>.
  
 
== Handle User Interactions by Command Binding ==
 
== Handle User Interactions by Command Binding ==
  
After we finish binding attributes to the ViewModel's data, we still need to handle user actions, button clicking. Under the MVVM approach, we handle events by binding an event attribute (e.g. <tt>onClick</tt>) to a '''Command''' of a ViewModel. After we bind an event to a Command, each time the event is sent, ZK will invoke the corresponding command method. Hence, we should write our business logic in a command method. After executing the command method, some properties might be changed. We should tell ZK which properties are changed by us, then the binder will reload them to components.  
+
After we finish binding attributes to the ViewModel's data, we still need to handle user actions, button clicking. Under the MVVM approach, we handle events by binding an event attribute (e.g. <code>onClick</code>) to a '''Command''' of a ViewModel. After we bind an event to a Command, each time the event is sent, ZK will invoke the corresponding command method. Hence, we should write our business logic in a command method. After executing the command method, some properties might be changed. We should tell ZK which properties are changed by us, then the binder will reload them to components.  
  
When creating the <tt>ProfileViewModel</tt> in the previous section, we have defined two commands: <tt>save</tt> and <tt>reload</tt>.
+
When creating the <code>ProfileViewModel</code> in the previous section, we have defined two commands: <code>save</code> and <code>reload</code>.
  
Then, we can bind <tt>onClick</tt> event to above commands with command binding <tt>@command('commandName')</tt> as follows:
+
Then, we can bind <code>onClick</code> event to above commands with command binding <code>@command('commandName')</code> as follows:
  
<source lang='xml' high='4'>
+
<source lang='xml' highlight='4'>
 
...
 
...
 
<hlayout>
 
<hlayout>
Line 662: Line 674:
  
  
Once you create a property binding with <tt>@bind</tt> for an input component, ZK will save user input back to a ViewModel automatically. But sometimes this automation is not what users want. In our example, most people usually expect <tt>currentUser</tt> to change after their confirmation for example, clicking a button.  
+
Once you create a property binding with <code>@bind</code> for an input component, ZK will save user input back to a ViewModel automatically. But sometimes this automation is not what users want. In our example, most people usually expect <code>currentUser</code> to change after their confirmation for example, clicking a button.  
  
 
There is a line of text "You are editing an Anonymous's profile" at the bottom of the form. If you change the full name to "Anonymous Somebody" and move to next field, the line of text is changed even you don't press the "Save" button. This could be a problem maybe it would mislead users, making them think they have changed their profile, so we don't want this.
 
There is a line of text "You are editing an Anonymous's profile" at the bottom of the form. If you change the full name to "Anonymous Somebody" and move to next field, the line of text is changed even you don't press the "Save" button. This could be a problem maybe it would mislead users, making them think they have changed their profile, so we don't want this.
Line 674: Line 686:
  
 
Steps to use a form binding:
 
Steps to use a form binding:
# Give an id to middle object in <tt>'''form'''</tt> attribute with <tt> @id </tt>.  
+
# Give an id to middle object in <code>'''form'''</code> attribute with <code> @id </code>.  
#: Then you can reference the middle object in ZK bind expression with its id, e.g. <tt>@id('fx')</tt>.
+
#: Then you can reference the middle object in ZK bind expression with its id, e.g. <code>@id('fx')</code>.
# Specify ViewModel's property to be loaded with <tt> @load </tt>
+
# Specify ViewModel's property to be loaded with <code> @load </code>
# Specify ViewModel's property to save and before which Command with <tt> @save </tt>
+
# Specify ViewModel's property to save and before which Command with <code> @save </code>
 
#: This means binder will save the middle object's properties to ViewModel before a command execution.
 
#: This means binder will save the middle object's properties to ViewModel before a command execution.
 
# Bind component's attribute to the middle object's properties like you do in property binding.
 
# Bind component's attribute to the middle object's properties like you do in property binding.
#: You should use middle object's id specified in <tt> @id </tt> to reference its property, e.g. <tt>@load(fx.account)</tt>.
+
#: You should use middle object's id specified in <code> @id </code> to reference its property, e.g. <code>@load(fx.account)</code>.
  
 
'''extracted from chapter5/profile-mvvm.zul'''
 
'''extracted from chapter5/profile-mvvm.zul'''
<source lang='xml' high='2,7,12, 19, 26,34, 43,47'>
+
<source lang='xml' highlight='2, 3, 8, 13, 20, 27, 36, 45, 51, 52, 53'>
 
...
 
...
<grid width="500px" form="@id('fx') @load(vm.currentUser) @save(vm.currentUser, before='save')">
+
<grid width="500px"  
 +
form="@id('fx')@load(vm.currentUser)@save(vm.currentUser, before='save')">
 
...
 
...
 
<rows>
 
<rows>
Line 696: Line 709:
 
<cell>
 
<cell>
 
<textbox value="@bind(fx.fullName)" width="200px"
 
<textbox value="@bind(fx.fullName)" width="200px"
constraint="no empty: Plean enter your full name" />
+
constraint="no empty: Plean enter your full name"/>
 
</cell>
 
</cell>
 
</row>
 
</row>
Line 703: Line 716:
 
<cell>
 
<cell>
 
<textbox value="@bind(fx.email)" width="200px"
 
<textbox value="@bind(fx.email)" width="200px"
constraint="/.+@.+\.[a-z]+/: Please enter an e-mail address" />
+
constraint="/.+@.+\.[a-z]+/: Please enter an e-mail address"/>
 
</cell>
 
</cell>
 
</row>
 
</row>
Line 716: Line 729:
 
<cell sclass="row-title">Country :</cell>
 
<cell sclass="row-title">Country :</cell>
 
<cell>
 
<cell>
<listbox model="@load(vm.countryList)" mold="select" width="200px"
+
<listbox model="@load(vm.countryList)"  
selectedItem="@bind(fx.country)">
+
mold="select" width="200px"
 +
selectedItem="@bind(fx.country)">
 
<template name="model">
 
<template name="model">
<listitem label="@load(each)" />
+
<listitem label="@load(each)"/>
 
</template>
 
</template>
 
</listbox>
 
</listbox>
Line 726: Line 740:
 
<row>
 
<row>
 
<cell sclass="row-title">Bio :</cell>
 
<cell sclass="row-title">Bio :</cell>
<cell><textbox value="@bind(fx.bio)" multiline="true" hflex="1" height="200px" /></cell>
+
<cell><textbox value="@bind(fx.bio)" multiline="true"  
 +
hflex="1" height="200px" />
 +
</cell>
 
</row>
 
</row>
 
</rows>
 
</rows>
 
</grid>
 
</grid>
<div>You are editing <label value="@load(vm.currentUser.fullName)"/>'s profile.</div>
+
<div>You are editing  
 +
<label value="@load(vm.currentUser.fullName)"/>'s profile.
 +
</div>
 
...
 
...
 
</source>
 
</source>
* Line 2: Define a form binding at <tt>form</tt> attribute and give the middle object's id <tt>fx</tt>. Specify <tt>@load(vm.currentUser)</tt> makes the binder load <tt>currentUser</tt>'s properties to the middle object and <tt>@save(vm.currentUser, before='save')</tt> makes the binder save middle object's data back to <tt>vm.currentUser</tt> before executing the command <tt>save</tt>.
+
* Line 2, 3: Define a form binding at <code>form</code> attribute and give the middle object's id <code>fx</code>. Specify <code>@load(vm.currentUser)</code> makes the binder load <code>currentUser</code>'s properties to the middle object and <code>@save(vm.currentUser, before='save')</code> makes the binder save middle object's data back to <code>vm.currentUser</code> before executing the command <code>save</code>.
* Line 7,12, 19, 26,33, 43: We should bind attributes to middle object's properties to avoid altering ViewModel's properties.
+
* Line 8, 13, 20, 27, 36, 45: We should bind attributes to middle object's properties to avoid altering ViewModel's properties.
* LIne 47: The label bound to <tt>vm.currentUser.fullName</tt> is not affected when <tt>fx</tt> is changed.
+
* LIne 51, 52, 53: The label bound to <code>vm.currentUser.fullName</code> is not affected when <code>fx</code> is changed.
  
  
After applying form binding, any user's input will not actually change <tt>currentUser</tt>'s value and they are stored in the middle object until you click the "Save" button, ZK puts the middle object's data back to the ViewModel's properties (<tt>currentUser</tt>).
+
After applying form binding, any user's input will not actually change <code>currentUser</code>'s value and they are stored in the middle object until you click the "Save" button, ZK puts the middle object's data back to the ViewModel's properties (<code>currentUser</code>).
  
 
[[File:Tutorial-ch5-form-binding.png | center | 300px]]
 
[[File:Tutorial-ch5-form-binding.png | center | 300px]]
Line 746: Line 764:
  
 
After completing above steps, visit http://localhost:8080/essentials/chapter5/index-mvvm.zul to see the result.
 
After completing above steps, visit http://localhost:8080/essentials/chapter5/index-mvvm.zul to see the result.
 
  
 
= Source Code =
 
= Source Code =

Latest revision as of 10:52, 19 January 2022

Stop.png This article is out of date, please refer to http://books.zkoss.org/zkessentials-book/master/ for more up to date information.



Target Application

This chapter we will demonstrate a common scenario: collecting user input in form style page. The target application looks as follows:

Tutorial-ch5-app.png

It is a personal profile form with 5 different fields. Clicking the "Save" button saves the user's data and clicking the "Reload" button loads previous saved data back into the form.

Starting from this chapter, we will show how to implement an example application using both the MVC and MVVM approaches. If you are not familiar with these two approaches, we suggest that you read Get ZK Up and Running with MVC and Get ZK Up and Running with MVVM. These two approaches are mutually interchangeable. You can choose one of them depending on your situation. Please refer to Approach Comparison.

MVC Approach

Under this approach, we implement all event handling and presentation logic in a controller with no code present in the ZUL file. This approach makes the responsibility of each role (Model, View, and Controller) more cohesive and allows you to control components directly. It is very intuitive and very flexible.

Construct a Form Style Page

With the concept and technique we talked about in last chapter, it should be easy to construct a form style user interface as follows. It uses a two-column Grid to build the form style layout and different input components to receive user's profile like name and birthday. The zul file below is included in the Center of the Border Layout.

chapter5/profile-mvc.zul

<?link rel="stylesheet" type="text/css" href="/style.css"?>
<window apply="org.zkoss.essentials.chapter5.mvc.ProfileViewController" 
	border="normal" hflex="1" vflex="1" contentStyle="overflow:auto">
	<caption src="/imgs/profile.png" sclass="fn-caption" 
		label="Profile (MVC)"/>
	<vlayout>
		<grid width="500px">
			<columns>
				<column align="right" hflex="min"/>
				<column/>
			</columns>
			<rows>
				<row>
					<cell sclass="row-title">Account :</cell>
					<cell><label id="account"/></cell>
				</row>
				<row>
					<cell sclass="row-title">Full Name :</cell>
					<cell>
					<textbox id="fullName" 
					constraint="no empty: Please enter your full name" 
					width="200px"/>
					</cell>
				</row>
				<row>
					<cell sclass="row-title">Email :</cell>
					<cell>
					<textbox id="email" 
					constraint="/.+@.+\.[a-z]+/: Please enter an e-mail address" 
					width="200px"/>
					</cell>
				</row>
				<row>
					<cell sclass="row-title">Birthday :</cell>
					<cell>
						<datebox id="birthday" 
						constraint="no future" width="200px"/>
					</cell>
				</row>
				<row>
					<cell sclass="row-title">Country :</cell>
					<cell>
						<listbox id="country" mold="select" width="200px">
							<template name="model">
								<listitem label="${each}" />
							</template>
						</listbox>
					</cell>
				</row>
				<row>
					<cell sclass="row-title">Bio :</cell>
					<cell><textbox id="bio" multiline="true" 
						hflex="1" height="200px" />
					</cell>
				</row>	
			</rows>
		</grid>
		<div>You are editing <label id="nameLabel"/>'s profile.</div>
		<hlayout>
			<button id="saveProfile" label="Save"/>
			<button id="reloadProfile" label="Reload"/>
		</hlayout>
	</vlayout>
</window>
  • Line 4, 5: Caption can be used to build compound header with an image for a Window.
  • Line 6: Vlayout is a light-weight layout component which arranges child components vertically without splitter, align, and pack support.
  • Line 14: Cell is used inside Row, Hbox, or Vbox for fully controlling style and layout.
  • Line 21, 22, 29, 30, 37: Specify constraint attribute of an input component can activate input validation feature and we will discuss it in later section.
  • Line 43: Some components have multiple molds, and each mold has its own style.
  • Line 44: Template component can create its child components repeatedly upon the data model of parent component, Listbox, and doesn't has a corresponding visual widget itself. The name attribute is required and has to be "model" in default case.
  • Line 45: The ${each} is an implicit variable that you can use without declaration inside Template, and it represents an object of the data model list for each iteration when rendering. We use this variable at component attributes to reference the object's property with dot notation. In our example, we just set it at a Listitem's label.
  • Line 59: Hlayout, like Vlayout, but arranges child components horizontally.

User Input Validation

We can specify the constraint attribute of an input component with constraint rule to activate its input validation feature and the feature can work without writing any code in a controller. For example:

	<textbox id="fullName" constraint="no empty: Plean enter your full name" 
		     width="200px"/>
  • The constraint rule means "no empty value allowed" for the Textbox. If the user input violates this rule, ZK will show the message after a colon.
	<textbox id="email" 
			 constraint="/.+@.+\.[a-z]+/: Please enter an e-mail address" 
			 width="200px"/>
  • We can also define a constraint rule using a regular expression that describes the email format to limit the value in correct format.
<datebox id="birthday" constraint="no future" width="200px"/>
  • The constraint rule means "date in the future is not allowed" and it also restricts the available date to choose.


Then, the input component will show the specified error message when an input value violates a specified constraint rule.


Tutorial-ch5-email-constraint.png

Initialize Profile Form

We want to create a drop-down list that contains a list of countries for selection. When a user visit the page, the data in drop-down list should be ready. To achieve this, we have to initialize a drop-down list in the controller.

Tutorial-ch5-collection.png
Country List

This is made using a Listbox in "select" mold. The Listbox's data is a list of country name strings provided by the controller. In ZK, all data components are designed to accept a separate data model that contains data to be rendered. You only have to provide such a data model and a data component will render the information as specified in the Template. This increases the data model's re-usability and decouples the data from a component's implementation.

For a Listbox, we can provide a ListModelList object.

Initialize data model for a Listbox

public class ProfileViewController extends SelectorComposer<Component>{
	...
	@Wire
	Listbox country;

	@Override
	public void doAfterCompose(Component comp) throws Exception{
		super.doAfterCompose(comp);
		
		ListModelList<String> countryModel = new ListModelList<String>(CommonInfoService.getCountryList());
		country.setModel(countryModel);
		
		...
	}

	...

}
  • Line 10: Create a ListModelList object with a list of String
  • Line 11: Provide prepared data model object to the component by setModel().


When a user visits this page, we want profile data to already appear in the form and ready to be modified. Hence, we should initialize those input components in a controller by loading saved data to input components.

public class ProfileViewController extends SelectorComposer<Component>{

	//wire components
	@Wire
	Label account;
	@Wire
	Textbox fullName;
	@Wire
	Textbox email;
	@Wire
	Datebox birthday;
	@Wire
	Listbox country;
	@Wire
	Textbox bio;
	
	//services
	AuthenticationService authService = new AuthenticationServiceChapter5Impl();
	UserInfoService userInfoService = new UserInfoServiceChapter5Impl();

	@Override
	public void doAfterCompose(Component comp) throws Exception{
		super.doAfterCompose(comp);
		
		ListModelList<String> countryModel = new ListModelList<String>(CommonInfoService.getCountryList());
		country.setModel(countryModel);
		
		refreshProfileView();
	}

	...

	private void refreshProfileView() {
		UserCredential cre = authService.getUserCredential();
		User user = userInfoService.findUser(cre.getAccount());
		if(user==null){
			//TODO handle un-authenticated access 
			return;
		}
		
		//apply bean value to UI components
		account.setValue(user.getAccount());
		fullName.setValue(user.getFullName());
		email.setValue(user.getEmail());
		birthday.setValue(user.getBirthday());
		bio.setValue(user.getBio());
		
		((ListModelList)country.getModel()).addToSelection(user.getCountry());
		...
	}
}
  • Line 4: Wire ZK components as we talked in chapter 4.
  • Line 17: Service classes that are used to perform business operations or get necessary data.
  • Line 28: Load saved data to input components to initialize the View, so we should call it after initializing country list.
  • Line 33: This method reloads the saved data from service classes to input components.
  • Line 42~46: Push saved user data to components by setValue().
  • Line 48: Use ListModelList.addToSelection() to control the Listbox's selection,

Save & Reload Data

The example application has 2 functions, save and reload, which are both triggered by clicking a button. If you click the "Save" button, the application will save your input and show a notification box.

Tutorial-ch5-save.png
Click "Save" button


In this section, we will demonstrate a more flexible way to define an event listener in a controller with @Listen annotation instead of calling addEventListener() method (mentioned in chapter 4).

An event listener method should be public, have a void return type, and have either no parameter or one parameter of the specific event type (corresponding to the event listened) with @Listen in a controller. You should specify event listening rule in the annotation's element value. Then ZK will "wire" the method to the specified components for specified events. ZK provides various wiring selectors to specify in the annotation, please refer to ZK Developer's Reference/MVC/Controller/Wire Event Listeners.

Listen "Save" button's clicking

public class ProfileViewController extends SelectorComposer<Component>{

	@Listen("onClick=#saveProfile")
	public void doSaveProfile(){
		...
	}
	...
}
  • Line 3: The @Listen will make doSaveProfile() be invoked when a user clicks a component (onClick) whose id is "saveProfile" (#saveProfile).


We can manipulate components to change the presentation in the event listener. In doSaveProfile(), we get user's input from input components and save the data to a User object. Then show the notification to the client.

Handle "Save" button's clicking

public class ProfileViewController extends SelectorComposer<Component>{


	@Listen("onClick=#saveProfile")
	public void doSaveProfile(){
		UserCredential cre = authService.getUserCredential();
		User user = userInfoService.findUser(cre.getAccount());
		if(user==null){
			//TODO handle un-authenticated access 
			return;
		}
		
		//apply component value to bean
		user.setFullName(fullName.getValue());
		user.setEmail(email.getValue());
		user.setBirthday(birthday.getValue());
		user.setBio(bio.getValue());
		
		Set<String> selection = ((ListModelList)country.getModel()).getSelection();
		if(!selection.isEmpty()){
			user.setCountry(selection.iterator().next());
		}else{
			user.setCountry(null);
		}
		
		userInfoService.updateUser(user);
		
		Clients.showNotification("Your profile is updated");
	}
	...
}
  • Line 7: In this chapter's example, UserCredential is pre-defined to "Anonymous". We will write a real case in chapter 8.
  • Line 14: Get users input by calling getValue().
  • Line 19: Get a user's selection for a Listbox from its model object.
  • Line 28: Show a notification box which is the most easy way to show a message to users.


To wire the event listener for "Reload" button's is similar as previous one, and it pushes saved user data to components using setValue().

public class ProfileViewController extends SelectorComposer<Component>{


	//wire components
	@Wire
	Label account;
	@Wire
	Textbox fullName;
	@Wire
	Textbox email;
	@Wire
	Datebox birthday;
	@Wire
	Listbox country;
	@Wire
	Textbox bio;

	...
	@Listen("onClick=#reloadProfile")
	public void doReloadProfile(){
		refreshProfileView();
	}

	...
}
  • Line 21: This method is listed in previous section.


After the above steps, we have finished all functions of the target application. Quite simple, right? You can see the result at http://localhost:8080/essentials/chapter5/index.zul.

MVVM Approach

In addition to the MVC approach, ZK also allows you to design your application using another architecture: MVVM (Model-View-ViewModel). This architecture also divides an application into 3 parts: View, Model, and ViewModel. The View and Model plays the same roles as they do in MVC. The ViewModel in MVVM acts like a special Controller for the View which is responsible for exposing data from the Model to the View and for providing required action and logic for user requests from the View. The ViewModel is a View abstraction, which contains a View's state and behavior. The biggest difference from the Controller in the MVC is that ViewModel should not contain any reference to UI components and knows nothing about the View's visual elements. Hence this clear separation between View and ViewModel decouples ViewModel from View and makes ViewModel more reusable and more abstract.

Since the ViewModel contains no reference to UI components, you cannot control components directly e.g. to get value from them or set value to them. Therefore we need a mechanism to synchronize data between the View and ViewModel. Additionally, this mechanism also has to bridge events from the View to the action provided by the ViewModel. This mechanism, the kernel operator of the MVVM design pattern, is a data binding system called "ZK Bind" provided by the ZK framework. In this binding system, the binder plays the key role to operate the whole mechanism. The binder is like a broker and responsible for communication between View and ViewModel.


Mvvm-architecture.png


This section we will demonstrate how to implement the same target application under MVVM approach.

Construct a View as MVC Approach

Building a user interface using the MVVM approach is not different from the MVC approach.

Extracted from chapter5/profile-mvvm-property.zul

<?link rel="stylesheet" type="text/css" href="/style.css"?>
<window border="normal" hflex="1" vflex="1" contentStyle="overflow:auto">
	<caption src="/imgs/profile.png" sclass="fn-caption" 
			 label="Profile (MVVM)"/>
	<vlayout>
		<grid width="500px" >
			<columns>
				<column align="right" hflex="min"/>
				<column/>
			</columns>
			<rows>
				<row>
					<cell sclass="row-title">Account :</cell>
					<cell><label/></cell>
				</row>
				<row>
					<cell sclass="row-title">Full Name :</cell>
					<cell>
					<textbox 
					constraint="no empty: Plean enter your full name" 
							 width="200px"/>
					</cell>
				</row>
				<row>
					<cell sclass="row-title">Email :</cell>
					<cell>
					<textbox
					constraint="/.+@.+\.[a-z]+/: Please enter an e-mail address" 
					width="200px"/>
					</cell>
				</row>
				<row>
					<cell sclass="row-title">Birthday :</cell>
					<cell><datebox constraint="no future" width="200px"/>
					</cell>
				</row>
				<row>
					<cell sclass="row-title">Country :</cell>
					<cell>
						<listbox  mold="select" width="200px">
							<template name="model">
								<listitem />
							</template>
						</listbox>
					</cell>
				</row>
				<row>
					<cell sclass="row-title">Bio :</cell>
					<cell>
					<textbox 
					multiline="true" hflex="1" height="200px" />
					</cell>
				</row>
			</rows>
		</grid>
		<div>You are editing <label />'s profile.</div>
		<hlayout>
			<button  label="Save"/>
			<button  label="Reload"/>
		</hlayout>
	</vlayout>
</window>
  • Line 41: You might notice that there is no EL expression ${each} as we will use data binding to access it.

Create a ViewModel

ViewModel is an abstraction of View which contains the View's data, state and behavior. It extracts the necessary data to be displayed on the View from one or more Model classes. Those data are exposed through getter and setter method like JavaBean's property. ViewModel is also a "Model of the View". It contains the View's state (e.g. user's selection, whether a component is enabled or disabled) that might change during user interaction.

In ZK, the ViewModel can simply be a POJO which contains data to display on the ZUL and doesn't have any components. The example application displays 2 kinds of data: the user's profile and country list in the Listbox. The ViewModel should look like the following:

Define properties in a ViewModel

public class ProfileViewModel implements Serializable{
	
	//services
	AuthenticationService authService = new AuthenticationServiceChapter5Impl();
	UserInfoService userInfoService = new UserInfoServiceChapter5Impl();
	
	//data for the view
	User currentUser;
	
	public User getCurrentUser(){
		return currentUser;
	}
	
	public List<String> getCountryList(){
		return CommonInfoService.getCountryList();
	}
	
	@Init // @Init annotates a initial method
	public void init(){
		UserCredential cre = authService.getUserCredential();
		currentUser = userInfoService.findUser(cre.getAccount());
		if(currentUser==null){
			//TODO handle un-authenticated access 
			return;
		}
	}
	...
}
  • Line 4,5: The ViewModel usually contains service classes that are used to get data from them or perform business logic.
  • Line 8, 10: We should define current user profile data and its getter method to be displayed in the zul.
  • Line 14: ViewModel exposes its data by getter methods, it doesn't have to define a corresponding member variable. Hence we can expose country list by getting from the service class.
  • Line 18: There is a marker annotation @Init for a method which should be at most one in each ViewModel and ZK will invoke this method after instantiating a ViewModel class. We should perform initialization in it, e.g. get user credential to initialize currentUser.


Define Commands

ViewModel also contains View's behaviors which are implemented by methods. We call such a method "Command" of the ViewModel. These methods usually manipulate data in the ViewModel, for example deleting an item. The View's behaviors are usually triggered by events from the View. The Data binding mechanism also supports binding an event to a ViewModel's command. Firing the component's event will trigger the execution of bound command that means invoking the corresponding command method.

For ZK to recognize a command method in a ViewModel, you should apply annotation @Command to a command method. You could specify a command name which is the method's name by default if no specified. Our example has two behavior: "save" and "reload", so we define two command methods for each of them:

Define commands in a ViewModel

public class ProfileViewModel implements Serializable{
	...

	@Command //@Command annotates a command method 
	public void save(){
		currentUser = userInfoService.updateUser(currentUser);
		Clients.showNotification("Your profile is updated");
	}

	@Command 
	public void reload(){
		UserCredential cre = authService.getUserCredential();
		currentUser = userInfoService.findUser(cre.getAccount());
	}
}
  • Line 4, 10: Annotate a method with @Command to make it become a command method, and it can be bound with data binding in a zul.
  • Line 5: Method name is the default command name if you don't specify in @Command. This method save the currentUser with a service class and show a notification.


During execution of a command, one or more properties may be changed due to performing business or presentation logic. Developers have to specify which property (or properties) is changed, then the data binding mechanism can reload them to synchronize the View to the latest state.

The syntax to notify property change:

One property:

@NotifyChange("oneProperty")

Multiple properties:

@NotifyChange({"property01","property02"})

All properties in a ViewModel:

@NotifyChange("*")


Define notification & commands in a ViewModel

public class ProfileViewModel implements Serializable{
	...

	@Command //@Command annotates a command method 
	@NotifyChange("currentUser") //@NotifyChange annotates data changed notification after calling this method 
	public void save(){
		currentUser = userInfoService.updateUser(currentUser);
		Clients.showNotification("Your profile is updated");
	}

	@Command 
	@NotifyChange("currentUser")
	public void reload(){
		UserCredential cre = authService.getUserCredential();
		currentUser = userInfoService.findUser(cre.getAccount());
	}
}
  • Line 5, 12: Notify which property change with @NotifyChange and zK will reload those attributes that are bound to currentUser.

Apply a ViewModel on a Component

Before data binding can work, we must apply a composer called org.zkoss.bind.BindComposer. It will create a binder for the ViewModel and instantiate the ViewModel's class. Then we should bind a ZK component to our ViewModel by setting its viewModel attribute with the ViewModel's id in @id and the ViewModel's full-qualified class name in @init . The id is used to reference the ViewModel's properties, e.g. vm.name, whilst the full-qualified class name is used to instantiate the ViewModel object itself. So that component becomes the Root View Component for the ViewModel. All child components of this Root View Component can be bound to the same ViewModel and its properties, so we usually bind the root component of a page to a ViewModel.

<window apply="org.zkoss.bind.BindComposer"
	viewModel="@id('vm') @init('org.zkoss.essentials.chapter5.mvvm.ProfileViewModel')" 
	border="normal" hflex="1" vflex="1" contentStyle="overflow:auto">
...
</window>
  • Line 1: Under MVVM approach, the composer we apply is fixed org.zkoss.bind.BindComposer.
  • Line 2: Specify ViewModel's id with @id and the its full-qualified class name in @init for the binder.

Data Binding to ViewModel's Properties

Now that ViewModel is prepared and bound to a component, we can bind a component's attributes to the ViewModel's property. The binding between an attribute and a ViewModel's property is called "property binding". Once the binding is established, ZK will synchronize (load and save) data between components and the ViewModel for us automatically.

Mvvm-databinding-role.png

Let's demonstrate how to make Listbox load a list of country name from the ViewModel. We have talked about the data model concept in MVC approach section, and we also need to prepare a model object that are defined in one of our ViewModel's properties, countryList. You might find getCountryList() return a List instead of a ListModelList, but don't worry. ZK will convert it automatically. We use @load to load a ViewModel's property to a component's attribute and @save to save an attribute value into a ViewModel's property (usually for an input component). If both loading and saving are required, we could use @bind.

...
	<cell>
		<listbox model="@load(vm.countryList)" mold="select" width="200px">
			<template name="model">
				<listitem label="@load(each)" />
			</template>
		</listbox>
	</cell>
...
  • Line 3: We setup a load binding with @load. The vm is the ViewModel's id we specified at @id in previous section and the target property (countryList) can be referenced in dot notation.
  • Line 4: Template component, we have explained in MVC approach section, can create its child components repeatedly upon the data model of parent component.
  • Line 5: The implicit variable each which you can use without declaration inside Template represents each object in the data model for each iterative rendering (It represents String object of a country name in this example). We use this variable to access objects of data model. In our example, we just make it as a Listitem's label.


In MVC approach, we have to call an input component's getter method (e.g. getValue() ) to collect user input. But in MVVM approach, ZK will save user input back to a ViewModel automatically. For example in the below zul, user input is saved automatically when you move the focus out of the Textbox.

		<textbox value="@bind(vm.currentUser.fullName)" 
			constraint="no empty: Plean enter your full name" width="200px"/>

For the property currentUser, we want to both save user input back to the ViewModel and load value from the ViewModel, so we should use the @bind at value attribute. Notice that you can bind selectedItem to a property, then the user's selection can be saved automatically to the ViewModel.

...
	<rows>
		<row>
			<cell sclass="row-title">Account :</cell>
			<cell>
				<label value="@load(vm.currentUser.account)"/>
			</cell>
		</row>
		<row>
			<cell sclass="row-title">Full Name :</cell>
			<cell>
				<textbox value="@bind(vm.currentUser.fullName)" 
					constraint="no empty: Plean enter your full name"
						width="200px"/>
			</cell>
		</row>
		<row>
			<cell sclass="row-title">Email :</cell>
			<cell>
				<textbox value="@bind(vm.currentUser.email)" 
				constraint="/.+@.+\.[a-z]+/: Please enter an e-mail address" 
					width="200px"/>
			</cell>
		</row>
		<row>
			<cell sclass="row-title">Birthday :</cell>
			<cell>
				<datebox value="@bind(vm.currentUser.birthday)"
					constraint="no future" width="200px"/>
			</cell>
		</row>
		<row>
			<cell sclass="row-title">Country :</cell>
			<cell>
				<listbox model="@load(vm.countryList)" 
					selectedItem="@bind(vm.currentUser.country)" 
						mold="select" width="200px">
					<template name="model">
						<listitem label="@load(each)" />
					</template>
				</listbox>
			</cell>
		</row>
		<row>
			<cell sclass="row-title">Bio :</cell>
			<cell>
				<textbox value="@bind(vm.currentUser.bio)" 
					multiline="true" hflex="1" height="200px" />
			</cell>
		</row>
	</rows>

...
  • Line 12, 20, 28, 47: Use @bind to save user input back to the ViewModel and load value from the ViewModel.
  • Line 36: Bind selectedItem to vm.currentUser.country and the selected country will be saved to currentUser.

Handle User Interactions by Command Binding

After we finish binding attributes to the ViewModel's data, we still need to handle user actions, button clicking. Under the MVVM approach, we handle events by binding an event attribute (e.g. onClick) to a Command of a ViewModel. After we bind an event to a Command, each time the event is sent, ZK will invoke the corresponding command method. Hence, we should write our business logic in a command method. After executing the command method, some properties might be changed. We should tell ZK which properties are changed by us, then the binder will reload them to components.

When creating the ProfileViewModel in the previous section, we have defined two commands: save and reload.

Then, we can bind onClick event to above commands with command binding @command('commandName') as follows:

...
		<hlayout>
			<button onClick="@command('save')" label="Save"/>
			<button onClick="@command('reload')" label="Reload"/>
		</hlayout>
...

Done with this binding, clicking each button will invoke corresponding command methods to save (or reload) the user profile to the ViewModel.

Keep Away Unsaved Input

Once you create a property binding with @bind for an input component, ZK will save user input back to a ViewModel automatically. But sometimes this automation is not what users want. In our example, most people usually expect currentUser to change after their confirmation for example, clicking a button.

There is a line of text "You are editing an Anonymous's profile" at the bottom of the form. If you change the full name to "Anonymous Somebody" and move to next field, the line of text is changed even you don't press the "Save" button. This could be a problem maybe it would mislead users, making them think they have changed their profile, so we don't want this.

Tutorial-ch5-unsaved.png
Unsaved Input Changes Data

We are going to improve this part with form binding feature in this section.

Form binding automatically creates a middle object as a buffer. Before saving to ViewModel all input data is saved to the middle object. In this way we can keep dirty data from saving into the ViewModel before the user confirms.

Steps to use a form binding:

  1. Give an id to middle object in form attribute with @id .
    Then you can reference the middle object in ZK bind expression with its id, e.g. @id('fx').
  2. Specify ViewModel's property to be loaded with @load
  3. Specify ViewModel's property to save and before which Command with @save
    This means binder will save the middle object's properties to ViewModel before a command execution.
  4. Bind component's attribute to the middle object's properties like you do in property binding.
    You should use middle object's id specified in @id to reference its property, e.g. @load(fx.account).

extracted from chapter5/profile-mvvm.zul

...
	<grid width="500px" 
	form="@id('fx')@load(vm.currentUser)@save(vm.currentUser, before='save')">
		...
		<rows>
			<row>
				<cell sclass="row-title">Account :</cell>
				<cell><label value="@load(fx.account)"/></cell>
			</row>
			<row>
				<cell sclass="row-title">Full Name :</cell>
				<cell>
					<textbox value="@bind(fx.fullName)" width="200px"
					constraint="no empty: Plean enter your full name"/>
				</cell>
			</row>
			<row>
				<cell sclass="row-title">Email :</cell>
				<cell>
					<textbox value="@bind(fx.email)" width="200px"
				constraint="/.+@.+\.[a-z]+/: Please enter an e-mail address"/>
				</cell>
			</row>
			<row>
				<cell sclass="row-title">Birthday :</cell>
				<cell>
					<datebox value="@bind(fx.birthday)" width="200px"
					constraint="no future" />
				</cell>
			</row>
			<row>
				<cell sclass="row-title">Country :</cell>
				<cell>
					<listbox model="@load(vm.countryList)" 
						mold="select" width="200px"
						selectedItem="@bind(fx.country)">
						<template name="model">
							<listitem label="@load(each)"/>
						</template>
					</listbox>
				</cell>
			</row>
			<row>
				<cell sclass="row-title">Bio :</cell>
				<cell><textbox value="@bind(fx.bio)" multiline="true" 
					hflex="1" height="200px" />
				</cell>
			</row>
		</rows>
	</grid>
	<div>You are editing 
		<label value="@load(vm.currentUser.fullName)"/>'s profile.
	</div>
...
  • Line 2, 3: Define a form binding at form attribute and give the middle object's id fx. Specify @load(vm.currentUser) makes the binder load currentUser's properties to the middle object and @save(vm.currentUser, before='save') makes the binder save middle object's data back to vm.currentUser before executing the command save.
  • Line 8, 13, 20, 27, 36, 45: We should bind attributes to middle object's properties to avoid altering ViewModel's properties.
  • LIne 51, 52, 53: The label bound to vm.currentUser.fullName is not affected when fx is changed.


After applying form binding, any user's input will not actually change currentUser's value and they are stored in the middle object until you click the "Save" button, ZK puts the middle object's data back to the ViewModel's properties (currentUser).

Tutorial-ch5-form-binding.png
Unsaved Input Doesn't Change Data


After completing above steps, visit http://localhost:8080/essentials/chapter5/index-mvvm.zul to see the result.

Source Code




Last Update : 2022/01/19

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