See Part 1 to see how the data architecture was made.
https://blogs.sap.com/2019/08/09/abap-fiori-elements-organizing-your-data-architecture-using-cds-vie...
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.
Conclusion
After all those steps, you will end up with this view of your Office entity. This is an Office instance I created with name 'c' and location 'c' with no Employee or Workstation instance.
Edit: You might have noticed there is no mention of OData. This is because there is nothing you need to to with OData: your OData project will create automatically a function import for the
Change Gender action.
Once you add an Employee instance, and you select it by clicking on the circle that will be to the left of the entity, the button "Change Gender" that we created will enable itself.
Then, if all goes well, you should see that the gender of the employee changed after you clicked on the button 'Change Gender'.
In conclusion, this tutorial covered how to add all the
Create/Delete buttons of the different entities as well as adding custom actions with
BOPF.
Hope this tutorial was helpful to you. Happy coding!
-Alexandre Therrien