How to properly enhance cProjects with an additional tab
Due to very little PPM development documentation available, I want to share a problem I encountered a lot during the implementation phase and my recommendations to achieve the proper functionality.
The requirement is to achieve data transfer between different custom tabs in real time. For example, we have two customer tabs at the project management level:
SAP guidelines suggest the usage of a subsystem (ABAP global class implementing IF_DPR_APPL_PLUG_IN_SUBSYSTEM and IF_DPR_APPL_BOOTSTRAP_MEMBER interfaces) as an intermediate layer between the UI and the back-end logic. I found that sometimes, the subsystem is instantiated more than once during the same working session. This is an issue, as the project data is loaded again. So basically we have the following scenario:
Some data is filled in the first tab, that we want to automatically be updated in the second tab (in real-time) or to trigger some event. As the subsystem instance is deleted and created again when the user switches between tabs, there is no possibility to handle an event or incoming data.
What I implemented and also advise everyone to do is to create a persistency layer (something similar with the BO, business layer, in PLM 7).
Basically, store and manipulate the back-end data inside a global class. If you are doing this already, that’s great, so I’ll take it to the next level.
Here’s a list of properties and design patterns your class should have:
1. As an interjacent agent in the application model, it is recommended that the back-end class is implemented as a Singleton in order to ensure that consistency. Basically, even if the subsystem is created again, the GET_INSTANCE( ) method of the business class will return the same instance (because of the same work process).
2. The class acts as a data buffer by minimizing the database interactions. It has setter and getter methods, that can be used by each component in the UI via the subsystem. For a better understanding and standardization, the persistency layer should implement the four basic CRUD functions (Create, Read, Update, Delete).
Additionally, if the data manipulated is of table type, besides having the same structure as the underlying database table, the buffer will have an extra control field (type DBOP) which stores the last operation performed on a particular entry. The values for operations are referenced by constants: PPTE_INSERT, PPTE_UPDATE, PPTE_DELETE. This will somehow be an ABAP implementation of the active record pattern.
Every time the user switches from one tab to the other, in the PROCESS_BEFORE_OUTPUT method of the second tab (automatically generated and called method in the component controller, created by implementing the IF_FPM_BUILDING_BLOCK), the READ method from the business class should be called via the subsystem in order to refresh the screen data from the buffer. This way, even if the subsystem is reinstantiated, the buffer data will always be consistent.
The following steps are a very lightly introduction on how to obtain the results described above:
We have the requirement to add an additional tab for project escalations, called ‘Escalation Overview’. All escalations created in a project should be stored in the underlying database in a customer table:
The tricky part is, we want to be able to create new escalations from another tab, let’s say, project structure. Via a customer button:
The issue is, after pressing this button and navigation to the ‘Escalation Overview’ tab, the subsystem is reinstantiated. Basically, all data that was not saved is lost.
Now, by implementing the persistent layer with the business class, there will only be one instance of the buffer per session.
As described above, we need to create a structure type for the buffer with the additional control field:
In this scenario, there will be multiple escalations for a single project (multiple to one relationship) so the buffer will be represented by an internal table. We also need to create a table type for the structure above. We’ll call it YPPM_EM_TTY_DATA.
Here, MO_OBJECT is the reference to the business class (necessary for the Singleton). And MT_DATA is the buffer represented by an internal table.
Among other methods, the class also has:
GET_INSTANCE – in order to retrieve the Singleton.
CHECK_BEFORE_SAVE and SAVE that will be later called by the subsystem.
When writing the coding for the Create/Read/Update/Delete methods, keep in mind the control field (DBOP).
Here’s a small example:
loop at it_data assigning <fs_create>.
append initial line to mt_data assigning <fs_data>.
move-corresponding <fs_create> to <fs_data>.
“generate guid for escalation
<fs_data>–escalation_guid = cl_system_uuid=>create_uuid_x16_static( ).
<fs_data>–created_by = sy–uname.
<fs_data>–created_on = sy–datum.
“set control field
<fs_data>–dbop = ppet_dbop–insert.
The idea behind this control field is a much more improved performance at save.
All the functionality of the business class should be connected to the UI via the subsystem. The reason behind this is to have some sort of bottle-neck between the front-end and the back-end. To have a clearer overview of the hole process, I advise you to name the subsystem methods the same as the methods in the business class:
An example of the CREATE_DATA method in the subsystem:
lo_bo type ref to ycl_yppm_em_bo.
lo_bo = ycl_yppm_em_bo=>get_instance( iv_project_guid = mv_project_guid ).
if lo_bo is bound.
lo_bo->create_data( it_data = it_data ).
In conclusion, when a user will create a new escalation in the project ‘Structure’ tab, the method CREATE_DATA from the subsystem will be called and a new entry will be inserted into the buffer. Afterwards, when the user will navigate to the ‘Escalation Overview’ tab. The following code will run in the PROCESS_BEFORE_OUTPUT method from the component controller:
This is a really simple example, but it can be use as the basis for more complex scenarios.