GWT Drag and Drop

Das Projekt unter http://code.google.com/p/gwt-dnd/ gehört mit den zu den bekanntesten Drag & Drop Bibliotheken für GWT. Die von Fred Allen-Sauer geschriebene Bibliothek ist ordentlich mit Beispielen hinterlegt, sodass der Einstieg einfach ist. Ein kleines Beispiel soll die Bibliothek demonstrieren.

a) Nach dem Einbinden der Jar-Datei ist in der XML-Datei für GWT folgendes einzutragen:

<inherits name='com.allen_sauer.gwt.dnd.gwt-dnd'/>

b) Von http://code.google.com/p/gwt-dnd/source/browse/trunk/DragDrop/war/DragDropDemo.css sind die Einträge beginnend mit demo-FlexTableRowExample in die eigene CSS-Datei mit aufzunehmen (nicht alle Einträge sind nötig)


c) Aus http://code.google.com/p/gwt-dnd/source/browse/trunk/DragDrop/#DragDrop/war/images nimmt man die Grafik row-dragger-8.gif passend etwa in das WEB-INF/images mit auf und schaut auf den URL-Eintrag in der CSS, dass der Pfad passt.


c) Von http://code.google.com/p/gwt-dnd/source/browse/trunk/DragDrop/#DragDrop/demo/com/allen_sauer/gwt/dnd/demo/client/example/flextable kopiert man die Dateien



  • FlexTableRowDragController

  • FlexTableRowDropController

  • FlexTableUtil

in das eigene GWT-Projekt.


In onModuleLoad() kann es dann so aussehen:


AbsolutePanel panel = new AbsolutePanel();
panel.setPixelSize(450, 300);
panel.addStyleName("demo-FlexTableRowExample");

FlexTableRowDragController dragController = new FlexTableRowDragController( panel );
FlexTable table = new FlexTable();

table.addStyleName( "demo-flextable" );

HTML handle1 = new HTML( "<b>Heinzelmann</b> (100 €)" );
handle1.addStyleName( "demo-drag-handle" );
table.setWidget( 0, 0, handle1 );
dragController.makeDraggable( handle1 );

HTML handle2 = new HTML( "<b>Wumme</b> (200 €)" );
handle2.addStyleName( "demo-drag-handle" );
table.setWidget( 1, 0, handle2 );
dragController.makeDraggable( handle2 );

HTML handle3 = new HTML( "<b>Fred</b> (90 €)" );
handle3.addStyleName( "demo-drag-handle" );
table.setWidget( 2, 0, handle3 );
dragController.makeDraggable( handle3 );

panel.add(table, 10, 20);
FlexTableRowDropController dropController = new FlexTableRowDropController(table);
dragController.registerDropController(dropController); 

RootPanel.get().add( panel );

Anschließend haben wir eine Webseite mit

Heinzelmann (100 €)

Wumme (200 €)

Fred (90 €)

wobei wir die 3 Einträge in der Reihenfolge verschieben können.

Labels: ,

Denormalisierung für schnelle Key/Value Speichersysteme

RDBMS kommen im distributed Cloud-Computing selten vor. Daher muss man auch die Datenhaltung überdenken. Ein Blog-Eintrag diskutiert die Änderungen sehr gut: http://highscalability.com/how-i-learned-stop-worrying-and-love-using-lot-disk-space-scale. Im Prinzip geht es darum, aus Joins zu verzichten, und Daten in den verschiedenen Entities zu duplizieren. Die Erfahrung habe ich bei meinen GWT und GAE/J Projekt ebenfalls gemacht. Es führt zu weniger Lesezugriffen insgesamt, wenn man die Daten lokal in der Entity hält und nicht erst aus verschiedenen Entities zusammensucht, wie man bei normalisierten relationalen Modellen tut. (Außerdem ist es bei vielen lese-Zugriffen auch billiger, die Daten an einer Stelle zu haben, anstatt sie von verschiedenen Stellen über Lesezugriffe, die jeweils Kosten nach sich ziehen, einzusammeln.)

