Ag-Grid component with input support

Thanks Shawn!
Unfortunately it didn’t solve my issue, but did motivate me to try a few other things, and I found a solution!

I created a new function that creates a new key for me

import random
def new_table_key():
    mykey = 'key_' + str(random.randrange(100000))
    st.session_state['mykey'] = mykey

This new key is used in my Aggrid call:

mykey = st.session_state['mykey']
with container:  
    rowchoice = AgGrid(df,gridOptions=gridOptions,
                       key=mykey,
                update_mode= GridUpdateMode.SELECTION_CHANGED,
                reload_data=False,
                    height = 500,
                    allow_unsafe_jscode=True,
                    )

and when I press submit of the form I am using, on_click calls the new function

submitted = col1.form_submit_button("Submit",on_click = new_table_key)

Thanks!

1 Like

Hello,

When I edit a cell in the grid, based on some functions other cells in my data changes but these changes are not shown in grid unless i change the key of grid. But I do not want to change the key because of the refresh. Is there any solution to this?

For example:
image
I edit VAT and based on this change SCT is updated. I want to show the updated data in the grid without changing the key ( refreshing the page). My data originally have too many columns so virtual column and changing key are not very good solutions

Hi @imClumsyPanda did you manage to add a Date picker in the cells?

Not yet. Have you got any idea?

hi @C45513 did you manage to implement it?

@C45513 , @imClumsyPanda @maarten-betman here is my preliminar solution:
1- copy the class DatePicker from the aggrid documentation Cell Editors, last example, look at the main.js ( JavaScript Data Grid: Cell Editors (ag-grid.com))

DatePicker = JsCode("""
class DatePicker {
  // gets called once before the renderer is used
  init(params) {
    // create the cell
    this.eInput = document.createElement('input');
    this.eInput.value = params.value;
    this.eInput.classList.add('ag-input');
    this.eInput.style.height = '100%';
    
    
    // https://jqueryui.com/datepicker/    
    $(this.eInput).datepicker({
      dateFormat: 'dd/mm/yy',
      onSelect: () => {
        this.eInput.focus();
      },
    });
  }

  // gets called once when grid ready to insert the element
  getGui() {
    return this.eInput;
  }

  // focus and select can be done after the gui is attached
  afterGuiAttached() {
    this.eInput.focus();
    this.eInput.select();
  }

  // returns the new value after editing
  getValue() {
    return this.eInput.value;
  }

}
""")

2- configure your column and set the cellEditor: gb.configure_column('Date_Column', cellEditor=DatePicker, cellEditorPopup=True)

3- Edit the content of the index.html in your library (Lib\site-packages\st_aggrid\frontend\build\index.html) to reference the JQUERY API:

<!doctype html>
<html lang="en">
<head>
  <title>Streamlit-AgGrid</title>
  <meta charset="UTF-8"/>
  <meta name="viewport" content="width=device-width,initial-scale=1"/>
  <meta name="theme-color" content="#000000"/>
  <meta name="description" content="Streamlit AgGrid"/>
  <link rel="stylesheet" href="bootstrap.min.css"/>
  <link href="./static/css/2.7fd92936.chunk.css" rel="stylesheet">
  <link href="./static/css/main.a8536b63.chunk.css" rel="stylesheet">
  <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.css"/>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/1.12.1/jquery.min.js">
  </script>
  <script src="https://cdnjs.cloudflare.com/ajax/libs/jqueryui/1.12.1/jquery-ui.min.js">
  </script>
</head>

