Skip to main content

Edit Table Data

The Table component provides spreadsheet-like editing capabilities. Users can edit table data to modify existing rows in the table, insert new rows, and delete existing rows.

By default, any edits are "staged", meaning that they show in the table's edit state and can be saved by the user by clicking the Save button on the table. It's common to process these updates using table bulk edit.

An edited table

Make a column editable

If you wish to allow users to edit a column, you will first need to make the column editable. To do so, you can check the column's Editable checkbox in the Properties panel.

Make a column editable

For each editable column, you can configure the Editing Type within the column's properties panel. Selecting an editing type will determine the type of input that will be displayed when the user edits the column.

The following options are available for selection: Text, Number, Email, Date Picker, Dropdown, Checkbox.

A user defines a table column's editing type.

These inputs behave the same way as their corresponding standalone input components. For example, to configure the Dropdown editing type, you can use the same Options property as you would for a standalone Dropdown component.

Update existing rows

When hovering over an editable cell, you will see a raised border around the cell. You can double-click the cell or press the Enter key to begin editing the cell's value.

Make table columns editable from the properties panel

By default, every editable column is "Editable on update", which means that the column is editable for existing rows in the table. You can control this behavior via the Editable on update property in the column's properties panel.

You can also make the same edits programmatically using the updateRows() function.

Insert new rows

To allow new rows to be inserted by the user, click on the table component to open the properties panel. Toggle on Enable row insertion.

Enable row insertion from the properties panel

When row insertion is enabled, new rows can be added by clicking the "+" button in the table footer. An empty row is added to the bottom of the table, and the user can edit each cell in the newly inserted row.

Columns in inserted rows can be edited

Additionally, if you click into the ... menu on the right side of a row, you can select from the dropdown menu to Insert Row Above, Insert Row Below, or Duplicate Row. Insert Row Above/Below will place a blank row above or below the currently selected row. Duplicate Row will add a row below the currently selected row and will auto-fill the same data as the selected row.

Insert row above, below or duplicate

You can also insert rows programmatically using the insertRows() function.

Delete rows

To allow rows to be deleted by the user, click on the table component to open the properties panel. Toggle on "Enable row deletion".

Enable row deletion from the properties panel

When row deletion is enabled, the user can mark a row to delete by hovering over it to reveal a context menu and then selecting the "Delete row" option.

One or more rows can be marked for deletion

You can also delete rows programmatically using the deleteRows() function.

Handle table edits

Edits to the table are "staged", meaning that they show visually in the table and are stored in the table component's state. The user then has two options:

  1. Click Save changes, which will trigger the onSaveChanges event handler
  2. Click Cancel to discard the changes and revert the table component's state to its original state
Click on Save changes or Cancel to call the event handlers

To handle the table edits, the developer must properly define their onSaveChanges event handler.

Define what Action should be triggered by the event handler

The most common way to handle these changes is run an API which processes the table's edit state and writes the changes back to the relevant data source.

To understand how to properly handle table edits, it's important to understand how table edits are stored in the table component's state.

Table edits in component state

Table edits are represented in a few different reference properties in the table component's state. To better understand how these properties work, we'll walk through an example with the following table:

Table edits in component state

Table1.editedRows

The editedRows property contains an object with 4 properties: insertedRows, updatedRows, deletedRows, originalRows. All of these properties include arrays of the relevant rows.

For the table above, the editedRows property would look like this:

{
"deletedRows": [
{
"action": "",
"date_joined": "2019-01-02",
"email": "katycat@hotmail.com",
"name": "Katy Perry",
"photo": "https://lowcode.s3-us-west-2.amazonaws.com/katy_perry.png",
"twitter": "https://twitter.com/katyperry"
}
],
"insertedRows": [
{
"name": "Sabrina Carpenter"
}
],
"originalRows": [
{
"action": "",
"date_joined": "2019-01-05",
"email": "karma_police_thom@hotmail.com",
"name": "Thom Yorke",
"photo": "https://lowcode.s3-us-west-2.amazonaws.com/thom_yorke.png",
"twitter": "https://twitter.com/thomyorke"
}
],
"updatedRows": [
{
"action": "",
"date_joined": "2019-01-05",
"email": "karma_police_thom@hotmail.com",
"name": "Thomas Yorke",
"photo": "https://lowcode.s3-us-west-2.amazonaws.com/thom_yorke.png",
"twitter": "https://twitter.com/thomyorke"
}
]
}

