How to make an attribute unchangeable?

There are a few entities in the system we are converting from an ancient language that have attributes that need to be unchangeable once they are set. What is the best way to achieve this behavior?

Hi,

Just o double check: Are you talking about the fact that a particular entity attribute should only be editable in the case of the creation of the entity?

Cheers
Mario

Exactly! I have several attributes that need to be readonly once the entity is created.

Hi,

I prepared an example for you here: GitHub - mariodavid/cuba-example-deactivated-entity-attributes: This CUBA example shows how to deactivate certain attributes of an Entity after entities creation

I will copy over the description of the README for having the information here in the forum as well.

Cheers
Mario


CUBA Example Deactivated Entity Attributes

This CUBA example shows how to deactivate entity attributes once an entity was created.

NOTE: this example only works on the UI layer. In case this constraint has to be fulfilled throughout all parts of the application (like REST API, Entity Inspector etc.) a Bean Validation / EntityChangedEvent based approach has to be used.

Running Example

overview

Variant 1: Manually

The first option is to manually activate a particular entity attribute in case the entity
is new.

This can be achieved by making the field in the XML Screen descriptor non-editable:

<textField 
       property="identificationNumber" 
       id="identificationNumberField" 
       editable="false" />

In the screen controller the entity attribute will be activated in case it is a new entity instance:


@UiController("petclinic_Pet.edit")
@UiDescriptor("pet-edit.xml")
@EditedEntityContainer("petDc")
@LoadDataBeforeShow
public class PetEdit extends StandardEditor<Pet> {

    @Inject
    protected TextField<String> identificationNumberField;

    @Subscribe
    protected void onInitEntity(InitEntityEvent<Pet> event) {
        identificationNumberField.setEditable(true);
    }
}

This variant works in case the amount of fields is not that big. In case there are multiple
entity attributes or the same logic has to be mirrored across different entities the Variant 2 is more suitable.

Variant 2: Screen Mixin

The second option is based on the Screen Mixin functionality which allows to extract particular common login into an Interface.

In this case, the following Interface can be created in the web module WithDeactivatableAttributes:


/**
 * Screen Mixin that allows to define a list of entity attributes, that should be
 * deactivated once an entity was created and is in edit mode
 */
public interface WithDeactivatableAttributes {


    /**
     * the list of Entity attributes that will be deactivated
     */
    List<Component.Editable> attributesToDeactivate();


    @Subscribe
    default void onInit(Screen.BeforeShowEvent event) {
        EntityStates entityStates = Extensions.getBeanLocator(event.getSource()).get(EntityStates.class);
        Entity editedEntity = ((StandardEditor) event.getSource()).getEditedEntity();

        if (!entityStates.isNew(editedEntity)) {
            attributesToDeactivate().forEach(
                    editable -> editable.setEditable(false)
            );
        }
    }

}

The interface has a method that needs to be implemented: attributesToDeactivate which should return the corresponding list of Component instances that should be deactivated.

A usage of that interface can be found in the VisitEdit screen:

public class VisitEdit extends StandardEditor<Visit> implements WithDeactivatableAttributes {

    @Inject
    protected LookupPickerField<Pet> petField;
    @Inject
    protected DateField<Date> visitDateField;

    @Override
    public List<Component.Editable> attributesToDeactivate() {
        return Arrays.asList(
            petField, visitDateField
        );
    }
}
1 Like

Excellent - thank you Mario!

Probably one of those approaches will work for our application - however, in the interest of providing examples here on the forums, is there an example somewhere of the other approach you mentioned (using Bean Validation/EntityChangedEvent)?

hi,

probably there is one example out there, but it might be based on the previous common EntityListener interfaces. With CUBA 7.x the EntityChangedEvent has become more relevant.

The idea is:

Create an EntityChangedEventListener and check the via the AttributeChanges changes = event.getChanges(); object for specific changes in for an attribute. Then you can throw an exception in case there was a change.

Cheers
Mario

Hmmmm - that’s probably a little above my head for now. I’m just getting started, but as I said the previous approaches will probably work fine, at least for now.