Conditionally Extracting Rows from Table

def transform(self, value, quality, timestamp):
	RouteData = []
	# This maps out the list.
	for row in value:
			RouteData.append({
		"DeviceID": row["DEVICE_ID"],
		"DeviceName": row["DEVICE_NAME"],
		"DestinationSelected": row["CHECKBOX"]
	})  
	return RouteData

image

I am able to everything from the Table and place it in a Custom Property. What I want to do is be able to only append the ones that have DestinationSelected checked.

I am guessing I would need an if then statement but need some help.

def transform(self, value, quality, timestamp):
	# This maps out the list.
	return [{'DeviceID':row['DEVICE_ID'],'DeviceName':row['DEVICE_NAME'],'DestinationSelected':row['CHECKBOX']} for row in value if row['CHECKBOX']]

Wow! Thank you for your help!

Part 2.

Let's say I want these entries to be added and removed based on the order they are selected.

I would just use the append and remove?

Do you mean you want to add them to a prop when they are clicked?

Or are you trying to 'retain the order' in which they are selected?

In the original question, they would have maintained the order in which they were in the original prop.

I'll suggest a slight change:

return [row for row in value if row['CHECKBOX']]

edit: nevermind, didn't notice the keys names were changed in the process.

This will require you to keep track of when they're selected. Doing this in a transform won't be enough, as far as I can tell there's no way to tell which one was selected first.

You could build your custom prop using click events on the table, appending/removing items to it every time something is selected/unselected.

I am thinking I misunderstood the question. It is probably add them to prop when they are checked and remove them when they are unchecked.

I would probably recommend that you use the onEditCellCommit event of the table and then use a script similar to this:

	if event.column == 'CHECKBOX':
		routeData = self.view.custom.RouteData
		dataItem = self.props.data[row]
		if event.value:
			if not dataItem in routeData:
				routeData.append(dataItem)
		else:
			if dataItem in routeData:
				routeData.remove(dataItem)
		self.view.custom.RouteData == routeData

NOTE: I haven't tested this, so no guarantee, but I think it shows the basic principal. Also, I have not modified the keys here, which will facilitate the remove operation, and the checks to see if the item exists in the list or not. You can change them, but you will need to manually check each value of an item to see if the items are actually the same, or I suppose at least the DeviceID if that is all that matters as far as duplication goes.

Perhaps, @pascal.fragnoud or @bkarabinchak.psi have a magic python trick to make that is not quite so ugly, I couldn't think of one.

1 Like

Frankly... this is clear and efficient.
Maybe I'd group the conditions to avoid the extra level of nesting, but there's nothing ugly about this code.

1 Like

I'll mess around with it. Thank you for the help everyone!

I can get it to add the route without an issue, but if I attempt to remove one, it gives me a

08:14:26.147 [AWT-EventQueue-0] ERROR com.inductiveautomation.ignition.client.util.gui.ErrorUtil - Error running action 'component.onEditCellCommit' on View/Recipe/Batching@D/root/LeftContainer/DestinationContainer/TableContainer/ScrollContainer/Table: Traceback (most recent call last):
File "function:runAction", line 16, in runAction
ValueError: : {u'CHECKBOX': True, u'DEVICE_NAME': u'BIN 60', u'DEVICE_ID': 60} is not in array

The error means that it didn't find the item you're trying to remove in the List. Can you post your script?

You may need to write code that locates your item in the list prior to attempting to remove it.

Absolutely.

	Column = event.column
	Row = event.row
	Value = event.value
	self.props.data[self.props.selection.selectedRow].CHECKBOX = Value
	system.perspective.print(Column)
	system.perspective.print(Row)
	system.perspective.print(Value)
	if Column == 'CHECKBOX':
		RouteTable = self.custom.RouteData
		RouteData = self.props.data[Row]
		system.perspective.print(RouteData)
		if Value:
			if not RouteData in RouteTable:
				system.perspective.print("Route Removed.")
				RouteTable.remove(RouteData)
		else:
			if RouteData in RouteTable:
				system.perspective.print("Route Removed.")
				RouteTable.remove(RouteData)
		self.custom.RouteData == RouteTable

