我正在尝试创建一个与Google表单创建页面非常相似的页面。
这就是我尝试使用GWT MVP框架(地点和活动)和编辑器对其进行建模的方式。
CreateFormActivity (活动和演示者)
CreateFormView (视图界面,带有嵌套的Presenter界面)
CreateFormViewImpl (实现CreateFormView和Editor< FormProxy>
CreateFormViewImpl具有以下子编辑器:
QuestionListEditor 实现IsEditor< ListEditor< QuestionProxy,QuestionEditor>>
QuestionEditor 实现编辑器< QuestionProxy> QuestionEditor有以下编辑:
每种问题类型的编辑器:
TextQuestionEditor
ParagraphTextQuestionEditor
MultipleChoiceQuestionEditor
CheckboxesQuestionEditor
ListQuestionEditor
ScaleQuestionEditor
GridQuestionEditor
问题编辑
public class QuestionDataEditor extends Composite implements
CompositeEditor<QuestionDataProxy, QuestionDataProxy, Editor<QuestionDataProxy>>,
LeafValueEditor<QuestionDataProxy>, HasRequestContext<QuestionDataProxy> {
interface Binder extends UiBinder<Widget, QuestionDataEditor> {}
private CompositeEditor.EditorChain<QuestionDataProxy, Editor<QuestionDataProxy>> chain;
private QuestionBaseDataEditor subEditor = null;
private QuestionDataProxy currentValue = null;
@UiField
SimplePanel container;
@UiField(provided = true)
@Path("dataType")
ValueListBox<QuestionType> dataType = new ValueListBox<QuestionType>(new Renderer<QuestionType>() {
@Override
public String render(final QuestionType object) {
return object == null ? "" : object.toString();
}
@Override
public void render(final QuestionType object, final Appendable appendable) throws IOException {
if (object != null) {
appendable.append(object.toString());
}
}
});
private RequestContext ctx;
public QuestionDataEditor() {
initWidget(GWT.<Binder> create(Binder.class).createAndBindUi(this));
dataType.setValue(QuestionType.BooleanQuestionType, true);
dataType.setAcceptableValues(Arrays.asList(QuestionType.values()));
/*
* The type drop-down UI element is an implementation detail of the
* CompositeEditor. When a question type is selected, the editor will
* call EditorChain.attach() with an instance of a QuestionData subtype
* and the type-specific sub-Editor.
*/
dataType.addValueChangeHandler(new ValueChangeHandler<QuestionType>() {
@Override
public void onValueChange(final ValueChangeEvent<QuestionType> event) {
QuestionDataProxy value;
switch (event.getValue()) {
case MultiChoiceQuestionData:
value = ctx.create(QuestionMultiChoiceDataProxy.class);
setValue(value);
break;
case BooleanQuestionData:
default:
final QuestionNumberDataProxy value2 = ctx.create(BooleanQuestionDataProxy.class);
value2.setPrompt("this value doesn't show up");
setValue(value2);
break;
}
}
});
}
/*
* The only thing that calls createEditorForTraversal() is the PathCollector
* which is used by RequestFactoryEditorDriver.getPaths().
*
* My recommendation is to always return a trivial instance of your question
* type editor and know that you may have to amend the value returned by
* getPaths()
*/
@Override
public Editor<QuestionDataProxy> createEditorForTraversal() {
return new QuestionNumberDataEditor();
}
@Override
public void flush() {
//XXX this doesn't work, no data is returned
currentValue = chain.getValue(subEditor);
}
/**
* Returns an empty string because there is only ever one sub-editor used.
*/
@Override
public String getPathElement(final Editor<QuestionDataProxy> subEditor) {
return "";
}
@Override
public QuestionDataProxy getValue() {
return currentValue;
}
@Override
public void onPropertyChange(final String... paths) {
}
@Override
public void setDelegate(final EditorDelegate<QuestionDataProxy> delegate) {
}
@Override
public void setEditorChain(final EditorChain<QuestionDataProxy, Editor<QuestionDataProxy>> chain) {
this.chain = chain;
}
@Override
public void setRequestContext(final RequestContext ctx) {
this.ctx = ctx;
}
/*
* The implementation of CompositeEditor.setValue() just creates the
* type-specific sub-Editor and calls EditorChain.attach().
*/
@Override
public void setValue(final QuestionDataProxy value) {
// if (currentValue != null && value == null) {
chain.detach(subEditor);
// }
QuestionType type = null;
if (value instanceof QuestionMultiChoiceDataProxy) {
if (((QuestionMultiChoiceDataProxy) value).getCustomList() == null) {
((QuestionMultiChoiceDataProxy) value).setCustomList(new ArrayList<CustomListItemProxy>());
}
type = QuestionType.CustomList;
subEditor = new QuestionMultipleChoiceDataEditor();
} else {
type = QuestionType.BooleanQuestionType;
subEditor = new BooleanQuestionDataEditor();
}
subEditor.setRequestContext(ctx);
currentValue = value;
container.clear();
if (value != null) {
dataType.setValue(type, false);
container.add(subEditor);
chain.attach(value, subEditor);
}
}
}
问题基础数据编辑器
public interface QuestionBaseDataEditor extends HasRequestContext<QuestionDataProxy>, IsWidget {
}
示例子类型
public class BooleanQuestionDataEditor extends Composite implements QuestionBaseDataEditor {
interface Binder extends UiBinder<Widget, BooleanQuestionDataEditor> {}
@Path("prompt")
@UiField
TextBox prompt = new TextBox();
public QuestionNumberDataEditor() {
initWidget(GWT.<Binder> create(Binder.class).createAndBindUi(this));
}
@Override
public void setRequestContext(final RequestContext ctx) {
}
}
唯一的问题是没有显示或刷新QuestionData子类型特定数据。我认为这与我正在使用的编辑器设置有关。
例如,BooleanQuestionDataEditor
中的提示值既未设置也未刷新,并且在rpc有效内容中为空。
我的猜测是:由于QuestionDataEditor实现了LeafValueEditor,因此驱动程序不会访问子编辑器,即使它已被附加。
非常感谢给任何能提供帮助的人!!!
答案 0 :(得分:9)
从根本上说,您希望CompositeEditor
处理从编辑器层次结构中动态添加或删除对象的情况。 ListEditor
和OptionalFieldEditor
适配器实现CompositeEditor
。
如果不同类型问题所需的信息基本上是正交的,那么多个OptionalFieldEditor
可以用于不同的字段,每个问题类型一个。当您只有几个问题类型时,这将起作用,但将来不会很好地扩展。
更好地扩展的另一种方法是使用处理多态CompositeEditor + LeafValueEditor
类型层次结构的QuestionData
的自定义实现。类型下拉UI元素将成为CompositeEditor
的实现细节。选择问题类型后,编辑器将使用EditorChain.attach()
子类型的实例和特定于类型的子编辑器调用QuestionData
。应保留新创建的QuestionData
实例以实现LeafValueEditor.getValue()
。 CompositeEditor.setValue()
的实现只是创建特定于类型的子编辑器并调用EditorChain.attach()
。
FWIW,OptionalFieldEditor
可与ListEditor
或任何其他编辑器类型一起使用。
答案 1 :(得分:2)
我们实施了类似的方法(参见接受的答案),它对我们这样有用。
由于驱动程序最初并不知道子编辑器可能使用的简单编辑器路径,因此每个子编辑器都有自己的驱动程序:
public interface CreatesEditorDriver<T> {
RequestFactoryEditorDriver<T, ? extends Editor<T>> createDriver();
}
public interface RequestFactoryEditor<T> extends CreatesEditorDriver<T>, Editor<T> {
}
然后我们使用以下编辑器适配器,允许使用任何实现RequestFactoryEditor的子编辑器。这是我们在编辑器中支持polimorphism的解决方法:
public static class DynamicEditor<T>
implements LeafValueEditor<T>, CompositeEditor<T, T, RequestFactoryEditor<T>>, HasRequestContext<T> {
private RequestFactoryEditorDriver<T, ? extends Editor<T>> subdriver;
private RequestFactoryEditor<T> subeditor;
private T value;
private EditorDelegate<T> delegate;
private RequestContext ctx;
public static <T> DynamicEditor<T> of(RequestFactoryEditor<T> subeditor) {
return new DynamicEditor<T>(subeditor);
}
protected DynamicEditor(RequestFactoryEditor<T> subeditor) {
this.subeditor = subeditor;
}
@Override
public void setValue(T value) {
this.value = value;
subdriver = null;
if (null != value) {
RequestFactoryEditorDriver<T, ? extends Editor<T>> newSubdriver = subeditor.createDriver();
if (null != ctx) {
newSubdriver.edit(value, ctx);
} else {
newSubdriver.display(value);
}
subdriver = newSubdriver;
}
}
@Override
public T getValue() {
return value;
}
@Override
public void flush() {
if (null != subdriver) {
subdriver.flush();
}
}
@Override
public void onPropertyChange(String... paths) {
}
@Override
public void setDelegate(EditorDelegate<T> delegate) {
this.delegate = delegate;
}
@Override
public RequestFactoryEditor<T> createEditorForTraversal() {
return subeditor;
}
@Override
public String getPathElement(RequestFactoryEditor<T> subEditor) {
return delegate.getPath();
}
@Override
public void setEditorChain(EditorChain<T, RequestFactoryEditor<T>> chain) {
}
@Override
public void setRequestContext(RequestContext ctx) {
this.ctx = ctx;
}
}
我们的示例子编辑器:
public static class VirtualProductEditor implements RequestFactoryEditor<ProductProxy> {
interface Driver extends RequestFactoryEditorDriver<ProductProxy, VirtualProductEditor> {}
private static final Driver driver = GWT.create(Driver.class);
public Driver createDriver() {
driver.initialize(this);
return driver;
}
...
}
我们的使用示例:
@Path("")
DynamicEditor<ProductProxy> productDetailsEditor;
...
public void setProductType(ProductType type){
if (ProductType.VIRTUAL==type){
productDetailsEditor = DynamicEditor.of(new VirtualProductEditor());
} else if (ProductType.PHYSICAL==type){
productDetailsEditor = DynamicEditor.of(new PhysicalProductEditor());
}
}
很高兴听到您的意见。
答案 2 :(得分:1)
关于您的问题,为什么不显示或刷新子类型特定数据:
我的情况略有不同,但我做了以下观察:
GWT编辑器数据绑定无法像编辑器层次结构中的抽象编辑器那样工作。在QuestionDataEditor中声明的subEditor是QuestionBaseDataEditor类型,这是完全抽象类型(接口)。当查找字段/子编辑器以填充数据/ flush时,GWT将获取此类型中声明的所有字段。由于QuestionBaseDataEditor没有声明子编辑器,因此不会显示/刷新任何内容。从调试中我发现,由于GWT使用生成的EditorDelegate用于该抽象类型,而不是当时存在的具体子类型的EditorDelegate。
在我的例子中,所有具体的子编辑器都有相同类型的叶值编辑器(我有两个不同的具体编辑器,一个用于显示,一个用于编辑相同的bean类型),所以我可以做这样的事情来解决这个限制:
interface MyAbstractEditor1 extends Editor<MyBean>
{
LeafValueEditor<String> description();
}
// or as an alternative
abstract class MyAbstractEditor2 implements Editor<MyBean>
{
@UiField protected LeafValueEditor<String> name;
}
class MyConcreteEditor extends MyAbstractEditor2 implements MyAbstractEditor1
{
@UiField TextBox description;
public LeafValueEditor<String> description()
{
return description;
}
// super.name is bound to a TextBox using UiBinder :)
}
现在,GWT在抽象基类中找到子编辑器,在这两种情况下,我都会获得相应的字段名称和描述,并填充和刷新。
不幸的是,当具体的子编辑器在你的bean结构中有不同的值来编辑时,这种方法是不合适的:(
我认为这是编辑器框架GWT代码生成的一个错误,只能由GWT开发团队解决。
答案 3 :(得分:0)
绑定是否在编译时发生的基本问题因此只会绑定到QuestionDataProxy,因此不会有子类型特定的绑定? CompositeEditor javadoc说“一个接口,指示给定的编辑器由未知数量的所有相同类型的子编辑器组成”,以便规定这种用法吗?
在我目前的工作中,我正在努力避免多态性,因为RDBMS也不支持它。遗憾的是,我们目前确实有一些,所以我正在尝试一个虚拟包装类,它使用特定的getter公开所有子类型,因此编译器可以处理。虽然不漂亮。
您是否看过这篇文章:http://markmail.org/message/u2cff3mfbiboeejr这似乎沿着正确的方向发展。
我有点担心代码膨胀。
希望有某种意义!