Bidirectional Bindings

I’m on 8.1.37

As I’ve written this, I think I solved my original question of why this behavior happens, but I’ll leave this here to provide some context. I’ve got some custom properties on a view that handle the information for the components on the view.

Custom Properties
self.view.custom = {
  'dataGettingObj':{
    'data':[],                          # This is bound to a query that takes a single parameter from a dropdown of available lines, query works well
    'timestamp':'12/23/2025 10:40:00'   # This is set by script transform
  },
  'dataDisplayObj':{
    'data':[],
    'timestamp':'12/23/2025 10:40:00',
    'getNext':False
  },
  'lastLoad':'12/23/2025 10:40:00'      # Set on request to retrieve (Setting getNext in dataDisplay)
  'loading':False                       # Driven by Expression Binding: if({view.custom.dataDisplayObj.getNext}, dateDiff({view.custom.dataDisplayObj.timestamp}, {view.custom.lastLoad), 'ms') > 0, False)
  'line':1                              # Undriven, but there's a dropdown that has a bidirectional binding to it.
}

The dataGettingObj has a query binding directly on it that retrieves data at a rate of 10 seconds, which has a script transform return {'data':value, 'timestamp':timestamp}. It also has an onChange script.

onChange Script
if self.view.custom.dataDisplayObj.getNext:
  self.view.custom.dataDisplayObj.getNext = False
  self.view.custom.dataDisplayObj.data = currentValue.value.data # I've also tried a direct reference to self.view.custom.dataGettingObj.data
  self.view.custom.dataDisplayObj.timestamp = system.date.now()
  

There is a dropdown that provides view.custom.line through bidirectional binding. It has an onActionPerformed event that runs a script self.view.custom.lastLoad = system.date.now()

Sometimes, if the properties that are driving the query change, then the dataDisplayObj will be changed before dataGettingObj has the data. This has a couple effects:

  1. It will get stuck on loading (hence the if statement wrapping the dateDiff which “fixes” it)
  2. It will have the result for the previous settings (this is settings for Assembly Line running parameters stored on the database, so it’ll show the results for the previously selected line, or an empty list if none was selected

What I’m stuck on is that there is a race. Most everything is triggered off the changes of the previous step, except for the bidirectional binding that starts pretty much all of it. That one event script and bidirectional binding race and can cause this one edge case if the event script loses. Most of the time, the query takes long enough that lastLoad is after dataGettingObj.timestamp, but it’s not guaranteed.

What is the best way to have the behavior of bidirectional bindings and not have this script race?

I could move the query binding to a script that gets ran with the parameters at the moment, so that I could pull it into a script instead, so that it doesn’t auto-refresh when the parameter changes, and instead synchronously runs on the script after setting the loadTime and such. Doesn’t entirely feel good, but is that the best way to do it?

Sometimes you just have to force the order of events to prevent races.

That would make sense. Originally, I was having troubles even tracking where the race was, which is why I started the post. Something, something, rubber duck and such.

I know it’s pretty much completely not feasible, but it’d be nice to have something that visualized the process flow that Ignition has. A lot of what happens synchronously and asynchronously seems like it’s really only documented in these forums, unfortunately, though maybe I just haven’t seen the right place.

I would ditch all of those properties and timestamps and booleans.

Make your query (named query?) include the line value in each row. Ignore or hide it downstream if not needed.

Bind your NQ to view.custom.data with view.custom.line as a parameter, and your 10-second polling. Keep your existing setup for view.custom.line.

Add an expression binding for a boolean view.custom.valid like so:

{view.custom.line} = try({view.custom.data}['line'], 'nonExistentLine')

Only use/display the content of view.custom.data when valid is true.

No script. No race.

You know, I think I used a similar format for a project like a year ago, and I have no idea why I didn’t just go with that format. Each row containing redundant info feels bad for keeping the table data limited, but it’s never more than 1000 rows anyway, so it definitely doesn’t concern me that much

Do make sure your View Custom properties are set to private, to avoid unnecessary browser traffic.

Finding yourself dealing with racy scripts is a hint that you shouldn't be using scripts. Scripts should be a last resort when tag bindings, property bindings, and expression bindings cannot solve your problem. (One of the reasons my Integration Toolkit exists--solve more problems without scripts.)