I took your snippet and got the Append to work fine, but even when I unchecked it it would not remove it.
So I left some duplicate routes in the table and moved the remove so I could see any errors. What you say though makes a ton of sense. I am wondering if using two buttons with this would be easier than just the Checkbox in the Table.

I'm wondering if this has something to do with the fact that you're storing the selected property too...
I'll set up a test and try it myself, see if I can get you a working solution.

1 Like

I notice that in your script you are trying to remove the selection in both cases. One of them should be an append. You say you were able to get the append to work, but I don't see an append in this script.

Try this, it only looks at the Device ID for equality (similar to how a Primary Key in a database works).

	if event.column == 'CHECKBOX':
		routeData = self.view.custom.RouteData
		rowData = self.props.data[row]
		if event.value and not any(rowData['DEVICE_ID'] == route['DEVICE_ID'] for route in routeData):
			routeData.append(rowData)
		else:
			routeData = [route for route in routeData if route['DEVICE_ID'] != rowData['DEVICE_ID']]
		self.view.custom.RouteData == routeData

And, @pascal.fragnoud the script looks much nicer without the unneeded nested conditions.

1 Like

So, I'm not quite sure why, but I couldn't get remove to work...
So I used pop instead:

def runAction(self, event):
	row = self.props.data[event.rowIndex]
	row.selected ^= True

	if not row.selected:
		self.custom.data.append({'label': row['label'],'value': row['value']})
	else:
		idx = next(i for i, d in enumerate(self.custom.data) if d.label == row.label)
		self.custom.data.pop(idx)

I put this on a onRowClick event, and toggle the selected column with row.selected ^= True.
I do this partly because this way you can click any column, not just the selected one, but mostly because clicking checkboxes in tables is a pain in the ass: gotta click several times to get to interact with them. This fixes that.

edit:
note that if for some reason the row isn't in the list when you deselect it in the table, you'll get a StopIteration error. This shouldn't happen, as long as you only add and remove items from the list by clicking on the table.
If you want to prevent errors, wrap the idx assignation in a try/except block.

2 Likes

Shouldn't that event.rowIndex be event.row?

It says, that rowIndex is the row index as it is represented in the current visible data. I take that to mean that it isn't always 1:1 to the index in self.props.data.

Maybe, I picked one randomly without asking myself what the difference is ;D

@lrose This did work. I will see if I can create something that will remove it if it sees it in the list.

   Column = event.column
   Row = event.row
   Value = event.value
   self.props.data[self.props.selection.selectedRow].CHECKBOX = Value
   system.perspective.print(Column)
   system.perspective.print(Row)
   system.perspective.print(Value)
   if Column == 'CHECKBOX':
   	routeData = self.custom.RouteData
   	rowData = self.props.data[Row]
   	if Value and not any(rowData['DEVICE_ID'] == route['DEVICE_ID'] for route in routeData):
   		routeData.append(rowData)
   else:
   	routeData = [route for route in routeData if route['DEVICE_ID'] != rowData['DEVICE_ID']]
   self.custom.RouteData == routeData

@pascal.fragnoud

I keep getting an error.

Error running action 'component.onRowClick' on View/Recipe/Batching@D/root/LeftContainer/DestinationContainer/TableContainer/ScrollContainer/Table: Traceback (most recent call last): File "function:runAction", line 3, in runAction AttributeError: 'com.inductiveautomation.perspective.gateway.script' object has no attribute 'selected'

	row = self.props.data[event.rowIndex]
	row.selected ^= True
					
	if not row.selected:
		self.custom.RouteData.append({'DeviceID': row['DEVICE_ID'],'DeviceName': row['DEVICE_NAME']})
	else:
		idx = next(i for i, d in enumerate(self.custom.RouteData) if d.DeviceID == row.DEVICE_ID)
		self.custom.RouteData.pop(idx)