/ Development  

Update multiple items in a grid (aka bulk-update)

Hi there AppWorks fans,

Welcome to a new installment of AppWorks tips.

Maybe you noticed already (or maybe not!?), but when you select multiple entity instances in a grid-component on a form, you only have the default actions available; like ‘Clear’ or ‘Delete’!? Have you ever asked yourself the question of how to execute (for example an action-type of ‘Rule’ BB) on these multiple selected items in that grid? Well, try it out and make the conclusion the action rule is only visible on a result list panel and not the grid-component…WHAT??? Yes, my friends…That’s the curve-ball we will solve in this post…

Let get right into it…

We start with an overview of what we try to build here. The most important thing to understand is making sure the solution gets a grip on a set of entities belonging to each other as we would like to update them (in bulk) with the same information! “Creating a set of entities” equals the principle of a to_many relation from a parent entity, or making one entity instance “primary” and select others to follow (again from a relation…to itself?)….Let’s see what we can produce!

For this post, I use the most common way with an entity ‘Video’ containing a ‘to_many’ relation to_many_video_profile to a ‘Video profile’ entity. I just follow my own blogpost with a simplified version. The ‘Video profile’ entity only has a vp_name property. The ‘Video’ has this set of properties; Just to have something nice to play with:

Label Name Type
Title vid_title Text
Subtitle vid_subtitle Long Text
Poster vid_poster Image
Genre vid_genre Enumerated Text
Rating vid_rating Integer

Make sure to add some interesting values for the Enum-property

The ‘Video’ entity will have an action-type of ‘Rule’ BB. Name the rule a_update_props with a corresponding label. Make it a dummy call by setting a property with its own value. Just like this:


To make the preparation complete, I also make these changes:

  1. Update to ‘DefaultLayout’ of the ‘Video profile’ with an extra result list panel showing the ‘All videos’ list.
  2. Filter this result list panel with an expression to only show the related ones: to_many_video_profile.Properties.vp_name={item.Properties.vp_name}
  3. Don’t forget (like I did) to add the ‘vp_name’ property of our relation as ‘filter’ column to the all-videos result ‘List’ BB! Otherwise, the above expression fails!
  4. Update the ‘Create’ form for the ‘Video’ entity and add the ‘to_many’ related ‘Video profile’ with an auto-suggested multiselect component…Like this:


Now is a nice moment to publish both entities. In runtime, create some ‘Video profile’ instances you can select, during the creation of a video:


When you now open a related ‘Video profile’ instance and select related ‘Video’ instances, you nicely see the ‘UpdateProps’ action rule passing by:


Check the ‘All videos’ List BB for that ‘Progress bar’ view on the ‘Rating’ property! To make this work nicely also set a minimal/maximum value on the property itself.

The action rule applies to ALL selected instances; So, starting a BPM will fire twice, setting a property is done for both; It’s even possible to place a modal popup in between which updates a certain (bulk) property for all selected instances. Give it a play and check its power…So, why looking further when this is working already fine? Well, that’s the next part of the post! 😉

The grid-component part

There are use-cases possible where the above result-list panel is not an option, and you’re left with the grid-component option on the form itself! To demonstrate this we add a bidirectional ‘to_many’ relation back from ‘Video profile’ to ‘Video’; This introduces a tight connection between both entities, and they are ‘aware’ of each other. So, updating the relation will be directly viewable in the UI result-list panel from the previous section! Add this relation as grid-component to the ‘Create’ form of the ‘Video profile’ entity.

After some tweaking the end-result of a ‘Video profile’ instance will look like this:


You directly see the difference in behavior, but you also see the missing part of that precious ‘UpdateProps’ action from the grid-component view (left panel)! There is our curve-bal…How are we going to update all selected grid entries when the result-list panel is not an option!? Well, I see one simple solution: Ask you analyst-guy/gal or the UI expert to change the requirement to use a result-panel. If this is still not an option, you’re doomed to implement the following workaround section.

The workaround

These are the steps for implementing a nice, but complex workaround…

The final test in runtime will look like this: Open a video profile; update the video entities upfront with a dirty flag in the grid; call a new action rule (on the video profile) with a popup to prepare bulk data; After OK it runs a BPM managing all the magic stuff…

Add “helper” properties:

  • Add an is_dirty Boolean property to the ‘Video’ entity and make it available in the grid-component. With this low-code change, you can update it before triggering a new action rule on the ‘Video profile’…continue reading…
  • Also add an extra Boolean flag called is_bulk_data on the ‘Video’ entity…We use it later on!

Expose webservice operations:

  • Expose the ‘Read’ webservice operation of the ‘Video profile’ entity (incl. the ‘Get’ operation for the ‘toMany’ video operation for a fancy BPM later on…)
  • Also expose the ‘Read’, ‘Update’, and ‘Delete’ webservice operations on the ‘Video’ entity; The ‘Read’ is required when we want to get video instance data from a new ‘toOne’ video relation on the ‘Video profile’ entity; The ‘Delete’ is for cleaning any mess afterwards!
  • The exposure of those webservices requires us to create a new service container of type ‘Application Server Connector’. Read about it here. This explains how to create a new ‘WS-AppServer’, but the ‘Application Server Connector’ has the same wizard; Just select the correct webservice interfaces for this specific service container.

Add a new relationship

  • Create a second ‘toOne’ relation with ‘Video’ on the ‘Video profile’. We “misuse” this relation to create a temporary “bulk data” video entity instance to read data later on.
  • With this new relation, we also expose the ‘toOne-clear’ operation from the webservice BB for the ‘Video profile; Also, for cleaning our own mess afterwards!


