问题
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 AbstractSubtypeEditor
s 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 withValueAwareEditor
and usesetValue
andflush()
to paper over the details.
来源:https://stackoverflow.com/questions/38636332/gwt-compositeeditor-dynamically-switched-editor-not-being-added-to-chain-prope