This data structure is often the most convenient to use to process table edits, for example:

tip

The editedRows property is typically the best property to use to handle table edits.

Table1.allEdits

The allEdits property contains an array including all existing, updated, and newly inserted rows.

info

The allEdits property is less frequently used than editedRows, but it can be useful when programmatically manipulating the table's edit state.

Notably, allEdits:

  1. Does not include deleted rows
  2. Is not filtered according to any UI filters applied to the table.

You can think of allEdits as the full dataset of the table after all edits have been made, excluding any deleted rows.

For the table above, the allEdits property would look like this:

[
{
"action": "",
"date_joined": "2019-01-06",
"email": "bad_guy@gmail.com",
"name": "Billie Eilish",
"photo": "https://lowcode.s3-us-west-2.amazonaws.com/billie_eilish.png",
"twitter": "https://twitter.com/billieeilish"
},
{
"action": "",
"date_joined": "2019-01-05",
"email": "karma_police_thom@hotmail.com",
"name": "Thomas Yorke",
"photo": "https://lowcode.s3-us-west-2.amazonaws.com/thom_yorke.png",
"twitter": "https://twitter.com/thomyorke"
},
{
"action": "",
"date_joined": "2019-01-03",
"email": "ryan_the_notebook@gmail.com",
"name": "Ryan Gosling",
"photo": "https://lowcode.s3-us-west-2.amazonaws.com/ryan_gosling.png",
"twitter": "https://twitter.com/RyanGosling"
},
{
"action": "",
"date_joined": "2019-01-07",
"email": "dua_levitating@yahoo.com",
"name": "Dua Lipa",
"photo": "https://lowcode.s3-us-west-2.amazonaws.com/dua_lipa.png",
"twitter": "https://twitter.com/DUALIPA"
},
{
"name": "Sabrina Carpenter"
},
{
"action": "",
"date_joined": "2019-01-09",
"email": "beyonce_run_the_world@hotmail.com",
"name": "Beyonce Knowles",
"photo": "https://lowcode.s3-us-west-2.amazonaws.com/beyonce_knowles.png",
"twitter": "https://twitter.com/Beyonce"
},
{
"action": "",
"date_joined": "2019-01-04",
"email": "jenny_from_the_block@yahoo.com",
"name": "Lopez Jennifer",
"photo": "https://lowcode.s3-us-west-2.amazonaws.com/jennifer_lopez.png",
"twitter": "https://twitter.com/JLo"
}
]

Table1.editedRowIndices

The editedRowIndices property contains an object that includes the indices of modified rows. Today, editedRowIndices only includes deletedRows.

info

The editedRowIndices property is most useful when programmatically manipulating the table's edit state along with the allEdits property.

editedRowIndices provides both the relative and absolute indices of the modified rows, where:

  • The relative index is the index of the row in the table as is visible to the user including any filters applied to the table
  • The absolute index is the index of the row in the table's original data

For example, if we filtered the table above as follows:

Filtered Table

The editedRowIndices property would look like this:

{
"deletedRows": {
"absolute": [
2
],
"relative": [
0
]
}
}

Immediately react to table edits

In addition to reacting to the user clicking the Save changes button to bulk update the table, you can also hook directly into updates as the user edits the table, such as when the user:

  • Edits a cell
  • Inserts a row
  • Deletes a row

React to cell edits

Any editable column in the table comes with Event handlers just like its corresponding input type (onSubmit for inputs, onOptionChange for dropdowns, onCheckChange for checkboxes).

You can use these event handlers to have your application react to cell edits immediately, for example, updating another column in the table based on the value of the edited cell.

React to row insertions

Whenever a row is inserted, the onRowInserted event handler will fire, which allows you to react to row insertions.

This event receives the row and rowIndex of the newly inserted row as arguments. You can easily use this information to programmatically update the newly inserted row or any other rows in the table.

React to row deletions

Whenever one or more rows are deleted, the onRowsDeleted event handler will fire, which allows you to react to row deletions.

This event receives rowIndices as an argument, which is the array of indices for the deleted rows. This event handler is useful if you want to modify other rows in the table based on a row deletion.

Programmatically manipulate table edit state

As covered throughout these docs, when a user edits table data in the UI by modifying an editable cell, inserting a new row, or deleting one or more rows, these edits are stored in the table's edit state.