<body>
  <noscript>You need to enable JavaScript to run this app.</noscript>
  <div id="root"></div>
  <script>!function(e){function r(r){for(var n,f,l=r[0],i=r[1],a=r[2],c=0,s=[];c<l.length;c++)f=l[c],Object.prototype.hasOwnProperty.call(o,f)&&o[f]&&s.push(o[f][0]),o[f]=0;for(n in i)Object.prototype.hasOwnProperty.call(i,n)&&(e[n]=i[n]);for(p&&p(r);s.length;)s.shift()();return u.push.apply(u,a||[]),t()}function t(){for(var e,r=0;r<u.length;r++){for(var t=u[r],n=!0,l=1;l<t.length;l++){var i=t[l];0!==o[i]&&(n=!1)}n&&(u.splice(r--,1),e=f(f.s=t[0]))}return e}var n={},o={1:0},u=[];function f(r){if(n[r])return n[r].exports;var t=n[r]={i:r,l:!1,exports:{}};return e[r].call(t.exports,t,t.exports,f),t.l=!0,t.exports}f.m=e,f.c=n,f.d=function(e,r,t){f.o(e,r)||Object.defineProperty(e,r,{enumerable:!0,get:t})},f.r=function(e){"undefined"!=typeof Symbol&&Symbol.toStringTag&&Object.defineProperty(e,Symbol.toStringTag,{value:"Module"}),Object.defineProperty(e,"__esModule",{value:!0})},f.t=function(e,r){if(1&r&&(e=f(e)),8&r)return e;if(4&r&&"object"==typeof e&&e&&e.__esModule)return e;var t=Object.create(null);if(f.r(t),Object.defineProperty(t,"default",{enumerable:!0,value:e}),2&r&&"string"!=typeof e)for(var n in e)f.d(t,n,function(r){return e[r]}.bind(null,n));return t},f.n=function(e){var r=e&&e.__esModule?function(){return e.default}:function(){return e};return f.d(r,"a",r),r},f.o=function(e,r){return Object.prototype.hasOwnProperty.call(e,r)},f.p="./";var l=this.webpackJsonpfrontend=this.webpackJsonpfrontend||[],i=l.push.bind(l);l.push=r,l=l.slice();for(var a=0;a<l.length;a++)r(l[a]);var p=i;t()}([])
  </script>
  <script src="./static/js/2.40163c18.chunk.js"></script>
  <script src="./static/js/main.506285f1.chunk.js"></script>


</body>
</html>

4- for a Prod environment you may need to write a command in Docker to copy this file and replace the default package file

5- run your Streamlit UI and you should see the calendar in the grid:
image

Note: The only problem is that my calendar is not working properly. Cannot assign the value to the cell and cannot change the month. But is a step closer.

If you have any suggestion or manage to make it work, please share it with the rest of the community.

@PablocFonseca any advice to not overwrite or edit the st_aggrid files to include the JQUERY URL? … any option to do it programmatically as the functions are passed in the index.html body section?

references:

1 Like

This looks really good. have you deployed it on streamlit? I would love to have look at app and code.

@Shinigami86 , what do you embed inside imageElement.src? I tried to do ./images/a.png from my relative path and it didnt work.

I was able to get delete and add buttons for each row within a table by inserting two columns each with their own rendered button and click on cell action. This functionality allows users to add or delete rows within the current table.

Implementing this consists of following three steps for the add button:

  1. create add row string
    string_to_add_row = "\n\n function(e) { \n \
    let api = e.api; \n \
    let rowIndex = e.rowIndex + 1; \n \
    api.applyTransaction({addIndex: rowIndex, add: [{}]}); \n \
        }; \n \n"
  1. create a string to render the button and put inside JsCode function:
cell_button_add = JsCode('''
    class BtnAddCellRenderer {
        init(params) {
            this.params = params;
            this.eGui = document.createElement('div');
            this.eGui.innerHTML = `
             <span>
                <style>
                .btn_add {
                  background-color: limegreen;
                  border: none;
                  color: white;
                  text-align: center;
                  text-decoration: none;
                  display: inline-block;
                  font-size: 10px;
                  font-weight: bold;
                  height: 2.5em;
                  width: 8em;
                  cursor: pointer;
                }

                .btn_add :hover {
                  background-color: #05d588;
                }
                </style>
                <button id='click-button' 
                    class="btn_add" 
                    >&CirclePlus; Add</button>
             </span>
          `;
        }

        getGui() {
            return this.eGui;
        }

    };
    ''')
  1. now add column to GridOptionsBuilder object:
        gb.configure_column('', headerTooltip='Click on Button to add new row', editable=False, filter=False,
                            onCellClicked=JsCode(string_to_add_row), cellRenderer=cell_button_add,
                            autoHeight=True, wrapText=True, lockPosition='left')

In case you would like to get the delete button you have to do following steps:

  1. create delete row string
    string_to_delete = "\n\n function(e) { \n \
    let api = e.api; \n \
    let sel = api.getSelectedRows(); \n \
    api.applyTransaction({remove: sel}); \n \

  1. create a string to render the button and put inside JsCode function
cell_button_delete = JsCode('''
    class BtnCellRenderer {
        init(params) {
            console.log(params.api.getSelectedRows());
            this.params = params;
            this.eGui = document.createElement('div');
            this.eGui.innerHTML = `
             <span>
                <style>
                .btn {
                  background-color: #F94721;
                  border: none;
                  color: white;
                  font-size: 10px;
                  font-weight: bold;
                  height: 2.5em;
                  width: 8em;
                  cursor: pointer;
                }

                .btn:hover {
                  background-color: #FB6747;
                }
                </style>
                <button id='click-button'
                    class="btn"
                    >&#128465; Delete</button>
             </span>
          `;
        }

        getGui() {
            return this.eGui;
        }

    };
    ''')

  1. Now add column to GridOptionsBuilder object
