Notification

From Documentation
Revision as of 08:36, 10 February 2012 by Henrichen (talk | contribs) (→‎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 knows when to reload which property.

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 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 @NotifyChange . The target property changed by setter method is notified automatically.

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


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 has exactly the same function as @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 Object 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:

@NotifyChange(".")

You have to use @NotifyChange("*") because it just reloads those components which bound to an object's property to reload, e.g. @bind(vm.person.firstname) . The component bound to base object itself, vm.person , will not be reloaded. If there is a component's attribute bound to vm.person , it won't change upon dependent data change.

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.

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.

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 object itself not an individual property. (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() 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

Last Update : 2012/02/10


Version Date Content
6.0.0 February 2012 The MVVM was introduced.



Last Update : 2012/02/10

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