Notification"
(Created page with "{{ZKDevelopersReferencePageHeader}} = Overview = The binder is responsible for synchronizing data between View and ViewModel. Typically ViewModel is a POJO and has no reference ...") |
|||
Line 275: | Line 275: | ||
+ | |||
+ | =Version History= | ||
+ | {{LastUpdated}} | ||
+ | {| border='1px' | width="100%" | ||
+ | ! Version !! Date !! Content | ||
+ | |- | ||
+ | | 6.0.0 | ||
+ | | February 2012 | ||
+ | | The MVVM was introduced. | ||
+ | |} | ||
{{ZKDevelopersReferencePageFooter}} | {{ZKDevelopersReferencePageFooter}} |
Revision as of 01:41, 9 February 2012
Overview
The binder is responsible for synchronizing data between View and ViewModel. Typically 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.
Notify Change
ZK bind provides a set of Java annotation ( @NotifyChange, @DependsOn, @NotifyChangeDisabled ) 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.
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.
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 dependentcy 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 convinience. 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 can't not 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;
}
};
}
}
- About how to implement a converter, please refer to ZK Developer's Reference/MVVM/DataBinding/Converter.
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 BindUtils.postNotifyChange() 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.
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. |