Using GWT Editors with a complex usecase

落花浮王杯 提交于 2019-11-27 11:30:37

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.

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.

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 can be used with ListEditor or any other editor type.

We implemented similar approach (see accepted answer) and it works for us like this.

Since driver is initially unaware of simple editor paths that might be used by sub-editors, every sub-editor has own driver:

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

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

Then we use the following editor adapter that would allow any sub-editor that implements RequestFactoryEditor to be used. This is our workaround to support polimorphism in editors:

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

Our example sub-editor:

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;
    }
...
}

Our usage example:

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

Would be great to hear your comments.

Regarding your question why subtype specific data isn't displayed or flushed:

My scenario is a little bit different but I made the following observation:

GWT editor databinding does not work as one would expect with abstract editors in the editor hierarchy. The subEditor declared in your QuestionDataEditor is of type QuestionBaseDataEditor and this is fully abstract type (an interface). When looking for fields/sub editors to populate with data/flush GWT takes all the fields declared in this type. Since QuestionBaseDataEditor has no sub editors declared nothing is displayed/flushed. From debugging I found out that is happens due to GWT using a generated EditorDelegate for that abstract type rather than the EditorDelegate for the concrete subtype present at that moment.

In my case all the concrete sub editors had the same types of leaf value editors (I had two different concrete editors one to display and one to edit the same bean type) so I could do something like this to work around this limitation:

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 :)
}

Now GWT finds the subeditors in the abstract base class and in both cases I get the corresponding fields name and description populated and flushed.

Unfortunately this approach is not suitable when the concrete subeditors have different values in your bean structure to edit :(

I think this is a bug of the editors framework GWT code generation, that can only be solved by the GWT development team.

Isn't the fundamental problem that the binding happens at compile time so will only bind to QuestionDataProxy so won't have sub-type specific bindings? The CompositeEditor javadoc says "An interface that indicates that a given Editor is composed of an unknown number of sub-Editors all of the same type" so that rules this usage out?

At my current job I'm pushing to avoid polymorphism altogether as the RDBMS doesn't support it either. Sadly we do have some at the moment so I'm experimenting with a dummy wrapper class that exposes all the sub-types with specific getters so the compiler has something to work on. Not pretty though.

Have you seen this post: http://markmail.org/message/u2cff3mfbiboeejr this seems along the right lines.

I'm a bit worried about code bloat though.

Hope that makes some sort of sense!

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!