HCM Processes & Forms: CRUD with FPM tables/lists – part 1 : the Basics and Standard Events
Eventually, anyone working with HCM Processes & Forms will encounter the need to create a “table” in their form interface. It might be needed to display all the wage types that make up Basic Pay or maybe all recurring payments made to an employee. Just like that lingering student loan, you can try to avoid it as you may….but oh yes….”data in table format” will find you!!! It will be at this time that you also realize how pretty complicated SAP has managed to make this seemingly straightforward, simple idea play out in our HCM Processes & Forms (HCM P&F) world. I explained this “long ago” in the “old” Adobe Interactive Forms world we lived in (HCM Processes & Forms: The Trouble with Tribbles…er, Tables ), but thankfully, we have managed to evolve a bit in the “new” FPM form option now available.
The first thing to understand and “wrap your head around” is how a “table” is actually represented in the HCM P&F framework. If you are familiar with defining our “form fields” in configuration of our form scenario, you have noticed that there is no where in there to define a “structure” for table rows or anything like that. You have to think of it in a slightly different way.
You define your “form fields” as we all know and love as:
But then, you need to visualize them a bit differently. Imagine you form fields as actually “columns” in your table.
As you see, the one thing that allows us to know which “row” of our table we are referring too is the index number. For example, if we want row 2 in our table, we would have to look at each form field that has an index value of 2. Otherwise, there really is nothing that enforces our columns staying “in synch” with one another (as you might find if you manually add a “row” and forget one of the columns).
In our FPM forms, a “table” is considered a “list” component, so you will actually create a “configuration” of the standard FPM “list” component FPM_LIST_UIBB_ATS. When you create your configuration, in the FLUID design tool, you will actually “bind” your form fields to the table columns. I will not cover that here as it is basic FPM form development. However, for CRUD (Create, Read, Update and Delete) operations, let us look at how we add this functionality to our table.
To add the buttons for our operations (add and delete), we switch to the menu view of our list configuration and click “add toolbar element”:
Then we select from the standard “user events”:
Once we add the “Add Row” button, we can make settings to it’s attributes (and we can see the standard USER_EVENT_ADD_INDEX_ROW event as it’s “action”):
Similarly, we add a button for “delete”:
We can see and change it’s attributes as well.
We can see it is tied to the USER_EVENT_DELETE_ROW “action”. In runtime, this now appears as:
But what if we want to add an icon to our “Add Row” button? Let’s go back and add a little icon on the “Add Row” button. We simply click the “Image Source” dropdown and select from the pop-up window:
There are lots of standard options. We select one and save.
In runtime, we now see a “fancier” view as:
Since we are using “standard” events, these are caught and handled in the standard “feeder” class.
…and more to the point, it all is handled in this method:
But now…..on to the operations!!!!
Create (add row)
If we look within the feeder class, we will see this particular handler for our “add row” event.
…where c_user_event_add_index_row is a constant class attribute defined as:
So we see it calls method ADD_ROW_AT_INDEX. From the comments in this method:
This method is called to add a row after selected index for Simple LIST provided the row selected and row after the selected row is not blank. If both this condition meets then only we add a blank row after the selected row. For doing so we directly add the records for the new index in A_FORM_DATA and rearrange the indices appropriately. This method is only provided for Line type structures in PA or table type infotypes in PD as in that case only we are allowing to add a row at any index and also we can select multiple rows and add rows after that provided the above given two conditions does not fail.
So when the user clicks the “Add Row” button which fires the standard “user_event_add_row_index” event, this is handled in the feeder class.
This calls class CL_HRASR00_FPM_FEEDER method ADD_ROW_AT_INDEX via method IF_FPM_GUIBB_LIST~GET_DATA. This will add a row under the selected row (and resort indexes) or add to bottom if no row is selected.
Let us look at the “simple” case. We start with an initial row, so we enter some data in it to make it unique (ie. has value):
We then select the “Add Row” button.
After clicking it, we get a new row added underneath the previous row:
This is all handled for us “auto-magically”!
But let’s show a bit more complex example to show you what it will really do. Here, I have defaulted some values into my table:
If I click “Add Row” now, it simply adds a new row to the bottom for me. The nice part is that it has also handled adding this new “row” into our dataset as well (unlike the old days where the ISR interface could not handle that for us!)
But the additional “nice’ thing this event will automatically do for us is to add a new row at a specific point in the table also! (ie. “Add row at index”). For this, we must first select a row:
Then, we click the “Add Row” button and we will see:
Again, the feeder class handles adding the “row” at the specific index and then reordering all other rows for us. MAGIC!
Read (read row)
There is not a whole lot to cover on “read” as it is handled either by using “standard” configuration to the standard services (SAP_PA, SAP_PD, and SAP_PT) or your own generic service. This is how you “default” your values into the table. Now, if you would like to “read” the row the user currently has selected (for example, if you wanted to display a “details” section under your table to show more information about a selected row.), well, sorry to say, there really is not anything “standard” as far as I know (though I do cover how to detect the “selected row” later in part 2 of this series!).
Update (modify row)
There is nothing directly “standard” for updating a particular row other than how you layout your column definitions. For example, if you want to allow the user to update a field, you would just define the control used for the column (“display type”) as an input field or drop-down list or whatever. Because your field is bound to the list/table, it knows automatically which index you are updating. There is really nothing you have to handle from there.
Delete (remove row)
The delete operation is an interesting one in that you might be looking at the wrong one if you are not careful! This is due to how SAP has named it in the feeder class code. Remember that our event is USER_EVENT_DELETE_ROW ,so be careful that you are looking at the right one. Looking at the class attribute, we find:
…and the constant name might look wrong at first. Do NOT get it confused with (which gets called on line 297):
Our code for “delete row” is actually handled here:
So when the user clicks the “delete row” button, this calls class CL_HRASR00_FPM_FEEDER method DELETE_ROW_SIMPLE via method IF_FPM_GUIBB_LIST~GET_DATA. This will delete the selected row from the table and re-order the indexes on remaining rows.
Let’s start with a bunch of initial rows.
Now we select a row and click the “Delete Row” button.
Here is the result….as expected, our row is removed.
The nice thing to notice it that it does reorder our indexes for us as well:
This is our “index” column before the change.
It then deletes the selected row (index 4)
And finally, reorders the remaining ones (you can tell because our “Values” are now off from the true index itself).
Limitations and last thoughts…
As you have hopefully seen, SAP provides some very nice “standard” events for most all of our CRUD needs and feeder class code to handle those. We can add rows and delete rows quite easily without having to put any additional code into our own process configuration via “do operations” in our generic service or what have you. However, there are some drawbacks to this “automatic” handling for us. This is not an exhaustive list and is just off the top of my head….
- No way to validate before row is added. For example, what if we do not allow more than some maximum number of rows?
- No way to handle default values intelligently. What if we want our new row to have particular default values based off of some business logic?
- No pre/post logic allowed. We cannot really do anything before or after we add the row. For example, what if we wanted to display a message about the new row?
- No way to catch/trap/validate a row marked for deletion. What if we do not want our user to delete “existing” and only allow them to delete rows they added as “new” entries?
- No “follow up” on delete. What if we wanted other things to occur after a delete? Maybe we want a recalculation of some total.
If you want better handling of these events, well….it is time to take matters into your own hands….which is what we will discuss in…