Application Development and Automation Blog Posts
Learn and share on deeper, cross technology development topics such as integration and connectivity, automation, cloud extensibility, developing at scale, and security.
cancel
Showing results for 
Search instead for 
Did you mean: 
NicholasArefta
Explorer
373

Objective

Implement dynamic field control on a chosen field, add our own validation along with an error message that targets that field when the validation fails.

Source code: https://github.com/nickcantstop/SchoolExample

As per capire documentation when using dynamic field control, it forces you to create your custom validations.

capire fieldcontrolcapire fieldcontrol

Available field control options.

Number KeyField Control
Screenshot 2025-05-01 at 4.38.49 pm.png
In our example we want the field averageGrade to be (Read Only) when firstName is null and then Mandatory when firstName has a value.

We will use averageGradeFC field to store and also annotate our averageGrade field.

(./db/school.cds)

The composition associations are included purely for illustrative purposes 🙂

namespace my.domain.education;

entity School {
    key ID      : Integer;
        name    : String;
        address : String;
        classes : Composition of many School.Classes
                      on classes.parent = $self;
}

entity School.Classes {
    key ID       : Integer;
    key parent   : Association to School;
        subject  : String;
        students : Composition of many School.Classes.Students
                       on students.parent = $self;
}

entity School.Classes.Students {
    key ID             : Integer;
    key parent         : Association to School.Classes;
        firstName      : String;
        lastName       : String;
        averageGrade   : String;
        averageGradeFC : Integer default 1;
}

Service Layer (./srv/schoolService.cds)

using {my.domain.education as education} from '../db/school';

service SchoolService {
    @odata.draft.enabled
    entity School                as projection on education.School;

    @cds.redirection.target: true
    entity SchoolClasses         as projection on education.School.Classes;

    @cds.redirection.target: true
    entity SchoolClassesStudents as projection on education.School.Classes.Students;
}

In annotations file (./app/school/annotations.cds)

annotate service.SchoolClassesStudents with @(Common : { SideEffects #averageGradeSideEffect : {
    SourceProperties: [firstName],
    TargetProperties: ['averageGradeFC']
}}) {
    averageGrade @Common.FieldControl : averageGradeFC;
};

Using the target method as seen here messages.error(“Error”).target() can help us indicate to the user which field needs attention, in our example we are throwing an error for each averageGrade field that does not pass the validation in the existing School entity.

If we use the following code snippet averageGrade @Common.FieldControl : 7; the framework will append a red asterisk (*) next to the annotated field, to signal that this field is mandatory, but it does not enforce that mandatory rule.

The following handler provides us the validation needed to enforce our mandatory field.

SchoolHandler - validation on when School entity is updated

@Component
@ServiceName(SchoolService_.CDS_NAME)
public class SchoolHandler implements EventHandler {

    @Autowired
    Messages messages;

    SchoolHandler(Messages messages) {
        messages = this.messages;
    }

    @Before(entity = School_.CDS_NAME, event = CqnService.EVENT_UPDATE)
    public void onSchoolUpdate(School school) {
        school.getClasses().forEach(classes -> classes.getStudents().forEach(students -> {
            if (Objects.isNull(students.getAverageGrade())) {
                messages.error("Average Grade cannot be null.").target(School_.class,
                        sc -> sc.classes(
                                cl -> cl.ID().eq(classes.getId())
                                        .and(cl.parent_ID().eq(classes.getParentId())
                                                .and(cl.IsActiveEntity().eq(false))))
                                .students(
                                        stu -> stu.ID().eq(students.getId())
                                                .and(stu.parent_ID().eq(students.getParentId()))
                                                .and(stu.parent_parent_ID().eq(students.getParentParentId()))
                                                .and(stu.IsActiveEntity().eq(false)))
                                .averageGrade());
            }
        }));
        messages.throwIfError();
    }

    @Before(entity = SchoolClassesStudents_.CDS_NAME, event = DraftService.EVENT_DRAFT_PATCH)
    public void onStudentSideEffect(SchoolClassesStudents students) {
        if (Objects.isNull(students.getFirstName())) {
            students.setAverageGradeFC(1);
        } else {
            students.setAverageGradeFC(7);
        }
    }
}

With this code if I leave the field averageGrade as null when it is set to mandatory in the field control, I am met with this error when saving the draft.

error highlighted.png
 
The following error message is returned in the backend, it depicts the target as the odata path to the corresponding field.
{
    "error": {
        "code": "400",
        "message": "Average Grade cannot be null.",
        "target": "in/classes(ID=2,parent_ID=1,IsActiveEntity=false)/students(ID=3,parent_ID=2,parent_parent_ID=1,IsActiveEntity=false)/averageGrade"
    }
}
Labels in this area