在复杂用例中使用 GWT 编辑器 [英] Using GWT Editors with a complex usecase

查看:22
本文介绍了在复杂用例中使用 GWT 编辑器的处理方法,对大家解决问题具有一定的参考价值,需要的朋友们下面随着小编来一起学习吧!

问题描述

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

I'm trying to create a page which is very similar to the Google Form creation page.

这就是我尝试使用 GWT MVP 框架(场所和活动)和编辑器对其进行建模的方式.

This is how I am attempting to model it using the GWT MVP framework (Places and Activities), and Editors.

CreateFormActivity(活动和演示者)

CreateFormView(用于视图的界面,带有嵌套的 Presenter 界面)

CreateFormView (interface for view, with nested Presenter interface)

CreateFormViewImpl(实现 CreateFormView 和 Editor

CreateFormViewImpl (implements CreateFormView and Editor< FormProxy >

CreateFormViewImpl 有以下子编辑器:

CreateFormViewImpl has the following sub-editors:

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

QuestionListEditor 实现 IsEditor<列表编辑器>

QuestionListEditor implements IsEditor< ListEditor< QuestionProxy, QuestionEditor>>

QuestionEditor 实现 Editor <问题代理>QuestionEditor有以下子编辑器:

QuestionEditor implements Editor < QuestionProxy> QuestionEditor has the following sub-editors:

  • TextBox questionTitle
  • 文本框帮助文本
  • ValueListBox 问题类型
  • 下面每种问题类型的可选子编辑器.

每种问题类型的编辑器:

An editor for each question type:

TextQuestionEditor

ParagraphTextQuestionEditor

MultipleChoiceQuestionEditor

CheckboxesQuestionEditor

ListQuestionEditor

ScaleQuestionEditor

GridQuestionEditor

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

<小时>

基于答案的实现

问题编辑器

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

The only issue left is that QuestionData subtype specific data isn't being displayed, or flushed. I think it has to do with the Editor setup I'm using.

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

For example, The value for prompt in the BooleanQuestionDataEditor is neither set nor flushed, and is null in the rpc payload.

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

My guess is: Since the QuestionDataEditor implements LeafValueEditor, the driver will not visit the subeditor, even though it has been attached.

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

Big thanks to anyone who can help!!!

推荐答案

从根本上说,您需要一个 CompositeEditor 来处理从编辑器层次结构中动态添加或删除对象的情况.ListEditorOptionalFieldEditor 适配器实现了 CompositeEditor.

Fundamentally, you want a CompositeEditor to handle cases where objects are dynamically added or removed from the Editor hierarchy. The ListEditor and OptionalFieldEditor adaptors implement CompositeEditor.

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

If the information required for the different types of questions is fundamentally orthogonal, then multiple OptionalFieldEditor could be used with different fields, one for each question type. This will work when you have only a few question types, but won't really scale well in the future.

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

A different approach, that will scale better would be to use a custom implementation of a CompositeEditor + LeafValueEditor that handles a polymorphic QuestionData type hierarchy. The type drop-down UI element would become 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. The newly-created QuestionData instance should be retained to implement LeafValueEditor.getValue(). The implementation of CompositeEditor.setValue() just creates the type-specific sub-Editor and calls EditorChain.attach().

FWIW、OptionalFieldEditor 可与 ListEditor 或任何其他编辑器类型一起使用.

FWIW, OptionalFieldEditor can be used with ListEditor or any other editor type.

这篇关于在复杂用例中使用 GWT 编辑器的文章就介绍到这了,希望我们推荐的答案对大家有所帮助,也希望大家多多支持IT屋!

查看全文
登录 关闭
扫码关注1秒登录
发送“验证码”获取 | 15天全站免登陆