How to avoid using a Timer to delay an action in the UI

I sometimes find myself resorting to use of a Timer to delay a UI action that I can't get to work otherwise, and it seems like a hack that I shouldn't have to be doing, and wonder if there's another way that I'm missing. Here's the latest example...

I have a text field that is in a hidden flex container. When a checkbox is checked, this container's visibility is toggled so that the text input becomes visible. At that point, I want the text input to become focused. When I try a simple onChange script on the visibility of the container, and call .focus() on the input, it doesn't work, I'm assuming because while the visibility property itself has become true, the effect of that change has yet to cause the input to be displayed, and so it isn't yet capable of accepting focus. When I add a slight (and entirely arbitrary) delay, it does work:

def valueChanged(self, previousValue, currentValue, origin, missedEvents):
	from threading import Timer
	
	def focus(self):
		self.getChild("User ID").focus()
	
	if currentValue is not None and currentValue.value:
		Timer(0.2, focus, [self]).start()

My use of this is usually related to the lack of an onLoad event that can fire when a view is finished loading, but in this case, it's in response to a simple user interaction, and feels especially hacky.

1 Like

I'm wondering if you could use an if statement for this purpose... I've never tried before in ignition but could you possibly open a loop that releases when the component returns?
Like this:

if self.getChild("User ID"):
   #break loop and call .focus()
else:
   #continue loop

Let me know if this works out for you because I've had this issue in the past.

I'm pretty sure self.getChild("User ID") will always return True as it only tests the existence of the component, not its visibility or potential to receive focus. I did add a system print that confirms this is the case when the visibility onChange event fires.

But even if there were a way to check for the appropriate state, a loop like you suggest would block execution of other scripts/events, unless you put in a sleep, which is no better than using a Timer.

1 Like

How about putting the focus call on the OnChange for the visibility property itself? When it goes true, it would then call focus to itself? (I haven't tested this)

That's basically what I'm doing, only it's the visibility of the parent container of the input, not the input itself. Its visibility is always True, and wouldn't trigger a change script. I tried making the toggle be its visibility instead of its parent, but got the same results.

Odd. Just tested it, and yeah.. it needs a slight forced delay. I even tried chaining a couple props together to "build" a delay, and it still didn't work.

Could you do an onStartup script on your text input that checks for the condition and focuses itself, as well as an onActionPerformed on the checkbox that focuses the text input if checked? Wouldn't these cover all your bases?

onStartup is not when I want it to be focused, but even if it was visible from the outset, this would not reliably work to focus it, since that event fires before the view finishes rendering (see my request for an onLoad event).

I tried onActionPeformed but that doesn't work, either. I figured it wouldn't since it happens at about the same time as (probably even before) the onChange event that I'm placing on the visibility.

1 Like

You are trying to find a magic wand that resolves the disconnect between jython code running in the gateway and rendering occurring in the browser. Consider using Ben Musson's module to send javascript that both sets the visibility and performs the focus in the browser.

2 Likes

Workaround:
Can you change your enablement logic to utilize other Styles? Either: 1) always show the object(s), albeit in a disabled state (greyed out, etc), 2) Utilize a style which permits events (leave visibility: true, instead change opacity: 0).

1 Like

The disabled state gives the same issue, though the opacity hack should work.

1 Like

Good thinking, it does work when I use opacity instead of visible. I'm going to mark @pturmel's response as the solution, though, since it explains the fundamental issue. This solution is still a slight hack, but much better than using a Timer.

1 Like