Succinct Iterables in Ignition
List comprehension and dictionary unpacking in Inductive Automation's Ignition platform
Wed Jul 05 2023
Flex repeaters (and other similar components) are already a powerful feature of Inductive Automation’s Ignition platform, but often we have to modify the lists and dictionaries we pass in. To demonstrate, we’ll use the example of a simple menu. Our menu will have two components:
- The
selector
component that will display and act as a button for each menu item. - The
menu
component the will contain a flex repeater, with an instance ofselector
for each menu item.
Our selector component will look like this:
Our menu component will look like this:
where the pages custom property is being pulled from a database and used directly in the instances of the flex repeater.
Doesn’t look to bad, right? Well, what if we wanted to highlight the menu item of the page we’re currently on so the user would easily know? We don’t want to store that in the database, because it will be different for every session. So to start we’ll add a custom property to the menu component to store the current page called selectedPage
. In a real application it would probably be bound to page.props.path
, but for this demonstration we’ll just set it to /page1
.
Then we also need to add a selected
parameter to the selector
component:
We’ve also made it so that the if the selected
property is true
we highlight the menu component blue.
Now we finally get to the part of this post I want to showcase. We need to add a transform to the binding on the flex repeater instances property that will add the selected property to each instance, setting the value to true
for the currently selected menu item and false
for all others. Years ago, this is probably how I would have approached that and it is typically what I see other people do:
Here it is in text:
def transform(self, value, quality, timestamp):
pages = []
for page in value['pages']:
pages.append({
'text': page['text'],
'location': page['location'],
'selected': page['location'] == value['selectedPage'],
})
return pages
But there are a couple problems with this:
- Every time pages is updated with additional information we may need in the
selector
component we have to update this script. - It feels long and clunky for something we have to do often.
We can make this better with a couple modern Python concepts: dictionary unpacking and list comprehension.
Lets use list comprehension to shorten this and make it more intuitive first. Python’s list comprehension syntax feature allows us to write a for loop within square brackets to instantiate a list. So the above would become this:
def transform(self, value, quality, timestamp):
return [{
'text': page['text'],
'location': page['location'],
'selected': page['location'] == value['selectedPage']
} for page in value['pages']]
This eliminates three lines of code making it more succinct and, arguably more readable.
Now we’ll use a concept called dictionary unpacking so each dictionary will just inherit all of the properties from the pages dictionary, and then we’ll add the selected
property. In modern python (v3) that changes our transform to this:
def transform(self, value, quality, timestamp):
return [{
**page,
'selected': page['location'] == value['selectedPage']
} for page in value['pages']]
Unfortunately, Ignition uses Jython which is currently stuck on version 2.7 of python, and can’t use the ** operator for dictionary unpacking. The most succinct way I’ve found to get around this limitation is the following:
def transform(self, value, quality, timestamp):
return [dict(
page,
**{'selected': page['location'] == value['selectedPage']}
) for page in value['pages']]
It’s not quite as nice as the latest versions of Python, but it works and you can see how this gets even nicer when there are more properties coming from the source list. Here’s the complete picture: