使用具有复杂用例的GET编辑器

时间:2011-08-12 17:09:29

标签: gwt requestfactory gwt-editors

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

enter image description here

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

CreateFormActivity (活动和演示者)

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

CreateFormViewImpl (实现CreateFormView和Editor< FormProxy>

CreateFormViewImpl具有以下子编辑器:

  • TextBox标题
  • TextBox说明
  • QuestionListEditor questionList

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

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

  • TextBox questionTitle
  • TextBox helpText
  • ValueListBox questionType
  • 以下每种问题类型的可选子编辑器。

每种问题类型的编辑器:

TextQuestionEditor

ParagraphTextQuestionEditor

MultipleChoiceQuestionEditor

CheckboxesQuestionEditor

ListQuestionEditor

ScaleQuestionEditor

GridQuestionEditor


具体问题:

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

  6. 基于答案的实施

    问题编辑

    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,因此驱动程序不会访问子编辑器,即使它已被附加。

    非常感谢给任何能提供帮助的人!!!

4 个答案:

答案 0 :(得分:9)

从根本上说,您希望CompositeEditor处理从编辑器层次结构中动态添加或删除对象的情况。 ListEditorOptionalFieldEditor适配器实现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这似乎沿着正确的方向发展。

我有点担心代码膨胀。

希望有某种意义!