Technical Articles
How to check mandatory fields in RAP
Introduction
When implementing my openSource based RAP Generator I had the requirement that on the object page on item level fields should be read-only based on data that I had entered in the object page on header level.
So on the item level I had a mixture of fields that are read only by default and some that are read only based on the instance features.
So in my behavior definition I had something like this:
field ( features : instance ) MandFieldInstfeat;
field ( mandatory ) MandFieldBdef;
Though on the UI level we find that the fields are marked accordingly as read-only or mandatory based on the values being entered on header level
Header object page | Item object page |
“1” is entered on header level |
![]() –> the field MandFieldInstfeat is read-only. |
![]() |
–> the field MandFieldInstfeat is mandatory. |
There is no out of the box support (so far) in RAP to validate the input of the user.
Code to control instance features
The method get_instance_features( ) is used by the RAP framework to determine which fields in the object page have to be set read-only or mandatory for each instance.
METHOD get_instance_features.
" get the root node(s). In a Fiori Elements UI this
" will be just one entry. But when being called via EML or
" as an API several instances of HeaderMand can be requested
READ ENTITIES OF ZI_HEADERMand IN LOCAL MODE
ENTITY iTEMmAND BY \_HeaderMand
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(HeaderMands).
" Read all associated child nodes and set the field MandFieldInstfeat
" to either read-only or mandatory based on the value of the field Mynumber
" in the root node
LOOP AT HeaderMands INTO DATA(HeaderMand).
READ ENTITIES OF ZI_HEADERMand IN LOCAL MODE
ENTITY HeaderMand BY \_ItemMand
ALL FIELDS
WITH VALUE #( ( %tky = HeaderMand-%tky ) )
RESULT DATA(ItemMands).
result = VALUE #( FOR ItemMand IN ItemMands
( %tky = ItemMand-%tky
%field-MandFieldInstfeat = COND #( WHEN HeaderMand-Mynumber = 2
THEN if_abap_behv=>fc-f-mandatory
ELSE if_abap_behv=>fc-f-read_only )
) ).
ENDLOOP.
ENDMETHOD.
Code to validate mandatory fields
The tricky thing is that we have to write code that finds out which fields
- have been defined as mandatory in the behavior defintion and
- have been defined as mandatory based on the features of our instance
and check whether data that has been entered by the user or the caller of the API for these fields.
While static settings as they are defined in the behavior definition are quite easy to spot it becomes more complicated if the status of a field can change dynamically, for example based on the content of another field of our business object.
GET PERMISSIONS
Whether a field of an entity is mandatory or not have to be retrieved using the statement GET PERMISSIONS.
This is (as I have to admit) not obvious because one would only expect to retrieve authorizations rather than the information whether a field is read-only, or mandatory.
The GET PERMISSIONS statement needs as an input a structure that must be typed with TYPE STRUCTURE FOR PERMISSIONS REQUEST. This structure (here called permission_request) contains a dynamic structure %field that contains the field names of our request. Using RTTI we can retrieve the field names dynamically in the internal table components_permission_request.
The result of the permission request contains instance specific information for each instance which is based on the (features : instance) statement such as:
field ( features : instance ) MandFieldInstfeat;
is stored in an internal table.
The static information which is based on statements such as
field ( mandatory ) MandFieldBdef;
is stored in a structure.
METHOD mandatory_fields_check.
DATA permission_request TYPE STRUCTURE FOR PERMISSIONS REQUEST ZI_ITEMMand.
DATA reported_zi_itemmand_li LIKE LINE OF reported-itemmand.
DATA(description_permission_request) = CAST cl_abap_structdescr( cl_abap_typedescr=>describe_by_data_ref( REF #( permission_request-%field ) ) ).
DATA(components_permission_request) = description_permission_request->get_components( ).
LOOP AT components_permission_request INTO DATA(component_permission_request).
permission_request-%field-(component_permission_request-name) = if_abap_behv=>mk-on.
ENDLOOP.
" Get current field values
READ ENTITIES OF ZI_HeaderMand IN LOCAL MODE
ENTITY ItemMand
ALL FIELDS
WITH CORRESPONDING #( keys )
RESULT DATA(entities).
LOOP AT entities INTO DATA(entity).
GET PERMISSIONS ONLY INSTANCE FEATURES ENTITY ZI_ItemMand
FROM VALUE #( ( itemuuid = entity-ItemUUID ) )
REQUEST permission_request
RESULT DATA(permission_result)
FAILED DATA(failed_permission_result)
REPORTED DATA(reported_permission_result).
LOOP AT components_permission_request INTO component_permission_request.
"permission result for instances (field ( features : instance ) MandFieldInstfeat;) is stored in an internal table.
"So we have to retrieve the information for the current entity
"whereas the global information (field ( mandatory ) MandFieldBdef;) is stored in a structure
IF ( permission_result-instances[ itemuuid = entity-ItemUUID ]-%field-(component_permission_request-name) = if_abap_behv=>fc-f-mandatory OR
permission_result-global-%field-(component_permission_request-name) = if_abap_behv=>fc-f-mandatory ) AND
entity-(component_permission_request-name) IS INITIAL.
APPEND VALUE #( %tky = entity-%tky ) TO failed-itemmand.
"since %element-(component_permission_request-name) = if_abap_behv=>mk-on could not be added using a VALUE statement
"add the value via assigning value to the field of a structure
CLEAR reported_zi_itemmand_li.
reported_zi_itemmand_li-%tky = entity-%tky.
reported_zi_itemmand_li-%element-(component_permission_request-name) = if_abap_behv=>mk-on.
reported_zi_itemmand_li-%msg = new_message( id = '/DMO/CM_RAP_GEN_MSG'
number = 066
severity = if_abap_behv_message=>severity-error
v1 = |{ component_permission_request-name }|
v2 = | with semantic key: { entity-SemanticKey } | ).
APPEND reported_zi_itemmand_li TO reported-itemmand.
ENDIF.
ENDLOOP.
ENDLOOP.
RESULT
If the value “2” has been entered on header level and if as a result we have two mandatory fields on item level you will get the following error message if both fields are iniial.
because in the debugger we will find
and we have retrieved the field names as follows
Great Job Andre!
this is another milestone to explore RAP.
Please, could you tell me if there is any way to replace the standard message with a custom message for the mandatory fields?
The message displays the technical name of the field, which is not good for the end user.
Thanks!
Hi Andre Fischer ,
first of all, thank you for this blog. I am surprised that this has to be done manually, although the framework knows that the field must be filled. But if I remember correctly, it was the same with Web Dynpro.
Is it correct that you have created the method "mandatory_fields_check" as validation?
I have an unmanaged scenario with draft. Actually, I interpret the documentation in such a way that the validations should also be executed there, since I can also define them in the behavior. But the debugger does not stop in my validation methods.
https://help.sap.com/docs/BTP/923180ddb98240829d935862025004d6/ab7459048c7e4ecda98d0b6f51b01e7b.html?locale=en-US
Or am I wrong and the validation does not work with unmanaged scenario? If yes, do we have to do it in the check_before_save? Unfortunately we have no keys parameter there. I could get them with my buffer class, but I don't think that's so nice.
Thanks Florian!