I am creating a rule builder similar to this one:
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?
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.