X

Technical Articles relating to Oracle Development Tools and Frameworks

Adventures in Mutation - Adding Rows to a Table or ListView in Oracle Visual Builder

Duncan Mills
Architect

Recently there have been a lots of questions from the Visual Builder community relating to how to display both a collection based UI such as a Table or ListView  and a data entry form for that collection on the same screen.  Something like the simple To-Do list application shown here:

How such a screen can be implemented, depends on the Data Provider being used by the table or ListView. If an ArrayDataProvider is used then the add action chain would usually have three steps:

  1. Call the rest endpoint (usually a POST) to add the new row at the back end 
  2. Call a page function that takes the new todo task and the array backing the ArrayDataProvider, pushes the new task to the array and returns the updated array as the result of the function
  3. Call an assign variable acton to copy this result back on top of the backing array.  This will have the side effect of updating the Array Data Provider and in turn the table will be refreshed.

If the Data Provider for the table is a ServiceDataProvider though, we'll often see a pattern like this for the Add button action chain:

 

So here, there are just two steps: the first to call the relevant POST to update the backend with the new task data, the second is  a Refresh Data Provider Event Action.  You'll find that this works just fine, however, there is a catch relating to the possible performance cost this this approach and other side effects, so let's learn some more:

Lesson 1 - Refresh does not actually "Re-Query" the Data Provider

It's a common misconception that the refresh data provider causes a provider to "re-query" its data by calling the associated endpoint on the REST service, this is not strictly true. All refresh is is an event associated with the Data Provider object but which as no direct effect on the DataProvider itself.  What actually happens is that components such as the <oj-table> that are bound to the Data Provider will be internally listening out for Data Provider refresh and mutation events. When they see a refresh event they will generally grab an iterator over the Data Provider data set to get all the data again (including any updates, inserts and deletes).  Now, because the Service Data Provider does not actually cache any data itself, it will need to go back to the service with a GET call to get the data from scratch. So it appears that the refresh event caused a re-GET of the data, but in reality this is just a side effect of the event. You might also run into secondary side effects such as the component resetting its scroll point or forgetting it's current selection - all because it's doing what you told it to do. 

So, why is this interesting?  Well in the case of a table backed with a Service Data Provider we can see that the simple two step action chain used to add the new task is actually quite expensive. We have one roundtrip to POST the new to-do task to the  backend and then we throw all of the task data we already have away and get it again from the back end with a fresh GET request.  In some cases this can be desirable because you really want to have an up to date picture of the state of the dataset on the server, sometimes, however, there is really no need to refresh the data you already know about. 

This is where the other mutation events come in.  In this article I'm concentrating on the add event only, but I'll cover update and remove events in a later article.

Lesson 2 - The add event is Purely Local 

Another misconception we see is that the add (or update or remove) Data Provider event somehow should update the back end service with changes.  This is not the case.  These events are just like the refresh event, the only effect they have is on components that are bound to the Data Provider in question. So really you can think of  a Data Provider add event as being a message to any component that is interested to tell them that there is a new row and the information that is in it.  These components can then react by internally adding the new row without having to go back to the server to get it.  This of course is much more efficient!

Reworking the Example 

So we have a minor change to make to the action chain, rather than selecting the refresh option on the radio buttons for the Data Provider event we choose Mutate and then it's all about the payload for the add event.  You can find the documentation for the event payload in the Oracle JET documentation for DataProviderAddOperationEventDetail. But I'll explain what the elements of the payload are here in context.  If you define a Mutation Data Provider event and then click assign and expand Add, this is what you will see:

I'll start with the simplest of these, the data element.  This simply needs to be set to the record that you are "adding". In most cases (such as when using the built-in business objects layer) this will actually be contained within the information returned from the POST call you used to create the new row in the first place.  The data will be an array because you can add multiple rows in one go should you wish to. The twist to watch out for is, in the typical case, this data object should actually be an items array inside of the data element - this matches itemsPath defined for the Service Data Provider. If your Service Data Provider has a different value for itemsPath then you should use that.  In this case, I'm using a standard Visual Builder Business Object and my REST POST action was called callPOSTNewTask, and so the data element of the add payload is:

{"items":[ $chain.results.callPOSTNewTask.body ]}

Make sure that the Expression radio button is selected when entering this, not Static Content.

Next we have to map the keys element. This again is an array containing the keys of the row(s) we are trying to add, in the same order as the contents of the data.items[] array if there is more than one. As before, we can get the correct key ID for the POSTed row from the return payload of the POST REST action:

[ $chain.results.callPOSTNewTask.body.id ]

Again, make sure that the Expression radio button is selected when entering this.

Finally we need to tell the Table or ListView where to add the new row.  This can be specified in one of two ways - either using the indexes element to indicate an absolute position or, in the next version of Visual Builder, using the addBeforeKeys element to indicate an insertion position which is relative to a particular existing row. Again, both of these elements are arrays so that you can add multiple rows in one go.  In this case I just want to add  the new task to the bottom of the table and I can use the special value of -1 for the index to achieve this (using 0 would insert at the top):

[ -1 ]

This time its OK if Static Content is selected.

If you switch to JSON code view for the Data Provider Event action it then looks like this:

                "dataProviderAddMutationEvent": {
                    "module": "vb/action/builtin/fireDataProviderEventAction",
                    "label": "Add Mutation Event",
                    "parameters": {
                        "target": "{{ $page.variables.tasksListSDP }}",
                        "add": {
                            "data": "{{ {\"items\":[ $chain.results.callPOSTNewTask.body]} }}",
                            "keys": "{{ [\n $chain.results.callPOSTNewTask.body.id\n] }}",
                            "indexes": [
                                -1
                            ]
                        }
                    }
                }

And visually the action chain looks like this:

One nice side effect that you'll see of using this add mutation event is that when it happens the Table or ListView will animate the addition of the new row, so as well as being more efficient, it also provides a better user experience. 

 

Be the first to comment

Comments ( 0 )
Please enter your name.Please provide a valid email address.Please enter a comment.CAPTCHA challenge response provided was incorrect. Please try again.