Search code examples
javagwtgwt-editors

How to implement GWT editors for subclasses of a type?


Let's say I have an object hierarchy like this:

Account > Site > Supply

An Account is an actual company, a Site is a building they have, and a Supply is either an ElecSupply or GasSupply. Supply is never instantiated and could be an abstract class in theory.

I am using Objectify for persistence, and have a page that displays the list of Supplies for each Site, regardless of whether they are an ElecSupply or GasSupply.

Now I am implementing the GWT Editor Framework and have come up against a problem with this polymorphic entity. How do I implement an Editor and set of sub-editors for an object like this?

@Entity
public class Supply implements Serializable
{
    @Id
    protected Long id;
    @Embedded
    protected List<BillingPeriod> billingPeriods = new ArrayList<BillingPeriod>();

    public Supply()
    {

    }
// ...
}

The subclasses: (ElecSupply has 5 unique fields and GasSupply has just one)

@Subclass
public class ElecSupply extends Supply implements Serializable
{
    private String profile;
    private String mtc;
    private String llf;
    private String area;
    private String core;

    public ElecSupply()
    {

    }
}

@Subclass
public class GasSupply extends Supply implements Serializable
{
    private String mpr;

    public GasSupply()
    {

    }
// ...
}

So I would like to know if anyone has any experience with this kind of structure? I have tried to make separate editors for ElecSupply and GasSupply, and then show or hide them as part of the edit page.

The other way I was thinking about doing it would be to have a single editor (for Supply), and then load different sub-editors depending on which type of object we are editing.

Any light shed will be gratefully received.


Solution

  • I've already been in this case, and I've implemented the following solution :

    • First create an generic utilitary class named AbstractSubTypeEditor which will activate a specific editor when you edit one of your subclass object :

      import com.google.gwt.editor.client.CompositeEditor;
      import com.google.gwt.editor.client.Editor;
      import com.google.gwt.editor.client.EditorDelegate;
      import com.google.gwt.editor.client.LeafValueEditor;
      
      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;
      
              /**
               * Construct an AbstractSubTypeEditor backed by the given sub-Editor.
               *
               * @param subEditor the sub-Editor that will be attached to the Editor
               *          hierarchy
               */
              public AbstractSubTypeEditor(E subEditor) {
                      this.subEditor = subEditor;
              }
      
              /**
               * Returns the sub-Editor that the OptionalFieldEditor was constructed
               * with.
               *
               * @return an {@link Editor} of type E
               */
              public E createEditorForTraversal() {
                      return subEditor;
              }
      
              public void flush() {
                      currentValue = chain.getValue(subEditor);
              }
      
              /**
               * Returns an empty string because there is only ever one sub-editor used.
               */
              public String getPathElement(E subEditor) {
                      return "";
              }
      
              public T getValue() {
                      return currentValue;
              }
      
              public void onPropertyChange(String... paths) {
              }
      
              public void setDelegate(EditorDelegate<T> delegate) {
              }
      
              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);
                      }
              }
      }
      
    • Now you can create an Editor for Supply, containing two sub-editors and two AbstractSubTypeEditor (one for each of your subtypes) :

      public class SupplyEditor extends Composite implements Editor<Supply> {
      
              public class ElecSupplyEditor implements Editor<ElecSupply> {
                      public final TextBox profile = new TextBox();
                      public final TextBox mtc = new TextBox();
                      public final TextBox llf = new TextBox();
                      public final TextBox area = new TextBox();
                      public final TextBox core = new TextBox();
              }
              @Ignore
              final ElecSupplyEditor elecSupplyEditor = new ElecSupplyEditor();
              @Path("")
              final AbstractSubTypeEditor<Supply, ElecSupply, ElecSupplyEditor> elecSupplyEditorWrapper = new AbstractSubTypeEditor<Supply, ElecSupply, SupplyEditor.ElecSupplyEditor>(elecSupplyEditor) {
                      @Override
                      public void setValue(final Supply value) {
                              setValue(value, value instanceof ElecSupply);
                              if (!(value instanceof ElecSupply)) {
                                      elecSupplyEditor.profile.setVisible(false);
                                      elecSupplyEditor.mtc.setVisible(false);
                                      elecSupplyEditor.llf.setVisible(false);
                                      elecSupplyEditor.area.setVisible(false);
                                      elecSupplyEditor.core.setVisible(false);
                              } else {
                                      elecSupplyEditor.profile.setVisible(true);
                                      elecSupplyEditor.mtc.setVisible(true);
                                      elecSupplyEditor.llf.setVisible(true);
                                      elecSupplyEditor.area.setVisible(true);
                                      elecSupplyEditor.core.setVisible(true);
                              }
                      }
              };
      
              public class GasSupplyEditor implements Editor<GasSupply> {
                      public final TextBox mpr = new TextBox();
              }
              @Ignore
              final GasSupplyEditor gasSupplyEditor = new GasSupplyEditor();
              @Path("")
              final AbstractSubTypeEditor<Supply, GasSupply, GasSupplyEditor> gasSupplyEditorWrapper = new AbstractSubTypeEditor<Supply, GasSupply, SupplyEditor.GasSupplyEditor>(gasSupplyEditor) {
                      @Override
                      public void setValue(final Supply value) {
                              setValue(value, value instanceof GasSupply);
                              if (!(value instanceof GasSupply)) {
                                      gasSupplyEditor.mpr.setVisible(false);
                              } else {
                                      gasSupplyEditor.mpr.setVisible(true);
                              }
                      }
              };
      
              public SupplyEditor () {
                      final VerticalPanel page = new VerticalPanel();
                      page.add(elecSupplyEditor.profile);
                      page.add(elecSupplyEditor.mtc);
                      page.add(elecSupplyEditor.llf);
                      page.add(elecSupplyEditor.area);
                      page.add(elecSupplyEditor.code);
                      page.add(gasSupplyEditor.mpr);
                      initWidget(page);
              }
      }
      

    This should show/hide your fields according to the subclass you are editing, and bind the properties to the good fields.