Friday 30 August 2019

ABAP / Fiori Elements – Adding BOPF Create Remove Update Delete (CRUD) Actions to CDS Views: Building from Scratch Walkthrough Part 2

See Part 1 to see how the data architecture was made.

http://sapabapcentral.blogspot.com/2019/08/abap-fiori-elements-organizing-your-data-architecture-using-cds-views-building-from-scratch-walkthrough-ui-and-logic.html

Note: I will be using ABAP Development Tools for this tutorial.

Note: If you do not know what is BOPF, you should check Oliver Jaegle’s blogs on this website. I will only cover how to make actions from BOPF in this blog, not what it is. This link could be useful, which is the first part of his BOPF to the future blogs. Also, this link could be useful to understand how actions are made (part 7 of his BOPF to the future blogs), although I had some difficulties understanding the code because it mentioned some objects that I had no idea what they were. I have added my own documentation in this blog for you to understand the parts that I did not.

In the previous tutorial, I made a data architecture using CDS views. In this tutorial, I am going to add actions to the different entities that composed my data architecture.

I used an annotation in the previous tutorial called @UI.facet.targetElement in the CDS view ZCPDB_OFFICE_TP. In this tutorial, the importance of that annotation is going to be shown.

Front-end JSON Code: Displaying Create and Delete Buttons


I finished the previous tutorial by showing the GUI of the created SAP Fiori Elements project. I will reference that project because one of the file is required to get modified from the frontend. The modification of this file will consequently show all of the Create and Delete buttons to create/ remove some CDS entity’s instances. That file is manifest.json. This file is created as soon as the SAP Fiori template project is created on SAP Web IDE. You will have to change the pages entry, which is inside of the sap.ui.generic.app entry, to the below value:

WARNING: The following JSON code is character sensitive. If you misspell any CDS view’s name or put a capital letter instead of a lowercase letter, you could get weird errors in your application. For example, you could click on the Create button generated from an entity and it could return an error. Also, some of the Create or Delete buttons could be invisible from entries that do not perfectly correspond.

"pages": {
"ListReport|ZCPDB_OFFICE_TP": {
"entitySet": "ZCPDB_OFFICE_TP",
"component": {
"name": "sap.suite.ui.generic.template.ListReport",
"list": true,
"settings": {
"condensedTableLayout": true,
"smartVariantManagement": true,
"enableTableFilterInPageVariant": true
}
},
"pages": {
"ObjectPage|ZCPDB_OFFICE_TP": {
"entitySet": "ZCPDB_OFFICE_TP",
"component": {
"name": "sap.suite.ui.generic.template.ObjectPage"
},
"pages": {
"ObjectPage|to_Workstation": {
"navigationProperty": "to_Workstation",
"entitySet": "ZCPDB_WORKSTATION_TP",
"component": {
"name": "sap.suite.ui.generic.template.ObjectPage"
}
},

"ObjectPage|to_Employee": {
"navigationProperty": "to_Employee",
"entitySet": "ZCPDB_EMPLOYEE_TP",
"component": {
"name": "sap.suite.ui.generic.template.ObjectPage"
}
}
}
}
}
}
}

This will generate the missing Create / Delete buttons that were not appearing by default. Looking back at the previous tutorial, only a Create button was missing for the entity Employee. With this JSON input, the Create button will appear.

So this snippet of JSON code above describes the flow of your application as seen through the GUI. Here are some things you have to keep in mind to understand the JSON code above:

◈ ZCPDB_OFFICE_TP is the name of my root CDS consumption view (Consumption view = CDS for UI).

◈ The entitySet entry is the name of a CDS consumption view.

◈ The entries ObjectPage|to_Workstation and ObjectPage|to_Employee are related to some annotation I used in the ZCPDB_OFFICE_TP, which is the @UI.facet.targetElement annotation. ObjectPage is a certain type of model page that Fiori Elements generates. to_Workstation and to_Employee relate to the @UI.facet.targetElement values _Workstation and _Employee I have set in the CDS view ZCPDB_OFFICE_TP. Notice that the navigationProperty uses the same to_Workstation and to_Employee values.

