gwtuibindergwt-editors

GWT Nested Editors - Not flushing correctly or giving cycle detected error


I am creating a rule builder similar to this one:

enter image description here

There can be two types of rule objects:

Both of these conditions implement a MetadataCondition interface.

Approach 1

I have used three different Editors to attempt to edit the rules but I'm getting a cycle detected error which makes sense as the rules are recursive. Below are the three editors.

MetadataConditionEditor

This uses the AbstractSubTypeEditor described here and here

This is the entry point into the rule editors. It will be given the top level metadata condition and from there it attaches the relevant editor.

public class MetadataConditionEditor extends Composite implements Editor<MetadataCondition> {
interface Binder extends UiBinder<Widget, MetadataConditionEditor> {}

@Ignore
final JoinMetadataConditionEditor joinMetadataEditor = LibsFactory.injector().getJoinMetadataConditionEditor();

@Path("")
final AbstractSubTypeEditor<MetadataCondition, JoinMetadataCondition, JoinMetadataConditionEditor> joinMetadataEditorWrapper =
    new AbstractSubTypeEditor<MetadataCondition, JoinMetadataCondition, JoinMetadataConditionEditor>( joinMetadataEditor ) {
        @Override
        public void setValue( final MetadataCondition value )
        {
            setValue( value, isJoinMetadataCondition( value ) );
            if( isJoinMetadataCondition( value ) ) {
                container.clear();
                container.add( joinMetadataEditor );
            }
        }
};


@Ignore
final LeafMetadataConditionEditor leafMetadataEditor = LibsFactory.injector().getLeafMetadataConditionEditor();

@Path("")
final AbstractSubTypeEditor<MetadataCondition, LeafMetadataCondition, LeafMetadataConditionEditor> leafMetadataEditorWrapper =
    new AbstractSubTypeEditor<MetadataCondition, LeafMetadataCondition, LeafMetadataConditionEditor>( leafMetadataEditor ) {
        @Override
        public void setValue( final MetadataCondition value )
        {
            setValue( value, !isJoinMetadataCondition( value ) );
            if( !isJoinMetadataCondition( value ) ) {
                container.clear();
                container.add( leafMetadataEditor );
            }
        }
};


@UiField @Ignore SimplePanel container;


public MetadataConditionEditor() {
    initWidget( GWT.<Binder> create(Binder.class).createAndBindUi( this ) );
}


private Boolean isJoinMetadataCondition( MetadataCondition value ) {
    return value instanceof JoinMetadataCondition;
}}

JoinMetadataConditionEditor

Used for editing the JoinMetadataCondition objects

public class JoinMetadataConditionEditor extends Composite implements Editor { interface Binder extends UiBinder {}

@UiField @Ignore FlowPanel container;

@UiField
@Path( "type" )
ManualSelectEditor type;

@Path( "conditions" )
ListEditor<MetadataCondition, MetadataConditionEditor> conditions;


public JoinMetadataConditionEditor() {
    initWidget( GWT.<Binder> create(Binder.class).createAndBindUi( this ) );

    conditions = ListEditor.of( new MetadataEditorSource() );
    type.setSelectOptions( EditorConstants.metadataJoins );
}


private class MetadataEditorSource extends EditorSource<MetadataConditionEditor>
{
    @Override
    public MetadataConditionEditor create( int index )
    {
        final MetadataConditionEditor subEditor = new MetadataConditionEditor();

        container.insert( subEditor, index );

        return subEditor;
    }

    public void dispose( MetadataConditionEditor subEditor ) {
        container.remove( subEditor );
    }
}}

LeafMetadataConditionEditor

And finally the editor used for the LeafMetadataCondition

public class LeafMetadataConditionEditor extends Composite implements Editor<LeafMetadataCondition> {
    interface Binder extends UiBinder<Widget, LeafMetadataConditionEditor> {}

    @UiField TextBoxEditor name;
    @UiField TextBoxEditor value;
    @UiField ManualSelectEditor operator;


    public LeafMetadataConditionEditor() {
        initWidget(GWT.<Binder> create(Binder.class).createAndBindUi(this));
        operator.setSelectOptions( EditorConstants.metadataOperators );
    }
}

Question

The question is: How do I avoid the Cycle detected error or build this recursiveness in a way that works?