In meinen GWT-GAE/J-Projekt habe ich noch einen anderen Ansatz, die Zugriffe auf die Big-Table zu minimieren. Grundsätzlich: Alle GWT-RPC Implementierungen sind Fassaden, die auf Services zurückgreifen (Googe Guice injiziert diese in die RPC-Fassaden). Für diese Endpoints gibt es dann noch einmal einen Caching-Dekorator als eine Art Caching-Aspekt, der sich wie ein Proxy um die eigentlichen RPC-Implementierungen legt. Diese speichert relevante Daten im MemCache zwischen und minimiert so (langsame) Zugriffe auf die Big-Table. Bei Schreibzugriffen löscht der Caching-Proxy die relevanten Daten aus dem Cache, sodass sie neu geladen werden müssen. Das ist einfach und fix.

Labels: ,

Mit dem MVP-Modell GWT-Anwendungen entwickeln

Unter http://code.google.com/webtoolkit/doc/latest/tutorial/mvp-architecture.html widmet sich Google nun etwas mehr der Architektur von GWT-Anwendungen. Der Artikel "Large scale application development and MVP" beschreibt, wie die drei Teile Model, View und Presenter zusammenarbeiten und getestet werden können. Komplexer wird das Bsp. da noch History-Management und der Event-Bus mit ins Boot kommen.

Im Quellcode auf der Seite gibt es einige Fehler durch ein falsch verarbeitet <. So steht etwa

  public interface Display extends HasValue> {
    HasClickHandlers getAddButton();
    HasClickHandlers getDeleteButton();
    HasClickHandlers getList();
    void setData(List data);
    int getClickedRow(ClickEvent event);
    List getSelectedRows();
    Widget asWidget();
  }

statt

  public interface Display {
    HasClickHandlers getAddButton();
    HasClickHandlers getDeleteButton();
    HasClickHandlers getList();
    void setData(List<String> data);
    int getClickedRow(ClickEvent event);
    List<Integer> getSelectedRows();
    Widget asWidget();
  }

Labels:

Google App Engine 1.3

Kurz nach 1.2.8 veröffentlich Google nun die Google App Engine 1.3. Von http://googleappengine.blogspot.com/2009/12/app-engine-sdk-130-released-including.html:

Store and Serve - Files can be uploaded and stored as blobs, to be served later in response to user requests. Developers can build their own organizational structures and access controls on top of blobs.
Pricing and Quotas - We include blob storage and transfer under the same datastore pricing and quotas you're already familiar with. For more information, see the App Engine quotas page.
The new Blobstore API is now available in both App Engine SDKs for local development. At this time it can only be used by applications that have enabled billing. There's a lot more information about the API and how it works in the Blobstore documentation (Python, Java) so please check that out and post any questions to the groups.
This release also includes some performance tweaks to the Java runtime. For example, we've sped up many reflective operations by up to 10x resulting in improvements on the order of 10% for applications based on dynamic languages. As always, there are a few other minor changes and bug fixes in this release, so make sure to read our release notes (Python, Java).

Labels: , ,

GWT 2.0 ist fertig

GWT 2.0 RC1 ist raus, Updates beim Google Eclipse Plugin

Infos dazu hier: http://code.google.com/p/google-web-toolkit/wiki/GWT_2_0_RC.

Major New Features in the GWT SDK
In-Browser Development Mode
Prior to 2.0, GWT hosted mode provided a special-purpose "hosted browser" to debug your GWT code. In 2.0, the web page being debugged is viewed within a regular-old browser. Development mode is supported through the use of a native-code plugin called the "Google Web Toolkit Developer Plugin" for many popular browsers. In other words, you can use development mode directly from Safari, Firefox, IE, and Chrome.

Developer-guided Code Splitting
Code splitting using GWT.runAsync(), along with compile reports (also known as The Story of Your Compile) allows you to chunk your GWT code into multiple fragments for faster startup. Imagine having to download a whole movie before being able to watch it. Well, that's what you have to do with most Ajax apps these days -- download the whole thing before using it. With code splitting, you can arrange to load just the minimum script needed to get the application running and the user interacting, while the rest of the app is downloaded as needed.