◈ Why does the entry ObjectPage|ZCPDB_OFFICE_TP does not have a navigationProperty nor a targetElement? Because it is the root CDS view.

◈ Notice the component entry is always added to describe the type of page that we are using to show the entities. I used a ListReport page to show the Office entities, and that changes to ObjectPage when I select an Office entity. The ZCPDB_OFFICE_TP has two entries with different components: one with the prefix ListReport and another with the prefix ObjectPage. This is because I can see the Office entity’s information once I have selected it. See my pictures in the conclusion to notice that I see some Office information from the ObjectPage (all the pictures in the conclusion show the ObjectPage)

◈ Also notice the order of the entries. First, we put the root CDS view Office entity ZCPDB_OFFICE_TP, the one we see as we open the ListReport application. Then, when we click on an Office entity, we get information about this Office. This is why the entry ListReport|ZCPDB_OFFICE_TP contains its own pages entry. However, when we click on an Office entity, we also see some Employees and Workstations associated to that Office. Thus, the Office entity contains Employees and Workstations. This is why the entry ObjectPage|ZCPDB_OFFICE_TP also contains its own pages entry.

Creating Custom Actions Using BOPF


Take the case where you would like to modify the gender of one instance of Employee. The first thing you are required to do would be to add an action to the entity Employee using BOPF. To do so, you have to check into the generated BOPF Business Object (BO) that gets created when your data architecture has been approved without any errors. See below:


Figure 1.1, BOPF BO in Projects Explorer in Eclipse

The business object is called ZIPDB_OFFICE_TP, just like one of the CDS views.

Double click on that object, and you will see the below screenshot. Press on “Go to the nodes of this BO” to get to the Employee entity.


Figure 1.2, BOPF Main View

This nodes view shows the different nodes of our Business Object (BO). The node ZIPDB_OFFICE_TP is the parent node to ZIPDB_EMPLOYEE_TP and ZIPDB_WORKSTATION_TP. The node ZIPDB_OFFICE_TP is also the root node (As you can see, it is the first entry, and that node expands into its children nodes). Remember those as they will be used later in the implementation of the BOPF code.

Then press on “Ctrl+Click” on the Name field of the entity Employee called ZIPDB_EMPLOYEE_TP (another of the CDS view’s name) to get to that object.


Figure 1.3, BOPF Nodes View

Then, create the action under the Employee entity by clicking on the underlined Actions.


Figure 1.4, BOPF Employee Node

Click on the New… button to create a new action. This will generate a new popup. Fill it with these values for the purpose of this custom action:


Figure 1.5, BOPF Creating an Action

The Implementation Class‘ name is generated by default while typing the name of the action.

I believe the Instance Multiplicity parameter defines how many instances you are taking as input.

Click on Finish. Then, your action will appear in the list of actions. If it does not, save and activate the BOPF Business Object, or wave your mouse on the first available line from the top and it should appear (no idea why). Then, go into that action by pressing “Ctrl+Click“, and you will get something similar to the following screen:


Figure 1.6, BOPF Changing an Action’s Parameters

I have already set the Exporting Type to None. That is because I am returning nothing from the action I am about to create. If you forget to set it to None, you will get an error mentioning something about a returned value ZCPDB_Employee_TPType from OData, because the program is expecting a returned value that you are not giving.

Once you are done, save and activate the BO. Then, create a class with the same name as the implementation class’ name. In this case, ZCL_IPDB_A_CHANGEGENDER.

Coding


In the beginning, after doing some modifications, the class should look like this:

class ZCL_IPDB_A_CHANGEGENDER definition
  public
  inheriting from /BOBF/CL_LIB_A_SUPERCL_SIMPLE
  final
  create public .

public section.
  methods /BOBF/IF_FRW_ACTION~EXECUTE
      redefinition .
protected section.
private section.
ENDCLASS.

