Dynamic Readonly Fields

Is there a recipe to dynamically make a property read only.

In LightSwitch, as part of the modelling exercise you can write methods to:

  1. Calculate the readonly state for a property (Enabling and disabling the UI controls).
  2. Validate a property.
  3. Handle a property change.

The real benefit of this architecture is that these methods are called in the context of the entity so decisions can be made based on the value of other properties (in the client tier). This is all before a round trip to insert, update or delete. This means that if you implement multiple edit screens or an editable grids for the entity, the business logic is the same anywhere in the app.

I have prototypes how to do these 3 functions but It’s not very elegant as I have to re-implement the code in various screens. In specific, the readonly function is a bit verbose when implemented on more that one screen (Especially grids) and IMHO should be encapsulated in the business logic.

Looking at the framework code there there appears to be a number of ways to affect the result of the isReadonly() metadata property.

Please note, I am new to CUBA and Java so I am a noob. Thanks for any suggestions.

Ian

Hi Ian,

Thank you for raising this issue.

Currently there is no ability to embed the dynamic read-only logic into the data model, you have to replicate it on each screen or better encapsulate it in a class where you pass your entity instance and a set of visual components. However, we will certainly think about a generic approach of solving this issue in the future.

Validating properties will be easy in the upcoming release 6.4: see Bean Validation

As for handling property changes, can you give us an example? Do you need it for implementing calculated read-only properties?

The Bean Validation functionality works for my validation requirements.

I have an idea to implement the isReadOnly functionality is a generic-yet-fit-your-architecture approach by extending the Bean Validation slightly. I involve adding a IsReadonly interface and IsReadonly annotation. It would look like this:


@MyEntityValidator
public class MyEntity {
	
    @Min(value=1, message="Min value is 1")
    @Max(value=100,  message="Max value is 100")
    @IsReadOnly(name="Field1")
    public int getField1() {
 
 }

    @Min(value=1, message="Min value is 1")
    @Max(value=100,  message="Max value is 100")
    @IsReadOnly(name="Field2")
    public int getField2() {

    }
}

public class MyEntityValidator implements ConstraintValidator<MyAnnotation, MyEntity>, IsReadOnly {
    public void initialize(MyAnnotation annotation) {
    }

    public boolean isValid(MyEntity entity, ConstraintValidatorContext context) {
		if (entity.getField1() > entity.getField2()) {
			return false;
		}
		
		return true;
	}
	
	public boolean isReadOnly(MyEntity entity, String fieldName) {
		if("Field1".equals(fieldName)) {
			return entity.getField2() > entity.getField1();
		}
		else if("Field2".equals(fieldName)) {
			return String.equals(entity.getField1(), "VALUE");
		}
	}
}

Your logic in the screens would be changed to not only look for the Readonly metadata attribute but also use the IsReadOnly attribute to determine if a field is enabled or not. I am spit balling here so the idea is not completely flushed out.

As far as handling property changes, I am not sure if you are familiar with XAML and Dependency Properties. I am not sure if there is a similar standard in Java to decouple the Entity and UI in a way that allows the UI to be notified when related properties are changed. Its a fundamental part of LightSwitch that allows the developer to write code to handle the validation/readonly/changed patterns without knowing the actual implementation of a screen.

It could be implemented like:


@MyEntityValidator
public class MyEntity {
    @Min(value=1, message="Min value is 1")
    @Max(value=100,  message="Max value is 100")
    @IsReadOnly(name="Field1")
    public int getField1() {
 
	}

    @Min(value=1, message="Min value is 1")
    @Max(value=100,  message="Max value is 100")
    @IsReadOnly(name="Field2")
    public int getField2() {

    }

    @Changed(name="Field3")
    public String getField3() {

    }
}

public class MyEntityValidator implements ConstraintValidator<MyAnnotation, MyEntity>, IsReadOnly, Changed {
    public void initialize(MyAnnotation annotation) {
    }

    public boolean isValid(MyEntity entity, ConstraintValidatorContext context) {
		if (entity.getField1() > entity.getField2()) {
			return false;
		}
		
		return true;
	}
	
	public boolean isReadOnly(MyEntity entity, String fieldName) {
		if("Field3".equals(fieldName)) {
                    // If Field3 == CUSTOMER then make Field2 read only.
			return String.equals(entity.getField3(), "CUSTOMER");
		}
	}

	public void changed(MyEntity entity, String fieldName) {
		if("Field3".equals(fieldName)) {
                    // Field2 will always be 100 if Field1 is a CUSTOMER
			if(entity.getField3().equals("CUSTOMER")) {
				entity.setField2(100);
			}
		}
	}
}

The changed method would be called when a screen changes the subscribed property. I am not sure how you would implement the Dependency Property functionality to update the UI with changes.

The goals: Code encapsulated in one class and capabilities could be added as required. If the entity is used on multiple screens or in a REST api (there are issues here) then the behavior will be consistent.

This is a naive example of a pattern I have found eliminates a lot of boilerplate code.

Thank you for these considerations - they are very valuable.

:ticket: See the following issue in our bug tracker:

https://youtrack.cuba-platform.com/issue/PL-8551