Use of Eval to getComponents: Is there a better approach for assembling a dynamic component list?

I'm in the process of refactoring an old script that uses a dataset to get components of various nested depths. The dataset is created at initialization, so it always precisely reflects the items that are in the Vision window.

There are two problems with this that I want to fix:
ā€¢ The component list only goes three levels deep
ā€¢ Later when another function uses the data, the code is structured in a way that repeats itself.

The sanitized code resembles this:

if levelSelectData == "1":
	selectedComponent = root.getComponent(levelOneData)
	if selectedComponent is not None:
		#[Do stuff with the component here]
if levelSelectData == "2":
	selectedComponent = root.getComponent(levelOneData).getComponent(levelTwoData)
	if selectedComponent is not None:
		#[Do the exact same stuff here]
if levelSelectData == "3":
	selectedComponent = root.getComponent(levelOneData).getComponent(levelTwoData).getComponent(levelThreeData)
	if selectedComponent is not None:
		#[Once again we have the exact same code repeated here]

My idea was to keep the dataset that is created by the initialization script, but instead of storing a list of container names and component names with a depth index, I would actually store the full path to the component as a string and use eval to get the component by the subsequent script. The refactored code would look like this:

selectedComponent = eval(path)
if selectedComponent is not None:
	#[Do stuff with the component here]

This would allow me to assemble a list of paths that are as deep as they need to be with a single column. It would also allow me to call the component later without violating the DRY programming practice. I should note that there is no editable user input for this code, so in this case, there is no risk [that I am aware of] of an injection attack with this approach, but my question is whether or not there is a better approach or practice to achieve this same functionality?

1 Like

I don't think you need eval here, still. Store your list as a string with a delimiter (e.g. slash, for human readability). Split the string and walk through each component, bailing early if you don't find one with the appropriate name.

1 Like

Understood. Based on this feedback, I now plan on creating a list of paths that look like this: 'levelOneData/levelTwoData/levelThreeData', and then, I plan to use the path to get the selected component in the following manner:

root = system.gui.getParentWindow(event).getRootContainer()
def getSelectedComponent(path):
	components = path.split('/')
	selectedComponent = root
	for component in components:
		selectedComponent = selectedComponent.getComponent(component)
	return selectedComponent
selectedComponent = getSelectedComponent(path)

Any other suggestions?

Actually...I think you don't need any of your own code for this at all. If you're just trying to traverse an Ignition component path (not the underlying Swing inner component indices) you can just call getComponentForPath from the window object. So system.gui.getParentWindow(event).getComponentForPath("levelOneData/levelTwoData/levelThreeData") should just work. And automatically return null safely for anything not found.

1 Like

I did a quick test, and it works as you said, but I couldn't get it to work with forward slashes [I had to use decimals], and also, the path needed the root container name:
rootContainerName = system.gui.getParentWindow(event).getRootContainer().name.
The completed path that worked during my test looked like this:
rootContainerName.container1Name.container2Name.componentName

I imagine that my refactored code will end up looking like this:

selectedComponent = system.gui.getParentWindow(event).getComponentForPath(path)
if selectedComponent is not None:
	#[Do stuff with component here]

Thanks for the guidance, and let me know if I've missed anything.

1 Like