Declarative User Interfaces with UiBinder
GWT's UiBinder now allows you to create user interfaces mostly declaratively. Previously, widgets had to be created and assembled programmatically, requiring lots of code. Now, you can use XML to declare your UI, making the code more readable, easier to maintain, and faster to develop. The Mail sample has been updated to show a practical example of using UiBinder.

Bundling of Resources via ClientBundle
GWT introduced ImageBundle in 1.4 to provide automatic spriting of images. ClientBundle generalizes this technique, bringing the power of combining and optimizing resources into one download to things like text files, CSS, and XML. This means fewer network round trips, which in turn can decrease application latency -- especially on mobile applications.

Simplified Unit Testing with HtmlUnit
Using HtmlUnit for running test cases based on GWTTestCase: Prior to 2.0, GWTTestCase relied on SWT and native code versions of actual browsers to run unit tests. As a result, running unit tests required starting an actual browser. As of 2.0, GWTTestCase no longer uses SWT or native code. Instead, it uses HtmlUnit as the built-in browser. Because HtmlUnit is written entirely in the Java language, there is no longer any native code involved in typical test-driven development. Debugging GWT Tests in development mode can be done entirely in a Java debugger.
Major New Features in the Google Plugin for Eclipse
Development Mode Launch View
Integrates your Development Mode logs right into Eclipse, which means one less window to shuffle around

UiBinder Support
The UiBinder template editor provides auto-completion and formatting for editing ui.xml files (and embedded CSS blocks)
Validation of
UiBinder templates and backing Java classes
New UiBinder wizard to quickly get started

ClientBundle Support
"New ClientBundle" wizard to bundle CSS and other resources together to minimize HTTP round-trips
As-you-type validation ensures that your app's static resources are always in the right place

RPC Refactoring
Automatically updates sync/async pairs of RPC interfaces and their methods

JNSI Reference Auto-completion
Auto-completion takes the pain out of referencing Java members from JSNI methods

Labels: ,

GWT-Bibliotheken

http://code.google.com/p/kiyaa/. Leistungsfähiges XHTML-Template Framework. Bis zum Google UI-Binder ist a) noch Zeit und b) nutzt Kiyaa einfaches XHTML, was man mit jedem HTML-Editor editieren auf aufbauen kann.

http://code.google.com/p/gwt-mosaic/. “GWT Mosaic is a highly usable, feature rich toolkit for creating Rich Internet Applications and an easy to use API.” http://69.20.122.77/gwt-mosaic/Showcase.html

http://code.google.com/p/cobogw/. In erster Linie Buttons, Button-Gruppen, runde Panels, Rating-Widget. Demo. Weiterhin “GWT emulation of java.sql”, also von Date, Time, Timestamp

http://www.asquare.net/gwttk/. Demo. Seit 2007 kein neues Release

http://code.google.com/p/gwtlib/. MenuBar, PagingBar, Table, PagingTable und StyleListBox. Table hat Renderer. Aktuell

http://code.google.com/p/gwt-masterview/. “GWT-Masterview library is an extension to Google Web Toolkit that provides widgets to filter, sort and paginate your data”

http://code.google.com/p/gwtchismes/. Demo. Rendert nicht ganz sauber die Buttons

http://code.google.com/p/gwt-beans-binding/

http://code.google.com/p/gwt-table/

http://code.google.com/p/gwt-datepicker/

http://code.google.com/p/guwit/

http://gwt-widget.sourceforge.net/. Besteht aus GWT Server Library (GWT-SL) und GWT Widget Library (GWT-WL). http://gwt-widget.sourceforge.net/demo/calc/index.html zeigt einen kleinen Taschenrechner, kann mal praktisch sein… (bin ich zynisch?) Der letzte Blog-Eintrag endete vor exakt 3 Jahren.

http://gwt-vl.sourceforge.net/. GWT Validation Library