CLASS ZCL_IPDB_A_CHANGEGENDER IMPLEMENTATION.

  METHOD /BOBF/IF_FRW_ACTION~EXECUTE.
  
  ENDMETHOD.

ENDCLASS.

The modifications I made show that this class is inheriting from the BOBF class /BOBF/CL_LIB_A_SUPERCL_SIMPLE, and that we are going to modify the method /BOBF/IF_FRW_ACTION~EXECUTE, which is the method that an action is going to execute. There is another method name when you create a determination or a validation. You can also know that from looking at Oliver Jaegle’s blogs.

Constructing the Method CHANGEGENDER


Updating and Reading BOPF Instances

class ZCL_IPDB_A_CHANGEGENDER definition
  public
  inheriting from /BOBF/CL_LIB_A_SUPERCL_SIMPLE
  final
  create public .

public section.
  methods /BOBF/IF_FRW_ACTION~EXECUTE
      redefinition .
protected section.
private section.
ENDCLASS.


CLASS ZCL_IPDB_A_CHANGEGENDER IMPLEMENTATION.

  METHOD /BOBF/IF_FRW_ACTION~EXECUTE.
    "Notice the type of the object: where is this object mentioned? how was it created?
    "If you look in bobf, this is the combined table type associated with the entity
    data lt_item type ztipdb_employee_tp.

    "You can find the generated interface in your project structure. You go in Source Code Library -> Interfaces and
    "find the right one. You need to know this because the first parameter requires you to mention the interface
    "zif_ipdb_office_tp_c, which is the interface for the root object.
    io_read->retrieve(
    exporting
    iv_node = zif_ipdb_office_tp_c=>sc_node-zipdb_employee_tp
    it_key = it_key

    importing
    et_data = lt_item ).

    "now modifying the item. Remember: you received a table of items, you need to loop
    "on the items that corresponded with your search criteria from the method above
    "Normally returns one result because we are searching for a specific key
    LOOP AT lt_item REFERENCE INTO DATA(ls_item).
      
      "Checks if the employee's gender contains the string 'M'. If so, modify to 'F'.
      IF ls_item->gender CS 'M'.
        ls_item->gender = 'F'.

      "Checks if the employee's gender contains the string 'F'. If so, modify to 'M'.
      ELSEIF ls_item->gender CS 'F'.
        ls_item->gender = 'M'.
      ENDIF.

      "parameters are pretty easy to understand. iv_node is the entity concerned, iv_key is 
      "the key of the entity, and is_data is the data we want to put instead.
      io_modify->update(
        iv_node = zif_ipdb_office_tp_c=>sc_node-zipdb_employee_tp
        iv_key = ls_item->key
        is_data = ls_item ).

    ENDLOOP.

  ENDMETHOD.

ENDCLASS.

Some of the comments in this code will help you understand what is happening. Also, let me tell you that the method execute has different objects it receives as input, such as the io_read and io_modify objects, which allows you to perform CRUD operations on some data you have. You also received the table it_key which gives you the keys for the entity received as input.

The sc_node item is what you must use to access the different entities in your BOPF Business Object instance.

io_read->retrieve does the Read operation. All you need to provide the method is iv_node, which is the type of entity concerned (in our case, the Employee entity. Remember the nodes that made up our Business Object? Figure 1.3), and the key, also known as the unique identifier of the object it_key. Then, you get your object as a table outputted from the method as et_data.

io_modify->update does the Update operation. The only parameter different from io_read->retrieve is the is_data parameter, which is your new updated object.

Now something that disturbed me when I looked at BOPF tutorials online was that I never knew what some of the code above meant. What is zif_ipdb_office_tp_c? What is ztipdb_employee_tp?

You will notice in your project architecture that you have an interface that has been generated (I believe at the same time as your BOPF Business Object was generated, which is when your CDS view data architecture has been approved because it has no errors). See the below image.


Figure 1.6, BOPF’s Generated Interface from the Project Explorer in Eclipse
So that answers your question on where zif_ipdb_office_tp_c comes from.

