Notification"

From Documentation
m ((via JWB))
 
(21 intermediate revisions by 4 users not shown)
Line 1: Line 1:
 
{{ZKDevelopersReferencePageHeader}}
 
{{ZKDevelopersReferencePageHeader}}
 +
{{Deprecated | url=[http://books.zkoss.org/zk-mvvm-book/8.0/viewmodel/notification.html zk-mvvm-book/8.0/viewmodel/notification]|}}
  
 
= Overview =
 
= Overview =
The binder is responsible for synchronizing data between the View and ViewModel. Typically the ViewModel is a POJO and has no reference to any UI component. Hence it cannot push data to a UI component. Developers have to specify the dependency of ViewModel's properties with ZK provided Java annotation, then binder will know when to reload which property.
+
The binder is responsible for synchronizing data between the View and ViewModel. Typically the ViewModel is a POJO and has no reference to any UI component. Hence it cannot push data to a UI component. Developers have to specify the dependency of ViewModel's properties with ZK provided Java annotation, then binder knows when to reload which property and to update the UI view.
  
 
= Notify Change =
 
= Notify Change =
  
ZK bind provides a set of Java annotation (<tt> @NotifyChange, @DependsOn, @NotifyChangeDisabled </tt>) to specify '''when to reload which property to UI components'''. Developers have to specify it at design time, and binder will synchronize data at run-time. The binder keeps track of binding relationships between component's attribute and ViewModel's property. After each time it invokes a ViewModel's method (setter, getter, or command method), it looks for corresponding component attribute to reload according to specified properties in the annotation.
+
ZK bind provides a set of Java annotations (<code>@NotifyChange, @DependsOn, @NotifyChangeDisabled</code>) to specify '''when to reload which property to UI components'''. Developers have to specify it at design time, and the binder will synchronize data at run-time. The binder keeps track of binding relationships between component's attribute and ViewModel's property. After each time it invokes a ViewModel's method (setter, getter, or command method), it looks for corresponding component attribute to reload according to specified properties in the annotation.
  
 
== Notify on Command ==
 
== Notify on Command ==
During executing a command, one or more properties are changed because Command method usually contains business or presentation logic. Developers have to specify which property (or properties) is changed in Java annotation's element, then the data binding mechanism can reload them from ViewModel to View at run-time after executing the Command.
+
During execution of a command, one or more properties are changed because Command method usually contains business or presentation logic. Developers have to specify which property (or properties) is changed in Java annotation's element, then the data binding mechanism can reload them from ViewModel to View at run-time after executing the Command.
  
 
The syntax to notify property change:
 
The syntax to notify property change:
Line 15: Line 16:
 
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>
  
  
 
Following is an example of @NotifyChange:
 
Following is an example of @NotifyChange:
  
<source lang="java" high="10,16,24">
+
<source lang="java" highlight="10,16,24">
 
public class OrderVM {
 
public class OrderVM {
  
Line 64: Line 65:
 
</source>
 
</source>
  
* In <tt> newOrder() </tt>, we change property "orders" and "selected", therefore we apply <tt> @NotifyChange </tt> to notify binder to reload 2 changed properties after command execution. (line 16)
+
* In <code>newOrder()</code>, we change property "orders" and "selected", therefore we apply <code>@NotifyChange</code> to notify binder to reload 2 changed properties after command execution. (line 16)
* The <tt> deleteOrder() </tt> also change the same 2 properties. Assume that this ViewModel class has only 2 properties, we can use '''"*"''' (asterisk sign) to notify all properties in current class. (line 24)
+
* The <code>deleteOrder()</code> also change the same 2 properties. Assume that this ViewModel class has only 2 properties, we can use '''"*"''' (asterisk sign) to notify all properties in current class. (line 24)
  
 
== Notify on Setter ==
 
== Notify on Setter ==
For similar reason, property might be changed in setter method. After value is saved to ViewModel's property, multiple components or other properties might depend on the changed property. We also have to specify this data dependentcy by <tt> @NotifyChange </tt>.
+
For similar reason, property might be changed in setter method. After value is saved to ViewModel's property, multiple components or other properties might depend on the changed property. We also have to specify this data dependency by <code>@NotifyChange</code>.
  
 
=== Enabled by Default ===
 
=== Enabled by Default ===
A setter method usually changes one property, e.g. setMessage() will set message to new value. If there is no other properties depend on this changed property, we don't have to add <tt> @NotifyChange </tt>. The target property changed by setter method is notified automatically.
+
A setter method usually changes only one property, e.g. setMessage() will set ''message'' to new value. To save developer's effort, '''the target property changed by setter method is notified automatically'''. That is, <code>@NotifyChange</code> annotation on a setter is not required unless there are some other properties changed by the setter and need to be explicitly notified.
  
 
<source lang="java">
 
<source lang="java">
Line 86: Line 87:
  
 
</source>
 
</source>
* There is no <tt> @NotifyChange </tt> on setter method in <tt> MessageViewModel </tt>.
+
* There is no need to specify <code>@NotifyChange</code> on <code>setMessage()</code> in <code>MessageViewModel</code> if only <code>message</code> is changed in the setter method.
  
  
The ZUL that uses <tt> MessageViewModel </tt>.
+
The ZUL that uses <code>MessageViewModel</code>.
 
<source lang="xml">
 
<source lang="xml">
  
Line 101: Line 102:
 
* When a user input value in msgBox and the value is saved back to ViewModel, the data binding mechanism will synchronize the value to msg1 and msg2 automatically.
 
* When a user input value in msgBox and the value is saved back to ViewModel, the data binding mechanism will synchronize the value to msg1 and msg2 automatically.
 
   
 
   
Developer can disable this default behavior by adding <tt> @NotifyChangeDisabled </tt> on setter method.
+
Developer can disable this default behavior by adding <code>@NotifyChangeDisabled</code> on setter method.
  
 
=== Specify Dependency ===
 
=== Specify Dependency ===
For setter method, <tt> @NotifyChange </tt> is used when multiple properties have dependency relationship, e.g. one property's value is calculated from the other properties. Notice that if you apply <tt> @NotifyChange </tt> on a setter method, default notification behavior will be overridden.
+
For setter method, <code>@NotifyChange</code> is used when multiple properties have dependency relationship, e.g. one property's value is calculated from the other properties. Notice that if you apply <code>@NotifyChange</code> on a setter method, default notification behavior will be overridden.
  
 
'''@NotifyChange on Setter'''
 
'''@NotifyChange on Setter'''
<source lang="java" high="5,10">
+
<source lang="java" highlight="5,10">
  
 
public class FullnameViewModel{
 
public class FullnameViewModel{
Line 130: Line 131:
  
 
</source>
 
</source>
* By default setLastname() will notify "lastname" property's change. Becuase we apply <tt>NotifyChange </tt> on it, this default notification is overridden. We have to notify it explicitly. (line 10)
+
* By default setLastname() will notify "lastname" property's change. Becuase we apply <code>NotifyChange</code> on it, this default notification is overridden. We have to notify it explicitly. (line 10)
  
 
In above code snippet, fullname is concatenated by firstname and lastname. When firstname and lastname is changed by setter, the fullname should be reload to reflect its change.
 
In above code snippet, fullname is concatenated by firstname and lastname. When firstname and lastname is changed by setter, the fullname should be reload to reflect its change.
  
If a property depends on many properties, <tt> @NotifyChange </tt> will distribute in multiple places in a ViewModel class. Developers can use <tt> @DependsOn </tt> for convinience. This annotation has exactly the same function as <tt> @NotifyChange </tt>, but it's used on getter method.
+
If a property depends on many properties, <code>@NotifyChange</code> will distribute in multiple places in a ViewModel class. Developers can use <code>@DependsOn</code> for convenience. This annotation defines the dependency relations among properties and can provide same function as explicit <code>@NotifyChange</code>, but it's used on getter method.
 
 
 
'''Example of @DependsOn'''
 
'''Example of @DependsOn'''
<source lang="java" high="13">
+
<source lang="java" highlight="13">
 
public class FullnameViewModel{
 
public class FullnameViewModel{
  
Line 160: Line 161:
 
* The example is functional equivalent to previous one, but written in reversed meaning. It means when any one of 2 properties (firstname, lastname) change,  fullname should be reloaded.
 
* The example is functional equivalent to previous one, but written in reversed meaning. It means when any one of 2 properties (firstname, lastname) change,  fullname should be reloaded.
  
==Notify Object Change==
+
==Notify Bean Change==
  
If you bind a component to an object and want to reload it after the object's property changed, you should apply the following syntax on the setter of that target property:
+
If you bind a component's attribute to a bean and want to reload it after the bean's property changed, you should apply the following syntax on the setter of that target property:
  
'''<tt> @NotifyChange(".") </tt>'''
+
'''<code>@NotifyChange(".")</code>'''
  
You can't not use <tt> @NotifyChange("*") </tt> because it just reloads those components which bound to an object's property to reload, e.g. <tt> @bind(vm.person.firstname) </tt>. The component bound to '''base object''' itself, <tt> vm.person </tt>, will '''not''' be reloaded. If there is a component's attribute bound to <tt> vm.person </tt>, it won't change upon dependent data change.
+
Note a dot(.) is a bit different from an asterisk(*) because an asterisk, <code>@NotifyChange("*")</code>, just notify binder to reload those components that bound to the bean's properties, e.g. <code>@bind(vm.person.firstname)</code>, <code>@bind(vm.person.lastname)</code>, and <code>@bind(vm.person.fullname)</code>. The component attribute that bound to the bean itself, i.e. <code>vm.person</code>, will '''NOT''' be reloaded with the @NotifyChange("*"). In such case you have to use <code>@NotifyChange(".")</code> to tell binder that the bean itself has changed and the component attribute bound to the bean shall be reloaded.
  
As ZK bind annotation only allows to to bind one attribute to only one property, if you need to access multiple properties at one time, you have to bind to a whole object. For example, you might have a converter which converts data upon an object's multiple properties.
+
For what use cases you will need this? Most of the time, we bind one component attribute to only one bean property. However, there are cases that you might want to bind the component attribute to a calculation result of multiple bean properties; yet you don't want to put that calculation logic in the bean. Then you can bind the component attribute to the whole bean and have a converter to convert the data upon the bean's multiple properties.
  
Assume there is a page in a tour website, a customer has to fill the leave and return day of a trip. And we use a converter to calculate days of a trip in order to remind the customer after he fills both fields.
+
For example, assume there is a web page in a tour website. A customer has to fill the leave and return day of a trip and the duration of the trip shall be automatically calculated and show on the screen after the user fills either fields. Here we choose to use a converter to do the duration calculation instead of implementing the logic in the Trip class as a property. In such scenario, you will need to annotate @NotifyChange(".") on both leaveDate and returnDate property setters so when either leaveDate or returnDate property changes, the duration attribute can be updated.
  
 
'''Bind to an object example'''
 
'''Bind to an object example'''
<source lang="xml" high="7">
+
<source lang="xml" highlight="7">
  
 
<hlayout>
 
<hlayout>
Line 184: Line 185:
  
 
</source>
 
</source>
* Because durationConverter needs to access 2 properties (leaveDate, returnDate), we have to bind label's value to the trip object itself not an individual property. (line 7)
+
* Because durationConverter needs to access 2 properties (leaveDate, returnDate), we have to bind label's value to the trip bean itself not an individual property of the bean. (line 7)
  
  
 
'''@NotifyChange(".") example'''
 
'''@NotifyChange(".") example'''
<source lang="java" high="13,20">
+
<source lang="java" highlight="13,20">
  
 
public class DurationViewModel  {
 
public class DurationViewModel  {
Line 248: Line 249:
 
</source>
 
</source>
 
* About how to implement a converter, please refer to [[ZK Developer's Reference/MVVM/DataBinding/Converter]].
 
* About how to implement a converter, please refer to [[ZK Developer's Reference/MVVM/DataBinding/Converter]].
 
  
 
== Notify Programmatically ==
 
== Notify Programmatically ==
Sometimes the changed properties we want to notify depends on value at run-time, so we cannnot determine property's name at design time. In this case, we can use <tt> BindUtils.postNotifyChange() </tt> to notify change dynamically. The underlying mechanism for this notification is binder subscribed event queue that we talk about in section [[ZK Developer's Reference/MVVM/DataBinding/Binder]]. It uses '''desktop scope''' event queue by default.
+
Sometimes the changed properties we want to notify depends on value at run-time, so we cannot determine the property's name at design time. In this case, we can use <javadoc class="true"  method="postNotifyChange(java.lang.String queueName, java.lang.String queueScope, java.lang.Object bean, java.lang.String property)">org.zkoss.bind.BindUtils</javadoc> to notify change dynamically. The underlying mechanism for this notification is binder subscribed event queue that we talk about in [[ZK Developer's Reference/MVVM/DataBinding/Binder | the binder section]]. It uses '''desktop scope''' event queue by default.
  
 
'''Dynamic Notification'''
 
'''Dynamic Notification'''
Line 274: Line 274:
 
* The first parameter of postNotifyChange() is queue name and second one is queue scope. You can just leave it null and default queue name ans scope (desktop) will be used.
 
* The first parameter of postNotifyChange() is queue name and second one is queue scope. You can just leave it null and default queue name ans scope (desktop) will be used.
  
 +
=Version History=
  
 
+
{| class='wikitable' | width="100%"
=Version History=
 
{{LastUpdated}}
 
{| border='1px' | width="100%"
 
 
! Version !! Date !! Content
 
! Version !! Date !! Content
 
|-
 
|-

Latest revision as of 07:36, 8 July 2022

Stop.png This article is out of date, please refer to zk-mvvm-book/8.0/viewmodel/notification for more up to date information.

Overview

The binder is responsible for synchronizing data between the View and ViewModel. Typically the ViewModel is a POJO and has no reference to any UI component. Hence it cannot push data to a UI component. Developers have to specify the dependency of ViewModel's properties with ZK provided Java annotation, then binder knows when to reload which property and to update the UI view.

Notify Change

ZK bind provides a set of Java annotations (@NotifyChange, @DependsOn, @NotifyChangeDisabled) to specify when to reload which property to UI components. Developers have to specify it at design time, and the binder will synchronize data at run-time. The binder keeps track of binding relationships between component's attribute and ViewModel's property. After each time it invokes a ViewModel's method (setter, getter, or command method), it looks for corresponding component attribute to reload according to specified properties in the annotation.

Notify on Command

During execution of a command, one or more properties are changed because Command method usually contains business or presentation logic. Developers have to specify which property (or properties) is changed in Java annotation's element, then the data binding mechanism can reload them from ViewModel to View at run-time after executing the Command.

The syntax to notify property change:

One property:

@NotifyChange("oneProperty")

Multiple properties:

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

All properties in a ViewModel:

@NotifyChange("*")


Following is an example of @NotifyChange:

public class OrderVM {

	//the order list
	List<Order> orders;
	//the selected order
	Order selected;

	//getter and setter

	@NotifyChange("selected")
	@Command
	public void saveOrder(){
		getService().save(selected);
	}

	@NotifyChange({"orders","selected"})
	@Command
	public void newOrder(){
		Order order = new Order();
		getOrders().add(order);
		selected = order;//select the new one
	}

	@NotifyChange("*")
	@Command
	public void deleteOrder(){
		getService().delete(selected);//delete selected
		getOrders().remove(selected);
		selected = null; //clean the selected
	}

}
  • In newOrder(), we change property "orders" and "selected", therefore we apply @NotifyChange to notify binder to reload 2 changed properties after command execution. (line 16)
  • The deleteOrder() also change the same 2 properties. Assume that this ViewModel class has only 2 properties, we can use "*" (asterisk sign) to notify all properties in current class. (line 24)

Notify on Setter

For similar reason, property might be changed in setter method. After value is saved to ViewModel's property, multiple components or other properties might depend on the changed property. We also have to specify this data dependency by @NotifyChange.

Enabled by Default

A setter method usually changes only one property, e.g. setMessage() will set message to new value. To save developer's effort, the target property changed by setter method is notified automatically. That is, @NotifyChange annotation on a setter is not required unless there are some other properties changed by the setter and need to be explicitly notified.

public class MessageViewModel{
	private String message;
	
	public String getMessage() {
		return this.message;
	}
	public void setMessage(String msg) {
		message = msg;
	}
}
  • There is no need to specify @NotifyChange on setMessage() in MessageViewModel if only message is changed in the setter method.


The ZUL that uses MessageViewModel.

<textbox id="msgBox" value="@bind(vm.message)"/>

<label id="msg1" value="@load(vm.message)"/>

<label id="msg2" value="@load(vm.message)"/>
  • When a user input value in msgBox and the value is saved back to ViewModel, the data binding mechanism will synchronize the value to msg1 and msg2 automatically.

Developer can disable this default behavior by adding @NotifyChangeDisabled on setter method.

Specify Dependency

For setter method, @NotifyChange is used when multiple properties have dependency relationship, e.g. one property's value is calculated from the other properties. Notice that if you apply @NotifyChange on a setter method, default notification behavior will be overridden.

@NotifyChange on Setter

public class FullnameViewModel{

	//getter and setter

	@NotifyChange("fullname")
	public void setFirstname(String firstname) {
		this.firstname = firstname;
	}
	
	@NotifyChange({"lastname","fullname"})
	public void setLastname(String lastname) {
		this.lastname = lastname;
	}

	public String getFullname() {
		return (firstname == null ? "" : firstname)	+ " "
			+ (lastname == null ? "" : lastname);
	}
}
  • By default setLastname() will notify "lastname" property's change. Becuase we apply NotifyChange on it, this default notification is overridden. We have to notify it explicitly. (line 10)

In above code snippet, fullname is concatenated by firstname and lastname. When firstname and lastname is changed by setter, the fullname should be reload to reflect its change.

If a property depends on many properties, @NotifyChange will distribute in multiple places in a ViewModel class. Developers can use @DependsOn for convenience. This annotation defines the dependency relations among properties and can provide same function as explicit @NotifyChange, but it's used on getter method.

Example of @DependsOn

public class FullnameViewModel{

	//getter and setter

	public void setFirstname(String firstname) {
		this.firstname = firstname;
	}
	
	public void setLastname(String lastname) {
		this.lastname = lastname;
	}

	@DependsOn({"firstname", "lastname"})
	public String getFullname() {
		return (firstname == null ? "" : firstname)	+ " "
				+ (lastname == null ? "" : lastname);
	}
}
  • The example is functional equivalent to previous one, but written in reversed meaning. It means when any one of 2 properties (firstname, lastname) change, fullname should be reloaded.

Notify Bean Change

If you bind a component's attribute to a bean and want to reload it after the bean's property changed, you should apply the following syntax on the setter of that target property:

@NotifyChange(".")

Note a dot(.) is a bit different from an asterisk(*) because an asterisk, @NotifyChange("*"), just notify binder to reload those components that bound to the bean's properties, e.g. @bind(vm.person.firstname), @bind(vm.person.lastname), and @bind(vm.person.fullname). The component attribute that bound to the bean itself, i.e. vm.person, will NOT be reloaded with the @NotifyChange("*"). In such case you have to use @NotifyChange(".") to tell binder that the bean itself has changed and the component attribute bound to the bean shall be reloaded.

For what use cases you will need this? Most of the time, we bind one component attribute to only one bean property. However, there are cases that you might want to bind the component attribute to a calculation result of multiple bean properties; yet you don't want to put that calculation logic in the bean. Then you can bind the component attribute to the whole bean and have a converter to convert the data upon the bean's multiple properties.

For example, assume there is a web page in a tour website. A customer has to fill the leave and return day of a trip and the duration of the trip shall be automatically calculated and show on the screen after the user fills either fields. Here we choose to use a converter to do the duration calculation instead of implementing the logic in the Trip class as a property. In such scenario, you will need to annotate @NotifyChange(".") on both leaveDate and returnDate property setters so when either leaveDate or returnDate property changes, the duration attribute can be updated.

Bind to an object example

	<hlayout>
		Leave Date: <datebox value="@save(vm.trip.leaveDate)"/>
	</hlayout>
	<hlayout>
		Return Date: <datebox value="@save(vm.trip.returnDate)"/>
	</hlayout>
	Duration including leave and return(day): <label value="@load(vm.trip) @converter(vm.durationConverter)"/>
  • Because durationConverter needs to access 2 properties (leaveDate, returnDate), we have to bind label's value to the trip bean itself not an individual property of the bean. (line 7)


@NotifyChange(".") example

public class DurationViewModel  {

	private Trip trip = new Trip();
	
	public class Trip{

		private Date leaveDate;
		private Date returnDate;
		
		public Date getLeaveDate() {
			return leaveDate;
		}
		@NotifyChange(".")
		public void setLeaveDate(Date leaveDate) {
			this.leaveDate = leaveDate;
		}
		public Date getReturnDate() {
			return returnDate;
		}
		@NotifyChange(".")
		public void setReturnDate(Date returnDate) {
			this.returnDate = returnDate;
		}
		
		
	}
	

	public Converter getDurationConverter(){
		return new Converter() {
			
			public Object coerceToUi(Object val, Component component, BindContext ctx) {
				if (val instanceof Trip){
					Trip trip = (Trip) val;
					Date leaveDate = trip.getLeaveDate();
					Date returnDate = trip.getReturnDate();
					if (null != leaveDate && null != returnDate){
						if (returnDate.compareTo(leaveDate)==0){
							return 1;
						
						}else if (returnDate.compareTo(leaveDate)>0){
							return ((returnDate.getTime() - leaveDate.getTime())/1000/60/60/24)+1;
						}
						return null;
					}
				}
				return null;
			}
			
			public Object coerceToBean(Object val, Component component, BindContext ctx) {
				return null;
			}
		};
	}
}

Notify Programmatically

Sometimes the changed properties we want to notify depends on value at run-time, so we cannot determine the property's name at design time. In this case, we can use BindUtils.postNotifyChange(String queueName, String queueScope, Object bean, String property) to notify change dynamically. The underlying mechanism for this notification is binder subscribed event queue that we talk about in the binder section. It uses desktop scope event queue by default.

Dynamic Notification

	String data;
	// setter & getter

	@Command
	public void cmd(){
		if (data.equal("A")){
			//other codes...
			BindUtils.postNotifyChange(null,null,this,"value1");
		}else{
			//other codes...
			BindUtils.postNotifyChange(null,null,this,"value2");
		}
		
	}
  • The first parameter of postNotifyChange() is queue name and second one is queue scope. You can just leave it null and default queue name ans scope (desktop) will be used.

Version History

Version Date Content
6.0.0 February 2012 The MVVM was introduced.



Last Update : 2022/07/08

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