http://raibledesigns.com/rd/entry/creating_a_facebook_style_autocomplete 

Noch mehr: http://google.wikia.com/wiki/Google_Web_Toolkit#Widgets

Fehlt was?

Labels: ,

GWT 2.0 Milestone 1

Im GWT-Forum wurde letzte Woche der erste Milestone von GWT 2.0 angekündigt:

Things that are changing with GWT 2.0 that might otherwise be confusing without explanation

* Terminology changes: We're going to start using the term
"development mode" rather than the old term "hosted mode." The term
"hosted mode" was sometimes confusing to people, so we'll be using the
more descriptive term from now on. For similar reasons, we'll be using
the term "production mode" rather than "web mode" when referring to
compiled script.

* Changes to the distribution: Note that there's only one download,
and it's no longer platform-specific. You download the same zip file
for every development platform. This is made possible by the new
plugin approach used to implement development mode (see below). The
distribution file does not include the browser plugins themselves;
those are downloaded separately the first time you use development
mode in a browser that doesn't have the plugin installed.

Functionality that will be coming in GWT 2.0
* In-Browser Development Mode: Prior to 2.0, GWT hosted mode provided
a special-purpose "hosted browser" to debug your GWT code. In 2.0, the
web page being debugged is viewed within a regular-old browser.
Development mode is supported through the use of a native-code plugin
for each browser. In other words, you can use development mode
directly from Safari, Firefox, IE, and Chrome.

* Code Splitting: Developer-guided code splitting allows you to chunk
your GWT code into multiple fragments for faster startup. Imagine
having to download a whole movie before being able to watch it. Well,
that's what you have to do with most Ajax apps these days -- download
the whole thing before using it. With code splitting, you can arrange
to load just the minimum script needed to get the application running
and the user interacting, while the rest of the app is downloaded as
needed.

* Declarative User Interface: GWT's UiBinder now allows you to create
user interfaces mostly declaratively. Previously, widgets had to be
created and assembled programmatically, requiring lots of code. Now,
you can use XML to declare your UI, making the code more readable,
easier to maintain, and faster to develop. The Mail sample has been
updated to use the new declarative UI.

* Bundling of resources (ClientBundle): GWT has shipped with
ImageBundles since GWT v1.4, giving developers automatic spriting of
images. ClientBundle generalizes this technique, bringing the power of
combining and optimizing resources into one download to things like
text files, CSS, and XML. This means fewer network round trips, which
in turn can decrease application latency -- especially on mobile
applications.

* Using HtmlUnit for running GWT tests: GWT 2.0 no longer uses SWT or
the old mozilla code (on linux) to run GWT tests. Instead, it uses
HtmlUnit as the built-in browser. HtmlUnit is 100% Java. This means
there is a single GWT distribution for linux, mac, and windows, and
debugging GWT Tests in development mode can be done entirely in a Java
debugger.

Known issues
* If you are planning to run the webAppCreator, i18nCreator, or the
junitCreator scripts on Mac or Linux, please set their executable bits
by doing a 'chmod +x *Creator'
* Our HtmlUnit integration is still not complete. Additionally,
HtmlUnit does not do layout. So tests can fail either because they
exercise layout or they hit bugs due to incomplete integration. If you
want such tests to be ignored on HtmlUnit, please annotate the test
methods with @DoNotRunWith({Platform.Htmlunit})
* The Google Eclipse Plugin will only allow you to add GWT release
directories that include a file with a name like gwt-dev-windows.jar.
You can fool it by sym linking or copying gwt-dev.jar to the
appropriate name.

Breaking changes
* The way arguments are passed to the GWT testing infrastructure has
been revamped. There is now a consistent syntax to support arbitrary
"runstyles", including user-written with no changes to GWT. Though
this does not affect common launch configs, some of the less common
ones will need to be updated. For example, '-selenium FF3' has become
'-runStyle selenium:FF3'

As always, remember that GWT milestone builds like this are use-at-
your-own-risk and we don't recommend it for production use. Please
report any bugs you encounter to the GWT issue tracker (http://
code.google.com/p/google-web-toolkit/issues/list) after doing a quick
search to see if your issue has already been reported.

