How to make Composite Component listen to EditableChangeEvent?

I have a MasterDetailForm that contains a number of Composite Components and some basic components. When I first enter the form, all of the basic components are not enabled/editable, but all of my composite components are enabled/editable before I’ve chosen an entity to edit.

How can I make my composite components behave like the basic components?

I have included this code in my composite component, but it doesn’t appear to be enough:

@CompositeDescriptor("historyField-component.xml")
public class HistoryField
            extends CompositeComponent<HBoxLayout>
            implements Field<BigDecimal>,
        CompositeWithCaption,
        CompositeWithHtmlCaption,
        CompositeWithHtmlDescription,
        CompositeWithIcon,
        CompositeWithContextHelp
{

My HistoryField is based on an HBoxLayout, which by itself does not implement Component.Edit. It’s the underlying fields that are editable.

    @Override
    public boolean isEditable() {
        return historyField_valueField.isEditable();
    }

    @Override
    public void setEditable(boolean editable) {
        historyField_valueField.setEditable(editable);
        historyField_historyBtn.setEnabled(editable);
    }

    @Override
    public boolean isEnabled() {
        return historyField_valueField.isEnabled();
    }

    @Override
    public void setEnabled(boolean enabled) {
        historyField_valueField.setEnabled(enabled);
        historyField_historyBtn.setEnabled(enabled);
    }

The MasterDetail XML contains:

            <vbox id="editBox" height="100%" margin="false,false,false,true" expand="fieldGroupBox" spacing="true">
                <scrollBox id="fieldGroupBox">
                    <form id="form" dataContainer="ordersourceDc">
                        <column width="250px">
                            <textField id="ordersourcecdField" property="ordersourcecd"/>
                            <textField id="descriptionField" property="description"/>
                            <lookupField id="sourcetypeField" property="sourcetype"/>
                            <app:historyField id="mailproccostField" property="mailproccost" field="osmailproccost"/>
                            <app:historyField id="callcentercostField" property="callcentercost" field="oscallcentercost" />
                            <app:historyField id="callcenterascostField" property="callcenterascost" field="oscallcenterascost" />
                            <app:historyField id="callcenterpctField" property="callcenterpct" field="oscallcenterpct" />
                        </column>
                    </form>
                </scrollBox>
                <hbox id="actionsPane" spacing="true" visible="false">
                    <button id="saveBtn" action="save"/>
                    <button id="cancelBtn" action="cancel"/>
                </hbox>
            </vbox>

The app:historyField items are my composite components.

I assume the problem is that my composite component is not listening to the EditableChangeEvent, but I can’t figure out how to make that happen.

I’ve been digging based on this post:

Compoisite Component Permission

After implementing an intermediate component (WebHistoryTextField) similar to the WebTodayDateField in that other post, I’ve found an issue with this EditableChangeEvent setup.

What I’m finding is that my EditableChangeListeners are set on the child components of my composite component, but not on the composite component itself. That is an HBoxLayout, so I’m not surprised it does not have listeners.

The MasterDetailScreen creates a Form to hold all the fields. It uses this code to set the Editable value of all the fields:

        getForm().setEditable(enabled);

Does Form somehow need to deal with composite components and call listeners of the child components?

And one step closer. I now have an implementation that works specifically for a WebForm, but I would like to make it generic. I added the following code to my composite component class:

    @Override
    public void setParent(Component parent) {
        super.setParent(parent);
        
        if (parent instanceof WebForm) {
            final WebForm form = (WebForm) parent;
            form.addEditableChangeListener(editableChangeEvent -> {
                historyField_valueField.setEditable(form.isEditable() && form.isEnabled());
                historyField_historyBtn.setEnabled(form.isEditable() && form.isEnabled());
            });
        }
    }

When adding the composite component to the parent, and if that parent is a WebForm, add a new EditableChangeListener that picks up the editable/enabled values on the Form and passes them into my child components.

So, what if my composite component is NOT in a WebForm (for example, in a table/datagrid)? What is the right way to make this generic?

Hi,

the extended example of a CompositeComponent (base implementation can be found in the live demo) that takes into account the parent’s editable state below:

...

private boolean editable = true;
private Subscription parentEditableChangeListener;

...

@Override
public void setParent(Component parent) {
    if (getParent() instanceof EditableChangeNotifier
            && parentEditableChangeListener != null) {
        parentEditableChangeListener.remove();
        parentEditableChangeListener = null;
    }

    super.setParent(parent);

    if (parent instanceof EditableChangeNotifier) {
        parentEditableChangeListener = ((EditableChangeNotifier) parent).addEditableChangeListener(event -> {
            boolean parentEditable = event.getSource().isEditable();
            boolean finalEditable = parentEditable && isEditable();
            setEditableInternal(finalEditable);
        });

        Editable parentEditable = (Editable) parent;
        if (!parentEditable.isEditable()) {
            setEditableInternal(false);
        }
    }
}

@Override
public boolean isEditable() {
    return editable;
}

@Override
public void setEditable(boolean editable) {
    if (this.editable != editable) {
        setEditableInternal(editable);
    }
}

private void setEditableInternal(boolean editable) {
    valueField.setEditable(editable);
    upBtn.setEnabled(editable);
    downBtn.setEnabled(editable);
}

...