If you made it through parts 1 and 2 of this series (HCM Processes & Forms: CRUD with FPM tables/lists – part 1 : the Basics and Standard Events and HCM Processes & Forms: CRUD with FPM tables/lists – part 2 : Advanced Operations), you should be truly formidable now versus FPM tables/lists. With a good grasp of triggering and catching “events” and manipulating the dataset information as you need to match said “events”, you should be able to easily look at this and know how to implement it in your own generic service:
You should also have a feeling of “wanting more”….how to share your code?….how to make this all more scalable?…..how to make it reusable across all of your processes that use table components?……oh let us just be honest here….you really want to know “how can I make my life/job easier so I can churn out some complex tables easily and in very little time so that I can take a longer lunch or go home an hour earlier?” Well….you have come to the right place for that answer! Because “part 3” of this series is going to show you how to easily “step up your game” so that you truly are in command of your HCM P&F FPM form List UIBBs! (geez at the string of acronyms there! haha).
Step 1….create your COMMON class
As mentioned in my previous blog (part 2 in this series), I like to create a custom class for all of my “common” code for my HCM P&F projects. My “common” class (which I might share one day) has methods for reading/setting dataset values, get field counts, reading process container, reading notes, creating “help” value lists, common validations, and much MUCH more. This also includes how I “catch” FPM events and related information. This is a
key in handling how my form will behave.
Similarly, it is better for us if the way we handle adding and deleting rows is consistent among all of our processes. In order to enforce this, we can create methods in our own custom class to handle adding or deleting rows to the dataset passed to us.
Create (Add Row)
If you are thinking ahead of me a bit, your first question will likely be “Chris, how can you write a method to add a row unless it knows the columns of your table and if you write it to work for certain columns, doesn’t that make it less flexible/abstract?” Well, I am glad you asked….because I faced the same issue myself when planning this out. But I think I hit upon a quite simple solution that has worked well for me in all cases…..I simply pass the method my “column structure/definition/layout”.
In the method call,
…You will notice “I_TABLE_COLUMNS”. This is a custom public type I created as,
Over in my generic service, I can reference this as,
Then at some point in my generic service before calling “add row”, I just define what my table columns are:
So now, I know exactly what the column (form field) names are in order to add a row to the table. You will also notice, I have the ability to pass a “default value” as well for my entries (or leave them empty).
Lastly, you might notice the method parameter “IO_SELECTED_INDEX”. This is an “optional importing” parameter (hence “io” as it’s prefix) that allows us to add our new row at a specific index (much like the “standard” add row functionality we talked about in part 1).
Now to the code!!! (*which as always I have as an image so as to irritate the “cut-and-paste” programmers out there..it is better to understand the code than copy it!) The code is well commented to hopefully help you follow along easier…
So now, we can add a row, add a row at a specific index, and default the values in our new row if needed….all from one very convenient, easy-to-use public method!
READ (Read Selected Row)
As we explained in the last series (part 2), a “read” on a table row is pretty straight forward. Since we know which row was selected (from our common FPM “get event” method), we know the index to use in order to read our row for each field in the row. For example, it would simply be something like:
READ TABLE service_datasets ASSIGNING <service_dataset_fs>
WITH KEY fieldgroup = ‘FG_01’ fieldname = ‘MY_COLUMN1_LIN’ fieldindex = l_selected_index.
READ TABLE service_datasets ASSIGNING <service_dataset_fs>
WITH KEY fieldgroup = ‘FG_01’ fieldname = ‘MY_COLUMN2_LIN’ fieldindex = l_selected_index.
As I mentioned earlier, my “common” class even has a “read dataset field” method that makes this even easier as well. I will leave the “how to” part of that to you as it is pretty straightforward (pass over field name, group and index….return value).
Update (Modify Row)
If you care to control updating entries in a row, it is often easiest to simply have them open for input as needed. It might be that some fields on the row should be open and some should not. You can set those in FLUID when you set the control type to use. However, what if some of these will “depend” (ie. dynamic)? Similar to “add row”, you can detect which row was selected and read your values accordingly. Then, as I mentioned in the blog (HCM Processes & Forms: Taking control of your FPM form tables…by brute force! (haha)), you can set the cell level UI attributes. One last thing I will add is that for my table row fields, I always add a hidden column (line type form field) that I call “IS DIRTY?”. I can then set that field (for example, “COL_IS_DIRTY” = ‘X’) if the user changes a field or I detect that “new value is not equal to old value”. This can be used in “rules” or in your own advanced generic service to know which rows actually need to be updated or which do not (no change made).
Delete (Remove Row)
Similar to “add row”, I have my common “delete from dataset” method.
You might notice the “IO_DELETE_CHECK_FIELD”. This is an “optional importing” parameter (hence “io”) that allows me to name a field that gets checked by my method to determine whether the selected row can be deleted or not. For example, we might not want a user to be able to delete existing “recurring payments”, but they can delete new entries/rows that they added. Therefore, in order for this to work, our code that first initializes our table (form fields) with “existing” values will need to set an additional field (“COL_NO_DELETE” = ‘X’) as well. When our “add row” code executes, it would create a new field for us too but simply not set it’s value ((“COL_NO_DELETE” = ‘ ‘). It makes this method very useful for us and flexible.
Now, this is a “one row” delete of course, but let’s talk about one that comes up now and again as well…..multi-row delete! There are TWO ways you can handle this that I will just discuss briefly (since honestly, I have only had one actual client requirement to do this and then it was even changed to single row delete). The first way is the “easy” way. Simply add a new form field…call it “row_selected” or something…add it as a column on your table….make it a checkbox…and then add a “delete selected” button with an event as discussed before. Now when your “delete selected” event happens, you simply loop through and find all “row_selected” fields that are “checked” (ie. value = ‘X’ for example) and delete/clear those rows.The second, only slightly more complicated way is to make your table/list “multi-row select” which can be done in FLUID in your “general settings” of your list. Then when the user selects multiple rows, you will see these indexes much the same way as you did before when we catch the FPM “params” values. With your list of “selected indexes”, you simply loop through and remove/clear/delete those rows as you did before with single rows. Pretty easy stuff.
Now with a combination of your new-found “CRUD” expertise with FPM “list” forms as well as how I described controlling them down to the very cell level UI attributes (“read only” or open for input) and using “extra” columns for more control (like “is dirty” or “do not delete”), you can get really creative and handle just about anything. Here is an example of one of my more complex tables using all of these “tools and tricks”….
So that is about it…and this is the end of part 3 of our 3 part series…so I guess it is that time where we part ways. I hope you have enjoyed these and found them useful. I will keep putting them out if you keep reading them. I never say “good bye” so as always…Till next time…