Add business logic with ‘Rule’ BBs

  • We want to flag the “bulk data” video instance with a true value on property is_bulk_data when it is related with the ‘toOne’ relationship. For this, we create a nice ‘onRelationChange’ rule with the name e_on_rel_change_set_prop_is_bulk_data to make the flag-setting-call.
  • Add an action Rule a_update_props_on_dirty_videos 🤣 (read it again a_update_props_on_dirty_videos!) to the ‘Video profile’ entity. Let it (for now) update its own ‘vp_name’ property, but later on it will start a magic BPM!
  • In the action rule, you can show an upfront modal popup. This will be a new ‘Cancellable’ form on the ‘Video profile’ entity with name frm_bulk_video_data


On this new form you expose the creation of a new ‘toOne’ video entity instance. You can show extra fields after the create-action to see if the “bulk data” flag is set to true with the previous ‘onRelationChange’ rule! Why the create-action? Well, you can’t update fields directly on a related entity if there is no instance available! We could hide this ‘creation’ part via business rules, but it’s fine for our purpose!


When you do a publication to try out the implementation you will experience some “mess” in the ‘All videos’ result-list. You have a flag to filter it out! Another option is to create a separate related entity ‘Video data’ hidden from the front-end, but that’s a choice to make. There are always “more roads to the city of Rome”.

The action rule a_update_props_on_dirty_videos will eventually trigger a short-lived BPM with this pseudocode:

var vidProfile = Read the current video profile instance
var vidBulkData = vidProfile -> Read the toOne Video instance
if(vidBulkData is "bulk data") {
var relVideos = Read the toMany videos of the profile
foreach(relVideo in relVideos) {
if(relVideo is "dirty") {
Update relVideo with vidBulkData
Update relVideo remove dirty flag
Clear the vidBulkData toOne relation with the video profile
Delete vidBulkData instance

Are you still with me?…Let’s continue the grind and learn great stuff! 😜

Implement the advanced business logic in a BPM

Now is the time to create a new BPM type of document which implements our pseudocode from above. So, from the bpms folder in the project, we create a new document of type ‘Business Process Model’ with name bpm_update_props_on_dirty_videos. Start small with just one step and make sure it’s a short-lived process; We only execute exposed webservices AND we want direct feedback in runtime when the BPM run is completed!


If you want to keep your monitoring in place, you need to update the settings in the ‘Monitoring’ tab; It’s decreased because of the short-lived option.

Our next step is making sure our BPM is triggered from the action rule on the video profile. A simple task to do:


Publish it all and try it out…Monitor the PIM for a precious first result! The next step is to extend the BPM with more activities and interesting logic. I first make sure the activities are in place, and we can do some looping. Just like this (without any webservices or messagemap updates!):



  • For a for-loop you can use the context menu by right-clicking an activity
  • Also put the iteration condition in place to have a fully running BPM
  • Make sure to connect the flows to the correct inputs (incl. the for-loop; a common mistake!…watch the screenshot again)
  • The small orange-decision icons have (for now) a static input value of true()

You can publish the BPM and have yourself a dry-run with a nice and clean view in the PIM:


Next step is putting those webservice calls in place…Right-click the activities to insert the corresponding service calls and make sure to have some view like this one:


No need for a publication now as we first need to make the messagemap in place. Have a look at this consolidated view of my configured messagemap. It’s not a hard task to do; Make sure to connect the correct source to the correct target:


After publication, you can walk through the BPM where also our ‘toOne’ video “mess” is nicely cleaned already; Only, the for-loop still has 0 results! We can fix this on the for-each iterator condition. Just like this:


Now run your test! 😎 I hope you also get those green flags in place! There is only one un-implemented feature (actual two features!). What would that be? Well, our dirty-flag check off-course (and the is-bulk-data check). In my last test all related videos are updated, but we only want the dirty ones! So a quick update on those two conditions (where the last one is most critical!):

  • ns6:ReadvideoOutput/ns6:ReadvideoResponse/ns4:video/ns4:is_bulk_data/text() = 'true'
  • ns2:Getto_many_videoOutput/ns2:Getto_many_videoResponse/ns4:video/ns4:vid_is_dirty/text() = 'true'

After a publication, I still have my green flags where I see only my dirty videos (when selected in runtime!) being updated; my ‘bulk data’ video instance in nicely cleaned; Also, the dirty flag is removed from the related video instances! It’s a party! 🎉

After some testing, I see one major bug passing by! When you don’t create that bulk data video instance and directly hit OK to start our BPM…The BPM will fail in the ‘ReadVideo’ activity because it’s not created. Well, two choices you can implement on your own:

  1. Create the instance with a business rule upfront; as already mentioned
  2. Add a check in the BPM if the instance is available and directly complete if not!

You see a lot more steps are involved on implementing that same kind of feature we started the post with. So, again the question: Do you still think you’re helping the customer for the long-run or was it a better choice to keep it simple with the result-list? Let me know in the comments…

Download my synchronized project here to have a view for yourself (includes the final bug-fix)!

Great, that’s a valuable “DONE” this time! We saw the impact on a different low-code approach for implementing a certain functionality. Is it good?; is it bad?; this all depends. The only advise I can give you; keep things simple in the first place and only start building “rockets” when your customer agrees on it, understand the full potential of the product, and collaborate in the choices made on this level of low-code craftsmanship. Have a great weekend, and we see each other in a new post; next week! Grz.

Don’t forget to subscribe to get updates on the activities happening on this site. Have you noticed the quiz where you find out if you are also “The AppWorks guy”?