I had similar problem and qgrid
was an overkill for me.
Workings in jupyter (inspired by add click feature on each row in html table):
Injected script adds handlers to every cell of the dataframe table (marked by id='T_table'
through Styler.set_uuid
function).
When a cell is clicked typical handler:
looks for the designated input element (hidden and marked by placeholder "undefined"),
sets the value property of the found input for class names of the clicked cell,
- triggers "change" event.
After that the class names (pandas makes them contain the necessary row/column information) of the clicked cell are available for the ipywidgets framework and can be processed in python.
Remarks:
Certain timeout is needed before calling the script. For bigger tables the needed timeout could be bigger.
The logic is a bit convoluted, because the way to bring js-values into the python world that I used was through ipywidgets.Text
.
Item 2.1 is added in case the DOM was rerendered after initiation.
import pandas as pd
import ipywidgets as wgt
from IPython.display import display, HTML
import re
# javascript-part
script = """
<script>
var input
var xpath = "//input[contains(@placeholder,'undefined')]";
function addHandlers() {
input = document.evaluate(xpath, document, null,
XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
input.setAttribute("hidden","");
var table = document.querySelector("#T_table");
var headcells = [].slice.call(table.getElementsByTagName("th"));
var datacells = [].slice.call(table.getElementsByTagName("td"));
var cells = headcells.concat(datacells);
for (var i=0; i < cells.length; i++) {
var createClickHandler = function(cell) {
return function() {
input = document.evaluate(xpath, document, null,
XPathResult.FIRST_ORDERED_NODE_TYPE, null).singleNodeValue;
input.value = cell.className;
var event = new Event('change', { bubbles: true });
input.dispatchEvent(event);
}}
cells[i].onclick = createClickHandler(cells[i]);
};
}
window.onload = setTimeout(addHandlers, 500);
</script>
"""
display(HTML(script))
# ipywidgets-part
newdf = pd.DataFrame(data={
'1': [11,21,31,41], '2': [12,22,32,42], '3': [13,23,33,43],
'4': [14,24,34,44], '5': [15,25,35,45], '6': [16,26,36,46],
'7': [17,27,37,47], '8': [18,28,38,48], '9': [19,29,39,49],
'10': [110,210,310,410],'11': [111,211,311,411],
})
html = newdf.style.\
set_uuid('table')
def on_change(change):
cls = change['new'].split(' ')
if len(cls) == 2:
place.value, row.value = cls
col.value = '0'
elif len(cls) == 3:
place.value, txtrow, txtcol = cls
res = re.search(r'\d+',txtrow).group(0)
row.value = str(int(res)+1)
res = re.search(r'\d+',txtcol).group(0)
col.value = str(int(res)+1)
else:
place.value, row.value, col.value = ['unknown']*3
status = wgt.Text(placeholder='undefined',layout={'font-size':'6px'})
status.observe(on_change,names=['value'])
table = wgt.Output()
with table: display(html)
layout = {'width':'192px'}
row = wgt.Text(layout=layout,description='row')
col = wgt.Text(layout=layout,description='col')
place = wgt.Text(layout=layout,description='place')
body = wgt.HBox([table,wgt.VBox([place,row,col])])
wgt.VBox([body,status])