Saturday, September 29, 2007

The marriage of GWT with Eclipse-Technologies

I'm currently on the track to explore GWT and started just this morning with it. The first thing I found missing is something like the Viewers-Concept from JFace.

I don't want to learn new things if the one I'm familiar with would make my life much easier. JFace-Viewers is one of my main working areas in Eclipse-land. So I sat down and took a closer look and after 1 hour of work my GWT-code looks like this:


package at.bestsolution.eclipse.jface.example.client;

import java.util.ArrayList;

import org.gwtwidgets.client.util.SimpleDateParser;

import at.bestsolution.eclipse.jface.client.viewers.TreeViewer;
import at.bestsolution.eclipse.jface.example.client.model.Family;
import at.bestsolution.eclipse.jface.example.client.model.Person;
import at.bestsolution.eclipse.jface.example.client.ui.MyContentProvider;
import at.bestsolution.eclipse.jface.example.client.ui.MyLabelProvider;

import com.google.gwt.core.client.EntryPoint;
import com.google.gwt.user.client.ui.RootPanel;
import com.google.gwt.user.client.ui.Tree;

/**
* Entry point classes define onModuleLoad().
*/
public class JFaceExampleApp implements EntryPoint {

/**
* This is the entry point method.
*/
public void onModuleLoad() {

Tree tree = new Tree();
TreeViewer viewer = new TreeViewer(tree);
viewer.setLabelProvider(new MyLabelProvider());
viewer.setContentProvider(new MyContentProvider());
viewer.setInput(createInput());

RootPanel.get().add(tree);
}

private ArrayList createInput() {
SimpleDateParser parser = new SimpleDateParser("yyyy-MM-dd");
ArrayList list = new ArrayList();

Family fam = new Family("Schindl");
Person p = new Person("Tom",parser.parse("1979-05-01"),fam);
p = new Person("Tina",parser.parse("1980-10-14"),fam);
p = new Person("Laura",parser.parse("1991-08-07"),fam);

list.add(fam);

fam = new Family("Hoppichler");
p = new Person("Andreas",parser.parse("1977-11-06"),fam);
p = new Person("Franzi",parser.parse("1978-11-02"),fam);

list.add(fam);

return list;
}
}


And the respective Viewer-classes like this:


package at.bestsolution.eclipse.jface.example.client.ui;

import java.util.ArrayList;

import at.bestsolution.eclipse.jface.client.viewers.ITreeContentProvider;
import at.bestsolution.eclipse.jface.client.viewers.Viewer;
import at.bestsolution.eclipse.jface.example.client.model.Family;
import at.bestsolution.eclipse.jface.example.client.model.Person;

public class MyContentProvider implements ITreeContentProvider {

public Object[] getChildren(Object parentElement) {
if( parentElement instanceof Family ) {
return ((Family)parentElement).getList().toArray();
}
return new Object[0];
}

public Object getParent(Object element) {
if( element instanceof Person ) {
return ((Person)element).getFamily();
}

return null;
}

public boolean hasChildren(Object element) {
return getChildren(element).length > 0;
}

public Object[] getElements(Object inputElement) {
return ((ArrayList)inputElement).toArray();
}

public void dispose() {

}

public void inputChanged(Viewer viewer, Object oldInput, Object newInput) {

}

}



package at.bestsolution.eclipse.jface.example.client.ui;

import org.gwtwidgets.client.util.SimpleDateFormat;

import at.bestsolution.eclipse.jface.client.viewers.ILabelProvider;
import at.bestsolution.eclipse.jface.example.client.model.Family;
import at.bestsolution.eclipse.jface.example.client.model.Person;

public class MyLabelProvider implements ILabelProvider {
private SimpleDateFormat format = new SimpleDateFormat("dd.MM.yyyy");

public String getText(Object element) {
if( element instanceof Family ) {
return ((Family)element).getName();
} else if( element instanceof Person ) {
return ((Person)element).getName() + " ("+format.format(((Person)element).getBirthday())+")";
}

return "";
}

}


