SmartGWT 1.2

Aus dem Blog http://www.jroller.com/sjivan/entry/smartgwt_1_2_released:

SmartGWT 1.2 has been released. I have added several new samples to the Showcase including a real world mini-application. The other new samples can be found under the „New Samples“ side nav item.

This is a feature-complete production ready release. There are only around 20 enhancement requests and 20 low / medium priority issues in the SmartGWT tracker which I think is pretty telling for such a comprehensive library. I hope to get to them over the weeks to come.

Here are some of the key features of this release :

  • GWT 1.7 is fully supported and integration with GWT widgets has been improved significantly. Along with standard GWT widgets, you can now easily add Google Maps or even a Google Visualization Chart to your SmartGWT application
  • Hosted mode performance improvements
  • Fully implemented the highly requested ResultSet API.
  • ListGrid performance improvements
  • Full Support for Safari 4.x
  • Support for Grid editing with all cell editors active
  • Auto-loading of dependent XML schema for loadWSDL() and loadXMLSchema()
  • Extended WebService APIs to allow simplified input data and allow setting SOAP headers
  • Numerous enhancements. See the detailed API Changes document. Around 35 additional enhancements and bug fixes that were logged in tracker
  • Official Maven Repository
  • Enhancements to RPC Tab in Developer Console (shows component that initiated request, if applicable)

Looking ahead there are several exciting new features that are going to be in the next release. Deep level of customization of pretty much any widget is going to be supported. For example you’ll be able to fully customize grid headers, provide your own widget implementation to use as a Tile in a TileGrid, or even customize pretty much any aspect of the Calendar component.

Java Datastore API für die Google App Engine

Um in der Google Cloud Daten zu speichern bietet Google drei API an: JPA, JDO und eine Low-Level-API. Infos dazu gibt liefert http://code.google.com/intl/de/appengine/docs/java/datastore/. JPA und JDO basieren im Kern auf der Low-Level API, die auf die http://en.wikipedia.org/wiki/BigTable zurückgreift.

Für JPA und JDO gibt es selbst von Google viele Beispiele, aber die Low-Level-API ist nicht so gut dokumentiert und selbst das Beispielprogramm in der JavaDoc enthält Fehler. Zeit daher, ein sehr einfaches Beispiel mit einer 1:n Relationen anzugehen.

Im Mittelpunkt der API steht der DatastoreService, der ein bisschen an den EntityManager von JPA erinnert. Er bietet Methoden für die CRUD-Operationen. Mein Beispiel geht schon ein bisschen “pseudo-ORM” an die Aufgabe ran, einer Person Nachrichten zuordnen zu können:

Die Personen-Klasse:

package com.tutego.server.entity;

import java.util.ArrayList;
import java.util.List;

import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.Query;

public class Person
{
  private final static String ENTITY_NAME = „Person“;
  Entity personEntity;

  public enum Gender
  {
    MALE, FEMALE
  }

  public Person()
  {
    personEntity = new Entity( ENTITY_NAME );
  }

  private Person( Key key )
  {
    try
    {
      personEntity = DatastoreServiceFactory.getDatastoreService().get( key );
    }
    catch ( EntityNotFoundException e )
    {
    }
  }

  private Person( Entity entity )
  {
    personEntity = entity;
  }

  public static Person get( Key key )
  {
    return new Person( key );
  }

  public void setUsername( String username )
  {
    personEntity.setProperty( „username“, username );
  }

  public String getUsername()
  {
    return personEntity.getProperty( „username“ ).toString();
  }

  public void setGender( Gender gender )
  {
    personEntity.setProperty( „gender“, gender.toString() );
  }

  public Gender getGender()
  {
    return Gender.valueOf( personEntity.getProperty( „gender“ ).toString() );
  }

  public Key put()
  {
    return DatastoreServiceFactory.getDatastoreService().put( personEntity );
  }

  public static void deleteAll()
  {
    Query deleteAllQuery = new Query( ENTITY_NAME );

    for ( Entity entity : DatastoreServiceFactory.getDatastoreService().prepare( deleteAllQuery ).asIterable() )
      DatastoreServiceFactory.getDatastoreService().delete( entity.getKey() );
  }

  private static List<Person> executeQuery( Query query )
  {
    List<Person> result = new ArrayList<Person>();
    for ( Entity entity : DatastoreServiceFactory.getDatastoreService().prepare( query ).asIterable() )
      result.add( new Person( entity ) );
    return result; 
  }

  public static List<Person> findAllPersons()
  {
    Query query = new Query( ENTITY_NAME );

    return executeQuery( query );
  }

  public static List<Person> findPersonByGender( Gender gender )
  {
    Query query = new Query( ENTITY_NAME );
    query.addFilter( „gender“, Query.FilterOperator.EQUAL, gender.toString() );

    return executeQuery( query );
  }

  @Override
  public String toString()
  {
    return String.format( „Person[%s,%s]“, getUsername(), getGender() );
  }
}

Die Nachrichten-Klasse:

package com.tutego.server.entity;

import java.util.ArrayList;
import java.util.Date;
import java.util.List;

import com.google.appengine.api.datastore.DatastoreServiceFactory;
import com.google.appengine.api.datastore.Entity;
import com.google.appengine.api.datastore.EntityNotFoundException;
import com.google.appengine.api.datastore.Key;
import com.google.appengine.api.datastore.Query;

public class Message
{
  private final static String ENTITY_NAME = „Message“;

  private Entity messageEntity;

  public Message()
  {
    messageEntity = new Entity( ENTITY_NAME );
  }

