Cell Value change problem on table with embedded views

Hello all,

I have an issue while using an embedded view (numeric entry field) in a table, for a few different cells. If I only change one value (Quantity), it's happy, but if I change two values (Quantity and Price), then I get errors.

I am building on Danila Talbot's ( @danieltalbot ) scripting for a table, data transform and embedded views.

Data comes in from a named query, gets passed to a function on the table: SetColumns, then transformed for the table to display.

SetColumns code, Parameter: value (dataset):

true = True
	false = False
	try:
		columns = system.dataset.getColumnHeaders(value)
		columns.append('ExtCost')
	except:
		columns = value[0].keys
	
	# Custom column order
	column_order = ['PartNumber', 
					'Description', 
					'Quantity', 
					'Price', 
					'ExtCost', 
					'UOM', 'ContainerID', 
					'LeadTime', 
					'ReqID',
					'ItemID']
	
	output = []
	container_ds = system.db.runNamedQuery("Purchasing/Dropdowns/Container_CBO")
	uom_options = system.db.runNamedQuery("Inventory/Dropdowns/UOM_CBO")
	
	for col in columns:
		colObject = {"field": col, "visible": true, "editable": false, "render": "auto", "justify": "auto", "align": "center", "resizable": true,
					 "sortable": false, "sort": "none", "filter": { "enabled": false, "visible": "on-hover", "string": { "condition": "", "value": ""},
					 "number": {"condition": "", "value": ""},
					 "boolean": {"condition": ""},
					 "date": {"condition": "", "value": ""} },
  					 "viewPath": "", "viewParams": {}, "boolean": "checkbox", "number": "value", 
  					 "progressBar": {"max": 100, "min": 0, "bar": {"color": "", "style": {"classes": ""} }, 
  					 "track": {"color": "", "style": {"classes": ""} }, "value": {"enabled": true, "format": "0,0.##", "justify": "center", 
  					 "style": {"classes": ""}}}, "toggleSwitch": {"color": {"selected": "", "unselected": ""}},
  					 "nullFormat": {"includeNullStrings": false, "strict": false, "nullFormatValue": ""},
					 "numberFormat": "0,0.##", "dateFormat": "MM/DD/YYYY", "width": "", "strictWidth": false,
					 "style": {"classes": ""},
  					 "header": {"title": "", "justify": "center", "align": "center", "style": {"classes": ""}},
  					 "footer": {"title": "", "justify": "left", "align": "center", "style": {"classes": ""}}}
  		if col == 'ReqID':
  			colObject['visible'] = false
  		elif col == 'ItemID':
  			colObject['visible'] = false
  		elif col == 'Description':
  			colObject['visible'] = true
  			colObject['width'] = 300
  			colObject['render'] = 'view'
  			colObject['viewPath'] = 'Purchasing/Objects/Txt Line Item'
  			colObject['viewParams']['source'] = self.custom.table_id
  		elif col == 'PartNumber':
  			colObject['visible'] = true
  			colObject['width'] = 160
  			colObject['header']['title'] = 'Part #'
  			colObject['render'] = 'view'
  			colObject['viewPath'] = 'Purchasing/Objects/Txt Line Item'
  			colObject['viewParams']['source'] = self.custom.table_id
  		elif col == 'Quantity':
  			colObject['visible'] = true
  			colObject['width'] = 75
  			colObject['render'] = 'view'
  			colObject['viewPath'] = 'Purchasing/Objects/Num Line Item'
  			colObject['viewParams']['source'] = self.custom.table_id
  		elif col == 'Price':
  			colObject['visible'] = true
  			colObject['width'] = 75
  			colObject['render'] = 'view'
  			colObject['viewPath'] = 'Purchasing/Objects/Num Line Item'
  			colObject['viewParams']['source'] = self.custom.table_id
  		elif col == 'ExtCost':
  			colObject['visible'] = true
  			colObject['width'] = 75
  			colObject['render'] = 'view'
  			colObject['viewPath'] = 'Purchasing/Objects/Num Line Item'
  			colObject['viewParams']['source'] = self.custom.table_id
  		elif col == 'LeadTime':
  			colObject['visible'] = true
  			colObject['width'] = 80
  			colObject['header']['title'] = 'Lead Time'
  			colObject['render'] = 'view'
  			colObject['viewPath'] = 'Purchasing/Objects/Num Line Item'
  			colObject['viewParams']['source'] = self.custom.table_id
  		elif col == 'UOM':
  			colObject['visible'] = true
  			colObject['width'] = 80
  			colObject['render'] = 'view'
  			colObject['viewPath'] = 'Purchasing/Objects/Drop Line Items'
  			colObject['viewParams']['source'] = self.custom.table_id
  			colObject['viewParams']['options'] = uom_options
  			colObject['viewParams']['placeHolder'] = 'UOM...'
  		elif col == 'ContainerID':
  			colObject['visible'] = true
  			colObject['width'] = 100
  			colObject['render'] = 'view'
  			colObject['viewPath'] = 'Purchasing/Objects/Drop Line Items'
  			colObject['viewParams']['source'] = self.custom.table_id
  			colObject['viewParams']['options'] = container_ds
  			colObject['viewParams']['placeHolder'] = 'Container...'
  			
  		output.append(colObject)
  	output_sorted = []
  	for col_name in column_order:
  		for col_obj in output:
  			if col_obj['field'] == col_name:
  				output_sorted.append(col_obj)
  				break
  	self.props.columns = output_sorted
  	return output_sorted