gb.configure_column('Delete', headerTooltip='Click on Button to remove row',
                                    editable=False, filter=False, onCellClicked=JsCode(string_to_delete),
                                    cellRenderer=cell_button_delete,
                                    autoHeight=True, suppressMovable='true')

If anyone is interesting I could also share some additional features we implemented with these buttons:

  • Instead of adding an empty row it is possible to define default values for the columns.

  • When clicking on delete a pop-up will appear asking for a confirmation from the user before deleting the row.

6 Likes

Such a great solution! I’m also looking for a method to add buttons to add or delete rows, have managed to add aggrid buttons above the table like this?

Thanks for sharing the solution! I’ve tried the code you’ve shared, I’m wondering why the buttons in first column doesnt have a circle corner button look.

You can make it a circle by adding the following code in the .btn_add { } section.

border-radius: 50%

This would give you the below button:
image

There are many more options have a look at How To Create Round Buttons (w3schools.com) or have a search on google.

Thanks a lot!

How can i add a drop down to a cell of a table created with or without using aggird inside a streamlite environment (python). Can somebody help on this. I need a drop down as “Long” or “Short” in the “Long/Short” column of the table rather than a manual text input. I’ve been struggling for hours, can somebody help. Thanks in advance.

Hi @asehmi ,

Can you elaborate a bit more on what you mean by “Simply build and then start the component, instead of running it in dev mode. Check your packages.json for for the commands.”?

Does this imply that by default we are using the streamlit-aggrid component in dev mode?

(Context: I’m deploying on Google Cloud Run and the app performance degrades over time when using streamlit-aggrid component. Searching for the underlying cause.)

Thanks a lot in advance! :slight_smile:

I think that’s a question for the component author, however I’d image it’s built in release mode for the pip install.

If you download the git repo for streamlit-aggrid you can set the release flag to false and start the component using something like npm start from its root directory, and then start your Streamlit app separately. Just make sure the component is mounted using the dev url parameter e.g. url=http://localhost:3000, not the path parameter. Then you can debug the component on its own.

1 Like

Hi, not sure where post to post so I’m putting this here. I am trying to use pre_selected_rows in GridOptionsBuilder to select a random row for my aggrid component to display some information about the current selected row (trying to mimic Google’s I’m Feeling Lucky by choosing an item at random vs. presenting an interactive table). I am using something like row_idx = df.head(15).sample().index.tolist()[0] to randomly pick from the first 15 rows and pass this into pre_selected_rows but streamlit keeps refreshing the table nonstop unless I hard code the index or pass in a random_state to sample. Here are my grid options and aggrid calls:

gb = GridOptionsBuilder.from_dataframe(df)
gb.configure_pagination(enabled=True)
gb.configure_column('link',  hide='True')
gb.configure_column('image', hide='True')

gb.configure_selection(selection_mode='single',
                       use_checkbox=False,
                       pre_selected_rows=[row_idx])

aggrid_table = AgGrid(df, fit_columns_on_grid_load=True,
                      gridOptions=aggrid_options,
                      update_mode=GridUpdateMode.SELECTION_CHANGED,
                      allow_unsafe_jscode=True,
                      theme='streamlit')

Any ideas? I don’t want the user to get the same selected row on every run so I don’t want to save this in a session state, but I’m not sure how to prevent the table from spazzing out. Sorry, I’m still very new to streamlit and aggrid and learning.

Hi @nimaim, why not randomize row_idx between the row limits of your data frame?

Hi Shawn, so my row limits are different based on which site I am scraping. Could be 15, could be 50, could be 500. That’s why I randomize something only out of the first 15, so the user can see what it chose (if it picked row 300, some sites would not return that much and the user would not see the row it chose as it’d be on the 3rd or 4th page of the table). But that is not the issue and does not solve the problem of the ag grid component constantly reloading due to randomization of row_idx.

I’ll have to take a video of this and post more of my code so others can better understand what is going on. Essentially streamlit will go into a constant RUNNING loop and table will keep reloading with row_idx changing (I expect it to only do it once but it does not). I tried different ways of generating a random # and it is the same behavior.

EDIT: Nevermind, got it working, assigned random.randint(0,14) to a session state variable and update it on the button press callback.

@Max_Wittemanfor this is a great idea! For some reason I cannot get your code to work :frowning: Can you publish the full code for your example, please? Thank you! Daniel