Perspective table assistance

I’m trying accomplish what seems to a “very simple” task in perspective on a table and am having some trouble with a solution. When a user clicks on a cell on the table to edit it, after they press Tab key on the keyboard, I want to set the value to the cell where the edit occurred and jump to the next column. I have come across some JS code on the forum using the markdown component but still have not found a solution that I’m looking for. Any suggestions, advice or pointers are greatly appreciated.

This is actually going to be very complicated, because you're trying to move from an ephemeral input to another input which does not yet exist.

Precondition:
Every column of the Table must be configured to be editable.

To manage this, you'll need to configure a key event for TAB, which broadcasts a message at the Session scope.

	system.perspective.sendMessage("TAB_PRESSED", scope="session", sessionId=page.session.props.id, pageId=page.id)

Next, you'll have to configure a listener for your Table to listen for this message and update the location of the cell open for editing.

def onMessageReceived(self, payload):
	editingCell = self.props.editingCell
	if editingCell.row != "" and editingCell.column != "":
		bump_row = False
		row = editingCell.row
		column = editingCell.column
		# begin updating column
		if column == self.props.columns[-1].field:
			# If we're already the last column, loop back to the front and next row
			self.props.editingCell.column = self.props.columns[0].field
			bump_row = True
		else:
			current_column_index = -1
			for i in range(len(self.props.columns) - 1):
				if self.props.columns[i].field == column:
					current_column_index = i
			if current_column_index == -1:
				raise ValueError("Unable to locate current columnn index!")
			else:
				self.props.editingCell.column = self.props.columns[current_column_index + 1].field
		# begin updating row
		if bump_row and row < len(self.props.data):
			self.props.editingCell.row = row + 1

I've tested this and it works well, but we're being greedy with focus here. The native browser behavior is to apply focus to the next component based on tab index, so if you have a busy View you'll notice that when you press TAB something else gains focus, but then we steal it back for the Table. Additionally, if your user is editing a cell, clicks outside the table, then clicks TAB the behavior gets a little weird.

2 Likes

A team member raised a scenario where you might want to actually commit a value (Enter) and then TAB to the next cell. To manage this, you'll need a custom property which acts as the registry of the latest valid value combo for the editingCell object. I'd recommend a change script attached to the object:

if currentValue:
    if currentValue.value.row != "" and currentValue.value.column != "":
        # only write to the custom object if both values are valid
        self.custom.editingCell = currentValue.value

Then you would amend the script I provided earlier to use this custom object instead of Table.props.editingCell.

@cmallonee Thank you for the quick response and additional insight. It's greatly appreciated. I have not done a lot with messaging, I usually try to keep the processing and logic as close to the component as possible. Sometimes that works, there is component capability and sometimes you just have to improvise. Thank you again.

1 Like

Please advise how "a key event for TAB" should be configured. I've tried to create onKeyPress event in the Table component with the following script (and tried checking the "Prevent Default" checkbox on and off) but this script is not executed and the Tab key works as usual (moving between components).
I also tried configuring onKeyPress event for the 'root' component.

    if event.key == 'Tab':
        system.perspective.sendMessage("TAB_PRESSED", scope="session", sessionId=page.session.props.id, pageId=page.id)
    	system.perspective.print("Tab pressed -- message sent")
        event.preventDefault()

I think @cmallonee might have meant a session keystroke event. Otherwise I'm not sure why a session wide message would be required.

1 Like

Yes, the event works like this. Thank you!

@cmallonee , I had to refine your solution a bit. Posting it here in case somebody else may need it too.

Table > editingCell > Edit Change Script:

def valueChanged(self, previousValue, currentValue, origin, missedEvents):
	if currentValue:
		if currentValue.value["row"].value != "" and currentValue.value["column"].value != "":
			# only write to the custom object if both values are valid
			self.custom.editingCell = currentValue.value

I have decided not to override Tab and Shift-Tab, because those keys have useful functionality in the browser (navigation between the components). I have also removed bump_row (better for my case) so whoever needs it, may analyze my changes and add them in the initial solution:

Perspective > Session Events > Keystroke:
[Conrol] ArrowLeft

def onKeyEvent(page, event):
	system.perspective.sendMessage("CtrlLeft_Pressed", scope="session", sessionId=page.session.props.id, pageId=page.id)

[Conrol] ArrowRight

def onKeyEvent(page, event):
	system.perspective.sendMessage("CtrlRight_Pressed", scope="session", sessionId=page.session.props.id, pageId=page.id)

Table > Configure Scripts > Message Handlers:
CtrlRight_Pressed:

def onMessageReceived(self, payload):
	editingCell = self.custom.editingCell
#	system.perspective.print("custom.editingCell: ")
#	system.perspective.print(editingCell)
	
	if editingCell.row != "" and editingCell.column != "":
		row = editingCell.row
		column = editingCell.column
		# begin updating column
		if column != self.props.columns[-1].field:
			# If we're not yet at the last column
			current_column_index = -1
			for i in range(len(self.props.columns) - 1):
				if self.props.columns[i].field == column:
					current_column_index = i
			if current_column_index == -1:
				raise ValueError("Unable to locate current columnn index!")
			else:
				self.props.editingCell.column = self.props.columns[current_column_index + 1].field
				self.props.editingCell.row = row
	
#	system.perspective.print("props.editingCell: ")
#	system.perspective.print(self.props.editingCell)
CtrlLeft_Pressed:
def onMessageReceived(self, payload):	
	editingCell = self.custom.editingCell
#	system.perspective.print("custom.editingCell: ")
#	system.perspective.print(editingCell)
	
	if editingCell.row != "" and editingCell.column != "":
		row = editingCell.row
		column = editingCell.column
		# begin updating column
		if column != self.props.columns[2].field:
			# If we're not yet at column 2 (the first two columns are not editable)
			current_column_index = -1
			for i in range(len(self.props.columns)):
				if self.props.columns[i].field == column:
					current_column_index = i
			system.perspective.print(current_column_index)
			if current_column_index == -1:
				raise ValueError("Unable to locate current column index!")
			else:
				self.props.editingCell.column = self.props.columns[current_column_index - 1].field
				self.props.editingCell.row = row
	
#	system.perspective.print("props.editingCell: ")
#	system.perspective.print(self.props.editingCell)
1 Like