Note: I am open to new ways of doing this. I have tried a few other routes such as initialising drivers for each subeditor and making them all CompositeEditors. This compiled and loaded but when it came time to flush the drivers the subeditor values were not being properly constructed even though the flush() methods seemed to be called but the values were then not used by the parent editors.

Approach 2

The second approach involves using a CompositeEditor and having the sub editors create their own drivers which are then flushed to create the final result.

MetadataConditionEditor

public class MetadataConditionEditor extends Composite implements
                            CompositeEditor<MetadataCondition, MetadataCondition, SimpleDriverEditor<MetadataCondition>>,
                            LeafValueEditor<MetadataCondition>
{
    interface Binder extends UiBinder<Widget, MetadataConditionEditor> {}

//    private SimpleBeanEditorDriver<MetadataCondition, ? extends Editor<MetadataCondition>> subDriver;
    private EditorChain<MetadataCondition, SimpleDriverEditor<MetadataCondition>> chain;
    private SimpleDriverEditor<MetadataCondition> subEditor;
    private EditorDelegate<MetadataCondition> delegate;
    private MetadataCondition value;

    @UiField @Ignore SimplePanel container;


    public MetadataConditionEditor() {
        initWidget( GWT.<Binder> create(Binder.class).createAndBindUi( this ) );
    }


    @Override
    public void flush()
    {
        if( Utils.isNull( subEditor ) )
            return;

        GWT.log( "----- flush-pre - " + System.identityHashCode(this) + " - " + value );// TODO

        value = subEditor.flush();

        GWT.log( "----- flush-post-1 - " + System.identityHashCode(this) + " - " + value.toJson() );// TODO

    }

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

    @Override
    @SuppressWarnings("unchecked")
    public void setValue( MetadataCondition value )
    {
        this.value = value;
//        subDriver = null;

        if( Utils.isNull( value ) )
            return;

        GWT.log( "----- setValue - " + value );// TODO

        if( value instanceof JoinMetadataCondition ) {
            SimpleDriverEditor<JoinMetadataCondition> newSubEditor = LibsFactory.injector().getJoinMetadataConditionEditor();
            SimpleDriverEditor<? extends MetadataCondition> newSubEditor1 = newSubEditor;
            subEditor = (SimpleDriverEditor<MetadataCondition>) newSubEditor1;

            newSubEditor.edit( (JoinMetadataCondition) value );
        }

        container.clear();
        container.add( subEditor );
    }

    @Override
    public void setDelegate( EditorDelegate<MetadataCondition> delegate ) {
        GWT.log( "----- setDelegate - " + delegate );// TODO
        this.delegate = delegate;
    }

    @Override
    public MetadataCondition getValue() {
        GWT.log( "----- getValue - " + System.identityHashCode(this) + " - " + value );// TODO
        return value;
    }

    @Override
    public SimpleDriverEditor<MetadataCondition> createEditorForTraversal() {
        GWT.log( "----- createEditorForTraversal - " + subEditor );// TODO
        return subEditor;
    }

    @Override
    public String getPathElement( SimpleDriverEditor<MetadataCondition> subEditor ) {
        GWT.log( "----- getPathElement - " + delegate.getPath() );// TODO
        return delegate.getPath();
    }

    @Override
    public void setEditorChain( EditorChain<MetadataCondition, SimpleDriverEditor<MetadataCondition>> chain ) {
        GWT.log( "----- setEditorChain - " + chain );// TODO
        this.chain = chain;
    }
}

JoinMetadataConditionEditor

public class JoinMetadataConditionEditor extends Composite implements SimpleDriverEditor<JoinMetadataCondition>
{
    interface Binder extends UiBinder<Widget, JoinMetadataConditionEditor> {}
    interface Driver extends SimpleBeanEditorDriver<JoinMetadataCondition, JoinMetadataConditionEditor> {}

    private Driver driver = GWT.create(Driver.class);

    @Inject
    ModelFactory factory;

    @UiField @Ignore HTML label;
    @UiField @Ignore FlowPanel container;
    @UiField @Ignore Button deleteMetadata;

    @UiField
    @Path( "type" )
    ManualSelectEditor type;

    @Path( "conditions" )
    ListEditor<MetadataCondition, MetadataConditionEditor> conditions = ListEditor.of( new MetadataEditorSource() );


