Skip to Content
Technical Articles
Author's profile photo Ramjee Korada

ABAP RAP / FUNCTIONS: Defaulting fields during new object creation in “Unmanaged Scenario without Draft”

 

Introduction:

In this blog post, I would like to share knowledge on usage of Functions in ABAP RAP and then default few fields while creating a new business object in “Unmanaged scenario without Draft”. It is a common requirement in most of the applications to default some fields to save end user time or routine work.

Goal:

  • Understanding and Developing functions in ABAP RAP
  • Defaulting fields while creating new business object

Prerequisites:

Basic knowledge on ABAP Restful Application Programming. if this is something new, then go through OpenSAP course Building Apps with the ABAP RESTful Application Programming Model

What are the functions?

Functions are specified as nonstandard operations in behavior definitions and are implemented in the ABAP behavior pool class. Function cannot change the state of the object while “Action” can influence.

Business example:

In this example, we are going to create a purchase contract and default some of the fields

  • Buyer Id: With logged in user (or “Manager id“ if logged in user is an assistant)
  • Company Code: With company code details from his/her attributes (or additional custom logic)
  • Financial year: With current financial year
  • Created By: Logged in user
  • Valid From: Today’s date (to be discussed in next blog post)
  • Valid To: 1 year, Today’s date + 364 days (to be discussed in next blog post)

