Issue with .remove on array

I'm sure I'm missing something simple here but I am trying to use .remove on an array but I keep getting an error saying the object is not in the array even though it is.

tableData = self.getSibling("Table").props.data
system.perspective.print(tableData)
	
selectedRow = self.getSibling("Table").props.selection.data
system.perspective.print(selectedRow[0])
	
tableData.remove(selectedRow[0])

Above is my code for this. I am just trying to remove the selected row from the table. I know this can be done in other ways but I need to be able to do it with the .remove function for my actual project. This is just a sample project I made so it is easier to see what is going on in my code and simpler for debugging this particular issue. For my actual project, I need to be able to delete an object from an array using a selected value from a totally different array so I can't use .pop(index) because the position will be different in the other array.

Error

Above is the error and the console output showing that the object is in the array.

What gives you the impression that those are arrays?

I see an ArrayWrapper and an object wrapper.

TableData is an ArrayWrapper and selectedRow is an ObjectWrapper.

What is the real objective to the script here, is there a binding on the table.props.data?

1 Like

Does an ArrayWrapper act different than a normal array? I seem to be able to use array methods on ArrayWrappers as if it is an array.

I'm not super familiar with this stuff, but it looks like the issue might be that ObjectWrapper doesn't have equality implemented on it, so the array removal can't determine if one element is equal to another or not.

1 Like

Yes, mostly because python and jython by extension doesn't have "arrays" it has lists or sequences if you want to be more accurate. The ArrayWrapper class is a wrapper which in this case facilitates writing back to the property tree.

remove is a valid function, the error is telling you that the element you're trying to remove isn't in the ArrayWrapper.

Which is why, I ask what the real goal of the script is? There may be a better way to accomplish what you're wanting to do.

3 Likes

So the actual use for this is a little more complicated. I have some custom filters set up on a table. In order to use multiple filters at the same time I save the original table data to an "OriginalData" custom property. Then when you delete one of your filters it can pull back in the original data and filter it on the filters you still have selected. I then have injected some javascript to allow me to drag rows out of this table and put them in other tables. When this happens I need to remove the dragged row from the "OriginalData" custom property so that if the filters are changed it will actually be removed and not show back up in the table it was removed from. And since the filtered table only contains the filtered results the index will be different than the "OriginalData" array so I can't use the index and say the .pop method. I'll need to find the row that matches the row moved in the sorted table and remove it from the "OriginalData" array. I would like to use the .remove method instead of just filtering through the whole "OriginalData" array every time a row is removed from the filtered tabled.

tableData = self.getSibling("Table").props.data
system.perspective.print(tableData)
	
selectedRow = self.getSibling("Table").props.selection.data
system.perspective.print(selectedRow[0])
	
tableData.remove({u'country': u'United States', u'city': u'San Diego', u'population': 1406630L})

I even tried hard coding the row I wanted to remove in my sample app and it didn't work. I tried with the 'u's in there and without them. Maybe I don't know how the .remove method works?

Is it because that is a regular dataset and not a python dataset?

pyData = system.dataset.toPyDataSet( )

Datasets - Ignition User Manual 8.1 - Ignition Documentation]

I tried that and get this error

TypeError: toPyDataSet(): 1st arg can't be coerced to com.inductiveautomation.ignition.common.Dataset

system.dataset.deleteRow - Ignition User Manual 8.1 - Ignition Documentation]

I thought about that as well but I won't know the row index of the array I need to delete from because it'll be different than the displayed table since the table is filtered.

print(type(tableData))

Something made your data that arraywrapper object.
Might be better off to get a dataset or to append into one.

Sorry, I didn't understand what LRose was saying at first, and I wanted to learn as well why .remove didn't work.


headers = ["City", "Population", "Timezone", "GMTOffset"]
 
# Then create an empty list, this will house our data.
data = []
 
# Then add each row to the list. Note that each row is also a list object.
data.append(["New York", 8363710, "EST", -5])
data.append(["Los Angeles", 3833995, "PST", -8])
data.append(["Chicago", 2853114, "CST", -6])
data.append(["Houston", 2242193, "CST", -6])
data.append(["Phoenix", 1567924, "MST", -7])
data.remove(["Los Angeles", 3833995, "PST", -8])
# Finally, both the headers and data lists are used in the function to create a Dataset object
cities = system.dataset.toDataSet(headers, data)

print (cities)

