GWT CompositeEditor - динамически переключаемый редактор не добавляется в цепочку должным образом

У меня есть тип значения Commission, который является частью более крупного графа редактора:

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

    // ...
}

Чего я хочу добиться, так это сначала показать раскрывающийся список только для перечисления commissionType, а затем выбрать соответствующий вспомогательный редактор для редактирования остальных полей на основе выбранного значения:

CommissionType.UNIT_RATE selected

Ранее я реализовал несколько редакторов подтипов (см. вопрос здесь) с использованием AbstractSubTypeEditor, но этот случай немного отличается, поскольку я не редактирую подклассы, все они редактируют один и тот же базовый тип Commission, и по какой-то причине один и тот же метод не работает с несколькими редакторами, которые редактируют один и тот же конкретный тип. .

В настоящее время у меня есть два подредактора (оба реализуют Editor<Commission> и IsWidget через настраиваемый интерфейс IsCommissionEditorWidget), но сами они имеют разные подредакторы, так как Money может быть в пенсах или фунтах, а множитель может представлять дни или месяцы, помимо прочего. изменения.

КомиссияРедактор

Я рассмотрел аналогичную проблему в этом вопросе и попытался создать CompositeEditor<Commission, Commission, Editor<Commission>>.

Это то, что у меня есть до сих пор (обратите внимание, что закомментированные части — это взломанный способ получения желаемой функциональности путем реализации LeafValueEditor<Commission> и ручного вызова setValue() и getValue() для amount, multiplier и 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)
    {

    }

}

Это интерфейс IsCommissionEditorWidget, который определяет контракт каждого субредактора, который также может быть добавлен на панель:

public interface IsCommissionEditorWidget extends Editor<Commission>, IsWidget
{

}

UnitRateCommissionEditor

Когда пользователь выбирает CommissionType.UNIT_RATE, я хочу добавить это в цепочку редактора, чтобы применить к 3 оставшимся полям:

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

Постоянная комиссияРедактор

Когда выбрано CommissionType.STANDING_CHARGE, я хочу это (UiBinders также немного отличаются, но основное отличие заключается в MoneyPoundsBox вместо 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());
//    }
}

В настоящее время flush() родительского типа (редактируемый тип, содержащий Commission) возвращает комиссию с неопределенными amount, multiplier и contractMonths. Единственный способ, которым я могу получить эти значения для передачи и вывода, — это вручную закодировать их (закомментированный код).

  • Правильно ли мой помощник прикреплен к EditorChain?

Редактировать 1: Предлагаемое решение

Я решил создать новый промежуточный класс, который будет обертывать каждую комиссию подтипа отдельно, -type/13358890#13358890">используя AbstractSubTypeEditor как в этом вопросе.

КомиссияРедактор

По-прежнему CompositeEditor, но у него всегда есть только один или ноль субредакторов, которые всегда будут только 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)
    {

    }

}

АннотацияSubTypeEditor

Кредит принадлежит Florent Bayle для этого класса. Я не думал, что смогу использовать его без полиморфных подтипов, но, похоже, он отлично работает. По сути, позволяет обертывать подредакторы, как будет показано в CommissionSubtypeEditor далее.

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

КомиссияПодтипРедактор

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 и StandingChargeCommissionEditor

И просто, и реализуемо 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));
    }

}

почти готово...

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

}

Это действительно работает и похоже на то, что я пробовал очень рано, когда у меня были AbstractSubtypeEditor в самом CompositeEditor. Я считаю, что проблема заключалась в том, что редактор не мог вызвать setValue() сам по себе. Я прав?

Комментарии, критика и советы приветствуются.


