GWT CompositeEditor - Dynamically switched editor not being added to chain properly

做~自己de王妃 提交于 2019-12-25 07:16:30

问题


I've got a value type Commission that's part of a larger editor graph:

public class Commission implements Serializable
{
    private CommissionType commissionType; // enum
    private Money value; // custom type, backed by BigDecimal
    private Integer multiplier;
    private Integer contractMonths;

    // ...
}

What I want to achieve is to initially only show the dropdown for the commissionType enum, and then select an appropriate sub-editor to edit the rest of the fields based on the value selected:

I have previously implemented multiple subtype editors (see question here) using an AbstractSubTypeEditor but this case is slightly different as I am not editing subclasses, they all edit the same basic Commission type, and for some reason the same method does not seem to work with multiple editors that edit the same concrete type.

I currently have two sub-editors (both implement Editor<Commission> and IsWidget via a custom interface IsCommissionEditorWidget) but they have different sub-editors themselves, as the Money can be in either pence or pounds, and the multiplier can represent days or months, among other changes.

CommissionEditor

I have looked at the similar problem in this question and tried to create a CompositeEditor<Commission, Commission, Editor<Commission>>.

This is what I've got so far (note the commented out parts are the hacked way of getting the functionality I want, by implementing LeafValueEditor<Commission> and manually calling setValue() and getValue() on amount, multiplier and contractMonths):

public class CommissionEditor extends Composite implements CompositeEditor<Commission, Commission, Editor<Commission>>, //LeafValueEditor<Commission>
{
    interface Binder extends UiBinder<HTMLPanel, CommissionEditor>
    {
    }

    private static Binder uiBinder = GWT.create(Binder.class);

    @UiField
    CommissionTypeEditor commissionType;

    @UiField
    Panel subEditorPanel;

    private EditorChain<Commission, Editor<Commission>> chain;
    private IsCommissionEditorWidget subEditor = null;
    private Commission value = new Commission();

    public CommissionEditor()
    {
        initWidget(uiBinder.createAndBindUi(this));

        commissionType.box.addValueChangeHandler(event -> setValue(new Commission(event.getValue())));
    }

    @Override
    public void setValue(Commission value)
    {
        Log.warn("CommissionEditor -> setValue(): value is " + value);

        chain.detach(subEditor);

        commissionType.setValue(value.getCommissionType());

        if(value.getCommissionType() != null)
        {
            switch(value.getCommissionType())
            {
                case UNIT_RATE:
                    Log.info("UNIT_RATE");
                    subEditor = new UnitRateCommissionEditor();
                    break;
                case STANDING_CHARGE:
                    Log.info("STANDING_CHARGE");
                    subEditor = new StandingChargeCommissionEditor();
                    break;
                case PER_MWH:
                    Log.info("PER_MWH");
                    // TODO
                    break;
                case SINGLE_PAYMENT:
                    Log.info("SINGLE_PAYMENT");
                    // TODO
                    break;
            }

            this.value = value;
            subEditorPanel.clear();
//            subEditor.setValue(value);
            subEditorPanel.add(subEditor);
            chain.attach(value, subEditor);
        }

    }

//    @Override
//    public Commission getValue()
//    {
//        if(subEditor != null)
//        {
//            return subEditor.getValue();
//        }
//        else
//        {
//        return value;
//        }
//    }

    @Override
    public void flush()
    {
        this.value = chain.getValue(subEditor);
    }

    @Override
    public void setEditorChain(EditorChain<Commission, Editor<Commission>> chain)
    {
        this.chain = chain;
    }

    @Override
    public Editor<Commission> createEditorForTraversal()
    {
        return subEditor;
    }

    @Override
    public String getPathElement(Editor<Commission> commissionEditor)
    {
        return "";
    }

    @Override
    public void onPropertyChange(String... strings)
    {

    }

    @Override
    public void setDelegate(EditorDelegate<Commission> editorDelegate)
    {

    }

}

This is the IsCommissionEditorWidget interface that defines the contract of each sub-editor also being able to be added to a panel:

public interface IsCommissionEditorWidget extends Editor<Commission>, IsWidget
{

}

UnitRateCommissionEditor

When the user selects CommissionType.UNIT_RATE, I want to add this to the editor chain to apply to the 3 remaining fields:

public class UnitRateCommissionEditor extends Composite implements IsCommissionEditorWidget
{
    interface Binder extends UiBinder<Widget, UnitRateCommissionEditor>
    {
    }

    private static Binder uiBinder = GWT.create(Binder.class);

    @UiField
    MoneyPenceBox amount;

    @UiField
    IntegerBox multiplier;

    @UiField
    IntegerBox contractMonths;

    public UnitRateCommissionEditor()
    {
        initWidget(uiBinder.createAndBindUi(this));
    }

//    @Override
//    public void setValue(Commission commission)
//    {
//        amount.setValue(commission.getAmount());
//        multiplier.setValue(commission.getMultiplier());
//        contractMonths.setValue(commission.getContractMonths());
//    }
//
//    @Override
//    public Commission getValue()
//    {
//        return new Commission(CommissionType.UNIT_RATE, amount.getValue(), multiplier.getValue(), contractMonths.getValue());
//    }
}