What about ztipdb_employee_tp? Or any other weird objects that have a similar name to the CDS view of ZIPDB_EMPLOYEE_TP?

You have to look into the generated BOPF object for that. You can type transaction bobf or you can go in Eclipse, go to your BOPF generated object ZIPDB_OFFICE_TP, and double click on it. Then click on Go to the nodes of this BO. This is the screen you will now see. (Same as Figure 1.3) Now look at the Combined Structure and Combined Table Type. The Combined Table Type corresponds to the object ztipdb_employee_tp.


(Refers back to Figure 1.3, BOPF Nodes View)
Creating Instances with BOPF (Optional)
I wanted to add this because I did struggle a little to find out what were the parameters that this method was requesting.

To create a new employee instance, I used the io_modify->create method. I will add this method right after the io_modify->update method call, and right before the statement ENDLOOP. This is how I did it:

io_modify->create(
      iv_source_node_key = zif_ipdb_office_tp_c=>sc_node-zipdb_office_tp
      iv_source_key = ls_item->parent_key
      iv_assoc_key = zif_ipdb_office_tp_c=>sc_association-zipdb_office_tp-_employee
      iv_node = zif_ipdb_office_tp_c=>sc_node-zipdb_employee_tp
      is_data = ls_item
      ).

I will now describe the different parameters of this function.

◈ iv_source_node_key: this is your root node. Look back at the information above Figure 1.3.

◈ iv_source_key: this is the parent’s node key. This is the key of the Office object, which is the root node.

◈ iv_assoc_key: this is the key representing the association between Office and Employee. sc_association is a generated instance variable from the interface. You do not decide of its name. zipdb_office_tp is our root node, and _employee is the targetElement I have set in ZCPDB_OFFICE_TP for Employee.

◈ iv_node: this is the node that will have this new item. (our new item is of type Employee) sc_node is a generated instance variable from the interface. You do not decide of its name.

◈ is_data: the data we want this node to contain.

If you do not recall what is a node, go back to Figure 1.3. There are 3 nodes in this BO.

Removing Instances with BOPF (Optional)

I added this after the ENDLOOP statement. It will remove the currently selected element.

io_modify->delete(
      iv_node = zif_ipdb_office_tp_c=>sc_node-zipdb_employee_tp
      it_key = it_key
      ).

The parameters are straight forward now that we have looked at some other CRUD operations, but here is a description of them:

◈ iv_node: this is the node that has the item you want removed (we want to remove an Employee instance).
◈ it_key: this is the key of the object you want removed. Remember it_key is given as a parameter to this function, /BOBF/IF_FRW_ACTION~EXECUTE.

If you do not know what is a node, go back to Figure 1.3. There are 3 nodes in this BO.

Final Method Implementation if you Followed the Optional Parts

This is what the code looks like after having followed the optional parts:

class ZCL_IPDB_A_CHANGEGENDER definition
  public
  inheriting from /BOBF/CL_LIB_A_SUPERCL_SIMPLE
  final
  create public .

public section.
  methods /BOBF/IF_FRW_ACTION~EXECUTE
      redefinition .
protected section.
private section.
ENDCLASS.