Superblocks allows you to make these same edits in code via frontend JavaScript. This functionality is useful to:

  • React to table edits immediately to immediately modify other columns/rows in the table, transparently to the user
  • Bulk edit the table programmatically, for example, to bulk delete a selected set of rows while still requiring that the user clicks the Save changes button to persist the updates to the database

Functions to manipulate table edit state

Superblocks provides the following functions to programmatically manipulate the table's edit state:

FunctionArgumentsDescription
updateRows()(rows: { [index: number]: Row }, { absoluteIndices?: boolean })Update one or more rows in a table based on their indices. Each row can be specified as a partial update. By default indices specified with be treated as relative indices
insertRows()(startIndex: number, rows: Row[])Insert one or more contiguous rows beginning with a start index. Each row can be specified as a partial update.
deleteRows()(rowIndices: number[])Delete one or more rows based on their indices

When using these functions, it is critical to understand index and row data.

Row index

By default, the index is relative to the table's current edit state as a user would see visually on the screen.

The index includes any inserted or deleted rows. For example, if I insert a new row via the UI directly after the first row (0th index), this new row is at index 1. Thus, if I called updateRows() or deleteRows() on index 1, I would be modifying/deleting this newly inserted row.

When using the updateRows() function, you can set the absoluteIndices argument to true to treat the indices as absolute indices. This means that the indices specified will be relative to the table's unfiltered data structure, allowing you to modify rows that are not currently visible rows.

Row data structure

The row data structure is an object where the column is the key and the value is the data in that column. This data structure mactches what you would see when inspecting the Table data in Superblocks.

Other important table properties in code

In addition to the allEdits, editedRows, and editedRowIndices properties, which are covered in the table edits in component state section above, it's important to also understand the following properties when manipulating the table's edit state programmatically:

Table1.tableData

The tableData property is an array of the initial data passed to the table via the Data property in the properties panel.

This property will never change solely as a result of the user manipulating the table on the frontend. The only way this property will change is if the data passed to the Data property in the properties panel changes.

Table1.filteredTableData

The filteredTableData property is an array of the rows that are visible to the user in the table at a given point in time.

This property accounts for any filters or searches applied to the table as well as any edited, deleted, or added rows. This property is 1-to-1 with what the user sees in the table at a given point in time.

For example, if we filtered the example table from the table edits in component state section above as follows:

Filtered Table Data

The filteredTableData property would look like this:

[
{
"action": "",
"date_joined": "2019-01-06",
"email": "bad_guy@gmail.com",
"name": "Billie Eilish",
"photo": "https://lowcode.s3-us-west-2.amazonaws.com/billie_eilish.png",
"twitter": "https://twitter.com/billieeilish"
},
{
"action": "",
"date_joined": "2019-01-02",
"email": "katycat@hotmail.com",
"name": "Katy Perry",
"photo": "https://lowcode.s3-us-west-2.amazonaws.com/katy_perry.png",
"twitter": "https://twitter.com/katyperry"
},
{
"action": "",
"date_joined": "2019-01-03",
"email": "ryan_the_notebook@gmail.com",
"name": "Ryan Gosling",
"photo": "https://lowcode.s3-us-west-2.amazonaws.com/ryan_gosling.png",
"twitter": "https://twitter.com/RyanGosling"
},
{
"action": "",
"date_joined": "2019-01-07",
"email": "dua_levitating@yahoo.com",
"name": "Dua Lipa",
"photo": "https://lowcode.s3-us-west-2.amazonaws.com/dua_lipa.png",
"twitter": "https://twitter.com/DUALIPA"
},
{
"name": "Sabrina Carpenter"
}
]

Using updateRows()

Let's imagine that we have a list of transactions to process displayed in a table. The status column in the table is used to display the status of the transaction.

We want to implement a feature to allow users to multi-select a set of rows and bulk modify their status to approved. The user will then review the statuses in the table along with any other edits, and click save to persist the updates to the database.

To do so, we can easily drag on a Button above the table and add a Run JS action to the button's onClick event handler with the following JavaScript.

const updates = {};

// loop over the selected rows to construct an object to pass to updateRows, setting each row to approved
Table1.selectedRowIndices.forEach((index) => {
updates[index] = { status: "approved" };
});

Table1.updateRows(updates);

