Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
Andre_Fischer
Product and Topic Expert
Product and Topic Expert

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.
"2" is entered on header level



--> 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

  1. have been defined as mandatory in the behavior defintion and

  2. 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


 
6 Comments
Ramjee_korada
Active Contributor
0 Kudos
Great Job Andre!

this is another milestone to explore RAP.
robson_soares
Participant
0 Kudos

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!

florian_halder
Participant
0 Kudos
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...

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!
Joseph_BERTHE
Active Contributor
0 Kudos
Hello,

Very nice blog. SAP should include that directly on the framework.

Nevertheless, I try to implement the code, and my system gave me an error at this point:
    LOOP AT components_permission_request INTO DATA(component_permission_request).
--> permission_request-(component_permission_request-name) = if_abap_behv=>mk-on.
ENDLOOP.

The compilation error :

Description Resource Path Location TypeThere is no dynamic component specification allowed at the current statement position.

I'm on S4HANA 2021.

 

What I'm doing wrong ?

Regards
arunsubbu
Explorer
0 Kudos
Hello andre.fischer,

In my RAP programming in BTP, in order to show the label of the field which is mandatory in error message, I tried to use CL_DD_DDL_ANNOTATION_SERVICE. But I get the error that use of this class is not permitted.

Do you know why this is not permitted? Is there some other way to get the field label that was defined in the metadata extension in runtime?

Thanks & Regards
Arun
szebenyib
Explorer
0 Kudos

This is a nice summary and food for thought article.

I believe -as others have commented above- that this should be part of the standard coding, as soon as possible. Having to manually add a check for something that is mandatory (and already recognized on the UI as such) but could be implemented generically should be a top priority for RAP framework development.

As for the:

- non standard message -> define a class based exception inheriting from cx_static_check

- message using the name of the component -> map those to data elements, by using RTTS on the structure of the CDS entity, then map the data elements to text by reading their texts from DD04T

- implementation -> yes, it is a validation