  private Message( Key key )
  {
    try
    {
      messageEntity = DatastoreServiceFactory.getDatastoreService().get( key );
    }
    catch ( EntityNotFoundException e )
    {
    }
  }

  private Message( Entity entity )
  {
    messageEntity = entity;
  }

  public static Message get( Key key )
  {
    return new Message( key );
  }

  public void setText( String text )
  {
    messageEntity.setProperty( „text“, text );
  }

  public String getText()
  {
    return messageEntity.getProperty( „text“ ).toString();
  }

  public void setCreationTime( Date d )
  {
    messageEntity.setProperty( „creationtime“, „“ + d.getTime() );
  }

  public Date getCreationTime()
  {
    return new Date( Long.parseLong( messageEntity.getProperty( „creationtime“ ).toString() ) );
  }

  public void setReceiver( Person p )
  {
    messageEntity.setProperty( „person_fk“, p.personEntity.getKey() );
  }

  public Key put()
  {
    return DatastoreServiceFactory.getDatastoreService().put( messageEntity );
  }

  private static List<Message> executeQuery( Query query )
  {
    List<Message> result = new ArrayList<Message>();

    for ( Entity entity : DatastoreServiceFactory.getDatastoreService().prepare( query ).asIterable() )
      result.add( new Message( entity ) );
    return result; 
  }

  public static List<Message> findMessagesForPerson( Person p )
  {
    Query query = new Query( ENTITY_NAME );
    query.addFilter( „person_fk“, Query.FilterOperator.EQUAL, p.personEntity.getKey() );

    return executeQuery( query );
  }

  @Override
  public String toString()
  {
    return String.format( „Message[%s,%s]“, getCreationTime(), getText() );
  }
}

Getestet werden soll das ganze in einer einfachen Server-Funktion:

StringWriter sw = new StringWriter();
PrintWriter out = new PrintWriter( sw );

// Insert new entity

Person p1 = new Person();
p1.setUsername( „chris“ );
p1.setGender( Person.Gender.MALE );
Key key1 = p1.put();

out.println( „* Key für erste Person “ + p1 );
out.println( KeyFactory.keyToString( key1 ) );

Person p2 = new Person();
p2.setUsername( „pallas“ );
p2.setGender( Person.Gender.FEMALE );
p2.put();

Person p3 = new Person();
p3.setUsername( „tina“ );
p3.setGender( Person.Gender.FEMALE );
p3.put();

// Search for entity with a given key

Person p = Person.get( key1 );
out.println( „* Suche mit Schlüssel “ + key1 );
out.println( p.getUsername() );

// Query

List<Person> findAll = Person.findAllPersons();
out.println( „* Alle Personen“ );
out.println( findAll.toString() );

// Query

List<Person> females = Person.findPersonByGender( Gender.FEMALE );
out.println( „* Alle Frauen:“ );
out.println( females.toString() );

Message msg1 = new Message();
msg1.setText( „Hallo Maus“ );
msg1.setCreationTime( new Date(1) );
msg1.setReceiver( p );
msg1.put();

Message msg2 = new Message();
msg2.setText( „Hallo Ratte“ );
msg2.setCreationTime( new Date(2) );
msg2.setReceiver( p );
msg2.put();

out.println( „* Alle Nachrichten für “ + p );
out.println( Message.findMessagesForPerson( p ) );
out.println( „\n“ );

// Clean up

Person.deleteAll();

out.flush();

return sw.toString().replace( „\n“, „<br/>“ );

Als Ergebnis kommt HTML zurück, was der Client zum Testen anschauen kann.

GWT und sein HTML/DOM, alternativer GWT FlowPanel

Zum Layout von GWT-Komponenten ist es unerlässlich zu verstehen, was GWT für ein DOM erzeugt. Zum einen sind da Werkzeuge wie FireBug unerlässlich und zum Anderen kann man sich vorher schon bei http://javabyexample.wisdomplug.com/component/content/article/75.html informieren, was GWT für eine Struktur erzeugen wird.

Schnell entstehen bei GWT-Anwendungen eine Unzahl geschachtelter Tabellen. Sie sind für die Performance der Darstellung nicht unerheblich, denn wenn man das Fenster zum Beispiel in der Größe ändert, so müssen die ganzen Größeninformationen neu berechnet werden.

Um das HTML schlank zu halten, lässt sich auf alternative Container zurückgreifen. Wer zum Beispiel horizontal oder vertikal anordnen will, greift sofort zum HorizontalPanel bzw. VerticalPanel. Doch zur Umsetzung setzt eben GWT eine Tabelle ein. Wenn man nur zum Beispiel eine Zeile wie “blal@googlemail.com  | My favorites | Profile  | Sign out” aufbauen möchte, ist das HorizontalPanel unnötig und schwergewichtig. Es bietet sich an, eine neue Panel-Klasse zu nutzen, etwa wie sie etwa http://blog.sudhirj.com/2009/05/vertical-and-horizontal-flow-panels-in.html vorstellt. Etwas komprimiert:

import com.google.gwt.user.client.ui.FlowPanel;
import com.google.gwt.user.client.ui.Widget;

public class HorizontalFlowPanel extends FlowPanel
{
  @Override
  public void add( Widget w )
  {
    w.getElement().getStyle().setProperty( „display“, „inline“ );
    super.add( w );
  }
}

Diese Implementierung führt nur zu einem <div>-Block statt einer <table>.

GWT-bezogene Videos von der Google I/O Konferenz 2009

Im Google Code Blog werden einige Videos vorgestellt, die den aktuellen Zustand von GWT widerspiegeln. Etwa

GWT Can Do What?!?! A Preview of Google Web Toolkit 2.0 (PDF):

 

Und weitere:

Google Wave – Powered by GWT