CLASS ZCL_IPDB_A_CHANGEGENDER IMPLEMENTATION.

  METHOD /BOBF/IF_FRW_ACTION~EXECUTE.
    "Notice the type of the object: where is this object mentioned? how was it created?
    "If you look in bobf, this is the combined table type associated with the entity
    data lt_item type ztipdb_employee_tp.

    "You can find the generated interface in your project structure. You go in Source Code Library -> Interfaces and
    "find the right one. You need to know this because the first parameter requires you to mention the interface
    "zif_ipdb_office_tp_c, which is the interface for the root object.
    io_read->retrieve(
    exporting
    iv_node = zif_ipdb_office_tp_c=>sc_node-zipdb_employee_tp
    it_key = it_key

    importing
    et_data = lt_item ).

    "now modifying the item. Remember: you received a table of items, you need to loop
    "on the items that corresponded with your search criteria from the method above
    "Normally returns one result because we are searching for a specific key
    LOOP AT lt_item REFERENCE INTO DATA(ls_item).

      "Checks if the employee's gender contains the string 'M'. If so, modify to 'F'.
      IF ls_item->gender CS 'M'.
        ls_item->gender = 'F'.

      "Checks if the employee's gender contains the string 'F'. If so, modify to 'M'.
      ELSEIF ls_item->gender CS 'F'.
        ls_item->gender = 'M'.
      ENDIF.

      "parameters are pretty easy to understand. iv_node is the entity concerned, iv_key is
      "the key of the entity, and is_data is the data we want to put instead.
      io_modify->update(
        iv_node = zif_ipdb_office_tp_c=>sc_node-zipdb_employee_tp
        iv_key = ls_item->key
        is_data = ls_item ).

      io_modify->create(
      iv_source_node_key = zif_ipdb_office_tp_c=>sc_node-zipdb_office_tp
      iv_source_key = ls_item->parent_key
      iv_assoc_key = zif_ipdb_office_tp_c=>sc_association-zipdb_office_tp-_employee
      iv_node = zif_ipdb_office_tp_c=>sc_node-zipdb_employee_tp
      is_data = ls_item
      ).

    ENDLOOP.

    io_modify->delete(
      iv_node = zif_ipdb_office_tp_c=>sc_node-zipdb_employee_tp
      it_key = it_key
      ).

  ENDMETHOD.

ENDCLASS.

CDS View Modifications


After the method is done, you have to add it to the right consumption view where this method will be used. In my case, that would be the consumption view of the Employee entity, called ZCPDB_EMPLOYEE_TP.

I will thus add the following lines to the consumption view ZCPDB_EMPLOYEE_TP (look for the ones that have the comment //this line below must be added above them. The rest of the lines are provided for context):

...
@Search.searchable: true
@VDM.viewType: #CONSUMPTION
define view ZCPDB_EMPLOYEE_TP as select from ZIPDB_EMPLOYEE_TP 
  association [1..1] to ZCPDB_OFFICE_TP as _Office on $projection.officeuuid = _Office.officeuuid
{

  @UI: {
       facet: [
       {
       label: 'Employee',
         id: 'employeeId',
         position: 10,
         type: #COLLECTION
       },
       {
         parentId: 'employeeId',
         type: #FIELDGROUP_REFERENCE,
         targetQualifier: 'employeeIdFG'
       }],
//this line below must be added.
       identification: [{ position: 10, importance: #HIGH, type: #FOR_ACTION, dataAction: 'BOPF:CHANGEGENDER', label: 'Change Gender'} ]
       
  }
  
  @UI.lineItem.position: 10
//this line below must be added.
  @UI.lineItem: [ {type: #FOR_ACTION, dataAction: 'BOPF:CHANGEGENDER', label: 'Change Gender'}]
  @UI.hidden: true
  key ZIPDB_EMPLOYEE_TP.employeeuuid,
...

Notice that the two lines to be added have the parameter type, which describes the type of event (we created an action, so #FOR_ACTION), the parameter dataAction, which selects the action we want with this entity (it must start with BOPF: followed by the name of the action, which is CHANGEGENDER – Refer to Figure 1.6) and then the parameter label, which is the text that will be inside the button doing the action.

2 comments:

  1. Very good blog for SAP which I have seen and it's absolutely great stuff on SAP Topics. Thanks for such a cool article about SAP topics. Very good explanation on SAP concepts we do SAP Training in Chennai for all SAP Modules.
    Regards,
    SAP Training Institutes in Chennai | Best SAP Training in Chennai

    ReplyDelete
  2. Excellent blog for SAP which I have seen and it's absolutely great stuff on SAP Topics. Thanks for such a cool blog about SAP topics. Very good explanation on SAP concepts we do SAP Training in Chennai for all SAP Modules.
    Regards,
    SAP Training Institutes in Chennai | Best SAP Training in Chennai

    ReplyDelete