Implement round-trip validation with RequestFactory and the Editor framework.
Make the decorator panel much more focused to eliminate excessive generic parameterization.
Patch by: bobv
Review by: rjrjr
Review at http://gwt-code-reviews.appspot.com/877801
git-svn-id: https://google-web-toolkit.googlecode.com/svn/trunk@8781 8db76d5a-ed1c-0410-87a9-c151d255dfc7
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 bf0cfc5..2dc6f11 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
@@ -64,10 +64,9 @@
}
});
- final DynaTableRequestFactory requests =
- GWT.create(DynaTableRequestFactory.class);
+ final DynaTableRequestFactory requests = GWT.create(DynaTableRequestFactory.class);
requests.init(eventBus);
-
+
// Add remote logging handler
RequestFactoryLogHandler.LoggingRequestProvider provider =
new RequestFactoryLogHandler.LoggingRequestProvider() {
@@ -78,7 +77,7 @@
Logger.getLogger("").addHandler(
new RequestFactoryLogHandler(provider, Level.WARNING,
"WireActivityLogger"));
-
+
FavoritesManager manager = new FavoritesManager();
PersonEditorWorkflow.register(eventBus, requests, manager);
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 6abdaf8..ff38cd5 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
@@ -106,22 +106,22 @@
@UiHandler("save")
void onSave(ClickEvent e) {
// MOVE TO ACTIVITY END
- RequestObject<Void> request = editorDriver.<Void> flush();
+ final RequestObject<Void> request = editorDriver.<Void> flush();
if (editorDriver.hasErrors()) {
- dialog.setText("Errors detected");
+ dialog.setText("Errors detected locally");
return;
}
- dialog.hide();
request.fire(new Receiver<Void>() {
@Override
public void onSuccess(Void response, Set<SyncResult> syncResults) {
+ dialog.hide();
}
@Override
public void onViolation(Set<Violation> errors) {
- for (Violation error : errors) {
- System.out.println(error.getPath() + " " + error.getMessage());
- }
+ dialog.setText("Errors detected on the server");
+ request.clearUsed();
+ editorDriver.setViolations(errors);
}
});
}
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 afffd74..56d6936 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
@@ -17,13 +17,11 @@
import com.google.gwt.core.client.GWT;
import com.google.gwt.editor.client.Editor;
-import com.google.gwt.editor.client.adapters.ValueBoxEditor;
-import com.google.gwt.editor.client.ui.EditorErrorPanel;
+import com.google.gwt.editor.client.ui.ValueBoxEditorDecorator;
import com.google.gwt.sample.dynatablerf.shared.AddressProxy;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.TextBox;
import com.google.gwt.user.client.ui.Widget;
/**
@@ -34,13 +32,13 @@
}
@UiField
- TextBox street;
+ ValueBoxEditorDecorator<String> street;
@UiField
- TextBox city;
+ ValueBoxEditorDecorator<String> city;
@UiField
- TextBox state;
+ ValueBoxEditorDecorator<String> state;
@UiField
- EditorErrorPanel<Integer, ValueBoxEditor<Integer>> zip;
+ ValueBoxEditorDecorator<Integer> 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 6e3aea0..4db5bc7 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
@@ -6,20 +6,36 @@
<g:HTMLPanel>
<div class="{style.rightAlign}">
Street:
- <g:TextBox ui:field="street" stylePrimaryName="{style.editField}" />
+ <e:ValueBoxEditorDecorator ui:field="street"
+ stylePrimaryName="{style.editField}">
+ <e:valuebox>
+ <g:TextBox stylePrimaryName="{style.editField}" />
+ </e:valuebox>
+ </e:ValueBoxEditorDecorator>
<br />
City:
- <g:TextBox ui:field="city" stylePrimaryName="{style.editField}" />
+ <e:ValueBoxEditorDecorator ui:field="city"
+ stylePrimaryName="{style.editField}">
+ <e:valuebox>
+ <g:TextBox stylePrimaryName="{style.editField}" />
+ </e:valuebox>
+ </e:ValueBoxEditorDecorator>
<br />
State:
- <g:TextBox ui:field="state" stylePrimaryName="{style.editField}" />
+ <e:ValueBoxEditorDecorator ui:field="state"
+ stylePrimaryName="{style.editField}">
+ <e:valuebox>
+ <g:TextBox stylePrimaryName="{style.editField}" />
+ </e:valuebox>
+ </e:ValueBoxEditorDecorator>
<br />
Zip:
- <e:EditorErrorPanel ui:field="zip" stylePrimaryName="{style.editField}">
- <e:contents>
- <a:IntegerBox stylePrimaryName="{style.editField}"/>
- </e:contents>
- </e:EditorErrorPanel>
+ <e:ValueBoxEditorDecorator ui:field="zip"
+ stylePrimaryName="{style.editField}">
+ <e:valuebox>
+ <a:IntegerBox stylePrimaryName="{style.editField}" />
+ </e:valuebox>
+ </e:ValueBoxEditorDecorator>
<br />
</div>
</g:HTMLPanel>
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/PersonEditor.java b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/PersonEditor.java
index 502a370..d398b2f 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/PersonEditor.java
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/PersonEditor.java
@@ -19,12 +19,12 @@
import com.google.gwt.core.client.Scheduler;
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.PersonProxy;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Composite;
-import com.google.gwt.user.client.ui.TextArea;
-import com.google.gwt.user.client.ui.TextBox;
+import com.google.gwt.user.client.ui.Focusable;
import com.google.gwt.user.client.ui.Widget;
/**
@@ -38,13 +38,16 @@
AddressEditor address;
@UiField
- TextBox name;
+ ValueBoxEditorDecorator<String> description;
@UiField
- TextBox description;
+ ValueBoxEditorDecorator<String> name;
@UiField
- TextArea note;
+ ValueBoxEditorDecorator<String> note;
+
+ @UiField
+ Focusable nameBox;
public PersonEditor() {
initWidget(GWT.<Binder> create(Binder.class).createAndBindUi(this));
@@ -53,7 +56,7 @@
public void focus() {
Scheduler.get().scheduleDeferred(new ScheduledCommand() {
public void execute() {
- name.setFocus(true);
+ nameBox.setFocus(true);
}
});
}
diff --git a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/PersonEditor.ui.xml b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/PersonEditor.ui.xml
index 2844495..59df545 100644
--- a/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/PersonEditor.ui.xml
+++ b/samples/dynatablerf/src/com/google/gwt/sample/dynatablerf/client/widgets/PersonEditor.ui.xml
@@ -1,18 +1,34 @@
<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: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">
</ui:style>
<g:HTMLPanel>
<div class="{style.rightAlign}">
Name:
- <g:TextBox ui:field="name" stylePrimaryName="{style.editField}" />
+ <e:ValueBoxEditorDecorator ui:field="name"
+ stylePrimaryName="{style.editField}">
+ <e:valuebox>
+ <g:TextBox ui:field="nameBox" stylePrimaryName="{style.editField}" />
+ </e:valuebox>
+ </e:ValueBoxEditorDecorator>
<br />
Description:
- <g:TextBox ui:field="description" stylePrimaryName="{style.editField}" />
+ <e:ValueBoxEditorDecorator ui:field="description"
+ stylePrimaryName="{style.editField}">
+ <e:valuebox>
+ <g:TextBox stylePrimaryName="{style.editField}" />
+ </e:valuebox>
+ </e:ValueBoxEditorDecorator>
<br />
Note:
- <g:TextArea ui:field="note" stylePrimaryName="{style.editField}" />
+ <e:ValueBoxEditorDecorator ui:field="note"
+ stylePrimaryName="{style.editField}">
+ <e:valuebox>
+ <g:TextArea stylePrimaryName="{style.editField}" />
+ </e:valuebox>
+ </e:ValueBoxEditorDecorator>
<br />
</div>
Address:
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 62bec08..e7b3580 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
@@ -17,7 +17,6 @@
import com.google.gwt.sample.dynatablerf.server.SchoolCalendarService;
-import javax.validation.Valid;
import javax.validation.constraints.DecimalMin;
import javax.validation.constraints.NotNull;
import javax.validation.constraints.Size;
@@ -39,14 +38,13 @@
}
@NotNull
- @Valid
private final Address address = new Address();
@NotNull
private String description = "DESC";
@NotNull
- @Size(min = 1, message = "Persons must have names")
+ @Size(min = 2, message = "Persons aren't just characters")
private String name;
@NotNull
@@ -56,7 +54,6 @@
@DecimalMin("0")
private Integer version = 0;
- @NotNull
private String note;
public Person() {
diff --git a/user/src/com/google/gwt/editor/client/Editor.java b/user/src/com/google/gwt/editor/client/Editor.java
index ddc0732..a9584bb 100644
--- a/user/src/com/google/gwt/editor/client/Editor.java
+++ b/user/src/com/google/gwt/editor/client/Editor.java
@@ -48,6 +48,15 @@
*/
public interface Editor<T> {
/**
+ * Tells the Editor framework to ignore an Editor accessor.
+ */
+ @Documented
+ @Retention(RetentionPolicy.RUNTIME)
+ @Target(value = {ElementType.FIELD, ElementType.METHOD})
+ public @interface Ignore {
+ }
+
+ /**
* Maps a composite Editor's component Editors into the data-model.
*/
@Documented
diff --git a/user/src/com/google/gwt/editor/client/adapters/ValueBoxEditor.java b/user/src/com/google/gwt/editor/client/adapters/ValueBoxEditor.java
index a16880f..6f367e4 100644
--- a/user/src/com/google/gwt/editor/client/adapters/ValueBoxEditor.java
+++ b/user/src/com/google/gwt/editor/client/adapters/ValueBoxEditor.java
@@ -15,9 +15,6 @@
*/
package com.google.gwt.editor.client.adapters;
-import com.google.gwt.editor.client.EditorDelegate;
-import com.google.gwt.editor.client.HasEditorDelegate;
-import com.google.gwt.editor.client.LeafValueEditor;
import com.google.gwt.user.client.ui.ValueBoxBase;
import java.text.ParseException;
@@ -29,18 +26,17 @@
*
* @param <T> the type of value to be edited
*/
-public class ValueBoxEditor<T> implements LeafValueEditor<T>,
- HasEditorDelegate<T> {
+public class ValueBoxEditor<T> extends TakesValueEditor<T> {
public static <T> ValueBoxEditor<T> of(ValueBoxBase<T> valueBox) {
return new ValueBoxEditor<T>(valueBox);
}
- private EditorDelegate<T> delegate;
- private ValueBoxBase<T> peer;
+ private final ValueBoxBase<T> peer;
private T value;
protected ValueBoxEditor(ValueBoxBase<T> peer) {
+ super(peer);
this.peer = peer;
}
@@ -54,15 +50,13 @@
try {
value = peer.getValueOrThrow();
} catch (ParseException e) {
- delegate.recordError(e.getMessage(), peer.getText(), e);
+ // TODO i18n
+ getDelegate().recordError("Bad value (" + peer.getText() + ")",
+ peer.getText(), e);
}
return value;
}
- public void setDelegate(EditorDelegate<T> delegate) {
- this.delegate = delegate;
- }
-
public void setValue(T value) {
peer.setValue(this.value = value);
}
diff --git a/user/src/com/google/gwt/editor/client/impl/AbstractEditorDelegate.java b/user/src/com/google/gwt/editor/client/impl/AbstractEditorDelegate.java
index dc1ae5e..7857c2b 100644
--- a/user/src/com/google/gwt/editor/client/impl/AbstractEditorDelegate.java
+++ b/user/src/com/google/gwt/editor/client/impl/AbstractEditorDelegate.java
@@ -27,6 +27,7 @@
import java.util.ArrayList;
import java.util.Collections;
+import java.util.HashMap;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
@@ -43,15 +44,20 @@
private class Chain<R, S extends Editor<R>> implements
CompositeEditor.EditorChain<R, S> {
+ private DelegateMap delegateMap;
private Map<S, AbstractEditorDelegate<R, S>> map = new LinkedHashMap<S, AbstractEditorDelegate<R, S>>();
+ public Chain(DelegateMap delegateMap) {
+ this.delegateMap = delegateMap;
+ }
+
public void attach(R object, S subEditor) {
AbstractEditorDelegate<R, S> subDelegate = createComposedDelegate();
map.put(subEditor, subDelegate);
@SuppressWarnings("unchecked")
Editor<Object> temp = (Editor<Object>) subEditor;
initializeSubDelegate(subDelegate, path
- + composedEditor.getPathElement(temp), object, subEditor);
+ + composedEditor.getPathElement(temp), object, subEditor, delegateMap);
}
public void detach(S subEditor) {
@@ -82,11 +88,16 @@
protected CompositeEditor<T, Object, Editor<Object>> composedEditor;
protected Chain<Object, Editor<Object>> editorChain;
- protected List<EditorError> errors = new ArrayList<EditorError>();
+ protected List<EditorError> errors;
protected HasEditorErrors<T> hasEditorErrors;
protected LeafValueEditor<T> leafValueEditor;
protected String path;
/**
+ * A map of local paths to sub-editors that do not have EditorDelegates (and
+ * will not be in the DelegateMap).
+ */
+ protected Map<String, Editor<?>> simpleEditors;
+ /**
* This field avoids needing to repeatedly cast {@link #editor}.
*/
protected ValueAwareEditor<T> valueAwareEditor;
@@ -97,6 +108,9 @@
public abstract T ensureMutable(T object);
+ /**
+ * Flushes both data and errors.
+ */
public void flush(List<EditorError> errorAccumulator) {
try {
if (valueAwareEditor != null) {
@@ -119,39 +133,15 @@
editorChain.collectErrors();
}
} finally {
- if (hasEditorErrors != null) {
- // Allow higher-priority co-editor to spy on errors
- for (Iterator<EditorError> it = errorAccumulator.iterator(); it.hasNext();) {
- EditorError error = it.next();
- if (error.getAbsolutePath().startsWith(getPath())) {
- errors.add(error);
- it.remove();
- }
- }
- // Include the trailing dot
- int length = getPath().length();
- int pathPrefixLength = length == 0 ? 0 : (length + 1);
- for (EditorError error : errors) {
- ((SimpleError) error).setPathPrefixLength(pathPrefixLength);
- }
- // Give all of the errors to the handler and use a new local accumulator
- hasEditorErrors.showErrors(Collections.unmodifiableList(errors));
-
- for (EditorError error : errors) {
- if (!error.isConsumed()) {
- errorAccumulator.add(error);
- }
- }
-
- // Reset local error list
- errors = new ArrayList<EditorError>();
- } else {
- errorAccumulator.addAll(errors);
- errors.clear();
- }
+ showErrors(errorAccumulator);
}
}
+ public void flushErrors(List<EditorError> errorAccumulator) {
+ flushSubEditorErrors(errorAccumulator);
+ showErrors(errorAccumulator);
+ }
+
public abstract T getObject();
public String getPath() {
@@ -163,13 +153,20 @@
errors.add(error);
}
+ public void recordError(String message, Object value, Object userData,
+ String extraPath) {
+ EditorError error = new SimpleError(this, message, value, userData,
+ extraPath);
+ errors.add(error);
+ }
+
public abstract HandlerRegistration subscribe();
protected String appendPath(String path) {
return appendPath(this.path, path);
}
- protected abstract void attachSubEditors();
+ protected abstract void attachSubEditors(DelegateMap delegates);
/**
* Only implemented by delegates for a {@link CompositeEditor}.
@@ -178,14 +175,24 @@
throw new IllegalStateException();
}
+ protected abstract void flushSubEditorErrors(
+ List<EditorError> errorAccumulator);
+
protected abstract void flushSubEditors(List<EditorError> errorAccumulator);
protected abstract E getEditor();
- protected void initialize(String pathSoFar, T object, E editor) {
+ protected Editor<?> getSimpleEditor(String declaredPath) {
+ return simpleEditors.get(declaredPath);
+ }
+
+ protected void initialize(String pathSoFar, T object, E editor,
+ DelegateMap map) {
this.path = pathSoFar;
setEditor(editor);
setObject(object);
+ errors = new ArrayList<EditorError>();
+ simpleEditors = new HashMap<String, Editor<?>>();
// Set up pre-casted fields to access the editor
if (editor instanceof HasEditorErrors<?>) {
@@ -203,7 +210,7 @@
@SuppressWarnings("unchecked")
CompositeEditor<T, Object, Editor<Object>> temp = (CompositeEditor<T, Object, Editor<Object>>) editor;
composedEditor = temp;
- editorChain = new Chain<Object, Editor<Object>>();
+ editorChain = new Chain<Object, Editor<Object>>(map);
composedEditor.setEditorChain(editorChain);
}
}
@@ -222,7 +229,7 @@
valueAwareEditor.setValue(object);
}
if (object != null) {
- attachSubEditors();
+ attachSubEditors(map);
}
}
@@ -231,9 +238,47 @@
*/
protected abstract <R, S extends Editor<R>> void initializeSubDelegate(
AbstractEditorDelegate<R, S> subDelegate, String path, R object,
- S subEditor);
+ S subEditor, DelegateMap map);
protected abstract void setEditor(E editor);
protected abstract void setObject(T object);
+
+ /**
+ * @param errorAccumulator an out parameter to which unhandled EditorErrors
+ * will be added.
+ */
+ private void showErrors(List<EditorError> errorAccumulator) {
+ assert errors != errorAccumulator;
+ if (hasEditorErrors != null) {
+ // Allow higher-priority co-editor to spy on errors
+ for (Iterator<EditorError> it = errorAccumulator.iterator(); it.hasNext();) {
+ EditorError error = it.next();
+ if (error.getAbsolutePath().startsWith(getPath())) {
+ errors.add(error);
+ it.remove();
+ }
+ }
+ // Include the trailing dot
+ int length = getPath().length();
+ int pathPrefixLength = length == 0 ? 0 : (length + 1);
+ for (EditorError error : errors) {
+ ((SimpleError) error).setPathPrefixLength(pathPrefixLength);
+ }
+ // Give all of the errors to the handler and use a new local accumulator
+ hasEditorErrors.showErrors(Collections.unmodifiableList(errors));
+
+ for (EditorError error : errors) {
+ if (!error.isConsumed()) {
+ errorAccumulator.add(error);
+ }
+ }
+
+ // Reset local error list
+ errors = new ArrayList<EditorError>();
+ } else {
+ errorAccumulator.addAll(errors);
+ errors.clear();
+ }
+ }
}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/editor/client/impl/AbstractSimpleBeanEditorDriver.java b/user/src/com/google/gwt/editor/client/impl/AbstractSimpleBeanEditorDriver.java
index 7fb7268..5bfc2f4 100644
--- a/user/src/com/google/gwt/editor/client/impl/AbstractSimpleBeanEditorDriver.java
+++ b/user/src/com/google/gwt/editor/client/impl/AbstractSimpleBeanEditorDriver.java
@@ -33,6 +33,7 @@
implements SimpleBeanEditorDriver<T, E> {
private SimpleBeanEditorDelegate<T, E> delegate;
+ private DelegateMap delegateMap = new DelegateMap(DelegateMap.IDENTITY);
private E editor;
private List<EditorError> errors;
private T object;
@@ -41,7 +42,8 @@
checkEditor();
this.object = object;
delegate = createDelegate();
- delegate.initialize("", object, editor);
+ delegate.initialize("", object, editor, delegateMap);
+ delegateMap.put(object, delegate);
}
public T flush() {
@@ -65,6 +67,13 @@
protected abstract SimpleBeanEditorDelegate<T, E> createDelegate();
+ /**
+ * Visible for testing.
+ */
+ DelegateMap getDelegateMap() {
+ return delegateMap;
+ }
+
private void checkDelegate() {
if (delegate == null) {
throw new IllegalStateException("Must call edit() first");
diff --git a/user/src/com/google/gwt/editor/client/impl/DelegateMap.java b/user/src/com/google/gwt/editor/client/impl/DelegateMap.java
new file mode 100644
index 0000000..f20ee51
--- /dev/null
+++ b/user/src/com/google/gwt/editor/client/impl/DelegateMap.java
@@ -0,0 +1,89 @@
+/*
+ * 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.impl;
+
+import java.util.ArrayList;
+import java.util.HashMap;
+import java.util.List;
+import java.util.Map;
+
+/**
+ * Allows fast traversal of an Editor hierarchy.
+ */
+public class DelegateMap {
+ /**
+ *
+ */
+ public interface KeyMethod {
+ Object key(Object object);
+ }
+
+ public static final KeyMethod IDENTITY = new KeyMethod() {
+ public Object key(Object object) {
+ return object;
+ }
+ };
+
+ private final Map<Object, List<AbstractEditorDelegate<?, ?>>> map = new HashMap<Object, List<AbstractEditorDelegate<?, ?>>>();
+ private final Map<String, List<AbstractEditorDelegate<?, ?>>> paths = new HashMap<String, List<AbstractEditorDelegate<?, ?>>>();
+ private final KeyMethod keyMethod;
+
+ public DelegateMap(KeyMethod key) {
+ this.keyMethod = key;
+ }
+
+ public List<AbstractEditorDelegate<?, ?>> get(Object object) {
+ Object key = keyMethod.key(object);
+ return key == null ? null : map.get(key);
+ }
+
+ /**
+ * Returns a list of Editors available at a particular absolute path.
+ */
+ public List<AbstractEditorDelegate<?, ?>> getPath(String path) {
+ return paths.get(path);
+ }
+
+ /**
+ * Accesses the delegate map without using the KeyMethod.
+ */
+ public List<AbstractEditorDelegate<?, ?>> getRaw(Object key) {
+ return map.get(key);
+ }
+
+ public <T> void put(T object, AbstractEditorDelegate<T, ?> delegate) {
+ {
+ List<AbstractEditorDelegate<?, ?>> list = paths.get(delegate.getPath());
+ if (list == null) {
+ list = new ArrayList<AbstractEditorDelegate<?, ?>>();
+ paths.put(delegate.getPath(), list);
+ }
+ list.add(delegate);
+ }
+ Object key = keyMethod.key(object);
+ if (key == null) {
+ return;
+ }
+ {
+ List<AbstractEditorDelegate<?, ?>> list = map.get(key);
+ if (list == null) {
+ list = new ArrayList<AbstractEditorDelegate<?, ?>>();
+ map.put(key, list);
+ }
+ list.add(delegate);
+ }
+ }
+}
\ No newline at end of file
diff --git a/user/src/com/google/gwt/editor/client/impl/SimpleBeanEditorDelegate.java b/user/src/com/google/gwt/editor/client/impl/SimpleBeanEditorDelegate.java
index bc3d6a4..df0dcf5 100644
--- a/user/src/com/google/gwt/editor/client/impl/SimpleBeanEditorDelegate.java
+++ b/user/src/com/google/gwt/editor/client/impl/SimpleBeanEditorDelegate.java
@@ -38,8 +38,8 @@
}
@Override
- public void initialize(String pathSoFar, T object, E editor) {
- super.initialize(pathSoFar, object, editor);
+ public void initialize(String pathSoFar, T object, E editor, DelegateMap map) {
+ super.initialize(pathSoFar, object, editor, map);
}
@Override
@@ -50,8 +50,8 @@
@Override
protected <R, S extends Editor<R>> void initializeSubDelegate(
AbstractEditorDelegate<R, S> subDelegate, String path, R object,
- S subEditor) {
+ S subEditor, DelegateMap map) {
((SimpleBeanEditorDelegate<R, S>) subDelegate).initialize(path, object,
- subEditor);
+ subEditor, map);
}
}
diff --git a/user/src/com/google/gwt/editor/client/impl/SimpleError.java b/user/src/com/google/gwt/editor/client/impl/SimpleError.java
index 2b0db0f..e2cf710 100644
--- a/user/src/com/google/gwt/editor/client/impl/SimpleError.java
+++ b/user/src/com/google/gwt/editor/client/impl/SimpleError.java
@@ -39,6 +39,19 @@
this.userData = userData;
}
+ /**
+ * Used to construct an error for an Editor that does not have a delegate.
+ */
+ SimpleError(AbstractEditorDelegate<?, ?> delegate, String message,
+ Object value, Object userData, String extraPath) {
+ assert extraPath != null && extraPath.length() > 0;
+ this.absolutePath = delegate.getPath() + extraPath;
+ this.editor = delegate.getSimpleEditor(extraPath);
+ this.message = message;
+ this.value = value;
+ this.userData = userData;
+ }
+
public String getAbsolutePath() {
return absolutePath;
}
diff --git a/user/src/com/google/gwt/editor/client/ui/EditorErrorPanel.java b/user/src/com/google/gwt/editor/client/ui/ValueBoxEditorDecorator.java
similarity index 62%
rename from user/src/com/google/gwt/editor/client/ui/EditorErrorPanel.java
rename to user/src/com/google/gwt/editor/client/ui/ValueBoxEditorDecorator.java
index 136052d..9132016 100644
--- a/user/src/com/google/gwt/editor/client/ui/EditorErrorPanel.java
+++ b/user/src/com/google/gwt/editor/client/ui/ValueBoxEditorDecorator.java
@@ -18,30 +18,47 @@
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.DivElement;
import com.google.gwt.dom.client.Style.Display;
-import com.google.gwt.editor.client.Editor;
import com.google.gwt.editor.client.EditorError;
import com.google.gwt.editor.client.HasEditorErrors;
import com.google.gwt.editor.client.IsEditor;
+import com.google.gwt.editor.client.adapters.ValueBoxEditor;
import com.google.gwt.uibinder.client.UiBinder;
import com.google.gwt.uibinder.client.UiChild;
import com.google.gwt.uibinder.client.UiConstructor;
import com.google.gwt.uibinder.client.UiField;
import com.google.gwt.user.client.ui.Composite;
import com.google.gwt.user.client.ui.SimplePanel;
+import com.google.gwt.user.client.ui.ValueBoxBase;
import com.google.gwt.user.client.ui.Widget;
import java.util.List;
/**
- * A simple decorator to display an Editor's EditorErrors in conjunction with a
- * Widget.
+ * A simple decorator to display leaf widgets with an error message.
+ * <p>
+ * <h3>Use in UiBinder Templates</h3>
+ * <p>
+ * The decorator may have exactly one ValueBoxBase added though an
+ * <code><e:valuebox></code> child tag.
+ * <p>
+ * For example:
+ * <pre>
+ * @UiField
+ * ValueBoxEditorDecorator<String> name;
+ * </pre>
+ * <pre>
+ * <e:ValueBoxEditorDecorator ui:field='name'>
+ * <e:valuebox>
+ * <g:TextBox />
+ * </e:valuebox>
+ * </e:ValueBoxEditorDecorator>
+ * </pre>
*
- * @param <T> the type of data being editod
- * @param <E> the type of Editor
+ * @param <T> the type of data being edited
*/
-public class EditorErrorPanel<T, E extends Editor<T>> extends Composite
- implements HasEditorErrors<T>, IsEditor<E> {
- interface Binder extends UiBinder<Widget, EditorErrorPanel<?, ?>> {
+public class ValueBoxEditorDecorator<T> extends Composite implements
+ HasEditorErrors<T>, IsEditor<ValueBoxEditor<T>> {
+ interface Binder extends UiBinder<Widget, ValueBoxEditorDecorator<?>> {
Binder BINDER = GWT.create(Binder.class);
}
@@ -51,45 +68,36 @@
@UiField
DivElement errorLabel;
- private E editor;
+ private ValueBoxEditor<T> editor;
@UiConstructor
- public EditorErrorPanel() {
+ public ValueBoxEditorDecorator() {
initWidget(Binder.BINDER.createAndBindUi(this));
}
- public EditorErrorPanel(Widget widget, E editor) {
+ public ValueBoxEditorDecorator(ValueBoxBase<T> widget,
+ ValueBoxEditor<T> editor) {
this();
- setWidget(widget);
- setEditor(editor);
+ contents.add(widget);
+ this.editor = editor;
}
- public E asEditor() {
+ public ValueBoxEditor<T> asEditor() {
return editor;
}
- public void setEditor(E editor) {
+ public void setEditor(ValueBoxEditor<T> editor) {
this.editor = editor;
}
/**
- * Set the widget that the EditorPanel will display. If the widget is also an
- * {@link Editor} or implements {@link IsEditor}, this method will
+ * Set the widget that the EditorPanel will display. This method will
* automatically call {@link #setEditor}.
*/
- @UiChild(limit = 1, tagname = "contents")
- public void setWidget(Widget widget) {
+ @UiChild(limit = 1, tagname = "valuebox")
+ public void setValueBox(ValueBoxBase<T> widget) {
contents.add(widget);
-
- if (widget instanceof Editor<?>) {
- @SuppressWarnings("unchecked")
- E isEditor = (E) widget;
- setEditor(isEditor);
- } else if (widget instanceof IsEditor<?>) {
- @SuppressWarnings("unchecked")
- E isEditor = ((IsEditor<E>) widget).asEditor();
- setEditor(isEditor);
- }
+ setEditor(widget.asEditor());
}
/**
@@ -98,18 +106,19 @@
* passed into {@link #setEditor()}.
*/
public void showErrors(List<EditorError> errors) {
- if (errors.isEmpty()) {
- errorLabel.setInnerText("");
- errorLabel.getStyle().setDisplay(Display.NONE);
- return;
- }
-
StringBuilder sb = new StringBuilder();
for (EditorError error : errors) {
if (error.getEditor().equals(editor)) {
sb.append("\n").append(error.getMessage());
}
}
+
+ if (sb.length() == 0) {
+ errorLabel.setInnerText("");
+ errorLabel.getStyle().setDisplay(Display.NONE);
+ return;
+ }
+
errorLabel.setInnerText(sb.substring(1));
errorLabel.getStyle().setDisplay(Display.INLINE_BLOCK);
}
diff --git a/user/src/com/google/gwt/editor/client/ui/EditorErrorPanel.ui.xml b/user/src/com/google/gwt/editor/client/ui/ValueBoxEditorDecorator.ui.xml
similarity index 100%
rename from user/src/com/google/gwt/editor/client/ui/EditorErrorPanel.ui.xml
rename to user/src/com/google/gwt/editor/client/ui/ValueBoxEditorDecorator.ui.xml
diff --git a/user/src/com/google/gwt/editor/rebind/AbstractEditorDriverGenerator.java b/user/src/com/google/gwt/editor/rebind/AbstractEditorDriverGenerator.java
index 9aa6ed9..dc2a0ce 100644
--- a/user/src/com/google/gwt/editor/rebind/AbstractEditorDriverGenerator.java
+++ b/user/src/com/google/gwt/editor/rebind/AbstractEditorDriverGenerator.java
@@ -25,6 +25,7 @@
import com.google.gwt.dev.util.Name;
import com.google.gwt.dev.util.Name.BinaryName;
import com.google.gwt.editor.client.Editor;
+import com.google.gwt.editor.client.impl.DelegateMap;
import com.google.gwt.editor.rebind.model.EditorData;
import com.google.gwt.editor.rebind.model.EditorModel;
import com.google.gwt.user.rebind.ClassSourceFileComposerFactory;
@@ -159,7 +160,8 @@
}
// For each entity property, create a sub-delegate and initialize
- sw.println("protected void attachSubEditors() {");
+ sw.println("protected void attachSubEditors(%s delegateMap) {",
+ DelegateMap.class.getCanonicalName());
sw.indent();
for (EditorData d : data) {
String subDelegateType = getEditorDelegate(d);
@@ -168,11 +170,16 @@
if (d.isDelegateRequired()) {
sw.println("%s = new %s();", delegateFields.get(d), subDelegateType);
writeDelegateInitialization(sw, d, delegateFields);
+ sw.println("delegateMap.put(%1$s.getObject(), %1$s);",
+ delegateFields.get(d));
} else if (d.isLeafValueEditor()) {
// if (can().access().without().npe()) { editor.subEditor.setValue() }
sw.println("if (%4$s) editor.%1$s.setValue(getObject()%2$s.%3$s());",
d.getSimpleExpression(), d.getBeanOwnerExpression(),
d.getGetterName(), d.getBeanOwnerGuard("getObject()"));
+ // simpleEditor.put("some.path", editor.simpleEditor());
+ sw.println("simpleEditors.put(\"%s\", editor.%s);",
+ d.getDeclaredPath(), d.getSimpleExpression());
}
sw.outdent();
sw.println("}");
@@ -226,6 +233,20 @@
sw.outdent();
sw.println("}");
+ // Flush each sub-delegate
+ sw.println("protected void flushSubEditorErrors(%s errorAccumulator) {",
+ List.class.getCanonicalName());
+ sw.indent();
+ for (EditorData d : data) {
+ if (d.isDelegateRequired()) {
+ // if (fooDelegate != null) fooDelegate.flushErrors(accumulator);
+ sw.println("if (%1$s != null) %1$s.flushErrors(errorAccumulator);",
+ delegateFields.get(d));
+ }
+ }
+ sw.outdent();
+ sw.println("}");
+
sw.println("public static void traverseEditor(%s editor,"
+ " String prefix, %s<String> paths) {",
editor.getQualifiedSourceName(), List.class.getName());
diff --git a/user/src/com/google/gwt/editor/rebind/SimpleBeanEditorDriverGenerator.java b/user/src/com/google/gwt/editor/rebind/SimpleBeanEditorDriverGenerator.java
index e2d9b21..1a2200b 100644
--- a/user/src/com/google/gwt/editor/rebind/SimpleBeanEditorDriverGenerator.java
+++ b/user/src/com/google/gwt/editor/rebind/SimpleBeanEditorDriverGenerator.java
@@ -56,7 +56,8 @@
// fooDelegate.initialize(appendPath("foo"), getObject().getFoo(),
// editor.fooEditor);
sw.println("%s.initialize(appendPath(\"%s\"), getObject()%s.%s(),"
- + " editor.%s);", delegateFields.get(d), d.getPropertyName(),
- d.getBeanOwnerExpression(), d.getGetterName(), d.getSimpleExpression());
+ + " editor.%s, delegateMap);", delegateFields.get(d),
+ d.getPropertyName(), d.getBeanOwnerExpression(), d.getGetterName(),
+ d.getSimpleExpression());
}
}
diff --git a/user/src/com/google/gwt/editor/rebind/model/EditorModel.java b/user/src/com/google/gwt/editor/rebind/model/EditorModel.java
index 13b8f0d..f58d086 100644
--- a/user/src/com/google/gwt/editor/rebind/model/EditorModel.java
+++ b/user/src/com/google/gwt/editor/rebind/model/EditorModel.java
@@ -320,7 +320,8 @@
for (JClassType type : editorType.getFlattenedSupertypeHierarchy()) {
for (JField field : type.getFields()) {
- if (field.isPrivate() || field.isStatic()) {
+ if (field.isPrivate() || field.isStatic()
+ || field.getAnnotation(Editor.Ignore.class) != null) {
continue;
}
JType fieldClassType = field.getType();
@@ -330,7 +331,8 @@
}
}
for (JMethod method : type.getMethods()) {
- if (method.isPrivate() || method.isStatic()) {
+ if (method.isPrivate() || method.isStatic()
+ || method.getAnnotation(Editor.Ignore.class) != null) {
continue;
}
JType methodReturnType = method.getReturnType();
diff --git a/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java b/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java
index 2f04c7c..4179cbd 100644
--- a/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java
+++ b/user/src/com/google/gwt/requestfactory/client/RequestFactoryEditorDriver.java
@@ -21,6 +21,7 @@
import com.google.gwt.requestfactory.shared.EntityProxy;
import com.google.gwt.requestfactory.shared.RequestFactory;
import com.google.gwt.requestfactory.shared.RequestObject;
+import com.google.gwt.requestfactory.shared.Violation;
import java.util.List;
@@ -87,4 +88,15 @@
* RequestFactory should be provided.
*/
void initialize(EventBus eventBus, RequestFactory requestFactory, E editor);
+
+ /**
+ * Show Violations returned from an attempt to submit a request. The
+ * violations will be converted into {@link EditorError} objects whose
+ * {@link EditorError#getUserData() getUserData()} method can be used to
+ * access the original Violation object.
+ *
+ * @return <code>true</code> if there were any unconsumed EditorErrors which
+ * can be retrieved from {@link #getErrors()}
+ */
+ boolean setViolations(Iterable<Violation> errors);
}
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java b/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java
index 9aaf8e8..ce6a8f3 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/AbstractRequestFactoryEditorDriver.java
@@ -17,11 +17,14 @@
import com.google.gwt.editor.client.Editor;
import com.google.gwt.editor.client.EditorError;
+import com.google.gwt.editor.client.impl.AbstractEditorDelegate;
+import com.google.gwt.editor.client.impl.DelegateMap;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.requestfactory.client.RequestFactoryEditorDriver;
import com.google.gwt.requestfactory.shared.EntityProxy;
import com.google.gwt.requestfactory.shared.RequestFactory;
import com.google.gwt.requestfactory.shared.RequestObject;
+import com.google.gwt.requestfactory.shared.Violation;
import java.util.ArrayList;
import java.util.List;
@@ -35,7 +38,17 @@
public abstract class AbstractRequestFactoryEditorDriver<R extends EntityProxy, E extends Editor<R>>
implements RequestFactoryEditorDriver<R, E> {
+ private static final DelegateMap.KeyMethod PROXY_ID_KEY = new DelegateMap.KeyMethod() {
+ public Object key(Object object) {
+ if (object instanceof EntityProxy) {
+ return ((EntityProxy) object).stableId();
+ }
+ return null;
+ }
+ };
+
private RequestFactoryEditorDelegate<R, E> delegate;
+ private DelegateMap delegateMap = new DelegateMap(PROXY_ID_KEY);
private E editor;
private EventBus eventBus;
private List<EditorError> errors;
@@ -48,16 +61,19 @@
this.saveRequest = saveRequest;
delegate = createDelegate();
delegate.initialize(eventBus, requestFactory, "", object, editor,
- saveRequest);
+ delegateMap, saveRequest);
+ delegateMap.put(object, delegate);
}
- @SuppressWarnings("unchecked")
public <T> RequestObject<T> flush() {
checkDelegate();
checkSaveRequest();
errors = new ArrayList<EditorError>();
delegate.flush(errors);
- return (RequestObject<T>) saveRequest;
+
+ @SuppressWarnings("unchecked")
+ RequestObject<T> toReturn = (RequestObject<T>) saveRequest;
+ return toReturn;
}
public List<EditorError> getErrors() {
@@ -81,6 +97,49 @@
traverseEditors(paths);
}
+ public boolean setViolations(Iterable<Violation> violations) {
+ checkDelegate();
+
+ // For each violation
+ for (Violation error : violations) {
+
+ /*
+ * Find the delegates that are attached to the object. Use getRaw() here
+ * since the violation doesn't include an EntityProxy reference
+ */
+ List<AbstractEditorDelegate<?, ?>> delegateList = delegateMap.getRaw(error.getProxyId());
+ if (delegateList != null) {
+
+ // For each delegate editing some record...
+ for (AbstractEditorDelegate<?, ?> baseDelegate : delegateList) {
+
+ // compute its base path in the hierarchy...
+ String basePath = baseDelegate.getPath();
+
+ // and the absolute path of the leaf editor receiving the error.
+ String absolutePath = (basePath.length() > 0 ? basePath + "." : "")
+ + error.getPath();
+
+ // Find the leaf editor's delegate.
+ List<AbstractEditorDelegate<?, ?>> leafDelegates = delegateMap.getPath(absolutePath);
+ if (leafDelegates != null) {
+ // Only attach the error to the first delegate in a co-editor chain.
+ leafDelegates.get(0).recordError(error.getMessage(), null, error);
+ } else {
+ // No EditorDelegate to attach it to, stick it on the base.
+ baseDelegate.recordError(error.getMessage(), null, error,
+ error.getPath());
+ }
+ }
+ }
+ }
+
+ // Flush the errors, which will take care of co-editor chains.
+ errors = new ArrayList<EditorError>();
+ delegate.flushErrors(errors);
+ return !errors.isEmpty();
+ }
+
protected abstract RequestFactoryEditorDelegate<R, E> createDelegate();
protected E getEditor() {
diff --git a/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryEditorDelegate.java b/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryEditorDelegate.java
index 7a7bb18..011bb58 100644
--- a/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryEditorDelegate.java
+++ b/user/src/com/google/gwt/requestfactory/client/impl/RequestFactoryEditorDelegate.java
@@ -18,6 +18,7 @@
import com.google.gwt.core.client.GWT;
import com.google.gwt.editor.client.Editor;
import com.google.gwt.editor.client.impl.AbstractEditorDelegate;
+import com.google.gwt.editor.client.impl.DelegateMap;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.event.shared.HandlerRegistration;
import com.google.gwt.requestfactory.shared.EntityProxy;
@@ -53,11 +54,12 @@
}
public void initialize(EventBus eventBus, RequestFactory factory,
- String pathSoFar, P object, E editor, RequestObject<?> editRequest) {
+ String pathSoFar, P object, E editor, DelegateMap delegateMap,
+ RequestObject<?> editRequest) {
this.eventBus = eventBus;
this.factory = factory;
this.request = editRequest;
- super.initialize(pathSoFar, object, editor);
+ super.initialize(pathSoFar, object, editor, delegateMap);
}
@Override
@@ -73,8 +75,8 @@
@Override
protected <R, S extends Editor<R>> void initializeSubDelegate(
AbstractEditorDelegate<R, S> subDelegate, String path, R object,
- S subEditor) {
+ S subEditor, DelegateMap delegateMap) {
((RequestFactoryEditorDelegate<R, S>) subDelegate).initialize(eventBus,
- factory, path, object, subEditor, request);
+ factory, path, object, subEditor, delegateMap, request);
}
}
diff --git a/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java b/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java
index 62c9203..452ea84 100644
--- a/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java
+++ b/user/src/com/google/gwt/requestfactory/client/testing/MockRequestFactoryEditorDriver.java
@@ -22,6 +22,7 @@
import com.google.gwt.requestfactory.shared.EntityProxy;
import com.google.gwt.requestfactory.shared.RequestFactory;
import com.google.gwt.requestfactory.shared.RequestObject;
+import com.google.gwt.requestfactory.shared.Violation;
import java.util.Collections;
import java.util.List;
@@ -37,10 +38,10 @@
implements RequestFactoryEditorDriver<P, E> {
private static final String[] EMPTY_STRING = new String[0];
- private RequestObject<?> saveRequest;
private EventBus eventBus;
private E editor;
private P proxy;
+ private RequestObject<?> saveRequest;
private RequestFactory requestFactory;
/**
@@ -124,4 +125,11 @@
this.requestFactory = requestFactory;
this.editor = editor;
}
+
+ /**
+ * A no-op method that always returns false.
+ */
+ public boolean setViolations(Iterable<Violation> errors) {
+ return false;
+ }
}
diff --git a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryEditorDriverGenerator.java b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryEditorDriverGenerator.java
index 23d7add..e36214f 100644
--- a/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryEditorDriverGenerator.java
+++ b/user/src/com/google/gwt/requestfactory/rebind/RequestFactoryEditorDriverGenerator.java
@@ -89,8 +89,9 @@
protected void writeDelegateInitialization(SourceWriter sw, EditorData d,
Map<EditorData, String> delegateFields) {
sw.println("%s.initialize(eventBus, factory, "
- + "appendPath(\"%s\"), getObject()%s.%s()," + " editor.%s, request);",
- delegateFields.get(d), d.getPropertyName(), d.getBeanOwnerExpression(),
- d.getGetterName(), d.getSimpleExpression());
+ + "appendPath(\"%s\"), getObject()%s.%s()," + " editor.%s,"
+ + " delegateMap, request);", delegateFields.get(d),
+ d.getPropertyName(), d.getBeanOwnerExpression(), d.getGetterName(),
+ d.getSimpleExpression());
}
}
diff --git a/user/src/com/google/gwt/user/client/ui/ValueBoxBase.java b/user/src/com/google/gwt/user/client/ui/ValueBoxBase.java
index bd83c3c..ed1dd09 100644
--- a/user/src/com/google/gwt/user/client/ui/ValueBoxBase.java
+++ b/user/src/com/google/gwt/user/client/ui/ValueBoxBase.java
@@ -18,7 +18,6 @@
import com.google.gwt.core.client.GWT;
import com.google.gwt.dom.client.Element;
import com.google.gwt.editor.client.IsEditor;
-import com.google.gwt.editor.client.LeafValueEditor;
import com.google.gwt.editor.client.adapters.ValueBoxEditor;
import com.google.gwt.event.dom.client.ChangeEvent;
import com.google.gwt.event.dom.client.ChangeHandler;
@@ -67,7 +66,7 @@
@SuppressWarnings("deprecation")
public class ValueBoxBase<T> extends FocusWidget implements
SourcesChangeEvents, HasChangeHandlers, HasName, HasDirectionEstimator,
- HasValue<T>, AutoDirectionHandler.Target, IsEditor<LeafValueEditor<T>> {
+ HasValue<T>, AutoDirectionHandler.Target, IsEditor<ValueBoxEditor<T>> {
private static TextBoxImpl impl = GWT.create(TextBoxImpl.class);
@@ -124,7 +123,7 @@
* may override this method to provide custom error-handling when using the
* Editor framework.
*/
- public LeafValueEditor<T> asEditor() {
+ public ValueBoxEditor<T> asEditor() {
return ValueBoxEditor.of(this);
}
diff --git a/user/test/com/google/gwt/editor/EditorSuite.java b/user/test/com/google/gwt/editor/EditorSuite.java
index 5c3bffe..a4d615c 100644
--- a/user/test/com/google/gwt/editor/EditorSuite.java
+++ b/user/test/com/google/gwt/editor/EditorSuite.java
@@ -18,6 +18,7 @@
import com.google.gwt.editor.client.EditorErrorTest;
import com.google.gwt.editor.client.SimpleBeanEditorTest;
import com.google.gwt.editor.client.adapters.ListEditorWrapperTest;
+import com.google.gwt.editor.client.impl.DelegateMapTest;
import com.google.gwt.editor.rebind.model.EditorModelTest;
import com.google.gwt.junit.tools.GWTTestSuite;
@@ -31,6 +32,7 @@
public static Test suite() {
GWTTestSuite suite = new GWTTestSuite(
"Test suite for core Editor functions");
+ suite.addTestSuite(DelegateMapTest.class);
suite.addTestSuite(EditorModelTest.class);
suite.addTestSuite(EditorErrorTest.class);
suite.addTestSuite(ListEditorWrapperTest.class);
diff --git a/user/test/com/google/gwt/editor/client/AddressEditor.java b/user/test/com/google/gwt/editor/client/AddressEditor.java
index 8fa108d..33862a6 100644
--- a/user/test/com/google/gwt/editor/client/AddressEditor.java
+++ b/user/test/com/google/gwt/editor/client/AddressEditor.java
@@ -21,6 +21,6 @@
* Simple editor used by multiple tests.
*/
public class AddressEditor implements Editor<Address> {
- SimpleEditor<String> city = SimpleEditor.of(SimpleBeanEditorTest.UNINITIALIZED);
- SimpleEditor<String> street = SimpleEditor.of(SimpleBeanEditorTest.UNINITIALIZED);
+ public SimpleEditor<String> city = SimpleEditor.of(SimpleBeanEditorTest.UNINITIALIZED);
+ public SimpleEditor<String> street = SimpleEditor.of(SimpleBeanEditorTest.UNINITIALIZED);
}
\ No newline at end of file
diff --git a/user/test/com/google/gwt/editor/client/PersonEditor.java b/user/test/com/google/gwt/editor/client/PersonEditor.java
index e6336e9..61c8b0c 100644
--- a/user/test/com/google/gwt/editor/client/PersonEditor.java
+++ b/user/test/com/google/gwt/editor/client/PersonEditor.java
@@ -21,8 +21,8 @@
* Simple editor used by multiple tests.
*/
class PersonEditor implements Editor<Person> {
- AddressEditor addressEditor = new AddressEditor();
- SimpleEditor<String> name = SimpleEditor.of(SimpleBeanEditorTest.UNINITIALIZED);
+ public AddressEditor addressEditor = new AddressEditor();
+ public SimpleEditor<String> name = SimpleEditor.of(SimpleBeanEditorTest.UNINITIALIZED);
@Path("manager.name")
- SimpleEditor<String> managerName = SimpleEditor.of(SimpleBeanEditorTest.UNINITIALIZED);
+ public SimpleEditor<String> managerName = SimpleEditor.of(SimpleBeanEditorTest.UNINITIALIZED);
}
\ No newline at end of file
diff --git a/user/test/com/google/gwt/editor/client/impl/DelegateMapTest.java b/user/test/com/google/gwt/editor/client/impl/DelegateMapTest.java
new file mode 100644
index 0000000..3c4c704
--- /dev/null
+++ b/user/test/com/google/gwt/editor/client/impl/DelegateMapTest.java
@@ -0,0 +1,123 @@
+/*
+ * 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.impl;
+
+import com.google.gwt.core.client.GWT;
+import com.google.gwt.editor.client.Address;
+import com.google.gwt.editor.client.AddressEditor;
+import com.google.gwt.editor.client.Editor;
+import com.google.gwt.editor.client.IsEditor;
+import com.google.gwt.editor.client.Person;
+import com.google.gwt.editor.client.SimpleBeanEditorDriver;
+import com.google.gwt.editor.client.adapters.SimpleEditor;
+import com.google.gwt.junit.client.GWTTestCase;
+
+import java.util.ArrayList;
+import java.util.Arrays;
+import java.util.List;
+
+/**
+ *
+ */
+public class DelegateMapTest extends GWTTestCase {
+ class AddressCoEditorView extends AddressEditor implements
+ IsEditor<AddressEditor> {
+ private AddressEditor addressEditor = new AddressEditor();
+
+ public AddressEditor asEditor() {
+ return addressEditor;
+ }
+ }
+
+ class PersonEditorWithCoAddressEditorView implements Editor<Person> {
+ AddressCoEditorView addressEditor = new AddressCoEditorView();
+ SimpleEditor<String> name = SimpleEditor.of("uninitialized");
+ @Path("manager.name")
+ SimpleEditor<String> managerName = SimpleEditor.of("uninitialized");
+ }
+
+ interface PersonEditorWithCoAddressEditorViewDriver extends
+ SimpleBeanEditorDriver<Person, PersonEditorWithCoAddressEditorView> {
+ }
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.editor.Editor";
+ }
+
+ private AbstractSimpleBeanEditorDriver<Person, PersonEditorWithCoAddressEditorView> driver;
+ private PersonEditorWithCoAddressEditorView editor;
+ private DelegateMap map;
+ private Person person;
+
+ @Override
+ protected void gwtSetUp() throws Exception {
+ Address a = new Address();
+ a.setCity("city");
+ a.setStreet("street");
+
+ Person m = new Person();
+ m.setName("manager");
+
+ person = new Person();
+ person.setName("name");
+ person.setAddress(a);
+ person.setManager(m);
+
+ editor = new PersonEditorWithCoAddressEditorView();
+ driver = GWT.create(PersonEditorWithCoAddressEditorViewDriver.class);
+ driver.initialize(editor);
+ driver.edit(person);
+
+ map = driver.getDelegateMap();
+ }
+
+ public void test() {
+ // Test by-object
+ assertEquals(Arrays.asList(editor), editors(map, person));
+ assertEquals(Arrays.asList(editor.addressEditor.addressEditor,
+ editor.addressEditor), editors(map, person.getAddress()));
+
+ // Test by-path
+ assertEquals(Arrays.asList(editor), editors(map, ""));
+ assertEquals(Arrays.asList(editor.addressEditor.addressEditor,
+ editor.addressEditor), editors(map, "address"));
+ }
+
+ public void testSimplePath() {
+ assertSame(editor.name, map.get(person).get(0).getSimpleEditor("name"));
+ assertSame(editor.managerName, map.get(person).get(0).getSimpleEditor(
+ "manager.name"));
+ // Only simple editors
+ assertNull(map.get(person).get(0).getSimpleEditor("address"));
+ }
+
+ private List<Editor<?>> editors(DelegateMap map, Object o) {
+ List<Editor<?>> toReturn = new ArrayList<Editor<?>>();
+ for (AbstractEditorDelegate<?, ?> delegate : map.get(o)) {
+ toReturn.add(delegate.getEditor());
+ }
+ return toReturn;
+ }
+
+ private List<Editor<?>> editors(DelegateMap map, String path) {
+ List<Editor<?>> toReturn = new ArrayList<Editor<?>>();
+ for (AbstractEditorDelegate<?, ?> delegate : map.getPath(path)) {
+ toReturn.add(delegate.getEditor());
+ }
+ return toReturn;
+ }
+}
diff --git a/user/test/com/google/gwt/editor/rebind/model/EditorModelTest.java b/user/test/com/google/gwt/editor/rebind/model/EditorModelTest.java
index 66e4482..e7bdf64 100644
--- a/user/test/com/google/gwt/editor/rebind/model/EditorModelTest.java
+++ b/user/test/com/google/gwt/editor/rebind/model/EditorModelTest.java
@@ -42,6 +42,7 @@
import com.google.gwt.requestfactory.shared.EntityProxy;
import com.google.gwt.requestfactory.shared.RequestFactory;
import com.google.gwt.requestfactory.shared.RequestObject;
+import com.google.gwt.requestfactory.shared.Violation;
import com.google.gwt.user.client.TakesValue;
import com.google.gwt.user.client.ui.HasText;
@@ -599,6 +600,7 @@
code.append("SimpleEditor<String> readonly;\n");
code.append("public static SimpleEditor ignoredStatic;\n");
code.append("private SimpleEditor<String> ignoredPrivate;\n");
+ code.append("@Editor.Ignore public SimpleEditor<String> ignoredPublic;\n");
code.append("}");
return code;
}
@@ -614,6 +616,7 @@
code.append("protected abstract SimpleEditor<String> readonlyEditor();\n");
code.append("public static SimpleEditor<String> ignoredStatic() {return null;}\n");
code.append("private SimpleEditor<String> ignoredPrivate() {return null;}\n");
+ code.append("@Editor.Ignore public abstract SimpleEditor<String> ignoredPublic();\n");
code.append("}");
return code;
}
@@ -767,6 +770,7 @@
new EmptyMockJavaResource(HasEditorErrors.class),
new RealJavaResource(HasText.class),
new RealJavaResource(IsEditor.class),
+ new EmptyMockJavaResource(Iterable.class),
new RealJavaResource(LeafValueEditor.class),
new EmptyMockJavaResource(Property.class),
new EmptyMockJavaResource(EntityProxy.class),
@@ -775,7 +779,8 @@
new EmptyMockJavaResource(RequestObject.class),
new RealJavaResource(SimpleEditor.class),
new RealJavaResource(TakesValue.class),
- new EmptyMockJavaResource(ValueAwareEditor.class),}));
+ new EmptyMockJavaResource(ValueAwareEditor.class),
+ new EmptyMockJavaResource(Violation.class),}));
toReturn.addAll(Arrays.asList(JavaResourceBase.getStandardResources()));
return toReturn;
}
diff --git a/user/test/com/google/gwt/requestfactory/client/EditorTest.java b/user/test/com/google/gwt/requestfactory/client/EditorTest.java
index 7d950db..aa0bdca 100644
--- a/user/test/com/google/gwt/requestfactory/client/EditorTest.java
+++ b/user/test/com/google/gwt/requestfactory/client/EditorTest.java
@@ -17,6 +17,8 @@
import com.google.gwt.core.client.GWT;
import com.google.gwt.editor.client.Editor;
+import com.google.gwt.editor.client.EditorError;
+import com.google.gwt.editor.client.HasEditorErrors;
import com.google.gwt.editor.client.adapters.SimpleEditor;
import com.google.gwt.event.shared.EventBus;
import com.google.gwt.event.shared.SimpleEventBus;
@@ -26,23 +28,27 @@
import com.google.gwt.requestfactory.shared.SimpleFooProxy;
import com.google.gwt.requestfactory.shared.SimpleRequestFactory;
import com.google.gwt.requestfactory.shared.SyncResult;
+import com.google.gwt.requestfactory.shared.Violation;
import java.util.Arrays;
+import java.util.List;
import java.util.Set;
/**
- * Integration test of the Editor framework.
+ * Integration test of the Editor framework. Only tests for
+ * RequestFactory-specific features belong here; all other tests should use the
+ * SimpleBeanEditorDriver to make the tests simpler.
*/
public class EditorTest extends GWTTestCase {
- interface SimpleFooDriver extends
- RequestFactoryEditorDriver<SimpleFooProxy, SimpleFooEditor> {
- }
-
static class SimpleBarEditor implements Editor<SimpleBarProxy> {
protected final SimpleEditor<String> userName = SimpleEditor.of();
}
- static class SimpleFooEditor implements Editor<SimpleFooProxy> {
+ interface SimpleFooDriver extends
+ RequestFactoryEditorDriver<SimpleFooProxy, SimpleFooEditor> {
+ }
+
+ static class SimpleFooEditor implements HasEditorErrors<SimpleFooProxy> {
/**
* Test field-based access.
*/
@@ -56,6 +62,12 @@
private final SimpleBarEditor barEditor = new SimpleBarEditor();
+ List<EditorError> errors;
+
+ public void showErrors(List<EditorError> errors) {
+ this.errors = errors;
+ }
+
/**
* Test method-based access with path override.
*/
@@ -68,6 +80,13 @@
private EventBus eventBus;
private SimpleRequestFactory factory;
+ private static final int TEST_TIMEOUT = 5000;
+
+ @Override
+ public String getModuleName() {
+ return "com.google.gwt.requestfactory.RequestFactorySuite";
+ }
+
@Override
public void gwtSetUp() {
factory = GWT.create(SimpleRequestFactory.class);
@@ -87,13 +106,6 @@
});
}
- @Override
- public String getModuleName() {
- return "com.google.gwt.requestfactory.RequestFactorySuite";
- }
-
- private static final int TEST_TIMEOUT = 5000;
-
public void test() {
delayTestFinish(TEST_TIMEOUT);
final SimpleFooEditor editor = new SimpleFooEditor();
@@ -131,4 +143,50 @@
}
});
}
+
+ public void testViolations() {
+ delayTestFinish(TEST_TIMEOUT);
+ final SimpleFooEditor editor = new SimpleFooEditor();
+
+ final SimpleFooDriver driver = GWT.create(SimpleFooDriver.class);
+ driver.initialize(eventBus, factory, editor);
+
+ factory.simpleFooRequest().findSimpleFooById(0L).with(driver.getPaths()).fire(
+ new Receiver<SimpleFooProxy>() {
+ public void onSuccess(SimpleFooProxy response,
+ Set<SyncResult> syncResults) {
+ driver.edit(response,
+ factory.simpleFooRequest().persistAndReturnSelf(response).with(
+ driver.getPaths()));
+ // Set to an illegal value
+ editor.userName.setValue("");
+
+ driver.<SimpleFooProxy> flush().fire(
+ new Receiver<SimpleFooProxy>() {
+ @Override
+ public void onSuccess(SimpleFooProxy response,
+ Set<SyncResult> syncResults) {
+ fail("Expected errors");
+ }
+
+ @Override
+ public void onViolation(Set<Violation> errors) {
+ assertEquals(1, errors.size());
+ Violation v = errors.iterator().next();
+
+ driver.setViolations(errors);
+ assertEquals(1, editor.errors.size());
+ EditorError error = editor.errors.get(0);
+ assertEquals("userName", error.getAbsolutePath());
+ assertSame(editor.userName, error.getEditor());
+ assertTrue(error.getMessage().length() > 0);
+ assertEquals("userName", error.getPath());
+ assertSame(v, error.getUserData());
+ assertNull(error.getValue());
+ finishTest();
+ }
+ });
+ }
+ });
+ }
}