Perspective bindings updating will null parameter data

I’m currently working on a popup view in Perspective that gets a few parameters passed in. I then have a few additional custom properties that are bound to the parameters as well as a few other component properties on the view using an expression structure binding with an associated script transform.

Using this method I have been getting very weird results. Based on the debugging I have done so far, this is what appears to be happening.

  1. View loads
  2. Bindings on the custom properties fire but pull in null values from the parameters (instance 1)
  3. Parameters are initialized on the view
  4. Bindings on the custom properties fire again since the binding values changed (instance 2)
  5. The instance 2 binding script finishes executing and writes the output value to the parameter
  6. The instance 1 binding script finishes executing and errors out the parameter due to null data

This is not what always happens as there are some obvious race conditions happening here, but my question is why? Why are my bindings firing before the parameters on the view are even initialized? Is there a way to implement some kind of “If not initial change” function like can be done on change scripts? This is not the first time I have run into this and it is extremely annoying to debug.

Is there any chance you could supply the view you are using for the popup and an example of you you’re opening the Popup (Script or Action and what param values are being passed)? It’s not clear from your description what exactly is happening. It sounds like you should provide some sort of value checking in a transform of the binding to handle null/None, or perhaps provide a default value for the param. It’s hard to tel without knowing the exact use-case and what is allowed/expected. Is your problem that the property does not exist (is not Persistent), or is your problem that the value of the property is evaluated before the param value is applied? Also, what version are you using?

Sorry to resurrect a dead thread, but we're debugging some bad behavior on our project and have tracked it down to what we think is this issue.

In one view, we have a button that calls a script like this:

	system.perspective.openPopup(
		'AirQTile-settings', 
		'RangerTiles/Settings/AirQSettings',
		params={
			'field_name': self.view.custom.field_name,
			'node_reference': self.view.custom.node_reference,
		},
		showCloseIcon=False,
		modal=True,
		overlayDismiss=True,
		draggable=False,
		resizable=False,
		viewportBound=True,
	)

Then, in the popup view, we have inputBehavior set to "replace", and a property bound to an expression of the two parameters like this:

In the browser debug console, I see one or more prints every time I open the popup. If there's only one, both parameters will have the proper values. If there's more than one, the extra prints will have null for one or both of the parameters. On occasion, the extra prints with nulls are shown last, and I have evidence that the binding is also calculating with those bad values last.

I could refuse to run most of the work when the binding is triggered with a null input, but it has to return SOMETHING into the property it is setup on.

So, I guess these are my questions:

  1. Is there some way to not have the binding trigger with the NULL parameters?
  2. Is there some way to have a binding not change the value it is setup on and just leave it as it was before the binding triggered?

(On Ignition 8.1.42)

UPDATE:

It occurred to me that I could achieve #2 in my questions by just making a dummy property which is bound to the expression structure and optionally writes to the real property that the binding used to be on in a script.

Further, I realized I could do everything in an onChange script of that dummy property instead of a binding transform. Trivial difference, except that it lets me see previous and current values, and all of them as qualified values.

So, I setup a new property with the same expression structure binding as above, but no transform script, and then put an onChange script on it that looks like this:

def valueChanged(self, previousValue, currentValue, origin, missedEvents):
	system.perspective.print(u'previous={}'.format(previousValue))
	system.perspective.print(u'current={}'.format(currentValue))
	# ... lots of other script work here ...
	self.custom.original_property = what_the_script_used_to_return_in_the_original_binding

And I then open/closed the popup over and over and I still get the duplicate prints in some openings.

When I get the duplicate triggers, I saw this in the console:

previous=None PerspectiveClient.4c66478bafda3caeebe1.js:2:473641
current=QV[{node_reference=[demo/353785720527380, Good_Unspecified, Thu Oct 24 16:33:01 UTC 2024 (1729787581240)], field_name=[null, Uncertain_InitialValue, Thu Oct 24 15:06:46 UTC 2024 (1729782406021)]}, Good, Thu Oct 24 16:33:01 UTC 2024] PerspectiveClient.4c66478bafda3caeebe1.js:2:473641
previous=QV[{node_reference=[demo/353785720527380, Good_Unspecified, Thu Oct 24 16:33:01 UTC 2024 (1729787581240)], field_name=[null, Uncertain_InitialValue, Thu Oct 24 15:06:46 UTC 2024 (1729782406021)]}, Good, Thu Oct 24 16:33:01 UTC 2024] PerspectiveClient.4c66478bafda3caeebe1.js:2:473641
current=QV[{node_reference=[demo/353785720527380, Good_Unspecified, Thu Oct 24 16:33:01 UTC 2024 (1729787581240)], field_name=[DEVAIRQ, Good_Unspecified, Thu Oct 24 16:33:01 UTC 2024 (1729787581240)]}, Good, Thu Oct 24 16:33:01 UTC 2024] PerspectiveClient.4c66478bafda3caeebe1.js:2:473641

So, it looks like the "Wait on All" is still allowing Uncertain_InitialValue through in this case...

At this stage, I can just filter them out on my own, but I feel like it might be a bug that I have to with "Wait On All" enabled.

UPDATE2:

Our final onChange script looks like this:

def valueChanged(self, previousValue, currentValue, origin, missedEvents):
	VALUE_FIELDS = ('node_reference','field_name')
	if not currentValue.getQuality().isGood():
		return
	value = currentValue.getValue()
	if any((vf not in value or str(value[vf].getQuality()) == 'Uncertain_InitialValue') for vf in VALUE_FIELDS):
		return
	# Everything that used to be accessed as "value.field_name" in the old binding script is now accessible like "value['field_name'].getValue()"

	# ... Remainder of original binding script goes down here ...

With this tweak in place, I have not been able to get our bad behavior that I was debugging to happen again.

Again, I feel like this is exactly what the "Wait On All" was supposed to be preventing. Did I misunderstand how it worked or is this a bug?

From the docs:

Indicates whether the binding should wait for every expression binding in the structure to finish before completing. If false, each expression in the structure will resolve individually and update their properties at that time. If true, all component properties will receive their new values at the same time.

Now, your bindings here are against properties. The moment those properties are found, their value is used for the expression. Wait on All does not wait for the values to be of any quality or non-null value. The true value of Wait on All could more easily be seen if one of your expressions within the structure was reaching out to a project script which took 2 seconds to execute; Wait on All would wait until the script - and therefore the expression - returned a value.

1 Like