Cleanups for the DynaTableRf sample. Resolves GWT issue 5413. Patch by: bobv Review by: rjrjr Review at http://gwt-code-reviews.appspot.com/975801 git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@9065 8db76d5a-ed1c-0410-87a9-c151d255dfc7
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/DynaTableRf.gwt.xml b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/DynaTableRf.gwt.xml index 4dd6e1d..63af732 100644 --- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/DynaTableRf.gwt.xml +++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/DynaTableRf.gwt.xml
@@ -22,7 +22,7 @@ <inherits name='com.google.gwt.logging.Logging'/> <set-property name="gwt.logging.enabled" value="TRUE"/> - <set-property name="gwt.logging.logLevel" value="INFO"/> + <set-property name="gwt.logging.logLevel" value="SEVERE"/> <set-property name="gwt.logging.consoleHandler" value="ENABLED" /> <set-property name="gwt.logging.developmentModeHandler" value="ENABLED" /> <set-property name="gwt.logging.firebugHandler" value="ENABLED" />
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/DynaTableRf.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/DynaTableRf.java index 4f78598..377e8b2 100644 --- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/DynaTableRf.java +++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/DynaTableRf.java
@@ -1,5 +1,5 @@ /* - * Copyright 2007 Google Inc. + * 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 @@ -28,7 +28,6 @@ import com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory; import com.google.gwt.uibinder.client.UiBinder; import com.google.gwt.uibinder.client.UiField; -import com.google.gwt.user.client.Window; import com.google.gwt.user.client.ui.RootLayoutPanel; import com.google.gwt.user.client.ui.Widget; @@ -57,10 +56,12 @@ @UiField(provided = true) DayFilterWidget filter; + /** + * This method sets up the top-level services used by the application. + */ public void onModuleLoad() { GWT.setUncaughtExceptionHandler(new UncaughtExceptionHandler() { public void onUncaughtException(Throwable e) { - Window.alert("Error: " + e.getMessage()); log.log(Level.SEVERE, e.getMessage(), e); } }); @@ -74,6 +75,7 @@ return requests.loggingRequest(); } }; + Logger.getLogger("").addHandler(new ErrorDialog().getHandler()); Logger.getLogger("").addHandler( new RequestFactoryLogHandler(provider, Level.WARNING, new ArrayList<String>())); @@ -86,5 +88,13 @@ RootLayoutPanel.get().add( GWT.<Binder> create(Binder.class).createAndBindUi(this)); + + // Fast test to see if the sample is not being run from devmode + if (GWT.getHostPageBaseURL().startsWith("file:")) { + log.log(Level.SEVERE, "The DynaTableRf sample cannot be run without its" + + " server component. If you are running the sample from a" + + " GWT distribution, use the 'ant devmode' target to launch" + + " the DTRF server."); + } } }
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/ErrorDialog.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/ErrorDialog.java new file mode 100644 index 0000000..63f0f8c --- /dev/null +++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/ErrorDialog.java
@@ -0,0 +1,84 @@ +/* + * 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; + +import com.google.gwt.core.client.GWT; +import com.google.gwt.event.dom.client.ClickEvent; +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.Window; +import com.google.gwt.user.client.ui.DialogBox; +import com.google.gwt.user.client.ui.TextArea; + +import java.util.logging.Handler; +import java.util.logging.Level; +import java.util.logging.LogRecord; + +/** + * A simple glasspanel popup that terminates interaction with the application. + */ +class ErrorDialog { + interface Binder extends UiBinder<DialogBox, ErrorDialog> { + } + + @UiField + DialogBox errorDialog; + + @UiField + TextArea errorMessage; + + public ErrorDialog() { + GWT.<Binder> create(Binder.class).createAndBindUi(this); + } + + /** + * @return + */ + public Handler getHandler() { + return new Handler() { + { + setLevel(Level.SEVERE); + } + + @Override + public void close() { + } + + @Override + public void flush() { + } + + @Override + public void publish(LogRecord record) { + if (isLoggable(record)) { + errorMessage.setText(record.getMessage()); + errorDialog.center(); + } + } + }; + } + + @UiHandler("dismiss") + void onDismiss(ClickEvent event) { + errorDialog.hide(); + } + + @UiHandler("reload") + void onReload(ClickEvent event) { + Window.Location.reload(); + } +} \ No newline at end of file
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/ErrorDialog.ui.xml b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/ErrorDialog.ui.xml new file mode 100644 index 0000000..5ca904c --- /dev/null +++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/ErrorDialog.ui.xml
@@ -0,0 +1,39 @@ +<ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder' xmlns:g='urn:import:com.google.gwt.user.client.ui'> + <ui:style> + .dialog { + background: white; + border: thin solid black; + margin: 2px; + overflow: hidden; + padding: 5px; + -moz-border-radius: 5px; + -webkit-border-radius: 5px; + } + + .glass { + filter: literal('alpha(opacity = 75)'); + opacity: 0.75; + background-color: #000000; + } + + .message { + height: 400px; + width: 400px; + } + </ui:style> + <g:DialogBox ui:field="errorDialog" glassEnabled="true" + stylePrimaryName="{style.dialog}" glassStyleName="{style.glass}"> + <g:caption> + <b>An unexpected application error has occurred</b> + <br /> + You may wish to reload the application + </g:caption> + <g:HTMLPanel> + <g:TextArea ui:field="errorMessage" readOnly="true" + stylePrimaryName="{style.message}"></g:TextArea> + <br /> + <g:Button ui:field="dismiss">Continue</g:Button> + <g:Button ui:field="reload">Reload</g:Button> + </g:HTMLPanel> + </g:DialogBox> +</ui:UiBinder> \ No newline at end of file
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/FavoritesManager.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/FavoritesManager.java index b291835..a2696aa 100644 --- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/FavoritesManager.java +++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/FavoritesManager.java
@@ -82,14 +82,13 @@ return favoriteIds.contains(person.stableId()); } - public void setFavorite(PersonProxy person, boolean isFavorite) { + public void setFavorite(EntityProxyId<PersonProxy> id, boolean isFavorite) { if (isFavorite) { - favoriteIds.add(person.stableId()); + favoriteIds.add(id); } else { - favoriteIds.remove(person.stableId()); + favoriteIds.remove(id); } - eventBus.fireEventFromSource(new MarkFavoriteEvent(person, isFavorite), - this); + eventBus.fireEventFromSource(new MarkFavoriteEvent(id, isFavorite), this); } }
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/PersonEditorWorkflow.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/PersonEditorWorkflow.java index 6e36f94..81dae75 100644 --- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/PersonEditorWorkflow.java +++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/PersonEditorWorkflow.java
@@ -30,8 +30,8 @@ import com.google.gwt.sample.dynatablerf.client.events.EditPersonEvent; import com.google.gwt.sample.dynatablerf.client.widgets.PersonEditor; import com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory; -import com.google.gwt.sample.dynatablerf.shared.PersonProxy; import com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory.PersonRequest; +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; @@ -99,38 +99,60 @@ }, KeyUpEvent.getType()); } + /** + * Called by the cancel button when it is clicked. This method will just tear + * down the UI and clear the state of the workflow. + */ @UiHandler("cancel") - void onCancel(@SuppressWarnings("unused") ClickEvent event) { + void onCancel(ClickEvent event) { dialog.hide(); } + /** + * Called by the edit dialog's save button. This method will flush the + * contents of the UI into the PersonProxy that is being edited, check for + * errors, and send the request to the server. + */ @UiHandler("save") - void onSave(@SuppressWarnings("unused") ClickEvent event) { - // MOVE TO ACTIVITY END + void onSave(ClickEvent event) { + // Flush the contents of the UI RequestContext context = editorDriver.flush(); + + // Check for errors if (editorDriver.hasErrors()) { dialog.setText("Errors detected locally"); return; } + + // Send the request context.fire(new Receiver<Void>() { @Override public void onSuccess(Void response) { + // If everything went as planned, just dismiss the dialog box dialog.hide(); } @Override public void onViolation(Set<Violation> errors) { + // Otherwise, show ConstraintViolations in the UI dialog.setText("Errors detected on the server"); editorDriver.setViolations(errors); } }); } + /** + * Called by the favorite checkbox when its value has been toggled. + */ @UiHandler("favorite") - void onValueChanged(@SuppressWarnings("unused") ValueChangeEvent<Boolean> event) { - manager.setFavorite(person, favorite.getValue()); + void onValueChanged(ValueChangeEvent<Boolean> event) { + manager.setFavorite(person.stableId(), favorite.getValue()); } + /** + * Construct and display the UI that will be used to edit the current + * PersonProxy, using the given RequestContext to accumulate the edits. + */ private void edit(RequestContext requestContext) { editorDriver = GWT.create(Driver.class); editorDriver.initialize(requestFactory, personEditor); @@ -149,19 +171,23 @@ private void fetchAndEdit() { // The request is configured arbitrarily Request<PersonProxy> fetchRequest = requestFactory.find(person.stableId()); - // Add the paths that the EditorDelegate computes are necessary + + // Add the paths that the EditorDrives computes fetchRequest.with(editorDriver.getPaths()); // We could do more with the request, but we just fire it - fetchRequest.fire(new Receiver<PersonProxy>() { + fetchRequest.to(new Receiver<PersonProxy>() { @Override public void onSuccess(PersonProxy person) { PersonEditorWorkflow.this.person = person; // Start the edit process PersonRequest context = requestFactory.personRequest(); - context.persist().using(person); + // Display the UI edit(context); + // Configure the method invocation to be sent in the context + context.persist().using(person); + // The context will be fire()'ed from the onSave() method } - }); + }).fire(); } }
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/events/MarkFavoriteEvent.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/events/MarkFavoriteEvent.java index 0f04762..a63d1f0 100644 --- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/events/MarkFavoriteEvent.java +++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/events/MarkFavoriteEvent.java
@@ -17,6 +17,7 @@ import com.google.gwt.event.shared.EventHandler; import com.google.gwt.event.shared.GwtEvent; +import com.google.gwt.requestfactory.shared.EntityProxyId; import com.google.gwt.sample.dynatablerf.shared.PersonProxy; /** @@ -32,11 +33,11 @@ public static final Type<Handler> TYPE = new Type<Handler>(); - private final PersonProxy person; + private final EntityProxyId<PersonProxy> id; private final boolean isFavorite; - public MarkFavoriteEvent(PersonProxy person, boolean isFavorite) { - this.person = person; + public MarkFavoriteEvent(EntityProxyId<PersonProxy> id, boolean isFavorite) { + this.id = id; this.isFavorite = isFavorite; } @@ -45,8 +46,8 @@ return TYPE; } - public PersonProxy getPerson() { - return person; + public EntityProxyId<PersonProxy> getId() { + return id; } public boolean isFavorite() {
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/AddressEditor.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/AddressEditor.java index 3a210a2..f4578a6 100644 --- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/AddressEditor.java +++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/AddressEditor.java
@@ -38,7 +38,7 @@ @UiField ValueBoxEditorDecorator<String> state; @UiField - ValueBoxEditorDecorator<Integer> zip; + ValueBoxEditorDecorator<String> zip; public AddressEditor() { initWidget(GWT.<Binder> create(Binder.class).createAndBindUi(this));
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/AddressEditor.ui.xml b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/AddressEditor.ui.xml index 52c2792..992b10c 100644 --- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/AddressEditor.ui.xml +++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/AddressEditor.ui.xml
@@ -1,4 +1,5 @@ <ui:UiBinder xmlns:ui='urn:ui:com.google.gwt.uibinder' + xmlns:dt='urn:import:com.google.gwt.sample.dynatablerf.client.widgets' xmlns:g='urn:import:com.google.gwt.user.client.ui' xmlns:e='urn:import:com.google.gwt.editor.ui.client'> <ui:style src="../common.css"> @@ -34,7 +35,7 @@ <e:ValueBoxEditorDecorator ui:field="zip" stylePrimaryName="{style.editField}"> <e:valuebox> - <g:IntegerBox stylePrimaryName="{style.editField}" /> + <dt:ZipPlusFourBox stylePrimaryName="{style.editField}" /> </e:valuebox> </e:ValueBoxEditorDecorator> <br />
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/DayFilterWidget.ui.xml b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/DayFilterWidget.ui.xml index f6ec9f9..658a9ab 100644 --- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/DayFilterWidget.ui.xml +++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/DayFilterWidget.ui.xml
@@ -1,5 +1,5 @@ -<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'> +<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'> <ui:style> .all { float: left; @@ -14,23 +14,22 @@ } </ui:style> <g:FlowPanel> - <dt:DayCheckBox ui:field="sunday" caption="Sunday" day="0" - styleName="{style.cb}" /> - <dt:DayCheckBox ui:field="monday" caption="Monday" day="0" - styleName="{style.cb}" /> - <dt:DayCheckBox ui:field="tuesday" caption="Tuesday" day="0" - styleName="{style.cb}" /> - <dt:DayCheckBox ui:field="wednesday" caption="Wednesday" day="0" - styleName="{style.cb}" /> - <dt:DayCheckBox ui:field="thursday" caption="Thursday" day="0" - styleName="{style.cb}" /> - <dt:DayCheckBox ui:field="friday" caption="Friday" day="0" - styleName="{style.cb}" /> - <dt:DayCheckBox ui:field="saturday" caption="Saturday" day="0" - styleName="{style.cb}" /> + <dt:DayCheckBox ui:field="sunday" caption="Sunday" + day="0" styleName="{style.cb}" /> + <dt:DayCheckBox ui:field="monday" caption="Monday" + day="1" styleName="{style.cb}" /> + <dt:DayCheckBox ui:field="tuesday" caption="Tuesday" + day="2" styleName="{style.cb}" /> + <dt:DayCheckBox ui:field="wednesday" caption="Wednesday" + day="3" styleName="{style.cb}" /> + <dt:DayCheckBox ui:field="thursday" caption="Thursday" + day="4" styleName="{style.cb}" /> + <dt:DayCheckBox ui:field="friday" caption="Friday" + day="5" styleName="{style.cb}" /> + <dt:DayCheckBox ui:field="saturday" caption="Saturday" + day="6" styleName="{style.cb}" /> - <g:Button ui:field="all" enabled="false" styleName="{style.all}">All</g:Button> - <g:Button ui:field="none" enabled="false" styleName="{style.none}">None</g:Button> - <g:Label>Not implemented yet</g:Label> + <g:Button ui:field="all" styleName="{style.all}">All</g:Button> + <g:Button ui:field="none" styleName="{style.none}">None</g:Button> </g:FlowPanel> </ui:UiBinder> \ No newline at end of file
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/FavoritesWidget.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/FavoritesWidget.java index 62e87d6..cc4b598 100644 --- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/FavoritesWidget.java +++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/FavoritesWidget.java
@@ -48,6 +48,10 @@ interface Binder extends UiBinder<Widget, FavoritesWidget> { } + /** + * A driver that accepts a List of PersonProxy objects, controlled by a + * ListEditor of PersonProxy instances, displayed using NameLabels. + */ interface Driver extends RequestFactoryEditorDriver<List<PersonProxy>, // ListEditor<PersonProxy, NameLabel>> { } @@ -57,7 +61,7 @@ } /** - * This is used by a ListEditor. + * This is used by the ListEditor to control the state of the UI. */ private class NameLabelSource extends EditorSource<NameLabel> { @Override @@ -86,10 +90,14 @@ @UiField Style style; + /** + * This list is a facade provided by the ListEditor. Structural modifications + * to this list (e.g. add(), set(), remove()) will trigger UI update events. + */ private final List<PersonProxy> displayedList; private final EventBus eventBus; private final RequestFactory factory; - private FavoritesManager manager; + private final FavoritesManager manager; private HandlerRegistration subscription; public FavoritesWidget(EventBus eventBus, RequestFactory factory, @@ -111,9 +119,7 @@ ListEditor<PersonProxy, NameLabel> listEditor = editor; driver.initialize(eventBus, factory, listEditor); - /* - * Notice the backing list is essentially anonymous. - */ + // Notice the backing list is essentially anonymous. driver.display(new ArrayList<PersonProxy>()); // Modifying this list triggers widget creation and destruction @@ -122,19 +128,16 @@ @Override protected void onLoad() { + // Subscribe to notifications from the FavoritesManager subscription = manager.addMarkFavoriteHandler(new MarkFavoriteEvent.Handler() { public void onMarkFavorite(MarkFavoriteEvent event) { FavoritesWidget.this.onMarkFavorite(event); } }); + // Initialize the UI with the existing list of favorites for (EntityProxyId<PersonProxy> id : manager.getFavoriteIds()) { - factory.find(id).fire(new Receiver<PersonProxy>() { - @Override - public void onSuccess(PersonProxy person) { - onMarkFavorite(new MarkFavoriteEvent(person, true)); - } - }); + onMarkFavorite(new MarkFavoriteEvent(id, true)); } } @@ -143,31 +146,36 @@ subscription.removeHandler(); } - void onMarkFavorite(MarkFavoriteEvent event) { - PersonProxy person = event.getPerson(); - if (person == null) { + private void onMarkFavorite(MarkFavoriteEvent event) { + EntityProxyId<PersonProxy> id = event.getId(); + if (id == null) { return; } // EntityProxies should be compared by stable id - EntityProxyId<PersonProxy> lookFor = person.stableId(); PersonProxy found = null; for (PersonProxy displayed : displayedList) { - if (displayed.stableId().equals(lookFor)) { + if (displayed.stableId().equals(id)) { found = displayed; break; } } if (event.isFavorite() && found == null) { - displayedList.add(person); + factory.find(id).to(new Receiver<PersonProxy>() { + @Override + public void onSuccess(PersonProxy response) { + displayedList.add(response); + sortDisplayedList(); + } + }).fire(); } else if (!event.isFavorite() && found != null) { - displayedList.remove(person); - } else { - // No change - return; + displayedList.remove(found); + sortDisplayedList(); } + } + private void sortDisplayedList() { // Sorting the list of PersonProxies will also change the UI display Collections.sort(displayedList, new Comparator<PersonProxy>() { public int compare(PersonProxy o1, PersonProxy o2) {
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/MentorSelector.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/MentorSelector.java index 7925ceb..b48101f 100644 --- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/MentorSelector.java +++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/MentorSelector.java
@@ -74,7 +74,7 @@ } @UiHandler("choose") - void onChoose(@SuppressWarnings("unused") ClickEvent event) { + void onChoose(ClickEvent event) { setEnabled(false); factory.schoolCalendarRequest().getRandomPerson().to( new Receiver<PersonProxy>() { @@ -87,7 +87,7 @@ } @UiHandler("clear") - void onClear(@SuppressWarnings("unused") ClickEvent event) { + void onClear(ClickEvent event) { setValue(null); }
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/NameLabel.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/NameLabel.java index d7ebe00..b169461 100644 --- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/NameLabel.java +++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/NameLabel.java
@@ -31,6 +31,10 @@ * the displayed object. */ class NameLabel extends Composite implements ValueAwareEditor<PersonProxy> { + /** + * Many of the GWT UI widgets that implement TakesValue also implement + * IsEditor and are directly usable as sub-Editors. + */ final Label nameEditor = new Label(); private PersonProxy person; private HandlerRegistration subscription; @@ -59,7 +63,9 @@ } public void setDelegate(EditorDelegate<PersonProxy> delegate) { - assert subscription == null; + if (subscription != null) { + subscription.removeHandler(); + } subscription = delegate.subscribe(); }
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/SummaryWidget.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/SummaryWidget.java index 348d2f2..213fd21 100644 --- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/SummaryWidget.java +++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/SummaryWidget.java
@@ -15,8 +15,12 @@ */ package com.google.gwt.sample.dynatablerf.client.widgets; +import static com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory.SchoolCalendarRequest.ALL_DAYS; + import com.google.gwt.cell.client.TextCell; import com.google.gwt.core.client.GWT; +import com.google.gwt.core.client.Scheduler; +import com.google.gwt.core.client.Scheduler.ScheduledCommand; import com.google.gwt.event.dom.client.ClickEvent; import com.google.gwt.event.shared.EventBus; import com.google.gwt.requestfactory.shared.EntityProxyChange; @@ -28,15 +32,15 @@ import com.google.gwt.sample.dynatablerf.client.events.FilterChangeEvent; import com.google.gwt.sample.dynatablerf.shared.AddressProxy; import com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory; -import com.google.gwt.sample.dynatablerf.shared.PersonProxy; import com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory.PersonRequest; +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.cellview.client.CellTable; import com.google.gwt.user.cellview.client.Column; -import com.google.gwt.user.cellview.client.SimplePager; import com.google.gwt.user.cellview.client.HasKeyboardSelectionPolicy.KeyboardSelectionPolicy; +import com.google.gwt.user.cellview.client.SimplePager; import com.google.gwt.user.client.ui.Composite; import com.google.gwt.user.client.ui.DockLayoutPanel; import com.google.gwt.user.client.ui.Widget; @@ -45,6 +49,7 @@ import com.google.gwt.view.client.SelectionChangeEvent; import com.google.gwt.view.client.SingleSelectionModel; +import java.util.ArrayList; import java.util.Collections; import java.util.List; @@ -111,13 +116,13 @@ CellTable<PersonProxy> table; private final EventBus eventBus; + private List<Boolean> filter = new ArrayList<Boolean>(ALL_DAYS); + private int lastFetch; private final int numRows; + private boolean pending; private final DynaTableRequestFactory requestFactory; private final SingleSelectionModel<PersonProxy> selectionModel = new SingleSelectionModel<PersonProxy>(); - private boolean[] filter = new boolean[] { - true, true, true, true, true, true, true}; - public SummaryWidget(EventBus eventBus, DynaTableRequestFactory requestFactory, int numRows) { this.eventBus = eventBus; @@ -148,8 +153,16 @@ FilterChangeEvent.register(eventBus, new FilterChangeEvent.Handler() { public void onFilterChanged(FilterChangeEvent e) { - filter[e.getDay()] = e.isSelected(); - fetch(0); + filter.set(e.getDay(), e.isSelected()); + if (!pending) { + pending = true; + Scheduler.get().scheduleFinally(new ScheduledCommand() { + public void execute() { + pending = false; + fetch(0); + } + }); + } } }); @@ -162,8 +175,8 @@ fetch(0); } - @UiHandler("create") - void onCreate(@SuppressWarnings("unused") ClickEvent event) { + @UiHandler("create") + void onCreate(ClickEvent event) { PersonRequest context = requestFactory.personRequest(); AddressProxy address = context.create(AddressProxy.class); PersonProxy person = context.edit(context.create(PersonProxy.class)); @@ -173,6 +186,12 @@ } void onPersonChanged(EntityProxyChange<PersonProxy> event) { + if (WriteOperation.PERSIST.equals(event.getWriteOperation())) { + // Re-fetch if we're already displaying the last page + if (table.isRowCountExact()) { + fetch(lastFetch + 1); + } + } if (WriteOperation.UPDATE.equals(event.getWriteOperation())) { EntityProxyId<PersonProxy> personId = event.getProxyId(); @@ -213,14 +232,15 @@ } private void fetch(final int start) { - // XXX add back filter - requestFactory.schoolCalendarRequest().getPeople(start, numRows).fire( + lastFetch = start; + requestFactory.schoolCalendarRequest().getPeople(start, numRows, filter).fire( new Receiver<List<PersonProxy>>() { @Override public void onSuccess(List<PersonProxy> response) { int responses = response.size(); table.setRowData(start, response); - if (!table.isRowCountExact()) { + pager.setPageStart(start); + if (start == 0 || !table.isRowCountExact()) { table.setRowCount(start + responses, responses < numRows); } }
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/ZipPlusFourBox.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/ZipPlusFourBox.java new file mode 100644 index 0000000..7712995 --- /dev/null +++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/ZipPlusFourBox.java
@@ -0,0 +1,71 @@ +/* + * 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.dom.client.Document; +import com.google.gwt.regexp.shared.RegExp; +import com.google.gwt.text.shared.Parser; +import com.google.gwt.text.shared.Renderer; +import com.google.gwt.user.client.ui.ValueBox; + +import java.io.IOException; +import java.text.ParseException; + +/** + * A simple implementation of a US zip code input field. + * <p> + * Accepted formats are <code>ddddd</code> or <code>ddddd-dddd</code>. + */ +public class ZipPlusFourBox extends ValueBox<String> { + private static final RegExp PATTERN = RegExp.compile("^\\d{5}(-\\d{4})?$"); + private static final Renderer<String> RENDERER = new Renderer<String>() { + public String render(String object) { + if (object == null) { + return null; + } + StringBuilder sb = new StringBuilder(String.valueOf(object)); + if (sb.length() == 9) { + sb.insert(5, '-'); + } + return sb.toString(); + } + + public void render(String object, Appendable appendable) throws IOException { + appendable.append(render(object)); + } + }; + private static final Parser<String> PARSER = new Parser<String>() { + public String parse(CharSequence text) throws ParseException { + switch (text.length()) { + case 9: + text = text.subSequence(0, 5) + "-" + text.subSequence(5, 9); + // Fall-though intentional + case 5: + case 10: + if (PATTERN.test(text.toString())) { + return text.toString(); + } else { + throw new ParseException("Illegal value", 0); + } + } + throw new ParseException("Illegal length", 0); + } + }; + + public ZipPlusFourBox() { + super(Document.get().createTextInputElement(), RENDERER, PARSER); + } +}
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Address.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Address.java index b87ee48..c7059bd 100644 --- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Address.java +++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Address.java
@@ -19,6 +19,7 @@ import javax.validation.constraints.DecimalMin; import javax.validation.constraints.NotNull; +import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; /** @@ -53,8 +54,8 @@ private Integer version = 0; @NotNull - @DecimalMin("10000") - private Integer zip; + @Pattern(regexp = "\\d{5}(-\\d{4})?") + private String zip; public Address() { } @@ -92,7 +93,7 @@ return version; } - public Integer getZip() { + public String getZip() { return zip; } @@ -129,7 +130,7 @@ this.version = version; } - public void setZip(Integer zip) { + public void setZip(String zip) { this.zip = zip; } }
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Person.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Person.java index d7eec2d..3c10fd8 100644 --- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Person.java +++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Person.java
@@ -15,8 +15,12 @@ */ package com.google.gwt.sample.dynatablerf.domain; +import static com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory.SchoolCalendarRequest.ALL_DAYS; + import com.google.gwt.sample.dynatablerf.server.SchoolCalendarService; +import java.util.List; + import javax.validation.constraints.DecimalMin; import javax.validation.constraints.NotNull; import javax.validation.constraints.Size; @@ -69,8 +73,7 @@ private String note; - private final boolean[] daysFilters = new boolean[] { - true, true, true, true, true, true, true}; + private List<Boolean> daysFilters = ALL_DAYS; public Person() { } @@ -129,7 +132,7 @@ return getScheduleWithFilter(daysFilters); } - public String getScheduleWithFilter(boolean[] daysFilter) { + public String getScheduleWithFilter(List<Boolean> daysFilter) { return classSchedule.getDescription(daysFilter); } @@ -158,10 +161,9 @@ this.address.copyFrom(address); } - public void setDaysFilter(boolean[] daysFilter) { - assert daysFilter.length == this.daysFilters.length; - System.arraycopy(daysFilter, 0, this.daysFilters, 0, - this.daysFilters.length); + public void setDaysFilter(List<Boolean> daysFilter) { + assert daysFilter.size() == this.daysFilters.size(); + this.daysFilters = daysFilter; } public void setDescription(String description) {
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Schedule.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Schedule.java index 6a7cfad..14b6ddb 100644 --- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Schedule.java +++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/domain/Schedule.java
@@ -32,10 +32,10 @@ timeSlots.add(timeSlot); } - public String getDescription(boolean[] daysFilter) { + public String getDescription(List<Boolean> daysFilter) { String s = null; for (TimeSlot timeSlot : timeSlots) { - if (daysFilter[timeSlot.getDayOfWeek()]) { + if (daysFilter.get(timeSlot.getDayOfWeek())) { if (s == null) { s = timeSlot.getDescription(); } else {
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/AddressFuzzer.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/AddressFuzzer.java index 16bcc4d..25bdb93 100644 --- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/AddressFuzzer.java +++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/AddressFuzzer.java
@@ -49,6 +49,11 @@ a.setStreet(r.nextInt(4096) + " " + STREET_NAMES[r.nextInt(STREET_NAMES.length)]); a.setState(STATE_NAMES[r.nextInt(STATE_NAMES.length)]); - a.setZip(10000 + r.nextInt(89999)); + StringBuilder zip = new StringBuilder(); + zip.append(String.format("%05d", r.nextInt(99999))); + if (r.nextBoolean()) { + zip.append(String.format("-%04d", r.nextInt(9999))); + } + a.setZip(zip.toString()); } }
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/PersonSource.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/PersonSource.java index 08ec81c..19b06e5 100644 --- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/PersonSource.java +++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/PersonSource.java
@@ -15,17 +15,24 @@ */ package com.google.gwt.sample.dynatablerf.server; +import static com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory.SchoolCalendarRequest.ALL_DAYS; +import static com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory.SchoolCalendarRequest.NO_DAYS; + import com.google.gwt.sample.dynatablerf.domain.Address; import com.google.gwt.sample.dynatablerf.domain.Person; import java.util.ArrayList; import java.util.Collections; +import java.util.Iterator; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; /** - * + * Provides a number of Person objects as a demonstration datasource. Many of + * the operations in this implementation would be much more efficient in a real + * database, but are implemented is a straightforward fashion because they're + * not really important for understanding the RequestFactory framework. */ public abstract class PersonSource { static class Backing extends PersonSource { @@ -43,7 +50,7 @@ @Override public List<Person> getPeople(int startIndex, int maxCount, - boolean[] daysFilter) { + List<Boolean> daysFilter) { int peopleCount = countPeople(); int start = startIndex; @@ -55,8 +62,31 @@ if (start == end) { return Collections.emptyList(); } - // This is ugly, but a real backend would have a skip mechanism - return new ArrayList<Person>(people.values()).subList(start, end); + + // If there's a simple filter, use a fast path + if (ALL_DAYS.equals(daysFilter)) { + return new ArrayList<Person>(people.values()).subList(start, end); + } else if (NO_DAYS.equals(daysFilter)) { + return new ArrayList<Person>(); + } + + /* + * Otherwise, iterate from the start position until we collect enough + * People or hit the end of the list. + */ + Iterator<Person> it = people.values().iterator(); + int skipped = 0; + List<Person> toReturn = new ArrayList<Person>(maxCount); + while (toReturn.size() < maxCount && it.hasNext()) { + Person person = it.next(); + if (person.getScheduleWithFilter(daysFilter).length() > 0) { + if (skipped++ < startIndex) { + continue; + } + toReturn.add(person); + } + } + return toReturn; } @Override @@ -107,13 +137,13 @@ @Override public List<Person> getPeople(int startIndex, int maxCount, - boolean[] daysFilter) { + List<Boolean> daysFilter) { List<Person> toReturn = new ArrayList<Person>(maxCount); for (Person person : backingStore.getPeople(startIndex, maxCount, daysFilter)) { Person copy = findPerson(person.getId()); - toReturn.add(copy); copy.setDaysFilter(daysFilter); + toReturn.add(copy); } return toReturn; } @@ -155,7 +185,7 @@ public abstract Person findPerson(String id); public abstract List<Person> getPeople(int startIndex, int maxCount, - boolean[] daysFilter); + List<Boolean> daysFilter); public abstract void persist(Address address);
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/SchoolCalendarService.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/SchoolCalendarService.java index 7bcdcd3..5493cd7 100644 --- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/SchoolCalendarService.java +++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/server/SchoolCalendarService.java
@@ -15,6 +15,8 @@ */ package com.google.gwt.sample.dynatablerf.server; +import static com.google.gwt.sample.dynatablerf.shared.DynaTableRequestFactory.SchoolCalendarRequest.ALL_DAYS; + import com.google.gwt.sample.dynatablerf.domain.Address; import com.google.gwt.sample.dynatablerf.domain.Person; @@ -33,8 +35,6 @@ * 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>(); @@ -50,8 +50,10 @@ return PERSON_SOURCE.get().findPerson(id); } - public static List<Person> getPeople(int startIndex, int maxCount) { - return getPeople(startIndex, maxCount, ALL_DAYS); + public static List<Person> getPeople(int startIndex, int maxCount, + List<Boolean> filter) { + checkPersonSource(); + return PERSON_SOURCE.get().getPeople(startIndex, maxCount, filter); } public static Person getRandomPerson() { @@ -77,15 +79,6 @@ } } - /** - * XXX private due to inability to add method overloads. - */ - private static List<Person> getPeople(int startIndex, int maxCount, - boolean[] filter) { - checkPersonSource(); - return PERSON_SOURCE.get().getPeople(startIndex, maxCount, filter); - } - private PersonSource backingStore; public void destroy() {
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/AddressProxy.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/AddressProxy.java index 3f59f78..f96e5c8 100644 --- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/AddressProxy.java +++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/AddressProxy.java
@@ -31,7 +31,7 @@ String getStreet(); - Integer getZip(); + String getZip(); void setCity(String city); @@ -39,7 +39,7 @@ void setStreet(String street); - void setZip(Integer zip); + void setZip(String zip); EntityProxyId<AddressProxy> stableId(); }
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/DynaTableRequestFactory.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/DynaTableRequestFactory.java index f7aa832..0a0e38a 100644 --- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/DynaTableRequestFactory.java +++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/shared/DynaTableRequestFactory.java
@@ -25,6 +25,8 @@ import com.google.gwt.sample.dynatablerf.domain.Person; import com.google.gwt.sample.dynatablerf.server.SchoolCalendarService; +import java.util.Arrays; +import java.util.Collections; import java.util.List; /** @@ -54,7 +56,14 @@ */ @Service(SchoolCalendarService.class) interface SchoolCalendarRequest extends RequestContext { - Request<List<PersonProxy>> getPeople(int startIndex, int maxCount); + List<Boolean> ALL_DAYS = Collections.unmodifiableList(Arrays.asList(true, + true, true, true, true, true, true)); + List<Boolean> NO_DAYS = Collections.unmodifiableList(Arrays.asList(false, + false, false, false, false, false, false)); + + Request<List<PersonProxy>> getPeople(int startIndex, int maxCount, + List<Boolean> dayFilter); + Request<PersonProxy> getRandomPerson(); }
diff --git a/user/src/com/google/gwt/editor/client/IsEditor.java b/user/src/com/google/gwt/editor/client/IsEditor.java index 265f936..f787df2 100644 --- a/user/src/com/google/gwt/editor/client/IsEditor.java +++ b/user/src/com/google/gwt/editor/client/IsEditor.java
@@ -17,7 +17,28 @@ /** * Extended by view objects that wish to participate in an Editor hierarchy, but - * that do not implement the {@link Editor} contract directly. + * that do not implement the {@link Editor} contract directly. The primary + * advantage of the IsEditor interface is that is allows composition of behavior + * without the need to implement delegate methods for every interface + * implemented by the common editor logic. + * <p> + * For example, an editor Widget that supports adding and removing elements from + * a list might wish to re-use the provided + * {@link com.google.gwt.editor.client.adapters.ListEditor ListEditor} + * controller. It might be roughly built as: + * + * <pre> + * class MyListEditor extends Composite implements IsEditor<ListEditor<Foo, FooEditor>> { + * private ListEditor<Foo, FooEditor> controller = ListEditor.of(new FooEditorSource()); + * public ListEditor<Foo, FooEditor> asEditor() {return controller;} + * void onAddButtonClicked() { controller.getList().add(new Foo()); } + * void onClearButtonClicked() { controller.getList().clear(); } + * } + * </pre> + * By implementing only the one <code>asEditor()</code> method, the + * <code>MyListEditor</code> type is able to incorporate the + * <code>ListEditor</code> behavior without needing to write delegate methods + * for every method in <code>ListEditor</code>. * <p> * It is legal for a type to implement both Editor and IsEditor. In this case, * the Editor returned from {@link #asEditor()} will be a co-Editor of the
diff --git a/user/test/com/google/gwt/editor/client/SimpleBeanEditorTest.java b/user/test/com/google/gwt/editor/client/SimpleBeanEditorTest.java index b5a9beb..1f15edb 100644 --- a/user/test/com/google/gwt/editor/client/SimpleBeanEditorTest.java +++ b/user/test/com/google/gwt/editor/client/SimpleBeanEditorTest.java
@@ -42,6 +42,14 @@ } } + class AddressEditorPartOne implements Editor<Address> { + SimpleEditor<String> city = SimpleEditor.of(UNINITIALIZED); + } + + class AddressEditorPartTwo implements Editor<Address> { + SimpleEditor<String> street = SimpleEditor.of(UNINITIALIZED); + } + class AddressEditorView implements IsEditor<LeafAddressEditor> { LeafAddressEditor addressEditor = new LeafAddressEditor(); @@ -105,6 +113,18 @@ SimpleBeanEditorDriver<Person, PersonEditorWithLeafAddressEditor> { } + class PersonEditorWithMultipleBindings implements Editor<Person> { + @Editor.Path("address") + AddressEditorPartOne one = new AddressEditorPartOne(); + + @Editor.Path("address") + AddressEditorPartTwo two = new AddressEditorPartTwo(); + } + + interface PersonEditorWithMultipleBindingsDriver extends + SimpleBeanEditorDriver<Person, PersonEditorWithMultipleBindings> { + } + interface PersonEditorWithOptionalAddressDriver extends SimpleBeanEditorDriver<Person, PersonEditorWithOptionalAddressEditor> { } @@ -361,10 +381,10 @@ List<SimpleEditor<String>> editors = editor.getEditors(); assertEquals(rawData.size(), editors.size()); - assertEquals(rawData, Arrays.asList(editors.get(0).getValue(), editors.get( - 1).getValue(), editors.get(2).getValue())); - assertEquals(editors, new ArrayList<SimpleEditor<String>>( - positionMap.values())); + assertEquals(rawData, Arrays.asList(editors.get(0).getValue(), + editors.get(1).getValue(), editors.get(2).getValue())); + assertEquals(editors, + new ArrayList<SimpleEditor<String>>(positionMap.values())); List<String> mutableList = editor.getList(); assertEquals(rawData, mutableList); @@ -383,8 +403,8 @@ mutableList.add("quux"); assertEquals(4, editors.size()); assertEquals("quux", editors.get(3).getValue()); - assertEquals(editors, new ArrayList<SimpleEditor<String>>( - positionMap.values())); + assertEquals(editors, + new ArrayList<SimpleEditor<String>>(positionMap.values())); // Delete an element SimpleEditor<String> expectedDisposed = editors.get(0); @@ -392,8 +412,8 @@ assertSame(expectedDisposed, disposed[0]); assertEquals(3, editors.size()); assertEquals("quux", editors.get(2).getValue()); - assertEquals(editors, new ArrayList<SimpleEditor<String>>( - positionMap.values())); + assertEquals(editors, + new ArrayList<SimpleEditor<String>>(positionMap.values())); } /** @@ -434,6 +454,23 @@ assertEquals("edited", person.addresses.get(1).getCity()); } + public void testMultipleBinding() { + PersonEditorWithMultipleBindingsDriver driver = GWT.create(PersonEditorWithMultipleBindingsDriver.class); + PersonEditorWithMultipleBindings editor = new PersonEditorWithMultipleBindings(); + + driver.initialize(editor); + driver.edit(person); + assertEquals("City", editor.one.city.getValue()); + assertEquals("Street", editor.two.street.getValue()); + + editor.one.city.setValue("Foo"); + editor.two.street.setValue("Bar"); + driver.flush(); + + assertEquals("Foo", person.getAddress().getCity()); + assertEquals("Bar", person.getAddress().getStreet()); + } + public void testOptionalField() { PersonEditorWithOptionalAddressDriver driver = GWT.create(PersonEditorWithOptionalAddressDriver.class); person.address = null;