Anees has posted 4 posts at DZone. View Full User Profile

Map Based Data Binding in GWT 1.6+

08.14.2009
| 9301 views |
  • submit to reddit
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.

Published at DZone with permission of its author, Anees Ur-rehman.

(Note: Opinions expressed in this article and its replies are the opinions of their respective authors and not those of DZone, Inc.)

Comments

Tom Schindl replied on Fri, 2009/08/14 - 5:31am

For none nested, none master detail this looks nice but what to do when you have more complex bindings, value conversion, validation, ... .

That's all solved by Eclipse Databinding which i ported to work with GWT and you could easily create your own domain observable but get all the cool stuff from Eclipse Databinding.

For a detailed discussion in this look:

The GWT-Databinding stuff is implemented an Eclipse Project named UFaceKit and available under EPL

Mladen Girazovski replied on Fri, 2009/08/14 - 6:12am

The concept is known as (Data-)TransferObject, Maps are a spezialized form of it, GenericTransferObjects.

Have a look at Dozer, Framework that saves you from writing a lot of boilerplate code for TransferObjectAssemblers.

Anees Ur-rehman replied on Sat, 2009/08/15 - 5:09am

Hi Tom,

I already checked and worked with these liberaries. I created this map based binding framework to
support the creation and binding of the widgets dynamically based on Database driven
widget creation for the forms. If we change the number of properties for the forms we do not
need to alter the POJO / DTO [:)] , as corrected by Ms. MGIRA, and create /alter widgets and
their bindings. Other important feature is for the developers who are working on swing
frameworks and they are familiar with jgoodies ValueModel. They will feel at home with same
ValueModel concept of binding the widgets with data.
Hi ms. mgira to be frank I am unable to understand your comment, can u precisely explain your
concerns or anything else. I still not able to understand for what purpose I should use Dozer as it
is just a java Bean to Java Bean mapper.

Mladen Girazovski replied on Sat, 2009/08/15 - 5:41am

Hi Annes, actually it's Mr. MGira ;) But no offence taken.

No concerns from my side, in fact i just wanted  to add the general name of the concept.

Dozer can also map complex Types to Maps and back, without having to write too much boiler plate code to create the DTOs, i think that was a concern of Tom.

Tom Schindl replied on Mon, 2009/08/17 - 2:53am in response to: Anees Ur-rehman

Well that's what I don't fully understand because you could have written your IProperties implementation on top of your java.util.Map within say 2 hours.

The point about the Swing developers and reusing their knowlegde is correct but I hope we can position Eclipse Databinding in the Swing space as well next year (many Observables are already there but we would need someone working on it exclusively). The problem with this is more of a political than a technical one (or rather an old preconception) because many Swing developers think whenever they use something from Eclipse they need to give up Swing and use SWT which is not true for many projects developed at Eclipse and one of the reasons I started UFaceKit.

The Eclipse Ecosystem has to offer a lot of cool stuff for all Swing, Qt-Jambi, GWT developers out there like Databinding, EMF, Equinox to name some of them. Even E4 could be used by Swing developers as an Rich Client Application platform because the Widget technology is completely plugable.

Comment viewing options

Select your preferred way to display the comments and click "Save settings" to activate your changes.