Steps: 

  1. Create a table with underlying fields
    @EndUserText.label : 'Purchase Contract'
    @AbapCatalog.enhancement.category : #NOT_EXTENSIBLE
    @AbapCatalog.tableCategory : #TRANSPARENT
    @AbapCatalog.deliveryClass : #A
    @AbapCatalog.dataMaintenance : #RESTRICTED
    define table zrk_t_pur_con {
      key client           : abap.clnt not null;
      key con_uuid         : sysuuid_x16 not null;
      object_id            : zrk_pur_con_id;
      description          : zrk_description;
      buyer                : zrk_buyer_id;
      supplier             : zrk_sup_no;
      sup_con_id           : zrk_sup_con_id;
      comp_code            : zrk_company_code;
      @Semantics.amount.currencyCode : 'zrk_t_pur_con.currency'
      target_value         : abap.curr(10,2);
      currency             : abap.cuky;
      stat_code            : zrk_stat_code;
      fiscl_year           : zrk_fiscal_year;
      valid_from           : zrk_valid_from;
      valid_to             : zrk_valid_to;
      created_by           : abp_creation_user;
      created_at           : abp_creation_tstmpl;
      last_changed_by      : abp_locinst_lastchange_user;
      last_changed_at      : abp_lastchange_tstmpl;
      locl_last_changed_at : abp_locinst_lastchange_tstmpl;
    
    }​
  2. Create an interface view entity for data modeling
    @AccessControl.authorizationCheck: #CHECK
    @EndUserText.label: 'ID for purchase contract'
    define root view entity Zrk_I_Pur_Con as select from zrk_t_pur_con
     
    {
        key con_uuid as ConUuid,
        object_id as ObjectId,
        description as Description,
        buyer as Buyer,
        supplier as Supplier,
        sup_con_id as SupConId,
        comp_code as CompCode,
        stat_code as StatCode,
        target_value as TargetValue,
        currency as Currency,
        valid_from as ValidFrom,
        valid_to as ValidTo,
        fiscl_year as FiscalYear,
        created_by as CreatedBy,
        created_at as CreatedAt,
        last_changed_by as LastChangedBy,
        last_changed_at as LastChangedAt,
        locl_last_changed_at as LoclLastChangedAt
        
    }​
  3. Create a projection view entity to expose in the UI service
    @EndUserText.label: 'Projection for purchase contract'
    @AccessControl.authorizationCheck: #CHECK
    @Metadata.allowExtensions: true
    define root view entity ZRK_C_PUR_CON as projection on Zrk_I_Pur_Con {
        key ConUuid,
        ObjectId,
        Description,
        Buyer,
        Supplier,
        SupConId,
        CompCode,
        StatCode,
        TargetValue,
      @Consumption.valueHelpDefinition: [{
          entity: {
              name: 'I_Currency',
              element: 'Currency'
          },
          useForValidation: true,
          label: 'Select currency from Dailog'
      }]    
        Currency,
        ValidFrom,
        ValidTo,
        FiscalYear,
        CreatedBy,
        CreatedAt,
        LastChangedBy,
        LastChangedAt,
        LoclLastChangedAt
    }​
  4. Enrich UI with metadata extension
    @Metadata.layer: #CORE
    @UI: {
      headerInfo: {
        typeName: 'Purchase Contract',
        typeNamePlural: 'Purchase Contracts',
        description: {
        type: #STANDARD,
        value: 'Description'
            },
            title: {
    //    type: #STANDARD,
        value: 'ObjectId'
            }
            
        }
    }
    annotate entity ZRK_C_PUR_CON with
    {
      @UI.facet: [ {
              id: 'Header',
              type: #HEADERINFO_REFERENCE,
              label: 'Header',
              purpose: #HEADER,
              position: 10,
              targetQualifier: 'Header'
      },
      {     id: 'General',
            type: #IDENTIFICATION_REFERENCE,
            purpose: #STANDARD,
            label: 'General',
            position: 20 ,
            targetQualifier: 'General'},
    
      {     id: 'Validities',
            type: #IDENTIFICATION_REFERENCE,
            label: 'Validities',
            position: 30 ,
            targetQualifier: 'Validities'}
    
       ]
    
      @UI.hidden: true
      ConUuid;
    
      @UI:{ lineItem: [{ position: 10 }] , identification: [{ position: 10 , qualifier: 'Header'}]}
      ObjectId;
      
      @UI:{ lineItem: [{ position: 20 }] , identification: [{ position: 20 , qualifier: 'Header'}]}
      Description;
      
      @UI:{ lineItem: [{ position: 30 }] , identification: [{ position: 30 , qualifier: 'General'}]}
      @Consumption.valueHelpDefinition: [{ 
          entity: {
              name: 'ZRK_I_BUYER',
              element: 'BuyerId'
          }
       }]  
      Buyer;
      
      @UI:{ lineItem: [{ position: 40 }] , identification: [{ position: 40 ,qualifier: 'General'}]}
      @Consumption.valueHelpDefinition: [{ entity: {
          name: 'ZRK_I_SUPPLIER',
          element: 'SupNo'
      } ,
            useForValidation: true
      }]  
      Supplier;
      
      @UI:{ lineItem: [{ position: 50 }] , identification: [{ position: 50 ,qualifier: 'General'}]}
      @Consumption.valueHelpDefinition: [{ entity: {
          name: 'ZRK_I_SUP_CON',
          element: 'SupConId'
      } ,
      additionalBinding: [{
          localElement: 'Supplier',
          localConstant: '',
          element: 'SupNo',
          usage: #FILTER_AND_RESULT 
      }] , 
            useForValidation: true
      }]   
      SupConId;
      
      @UI:{ lineItem: [{ position: 50 }] , identification: [{ position: 55 ,qualifier: 'General'}]}
      @Consumption.valueHelpDefinition: [{ entity: {
          name: 'ZRK_I_COMP_CODE',
          element: 'CompCode'
      } ,
            useForValidation: true
      }]   
      CompCode;  
      
      @UI:{ lineItem: [{ position: 60 }] , identification: [{ position: 60 ,qualifier: 'Header'}]}
      StatCode;
      
      @UI:{ lineItem: [{ position: 70 }] , identification: [{ position: 70 , qualifier: 'Validities' }]}
      ValidFrom;
      
      @UI:{ lineItem: [{ position: 80 }] , identification: [{ position: 80 , qualifier: 'Validities' }]}
      ValidTo;
      
      @UI:{ lineItem: [{ position: 80 }] , identification: [{ position: 90 , qualifier: 'Validities' }]}
      @Consumption.valueHelpDefinition: [{ 
          entity: {
              name: 'ZRK_I_FISCAL_YEAR',
              element: 'fiscal_year'
          }
       }]   
      FiscalYear;
      
      @UI:{ lineItem: [{ position: 50 }] , identification: [{ position: 57 ,qualifier: 'General' , label: 'Target value'}]}
      TargetValue;
      
      @UI:{ lineItem: [{ position: 90 }] , identification: [{ position: 100, qualifier: 'General' , label: 'Created By' }]}
      CreatedBy;
      
      @UI.hidden: true
      CreatedAt;
      @UI.hidden: true
      LastChangedBy;
      @UI.hidden: true
      LastChangedAt;
      @UI.hidden: true
      LoclLastChangedAt;
    
    }​
  5. Create a behavior definition as “Unmanaged implementation without Draft”
    • Declare characteristics such as mandatory, numbering etc
    • Declare a static function “DefaultForCreate” (scope of this blog post) with returning parameter as $self.
      • Explanation: We are declaring the function as static since we don’t need any inputs from UI at this moment.
        unmanaged implementation in class zbp_rk_i_pur_con unique;
        strict;
        
        define behavior for Zrk_I_Pur_Con alias PurCon
        late numbering
        lock master
        authorization master ( instance )
        etag master LoclLastChangedAt
        {
          create;
          update;
          delete;
        
          field ( mandatory ) Buyer, Supplier, SupConId ;
        
          static function DefaultForCreate result [1] $self;
        }​
  6. Implement behavior pool class
    • Implement the method generated for given function
      • Explanation: This has exporting parameter “result” and it has 2 properties
        • %cid: we have to return the same value that we received in keys
        • %param: This is a dynamic structure based on return parameter defined
        • Business Logic
            METHOD DefaultForCreate.
          
              DATA : lt_pur_con TYPE TABLE FOR READ RESULT zrk_i_pur_con\\purcon .
          
              APPEND INITIAL LINE TO lt_pur_con ASSIGNING FIELD-SYMBOL(<fs_new_con>).
          
              <fs_new_con>-conuuid = cl_system_uuid=>create_uuid_x16_static( ).
          
              <fs_new_con>-Description = 'Defaulted from backend'.
          
              <fs_new_con>-ValidFrom = cl_abap_context_info=>get_system_date( ).
          
              <fs_new_con>-ValidTo = <fs_new_con>-ValidFrom + 364.
          
              <fs_new_con>-Buyer = sy-uname.
          
              <fs_new_con>-CreatedBy = sy-uname.
              
              " Wrapper logic to default company code based on logged in user
              <fs_new_con>-CompCode =
                  zrk_cl_mng_pur_con=>get_defaults_for_create(  )-comp_code.
          
              DATA(lv_date) = cl_abap_context_info=>get_system_date( ).
              <fs_new_con>-FiscalYear = lv_date+0(4).
          
              result = VALUE #( FOR <fs_rec_m> IN lt_pur_con
                                  ( %cid = keys[ 1 ]-%cid
                                    %param = <fs_rec_m> )
                                  ) .
          
            ENDMETHOD.​
  7. Generate the service Definition and Binding
    @EndUserText.label: 'Service definition forZRK_C_PUR_CON_H'
    define service ZRK_UI_PUR_CON {
      expose ZRK_C_PUR_CON_H as PCHead;
    }​

    Service%20Binding

  8. Preview the application and check the metadata for following
    <FunctionImport Name="DefaultForCreate" ReturnType="cds_zrk_ui_pur_con_u.PurConType" EntitySet="PurCon" m:HttpMethod="GET"/>​
  9. You might notice that the fields are not defaulted on “Create”. We need to implement an annotation in Fiori Elements app with WebIDE/BAS.
    Annotation Term=”Common.DefaultValuesFunction” fires a backend call that triggers the function “DefaultForCreate
    <Annotations Target="cds_zrk_ui_pur_con_u.cds_zrk_ui_pur_con_u_Entities/PurCon" >
          <Annotation Term="Common.DefaultValuesFunction" String="DefaultForCreate">                                   
           </Annotation>
    </Annotations>​

  10. Save and preview the generated application and see the fields defaulted.
  11. See the response from backend in network calls.

 

