Conditionally Extracting Rows from Table

Yes well I named my column selected, but you need to replace with your own column's name.
If there are spaces in it, you'll need to use another notation: row['column name'].

edit: Assuming your column is called 'Destination Selected':

	row = self.props.data[event.rowIndex]
	row['Destination Selected'] ^= True
					
	if not row['Destination 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)

and if it's 'Destination_Selected', you can use the dot notation row.Destination_Selected

Dang it. I did get it to work but now I am getting this 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 TypeError: unsupported operand type(s) for ^: 'unicode' and 'bool'

def runAction(self, event):
	row = self.props.data[event.rowIndex]
	row.CHECKBOX ^= True
#	if not row.CHECKBOX:
#		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.label == row.label)
#		self.custom.RouteData.pop(idx)

Your CHECKBOX value is apparently a string, not a boolean.

Also, I don't think you're gaining anything with the bitwise ^=. If you're changing it to True, just use a direct assignment.

I think the intent is to toggle the selection by clicking on the whole row, avoiding the redundant clicks to get focus, begin edit, and then finally change value in the cell.

But then it's not a toggle anymore.

The "pythonic" solution is:

row.CHECKBOX = not row.CHECKBOX

Much more readable, too.

4 Likes
	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

What does the else expression do?

The xor solution has the advantage of raising a TypeError when there's an unsupported operand, like here.
If he's really using "true" and "false" strings, using not will just return False everytime.

But mostly, I really love that it looks like a little party hat ! ^

1 Like

:roll_eyes:

Hmm. Need a eye-roll icon that is extra exaggerated.

2 Likes

With a party hat ?

4 Likes

Must be riding the struggle bus today.

Now I get this 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 6, in runAction AttributeError: 'com.inductiveautomation.perspective.gateway.script' object has no attribute 'append'

def runAction(self, event):
	row = self.props.data[event.rowIndex]
	row.CHECKBOX = not row.CHECKBOX

	if not row.CHECKBOX:
		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)

No idea why it started doing this either.

Ok! I think I got it. Just have some questions with what is really happening.

def runAction(self, event):
	# Grabs data from the Row that was clicked on.
	row = self.props.data[event.rowIndex]
	# No clue what this does. Seems it is an inverse of an output.
	row.CHECKBOX = not row.CHECKBOX
	# Depends whether Checkbox is checked or not.
	# Will either Append a Row to RouteData or Remove a Row from RouteData.
	if not row.CHECKBOX:
		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)

What is the purpose of the row.CHECKBOX = not row.CHECKBOX?

And how does it tie to the if not statement? Placing a print statement shows false before and after the row.CHECKBOX = not row.CHECKBOX.

I appreciate everyone's help and input!

row.CHECKBOX = not row.CHECKBOX is an assignment. When the row is clicked, set the row.CHECKBOX to the inverse of its current value.

It is equivalent to this (don't actually do this in a script):

if row.CHECKBOX == True:
   row.CHECKBOX = False
elif row.CHECKBOX == False:
   row.CHECKBOX = True

It is not necessary to the script. It's outside of the logic that deals with the issue at hand.
The checkboxes in a table are already togglable. But, I don't like how they work: You need to make the cell active first, which is done by double clicking on it by default (this is configurable in the table's properties, under props.cells.allowEditOn).
So, to work around this where it's reasonable to do so (in some cases, it might be a good idea to have an extra step, so you don't click on something by accident), I put this line in the script, so I can toggle the checkbox with a single click. That's all it does.

I've already addressed this in response to Phil:

Your checkbox's value seems to be a string, instead of a boolean. Which is why row.CHECKBOX ^= True raised an error.
I suggest you read about 'truth' in python, but to give you a quick explanation, everything can be either True or False. In the case of integers, 0 is False, and everything else is True. For strings, an empty string ("") is False, all other strings are True.
Here, if I'm correct, you're using the strings "True" and "False", or "true" and "false" maybe, instead of the booleans True and False. Note the quotes, they make all the difference.
So, when your script executes row.CHECKBOX = not row.CHECKBOX, it does row.CHECKBOX = not "False". "False" is not an empty string, therefore it is considered a True value. So, not "False" doesn't return True, like not False would. It returns False, always.

The best thing you can do is make your values real booleans. It's simpler, clearer, and more efficient.
If you can't (but you can), you'll need to change the comparison to row.CHECKBOX = not (row.CHECKBOX == "True"). Or some variation on this, using string comparison.
Make them booleans. Really.

4 Likes

Ahh, did not catch that.

Thank you everyone for your help and input!

Is there a way I can limit the selections to 5?

Do you mean that if there are already 5 items in self.custom.RouteData, selecting another one won't work ?

def runAction(self, event):
	if len(self.custom.routeData) >= 5:
		return
	# Grabs data from the Row that was clicked on.
	row = self.props.data[event.rowIndex]
	# No clue what this does. Seems it is an inverse of an output.
	row.CHECKBOX = not row.CHECKBOX
	# Depends whether Checkbox is checked or not.
	# Will either Append a Row to RouteData or Remove a Row from RouteData.
	if not row.CHECKBOX:
		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)

If I place the if statement at the top, it works until I get 5 in there, then it will lock out the onRowClick.

If I combine it with the bottom if statement, I can get it to work with errors which I suppose I could use try so it doesn't pop up a window but that never seems like a good idea.

What do you want to happen when a user clicks on a row that isn't selected when there are already 5 selections?

Nothing?, Deselect last selection?, Deselect first selection? Popup a message box?

Add a check to the if to see if the row was already selected.

def runAction(self, event):
	# Grabs data from the Row that was clicked on.
	row = self.props.data[event.rowIndex]
	# If this would be the six selection then return without doing anything.
	if len(self.custom.routeData) == 5 and not row.CHECKBOX:
		return
	# No clue what this does. Seems it is an inverse of an output.
	row.CHECKBOX = not row.CHECKBOX
	# Depends whether Checkbox is checked or not.
	# Will either Append a Row to RouteData or Remove a Row from RouteData.
	if not row.CHECKBOX:
		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)