StandingChargeCommissionEditor

When CommissionType.STANDING_CHARGE is selected I want this one (The UiBinders are also a bit different, but the main difference is the MoneyPoundsBox instead of the MoneyPenceBox):

public class StandingChargeCommissionEditor extends Composite implements IsCommissionEditorWidget
{
    interface Binder extends UiBinder<Widget, StandingChargeCommissionEditor>
    {
    }

    private static Binder uiBinder = GWT.create(Binder.class);

    @UiField
    MoneyPoundsBox amount;

    @UiField
    IntegerBox multiplier;

    @UiField
    IntegerBox contractMonths;

    public StandingChargeCommissionEditor()
    {
        initWidget(uiBinder.createAndBindUi(this));
    }

//    @Override
//    public void setValue(Commission commission)
//    {
//        amount.setValue(commission.getAmount());
//        multiplier.setValue(commission.getMultiplier());
//        contractMonths.setValue(commission.getContractMonths());
//    }
//
//    @Override
//    public Commission getValue()
//    {
//        return new Commission(CommissionType.STANDING_CHARGE, amount.getValue(), multiplier.getValue(), contractMonths.getValue());
//    }
}

Currently the flush() of the parent type (the type being edited that contains the Commission) returns a Commission with an undefined amount, multiplier and contractMonths. The only way I can get those values to be passed in and out is to manually code it in (the commented code).

  • Is my sub-editor being attached to the EditorChain correctly?

Edit 1: Solution Proposed

I decided to create a new intermediate class that would wrap each subtype commission separately, using AbstractSubTypeEditor like I did in this question.

CommissionEditor

Still a CompositeEditor, but it only ever has one or zero sub-editors, which will only ever be a CommissionSubtypeEditor:

public class CommissionEditor extends Composite implements CompositeEditor<Commission, Commission, CommissionSubtypeEditor>, LeafValueEditor<Commission>
{
    interface Binder extends UiBinder<HTMLPanel, CommissionEditor>
    {
    }

    private static Binder uiBinder = GWT.create(Binder.class);

    @UiField
    @Ignore
    CommissionTypeEditor commissionType;

    @UiField
    Panel subEditorPanel;

    private EditorChain<Commission, CommissionSubtypeEditor> chain;

    @Ignore
    @Path("")
    CommissionSubtypeEditor subEditor = new CommissionSubtypeEditor();

    private Commission value;

    public CommissionEditor()
    {
        initWidget(uiBinder.createAndBindUi(this));

        commissionType.box.addValueChangeHandler(event -> setValue(new Commission(event.getValue())));
    }

    @Override
    public void setValue(Commission value)
    {
        Log.warn("CommissionEditor -> setValue(): value is " + value);

        commissionType.setValue(value.getCommissionType());

        if(value.getCommissionType() != null)
        {
            this.value = value;
            subEditorPanel.clear();
            subEditorPanel.add(subEditor);
            chain.attach(value, subEditor);
        }

    }

    @Override
    public Commission getValue()
    {
        Log.info("CommissionEditor -> getValue: " + value);
        return value;
    }

    @Override
    public void flush()
    {
        chain.getValue(subEditor);
    }

    @Override
    public void setEditorChain(EditorChain<Commission, CommissionSubtypeEditor> chain)
    {
        this.chain = chain;
    }

    @Override
    public CommissionSubtypeEditor createEditorForTraversal()
    {
        return subEditor;
    }

    @Override
    public String getPathElement(CommissionSubtypeEditor commissionEditor)
    {
        return "";
    }

    @Override
    public void onPropertyChange(String... strings)
    {

    }

    @Override
    public void setDelegate(EditorDelegate<Commission> editorDelegate)
    {

    }

}

AbstractSubTypeEditor

Credit goes to Florent Bayle for this class. I didn't think I could use it without the subtypes being polymorphic but it seems to work great. Essentially allows wrapping of sub-editors as will be show in CommissionSubtypeEditor next.

public abstract class AbstractSubTypeEditor<T, C extends T, E extends Editor<C>> implements CompositeEditor<T, C, E>, LeafValueEditor<T>
{
    private EditorChain<C, E> chain;
    private T currentValue;
    private final E subEditor;

    public AbstractSubTypeEditor(E subEditor)
    {
        this.subEditor = subEditor;
    }

    @Override
    public E createEditorForTraversal()
    {
        return subEditor;
    }

    @Override
    public void flush()
    {
        currentValue = chain.getValue(subEditor);
    }

    @Override
    public String getPathElement(E subEditor)
    {
        return "";
    }

    @Override
    public T getValue()
    {
        return currentValue;
    }

    @Override
    public void onPropertyChange(String... paths)
    {
    }