Google Web Toolkit Architecture: Best Practices for Architecting your GWT App

Measure in Milliseconds: Performance Tips for Google Web Toolkit

Effective GWT: Developing a Complex, High- performance App with Google Web Toolkit

The Story of your Compile: Reading the Tea Leaves of the GWT Compiler for an Optimized Future

Progressively Enhance AJAX Applications with Google Web Toolkit and GQuery

Building Applications with Google APIs

Ext GWT Beispiel mit EditorGrid/GroupingView/GroupingStore

sshot-1

GroupingStore<BaseModel> store = new GroupingStore<BaseModel>();

BaseModel c1 = new BaseModel();
c1.set( „title“, „Java 1“ );
c1.set( „category“, „JAVA“ );
c1.set( „price“, 100 );
store.add( c1 );

BaseModel c2 = new BaseModel();
c2.set( „title“, „Java 2“ );
c2.set( „category“, „JAVA“ );
c2.set( „price“, 200 );
store.add( c2 );

BaseModel c3 = new BaseModel();
c3.set( „title“, „C#“ );
c3.set( „category“, „.NET“ );
c3.set( „price“, 100 );
store.add( c3 );

List<ColumnConfig> config = new ArrayList<ColumnConfig>();
config.add( new ColumnConfig( „category“, „Kategorie“, 200 ) );
config.add( new ColumnConfig( „title“, „Titel“, 100 ) );
config.add( new ColumnConfig( „price“, „Preis“, 100 ) );

store.groupBy( „category“ );

final ColumnModel cm = new ColumnModel( config );

GroupingView view = new GroupingView();
view.setShowGroupedColumn( false );
view.setForceFit( true );
view.setGroupRenderer( new GridGroupRenderer()
{
  public String render( GroupColumnData data )
  {
    String header = cm.getColumnById( data.field ).getHeader();
    String items = data.models.size() == 1 ? „Eintrag“ : „Einträge“;
    return header + “ “ + data.group + “ (“ + data.models.size() + “ “ + items + „)“;
  }
} );

EditorGrid<BaseModel> grid = new EditorGrid<BaseModel>( store, cm );
grid.setView( view );
grid.setBorders( true );
grid.setAutoHeight( true ); 

ContentPanel cp = new ContentPanel();
cp.add( grid );
cp.setHeight( 500 );
RootPanel.get().add( cp );

Will ich eigentlich RIA mit Ext GWT?

Nach dem ich nun eine Woche eine Anwendung mit Ext GWT (GXT) zusammengebaut habe, kommen mir Zweifel, ob Ext GWT überhaupt das richtige für meinen Anwendungsfall ist. Einige Fragen in dem Zusammenhang:

Ist Ext GWT 2.0 M2 gut gewählt?

Ext GWT 2 gibt es noch gar nicht lange (http://extjs.com/blog/2009/05/20/ext-gwt-20-milestone-2-released/), neues M2 Release also vor 2 Wochen, und die Komponenten sind grundsätzlich toll. Auf der Demo Seite http://extjs.com/examples-dev/explorer.html#overview kann man sich das anschauen.

Probleme: Ext GWT 2.0 hat in meinen Augen sehr vielen Änderungen in der API. Quellcode für Ext GWT 1.0 findet man im Netz, doch viele Beispiele lassen sich nicht ohne weiteres auf die 2er Version übertragen. Wer migrieren will/muss wird noch seinen Spaß bekommen. Einige Beispiele laufen in der Demo nicht (http://extjs.com/examples-dev/explorer.html#grouping), bei anderen fehlt der Quellcode im Demo. Da muss man dann die Quellen laden.

Laut Ankündigung müsste GXT schon längst fertig sein.

Was passt mir an Ext GWT nicht?

  • Die Dokumentation. Einfache Beispiele zu finden kostet viel Zeit. Es fehlt so etwas wie ein Java-Almanac für Ext GWT. Aus komplexen Szenarien kann man sich einfache Beispiele ableiten, von dort diese auf seinen Anwendungsfall übertragen. Selbst die Beispiele aus dem ShowCase könnten einfacher sein, denn der ShowCase nutzt interne “Demo-Datengeber-Klassen”. Hier wären in meinen Augen einfachere Beispiele gut gewesen, die wirklich eigenständig sind um schnell die wichtigsten Schnittstellen ablesen zu können.
  • Compiliere ich meine Programme, so lassen sich im Browser plötzlich keine Textfelder mehr selektieren und den Cursor sieht man nicht.
  • Das Layout-Handing. Da die Ext GWT Anwendungen auf eine “Bildschirmseite” kommen, und weniger auf einem beliebig langen HTML-Dokument, gibt es Probleme mit den Höhen. Manches mal sind die Elemente in einem Scroll-Container zu klein, dann zu groß. Ich habe viel Zeit damit verbracht, große Tabellen in den richtigen Größen auf den Container zu setzen.
  • Während das Orginal-GWT viele eher den typischen Web-Charakter hat, basiert Ext GWT auf dem Applikationsgedanken: (Popup-)Menüs in Web-Applikationen? Hm …
  • Anpassungen des Aussehens sind eine Qual. Die CSS-Datei gxt-all.css ist 6478 Zeilen lang, gxt-gray.css noch mal 443! Wer viel Zeit hat, kann einmal versuchen, den Font 2 Pixel größer zu setzen. Dazu müssen natürlich auch die Icons passen. Strg++ geht nicht einfach so.
  • Es “erfindet” GXT eigentlich alles neu: Widgets, Container, Layout-Manager, Grafik-Einbindung, …. Besonders blöd ist das bei den Grafiken, wo Google mit Proxies einen schönen Ansatz fährt.
  • Es gibt keinen Gui-Builder, für GWT aber schon.

War Ext GWT eine gute Wahl, oder hätte es doch SmartGWT sein sollen?

Das weiß ich nicht, denn mit SmartGWT habe ich nur einfache Beispiele programmiert. Ext GWT hat aber den gleichen RIA-Ansatz wie SmartGWT, und unterscheidet sich nicht großartig im Ansatz. Die GXT-Komponenten sehen auf jeden Fall super aus. Daher hat mich GXT ja auch so angezogen. Wer hübscher ist gewinnt 🙂 Die Komponenten sind fancy und meine Lieblinge sind http://extjs.com/examples-dev/explorer.html#advancedcharts (cool), http://extjs.com/examples-dev/explorer.html#gridplugins, http://extjs.com/examples-dev/explorer.html#grouping, http://extjs.com/examples-dev/explorer.html#filtertree, http://extjs.com/examples-dev/explorer.html#gridtogrid.

Ist ein RIA-Framework wie GXT immer gut?

Das ist eigentlich die zentrale Frage.

  • Die Standard-Komponenten von GWT sind etwas “einfach”. Natürlich nutzt GXT das GWT als Infrastruktur, aber hat sich in meinen Augen weit davon entfernt.
  • GXT versucht, alle Desktop-Komponenten in den Browser zu bekommen. Und so bekommen wir immer mehr und mehr tolle Komponenten. Das hat aber seinen Preis! Der generierte JavaScript-Code wird schnell astronomisch. Und die Anwendungen sehen eben nicht mehr so aus wie Webseiten. Das ist mal gewollt, mal nicht gewollt. Ein richtig und falsch gibt es nicht! Wer aber vollständige Applikationen im Web-Schreiben möchte, der bekommt mit GXT ein gutes Framework. Alles ist vorbereitet und das Look-and-Feel konsistent.
  • Änderungen des Aussehens sind aufwändig. Wenn man also HTML/CSS sehen und damit designen möchte, ist mit GXT nicht optimal bedient. (Als nächstes teste ich http://code.google.com/p/google-web-toolkit-incubator/wiki/UiBinder — wenn der denn mal im Trunk ist…) Soll ein (echte) Webdesigner eine GXT-Anwendung skinnen, wird der sicherlich nicht glücklich werden. Es ist eben nicht Design first.

Einen einfachen Baum mit Ext GWT (Tree, TreeItem)

Im Mittelpunkt steht com.extjs.gxt.ui.client.widget.tree.Tree, dem entweder com.extjs.gxt.ui.client.widget.tree.TreeItem-Objekte zugeordnet werden, oder ein komplexeres Modell zugewiesen wird. Mit TreeItem ist schnell ein statischer Baum aufgebaut:

Tree tree = new Tree();

TreeItem rootTreeItem = new TreeItem( „Hauptobjekte“ );
tree.getRootItem().add( rootTreeItem );

rootTreeItem.add( new TreeItem( „Kunden“ ) );
rootTreeItem.add( new TreeItem( „Seminare“ ) );
rootTreeItem.add( new TreeItem( „Referenten“ ) );
rootTreeItem.setExpanded( true );

tree.addListener( Events.OnClick, new Listener<TreeEvent>()
{
  @Override public void handleEvent( TreeEvent be )
  {
    if ( be.getItem().isLeaf() )
      Info.display( „Element: „, be.getItem().getText() );
  }
} );
contentPanel.add( tree );

Wie bei Swing gibt es ein Root-Icon und darunter liegen die Kinder.

Mit einem Listener lässt sich erfragen, ob der Baum ausgefaltet wird oder ob Blätter ausgewählt wurden. Das Beispiel hört nur auf die Auswahl der Kinder und gibt eine Meldung aus.

Wie viel GWT steckt in Google Square?

In Google Wave steckt eine Zeile, die einen Einsatz
vom GWT nahe legt:

<link rel="stylesheet" href="/squared/styles/squared.css">
<script language="javascript" src="/squared/js/templates.js"></script>
<script language="javascript" src="/squared/api/com.google.quality.views.squared.client.Squared.nocache.js"></script>

Verfolgt man den Link ist das typische obfuscated JavaScript zu sehen. Wer viiiiel Zeit hat, kann das ja mal debuggen und hier berichten. Ein kleiner Test mit FireBug zeigt, dass Squared sich über Service per RPC die Objekte geben lässt

["com.google.quality.views.squared.shared.model.Workspace/4054808271","US presidents",
"com.google.quality.views.squared.shared.model.Suggestions/3280816700",
"java.util.ArrayList/3821976829",
"com.google.quality.views.squared.shared.model.Entry/3459296909" ...

Wie Google Wave das GWT antreibt

Über Googe Wave wurde in den Medien schon ausreichend gesprochen. (Auf jeden Fall das Video ansehen! Der korrigierende Editor ist super.)

Wirklich interessant ist ist die Tatsache, dass die Clientseite von Google Wave in GWT implementiert ist. Das ist in meinen Augen ein unschlagbares Argument für die Ausgereiftheit von GWT, wenn Google das in einem Projekt einsetzt, was von unglaublichen vielen Menschen verwendert werden wird und definitiv performant laufen muss. Dabei sollte laut einem Blog-Eintrag http://mtwong.ning.com/profiles/blogs/google-io-conference-google-1 Wave auch überhaupt nicht in GWT entwickelt werden, da der Projektleiter Zweifel hatte, GWT würde gescheit laufen. Aber Wave hat wohl auch die Entwicklung von GWT positiv beeinflusst, etwa bei den Tools. Der Blog-Eintrag erwähnt interessante weitere Projekte und GWT-Features:

SmartGWT 1.1

Von SmartGWT, der bekannten OpenSource-GWT-Toollibrary gibt es eine neue Version. Der Showcase zeigt neue Komponenten: http://www.smartclient.com/smartgwt/showcase/. Die Release-Notes führen auf:

Unified DataBoundComponent interface which is implemented by DynamicForm, ListGrid, TreeGrid, Menu, ColumnTree, TileGrid, DetailViewer and Calendar.

– Consistent representation of a Record which can be used by any DataBoundComponent

– Support for GWT-RPC DataSource with DataBoundComponents

– Various component and skin performance improvements

Formula and Summary fields:
built-in wizards for end users to define formula fields that can
compute values using other fields, or summary fields that can combine
other fields with intervening / surrounding text. Available in all
DataBoundComponents, easy to persist as preferences

I18n support with resource bundles from 14 locales

– Significant improvements to Javadocs with tons of documentation on various concepts. (over 30MB on disk)

– API’s to save and restore the display and selection state of various components to/from the server

Automatic databound dragging
behaviors: grids and trees will now inspect data relations declared in
DataSources and automatically “do the right thing” for a databound drag

– Numerous enhancements. See the detailed API Changes document

Over 50 additional enhancements and bug fixes that were logged in tracker

– Several new events and extension points on various components to allow advanced usage

– A helloworld-1.6 starter project for SmartGWT with GWT 1.6

– Several new examples in the SmartGWT Showcase (see „New Samples“ side nav item in the Showcase)

Key API’s added

DateUtil

– Ability to globally set the Date input and rendered formats across the application

DataBoundComponent

– ability to select / deselect records, fetch / filter data and carry out various other operations on any DataBoundComponent

DataSource and DataSourceField

– ability to specify custom field value extractor logic for a given DataSourceField

– API’s to control http caching and result batch size

– DataSourceField.setCanSortClientOnly

Window

– listeners for minimize, maximize, restore and close

Calendar

– support for customizing event edit dialog fields

– event remove listener

Forms

– convenience constructors for various FormItems

– support for customizing form field and title tooltips

– TextItem : support for character casing and entry restrictions (regexp based)

– TextItem : support for hints displayed in-field

– ComboBoxItem : fetchDelay setting

– AutoFitTextAreaItem: autofits to contained text as the user types

Column Tree (Miller Columns)

– added node selected listener

ListGrid

– Grouping modes: built-in and custom grouping modes, such as the ability to group a date column by week, day, month or year

– added header click listener

– added record drop listener

– ability to customize grid groupings by record values and also group title

– support for adding custom formula and summary fields on the fly

– ability to register custom handler to control visibility of a ListGridField

– added ListGridField.setCanFreeze, ListGridField.setCanSort

SectionStack (Accordion)

– added section header click event

TreeGrid

– added folder drop listener

– ability to override getIcon() / getNodeTitle() to allow users to have custom node icon / title logic

Ext GWT Beispiel einer editierbaren Tabelle

Die Daten einer Tabelle liegen zeilenweise in einem ListStore. Die ListStore wird mit dem generischen Typ parametrisiert, den das Modell speichern soll. Es können entweder eigene Beans sein, oder wir nutzen BaseModel, ein generischer Speicher von Ext GWT, der wie eine Map (oder Dyna-Bean) arbeitet.

ListStore<BaseModel> store = new ListStore<BaseModel>();

Geben wir der Tabelle zwei Zeilen, also zwei BaseModel-Objekte.

BaseModel c = new BaseModel();
c.set( „url“, „http://www.tutego.com/index.html „);
c.set( „days“, „34“);
c.set( „price“, „233“);
store.add( c );

c = new BaseModel();
c.set( „url“, „http://www.heise.de/newsticker/ „);
c.set( „days“, „3“);
c.set( „price“, „2553“);
store.add( c );

Die BaseModel-Objekte beschreiben also drei Spalten.

Nun sind die Daten im Speicher und die Informationen für die Spalten müssen aufgebaut werden. Jede Spalte wird durch ein ColumnConfig-Objekt beschrieben. Die ColumnConfig-Objekte werden dann in einer Liste gespeichert und zur Initialisierung eines ColumnModels genutzt. Ein ColumnConfig-Objekt bekommt Informationen wie die Breite, aber auch Editoren zugewiesen.

List<ColumnConfig> configs = new ArrayList<ColumnConfig>();

TextField<String> column1TextEditor = new TextField<String>();
column1TextEditor.setAllowBlank( false );
ColumnConfig column1 = new ColumnConfig( „url“, „URL“, 400 );
column1.setEditor( new CellEditor( column1TextEditor ) );
configs.add( column1 ); 

ColumnConfig column2 = new ColumnConfig( „days“, „Tag(e)“, 80 );
column2.setEditor( new CellEditor( new TextField<String>() ) );
configs.add( column2 );

ColumnConfig column3 = new ColumnConfig( „price“, „Gesamtpreis“, 80 );
column3.setEditor( new CellEditor( new TextField<String>() ) );
configs.add( column3 );

ColumnModel cm = new ColumnModel( configs );

Der parametrisierte Konstruktor von ColumnConfig erwartet einen Schlüsselnamen, den Titel für den Spaltenkopf und die Breite.

Jetzt kommt es zur Hautkomponente, dem EditorGrid. Er benötigt drei Informationen: Die Tabellendaten (der ListStore store), die Spaltendaten (ColumnModel cm) und als generische Angabe der Datentyp der Modellelemente.

EditorGrid<BaseModel> grid = new EditorGrid<BaseModel>( store, cm );

Die Komponenten wird nun wie jede andere auf den Container gesetzt.

panel.add( grid );

Zugriff auf die Daten liefert store.getModels(). Möchte man durch alle Zeilen laufen, erledigt dies einfach das erweiterte for:

for ( BaseModel model :store.getModels() )
  …

Ext GWT BeanBinding Beispiel

Ext GWT bietet eine einfache Möglichkeit, JavaBeans an Formulare zu binden. Im ersten Schritt muss die Bean dazu vorbereitet werden.

  1. Die JavaBean implementiert das Marker-Interface com.extjs.gxt.ui.client.data.BeanModelTag.
  2. Will man’s nicht-invasiv, so deklariert man ein Zusatz-Interface, was BeanModelMarker erweitert und hängt dort eine spezielle Zusatz-Annotation an, mit dem Verweise auf die JavaBean.

Gebunden werden soll der Titel eines Seminars an das Textfeld titleTextField auf dem FormPanel:

FormPanel courseDescriptionPanel = new FormPanel();

TextField<String> titleTextField = new TextField<String>();
titleTextField.setFieldLabel( „Kurstitel“ );
titleTextField.setName( „title“ );
titleTextField.setAllowBlank( false ); 
courseDescriptionPanel.add( titleTextField );

Im nächsten Schritt kann man die Bean aufbauen und etwa mit Werten aus einem Service füllen:

Course c = new Course();
c.setTitle( „ejb super kurs“ );

Dann wird um diese Standard-JavaBean eine Ext GWT-Klasse gelegt, quasi ein Wrapper:

BeanModel userModel = BeanModelLookup.get().getFactory(Course.class).createModel( c );

Dieses BeanModel wird als nächstes mit dem FormPanel verbunden, auf dem das Texteingabefeld liegt.

FormBinding formBindingCourse = new FormBinding( courseDescriptionPanel );

Und das muss mit dem BeanModel (dem Wrapper um das Exemplar unserer JavaBean) verbunden werden:

formBindingCourse.bind( userModel );

Nun kennt der formBindingCourse die Bean, aber noch nicht, welches Gui-Element eigentlich mit welcher Property verbunden ist. Das kann man mit autoBind() automatisieren, oder aber mit addFieldBinding() manuell für jedes Element setzen. Bei unserem Textfeld heißt das:

formBindingCourse.addFieldBinding( new FieldBinding( titleTextField, „title“ ) );

Jede Änderung an der Gui wird nun an die Bean weitergeben.

Weiteres Infos unter http://extjs.com/blog/2008/07/14/preview-java-bean-support-with-ext-gwt/ oder in Foren-Beispielen.

gwtrpc-spring zur Einfachen Integration von Spring in GWT 1.6

GWT-SL bietet schon seit längerem die Möglichkeit, die RPC-Services durch Spring-Beans zu definieren. Ist Spring auf der Serverseite, so möchte man gerne Spring-POJOs als GWT-RPC-Service freigeben. GWT-SL ist aber relativ groß und mit http://code.google.com/p/gwtrpc-spring/ gibt es eine sehr schlanke Alternative, die nur aus zwei Klassen besteht. Das die Klassen, die beide im Quellcode unter 3 KB liegen, liegt daran, dass GWT in den neuen Versionen eine Integration RPC-Implementierungen vereinfacht. Die Hauptseite zeigt die 4 Schritten zur Integration anschaulich.

Noch besser beschreibt es allerdings http://devbright.com/2009/05/super-simple-gwt-spring-integration/; hier kann man gleich ein Archiv laden, mit allen Jar-Dateien und alles ist fertig. So läuft das Beispiel nach wenigen Minuten.

gwt-connectors – Verbindungen zwischen Formen

gwt-connectors ist eine GWT-Bibliothek, um Formen miteinander zu verbinden.

Die Präsentation zeigt, wie das geht.

Wer’s selber ausprobieren möchte, schaut unter demo (IE, Firefox, Opera, Chrome). (Aber wie kann man nun die Verbindung wieder lösen …)

Der nötige Programmcode ist kurz:

// Create boundary panel
AbsolutePanel boundaryPanel = new AbsolutePanel();
boundaryPanel.setSize("600px", "400px");
RootPanel.get().add(boundaryPanel, 10, 10);
Diagram diagram = new Diagram(boundaryPanel);
boundaryPanel.add(new Label("Connectors example"), 10, 2);

// Add connectors
Connector connector1 = new Connector(50, 80, 100, 100);
connector1.showOnDiagram(diagram);

Connector connector2 = new Connector(350, 200, 270, 80);
connector2.showOnDiagram(diagram);

Connector connector3 = new Connector(450, 120, 500, 80);
connector3.showOnDiagram(diagram);

// Add some elements that can be connected
Label label = new Label("LABEL");
Image image = new Image("http://code.google.com/images/code_sm.png");
HTML html = new HTML("<b>HTML<br>ELEMENT</b>");

boundaryPanel.add(label, 50, 270);
boundaryPanel.add(image, 180, 250);
boundaryPanel.add(html, 450, 250);

Shape shapeForLabel = new Shape(label);
shapeForLabel.showOnDiagram(diagram);

Shape shapeForImage = new Shape(image);
shapeForImage.showOnDiagram(diagram);

Shape shapeForHtml = new Shape(html);
shapeForHtml.showOnDiagram(diagram);

Ich bin gespannt, wann es das erste UML-Tool mit GWT gibt. gwt-connectores basiert im Übrigen auf Fred Sauer’s gwt-dnd. (Dazu auch das Demo http://allen-sauer.com/com.allen_sauer.gwt.dnd.demo.DragDropDemo/DragDropDemo.html.)

Dazu passt auch die Ankündigung von http://googledocs.blogspot.com/2009/03/drawing-on-your-creativity-in-docs.html, ein Zeichenwerkzeug in Google Docs einzubetten:

http://www.googlewatchblog.de/2009/03/26/google-docs-drawing-veroeffentlicht/ hat dazu ebenfalls ein Bild parat:

GWT Charting Bibliothek gflot und charts4j.

Alles fängt mit http://jquery.com/ an. Darauf baut auf http://code.google.com/p/flot/, eine JavaScript-Bib. für Chars:

http://code.google.com/p/gflot/ ist nun der GWT-Aufsatz auf flot.

Für eine Bar-Diagramm ist nur folgender Quellcode nötig:

final String[] MONTH_NAMES = { "jan", "feb", "mar", "apr", "may", "jun", "jul", "aug", "sep", "oct", "nov", "dec" };

PlotModel model = new PlotModel();
PlotOptions plotOptions = new PlotOptions();

BarSeriesOptions barSeriesOptions = new BarSeriesOptions();
barSeriesOptions.setShow(true);
barSeriesOptions.setLineWidth(1);
barSeriesOptions.setBarWidth(1);
barSeriesOptions.setAlignment(BarAlignment.CENTER);

plotOptions.setDefaultBarsSeriesOptions(barSeriesOptions);
plotOptions.setLegendOptions(new LegendOptions().setShow(false));



// add tick formatter to the options

plotOptions.setXAxisOptions(new AxisOptions().setTicks(12).setTickFormatter(new TickFormatter() {

public String formatTickValue(double tickValue, Axis axis) {

if (tickValue > 0 && tickValue <= 12) {

return MONTH_NAMES[(int) (tickValue - 1)];

}

return "";

}

}));


// create a series

SeriesHandler handler = model.addSeries("Ottawa's Month Temperatures (Daily Average in &deg;C)", "blue");


// add data

handler.add(new DataPoint(1, -10.5));

handler.add(new DataPoint(2, -8.6));
handler.add(new DataPoint(3, -2.4));
handler.add(new DataPoint(4, 6));
handler.add(new DataPoint(5, 13.6));
handler.add(new DataPoint(6, 18.4));
handler.add(new DataPoint(7, 21));
handler.add(new DataPoint(8, 19.7));
handler.add(new DataPoint(9, 14.7));
handler.add(new DataPoint(10, 8.2));
handler.add(new DataPoint(11, 1.5));
handler.add(new DataPoint(12, -6.6));

// create the plot
SimplePlot plot = new SimplePlot(model, plotOptions);

// put it on a panel
FlowPanel panel = new FlowPanel();
panel.add(plot);

return panel;

Eine Alternative dazu ist http://code.google.com/p/charts4j/  bzw. http://code.google.com/p/charts4j/wiki/GWT_Port.

Google Web Toolkit 1.6, Google Plugin for Eclipse

Am 7.4.09 hat Google das Google Web Toolkit 1.6 veröffentlicht:

Zu den Neuerungen gehören unter anderem DatePicker und DateBox (http://gwt.google.com/samples/Showcase/Showcase.html#CwDatePicker) und ein LazyPanel. Viele Bug-Fixes wurden gemacht. Eine der größten Änderungen ist das Eventing: Event-Listener wurden durch Event-Handler ersetzt. Damit gibt es nur noch eine zentrale Stelle, an der die Events verwaltet werden und nicht mehr viele kleine Mini-Listen bei jedem Widget.

Das Google Plugin for Eclipse wird wohl die Standard-IDE werden und vermutlich Cypal verdrängen. Insbesondere da es laut Webseite bietet:

  • Recognition of inline JavaScript (JSNI): syntax highlighting, auto-indenting, Java Search and Refactoring integration
  • GWT compiler shortcuts and configuration UI
  • Wizards to create entry points, modules and HTML pages
  • Support for GWT JUnit tests

Die Doku für das Eclipse-Plugin ist unter http://code.google.com/intl/de/eclipse/docs/gwt.html. Laut der Beschreibung fehlt die schöne Möglichkeit, Stubs für RPC-Dienste aufzubauen; das bietet Cypal. Schade.

PS: tutego hat das GWT-Seminar für GWT 1.6 aktualisiert: http://www.tutego.de/seminare/java-schulung/GWT-Seminar-Google-Web-Toolkit-Schulung.html.

Auffälligkeiten von SmartGWT 1.0b2 und Ext-GWT 1.2.3. nach einer Stunde spielen

  • Die Container und Layout-API sehen ein wenig anders aus. Bei SmartGWT gibt es eine Klasse Layout (und Unterklassen wie VLayout), die eigentlich Container mit einem speziellen assoziierten Layoutmechanismus sind. So heißt es dann auch layout.addMember(…) und das ist gewöhnungsbedürftig. Da gefällt mir Ext-GWT besser, wo wie bei AWT/Swing den Layout-Manager auf einem Container setzt. Hier wird also bei Ext-GWT das übliche Strategie-Muster eingesetzt.
  • Die Optik von Ext-GWT (etwa bei Menüs) ist in meinen Augen deutlich besser. Es gibt viele kleine Fehler in der Darstellung, etwa Ein-Pixel freie Linien.
  • Ext-GWT bietet bessere Unterstützung für Formulare, was etwa das automatische Ein-/Ausfalten von Details angeht.
  • SmartGWT bietet vertikale Tabs und eine Integration vom Google Kalander.
  • Das Binding funktioniert bei beiden völlig anders. SmartGWT erwartet spezielle Binding-Komponenten, also etwa DataSourceTextField statt TextItem (ja, warum das nun nicht DataSourceTextItem heißt, ….). Wenn später Quellcode umbaut wird, muss man die Komponenten ändern und wenn eine angepasste Unterklasse etwa von TextItem erbt, muss das auch anpasst werde. Das Design ist in meinen Augen nicht optimal. Besser macht es Ext-GW; hier bleibt man bei den Standardkomponenten und setzt diese später zu einer Gruppe (FormBinding) zusammen.
  • Ext-GWT bietet eine prima XTemplate-Klasse, in der dann Platzhalter definiert werden können, etwa {[new Number(values.change).toFixed(2)]}, die später über das Binding ersetzt werden.
  • Bei SmartGWT lässt sich nicht jede Komponente in einen DynamicForm setzen. Bei der RTF-Komponente RichTextEditor (ein VLayout) geht das zum Beispiel nicht. Dafür gibt es noch mal den Wrapper RichTextItem (ein FormItem). Das ist verwirrend.
  • Ohne Anpassungen beim SmartGWT ist der Font rechts einer Checkbox ein andere als Links von einem Textfeld.Und Warum muss bei TextItem immer standardmäßig ein Doppelpunkt gesetzt werden, was dann auch noch mit einem Leerzeichen vom Text abgesetzt wird?
  • SmartGWT hat merkwürdige Klassennamen wie com.smartgwt.client.core.BaseClass oder IButton (und eine Button-Klasse gibt es auch noch).
  • SmartGWT-Methoden mit den Namen addItem(), addMember(), addChild() und addPeer() tragen nicht zur Übersichtlichkeit beim Hinzufügen von Komponenten be

Fazit von einer Std. spielen und API-blättern: Ext-GWT erscheint mit vom Design durchdachter und professioneller. Die SmartGWT API wirkt an vielen Stellen unverständlich und überfachtet. Ein Beispiel:

  • void setAttribute(java.lang.String attribute, boolean value)
  • void setAttribute(java.lang.String attribute, java.lang.Boolean value)
  • void setAttribute(java.lang.String attribute, DataClass value)
  • void setAttribute(java.lang.String attribute, DataClass[] value)
  • void setAttribute(java.lang.String attribute, java.util.Date value)
  • void setAttribute(java.lang.String attribute, double value)
  • void setAttribute(java.lang.String attribute, int value)
  • void setAttribute(java.lang.String attribute, int[] value)
  • void setAttribute(java.lang.String attribute, com.google.gwt.core.client.JavaScriptObject value)
  • void setAttribute(java.lang.String attribute, java.util.Map value)
  • void setAttribute(java.lang.String attribute, java.lang.String value)
  • void setAttribute(java.lang.String attribute, java.lang.String[] value)
  • void setAttribute(java.lang.String attribute, ValueEnum[] value)

Mit Ext-GWT macht mir die Entwicklung mehr Spaß. Bei der API sind auch viel weniger Klassen/Schnittstellen im Spiel, obwohl die API viel älter ist.

SmartGWT in Eclipse nutzen

  1. Unter http://code.google.com/p/smartgwt/downloads/list lade smartgwt-1.0b2.zip und packe es aus.
  2. Setzte smartgwt.jar (und smartgwt-skins.jar falls Skinning gewünscht ist) in das lib-Verzeichnis der Web-Applikation (oder überall anderes hin, die Jar-Datei spielt ja später keine Rolle mehr) und melde die Java-Archive im Klassenpfad an.
  3. In der Modul-XML-Datei füge ein:
    <inherits name=“com.smartgwt.SmartGwt“/>
    Eintragungen für CSS in der HTML-Datei sind nicht nötig.
  4. In der onModuleLoad() kann man man etwa schreiben:
    RootPanel.get().add( new IButton(„Klick mich härter“) );
    Imports wieder generieren lassen, mit dem Präfix I gibt es keine Verwechslungen.
  5. Ideen und API aus dem http://www.smartclient.com/smartgwt/showcase/ und http://www.smartclient.com/smartgwt/javadoc/

Beispiel, um Ext GWT in Eclipse zu nutzen

Voraussetzung: http://www.tutego.com/blog/javainsel/2008/05/gwt-beispiel-in-eclipse.html

  1. Lade unter http://extjs.com/products/gxt/download.php das Ext GWT 1.2.3 SDK http://extjs.com/products/gxt/download.php?dl=gxt123 und entpacke es.
  2. Setzte gxt.jar in das WebContent/WEB-INF/lib Verzeichnis (oder überall anderes hin, die Jar-Datei spielt später keine Rolle mehr). Füge das Java-Archiv zum Klassenpfad hinzu (Java Build Path > Add)
  3. Die Host-Page im public-Ordner bekommt einen Verweis auf das Sytesheet. Setzte unter <head>:
    <link rel=“stylesheet“ type=“text/css“ href=“css/ext-all.css“ />
  4. Im public-Verzeichnis gibt es ebenfalls die Modul-XML Datei. Füge hinzu:
    <inherits name=’com.extjs.gxt.ui.GXT’/>
  5. In onModuleLoad() kann man nun ein Demoprogramm schreiben, was eine Ext GWT Komponente nutzt:
    RootPanel.get().add( new DatePicker() );
    Imports wieder automatisch generieren lassen. Aufpassen, dass man bei Mehrdeutigkeiten die korrekten import-Anweisungen generiert werden. So gibt es Window zum Beispiel zweimal.
  6. Die API-Doku http://extjs.com/deploy/gxtdocs/ und die Beispiele vom ShowCase http://extjs.com/explorer/#overview zeigen den Gebrauch der API.