The script transform code on table.custom.data, to which the table.props.data is bound:

	columns = self.SetColumns(value) # value is a dataset
	output = []
	
	# for each row in the dataset
	for row in range(value.getRowCount()):
		row_value = {'updateMe':False, 'deleteMe':False}
		
		price = 0
		qty = 0
		
		# for each column in the column list:
		for col in columns:
			colName = col['field']
			if colName == 'Price':
				price = value.getValueAt(row, colName)
			if colName == 'Quantity':
				qty = value.getValueAt(row, colName)
			if colName != 'ExtCost':
				cellValue = value.getValueAt(row, colName)
				row_value[colName] = {'value':cellValue, 'action':'update'
					, 'enabled':True, 'display':True, 'style':{}}
			ext_cost = round(price * qty, 2)
			row_value['ExtCost'] = {
				'value': ext_cost,
				'action':'update',
				'enabled':False,
				'display':True,
				'style':{}
			}
		output.append(row_value)
	return output

Embedded view parameters:
column, rowData{}, rowIndex, source (table identifier), value

custom.props:

the custom.value elements are expression bound like this, for each one:

try({view.params.value}['action'],{view.params.value})

the custom.payload elements are property bound like this:

view.params.rowData

newValue is bound to the Numberic Entry Field's props.value.

The props.value is expression bound like so:

coalesce({view.custom.value.value}, 0)

There is an onChange script on the props.value:

	if origin != 'Binding':
		if currentValue.value != previousValue.value:
			if self.view.params.source == 'lineItems':
				message = 'recalcExtCost'
				payload = self.view.custom.payload
				scope = 'page'
				system.perspective.sendMessage(message, payload, scope)

And finally the message handler code that throws the error:

	rowIndex = int(payload['rowIndex'])
	column = payload['column']
	newValue = payload['newValue']
	
#	message = str(rowIndex) + ': ' + str(column) + ': ' + str(newValue)
	system.perspective.print(column)
	self.custom.table_data[rowIndex][column] = newValue
	if column == 'Quantity':
		price = self.custom.table_data[rowIndex].get('Price',0)
		quantity = newValue
	elif column == 'Price':
		price = newValue
		quantity = self.custom.table_data[rowIndex].get('Quantity',0)
		
#	quantity = self.custom.table_data[rowIndex]['Quantity']['value']
	system.perspective.print(price)
	system.perspective.print(quantity)
	ext_cost = round(price * quantity, 2)
	self.custom.table_data[rowIndex]['ExtCost'] = ext_cost