Now you think this is cool? Because this was to trivial I thought I'll have to go further and after ~ 6h I had ported the the core-parts from the JFace-Databinding-Framework to GWT! So my UI-Binding-Code looks like this now:


// ....
TextBox box = new TextBox();

TextBoxObservableValue targetValue = new TextBoxObservableValue(box,TextBoxObservableValue.Change);
IObservableValue modelValue = PEMFObservablesFactory.observeDetailValue(Realm.getDefault(), value, "id", String.class);

DataBindingContext ctx = new DataBindingContext();
UpdateValueStrategy targetToModel = new UpdateValueStrategy();
targetToModel.setConverter(new IConverter() {

public Object convert(Object fromObject) {
return fromObject.toString();
}

public Object getFromType() {
return null;
}

public Object getToType() {
return null;
}

});

UpdateValueStrategy modelToTarget = new UpdateValueStrategy();
modelToTarget.setConverter(new IConverter() {

public Object convert(Object fromObject) {
return fromObject.toString();
}

public Object getFromType() {
return null;
}

public Object getToType() {
return null;
}

});
ctx.bindValue(targetValue, modelValue, targetToModel, modelToTarget);


The GWT-Compiler translates all into JS-Code and because there's no Reflection-API available in this space I had to invent my own concept which I lend from EMF hence the name PEMF (for PoorEMF). Without question much of the Databinding-Framework is not working but at this stage I don't mind. The good thing is that all those low-level modules use fairly only classes already available from GWT so porting the whole framework is only a matter of time.

9 comments:

Philippe said...

That is awesome. Would there be a way to make plug compatible?

Boris Bokowski said...

Excellent. So next time when someone asks why we restrict ourselves to CDC/Foundation 1.0, I can point to this!

Tom said...

Well I don't know but when all those things are broken down to the Java-classes implemented by GWT or you reimplement those things it seems to be do able. Things that won't work are the one like OSGi because there's no class-loader, ... in JavaScript.

Boris Bokowski said...

After thinking a bit more about this - how much would we have to change in the core data binding classes to make it (or a subset) work with GWT? Any chance we can do this with just one codebase, and in an API-compatible way?

Tom said...

having MVC + Databinding available through GWT would be cool and as discussed on IRC, I'm going to have a look at EMF 3.2 to see how this can also be compiled with GWT. This would make writing GWT applications a piece of cake :-)

Tom said...

Hi Boris, I think an API compatible way is possible but would mean that all current classes have to be moved into a package called "org.eclipse.core.databinding.client.*"
and the current classes are delegating to these classes and casting converting the internal types to the one externally visible which might have a big performance impact.

I think the better way is that we make the current databinding (and classes it depends on in core and icu) compileable with GWT and then setup a automatic conversion tool which only has to replaces package names. This way we would have one code base to maintain because the GWT implementation is created with a set of automatic scripts.

The other possibility would be to findout how one could work-around the client-package limitation in GWT, but I think we can do this at any given point later on when the proof-of-concept implementation is ready.

dgirard said...

Just have a look on this article :
"GWT Databinding"
http://www.zenika.com/blog/2007/08/03/gwt-data-binding-english-version/

Tom said...

Thanks for the Pointer, looks interesting and to some extent the same I'm doing with Databinding but the scope of Databinding is a bit wider than the framework describe there. The most interesting thing is the how the automatic wrapper generation works.

Anonymous said...

This is a very nice article, I try to follow up for my example. OK, and I want to sugguest new best artical about GWT and Eclipse, That is Extjs-GWT on Eclipse which reduce development time.
I like this example and try to use extjs-gwt in eclipse project.
See more about Extjs-GWT on Eclipse by using Eclipse's plugin.
I think this Extjs-GWT article can help beginner to learn faster.