Revision: 8926
Author: [email protected]
Date: Sun Oct 3 11:35:25 2010
Log: Introduce OptionalFieldEditor to support editing nullable fields.
Update DynaTableRf sample to demonstrate.
Patch by: bobv
Review by: rjrjr
Suggested by: pjulien
Review at http://gwt-code-reviews.appspot.com/948801
http://code.google.com/p/google-web-toolkit/source/detail?r=8926
Added:
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/MentorSelector.java
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/MentorSelector.ui.xml
/trunk/user/src/com/google/gwt/editor/client/adapters/OptionalFieldEditor.java
Modified:
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/PersonEditorWorkflow.java
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/NameLabel.java
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/PersonEditor.java
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/PersonEditor.ui.xml
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/SummaryWidget.java
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Person.java
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/SchoolCalendarService.java
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/DynaTableRequestFactory.java
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/PersonProxy.java
/trunk/user/src/com/google/gwt/editor/rebind/model/EditorModel.java
/trunk/user/test/com/google/gwt/editor/client/SimpleBeanEditorTest.java
=======================================
--- /dev/null
+++
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/MentorSelector.java
Sun Oct 3 11:35:25 2010
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
not
+ * use this file except in compliance with the License. You may obtain a
copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
under
+ * the License.
+ */
+package com.google.gwt.sample.dynatablerf.client.widgets;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.editor.client.IsEditor;
+import com.google.gwt.editor.client.adapters.OptionalFieldEditor;
+import com.google.gwt.event.dom.client.ClickEvent;
+import com.google.gwt.requestfactory.shared.Receiver;
+import com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory;
+import com.google.gwt.sample.dynatablerf.shared.PersonProxy;
+import com.google.gwt.uibinder.client.UiBinder;
+import com.google.gwt.uibinder.client.UiField;
+import com.google.gwt.uibinder.client.UiHandler;
+import com.google.gwt.user.client.ui.Button;
+import com.google.gwt.user.client.ui.Composite;
+import com.google.gwt.user.client.ui.Widget;
+
+/**
+ * This demonstrates how an editor can be constructed to handle optional
fields.
+ * The Person domain object's mentor property is initially
<code>null</code>.
+ * This type delegates editing control to an instance of the
+ * {...@link OptionalValueEditor} adapter class.
+ */
+public class MentorSelector extends Composite implements
+ IsEditor<OptionalFieldEditor<PersonProxy, NameLabel>> {
+
+ interface Binder extends UiBinder<Widget, MentorSelector> {
+ }
+
+ @UiField
+ Button choose;
+
+ @UiField
+ Button clear;
+
+ @UiField
+ NameLabel nameLabel;
+
+ private final OptionalFieldEditor<PersonProxy, NameLabel> editor;
+ private final DynaTableRequestFactory factory;
+
+ public MentorSelector(DynaTableRequestFactory factory) {
+ this.factory = factory;
+ initWidget(GWT.<Binder> create(Binder.class).createAndBindUi(this));
+ editor = OptionalFieldEditor.of(nameLabel);
+ }
+
+ public OptionalFieldEditor<PersonProxy, NameLabel> asEditor() {
+ return editor;
+ }
+
+ public void setEnabled(boolean enabled) {
+ choose.setEnabled(enabled);
+ clear.setEnabled(enabled);
+ }
+
+ @Override
+ protected void onUnload() {
+ nameLabel.cancelSubscription();
+ }
+
+ @UiHandler("choose")
+ void onChoose(ClickEvent event) {
+ setEnabled(false);
+ factory.schoolCalendarRequest().getRandomPerson().to(
+ new Receiver<PersonProxy>() {
+ @Override
+ public void onSuccess(PersonProxy response) {
+ setValue(response);
+ setEnabled(true);
+ }
+ }).fire();
+ }
+
+ @UiHandler("clear")
+ void onClear(ClickEvent event) {
+ setValue(null);
+ }
+
+ /**
+ * This method is not called by the Editor framework.
+ */
+ private void setValue(PersonProxy person) {
+ editor.setValue(person);
+ nameLabel.setVisible(person != null);
+ }
+}
=======================================
--- /dev/null
+++
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/MentorSelector.ui.xml
Sun Oct 3 11:35:25 2010
@@ -0,0 +1,10 @@
+<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder'
xmlns:g='urn:import:com.google.gwt.user.client.ui'
+ xmlns:dt='urn:import:com.google.gwt.sample.dynatablerf.client.widgets'
+ xmlns:e='urn:import:com.google.gwt.editor.client.ui'>
+ <ui:style src="../common.css" />
+ <g:FlowPanel>
+ <dt:NameLabel ui:field="nameLabel"
stylePrimaryName="{style.editField}" />
+ <g:Button ui:field="choose">Choose Randomly</g:Button>
+ <g:Button ui:field="clear">Clear</g:Button>
+ </g:FlowPanel>
+</ui:UiBinder>
=======================================
--- /dev/null
+++
/trunk/user/src/com/google/gwt/editor/client/adapters/OptionalFieldEditor.java
Sun Oct 3 11:35:25 2010
@@ -0,0 +1,101 @@
+/*
+ * Copyright 2010 Google Inc.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you may
not
+ * use this file except in compliance with the License. You may obtain a
copy of
+ * the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT
+ * WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+ * License for the specific language governing permissions and limitations
under
+ * the License.
+ */
+package com.google.gwt.editor.client.adapters;
+
+import com.google.gwt.editor.client.CompositeEditor;
+import com.google.gwt.editor.client.Editor;
+import com.google.gwt.editor.client.EditorDelegate;
+import com.google.gwt.editor.client.LeafValueEditor;
+
+/**
+ * This adapter can be used when a type being edited has an optional field
that
+ * may be nullified or reassigned as part of the editing process. This
consumer
+ * of this adapter will typically expose it via the
+ * {...@link com.google.gwt.editor.client.IsEditor IsEditor} interface:
+ *
+ * <pre>
+ * class FooSelector extends Composite implements
IsEditor<OptionalFieldEditor<Foo, FooEditor>> {
+ * private OptionalFieldEditor<Foo, FooEditor> editor =
OptionalFieldEditor.of(new FooEditor());
+ * public OptionalFieldEditor<Foo, FooEditor> asEditor() {
+ * return editor;
+ * }
+ * }
+ * </pre>
+ *
+ * @param <T> The type of data being managed
+ * @param <E> The type of Editor
+ */
+public class OptionalFieldEditor<T, E extends Editor<T>> implements
+ CompositeEditor<T, T, E>, LeafValueEditor<T> {
+
+ /**
+ * Construct an OptionalFieldEditor backed by the given sub-Editor.
+ *
+ * @param <T> The type of data being managed
+ * @param <E> The type of Editor
+ * @param subEditor the sub-Editor that will be attached to the Editor
+ * hierarchy
+ * @return a new instance of OptionalFieldEditor
+ */
+ public static <T, E extends Editor<T>> OptionalFieldEditor<T, E> of(
+ E subEditor) {
+ return new OptionalFieldEditor<T, E>(subEditor);
+ }
+
+ private EditorChain<T, E> chain;
+ private T currentValue;
+ private final E subEditor;
+
+ protected OptionalFieldEditor(E subEditor) {
+ this.subEditor = subEditor;
+ }
+
+ public void flush() {
+ currentValue = chain.getValue(subEditor);
+ }
+
+ /**
+ * Returns an empty string because there is only ever one sub-editor
used.
+ */
+ public String getPathElement(E subEditor) {
+ return "";
+ }
+
+ public T getValue() {
+ return currentValue;
+ }
+
+ public void onPropertyChange(String... paths) {
+ }
+
+ public void setDelegate(EditorDelegate<T> delegate) {
+ }
+
+ public void setEditorChain(EditorChain<T, E> chain) {
+ this.chain = chain;
+ }
+
+ public void setValue(T value) {
+ if (currentValue != null && value == null) {
+ chain.detach(subEditor);
+ }
+ currentValue = value;
+ if (value != null) {
+ chain.attach(value, subEditor);
+ }
+ }
+
+}
=======================================
---
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/PersonEditorWorkflow.java
Fri Oct 1 18:15:55 2010
+++
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/PersonEditorWorkflow.java
Sun Oct 3 11:35:25 2010
@@ -75,7 +75,7 @@
@UiField
CheckBox favorite;
- @UiField
+ @UiField(provided = true)
PersonEditor personEditor;
private Driver editorDriver;
@@ -88,6 +88,7 @@
this.requestFactory = requestFactory;
this.manager = manager;
this.person = person;
+ personEditor = new PersonEditor(requestFactory);
Binder.BINDER.createAndBindUi(this);
contents.addDomHandler(new KeyUpHandler() {
public void onKeyUp(KeyUpEvent event) {
@@ -99,13 +100,11 @@
}
@UiHandler("cancel")
- @SuppressWarnings("unused")
void onCancel(ClickEvent e) {
dialog.hide();
}
@UiHandler("save")
- @SuppressWarnings("unused")
void onSave(ClickEvent e) {
// MOVE TO ACTIVITY END
RequestContext context = editorDriver.flush();
@@ -128,7 +127,6 @@
}
@UiHandler("favorite")
- @SuppressWarnings("unused")
void onValueChanged(ValueChangeEvent<Boolean> event) {
manager.setFavorite(person, favorite.getValue());
}
=======================================
---
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/NameLabel.java
Fri Sep 17 07:40:00 2010
+++
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/NameLabel.java
Sun Oct 3 11:35:25 2010
@@ -35,14 +35,20 @@
private PersonProxy person;
private HandlerRegistration subscription;
+ public NameLabel() {
+ this(null);
+ }
+
public NameLabel(final EventBus eventBus) {
initWidget(nameEditor);
- nameEditor.addClickHandler(new ClickHandler() {
- public void onClick(ClickEvent event) {
- eventBus.fireEvent(new EditPersonEvent(person));
- }
- });
+ if (eventBus != null) {
+ nameEditor.addClickHandler(new ClickHandler() {
+ public void onClick(ClickEvent event) {
+ eventBus.fireEvent(new EditPersonEvent(person));
+ }
+ });
+ }
}
public void flush() {
=======================================
---
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/PersonEditor.java
Wed Sep 15 02:26:39 2010
+++
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/PersonEditor.java
Sun Oct 3 11:35:25 2010
@@ -20,6 +20,7 @@
import com.google.gwt.core.client.Scheduler.ScheduledCommand;
import com.google.gwt.editor.client.Editor;
import com.google.gwt.editor.client.ui.ValueBoxEditorDecorator;
+import com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory;
import com.google.gwt.sample.dynatablerf.shared.PersonProxy;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
@@ -40,6 +41,9 @@
@UiField
ValueBoxEditorDecorator<String> description;
+ @UiField(provided = true)
+ MentorSelector mentor;
+
@UiField
ValueBoxEditorDecorator<String> name;
@@ -49,7 +53,8 @@
@UiField
Focusable nameBox;
- public PersonEditor() {
+ public PersonEditor(DynaTableRequestFactory factory) {
+ mentor = new MentorSelector(factory);
initWidget(GWT.<Binder> create(Binder.class).createAndBindUi(this));
}
=======================================
---
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/PersonEditor.ui.xml
Wed Sep 15 02:26:39 2010
+++
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/PersonEditor.ui.xml
Sun Oct 3 11:35:25 2010
@@ -33,5 +33,7 @@
</div>
Address:
<dt:AddressEditor ui:field="address"
stylePrimaryName="{style.editField}" />
+ Mentor:
+ <dt:MentorSelector ui:field="mentor"
stylePrimaryName="{style.editField}" />
</g:HTMLPanel>
</ui:UiBinder>
=======================================
---
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/SummaryWidget.java
Fri Oct 1 18:15:55 2010
+++
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/SummaryWidget.java
Sun Oct 3 11:35:25 2010
@@ -163,7 +163,6 @@
}
@UiHandler("create")
- @SuppressWarnings("unused")
void onCreate(ClickEvent event) {
PersonRequest context = requestFactory.personRequest();
AddressProxy address = context.create(AddressProxy.class);
=======================================
---
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Person.java
Thu Sep 30 06:57:12 2010
+++
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Person.java
Sun Oct 3 11:35:25 2010
@@ -54,6 +54,8 @@
@NotNull
private String description = "DESC";
+ private Person mentor;
+
@NotNull
@Size(min = 2, message = "Persons aren't just characters")
private String name;
@@ -82,6 +84,7 @@
classSchedule = copyFrom.classSchedule;
description = copyFrom.description;
name = copyFrom.name;
+ mentor = copyFrom.mentor;
id = copyFrom.id;
version = copyFrom.version;
note = copyFrom.note;
@@ -109,6 +112,10 @@
public String getId() {
return id;
}
+
+ public Person getMentor() {
+ return mentor;
+ }
public String getName() {
return name;
@@ -165,6 +172,10 @@
this.id = id;
address.setId(id);
}
+
+ public void setMentor(Person mentor) {
+ this.mentor = mentor;
+ }
public void setName(String name) {
this.name = name;
=======================================
---
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/SchoolCalendarService.java
Fri Sep 24 12:10:50 2010
+++
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/SchoolCalendarService.java
Sun Oct 3 11:35:25 2010
@@ -20,6 +20,7 @@
import java.io.IOException;
import java.util.List;
+import java.util.Random;
import javax.servlet.Filter;
import javax.servlet.FilterChain;
@@ -32,6 +33,9 @@
* The server side service class.
*/
public class SchoolCalendarService implements Filter {
+ private static final boolean[] ALL_DAYS = new boolean[] {
+ true, true, true, true, true, true, true};
+
private static final ThreadLocal<PersonSource> PERSON_SOURCE = new
ThreadLocal<PersonSource>();
public static Person createPerson() {
@@ -46,12 +50,14 @@
return PERSON_SOURCE.get().findPerson(id);
}
- /**
- * XXX Remove this once primitive lists work.
- */
public static List<Person> getPeople(int startIndex, int maxCount) {
- return getPeople(startIndex, maxCount, new boolean[] {
- true, true, true, true, true, true, true});
+ return getPeople(startIndex, maxCount, ALL_DAYS);
+ }
+
+ public static Person getRandomPerson() {
+ PersonSource source = PERSON_SOURCE.get();
+ return source.getPeople(new Random().nextInt(source.countPeople()), 1,
+ ALL_DAYS).get(0);
}
public static void persist(Address address) {
=======================================
---
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/DynaTableRequestFactory.java
Fri Oct 1 18:15:55 2010
+++
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/DynaTableRequestFactory.java
Sun Oct 3 11:35:25 2010
@@ -55,6 +55,7 @@
@Service(SchoolCalendarService.class)
interface SchoolCalendarRequest extends RequestContext {
Request<List<PersonProxy>> getPeople(int startIndex, int maxCount);
+ Request<PersonProxy> getRandomPerson();
}
AddressRequest addressRequest();
=======================================
---
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/PersonProxy.java
Fri Sep 24 12:10:50 2010
+++
/trunk/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/PersonProxy.java
Sun Oct 3 11:35:25 2010
@@ -25,13 +25,15 @@
*/
@ProxyFor(Person.class)
public interface PersonProxy extends EntityProxy {
-
+
AddressProxy getAddress();
String getDescription();
-
+
String getId();
+ PersonProxy getMentor();
+
String getName();
String getNote();
@@ -42,9 +44,11 @@
void setDescription(String description);
+ void setMentor(PersonProxy mentor);
+
void setName(String name);
void setNote(String note);
-
+
EntityProxyId<PersonProxy> stableId();
}
=======================================
--- /trunk/user/src/com/google/gwt/editor/rebind/model/EditorModel.java Fri
Oct 1 18:15:55 2010
+++ /trunk/user/src/com/google/gwt/editor/rebind/model/EditorModel.java Sun
Oct 3 11:35:25 2010
@@ -78,8 +78,8 @@
throws UnableToCompleteException {
JClassType editorIntf = editorType.getOracle().findType(
Editor.class.getName());
- JClassType parameterization[] =
ModelUtils.findParameterizationOf(editorIntf,
- editorType);
+ JClassType parameterization[] = ModelUtils.findParameterizationOf(
+ editorIntf, editorType);
if (parameterization != null) {
return parameterization[0];
}
@@ -97,8 +97,8 @@
JClassType editorType) throws UnableToCompleteException {
JClassType editorIntf = editorType.getOracle().findType(
IsEditor.class.getName());
- JClassType[] parameterization =
ModelUtils.findParameterizationOf(editorIntf,
- editorType);
+ JClassType[] parameterization = ModelUtils.findParameterizationOf(
+ editorIntf, editorType);
if (parameterization != null) {
return parameterization[0];
}
@@ -222,7 +222,8 @@
die(tooManyInterfacesMessage(intf));
}
- JClassType[] parameters =
ModelUtils.findParameterizationOf(driverType, intf);
+ JClassType[] parameters = ModelUtils.findParameterizationOf(driverType,
+ intf);
assert parameters.length == 2 : "Unexpected number of type parameters";
proxyType = parameters[0];
editorType = parameters[1];
@@ -287,14 +288,14 @@
flatData.addAll(data);
allData.addAll(data);
for (EditorData d : data) {
- if (!d.isLeafValueEditor()) {
- descendIntoSubEditor(allData, d);
- }
+ descendIntoSubEditor(allData, d);
}
}
/**
* Create the EditorData objects for the {...@link #editorData} type.
+ * Essentially, the point of this method is to calculate the paths of all
+ * Editor types referenced by {...@link #editorType}.
*/
private EditorData[] calculateEditorData() throws
UnableToCompleteException {
List<EditorData> flatData = new ArrayList<EditorData>();
@@ -340,6 +341,16 @@
editorSoFar).build();
List<EditorData> accumulator = new ArrayList<EditorData>();
descendIntoSubEditor(accumulator, subEditor);
+
+ /*
+ * It's necessary to generate a sub-Model here so that any Editor
types
+ * reachable only through the composite type will be added to the
types
+ * map. The path data isn't actually useful, since we rely on
+ * CompositeEditor.getPathElement() at runtime.
+ */
+ EditorModel subModel = new EditorModel(this,
subEditor.getEditorType(),
+ subEditor, subEditor.getEditedType());
+ poisoned |= subModel.poisoned;
}
if (!typeData.containsKey(editorType)) {
=======================================
--- /trunk/user/test/com/google/gwt/editor/client/SimpleBeanEditorTest.java
Mon Sep 13 09:30:34 2010
+++ /trunk/user/test/com/google/gwt/editor/client/SimpleBeanEditorTest.java
Sun Oct 3 11:35:25 2010
@@ -18,6 +18,7 @@
import com.google.gwt.core.client.GWT;
import com.google.gwt.editor.client.adapters.EditorSource;
import com.google.gwt.editor.client.adapters.ListEditor;
+import com.google.gwt.editor.client.adapters.OptionalFieldEditor;
import com.google.gwt.editor.client.adapters.SimpleEditor;
import com.google.gwt.junit.client.GWTTestCase;
@@ -103,6 +104,19 @@
interface PersonEditorWithLeafAddressEditorDriver extends
SimpleBeanEditorDriver<Person, PersonEditorWithLeafAddressEditor> {
}
+
+ interface PersonEditorWithOptionalAddressDriver extends
+ SimpleBeanEditorDriver<Person,
PersonEditorWithOptionalAddressEditor> {
+ }
+
+ class PersonEditorWithOptionalAddressEditor implements Editor<Person> {
+ OptionalFieldEditor<Address, AddressEditor> address;
+ SimpleEditor<String> name = SimpleEditor.of(UNINITIALIZED);
+
+ public PersonEditorWithOptionalAddressEditor(AddressEditor delegate) {
+ address = OptionalFieldEditor.of(delegate);
+ }
+ }
class PersonEditorWithValueAwareAddressEditor implements Editor<Person> {
ValueAwareAddressEditor addressEditor = new ValueAwareAddressEditor();
@@ -419,6 +433,30 @@
driver.flush();
assertEquals("edited", person.addresses.get(1).getCity());
}
+
+ public void testOptionalField() {
+ PersonEditorWithOptionalAddressDriver driver =
GWT.create(PersonEditorWithOptionalAddressDriver.class);
+ person.address = null;
+
+ AddressEditor delegate = new AddressEditor();
+ PersonEditorWithOptionalAddressEditor editor = new
PersonEditorWithOptionalAddressEditor(
+ delegate);
+ driver.initialize(editor);
+
+ // Make sure we don't blow up with the null field
+ driver.edit(person);
+ editor.address.setValue(personAddress);
+ assertEquals("City", delegate.city.getValue());
+ delegate.city.setValue("Hello");
+ driver.flush();
+ assertNotNull(person.address);
+ assertSame(personAddress, person.address);
+ assertEquals("Hello", personAddress.city);
+
+ editor.address.setValue(null);
+ driver.flush();
+ assertNull(person.address);
+ }
public void testValueAwareEditorInDeclaredSlot() {
PersonEditorWithValueAwareAddressEditorDriver driver =
GWT.create(PersonEditorWithValueAwareAddressEditorDriver.class);
--
http://groups.google.com/group/Google-Web-Toolkit-Contributors