Start line:  
End line:  

Snippet Preview

Snippet HTML Code

Stack Overflow Questions
  /* AnnotateDataBinder.java
  
  {{IS_NOTE
  	Purpose:
  		
  	Description:
  		
  	History:
  		Thu Nov 16 13:22:37     2006, Created by Henri Chen
 }}IS_NOTE
 
 Copyright (C) 2006 Potix Corporation. All Rights Reserved.
 
 {{IS_RIGHT
 }}IS_RIGHT
 */
 package org.zkoss.zkplus.databind;
 
 import java.util.List;
 import java.util.Map;
 

The DataBinder that reads ZUML annotations to create binding info.

You have two ways to annotate ZK components. For ZK data binding, you can use <a:bind> annotation expression or

Author(s):
Henri Chen
Since:
2.4.0 Supporting @{...} annotations.
3.0.0 Supporting multiple events of save-when tag and validation phase.
See also:
AnnotateDataBinderInit
DataBinder
:
...} annotaion expression.

To use <a:bind> annotation expression, in the ZUML page you must declare the XML namespace, xmlns:a="http://www.zkoss.org/2005/zk/annotation" first. Then declare <a:bind> before component to make the annotation.

However, since ZK 2.4, you can choose to annotate directly on the component attribute with intuitive @{...} expression.

For example, the following annotation associates the attibute "value" of the component "textbox" to the bean's value "person.address.city".

<a:bind> way:

 <a:bind value="person.address.city"/>
 <textbox/>
 

@{...} way:

 <textbox value="@{person.address.city}"/>
 

The @{...} pattern tells the ZUML parser that this is for annotation.

You can put more metainfo inside the <a:bind> or @{...} so this DataBinder knows what to do. The complete format is like this:

<a:bind> way:

 <a:bind attrY="bean's value;[tag:expression]..."/>
 <componentX/>
 

@{...} way:

 <componentX attrY="@{bean's value,[tag='expression']...}"/>
 

This associates the componentX's attribute attrY to the bean's value. The bean's value is something in the form of beanid.field1.field2... You can either call DataBinder.bindBean(java.lang.String,java.lang.Object) to bind the beanid to a real bean object or you can neglect it and this DataBinder would try to find it from the variables map via (org.zkoss.zk.ui.Component.getVariable(java.lang.String,boolean) method. That is, all those variables defined in zscript are accessible by this DataBinder. Note that you can choose either two formats of annotations as your will and you can even hybrid them together though it is not generally a good practice.