Download unter http://code.google.com/p/google-web-toolkit/downloads/list?can=1&q=gwt+2.0&colspec=Filename+Summary+Uploaded+Size+DownloadCount.

Labels: ,

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.

Labels: ,

Eclipse Google Plugin unterstützt nun Version 3.5

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.

Labels:

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>.

Labels:

GWT 1.7 mit kleinen Updates ist raus

Sie dazu auch die Ankündigung http://groups.google.com/group/Google-Web-Toolkit-Contributors/browse_thread/thread/a0d35938d940d32d?pli=1.

GWT 1.7 is a minor update that adds better support for Internet Explorer 8, Firefox 3.5, and Safari 4.

Labels:

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

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 );

Labels: ,

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.

Labels: ,

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.

Labels:

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" ...

Labels: ,

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:

Labels:

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

Labels:

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() )
  …

Labels: ,

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.

Labels: ,

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.

Labels: , ,

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:

Labels: , ,

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.

Labels: , ,

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.

Labels: ,

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/

Labels: ,

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.

Labels: ,

GWT 1.6 Milestone 2 ist da

Aus der http://groups.google.com/group/Google-Web-Toolkit-Contributors/browse_thread/thread/09ce2144aa8491e6?pli=1:

The GWT team is happy to announce the availability of Google Web Toolkit 1.6 Milestone 2! Binary distributions are available for download directly from the GWT Google Code project. http://code.google.com/p/google-web-toolkit/downloads/list?can=1&q=1.... If you have already tried GWT 1.6 Milestone 1, you may not notice much difference in Milestone 2. Changes have mostly been general polish and various small bug fixes. For the nitty-gritty details of the changes since the previous milestone, see the attached source control commit logs. You can also watch the GWT bug tracker for the most up-to-date list of resolved issues ( http://code.google.com/p/google-web-toolkit/issues/list?can=1&q=statu...). As for doc, the javadoc that comes bundled with the distribution should be up-to-date, but the online Developer Guide ( http://code.google.com/docreader/#p=google-web-toolkit-doc-1-6) is still being tweaked. We anticipate that GWT 1.6 M2 will be the last milestone build for this version. After a few more bugs fixes and a bit more doc, we expect it to be followed soon by a release candidate build. In the meanwhile, please report bugs to the GWT issue tracker (http://code.google.com/p/google-web-toolkit/issues/list) after doing a quick search to see if your issue has already been reported. If you encounter a bug in 1.6 M2 that you think qualifies as a showstopper, we would greatly appreciate it if you'd also reply on this thread to alert the engineering team.

Über weitere GWT-Updates und Produkte hält http://www.ongwt.com/ auf dem Laufenden.

Labels: ,

GWT API's for SmartClient

