Full-row editing in ag-Grid committing changes on a button click

  |   How To

This post shows how to use full-row editing together with buttons to start and cancel editing, commit changes or delete the row. This functionality allows the user to commit the edits to an entire row as a single transaction or discard the edited values using an easy to use UI.

We have implemented this in live samples in JavaScript, Angular, React and Vue.js.

Please see the illustration below showing this in action:

See the live sample in JavaScript below and links to the same sample in other frameworks just below it.

Live examples

Open the live example in all the major frameworks using the links below:
 •  JavaScript
 •  Angular
 •  React
 •  Vue

Contents

Displaying the action buttons in a separate column

Let's take a look at how this is implemented. We add an "action" column in the grid to render buttons to control editing and deletion. When a row is not edited, we display buttons  Edit and Delete. Clicking the Edit button will start editing the row and display Update and Cancel buttons instead, which when clicked will end editing and once again display the Edit and Delete buttons.

To achieve this the example provides a custom cellRenderer for the action column to render the appropriate buttons based on whether the row is in edit mode or not. You can read more about custom CellRenderers in our documentation.

See how this is implemented in the JavaScript code below (see the code in React/Angular/Vue in the samples linked to above):

function actionCellRenderer(params) {
  let eGui = document.createElement('div');

  let editingCells = params.api.getEditingCells();
  // checks if the rowIndex matches in at least one of the editing cells
  let isCurrentRowEditing = editingCells.some((cell) => {
    return cell.rowIndex === params.node.rowIndex;
  });

  if (isCurrentRowEditing) {
    eGui.innerHTML = `
        <button  
          class="action-button update"
          data-action="update">
               update  
        </button>
        <button  
          class="action-button cancel"
          data-action="cancel">
               cancel
        </button>
        `;
  } else {
    eGui.innerHTML = `
        <button 
          class="action-button edit"  
          data-action="edit">
             edit 
          </button>
        <button 
          class="action-button delete"
          data-action="delete">
             delete
        </button>
        `;
  }

  return eGui;
}

In the code above notice the following:

1. When a cell in the action column is rendering, the isCurrentRowEditing flag is set if any of the edited cells has row index equal to params.node.rowIndex

2. Based on whether the row is in edit mode, the renderer returns the HTML for the appropriate buttons - if the row is in editing mode it returns Update & Cancel buttons, and if not it returns Edit and Delete buttons.

3. Each button has its own data-action attribute, which will be used when handling click events to determine which button was clicked.

Refreshing action buttons when row edit starts/stops

We now have the Edit and Delete buttons rendering inside the action column but they don't automatically change to the Update and Cancel buttons when a user starts editing a particular row. This is because ag-Grid doesn't automatically refresh the cellRenderers when editing starts or stops.

In order to implement this, we need to handle the rowEditingStarted and rowEditingStopped events that ag-Grid provides and call the refreshCells() API method which will refresh the action cells in the edited row. This will call the cell renderer once again, causing the correct buttons to be rendered depending on whether the row is in edit mode or not.

See this implemented in the code below:

gridOptions: {
// ...
onRowEditingStarted: (params) => {
    params.api.refreshCells({
      columns: ['action'],
      rowNodes: [params.node],
      force: true,
    });
  },
  onRowEditingStopped: (params) => {
    params.api.refreshCells({
      columns: ['action'],
      rowNodes: [params.node],
      force: true,
    });
  },
}  
      

In the code above notice that we are passing the force:true parameter. The grid doesn't refresh the cells unless their values have changed. This is why in this case we are using this parameter to force a refresh because the user has started or stopped editing the row and we need to refresh the cells to update the buttons.

Implementing action button behavior

Now that we have the correct buttons rendering depending on whether the row is edited, we need to implement the click behavior of the Edit, Delete, Update and Cancel buttons.

Notice that we don't add any event listeners to the buttons in our custom cellRenderer. This is because if you have a large number of rows it may cause a performance hit due to all the event listeners being created and destroyed when scrolling.

In order to avoid this potential performance issue, we implement this by registering a single onCellClicked callback, which receives parameters about the clicked cell together with the event. Then, based on the CellClicked event parameters we can tell which button was clicked, and this is where the data-action attribute comes in handy.

Once we know which button was clicked, we can take the appropriate action to edit, delete, update or discard the edited values.

See this code in the onCellClicked callback implemented below:

  onCellClicked(params) {
    // Handle click event for action cells
    if (
      params.column.colId === 'action' &&
      params.event.target.dataset.action
    ) {
      let action = params.event.target.dataset.action;

      if (action === 'edit') {
        params.api.startEditingCell({
          rowIndex: params.node.rowIndex,
          // gets the first columnKey
          colKey: params.columnApi.getDisplayedCenterColumns()[0].colId,
        });
      }

      if (action === 'delete') {
        params.api.applyTransaction({
          remove: [params.node.data],
        });
      }

      if (action === 'update') {
        params.api.stopEditing(false);
      }

      if (action === 'cancel') {
        params.api.stopEditing(true);
      }
    }
  },

In the code above notice the following:

1. The example checks if the click event comes from a cell that belongs to the action column and if the target element has a dataset.action property.

2. If the Edit button is clicked, we call the startEditing API method. Notice that we get the rowIndex from onCellClicked params object. Also notice that the colKey is determined by checking what is the first column shown in the grid.

3. If the Delete button is clicked, we use the transaction API to tell the grid to remove the row.

4. The code for Update and Cancel simply calls the stopEditing API method, with a boolean parameter to tell the grid whether to commit the changes or cancel the editing operation.

What's next?

We hope that you find this article useful to implement full-row editing letting your users commit the edit or discard it altogether. Please use the samples in the blogpost and modify them as you see fit.

If you would like to try out ag-Grid check out our getting started guides (JS / React / Angular / Vue)

Happy coding!

Read more posts about...