Movatterモバイル変換


[0]ホーム

URL:


Skip to content
DEV Community
Log in Create account

DEV Community

Gjorgji Kirkov
Gjorgji Kirkov

Posted on

     

Writing a complex AG-grid popup cell editor

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

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

The solution

First thing we need to setup is a basic HTML file which will hold adiv with anid 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 foundhere).

<!DOCTYPE html><htmllang="en"><head><title>AG grid input widget popup</title><scriptsrc="https://unpkg.com/@ag-grid-community/all-modules@23.0.2/dist/ag-grid-community.min.js"></script></head><body><divid="myGrid"style="height: 100%;"class="ag-theme-balham"></div><scriptsrc="index.js"></script></body></html>
Enter fullscreen modeExit fullscreen mode

Once that is set up we can also add some default styling for the grid so it looks proper.

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;}
Enter fullscreen modeExit fullscreen mode

For the styling applied to thetd andtr 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.

constrowData=[{expenses:'Rent',january:1000,february:1000},{expenses:'Food',january:150,february:125},{expenses:'Car',january:100,february:200},{expenses:'Electricity',january:100,february:200},];constcolumnDefs=[{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'}];constgridOptions={columnDefs,rowData,defaultColDef:{editable:true,sortable:true}};document.addEventListener('DOMContentLoaded',()=>{constgridDiv=document.querySelector('#myGrid');newagGrid.Grid(gridDiv,gridOptions);});
Enter fullscreen modeExit fullscreen mode

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 adiv with anid ofmyGrid 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 librarynew agGrid.Grid with thediv element as an argument and the grid options.
  3. ThegridOptions 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 thecolumnDefs - an array of objects that hasfield which is a unique identifier of a column and aheaderName which is the text that is displayed in the header of a column
  6. ThedefaulColDef 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 thedefaultColDef with another propertycellEditor which will hold a reference to our custom class for the cell editor.

constgridOptions={columnDefs,rowData,defaultColDef:{editable:true,sortable:true,cellEditor:ExpensePopupCellEditor}};
Enter fullscreen modeExit fullscreen mode

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

 javascript{ field: 'expenses', editable: false, cellRenderer: '' }
Enter fullscreen modeExit fullscreen mode

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

 javascriptclass 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;  }}
Enter fullscreen modeExit fullscreen mode

Most of the methods in the popup are self describing so the most interesting part here would be to dive into theinit method.

  1. First we create the container element which will contain the whole popup and apply the CSSclass we defined earlier in our HTML file.
  2. After that we create the table structure and register the click listener for theApply button
  3. At the end we also save theparams object for later use.
_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></select></td>        </tr>        <tr></tr>        <tr>            <td>${params.data.expenses}</td>            <td></td>            <td><input type="number"/></td>        </tr>        <tr>            <td></td>            <td></td>            <td><button>Apply</button></td>        </tr>      </table>    `;this.monthDropdown=this.container.querySelector('#monthSelection');for(leti=0;i<months.length;i++){constoption=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;}
Enter fullscreen modeExit fullscreen mode

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 theApply button. Note that we also set the cell input value to be the same as the one in the cell that is currently edited.

Themonths variable is generated at the start as an array based on thecolumnDefs.

letmonths=columnDefs.filter(colDef=>colDef.field!=='expenses').map(colDef=>colDef.headerName);
Enter fullscreen modeExit fullscreen mode

The last thing to do is to add a listener to theApply button and execute logic when it is clicked.

_registerApplyListener(){this.applyButton=this.container.querySelector('#applyBtn');this.applyButton.addEventListener('click',this._applyValues);}_applyValues=()=>{constnewData={...this.params.data};conststartingMonthIndex=months.indexOf(this.params.colDef.headerName);constendMonthIndex=parseInt(this.monthDropdown.value);constsubset=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();}
Enter fullscreen modeExit fullscreen mode

After the registering the_applyValues callback to theclick event on the button we do the following:

  1. Create a copy of thedata object on theparams
    • In this case thedata holds the whole row data as one object from therowData 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 thatnewData to therowNode

For example:
A cell edit that stemmed in theMarch column for theRent expenses and a selection for the ending month ofJune with an input value of500 would generate an object like this:

{expenses:'Rent',january:1000,// preexisting valuefebruary:1000,// preexisting valuemarch:500,april:500,may:500,june:500}
Enter fullscreen modeExit fullscreen mode

At the end we call thestopEditing() method on theparams after which the grid will close the popup automatically and take over the new values from thenewData 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 thedefaultColDef with another property and define the renderer class similar to the one we did for the editor.

defaultColDef:{...cellRenderer:ExpensesCellRenderer,cellEditor:ExpensePopupCellEditor}classExpensesCellRenderer{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){returnvalue!==undefined&&value!==null;}_isNotEmptyString(value){returntypeofvalue==='string'&&value!=='';}_isNumber(value){return!Number.isNaN(Number.parseFloat(value))&&Number.isFinite(value);}getGui(){returnthis.gui;}}
Enter fullscreen modeExit fullscreen mode

In contrast to the editor - the renderer only needs to define thegetGui method which will return the DOM element of the renderer and theinit 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!

Top comments(1)

Subscribe
pic
Create template

Templates let you quickly answer FAQs or store snippets for re-use.

Dismiss
CollapseExpand
 
yashwanth2714 profile image
Yashwanth Kumar
A passionate JavaScript Developer
  • Location
    India
  • Joined

Hey! Thanks for the post.

In my case, getValue() is not returning the editor value. Do you know the reasons for this?

Are you sure you want to hide this comment? It will become hidden in your post, but will still be visible via the comment'spermalink.

For further actions, you may consider blocking this person and/orreporting abuse

Passionate about building software and applying engineering principles to a solution.
  • Joined

More fromGjorgji Kirkov

DEV Community

We're a place where coders share, stay up-to-date and grow their careers.

Log in Create account

[8]ページ先頭

©2009-2025 Movatter.jp