person slugmandrew    schedule 28.07.2016    source источник
comment
Где вы инициализируете editorDriver. Это очень распространенная проблема. Если вызов editorDriver для инициализации не имеет доступа к подчиненному редактору (не может его видеть), он не будет сброшен, когда вы сбрасываете editorDriver.   -  person Chris Hinshaw    schedule 29.07.2016
comment
Это просто в Presenter/View в том же пакете, что и редакторы. Я не думаю, что проблема в том, чтобы увидеть его — думаю, что он должен увидеть это, как только прикрепится к цепочке, вот в чем проблема. Возможно, я не могу одновременно редактировать один и тот же объект с помощью родительского и дочернего редакторов, поэтому мне нужно создать оболочку для раскрывающегося списка... можно попробовать.   -  person slugmandrew    schedule 29.07.2016
comment
Я делаю то же самое для обработки различных типов рекламных объектов, но, как упомянул @colin-alworth, я использую ValueAwareEditor в абстрактном классе. Вам нужно обрабатывать сброс и установку, но это небольшой компромисс для простоты возможности динамического добавления редактора.   -  person Chris Hinshaw    schedule 29.07.2016
comment
Я бы реализовал что-то вроде LeafValueEditor‹Commission› и HasSelectionHandlers‹Commission› с соответствующей реализацией getValue и setValue.   -  person Euclides    schedule 05.08.2016
comment
Я сделал это по-другому, ребята, и был бы признателен за отзывы, если вы хотите внести свой вклад :)   -  person slugmandrew    schedule 10.08.2016


Ответы (1)


Ваш составной редактор объявлен как CompositeEditor<Commission, Commission, Editor<Commission>>, что означает, что ожидается, что цепочка предоставит несколько произвольных Editor<Commission>. Это проблема, так как компилятор проверяет тип Editor<Commission> и не видит ни подредакторов, ни других интерфейсов редактора (LeafValueEditor, ValueAwareEditor и т. д.), поэтому ничего к нему не привязывает.

Вы упомянули, что у вас есть два разных подтипа Editor<Commission>, но генератор кодов не может знать об этом заранее, и не имеет смысла проверять каждый возможный подтип и строить любую возможную проводку, которая может подойти. К лучшему или к худшему, структура редактора должна быть статичной — объявите все заранее, и тогда генератор создаст только то, что нужно.

Два варианта которые я вижу:

  • либо создайте два разных CompositeEditors, по одному для каждого типа вспомогательного редактора, и просто убедитесь, что вы подключаете только один за раз, или
  • извлеките общий супертип/интерфейс из обоих редакторов, которые имеют последовательно названные и типизированные методы, которые возвращают одни и те же типы редактора (никогда не возвращая просто Editor<Foo> по той же причине, что и выше). Это, вероятно, будет сложнее сделать, но, возможно, вы могли бы завершить работу с помощью ValueAwareEditor и использовать setValue и flush() для уточнения деталей.
person Colin Alworth    schedule 29.07.2016
comment
Большое спасибо за ответ. Я объявил CompositeEditor<Commission, Commission, Editor<Commission>>, потому что копировал другой вопрос, в котором было CompositeEditor<QuestionDataProxy, QuestionDataProxy, Editor<QuestionDataProxy>>, но я думаю, это объясняет, почему он говорит, что его метод flush() не работает. Если я изменю интерфейс IsCommissionEditor на базовый класс, а затем объявлю CompositeEditor<Commission, Commission, BaseCommissionEditorWidget>, должно ли это сработать? Я попытаюсь... - person slugmandrew; 02.08.2016
comment
Когда вы попробуете, если возникнут другие проблемы, обновите вопрос (возможно, под ----), указав тело BaseCommissionEditorWidget и любые подклассы. - person Colin Alworth; 02.08.2016
comment
Я пытался сделать так, чтобы все мои подтипы реализовывали BaseCommissionEditorWidget, но по какой-то причине у меня все еще возникали те же проблемы, связанные с цепочкой. Однако ваше объяснение того, что генератору кода нужен конкретный класс, заставило меня понять, что я могу реализовать логику внутри нового конкретного класса (CommissionSubtypeEditor)... Я опубликовал свое решение выше и надеюсь, что оно не слишком подробное/неуклюжее. Ну, в конце концов, это GWT ;))) Как всегда, огромное спасибо, Колин. - person slugmandrew; 10.08.2016