This is a class of problem I’ve faced many times and would like to try to figure out the “right” way to handle.
I have a Popup window that is populated by a root container dataset property called initialData
. Each component on my window that is populated by it has a property called initialValue
that is an expression of the form try({Root Container.initialData}[0, 'someDBColumn'], 'defaultValueForNewForm)
and this works nicely - if there is some thing to load, it loads, and if there isn’t, then the form is in addMode
and I can set my default value for the form as well. The relevant property of the component is then bound to this initialValue
property - selectedValue for dropdowns, text for textfields, etc.
Sometimes in my UI, when a user changes something, I want to set preset values for them based on some other dataset to fill out part of the form. They can then modify this if they want, but much of the time they won’t be changing it so it helps them fill out the form quicker/more accurately.
For example, when they change a Vendor
, I use the selectedValue of this dropdown in a namedQuery binding for a root container property called vendorData
, grab stuff from it and populate components as necessary like this
with this script
allowCalcUpdates = event.source.allowCalcUpdates
if event.propertyName == 'vendorData' and allowCalcUpdates and event.newValue.rowCount > 0:
ds = event.newValue
remitAddress = ds.getValueAt(0, 'remitAddress')
event.source.getComponent('remitToAddress').text = remitAddress
paymentTerms = ds.getValueAt(0, 'paymentTerms')
event.source.getComponent('termsId').selectedValue=paymentTerms
taxCode = ds.getValueAt(0, 'taxCode')
event.source.getComponent('taxCode').selectedValue = taxCode
shipViaId = ds.getValueAt(0, 'shipViaId')
event.source.getComponent('shipViaId').selectedValue = shipViaId
vendorOrderingContactId = ds.getValueAt(0, "orderingContactId")
event.source.getComponent('vendorContactId').selectedValue = vendorOrderingContactId
allowCalcUpdates
is a boolean root container property. I use it on a bunch of property change scripts as a condition. If it is true, then when a user changes this drop down for instance, then we do set the presets. If its false, we just skip that part - this then allows the window to load data from the db that may not fit the presets without triggering anything.
The idea is the window starts with allowCalcUpdates
as false, the data loads from the db, then it is true, so that if the user then makes any other changes that would do presetting of values - we then allow it. But I need it off before/while the db loads so I don’t overwrite custom changes.
I have tried this two ways.
The first one I know is racey but it works 99% of the time, but when someone hits a bad piece of network lag it fails. The first method is this script on the internalFrameActivated extenstion function
system.gui.getParentWindow(event).getComponentForPath('Root Container').allowCalcUpdates = False
def updateCalcProperties(event = event):
import system.gui
system.gui.getParentWindow(event).getComponentForPath('Root Container').allowCalcUpdates = True
system.util.invokeLater(updateCalcProperties, 1000)
So this provides 1 second for the initialData
to load and the initialValue
properties to evaluate, and populate the screen and then turns allowCalcUpdates on, so if the user makes any more changes, we are using presets. This most of the time, but recently had an issue where the database was slow one day, and this was failing a lot due to the long query load time - customized values that were already saved being overwritten by preset values.
My second method I tried be a little more programmatic about it and to try to catch when things are loaded. I made a new root container property called datasetsLoaded
with the expression
len({Root Container.initialData})>0
&&
len({Root Container.projectData})>0
&&
len({Root Container.locationChoices})>0
where these are all datasets that help load up the screen.
I then change my allowCalcUpdates from being a a non-binded property that is only written to via that internalFrameActivated script to an expression like this (and commented out my internalFrameActivated script)
if({Root Container.addMode},
//if a new form, we can enable the presets imediately
True,
{Root Container.datasetsLoaded})
But I realize too this doesn’t provide enough time. The datasetsLoaded
turns true sets allowCalcUpdates
to true before the component bindings finish and then the propertyChange events are running.
So if I save with this custom value
on reload with this programmatic way I see this
In both instances of opening it you see the preset address, in the second instance you can see the customized address first and then see it overwritten via the root container script.
So it seems like even with the datasetsLoaded
expression, I would need to catch that in the root container property change event, then do another system.util.invokeLater(setAllowCalcUpdatesToTrue, 1000)
and I am back to just guessing/hoping things are loaded.
I think the datasetsLoaded method with a system.util.invokeLater seems more robust, but I am still just hoping the timer is enough - if the client for some reason really slows down randomly at the wrong time, it too would fail.
Is there a way to accomplish this without introducing some race condition via system.util.invokeLater()?
Tagging @pturmel - wonder what your thoughts are on this.