12

我正在尝试创建一个与 Google 表单创建页面非常相似的页面。

在此处输入图像描述

这就是我尝试使用 GWT MVP 框架(地点和活动)和编辑器对其建模的方式。

CreateFormActivity(活动和演示者)

CreateFormView(视图接口,带有嵌套的 Presenter 接口)

CreateFormViewImpl(实现 CreateFormView 和 Editor<FormProxy>

CreateFormViewImpl 有以下子编辑器:

  • 文本框标题
  • 文本框说明
  • QuestionListEditor questionList

QuestionListEditor实现 IsEditor< ListEditor< QuestionProxy, QuestionEditor>>

QuestionEditor实现 Editor < QuestionProxy> QuestionEditor 有以下子编辑器:

  • 文本框问题标题
  • 文本框帮助文本
  • 值列表框问题类型
  • 下面每个问题类型的可选子编辑器。

每种问题类型的编辑器:

文本问题编辑器

段落文本问题编辑器

多项选择题编辑器

复选框问题编辑器

列表问题编辑器

ScaleQuestionEditor

网格问题编辑器


具体问题:

  1. 从表单中添加/删除问题的正确方法是什么。 (见后续问题
  2. 我应该如何为每种问题类型创建编辑器?我试图听 questionType 值的变化,我不知道之后该怎么做。(由 BobV 回答)
  3. 每个问题类型特定的编辑器都应该用 optionalFieldEditor 包装吗?因为一次只能使用其中一个。(由 BobV 回答)
  4. 如何最好地管理在对象层次结构深处创建/删除对象。例如)指定问题编号 3 的答案,该问题属于选择题类型。(见后续问题
  5. 可以使用 OptionalFieldEditor 编辑器来包装 ListEditor 吗?(由 BobV 回答)

基于Answer的实现

问题编辑器

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 子类型特定数据。我认为这与我正在使用的编辑器设置有关。

例如,prompt 中的BooleanQuestionDataEditor值既未设置也未刷新,并且在 rpc 有效负载中为 null。

我的猜测是:由于 QuestionDataEditor 实现了 LeafValueEditor,驱动程序不会访问子编辑器,即使它已附加。

非常感谢任何可以提供帮助的人!!!

4

4 回答 4

9

从根本上说,您希望CompositeEditor处理从编辑器层次结构中动态添加或删除对象的情况。和ListEditor适配器OptionalFieldEditor实现CompositeEditor.

如果不同类型的问题所需的信息基本上是正交的,那么多个OptionalFieldEditor可以用于不同的字段,每个问题类型一个。当您只有几个问题类型时,这将起作用,但将来不会很好地扩展。

另一种可以更好地扩展的方法是使用CompositeEditor + LeafValueEditor处理多态QuestionData类型层次结构的自定义实现。类型下拉 UI 元素将成为CompositeEditor. 选择问题类型后,编辑器将调用子类型EditorChain.attach()的实例QuestionData和特定于类型的子编辑器。QuestionData应该保留新创建的实例来实现LeafValueEditor.getValue()。的实现CompositeEditor.setValue()只创建特定类型的子编辑器并调用EditorChain.attach().

FWIW,OptionalFieldEditor可以与ListEditor或任何其他编辑器类型一起使用。

于 2011-08-15T13:15:18.203 回答
2

我们实施了类似的方法(见接受的答案),它对我们有用。

由于驱动程序最初不知道子编辑器可能使用的简单编辑器路径,因此每个子编辑器都有自己的驱动程序:

public interface CreatesEditorDriver<T> {
    RequestFactoryEditorDriver<T, ? extends Editor<T>> createDriver();
}

public interface RequestFactoryEditor<T> extends CreatesEditorDriver<T>, Editor<T> {
}

然后我们使用以下编辑器适配器,它允许使用任何实现 RequestFactoryEditor 的子编辑器。这是我们在编辑器中支持多态的解决方法:

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());
            }
        }

很高兴听到你的评论。

于 2012-03-26T16:34:58.520 回答
1

关于您为什么不显示或刷新子类型特定数据的问题:

我的情况有点不同,但我做了以下观察:

GWT 编辑器数据绑定不像编辑器层次结构中的抽象编辑器所期望的那样工作。在 QuestionDataEditor 中声明的 subEditor 是 QuestionBaseDataEditor 类型,这是完全抽象的类型(接口)。当寻找字段/子编辑器来填充数据/刷新时,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 开发团队解决。

于 2011-09-23T09:50:49.033 回答
0

绑定不是在编译时发生的基本问题,所以只会绑定到 QuestionDataProxy 所以不会有子类型特定的绑定?CompositeEditor javadoc 说“一个接口,指示给定编辑器由未知数量的相同类型的子编辑器组成”,因此排除了这种用法?

在我目前的工作中,我正在努力完全避免多态性,因为 RDBMS 也不支持它。遗憾的是,我们目前确实有一些,所以我正在试验一个虚拟包装类,它使用特定的 getter 公开所有子类型,以便编译器可以处理一些事情。虽然不漂亮。

你看过这篇文章吗:http: //markmail.org/message/u2cff3mfbiboeejr这似乎是正确的。

不过,我有点担心代码膨胀。

希望这有某种意义!

于 2012-05-19T10:43:10.733 回答