SmartClient Ajax (http://www.smartclient.com/) ist eine (weitere) Ajax-Bibliothek für RIA-Anwendungen. Die Demos unter http://www.smartclient.com/index.jsp#_Welcome zeigen auch, was man heute von RIA-Frameworks erwartet: Nette Komponenten, Layout-Manager und Data-Binding über diverse Datenquellen. Es geht nicht nur um hübsche Widgets allein und die Optik ist auch nur OK. (Obwohl http://extjs.com/ in meinen Augen immer noch aus optischen Gründen vorne liegt, hat doch SmartClient Ajax einen sehr großen Vorteil: SmartClient Ajax platform now open source under LGPL. Ext JS ist, wenn man es denn kommerziell einsetzten möchte, nicht ganz billig.)

Für die JS-Library gibt es mit http://code.google.com/p/smartgwt/ auch eine GWT-Wrapper SmartGWT. Der Showcase liegt unter http://www.smartclient.com/smartgwt/showcase/, die JavaDoc unter http://www.smartclient.com/smartgwt/javadoc/. SmartGWT liegt wie auch SmartClient Ajax unter der LGPL. Der Port ist mehr oder weniger die (Fleiß)Arbeit einer Person Sanjiv Jivan. Die folgenden Screenshots stammen aus seinem Blog http://www.jroller.com/sjivan/entry/smartgwt_1_0_released:

Miller Columns

Filter Builder

Labels: , ,

Zwei interessante GWT-Erweiterungen: Gilead und GWT-SL

Sind

Gilead (früher Hibernate4GWT), abgeleitet von "Generic Light Entity Adapter" ist ein interessantes Projekt mit folgendem Konzept:

transform persistent entities back to simple Plain Old Java Object, by removing code instrumentation and replacing persistent collections with their regular counterpart.

Kommt zum Beispiel eine JPA-Bean mit Assoziationen mit Eager-fetching rein, kommt ein vollständig geladener Objektgraph raus. Das ist praktisch genau dann, wenn man einen Daten-Service hat, den man von der GWT-Oberfläche zum Holen der Daten nutzt, aber die JPA-Implementierung baut diverse Proxies ein.

Das SF-Projekt "GWT-Widgets" besteht aus zwei Teilen: Der GWT Server Library (GWT-SL) und der GWT Widget Library. Die GWT-SL ist eine GWT-Spring-Integration. Mit dem GWTRPCServiceExporter können etwa die Spring-POJOs als GWT-Services veröffentlicht werden. Die Doku stellt die Arbeitsweise kurz vor.

Labels: , ,

GWT 1.6 Milestone 1

Der erste Milestone von GWT 1.6 ist unter http://code.google.com/p/google-web-toolkit/downloads/list?can=1&q=1.6.0 verfügbar. In einem Google Group Beitrag werden die Neuerungen genannt:
*** New Project Structure in GWT 1.6 ***
One of the biggest changes to GWT 1.6 is a new project structure. The old
output format has been replaced by the standard Java web app expanded "war"
format, and the actual directory name does default to "/war". Note that the
war directory is not only for compiler output; it is also intended to
contain handwritten static resources that you want to be included in your
webapp alongside GWT modules (that is, things you'd want to version
control). Please also note that the "GWTShell" and "GWTCompiler" tools will
maintain their legacy behavior, but they have been deprecated in favor of
new "HostedMode" and "Compiler" tools which use the new war output. When 1.6
is officially released, we will be encouraging existing projects to update
to the new directory format and to use the new tools to take advantage of
new features and for compatibility with future GWT releases.
The sample projects provided in the GWT distribution provide an example of
correct new project configurations. For more details on the specifics of the
new project format, please see GWT 1.6 WAR design document (
http://code.google.com/p/google-web-toolkit/wiki/WAR_Design_1_6).
A couple of important changes we should highlight here:
- Projects with server-side code (GWT RPC) must configure a "web.xml" file
at "/war/WEB-INF/web.xml". This web.xml file must define and publish any
servlets associated with the web application. See the included DynaTable
sample. Additionally, server-side library dependencies must be copied into
"/war/WEB-INF/lib". For example, any GWT RPC servlets must have a copy of
gwt-servlet.jar in this folder.
- HTML host pages will no longer typically be located in a GWT module's
public path. Instead, we'll be recommending that people take advantage of
the natural web app behavior for serving static files by placing host pages
anywhere in the war structure that makes sense. For exmaple, you might want
to load a GWT module from a JSP page located in the root of your web app. To
keep such handwritten static files separate from those produced by the GWT
compiler, the latter will be placed into module-specific subdirectories. Any
page that wishes to include a GWT module can do so via a script tag by
referencing the GWT-produced "<module>.nocache.js script" within that
module's subdirectory. As of 1.6, we'll be recommending that only
module-specific resources used directly by GWT code, such as image files
needed by widgets, should remain on the public path. See the included
Showcase sample for some examples of this distinction.
- When you do need to load resources from a module's public path, always
construct an absolute URL by prepending GWT.getModuleBaseURL(). For example,
'GWT.getModuleBaseURL() + "dir/file.ext"'. This advice has not changed, but
in the past it was easy to be sloppy with this, because the host page and
GWT module typically lived in the same directory, so using a relative URL
would usually do the right thing. Now that GWT modules live in a
subdirectory, you must reference public resources through
GWT.getModuleBaseURL().
*** Hosted Mode Enhancements ***
Although the legacy GWTShell still uses an embedded Tomcat server, the new
HostedMode runs Jetty instead. There is also a new "Restart Server" button
on the main hosted mode window. Clicking this button restarts the internal
Jetty server, which allows Java code changes to take effect on the server
without having to completely exit and restart hosted mode. This is useful
when making code changes to RPC servlets, or when serializable RPC types are modified and the server and client are out of sync.
*** New EventHandler System ***
Event handlers have been added to replace the old event listeners used by
Widgets, History, and various other classes. The new system has a few
differences from the old system:
- EventHandler methods always take a single parameter: the GwtEvent that the Widget fired. For example, ClickHandler has a single method
onClick(ClickEvent).
- Each GwtEvent contains accessors relevant to the event, such as the key
that was pressed on KeyEvents. Native events provide access to the
underlying native event object.
- Each EventHandler defines only one method, so you do not need to create
empty methods just to satisfy the interface requirements.
For users who create their own Widgets, you no longer need to manage
listeners manually. Every Widget has a HandlerManager that manages all of
its handlers. For native events, such as ClickEvent, just call
addDomHandler() from within your code to register a handler and sink the
associated event on the Widget. When the native event is detected, the
handler will automatically be called. For logical events, such as
SelectionEvent, call addHandler() and fire the event manually using the
fireEvent() method.
You can see examples of EventHandler usage in many of the updated GWT
widgets and samples, or in new projects created with the new webAppCreator tool.
You can now trigger a native event on almost any Element. Create a new
native event using the Document.create*Event() methods, then dispatch it on a specific Element by calling Element.dispatchEvent(). These methods allow you to expand your test coverage in ways that were previously impossible.
*** New Widgets ***
DatePicker
The new DatePicker and DateBox widgets allow your users to select a date
from a calendar. The Showcase sample provides examples of both of these
widgets.
LazyPanel
The new LazyPanel widget allows you to delay the creation of certain
sections of your application until they are first accessed, improving
startup performance. For example, if your application has a seldom used
"Help" section, you can wrap it in a LazyPanel and create the user interface
only if and when the user tries to access it. To use the LazyPanel, extend
the class and override the abstract createWidget() method. The
createWidget() method will be called the first time you call setVisible() on
the LazyPanel.

Labels: ,

GWT 1.5 fertig

GWT 1.5 RC2

Bezug unter http://code.google.com/webtoolkit/download.html . Changelog unter http://code.google.com/webtoolkit/releases/release-notes-1.5.1.html#Release_Notes_Current . Grundsätzlich für GWT 1.5:
  • Java 1.5 Support
  • Compiler Enhancements to Improve Application Speed
  • UI Library Additions: Widget Animations, Visual Themes
  • DOM API for simplified DOM Programming
  • Internationalization Improvements: Bi-di, Pluralization
  • Accessibility Support
  • Enhancements to the JRE Emulation Library

Labels: , ,

Thema der Woche: Google Web Toolkit (GWT)

Das Google Web Toolkit (GWT)  ist ein Web-Framework, mit einem interessanten Ansatz. Statt HTML-Seiten in den Vordergrund zu setzen, schreibt man Gui-Anwendungen in Java mit einer in AWT/Swing bekannten Art (Komponenten/Container und Listener) und lässt diese Anwendung von einem Compiler in JavaScript übersetzten. Die ganze Gui wird somit Teil des Web-Browers. Für den Teil, der nicht im Browser läuft, also die gesamte Geschäftslogik und Datenbank, gibt es eine RCP-Schnittstelle.

Schaue drei Beispiele für die Möglichkeiten von GWT an.

Um etwas mehr vom Hintergrund zu verstehen, kann man http://www.maximilien.org/presentations/2006/AJAX_and_GWT_Public_07052006.pdf lesen.

Wer praktisch spielen möchte, kann GWT+Eclipse+Plugin nach dem folgenden Blog-Eintrag installieren. Mehr über die Gui-Komponenten erfährt man in einem Kapitel des Hanser-Verlags (Übersetzung von GWT in Action): Widgets (deutsch).

Labels: ,

GWT-Beispiel in Eclipse


  1. Beziehe Tomcat 6.0 und entpacke es.
  2. Beziehe GWT 1.5: http://code.google.com/webtoolkit/download.html. Gerade aktuelle Version: http://google-web-toolkit.googlecode.com/files/gwt-windows-1.5.3.zip
  3. Lade Eclipse IDE for Java EE Developers (http://phoenix.eclipse.org/packages/)
  4. Beziehe Cypal Studio for GWT (http://code.google.com/p/cypal-studio/downloads/list).
    Das Neue bei Eclipse 3.4: Man packt das GWT-Plugin aus und setzt es in einen
    neuen Unterordner Cypal/plugins von Eclipses dropins-Ordner
    (und nicht wie früher plugins). Zum Beispiel stehen dann die drei
    Jars direkt unter C:\Programme\Eclipse\eclipse\dropins\Cypal\plugins.
  5. Starte Eclipse
  6. Zwei Einstellungen unter Window > Preferences sind vorzunehmen: 1) Installieren in Eclipse Tomcat 6.0. Gehe unter Server > Runtime Environment, Add, ... 2) GWT-Pfad einstellen: Bei Cypal Studio das GWT Home anpassen.
  7. Lege in Eclipse ein File > New > Other > Web > Dynamic Web Project an. Unter Configuration gehe auf Modify. Setze ein Häkchen bei Cypal's GWT Facet. Setze Java auf 5.0. Das JavaScript Toolkit ist nicht nötig.
  8. Beim neuen Web-Projekt gehe auf Properties, dann Java Build Path im Reiter Libraries. Lösche die Apache Tomcat Libraries.
  9. Lege mit File > New > Other > Cypal Studio ein neues Module an. Gib Paketnamen und Name an. Das Plugin erzeugt daraufhin ein Unterpaket client, mit der Klasse, die EntryPoint erweitert.
  10. In die Vorlage setzte in onModuleLoad():
    RootPanel.get().add( new Button( "Sag 'Hallo'", new ClickListener() {
    public void onClick( Widget w ) {
    Window.alert( "Hallo" );
    }
    } ) );

    Lasse Strg+Shift+O alle import-Anweisungen einsetzen. (Paket com.google.gwt.core.client und com.google.gwt.core.client.io).
  11. Auf dem Projekt gehe unter Run As > Run Configuration, GWT Hosted Mode Application, New_configuration. Ist das Modul ausgewählt. startet Run die Google GWT-Umgebung.
  12. Für einen neuen Service öffne File > New > Other Cypal Studio > Remote Service. Der Service soll FileService heißen. Die Service-URL soll file heißen. Bestätige den Dialog. Der Generator legt eine Klasse und eine Schnittstelle für die Client- und eine weitere Klasse für die Server-Seite an. Wähle zunächst den Client aus com.tutego.client.FileService. Setze in den Rumpf der Klasse die Methode String[] getFiles(String path);. Ein Compilerfehler taucht dann bei der Realisierung com.tutego.server.FileServiceImpl auf. Setze dort hinein: public String[] getFiles( String path ) { return new File(path).list(); }
  13. Erweitere in der Hauptklasse die Methode onModuleLoad() durch:
    final ListBox listBox = new ListBox();
    FileService.Util.getInstance().getFiles( "c:/", new AsyncCallback<String[]>() {
    public void onFailure( Throwable throwable ) { }
    public void onSuccess( String[] strings ) {
    for ( String string : strings )
    listBox.addItem( string );
    };
    } );
    RootPanel.get().add( listBox );
  14. Zum Weiterlesen http://blogs.pathf.com/agileajax/2007/07/36-gwt-tutorial.html

Labels: ,