Writing a complex AG-grid popup cell editor
Tags: javascript, aggrid, tutorial
Published: 2020-04-23 15:00:39

---
title: Writing a complex AG-grid popup cell editor
published: true
description: 
tags: javascript,ag-grid,tutorial
---


### What is AG-grid?
Be it that you want to display some data from your database or have an advanced way of editing information in a table in your application, you probably need a robust, easy-to-use grid component for managing that goal. That is where AG-grid comes up.

With over 600'000 weekly downloads it is one of the best data-grid libraries in the JavaScript world. Besides the obvious popularity it still boasts an enormous performance boost even when working with huge data sets and still manages to have a ton of useful features for even the most complex use cases.

That kind of a complex use case we are going to go explain in this post.

### The problem
For this tutorial we are going to tackle a rather known problem, going over monthly expenses. What we would like to have is a table in which we can enter our expenses (rows) for separate months (columns).
![Initial state](https://dev-to-uploads.s3.amazonaws.com/i/bcjx3itpwv31smvc9763.PNG)

Now this seems fine and dandy, but what happens if you want to try and edit multiple cells at the same time or somehow input the same value for multiple months?
This is where the advanced cell editing of ag-grid comes up. We can override the simple text editing of the grid with a popup which knows how edit multiple cells at one time.
![Input popup cell editor](https://dev-to-uploads.s3.amazonaws.com/i/55nbqbousnrm2zb4yiot.PNG)

### The solution
First thing we need to setup is a basic HTML file which will hold a `div` with an `id` so we can reference the grid from inside our script file. Besides that we can also define a preexisting theme for the grid. (More about themes can be found [here](https://www.ag-grid.com/javascript-grid-themes-provided/)).

```html
<!DOCTYPE html>
<html lang="en">
<head>
    <title>AG grid input widget popup</title>
    <script src="https://unpkg.com/@ag-grid-community/all-modules@23.0.2/dist/ag-grid-community.min.js"></script>
</head>

<body>
<div id="myGrid"  style="height: 100%;" class="ag-theme-balham"></div>

<script src="index.js"></script>
</body>
</html>
```
Once that is set up we can also add some default styling for the grid so it looks proper.
```css
html, body {
    height: 100%;
    width: 100%;
    margin: 0;
    box-sizing: border-box;
    -webkit-overflow-scrolling: touch;
}

html {
    position: absolute;
    top: 0;
    left: 0;
    padding: 0;
    overflow: auto;
}

body {
    padding: 1rem;
    overflow: auto;
}

td, th {
    text-align: left;
    padding: 8px;
}

#monthSelection, #inputValue {
    width: 100%;
}

.input-widget-popup {
    width: 250px;
    height: 150px;
}        
```
For the styling applied to the `td` and `tr` elements and the specific id and class selectors below them - we will go over them in detail when implementing the popup cell editor. 

After we have set up the basic HTML skeleton of our grid we now have to head over to the JavaScript side and somehow wire up the grid so we can display some data in it.

What we need to do now is create and index.js file and create the grid with some configuration.

```javascript
const rowData = [
  {
    expenses: 'Rent',
    january: 1000,
    february: 1000
  },
  {
    expenses: 'Food',
    january: 150,
    february: 125
  },
  {
    expenses: 'Car',
    january: 100,
    february: 200
  },
  {
    expenses: 'Electricity',
    january: 100,
    february: 200
  },
];

const columnDefs = [
  { field: 'expenses', editable: false },
  { field: 'january', headerName: 'January' },
  { field: 'february', headerName: 'February' },
  { field: 'march', headerName: 'March' },
  { field: 'april', headerName: 'April' },
  { field: 'may', headerName: 'May' },
  { field: 'june', headerName: 'June' },
  { field: 'july', headerName: 'July' },
  { field: 'august', headerName: 'August' },
  { field: 'september', headerName: 'September' },
  { field: 'october', headerName: 'October' },
  { field: 'november', headerName: 'November' },
  { field: 'december', headerName: 'December' }
];

const gridOptions = {
  columnDefs,
  rowData,
  defaultColDef: {
    editable: true,
    sortable: true
  }
};

document.addEventListener('DOMContentLoaded', () => {
  const gridDiv = document.querySelector('#myGrid');
  new agGrid.Grid(gridDiv, gridOptions);
});
```

OK, so this might look a little bit overwhelming, but bear with me - we will go over the points and explain it.

1. First we need to somehow the element from the DOM. (Remember we introduced a `div` with an `id` of `myGrid` in the HTML file)
2. After that we just create a new ag grid instance by calling the constructor made available by the ag-grid library `new agGrid.Grid` with the `div` element as an argument and the grid options.
3. The `gridOptions` are where the magic happens and all of the configurations can be done.
4. We define the row data (a simple JavaScript array of objects) which holds the data that we want to display
5. We define the `columnDefs` - an array of objects that has `field` which is a unique identifier of a column and a `headerName` which is the text that is displayed in the header of a column
6. The `defaulColDef` is exactly what the name says - it acts as a default option and adds the defined properties in it to all the other column definitions.

Now that we have the grid setup and all the fields are editable we can go over into wiring up our custom cell editor.
We first need to extend the `defaultColDef` with another property `cellEditor` which will hold a reference to our custom class for the cell editor.
```javascript
const gridOptions = {
  columnDefs,
  rowData,
  defaultColDef: {
    editable: true,
    sortable: true,
    cellEditor: ExpensePopupCellEditor
  }
};
```

We will also need to update the first `columnDef` for the expenses to use the default cell renderer so for now we can just initialize the `cellRenderer` property as an empty string.

``` javascript
{ field: 'expenses', editable: false, cellRenderer: '' }
```

For the cell editor we will define a JavaScript class called ExpensePopupCellEditor which will hold our custom logic.

``` javascript
class ExpensePopupCellEditor {

  // gets called once after the editor is created
  init(params) {
    this.container = document.createElement('div');
    this.container.setAttribute('class', 'input-widget-popup');
    this._createTable(params);
    this._registerApplyListener();
    this.params = params;
  }

  // Return the DOM element of your editor,
  // this is what the grid puts into the DOM
  getGui() {
   return this.container;
  }

  // Gets called once by grid after editing is finished
  // if your editor needs to do any cleanup, do it here
  destroy() {
    this.applyButton.removeEventListener('click', this._applyValues);
  }

  // Gets called once after GUI is attached to DOM.
  // Useful if you want to focus or highlight a component
  afterGuiAttached() {
    this.container.focus();
  }

  // Should return the final value to the grid, the result of the editing
  getValue() {
    return this.inputValue.value;
  }

  // Gets called once after initialised.
  // If you return true, the editor will appear in a popup
  isPopup() {
    return true;
  }
}
```
Most of the methods in the popup are self describing so the most interesting part here would be to dive into the `init` method.

1. First we create the container element which will contain the whole popup and apply the CSS `class` we defined earlier in our HTML file.
2. After that we create the table structure and register the click listener for the `Apply` button
3. At the end we also save the `params` object for later use.

```javascript
 _createTable(params) {
    this.container.innerHTML = `
      <table>
        <tr>
            <th></th>
            <th>From</th>
            <th>To</th>
        </tr>
        <tr>
            <td></td>
            <td>${params.colDef.headerName}</td>
            <td><select id="monthSelection"></select></td>
        </tr>
        <tr></tr>
        <tr>
            <td>${params.data.expenses}</td>
            <td></td>
            <td><input id="inputValue" type="number"/></td>
        </tr>
        <tr>
            <td></td>
            <td></td>
            <td><button id="applyBtn">Apply</button></td>
        </tr>
      </table>
    `;
    this.monthDropdown = this.container.querySelector('#monthSelection');
    for (let i = 0; i < months.length; i++) {
      const option = document.createElement('option');
      option.setAttribute('value', i.toString());
      option.innerText = months[i];
      if (params.colDef.headerName === months[i]) {
        option.setAttribute('selected', 'selected');
      }
      this.monthDropdown.appendChild(option);
    }
    this.inputValue = this.container.querySelector('#inputValue');
    this.inputValue.value = params.value;
  }
```
In this `_createTable(params)` method we create the necessary HTML structure of our popup. We have generated three rows of data for the column headers, the cell input, the dropdown for our months selection and the `Apply` button. Note that we also set the cell input value to be the same as the one in the cell that is currently edited.

The `months` variable is generated at the start as an array based on the `columnDefs`.
```javascript
let months = columnDefs
                .filter(colDef => colDef.field !== 'expenses')
                .map(colDef => colDef.headerName);
```

The last thing to do is to add a listener to the `Apply` button and execute logic when it is clicked.
```javascript
  _registerApplyListener() {
    this.applyButton = this.container.querySelector('#applyBtn');
    this.applyButton.addEventListener('click', this._applyValues);
  }

  _applyValues = () => {
    const newData = { ...this.params.data };
    const startingMonthIndex = months.indexOf(this.params.colDef.headerName);
    const endMonthIndex = parseInt(this.monthDropdown.value);
    const subset = startingMonthIndex > endMonthIndex
      ? months.slice(endMonthIndex, startingMonthIndex)
      : months.slice(startingMonthIndex, endMonthIndex + 1);

    subset
      .map(month => month.toLowerCase())
      .forEach(month => {
        newData[month] = this.inputValue.value;
      });
    this.params.node.setData(newData);
    this.params.stopEditing();
  }
```
After the registering the `_applyValues` callback to the `click` event on the button we do the following:
1. Create a copy of the `data` object on the `params`
  * In this case the `data` holds the whole row data as one object from the `rowData` array, based on which cell is edited
2. Then we need to determing the starting index (based on the currently edited cell) and ending index (based on the selected month from the dropdown) of the months
3. After this we can generate an sub array of month keys based on the selection
4. While looping through that array we can set the input value for all months from the subset and set that `newData` to the `rowNode`

For example:
A cell edit that stemmed in the `March` column for the `Rent` expenses and a selection for the ending month of `June` with an input value of `500` would generate an object like this:
```javascript
{
  expenses: 'Rent',
  january: 1000, // preexisting value
  february: 1000, // preexisting value
  march: 500,
  april: 500,
  may: 500,
  june: 500
}
```

At the end we call the `stopEditing()` method on the `params` after which the grid will close the popup automatically and take over the new values from the `newData` object.

As a bonus - we can also have a simple custom cell renderer which will render the cell values as monetary values. We only need to extend the `defaultColDef` with another property and define the renderer class similar to the one we did for the editor.

```javascript
defaultColDef: {
    ...
    cellRenderer: ExpensesCellRenderer,
    cellEditor: ExpensePopupCellEditor
}

class ExpensesCellRenderer {
  init(params) {
    this.gui = document.createElement('span');
    if (this._isNotNil(params.value)
        && (this._isNumber(params.value) || this._isNotEmptyString(params.value))) {
      this.gui.innerText = `$ ${params.value.toLocaleString()}`;
    } else {
      this.gui.innerText = '';
    }
  }

  _isNotNil(value) {
    return value !== undefined && value !== null;
  }

  _isNotEmptyString(value) {
    return typeof value === 'string' && value !== '';
  }

  _isNumber(value) {
    return !Number.isNaN(Number.parseFloat(value)) && Number.isFinite(value);
  }

  getGui() {
    return this.gui;
  }
}
```

In contrast to the editor - the renderer only needs to define the `getGui` method which will return the DOM element of the renderer and the `init` which will create the element with the necessary values.


### Conclusion
And basically that's all of it!
We saw how easy is to implement a more complex use case of custom editing of cells in AG-grid with only JavaScript, HTML and CSS.

### P.S.
The full source code can be found in the following repo on github.
Feel free to raise an issue or open a PR.
Cheers!

{% github https://github.com/kirkovg/ag-grid-input-widget no-readme %}