In this post, you will learn about

  1. Adding field validations for ABAP RAP Business Objects
  2. Send messages to UI for display

This post refers to a RAP service created in the earlier post – Managed with unmanaged save

Field Validation using value help

Field validation can be done using a Value help for a field that can be added to the CDS Entity.

  @Consumption.valueHelpDefinition: [ {
    entity: {
      name: 'SourceForValues',
      element: 'FieldName'
    }      
  } ]
  cdsFieldName,

For example, let us add value help to the Currency field from the application.

  @Consumption.valueHelpDefinition: [ {
    entity: {
      name: '/DMO/I_Customer',
      element: 'CustomerID'
    }
  } ]
 CurrencyCode,

Value help is added.

This also acts as a validation.

When an incorrect value is entered for the currency, it shows an error immediately. However, every scenario can not be handled through value help validation and we may need to evaluate it using multiple select queries or different business logic in the code.

Field Validations in Behavior Definition

Let us implement the same validation using the Behavior Definition. Value help is removed for the below demonstration.

Syntax

validation ValName on save { CUD1; CUD2; ... } 
                         | { field Field1, Field2, ... ; } 

The validation works on save event, in multiple CUD operations (Create/Update/Delete), and on change of specific fields.

Once validation is added, create a method for the validation using the quick-fix functionality.

Activate the class and behavior definition.

Add the code. This code reads the entity from the keys, checks whether the currency is available in the table and if the currency is not available then add the entity into the table failed and reported.

  METHOD validatecurrency.

    READ ENTITIES OF zjp_i_carrier IN LOCAL MODE
        ENTITY carrier
        FIELDS ( currencycode ) WITH CORRESPONDING #( keys )
        RESULT DATA(carriers).

    LOOP AT carriers INTO DATA(carrier).
      SELECT SINGLE * FROM i_currency
          WHERE currency = @carrier-currencycode
          INTO @DATA(ls_currrency).
      IF sy-subrc NE 0.
        APPEND VALUE #( %tky = carrier-%tky ) TO failed-carrier.
        APPEND VALUE #( %tky = carrier-%tky
                        %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error
                                                      text = 'Currency Code is not valid' )
                       ) TO reported-carrier.
      ENDIF.
    ENDLOOP.

  ENDMETHOD.

Refresh the application. Add incorrect currency code

The error message can be seen with a click of the Save button.

How to display the messages?

The entities where messages need to be displayed are added to the reported table.

APPEND VALUE #( %tky = carrier-%tky
                %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error
                                              text     = 'Currency Code is not valid' )
              ) TO reported-carrier.

Here, the entities are read in table carriers and looped into the work area carrier. The carrier contains all the key and field information.

While adding the information to the table reported, %msg is populated using the severity and text using the method new_message_with_text.

Severity has below options based on the type of message

  • if_abap_behv_message=>severity-error
  • if_abap_behv_message=>severity-information
  • if_abap_behv_message=>severity-none
  • if_abap_behv_message=>severity-warning
  • if_abap_behv_message=>severity-success

An alternate method new_message can also be used, which has all message details like id, number, type/severity, and 4 message variables.

To pass more specific information about the fields which should be highlighted in case of an error, the below code can be used. The %element-fieldname can be used to mention which field is causing an issue.

  APPEND VALUE #( %key = carrier-%key
                  %update = if_abap_behv=>mk-on ) TO failed-carrier.
  APPEND VALUE #( %key = carrier-%key
                  %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error
                                                text = 'Currency Code is not valid' )
                  %update = if_abap_behv=>mk-on
                  %element-currencycode = if_abap_behv=>mk-on
                 ) TO reported-carrier.

Note that the validation occurs at the time of saving and not immediately after the value is entered. For real-time validation, the checks can be implemented using the Prechecks in RAP BO.

Using Prechecks for Validation

Prechecks can be used with the update operation for the field-level validation apart from the primary key. In case the user is entering the primary key by themselves, then the prechecks can be implemented in Create operation.

  update( precheck );

Activate and use quick-fix to adjust the class i.e. create the method definition and implementation in the behavior class.

