Thursday, January 10, 2008

One month of UFacekit-Development

A month ago James Strachan and I started hacking on a project we named UFacekit.

We both faced the need to write datacentric applications for different deployment environments (Desktop (Swing/SWT), GWT (MyGWT/GWT-Ext), ...) and we thought there must be an easy and fast way to come up with something that makes our day job easier. At this very moment UFacekit was born.

There are different things we are targeting with the project.

  1. Provide a uniform Facade above widget implementations (SWT,Swing,GWT,...)

  2. Promote the use of Eclipse-Databinding (and other related technologies e.g. EMF) outside Eclipse-World (Swing,GWT, ...) and provide a highlevel API above the low-level databinding API

  3. Provide reuseable Bundles which can be used independently
    • you are only interested in Swing-Databinding but still want to use the native Swing-API? - UFacekit provides an indepented Swing-Observable bundle for you
    • you want to use an alternative Bean-like implementation not relying on reflection and EMF is too heavy for you? - UFacekit provides an independent implementation for you named UBean



Instead of writing an SWT-Port for GWT/Swing (I know for Swing there's already an SWT port) we decided to go the Facade/Factory way and started hacking and the progress we made in this very short period of time (we are only working on it in the evening) is more than amazing.

Take a look at the following Screenshots they show what's already possible with the current sources.





They look fairly identical (the only reason they not look completely the same is that I haven't ported the GridLayout from SWT to Swing but relying on Mig-Layout).

They are full of features you need many lines code without a framework doing the nifity bits for you (e.g. MasterDetail + Field-Dependencies):

  • Master-Detail support

  • Model-UI-Binding

  • Field-Dependencies (Country/Federalstate)



Setting up this UI needs:

  • ~ 80 Lines of Widget-Toolkit-Independent code

  • ~ 20 Lines of Widget-Dependent code (= the start of the application which is currently not abstracted)



The amazing thing is that both UIs are made up by the same source code:


public void createFormUI(UIComposite root) {
UIFactory ui = root.getFactory();

UBeanForm listForm = new UBeanForm();
final UBeanForm detailForm_1 = new UBeanForm();

UIComposite containerComposite = ui.newComposite(root, null, ui.newGridLayout(2));

TableColumn[] columns = {
new TableColumn("People", new NameLabelProvider())
};

UITable table = ui.newTable(listForm, containerComposite, new GridLayoutData(200, GridLayoutData.DEFAULT, GridLayoutData.ALIGN_BEGINNING, GridLayoutData.ALIGN_FILL, false, true), listForm.detailList(AddressBook.PEOPLE, Collection.class), columns);

// ---------------------

UIComposite detailComposite = ui.newComposite(containerComposite, new GridLayoutData(GridLayoutData.ALIGN_FILL, GridLayoutData.ALIGN_FILL, true, true), ui.newGridLayout(2));
ui.newLabel(detailComposite, new GridLayoutData(GridLayoutData.ALIGN_END, GridLayoutData.ALIGN_CENTER), "ID");
ui.newTextField(detailForm_1, detailComposite, new GridLayoutData(GridLayoutData.ALIGN_FILL, GridLayoutData.ALIGN_FILL, true, false), detailForm_1.detailValue(Person.ID, int.class));

ui.newLabel(detailComposite, new GridLayoutData(GridLayoutData.ALIGN_END, GridLayoutData.ALIGN_CENTER), "Name");
ui.newTextField(detailForm_1, detailComposite, new GridLayoutData(GridLayoutData.ALIGN_FILL, GridLayoutData.ALIGN_FILL, true, false), detailForm_1.detailValue(Person.NAME, String.class));

ui.newLabel(detailComposite, new GridLayoutData(GridLayoutData.ALIGN_END, GridLayoutData.ALIGN_CENTER), "Location");
ui.newTextArea(detailForm_1, detailComposite, new GridLayoutData(GridLayoutData.ALIGN_FILL, GridLayoutData.ALIGN_FILL, true, false), detailForm_1.detailValue(Person.LOCATION, String.class));

ui.newLabel(detailComposite, new GridLayoutData(GridLayoutData.ALIGN_END, GridLayoutData.ALIGN_CENTER), "Country");
UICombo combo = ui.newCombo(detailForm_1, detailComposite, new GridLayoutData(GridLayoutData.ALIGN_FILL, GridLayoutData.ALIGN_FILL, true, false), detailForm_1.detailValue(Person.COUNTRY, Country.class), ModelHelper.createWritableList(getModel().getCountries(), Country.class), new CountryLabelProvider());

ui.newLabel(detailComposite, new GridLayoutData(GridLayoutData.ALIGN_END, GridLayoutData.ALIGN_BEGINNING), "Federalstate");
ui.newDependentListBox(detailForm_1, detailComposite, new GridLayoutData(GridLayoutData.DEFAULT,120,GridLayoutData.ALIGN_FILL, GridLayoutData.ALIGN_FILL, true, false), detailForm_1.detailValue(Person.FEDERAL_STATE, FederalState.class), (IObservableList) detailForm_1.detailList(Country.FEDERAL_STATES, Collection.class).createObservable(combo.getSelectionObservable()), new FederalStateLabelProvider());

WritableValue value = ModelHelper.createWritableValue(getModel());
listForm.bind(value);

IObservableValue detailObservable = table.getSelectionObservable();
detailForm_1.bind(detailObservable);

UBeanForm detailForm_2 = new UBeanForm();

columns = new TableColumn[] {
new TableColumn("ID", new IDLabelProvider()),
new TableColumn("Name", new NameLabelProvider()),
new TableColumn("Location", new LocationLabelProvider())
};

ui.newLabel(detailComposite, new GridLayoutData(GridLayoutData.ALIGN_END, GridLayoutData.ALIGN_CENTER), "");
ui.newTable(detailForm_2, detailComposite, new GridLayoutData(GridLayoutData.ALIGN_FILL,GridLayoutData.ALIGN_FILL,true,true), detailForm_2.detailList(Person.FRIENDS, Collection.class), columns);
detailForm_2.bind(detailObservable);
}


  public void run(final String uiMethod) {
Display display = new Display();

Realm.runWithDefault(SWTObservables.getRealm(display), new Runnable() {
public void run() {
Shell shell = createUI(uiMethod);
shell.setSize(650, 500);
shell.setText("UFacekit - JFace-Demo ");
Display display = Display.getCurrent();
while (!shell.isDisposed()) {
if (!display.readAndDispatch()) {
display.sleep();
}
}
}
});
}

private Shell createUI(String uiMethod) {
final Shell shell = new Shell();
SWTComposite composite = new SWTComposite(shell,new JFaceFactory().newFillLayout());
// generic code as shown above


You can even more reduce the size and complexity of the source by using FormBuilder-classes we are going to provide to make creating standard-forms like this as simple as possible.

Before we are publishing a first cut we need to finish the GWT-implementation (GWT/MyGWT/GWT-Ext) but we (in this case James) are on a good way and we should have a release fairly soon. Naturally there are some gaps (e.g. missing widgets, API inconsistencies) but hey the project just got 1 month so I hope you don't mind.

Did this spot your interest? Come and join our google group and share your ideas with us.

1 comment:

dennis said...

i applaud your initiative--nice work, great progress, thus far!