.remove didn't work on a dataset or a pydataset for me. It did work on the list though.

Something == Perspective.

This is the way it works.

If you create a new view and add a table component to it with an onClick event script on the table, with this code you'll find that the data property is already an ArrayWrapper object.

system.perspective.print(type(self.props.data))
Console Output
09:50:05.361 [Browser Thread: 65041] INFO Perspective.Designer.Workspace - class com.inductiveautomation.perspective.gateway.script.PropertyTreeScriptWrapper$ArrayWrapper

Now you can force it to be a dataset with a binding or a script, but if you just leave it alone it will be an ArrayWrapper with ObjectWrapper Elements.

If you use this script you can get the type of the Objects that represent the rows.

system.perspective.print(type(self.props.data[0]))
Console Output
09:50:05.361 [Browser Thread: 65041] INFO Perspective.Designer.Workspace - class com.inductiveautomation.perspective.gateway.script.PropertyTreeScriptWrapper$ObjectWrapper

Thats because datasets do not have a remove method, but since these aren't actually datasets that were working with, it isn't important. If you look here:

https://files.inductiveautomation.com/sdk/javadoc/ignition81/8.1.0/com/inductiveautomation/perspective/gateway/script/PropertyTreeScriptWrapper.ArrayWrapper.html

You will see that the ArrayWrapper class does have a remove method.

Now to get to the bottom of what @Gavin_Tipker is seeing.

As @Kevin.Herron said, the remove function relies on the equality evaluation. We can test this with a script.

system.perspective.print(self.props.data[self.props.selection.selectedRow])
system.perspective.print(self.props.selection.data[0])
system.perspective.print(self.props.data[self.props.selection.selectedRow] == self.props.selection.data[0])
Console Output

10:05:28.719 [Browser Thread: 65041] INFO Perspective.Designer.Workspace - : {u'country': u'United States', u'city': u'Washington, DC', u'population': 658893L}
10:05:28.719 [Browser Thread: 65041] INFO Perspective.Designer.Workspace - : {u'country': u'United States', u'city': u'Washington, DC', u'population': 658893L}
10:05:28.719 [Browser Thread: 65041] INFO Perspective.Designer.Workspace - false

As you can see, even though the objects are equal, the equality evaluation returns false. And you will find that even if you compare the same row within the table to itself it will return false. This means that the remove method fails as it finds that the element that is trying to be removed is not in the array (exactly as the error message says).

So the solution is to manually evaluate and find the row you're trying to remove. Then you should be able to pick your poison as to how you actually remove the row.

If the real data has an index then this can be quite simple, if not then you will need to combine evaluations in some way to determine equality. Fortunately we can convert the ObjectWrappers to a dictionary and then verify equality. I would recommend putting this in a project script and then you can call it from where ever you need it.

def removeRow(data,rowToRemove):
	for rowNumber,row in enumerate(data):
		testRow = dict(row)
		searchRow = dict(rowToRemove)
		
		if testRow == searchRow:	
			data.pop(rowNumber)
			return data
4 Likes

Thank you for the detailed reply. This is the conclusion I came to after trying everything I could think of yesterday to get the .remove method to work. Seems kind of pointless to have that .remove method for ArrayWrappers if it isn't possible to use it.

I took the manual approach this morning and did something similar to what you posted. Below is a snip of my code from the actual application I was having trouble with.

for x in range(len(originalData)):
	if originalData[x]['ID'] == draggedDataForSort['ID']:
		originalData.pop(x)
		break
1 Like

Before @pascal.fragnoud gets to it :rofl:, I would make one small tweak to your script to make it more pythonic and readable (IMHO).

for index,originalRow in enumerate(originalData):
    if originalRow['ID'] == draggedDataForSort['ID']:
        originalData.pop(index)
        break
3 Likes

I feel like there should be a better way.
But I'm not even sure what type we're working on :X

Why pop an index instead of using remove?

Because, remove will still fail. The equality operation on ObjectWrappers is not defined it will always return false.

3 Likes

I think there's an existing ticket to further improve the wrapper classes that should cover this case; they just need to be smarter about their own equality.

4 Likes

I am also doing something similar to @Gavin_Tipker and this post has been helpful. However I have noticed that when this script runs it only clears some of the objects within that list and not everything, I have to run the script again for it to complete clearing everything that I want.

I also noticed that if I followed the code above before you tweaked it, it would come with an "index out of range: X" error.