Know when all templates in repeater are loaded

Title says it all,

Is there a way to know when all the templates in a template repeater are loaded and run a script when that becomes true?

I have a template repeater that repeats a Label next to a Text Field. This arrangement creates a form users can interact with. When user’s push a button the data is stored in a SQL table. When users open the form back up, I would like to populate the data from SQL back into the form. I can successfully do this with a button, but I’d like it to be automatic if possible. I can also do it automatically if I put the repopulating code in a invokeAsynchronous command with a 2-3 second delay, but some of our production machines take longer than that, making this an unreliable method.

I’d like to believe that there is a simple flag somewhere so I can know when the template repeater is finished. I just don’t know where to find it if it exists.

Gateway Java Version: 1.8.0_151-b12
Ignition Version: 7.9.6

Not sure if this will work 100%, but you could put a boolean custom property on your template repeater, and then build a wrapper for your SQL query that triggers the bool at the end of it. Something like this:

//it's pseudocode
//wrapper function...
def myfunction:
	getSqlData()
	populateRepeater()
	event.source.....getComponent("myTemplateRepeater").doneLoading = 1
//main script body...
invokeAsynchronous(myfunction)

and then put a property change script on that bool, and set the bool back to 0 when the property change script is done.

You might want to have another bool custom property to use in the script at the beginning of it, like this:

//it's pseudocode
//wrapper function...
def myfunction:
	event.source.....getComponent("myTemplateRepeater").startedLoading = 1
	getSqlData()
	populateRepeater()
	event.source.....getComponent("myTemplateRepeater").doneLoading = 1
	event.source.....getComponent("myTemplateRepeater").startedLoading = 0

//main script body...
if !event.source.....getComponent("myTemplateRepeater").startedLoading:
	invokeAsynchronous(myfunction)

This way you won’t accidentally start your script multiple times in different threads.

1 Like

That’s a good idea. I had been setting the template repeater ‘templateParams’ based on a SQL property binding so that solution never crossed my mind. I also figured out a different way earlier this morning.

I put this script on property change for the template repeater

if event.propertyName == 'templateParams':
	from time import sleep as sleep
	def async():
		while True:
			l = len(event.source.getLoadedTemplates())
			if l>2:
				populate()
				break
			sleep(.1)
	system.util.invokeAsynchronous(async)

Since all the repeated templates seem to be loaded in one batch, this works well.

Thanks for your solution as well. If mine shows any unexpected side effects, I’ll try yours.

Y’all are treading on dangerous ground… you cannot safely assign to component properties from an async function nor call any component methods. You are going to freeze your client/designer at some random point. Assigning to those booleans from the async function will make the propertyChange events you define on them run in the background. Do not do this!

Is there a safe way to know when the template repeater is finished loading all templates?

Does invokeLater carry the same dangers as invokeAsynchronous?

Aww shucks; I started writing this and then took lunch and now it might not be relevant. pturmel is a good programmer, so his solution will probably be the best thing for you. This is what I was gonna say:

Glad you found a solution that works. I would just caution you, though, that you might want to put another condition on your “if event.propertyName == …” statement. If you change template parameters multiple times before getLoadedTemplates()>2 then you may end up with several running threads. Also, if you exit the window before your templates load, you might end up with a ghost thread that never clears itself. I’m not sure how Ignition handles those scenarios, but you might be able to test it with some clever use of print and Logger.

To avoid having those kinds of problems, I recommend taking a cursory look at mutexes and semaphores in programming. Basically, when you want a thread to perform some function, you want to use a bit as a lock somehow so that no other thread will attempt to perform the same function at the same time. Also, in this case where we are shifting contexts often, it’s a good idea to put in a thread watchdog or something to make sure you don’t have runaway threads. Maybe instead of “while true” you could put something like “for k in range(10000)” so that eventually the thread is guaranteed to die.

Not off the top of my head. I’d probably attack the problem by using a propertyChange event in each template monitoring componentRunning, which then uses invokeLater on a custom method on the repeater which checks all of the loaded components.

No. In fact, one of its purposes is to provide a way to safely send information back from an async function to the foreground. Please re-read the docs and examples on these two functions, and search the forum for “later.py” for some useful helper functions.

Thanks @pturmel and @zacslade for your ideas.

Phil, I did get it working exactly how I wanted with no invokeAsynchronous commands. You mentioning the componentRunning property was exactly the hint I needed to figure out how to proceed. Thank you for that, and thanks for helping me avoid a future train wreck.

Zach

2 Likes

@b_zacht What exactly did you do to get this working the way you like? I tried having a propertyChange script that runs when:

event.propertyName == “componentRunning”

but when try to then get the loaded templates:

tpRepeat = event.source.getLoadedTemplates()
there’s no templates loaded in that variable. Did you do something more than just filter by the property “componentRunning?”

can you explain how you did this. I need to know when all the templates have loaded

I switched the way my project worked. Instead of performing a top down action when all the templates were loaded I followed @pturmel’s advice and had each template load its individual data when its individual ‘componentRunning’ property transitioned to true.

I still don’t know how you would figure out that every component is running, but in the end I didn’t actually need to know.