Complete the implementation using the below code.

  METHOD precheck_update.
  
    LOOP AT entities INTO DATA(carrier).

      IF carrier-currencycode IS NOT INITIAL.

        SELECT SINGLE * FROM i_currency
            WHERE currency = @carrier-currencycode
            INTO @DATA(ls_currrency).
        IF sy-subrc NE 0.
          APPEND VALUE #( %key = carrier-%key
                          %update = if_abap_behv=>mk-on ) TO failed-carrier.
          APPEND VALUE #( %key = carrier-%key
                          %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error
                                                        text = 'Currency Code is not valid2' )
                          %update = if_abap_behv=>mk-on
                          %element-currencycode = if_abap_behv=>mk-on
                         ) TO reported-carrier.
        ENDIF.

      ENDIF.

    ENDLOOP.

  ENDMETHOD.

Activate everything that is changed. Refresh the application page and test the validation. In the above code, the message text is used as ‘Currency Code is not valid2’ to understand whether the message is triggered from precheck or from the validation added earlier.

This way, the validations, and prechecks can be implemented in ABAP RESTful Application Programming.

Here is the behavior definition and the class source code for reference. (Expand if the code is collapsed)

Behavior Definition: ZJP_I_CARRIER
managed implementation in class zbp_jp_i_carrier unique;
strict ( 1 );
with draft;

define behavior for ZJP_I_CARRIER alias Carrier
//persistent table zjp_carrier
draft table zjp_d_carrier
with unmanaged save
lock master
total etag LastChangedAt
authorization master ( instance )
etag master LocalLastChangedAt
{

  field ( mandatory ) CarrierId, Name, CurrencyCode;
  field ( readonly ) LocalCreatedBy, LocalCreatedAt, LocalLastChangedBy, LocalLastChangedAt, LastChangedAt;

  create;
  update( precheck );
  delete;
  association _Connection { create; with draft; }

  validation validateCurrency on save { field CurrencyCode; create; update; }

  draft action Edit;
  draft action Activate;
  draft action Discard;
  draft action Resume;
  draft determine action Prepare;

  mapping for zjp_carrier control zjp_x_carrier corresponding
  {
    CarrierId = carrier_id;
    Name = name;
    CurrencyCode = currency_code;
    LocalCreatedBy = local_created_by;
    LocalCreatedAt = local_created_at;
    LocalLastChangedBy = local_last_changed_by;
    LocalLastChangedAt = local_last_changed_at;
    LastChangedAt = last_changed_at;
  }

}

define behavior for zjp_i_connection alias Connection
persistent table zjp_connection
draft table zjp_d_connection
lock dependent by _Carrier
authorization dependent by _Carrier
etag dependent by _Carrier
{

  field ( readonly ) CarrierId;

  update;
  delete;

  association _Carrier { with draft; }

  mapping for zjp_connection corresponding
  {
    CarrierId = carrier_id;
    ConnectionId = connection_id;
    AirportFromId = airport_from_id;
    AirportToId = airport_to_id;
    DepartureTime = departure_time;
    ArrivalTime = arrival_time;
    Distance = distance;
    DistanceUnit = distance_unit;
  }

}
Implementation class
CLASS lhc_carrier DEFINITION INHERITING FROM cl_abap_behavior_handler.
  PRIVATE SECTION.

    METHODS get_instance_authorizations FOR INSTANCE AUTHORIZATION
      IMPORTING keys REQUEST requested_authorizations FOR carrier RESULT result.
    METHODS validatecurrency FOR VALIDATE ON SAVE
      IMPORTING keys FOR carrier~validatecurrency.

    METHODS precheck_update FOR PRECHECK
      IMPORTING entities FOR UPDATE carrier.

ENDCLASS.

CLASS lhc_carrier IMPLEMENTATION.

  METHOD get_instance_authorizations.
  ENDMETHOD.

  METHOD validatecurrency.

    READ ENTITIES OF zjp_i_carrier IN LOCAL MODE
        ENTITY carrier
        FIELDS ( currencycode ) WITH CORRESPONDING #( keys )
        RESULT DATA(carriers).

    LOOP AT carriers INTO DATA(carrier).
      SELECT SINGLE * FROM i_currency
          WHERE currency = @carrier-currencycode
          INTO @DATA(ls_currrency).
      IF sy-subrc NE 0.
        APPEND VALUE #( %tky = carrier-%tky ) TO failed-carrier.
        APPEND VALUE #( %tky = carrier-%tky
                        %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error
                                                      text = 'Currency Code is not valid' )
                       ) TO reported-carrier.
      ENDIF.
    ENDLOOP.

  ENDMETHOD.

  METHOD precheck_update.

    LOOP AT entities INTO DATA(carrier).
      IF carrier-currencycode IS NOT INITIAL.

        SELECT SINGLE * FROM i_currency
            WHERE currency = @carrier-currencycode
            INTO @DATA(ls_currrency).
        IF sy-subrc NE 0.
          APPEND VALUE #( %key = carrier-%key
                          %update = if_abap_behv=>mk-on ) TO failed-carrier.
          APPEND VALUE #( %key = carrier-%key
                          %msg = new_message_with_text( severity = if_abap_behv_message=>severity-error
                                                        text = 'Currency Code is not valid2' )
                          %update = if_abap_behv=>mk-on
                          %element-currencycode = if_abap_behv=>mk-on
                         ) TO reported-carrier.
        ENDIF.

      ENDIF.

    ENDLOOP.

  ENDMETHOD.