    @Override
    public void setDelegate(EditorDelegate<T> delegate)
    {
    }

    @Override
    public void setEditorChain(EditorChain<C, E> chain)
    {
        this.chain = chain;
    }

    public void setValue(T value, boolean instanceOf)
    {
        if(currentValue != null && value == null)
        {
            chain.detach(subEditor);
        }
        currentValue = value;
        if(value != null && instanceOf)
        {
            chain.attach((C) value, subEditor);
        }
    }
}

CommissionSubtypeEditor

public class CommissionSubtypeEditor extends Composite implements Editor<Commission>
{

    interface Binder extends UiBinder<HTMLPanel, CommissionSubtypeEditor>
    {
    }

    private static Binder uiBinder = GWT.create(Binder.class);

    @UiField
    Panel subEditorPanel;

    @Ignore
    final UnitRateCommissionEditor unitRateCommissionEditor = new UnitRateCommissionEditor();

    @Path("")
    final AbstractSubTypeEditor<Commission, Commission, UnitRateCommissionEditor> unitRateWrapper = new AbstractSubTypeEditor<Commission, Commission,
            UnitRateCommissionEditor>(unitRateCommissionEditor)
    {
        @Override
        public void setValue(Commission value)
        {
            if(value.getCommissionType() == CommissionType.UNIT_RATE)
            {
                Log.info("unitRateWrapper setValue");
                setValue(value, true);

                subEditorPanel.clear();
                subEditorPanel.add(unitRateCommissionEditor);

            }
        }
    };

    @Ignore
    final StandingChargeCommissionEditor standingChargeCommissionEditor = new StandingChargeCommissionEditor();

    @Path("")
    final AbstractSubTypeEditor<Commission, Commission, StandingChargeCommissionEditor> standingChargeWrapper = new AbstractSubTypeEditor<Commission,
            Commission, StandingChargeCommissionEditor>(standingChargeCommissionEditor)
    {
        @Override
        public void setValue(Commission value)
        {
            if(value.getCommissionType() == CommissionType.STANDING_CHARGE)
            {
                Log.info("standingChargeWrapper setValue");
                setValue(value, true);

                subEditorPanel.clear();
                subEditorPanel.add(standingChargeCommissionEditor);

            }
        }
    };

    public CommissionSubtypeEditor()
    {
        initWidget(uiBinder.createAndBindUi(this));
    }

}

UnitRateCommissionEditor and StandingChargeCommissionEditor

Both simple and implement Editor<Commission>:

public class UnitRateCommissionEditor extends Composite implements Editor<Commission>
{
    interface Binder extends UiBinder<Widget, UnitRateCommissionEditor>
    {
    }

    private static Binder uiBinder = GWT.create(Binder.class);

    @UiField
    MoneyPenceBox amount;

    @UiField
    IntegerBox multiplier;

    @UiField
    IntegerBox contractMonths;

    public UnitRateCommissionEditor()
    {
        initWidget(uiBinder.createAndBindUi(this));
    }

}

almost there...

public class StandingChargeCommissionEditor extends Composite implements Editor<Commission>
{
    interface Binder extends UiBinder<Widget, StandingChargeCommissionEditor>
    {
    }

    private static Binder uiBinder = GWT.create(Binder.class);

    @UiField
    MoneyPoundsBox amount;

    @UiField
    IntegerBox multiplier;

    @UiField
    IntegerBox contractMonths;

    public StandingChargeCommissionEditor()
    {
        initWidget(uiBinder.createAndBindUi(this));
    }

}

This does work and is similar to something I tried very early on when I had the AbstractSubtypeEditors in the CompositeEditor itself. The problem there I believe was that the editor could not call setValue() on itself. Am I right?

Comments, criticisms and advice greatly appreciated.


回答1:


Your composite editor is declared as CompositeEditor<Commission, Commission, Editor<Commission>>, which means that the chain will be expected to provide some arbitrary Editor<Commission>. This is a problem, since the compiler inspects the type Editor<Commission> and sees no sub-editors, and no other editor interfaces (LeafValueEditor, ValueAwareEditor, etc), so binds nothing to it.

You mention that you have two different Editor<Commission> subtypes - but the codegenerator can't know this ahead of time, and it wouldn't make sense to inspect every possible subtype and build any possible wiring that could fit. For better or worse, the editor framework is intended to be static - declare everything up-front, and then the generator will create only exactly what is needed.

Two options that I see:

  • either create two different CompositeEditors, one for each sub-editor type, and just make sure you only attach one at a time, or
  • extract a common supertype/interface from both editors, that have consistently named and typed methods which return the same editor types (never returning simply Editor<Foo> for the same reason as above). This likely will be harder to do, but its possible that you could wrap things up with ValueAwareEditor and use setValue and flush() to paper over the details.


来源:https://stackoverflow.com/questions/38636332/gwt-compositeeditor-dynamically-switched-editor-not-being-added-to-chain-prope

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