Wednesday, September 30, 2009
MY planned visit to my homeland - my beloved land
Thursday, August 13, 2009
Databinding in GWT
Binding is the most discussed issue in the GWT community. Everybody has different solution and different material to support their arguments. When we say binding we say POJO based binding. I am from school of thoughts who loved to see the client interface totally decoupled from the Business layer, passing Business Objects to the User Interface layer will destroy the concept of decoupling. We can achieve the concept of decoupling the UI layer from Business layer if we pass the Map based data from Business layer. Map is key/value pair system in which we consider ‘key’ as the name of domain property and value will be ‘Object’ associated with this property. In this article I will explain the mechanism of creating binding system for the UI widget with the Map based data. We had created the Mab2BeanConvertor utility class which will convert the POJO into HashMap and vice versa. This utility class will take the burden of transformation of the HashMap to POJO and POJO to HashMap.
Now I will explain how we achieved the binding with GWT. We follow the JGoodies style of ValueModel concept of holding the data, and Binder System which will bind the ValueModel to the UI component.
This system has three(3) number of classes i.e.
(i) GWTMapValueModel
(ii) GWTBinding
(iii) GWTWidgetDomainSynchronizer
1. GWTMapValueModel is the value model which holds the value for widget associated to some property of the bean i.e. key of Map.
2. GWTBinding is the main actor of this system which will bind the widget to a key of the map.
3. GWTWidgetDomainSynchronizer will act as the model to keep the widget and map synchronized.
Our GWTMapValueModel is shown below
import java.util.Map;
/**
* @author Anees
*
*/
public class GWTMapValueModel {
private String key;
private Map map;
private GWTWidgetDomainSynchronizer synchronizer ;
public GWTMapValueModel(String key, Map map){
this.key = key;
this.map = map;
public void setValue(final Object newValue){
Object oldValue = map.get(key);
map.put(key, newValue);
firePropertyChange(oldValue, newValue);
public Object getValue(){
return map.get(key);
}
public Map getMap(){
return map;
}
protected String getKey(){
return key;
}
public void createBindableSynch(GWTWidgetDomainSynchronizer synchronizer){
this.synchronizer = synchronizer;
}
/**
*
* @param oldValue
* @param newValue
*/
private void firePropertyChange(Object oldValue, Object newValue){
synchronizer.processGWTMapValueModelChange(oldValue, newValue);
}
}
the GWTBinding class is shown below with full source code for your guidance:
package com.eagle.coders.core.web.gwt.client.ui.bindings;
import com.eagle.coders.core.web.gwt.client.ui.form.widgets.RadioButtonWidget;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
import com.google.gwt.event.logical.shared.ValueChangeEvent;
import com.google.gwt.event.logical.shared.ValueChangeHandler;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.PasswordTextBox;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.datepicker.client.DateBox;
/**
* @author Anees
*
*/
public class GWTBinding {
private GWTMapValueModel valueModel;
private static GWTWidgetDomainSynchronizer sync ;
public static void bindTextBox(TextBox textBox, GWTMapValueModel valueModel, String
caseType){
sync = new GWTWidgetDomainSynchronizer(valueModel, textBox);
textBox.addValueChangeHandler(new
BindingValueChangeHandler(valueModel, caseType));
}
public static void bindPasswordBox(PasswordTextBox passwordBox, GWTMapValueModel
valueModel){
sync = new GWTWidgetDomainSynchronizer(valueModel, passwordBox);
passwordBox.addValueChangeHandler(new
BindingValueChangeHandler(valueModel, null));
}
public static void bindRadioButton(RadioButtonWidget radioButton, GWTMapValueModel
valueModel){
sync = new GWTWidgetDomainSynchronizer(valueModel, radioButton);
radioButton.addValueChangeHandler(new
BindingValueChangeHandler(valueModel, null));
}
public static void bindCheckBox(final CheckBox checkBox, GWTMapValueModel valueModel){
sync = new GWTWidgetDomainSynchronizer(valueModel, checkBox);
checkBox.addValueChangeHandler(new
BindingValueChangeHandler(valueModel, null));
}
public static void bindComboBox(final ListBox listBox, GWTMapValueModel valueModel){
sync = new GWTWidgetDomainSynchronizer(valueModel, listBox);
listBox.addChangeHandler(new
BindingChangeHandler(valueModel));
}
public static void bindTextAreaWidget(final TextArea textArea, GWTMapValueModel valueModel){
sync = new GWTWidgetDomainSynchronizer(valueModel, textArea);
textArea.addValueChangeHandler(new
BindingValueChangeHandler(valueModel, null));
}
public static void bindDateBoxWidget(final DateBox dateBox, GWTMapValueModel valueModel){
sync = new GWTWidgetDomainSynchronizer(valueModel, dateBox);
dateBox.addValueChangeHandler(new
BindingValueChangeHandler(valueModel, null));
}
/**
* @help for ListBox Binding
* @author Anees
*
* @param
*/
private static class BindingChangeHandler implements
ChangeHandler{
private T valueModel;
public BindingChangeHandler(T valueModel){
this.valueModel = valueModel;
}
@Override
public void onChange(ChangeEvent event) {
if(event.getSource() instanceof ListBox){
int index = ((ListBox)event.getSource()).getSelectedIndex();
String value = ((ListBox)event.getSource()).getItemText(index);
processListBox(value, valueModel);
}
}
}
/**
*
* @author Anees
*
* @param
*/
private static class BindingValueChangeHandler implements ValueChangeHandler{
private T valueModel;
private String caseType;
public BindingValueChangeHandler(T valueModel, String caseType){
this.valueModel = valueModel;
this.caseType = caseType;
}
@Override
public void onValueChange(ValueChangeEvent event) {
if(event.getSource() instanceof TextBox && !(event.getSource() instanceof
PasswordTextBox))
sync.processTextBox(event.getValue(), caseType ,valueModel);
else if(event.getSource() instanceof PasswordTextBox)
sync.processPasswordTextBox(event.getValue(), valueModel);
else if(event.getSource() instanceof RadioButtonWidget)
sync.processRadioButton(event.getValue(),(RadioButtonWidget)event.getSource()
,valueModel);
else if(event.getSource() instanceof CheckBox)
sync.processCheckBox(event.getValue(), valueModel);
else if(event.getSource() instanceof DateBox)
sync.processDateBox(event.getValue(), valueModel);
}
}
}
Now the moderator class which will synchronize the state of HashMap and Widgets i.e.
GWTWidgetDomainSynchronizer :
package com.eagle.coders.core.web.gwt.client.ui.bindings;
import java.util.List;
import com.eagle.coders.core.web.gwt.client.ui.form.widgets.RadioButtonWidget;
import com.google.gwt.user.client.ui.CheckBox;
import com.google.gwt.user.client.ui.ListBox;
import com.google.gwt.user.client.ui.PasswordTextBox;
import com.google.gwt.user.client.ui.TextArea;
import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
/**
* @author Anees
*
*/
public class GWTWidgetDomainSynchronizer {
private String propertyName;
private GWTMapValueModel valueModel;
private Widget widget;
public GWTWidgetDomainSynchronizer(GWTMapValueModel valueModel, Widget widget){
this.propertyName = valueModel.getKey();
this.valueModel = valueModel;
this.widget = widget;
this.valueModel.createBindableSynch(this);
synchOnBinding();
}
private void synchOnBinding(){
processGWTMapValueModelChange(null, valueModel.getValue());
}
/**
*
* @param valueModel
* @param property
*/
public void processGWTMapValueModelChange(Object oldValue, Object newValue){
if(!(widget instanceof PasswordTextBox) && widget instanceof TextBox){
((TextBox)widget).setText(newValue.toString());
}else if(!(widget instanceof RadioButtonWidget)&& widget instanceof CheckBox){
CheckBox checkBox = (CheckBox)widget;
if(!newValue.equals(checkBox.getValue())){
checkBox.setValue(Boolean.valueOf(newValue.toString()));
}
}else if(widget instanceof ListBox){
// TODO: as listbox can have multiple selected data
if(newValue instanceof List){
}
}else if(widget instanceof RadioButtonWidget){
RadioButtonWidget radioButton =(RadioButtonWidget)widget;
if(radioButton.getChoice().equals(newValue)){
radioButton.setValue(true);
}else
radioButton.setValue(false);
}else if(widget instanceof TextArea){
((TextArea)widget).setText(newValue.toString());
}else if(widget instanceof PasswordTextBox){
((PasswordTextBox)widget).setText(newValue.toString());
}
}
/**
*
* @param
* @param value
* @param valueModel
*/
public void processTextBox(Object value,String
caseType ,T valueModel ){
String cValue = "";
if(null != caseType){
if(caseType.equals("upper")){
cValue = value.toString().toUpperCase();
}else if (caseType.equals("lower")){
cValue = value.toString().toLowerCase();
}else if (caseType.equals("mixed")){
cValue = value.toString();
}
valueModel.setValue(cValue);
}else {
valueModel.setValue(value);
}
}
/**
*
* @param
* @param value
* @param valueModel
*/
public void processPasswordTextBox(Object value,
T valueModel ){
valueModel.setValue(value);
}
/**
*
* @param
* @param value
* @param valueModel
*/
public void processRadioButton(Object
value, RadioButtonWidget radioButton ,T valueModel ){
valueModel.setValue(radioButton.getChoice());
}
/**
*
* @param
* @param value
* @param valueModel
*/
public void processCheckBox(Object value, T valueModel
){
valueModel.setValue(value);
}
/**
*
* @param
* @param value
* @param valueModel
*/
public void processListBox(Object value, T valueModel
){
valueModel.setValue(value);
}
/**
*
* @param
* @param value
* @param valueModel
*/
public void processDateBox(Object value, T valueModel
){
valueModel.setValue(value);
}
}
We have tested with all kind of widgets provided by GWT1.6.x+. There is one drawback with this
approach we can use the GWT1.6+ compatible provided widgets as they are based on
new EventModel i.e. Handler's rather than Listeners.
Example :
Map map = new HashMap();
map.put("firstName","");
GWTMapValueModel valueModel = new GWTMapValueModel("firstName", map);
TextBox firstNameWidget = new TextBox();
GWTBinding.bindTextBox(firstNameWidget, valueModel);
Hopefully this approach will solve many of the binding issues in Google Web Toolkit.