    public JoinMetadataConditionEditor() {
        initWidget( GWT.<Binder> create(Binder.class).createAndBindUi( this ) );
        type.setSelectOptions( EditorConstants.metadataJoins );
    }


    public SimpleBeanEditorDriver<JoinMetadataCondition, JoinMetadataConditionEditor> createDriver() {
        driver.initialize( this );
        return driver;
    }


    @Override
    public JoinMetadataCondition flush()
    {
        GWT.log( "---------- flush-pre - " + System.identityHashCode(this) + " - " + type.getValue() );// TODO
        GWT.log( "---------- flush-pre - " + System.identityHashCode(this) + " - " + conditions.getList() );// TODO
        JoinMetadataCondition value = driver.flush();
        GWT.log( "---------- flush-post - " + System.identityHashCode(this) + " - " + value.toJson() );// TODO
        return value;
    }


    @Override
    public void edit( JoinMetadataCondition object ) {
        createDriver();
        driver.edit( object );
        label.setText( getLabel( object ) );
    }


    private String getLabel( JoinMetadataCondition value ) {
        if( StringUtils.equals( value.getType(), JoinMetadataTypes.AND.value() ) )
            return LibsFactory.lang().allConditionsAreTrue();
        return LibsFactory.lang().anyConditionsAreTrue();
    }


    @Override
    public HandlerRegistration addDeleteHandler( MetadataDeletedHandler handler ) {
        return addHandler( handler, MetadataDeletedEvent.TYPE );
    }


    @UiHandler("deleteMetadata")
    protected void onDeleteMetadata( ClickEvent event ) {
        fireEvent( new MetadataDeletedEvent( (Event) event.getNativeEvent() ) );
    }


    @UiHandler("addAllMetadata")
    protected void onAddAllMetadata(ClickEvent event) {
        add( factory.newJoinMetadataCondition( JoinMetadataTypes.AND ) );
    }


    @UiHandler("addOrMetadata")
    protected void onAddOrMetadata(ClickEvent event) {
        add( factory.newJoinMetadataCondition( JoinMetadataTypes.OR ) );
    }

    @UiHandler("addLeafMetadata")
    protected void onAddLeafMetadata(ClickEvent event) {
        add( factory.newLeafMetadataCondition() );
    }


    private void add( MetadataCondition metadata ) {
        try {
            GWT.log("--------------------------------- add() - pre");// TODO
            conditions.getList().add( metadata );
            clearErrors();
        } catch (Exception e) {
            GWT.log("--------------------------------- add() - " + e.getMessage()); // TODO
        }
    }


    public void clearErrors() {
        type.getErrorHandler().clearErrors();
    }


    private class MetadataEditorSource extends EditorSource<MetadataConditionEditor>
    {
        @Override
        public MetadataConditionEditor create( int index )
        {
            final MetadataConditionEditor subEditor = new MetadataConditionEditor();
            container.insert( subEditor, index );
            return subEditor;
        }

        public void dispose( MetadataConditionEditor subEditor ) {
            container.remove( subEditor );
        }
    }
}

Issue

This second approach seems to be almost working. The issue is that when I flush the driver, the sub-drivers are also flushed but for some reason the parent editors don't actually include the values of the subeditors.

The log statements above generate the following output when I have one single nested Join condition:

----- flush-pre - 111 - JoinMetadataCondition@2d1
---------- flush-pre - 761 - null
---------- flush-pre - 761 - [JoinMetadataCondition@379]
----- flush-pre - 886 - JoinMetadataCondition@379
---------- flush-pre - 929 - and
---------- flush-pre - 929 - []
---------- flush-post - 929 - {"type":"and", "conditions":[]}
----- flush-post-1 - 886 - {"type":"and", "conditions":[]}
----- getValue - 886 - JoinMetadataCondition@379
---------- flush-post - 761 - {"type":"and", "conditions":[]}
----- flush-post-1 - 111 - {"type":"and", "conditions":[]}
----- getValue - 111 - JoinMetadataCondition@2d1
{"type":"and", "conditions":[]}

As you can see the main editor (111) doesn't actually include the contents of it's subeditors. Any ideas why that might be?


Solution

  • Unfortunately I couldn't get this to work using a structured Editor approach.

    The final solution was to create a LeafValueEditor and build the form "manually" based on the data input provided through setValue(). I also implemented the HasEditorErrors in order to handle errors correctly.