ENDCLASS.

CLASS lsc_zjp_i_carrier DEFINITION INHERITING FROM cl_abap_behavior_saver.
  PROTECTED SECTION.

    METHODS save_modified REDEFINITION.

    METHODS cleanup_finalize REDEFINITION.

ENDCLASS.

CLASS lsc_zjp_i_carrier IMPLEMENTATION.

  METHOD save_modified.

    DATA : carriers    TYPE STANDARD TABLE OF zjp_carrier,
           carriers_u  TYPE STANDARD TABLE OF zjp_carrier,
           controls    TYPE STANDARD TABLE OF zjp_x_carrier,
           lr_carriers TYPE RANGE OF zjp_carrier-carrier_id.

    "Implement Logic for all possible Save Operations - Create / Update / Delete

    IF create IS NOT INITIAL.
      carriers = CORRESPONDING #( create-carrier MAPPING FROM ENTITY ).
      INSERT zjp_carrier FROM TABLE @carriers.
    ENDIF.

    IF update IS NOT INITIAL.
      carriers = CORRESPONDING #( update-carrier MAPPING FROM ENTITY ).
      controls = CORRESPONDING #( update-carrier MAPPING FROM ENTITY USING CONTROL ).

      SELECT * FROM zjp_carrier
        FOR ALL ENTRIES IN @carriers
        WHERE carrier_id = @carriers-carrier_id
        INTO TABLE @DATA(carriers_o).
      IF sy-subrc EQ 0.
        carriers_u =
          VALUE #(
            FOR i = 1 WHILE i LE lines( carriers )
            LET
              control =
                VALUE #( controls[ i ] OPTIONAL )
              carrier   =
                VALUE #( carriers[ i ] OPTIONAL )
              carrier_o =
                VALUE #( carriers_o[ carrier_id = carrier-carrier_id ] OPTIONAL )
            IN
            ( carrier_id            = carrier-carrier_id
              name                  = COND #( WHEN control-name IS NOT INITIAL
                                              THEN carrier-name
                                              ELSE carrier_o-name )
              currency_code         = COND #( WHEN control-currency_code IS NOT INITIAL
                                              THEN carrier-currency_code
                                              ELSE carrier_o-currency_code )
              local_created_by      = carrier_o-local_created_by
              local_created_at      = carrier_o-local_created_at
              local_last_changed_by = COND #( WHEN control-local_last_changed_by IS NOT INITIAL
                                              THEN carrier-local_last_changed_by
                                              ELSE carrier_o-local_last_changed_by )
              local_last_changed_at = COND #( WHEN control-local_last_changed_at IS NOT INITIAL
                                              THEN carrier-local_last_changed_at
                                              ELSE carrier_o-local_last_changed_at )
              last_changed_at       = COND #( WHEN control-last_changed_at IS NOT INITIAL
                                              THEN carrier-last_changed_at
                                              ELSE carrier_o-last_changed_at )

             ) ).

      ELSE.
        carriers_u = carriers.
      ENDIF.
      UPDATE zjp_carrier FROM TABLE @carriers_u.
    ENDIF.

    IF delete IS NOT INITIAL.
      lr_carriers = VALUE #( FOR ls_carrier_id IN delete-carrier
                           ( sign = 'I' option ='EQ' low = ls_carrier_id-carrierid )
                           ).

      DELETE FROM zjp_carrier WHERE carrier_id IN @lr_carriers.
    ENDIF.


  ENDMETHOD.

  METHOD cleanup_finalize.
  ENDMETHOD.

ENDCLASS.

Visit ABAP RESTful Application Programming Model to explore all articles on ABAP RAP Model.


If you like the content, please subscribe…

Join 4,016 other subscribers

Discovering ABAP YouTube Channel