The tag:expression or tag='expression' is a generic form to bind more metainfo to the attrY of the componentX. The currently supported tags includes "load-when", "save-when", "access", and "converter".

  • load-when. You can specify the events concerned when to load the attribute of the component from the bean. Multiple definition is allowed and would be called one by one. For example, the following code snip tells DataBinder that the attribute "value" of Label "fullName" will load from "person.fullName" when the Textbox "firstName" or "lastName" fire "onChange" event.

    The <a:bind> way that declare in front of the Component:

     <a:bind value="person.firstName"/>
     <textbox id="firstname"/>
    
     <a:bind value="person.lastName"/>
     <textbox id="lastname"/>
    
     <a:bind value="person.fullName; load-when:firstname.onChange; load-when:lastname.onChange"/>
     <label id="fullname"/>
     

    Or the @{...} way that specify directly on the Component's attribute:

     <textbox id="firstname" value="@{person.firstName}"/>
     <textbox id="lastname" value="@{person.lastName}"/>
     <label id="fullname" value="@{person.fullName, load-when='firstname.onChange,lastname.onChange'}"/>
     
  • save-when. You can specify the events concerned when to save the attribute of the component into the bean. Since ZK version 3.0.0, you can specify multiple events in save-when tag (i.e. before ZK 3.0.0, you can specify only one event). The events specified, if fired, will trigger this DataBinder to save the attribute of the component into the specified bean. For example, the following code snip tells DataBinder that the attribute "value" of Textbox "firstName" will save into "person.firstName" when the Textbox itself fire "onChange" event.

    The <a:bind> way that declare in front of the Component:

     <a:bind value="person.firstName; save-when:self.onChange"/>
     <textbox id="firstName"/>
     

    Or the @{...} way that specify directly on the Component's attribute:

     <textbox id="firstName" value="@{person.firstName, save-when='self.onChange'}"/>
     

    However, you don't generally specify the save-when tag. If you don't specify it, the default events are used depends on the natural charactieric of the component's attribute as defined in lang-addon.xml. For example, the save-when of Label.value is default to none while that of Textbox.value is default to self.onChange. That is, the following example is the same as the above one.

    The <a:bind> way that declare in front of the Component:

     <a:bind value="person.firstName"/>
     <textbox id="firstName"/>
     

    Or the @{...} way that specify directly on the Component's attribute:

     <textbox id="firstName" value="@{person.firstName}"/>
     

    On the other hand, you might not specify the save-when tag nor you want the default events to be used. Then you can specify a "none" keyword or simply leave empty to indicate such cases.

     <a:bind value="person.firstName; save-when:none;"/>
     <textbox id="firstName"/>
     
    or
     <a:bind value="person.firstName; save-when: ;"/>
     <textbox id="firstName"/>
     
    or
     <textbox id="firstName" value="@{person.firstName, save-when='none'}"/>
     
    or
     <textbox id="firstName" value="@{person.firstName, save-when=''}"/>
     
  • Since 3.1, if you specify some tags other than the supported tags, they will be put into an argument Map and is stored as a component attribute "bindingArgs". e.g. Also from 3.1, we start to support the "distinct" concept for collection components with "model" attribute(i.e. Grid, Listbox, Comobobox, etc.). You can specify as follows to tell the Data Binder that there might be one same object in multiple entries.

    
     <grid model="@{persons, distinct=false}" ...>
        ...
     </grid>
     

    or

    
     <a:bind model="persons; distinct:false"/>
     <grid ...>
        ...
     </grid>
     

    The default value for distinct is "true". However, if you specify distinct=false, the Data Binder will scan the whole ListModel to find out all items with the specified objects (thus worse performance if a big ListModel).

    Since 3.0.0, DataBinder supports validation phase before doing a save. Note that a DataBinder "save" is triggered by a component event as specified on the "save-when" tag. Before doing a save, it first fires an onBindingSave event to each data-binding component and then it fires an onBindingValidate event to the event triggering component before really saving component attribute contents into bean's property. So application developers get the chance to handle the value validation before saving. In the following example when end user click the "savebtn" button, an "onBindingSave" is first fired to "firtName" and "lastName" textboxes and then an "onBindingValidate" is fired to "savebtn" button. Application developers can register proper event handlers to do what they want to do.

     <textbox id="firstName" value="@{person.firstName, save-when="savebtn.onClick"}" onBindingSave="..."/>
     <textbox id="lastName" value="@{person.lastName, save-when="savebtn.onClick"}" onBindingSave="..."/>
     <button id="savebtn" label="save" onBindingValidate="..."/>
     

    Note that the original textbox constraint mechanism is still there. This DataBinder validation phase is an add-on feature that can be applied to all components and attributes that use data binding mechanism.

  • access. You can set the access mode of the attrY of the componentX to be "both"(load/save), "load"(load Only), "save"(save Only), or "none"(neither). Multiple definition is NOT allowed and the later defined would override the previous defined one. The access mode would affects the behavior of the DataBinder's loadXxx and saveXxx methods. The DataBinder.loadAll() and DataBinder.loadComponent(org.zkoss.zk.ui.Component) would load only those attributes with "both" or "load" access mode. The DataBinder.saveAll() and DataBinder.saveComponent(org.zkoss.zk.ui.Component) would save only those attributes with "both" or "save" access mode. If you don't specify it, the default access mode depends on the natural characteristic of the component's attribute as defined in lang-addon.xml. For example, Label.value is default to "load" access mode while Textbox.value is default to "both" access mode. For example, the following code snips tells DataBinder that Textbox "firstName" would allowing doing save into bean only not the other way.

    The <a:bind> way that declare in front of the Component:

     <a:bind value="person.firstName;access:save;"/>
     <textbox id="firstName"/>
     

    Or the @{...} way that specify directly on the Component's attribute:

     <textbox id="firstName" value="@{person.firstName, access='save'}"/>
     
  • converter. You can specify the class name of the converter that implments the TypeConverter interface. It is used to convert the value between component attribute and bean field. Multiple definition is NOT allowed and the later defined would override the previous defined one. Most of the time you don't have to specify this since this DataBinder supports converting most commonly used types. However, if you specify the TypeConverter class name, this DataBinder will new an instance and use it to cast the class.