Now, if the user selects the first, second, and third rows:

  • Table1.selectedRowIndices would be [0, 1, 2]
  • The JS function would produce an updates object with the following structure, which matches the schema accepted by the updateRows() function
{
"0": {
"status": "approved"
},
"1": {
"status": "approved"
},
"2": {
"status": "approved"
}
}
Transactions with bulk approve

Using insertRows()

Similarly, the insertRows() function can be used to programmatically insert new rows into the table.

For example, let's imagine that we wanted to allow users to easily select a set of rows in a table and duplicate those rows below.

To do so, we could add a button above the table and add a Run JS action to the button's onClick event handler with the following JavaScript:

const insertStartIndex = Math.max(...Table1.selectedRowIndices) + 1;

Table1.insertRows(insertStartIndex, Table1.selectedRows);

This code would find the index of the last selected row and increment by 1 to identify where to begin the row insertion. In our insertRows call, we then pass that insertStartIndex as the first argument, and as the second argument pass the row data for the selected rows.

Insert rows example

Using deleteRows()

Finally, the deleteRows() function can be used to programmatically delete rows from the table.

For example, let's imagine that as part of the same workflow, the user wants to be able to bulk delete any transactions with the status rejected.

Rather than requiring the user to manually select each rejected transaction, we could implement a feature to allow the user to click a button and automatically mark all rows in the table as deleted.

To do so, we can easily drag on a Button above the table and add a Run JS action to the button's onClick event handler with the following JavaScript:

const rowIndicesToDelete = [];

Table1.tableData.forEach((row, index) => {
if (row.status == "rejected") {
rowIndicesToDelete.push(index);
}
});

Table1.deleteRows(indicesToDelete);
info

Note: The code above only works reliably if row insertion is disabled for the table. To account for row insertion, see this (slighly more complicated) example.

Example: Maintaining a process sequence in an editable table

Finally, let's demonstrate how we would maintain a sequence of steps displayed in a table in an end-to-end fashion.

For this use case, our support agents are completing a process and recording all steps they take when monitoring a transaction. The user can:

  • Insert new steps
  • Delete steps
  • Modify steps
  • Filter any column in the table
Process table

This data is going to be written back to the database, and the order in which the steps were taken is critical. As such, we need to ensure that the Step column is updated as the user interacts with the table.

To facilitate this user experience, we can use the same JavaScript code to handle row insertions, deletions, and edits.

const updates = {};

// Calculate the length of the table, accounting for any inserted rows
const tableLength =
Table1.tableData.length + Table1.editedRows.insertedRows.length;

// Loop over the length of the table and increment the current step for any non-deleted row to construct the updates object
let step = 0;
for (let i = 0; i < tableLength; i++) {
if (!Table1.editedRowIndices.deletedRows.absolute.includes(i)) {
updates[i] = {
Step: step,
};
step++;
}
}

// Write the updates back to the table
// Set the absoluteIndices argument to true to ensure that the updates are applied to all rows in the table, even those that are not currently visible
Table1.updateRows(updates, { absoluteIndices: true });

Note that we set the absoluteIndices argument to true to ensure that the updates are applied to all rows in the table, even those that are not currently visible.

We also use the Table1.editedRowIndices.deletedRows.absolute property to ensure that we do not update rows that have been deleted. Since we are looping over the full table data, we must use the absoluteIndices argument to ensure that we are ignoring the correct rows.

We can write this JavaScript inside of a Custom Event so that we can call the same custom event from the onRowInserted and onRowDeleted event handlers, as well as the onFocusOut event handler on the Step column.

Custom Event to Reorder Table

Now, every time a user inserts, deletes, or edits the step column for a row in the table, the custom event will fire and the table will be reordered, even if the user has filtered the table.

Example - setting another column's value dynamically based on a dropdown

For example, let's imagine we have a column Country that is editable as a dropdown. When a user selects the country, we want to automatically update the Currency column based the mapping from the getCurrencyMappings API.

To do so, we would select the Country column from the properties panel and set the Editing type to Dropdown. Then, we'd add an onOptionChange event handler with the following code:

const selectedCountry = currentRow.country;
const currencyMapping = getCurrencyMappings.response[selectedCountry];

const updates = {
[currentRowIndex]: {
currencyCode: currencyMapping,
},
};

Table1.updateRows(updates);
Table dropdown option selected

Now, when the user selects an option from the country dropdown, the JS code would run and set the currencyCode column for the currentRow (the row being edited) based on getCurrencyMappings.