And the error message:

Error running Purchasing/Primary Views/Request Form@D/root/Flx Bottom/Flx Bottom Left/Flx Bottom Table/Table.onMessageReceived(self, payload): Traceback (most recent call last): File "<function:onMessageReceived>", line 20, in onMessageReceived TypeError: unsupported operand type(s) for *: 'com.inductiveautomation.perspective.gateway.script.PropertyTreeScriptWrapper$ObjectWrapper' and 'long'

The error message changes a little depending on how I try to extract the actual value, but it all seems to relate to not being able to get just the numbers from both embedded views and multiplying them. I have been using Copilot to help me figure this out, but it's recommending something I don't understand on changing the embedded view's props.value expression binding, which caused some other error, and now it's recommending I do something completely different.

Is there a simple fix I can use in the message handler or something else I should do?

Thank you!

From your error message, it appears that newValue is a PropertyTreeScriptWrapper$ObjectWrapper type, when you’re expecting it to be numeric. I would try defining the payload inside of the change script on your embedded view instead of sending the view.custom.payload property directly. Otherwise, look into why that newValue is coming in as the wrong datatype.

I don't think it's the newValue that throws the error. It's the getting the new value from the other embedded view for the calculation.

On line 20 in your message received script it is trying to multiply an ObjectWrapper with a long. You are printing the values for both price and quantity - is it printing the correct values?

What I see, when I change the Quantity, the error lists the wrapper then the long objects.
If I change the Price first, it lists the long then the wrapper objects.
Ah, just noticed this in the console:

17:09:51.739 [Browser Thread: a812c4f4-d4a5-4b62-b4b9-44294b9ffc30] INFO Perspective.Designer.Workspace -- Quantity
17:09:51.743 [Browser Thread: a812c4f4-d4a5-4b62-b4b9-44294b9ffc30] INFO Perspective.Designer.Workspace -- <ObjectWrapper>: {u'display': True, u'action': u'update', u'style': {}, u'value': 40.0, u'enabled': True}

So this:

price = self.custom.table_data[rowIndex].get('Price',0)

is the wrapper object, and vice versa if I change the Price first.

But, if I change it to this:

price = self.custom.table_data[rowIndex]['Price']['value']

Then, if I change only the Quantity cells, it's OK, or if I change only the Price cells it's OK. But if I change one then the other, I get:

File "<function:onMessageReceived>", line 11, in onMessageReceived TypeError: 'long' object is unsubscriptable


In this instance, line 15 is:

quantity = self.custom.table_data[rowIndex]['Quantity']['value']

I discovered why that is happening. The message handler (recalcExtCost) is changing the table row object into a long. So, I thought I could set the value key (marked with number 10):

	rowIndex = int(payload['rowIndex'])
	column = payload['column']
	newValue = payload['newValue']
	rowValue = payload['rowValue']

#	system.perspective.print(column)
	system.perspective.print(newValue)
10	rowValue['value'] = newValue
#	system.perspective.print(rowValue['value'])
	self.custom.table_data[rowIndex][column] = rowValue
	if column == 'Quantity':
		price = self.custom.table_data[rowIndex]['Price']
		quantity = newValue
	elif column == 'Price':
		price = newValue
		quantity = self.custom.table_data[rowIndex]['Quantity']

But, no dice. I am not sure what the next step is.

Can you share a screenshot of what a row in your data looks like before updating any columns, then after updating one, then after updating the other? So 3 screenshots of the data in total? (by row in your data, I mean the row object in the property editor)

I think what’s happening is that the data is originally nested, but when you’re writing it back it isn’t nested in the same way.

From {u'display': True, u'action': u'update', u'style': {}, u'value': 40.0, u'enabled': True}

Probably to whatever value you’re entering, like 1

So when you write to your data, make sure you write to the [“value”] key instead of the entire column object.