Best Way to Return Data from Vision Popup

I have a simple "comments" button popup I want to get data back from, and I don't know the best way to do this.

I have a template repeater on the main window, and each template instance has a "edit comments" button on it. When I press that, it opens a new popup window for the user to put their comments in. I don't know what to put in the "actionPerformed" script for the "submit" button on the popup.

My first guess would be a message handler, but Vision components can't receive messages like Perspective components can. How should I go about this?

project = system.project.getProjectName()
messageHandler = 'myMessageHandler'
payload = {
	'templateID': event.source.parent.templateID,
	'comments': event.source.parent.comments
}
clientSessionId = system.util.getClientId()

system.util.sendMessage(project, messageHandler, payload, scope='C', clientSessionId=clientSessionId)
system.nav.closeParentWindow(event)

I have already thought of system.gui.inputBox, but the text is too small. A client tag dataset seems most logical to me, but it just feels like a messy way to do a simple thing.

What are you doing with the comment? If it's being written to a database or a tag, why not do it directly in your custom popup window?

1 Like

In vision, you would get a reference to the window using: system.gui.getWindow() and then write to a property to hold the data.

1 Like

I want the whole screen to be like a form where a "submit" button enters data from all the template instances. I don't want any database writing to happen until that button is pressed. That's what made sense to me. No undo-redo, just a hard submit with an "are you sure?" attached.

Okay, so that seems to solve the template to window gap, but what about communicating from the popup window to the main window? system.gui.getOpenedWindows()?

system.gui.getOpenedWindows() returns all of the currently opened windows. Since, presumably you already know the name of the window, and you know that it will be open, then there is no need to use that, just get a reference to the window.

1 Like

Is the "name" of the window just the name of the window component definition?

Yes.

1 Like

With folder names, just like you'd use with system.nav.openWindow().

1 Like

You can right click on the window, and select copy path and get exactly what you need.

1 Like

Instead of searching for windows to pass data to, wouldn't it be simpler to just use a client tag?

2 Likes

I've used something like this as well when having a generic popup to do things.

Basically, you have a function on your window that you would pass to the popup, and the popup would call that function before closing to either pass data back, or provide a confirmation etc...

2 Likes

I think this is what I'm looking for!

Here's the situation I have, in case I wasn't super clear about it before:
image

I'm hoping I can use closure to create a reference to a property in the template instance ("comment" property), put that in a callback function, use the suggested method to pass that function to the popup, then call it once the popup returns.

This is making sense. I'll give it a try!

1 Like

I tend to only use tags when I need the data to be persistent across startup. But yes, a client tag would also work here.

Not closures, no, but the script that opens that popup can use the returned window object's .putClientProperty() method to store an arbitrary object reference in the popup. The popup's save action would use the window's matching .getClientProperty() to retrieve the original component, to which it can then assign results to properties.

Unlike client tags, this approach scales to many popup windows, even those created with .openWindowInstance().

2 Likes

I got it! But I want to make sure I'm not doing anything dangerous (not thread-safe) or unnecessary. Do I need the invokeLater call?

Template Open Popup Button

from functools import partial

template = event.source.parent

def setNewComment(newComment):
	template.comment = newComment

def updateComment(newComment):
	'''Callback function to update the comment property on the template from the popup context'''
	
	system.util.invokeLater(partial(setNewComment, newComment))

window = system.nav.openWindow('Path/To/MyPopup', {'comments': event.source.parent.comment})
window.putClientProperty('updateComment', updateComment)
system.nav.centerWindow(window)

Popup OK button

newComment = event.source.parent.comments
window = system.gui.getParentWindow(event)
updateComment = window.getClientProperty("updateComment")
updateComment(newComment)
system.nav.closeParentWindow(event)

While I'll admit first off that I haven't tested your code, but couldn't you just remove both the setNewComment function and from functools import partial and change the updateComment function as follows:

def updateComment(newComment):
	template.comment = newComment
1 Like

Not when the script is already running in the foreground (like a save button's actionPerformed event).

I wouldn't put a function in the window client property for simple tasks. Just put the target component itself.

...
window.putClientProperty('commentTemplate', template)

Then:

...
template = window.getClientProperty('commentTemplate')
template.comment = newComment
1 Like

Template Open Popup Button

template = event.source.parent
window = system.nav.openWindow('Path/To/MyPopup', {'comments': template.comment})
window.putClientProperty('commentTemplate', template)
system.nav.centerWindow(window)

Popup OK Button

newComment = event.source.parent.comments
window = system.gui.getParentWindow(event)
template = window.getClientProperty('commentTemplate')
template.comment = newComment #<-- Fails

I'm getting this error:

com.inductiveautomation.ignition.common.script.JythonExecException: Traceback (most recent call last):
  File "<event:actionPerformed>", line 4, in <module>
AttributeError: 'com.inductiveautomation.factorypmi.application.com' object has no attribute 'comment'

Presumably because the property is now private in this context.

print(template.isPropertyDefined('comment'))
print(template.isPropertyDefinedAndPublic('comment'))
# Prints:
# True
# False

Should I just switch back to the function closure method then, since that worked?

Hmmm, this is one of the PyComponentWrapper gotchas, where jython won't recognize custom properties nor component custom methods.

If you can, install my Integration Toolkit to fix that for all cases. See more here, including a non-module solution:

2 Likes