Platinum Partner
java,frameworks,gwt bindings,gwt map based bindings

Map Based Data Binding in GWT 1.6+

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.

  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.

{{ tag }}, {{tag}},

{{ parent.title || parent.header.title}}

{{ parent.tldr }}

{{ parent.urlSource.name }}
{{ parent.authors[0].realName || parent.author}}

{{ parent.authors[0].tagline || parent.tagline }}

{{ parent.views }} ViewsClicks
Tweet

{{parent.nComments}}