public class AnnotateDataBinder extends DataBinder {
	private static final long serialVersionUID = 200808191510L;

Constructor that read all binding annotations of the components inside the specified desktop.

Parameters:
desktop the ZUML desktop.
	public AnnotateDataBinder(Desktop desktop) {
		this(desktoptrue);
	}

Constructor that read all binding annotations of the components inside the specified page.

Parameters:
page the ZUML page.
	public AnnotateDataBinder(Page page) {
		this(pagetrue);
	}

Constructor that read all binding annotations in the components inside the specified component (inclusive).

Parameters:
comp the ZUML component.
	public AnnotateDataBinder(Component comp) {
		this(comptrue);
	}

Constructor that read all binding annotations of the given components array.

Parameters:
comps the Component array.
Since:
3.0.0
	public AnnotateDataBinder(Component[] comps) {
		this(compstrue);
	}

Constructor that read all binding annotations of the components inside the specified desktop.

Parameters:
desktop the ZUML desktop.
defaultConfig whether load default binding configuration defined in lang-addon.xml
	public AnnotateDataBinder(Desktop desktopboolean defaultConfig) {
		setDefaultConfig(defaultConfig);
		for (final Iterator	it = desktop.getComponents().iterator(); it.hasNext(); ) {
		}			
	}

Constructor that read all binding annotations of the components inside the specified page.

Parameters:
page the ZUML page.
defaultConfig whether load default binding configuration defined in lang-addon.xml
	public AnnotateDataBinder(Page pageboolean defaultConfig) {
		setDefaultConfig(defaultConfig);
		for (final Iterator it = page.getRoots().iterator(); it.hasNext(); ) {
		}
	}

Constructor that read all binding annotations of the given component array.

Parameters:
comps the Component array
defaultConfig whether load default binding configuration defined in lang-addon.xml
Since:
3.0.0
	public AnnotateDataBinder(Component[] compsboolean defaultConfig) {
		setDefaultConfig(defaultConfig);
		for (int j = 0; j < comps.length; ++j) {
			loadAnnotations(comps[j]);
		}
	}

Constructor that read all binding annotations in the components inside the specified component (inclusive).

Parameters:
comp the ZUML component.
defaultConfig whether load default binding configuration defined in lang-addon.xml
	public AnnotateDataBinder(Component compboolean defaultConfig) {
		setDefaultConfig(defaultConfig);
	}
	private void loadAnnotations(Component comp) {
		final List children = comp.getChildren();
		for (final Iterator it = children.iterator(); it.hasNext(); ) {
			loadAnnotations((Componentit.next()); //recursive back
		}
	}
	}
	private void loadComponentPropertyAnnotationByAnnotName(Component compString annotName) {
		ComponentCtrl compCtrl = (ComponentCtrlcomp;
		final List props = compCtrl.getAnnotatedPropertiesBy(annotName);
		for (final Iterator it = props.iterator(); it.hasNext(); ) {
			final String propName = (Stringit.next();
			//[0] value, [1] loadWhenEvents, [2] saveWhenEvents, [3] access, [4] converter, [5] args
			final Object[] objs = loadPropertyAnnotation(comppropNameannotName);
			addBinding(comppropName, (Stringobjs[0], 
					(Listobjs[1], (Listobjs[2], (Stringobjs[3], (Stringobjs[4], (Mapobjs[5]);
		}
	}
	private void loadComponentAnnotation(Component comp) {
		loadComponentAnnotation(comp"default");
	}
	private void loadComponentAnnotation(Component compString annotName) {
		ComponentCtrl compCtrl = (ComponentCtrlcomp;
		Annotation ann = compCtrl.getAnnotation(annotName);
		if (ann != null) {
			Map attrs = ann.getAttributes();
			for(final Iterator it = attrs.entrySet().iterator(); it.hasNext();) {
				Entry me = (Entryit.next();
				String attr = (Stringme.getKey();
				//[0] bean value, [1 ~ *] tag:expression
				List expr = parseExpression((Stringme.getValue(), ";");
				if (expr == null || expr.get(0) == null) {
					throw new UiException("Cannot find any bean value in the annotation <a:bind "+attr+"=\"\"/> for component "+comp+", id="+comp.getId());
else {
					List tags = parseExpression((String)expr.get(0), ":");
					if (tags.size() > 1) {
						throw new UiException("bean value must be defined as the first statement in the annotation <a:bind "+attr+"=\"\"/> for component "+comp+", id="+comp.getId());
					}
				}
				List loadWhenEvents = null;
				List saveWhenEvents = null;
				String access = null;
				String converter = null;
				Map args = null;
				//process tags
				for(int j = 1; j < expr.size(); ++j) {
					List tags = parseExpression((String)expr.get(j), ":");
					if (tags == null) {
						continue//skip
					}
					if ("load-when".equals(tags.get(0))) {
						if (tags.size() > 1 && tags.get(1) != null) {
							loadWhenEvents = parseExpression((String)tags.get(1), ",");
else {
							loadWhenEvents.add();
						}
else if ("save-when".equals(tags.get(0))) {
						if (tags.size() > 1 && tags.get(1) != null) {
							saveWhenEvents = parseExpression((String)tags.get(1), ",");
else {
							saveWhenEvents.add();
						}
else if ("access".equals(tags.get(0))) {
						access = tags.size() > 1 ? (Stringtags.get(1) : ;
else if ("converter".equals(tags.get(0))) {
						converter = tags.size() > 1 ? (Stringtags.get(1) : ;
else {
						if (args == null) {
							args = new HashMap(1);
						}
						args.put(tags.get(0), tags.get(1));
					}
				}
				if (loadWhenEvents != null && loadWhenEvents.isEmpty()) {
					loadWhenEvents = null;
				}
				if (saveWhenEvents != null && saveWhenEvents.isEmpty()) {
					saveWhenEvents = null;
				}
				addBinding(compattr, (Stringexpr.get(0), loadWhenEventssaveWhenEventsaccessconverterargs);
			}
		}
	}
New to GrepCode? Check out our FAQ X