Conclusion 

Job is done and we understand what the functions are and how to implement them.

We can play around further logic to enhance this feature.

 

Assigned Tags

      19 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Rajanna pranam
      Rajanna pranam

      Hi Ramjee

      Very nice blog. I just want to understand how do you call the functions ? In case of actions, on click of the button the corresponding method for the action gets called. But in case of function how is it triggered ?

      Author's profile photo Ramjee Korada
      Ramjee Korada
      Blog Post Author

      Hi Rajanna,

      thanks for the feedback.

      In this example, annotation mentioned in step #9 is trigger point to call this function.

      Common.DefaultValuesFunction

      This is standard annotation to default fields . So I have mapped my function to it so that framework fires call.

      best wishes,

      Ramjee Korada

       

      Author's profile photo Souvik Roy
      Souvik Roy

      Hi Ramjee

      Very nice blog.

      I need to achieve the same functionality and followed the steps you mentioned. Only difference is, I generated the UI annotation of the output in business application studio instead of annotating from CDS view.Then, I created another annotation file from annotation modeler for this default values annotation and same is linked in the manifest.json file.

      But for me the backend method is not getting triggered. I can see the function import is getting created in metadata of the backend service. Also, I am using S4HANA 2020 system.

      Any pointer you can suggest me to check please.

      Author's profile photo Sandeep Doddi
      Sandeep Doddi

      Hi Ramjee,

      I tried the example you have provided  and the function is not getting called. Everything is same as what you have mentioned but there is no call to the function import. I am using an Odata V2 service with unmanaged with no draft similar to you. Can you please highlight if there are additional steps?

       

      Thanks,

      Sandeep

      Author's profile photo Ramjee Korada
      Ramjee Korada

      Hi Sundeep,

      Can you please check in Console if there is any other error.

      If possible, share the sample code snippet.

       

      BR,

      Ramjee

      Author's profile photo Sandeep Doddi
      Sandeep Doddi

      Hi Ramjee,

      Thanks for getting back. I finally figured out the issue. My Ui5 version was 1.90 and it was not working. I updated to the latest version and it worked. It was basically a UI5 version issue.

       

      Thanks,

      Sandeep

      Author's profile photo Ramjee Korada
      Ramjee Korada

      Glad that issue is resolved

      Author's profile photo Felix Huang
      Felix Huang

      Hello Sandeep,

       

      I came across the same issue, when I load the UI5 1.90, default value function is not working but it's working fine when load the UI5 1.96.

       

      I'm wondering how you know it's due to the version of UI5, because I didn't find any clue in Chrome's developer tools nor in backend ST22 or /IWFND/ERROR_LOG.

       

      Regards,

      Felix

      Author's profile photo George Chu
      George Chu

      Hi Ramjee,

       

      Thanks for posting. Compared with action, what advantages does function have?

      All the things a function can do, can be done by an action as well, right?

       

      Regards,

      George

      Author's profile photo Ramjee Korada
      Ramjee Korada

      Hi George,

      Conceptually Functions are different from Actions.

      Reference : RAP - Nonstandard Operations

      Actions

      An action in RAP is a non-standard modifying operation that is part of the business logic.
      Functions

      A function in RAP is a custom read-operation that is part of the business logic.
      Functions perform calculations or reads on business objects without causing any side effects. Functions don't issue any locks on database tables and you can't modify or persist any data computed in a function implementation.
      Ex: Status change or value change in the entity.
      I think above documentation is self explanatory and let me know if you are looking for information.
      Best wishes,
      Ramjee Korada.
      Author's profile photo Somnath Paul
      Somnath Paul

       

      Nice blog post. Ramjee,

      What is your ABAP release as I am not able to use function in my Behavioural  Definition.

      • Thanks, Somnath
      Author's profile photo Ramjee Korada
      Ramjee Korada

      Hi Somnath Paul

      Thanks for the feedback and My example is from SAP BTP Trail system.

      Best wishes,

      Ramjee Korada.

      Author's profile photo Marcelino Ponty
      Marcelino Ponty

      Hi Ramjee, thanks for the very useful guide. I managed to get it working.

      I'm now exploring this functionality when creating records by association (creating child entry).

      So far, I managed to get the function called when creating child entity by using annotation target as per below (Store is the parent, and Menu is the child):

      <Annotations Target="cds_zmp_ui_c_store_u.ZMP_C_STORE_UType/to_menu" >

       

      However I'm currently stuck at getting extra information when the function is called, such as the parent's ID or other field values of the parent (Because in my case the child defaults need to be derived from parent's information)

      As per my observation, none of the parameter passed into the method carry useful information.
      Do you have any advice how to achieve this?
      Thanks.
      Regards,
      Ponty
      Author's profile photo Marcelino Ponty
      Marcelino Ponty

      Hi Ramjee, this is solved.

      Turns out we have option also to define the function as instance function. So when declaring the behavior of the parent we only need to declare it without using 'static' keywords. Example:

      function default_for_cba_menu result [1] ZMP_I_MENU_U;

      Then when the method in parent behavior class is called, it will carry the keys of the parent instance. In the result, we don't use %CID, and instead we supply the parent ID:

        METHOD default_for_cba_menu.
      
          DATA : lt_menu TYPE TABLE FOR READ RESULT zmp_i_menu_u.
      
          READ ENTITIES OF zmp_i_store_u IN LOCAL MODE
              ENTITY store
              FIELDS ( store_id currency )
              WITH CORRESPONDING #( keys )
              RESULT DATA(lt_store_read_results)
              FAILED failed.
      
          APPEND INITIAL LINE TO lt_menu ASSIGNING FIELD-SYMBOL(<fs_menu>).
          <fs_menu>-currency = lt_store_read_results[ 1 ]-currency.
      
          result = VALUE #( FOR <fs_rec_m> IN lt_menu
                                  store_id = lt_store_read_results[ 1 ]-store_id
                                ( %param = <fs_rec_m> )
                              ) .
        ENDMETHOD.

       

      Regards,

      Ponty

      Author's profile photo Pratik Gupta
      Pratik Gupta

      Hi Ramjee Korada

      I am trying to achieve same scenario from ADT. Could you please tell me which annotation is to be used in this case?

      I have 1 more question related to RAP.
      Can we display different fields on UI with the same Service?

      Below is the scenario for my case.

      I have 3 fields for UI out of these 3 fields i want to display Field1 & Field2 as editable and Field 3 as non-editable in the case of creating entries into some database table.

      in the case of Updating entries Field1 & Field2 must be non-editable and Field3 should be editable.

      Andre Fischer

      Author's profile photo Ramjee Korada
      Ramjee Korada

      Hi Pratik,

      1. "I am trying to achieve same scenario from ADT. Could you please tell me which annotation is to be used in this case?"

        => This is possible in ADT with "CDS Behaviour - determination on Create" only in case of Draft application. But it not possible possible in ADT for Non-Draft.

      2. Manipulation of fields as per requirement is difficult in RAP in my perspective.
        I request you to raise a question so that experts can respond.
        Commenting on this blog may not notify other experts.

      Best wishes,

      Ramjee Korada

      Author's profile photo Pratik Gupta
      Pratik Gupta

      HI Ramjee

      Thanks for your reply.

       

      I am using below code but still its not working. Am i Missing something?

       

      METHOD relevant.

      READ ENTITIES OF zi_account IN LOCAL MODE
      ENTITY account
      FIELDS ( relevant )
      WITH CORRESPONDING #( keys )
      RESULT DATA(accounts).

      MODIFY ENTITIES OF zi_account IN LOCAL MODE
      ENTITY account
      UPDATE FIELDS ( relevant ) WITH VALUE #( FOR account IN accounts
      ( %tky = account-%tky
      relevant = 'Y' ) )
      REPORTED DATA(modifyReported).
      reported = CORRESPONDING #( DEEP modifyreported ).

      ENDMETHOD.

      Author's profile photo Felix Huang
      Felix Huang

      Hi Ramjee,

       

      You app is running in a Fiori Launchpad sandbox, did you try to run you app in standalone (visit index.html of your app in the browser).

       

      The default value function is only working in Fiori Launchpad, not working in standalone mode.

       

      Regards,

      Felix

      Author's profile photo Ramjee Korada
      Ramjee Korada

      Hi Felix,

      I was testing through launchpad.

      I am not aware of difference when used 'index.html' .

      Best wishes,

      Ramjee