Skip to content

Commit

Permalink
Implement Tabulator buttons (#3111)
Browse files Browse the repository at this point in the history
* Add docs

* Implement Tabulator buttons

* Change on_button_click API
  • Loading branch information
philippjfr authored Jan 18, 2022
1 parent ee9df3b commit 06869c5
Show file tree
Hide file tree
Showing 4 changed files with 110 additions and 5 deletions.
34 changes: 32 additions & 2 deletions examples/reference/widgets/Tabulator.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,7 @@
"import pandas as pd\n",
"import panel as pn\n",
"\n",
"pn.extension('tabulator')"
"pn.extension('tabulator', css_files=[pn.io.resources.CSS_URLS['font-awesome']])"
]
},
{
Expand All @@ -29,6 +29,7 @@
"##### Core\n",
"\n",
"* **``aggregators``** (``dict``): A dictionary mapping from index name to an aggregator to be used for `hierarchical` multi-indexes (valid aggregators include 'min', 'max', 'mean' and 'sum'). If separate aggregators for different columns are required the dictionary may be nested as `{index_name: {column_name: aggregator}}`\n",
"* **``buttons``** (``dict``): A dictionary of buttons to add to the table mapping from column name to the HTML contents of the button cell, e.g. `{'print': '<i class=\"fa fa-print\"></i>'}`. Buttons are added after all data columns.\n",
"* **``configuration``** (``dict``): A dictionary mapping used to specify tabulator options not explicitly exposed by panel.\n",
"* **``editors``** (``dict``): A dictionary mapping from column name to a bokeh `CellEditor` instance or tabulator editor specification.\n",
"* **``embed_content``** (``boolean``): Whether to embed the `row_content` or to dynamically fetch it when a row is expanded.\n",
Expand Down Expand Up @@ -105,7 +106,7 @@
" 'datetime': [dt.datetime(2019, 1, 1, 10), dt.datetime(2020, 1, 1, 12), dt.datetime(2020, 1, 10, 13)]\n",
"}, index=[1, 2, 3])\n",
"\n",
"df_widget = pn.widgets.Tabulator(df)\n",
"df_widget = pn.widgets.Tabulator(df, buttons={'Print': \"<i class='fa fa-print'></i>\"})\n",
"df_widget"
]
},
Expand Down Expand Up @@ -1033,6 +1034,35 @@
")"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"## Buttons\n",
"\n",
"If you want to trigger custom actions by clicking on a table cell you may declare a set of `buttons` that are rendered in columns after all the data columns. To respond to button clicks you can register a callback using the `on_button_click` method:"
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"button_table = pn.widgets.Tabulator(df, buttons={\n",
" 'print': '<i class=\"fa fa-print\"></i>',\n",
" 'check': '<i class=\"fa fa-check\"></i>'\n",
"})\n",
"\n",
"string = pn.widgets.StaticText()\n",
"\n",
"button_table.on_button_click(\n",
" lambda e: string.param.update(value=f'Clicked {e.column!r} on row {e.row}')\n",
")\n",
"\n",
"pn.Row(button_table, string)"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down
13 changes: 13 additions & 0 deletions panel/models/tabulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -36,6 +36,17 @@ def __init__(self, model, column, row, value=None):
self.value = value
super().__init__(model=model)


class CellClickEvent(ModelEvent):

event_name = 'cell-click'

def __init__(self, model, column, row):
self.column = column
self.row = row
super().__init__(model=model)


def _get_theme_url(url, theme):
if 'bootstrap' in theme:
url += 'bootstrap/'
Expand Down Expand Up @@ -68,6 +79,8 @@ class DataTabulator(HTMLBox):

aggregators = Dict(String, String)

buttons = Dict(String, String)

configuration = Dict(String, Any)

columns = List(Instance(TableColumn), help="""
Expand Down
29 changes: 29 additions & 0 deletions panel/models/tabulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,18 @@ export class TableEditEvent extends ModelEvent {
}
}

export class CellClickEvent extends ModelEvent {
event_name: string = "cell-click"

constructor(readonly column: string, readonly row: number) {
super()
}

protected _to_json(): JSON {
return {model: this.origin, column: this.column, row: this.row}
}
}

declare const Tabulator: any;

function find_group(key: any, value: string, records: any[]): any {
Expand Down Expand Up @@ -702,6 +714,21 @@ export class DataTabulatorView extends PanelHTMLBoxView {
if (config_columns == null)
columns.push(tab_column)
}
for (const col in this.model.buttons) {
const button_formatter = () => {
return this.model.buttons[col];
};
const button_column = {
formatter: button_formatter,
width: 40,
hozAlign: "center",
cellClick: (_: any, cell: any) => {
const index = cell._cell.row.data._index;
this.model.trigger_event(new CellClickEvent(col, index))
}
}
columns.push(button_column)
}
return columns
}

Expand Down Expand Up @@ -991,6 +1018,7 @@ export namespace DataTabulator {
export type Attrs = p.AttrsOf<Props>
export type Props = HTMLBox.Props & {
aggregators: p.Property<any>
buttons: p.Property<any>
children: p.Property<any>
columns: p.Property<TableColumn[]>
configuration: p.Property<any>
Expand Down Expand Up @@ -1036,6 +1064,7 @@ export class DataTabulator extends HTMLBox {

this.define<DataTabulator.Props>(({Any, Array, Boolean, Nullable, Number, Ref, String}) => ({
aggregators: [ Any, {} ],
buttons: [ Any, {} ],
children: [ Any, {} ],
configuration: [ Any, {} ],
columns: [ Array(Ref(TableColumn)), [] ],
Expand Down
39 changes: 36 additions & 3 deletions panel/widgets/tables.py
Original file line number Diff line number Diff line change
Expand Up @@ -805,6 +805,10 @@ class Tabulator(BaseTable):
table to provide a full-featured interactive table.
"""

buttons = param.Dict(default={}, doc="""
Dictionary mapping from column name to a HTML element
to use as the button icon.""")

expanded = param.List(default=[], doc="""
List of expanded rows, only applicable if a row_content function
has been defined.""")
Expand Down Expand Up @@ -916,6 +920,7 @@ def __init__(self, value=None, **params):
self._computed_styler = None
self._child_panels = {}
self._on_edit_callbacks = []
self._on_click_callbacks = {}
super().__init__(value=value, **params)
self._configuration = configuration
self.param.watch(self._update_children, self._content_params)
Expand All @@ -938,9 +943,15 @@ def _cleanup(self, root):
super()._cleanup(root)

def _process_event(self, event):
event.value = self.value[event.column].iloc[event.row]
for cb in self._on_edit_callbacks:
cb(event)
if event.event_name == 'table-edit':
event.value = self.value[event.column].iloc[event.row]
for cb in self._on_edit_callbacks:
cb(event)
else:
for cb in self._on_click_callbacks.get(None, []):
cb(event)
for cb in self._on_click_callbacks.get(event.column, []):
cb(event)

def _get_theme(self, theme, resources=None):
from ..io.resources import RESOURCE_MODE
Expand Down Expand Up @@ -1238,6 +1249,7 @@ def _get_properties(self, source):
selectable = self.selectable
props.update({
'aggregators': self.aggregators,
'buttons': self.buttons,
'expanded': self.expanded,
'source': source,
'styles': self._get_style_data(),
Expand Down Expand Up @@ -1283,8 +1295,10 @@ def _get_model(self, doc, root=None, parent=None, comm=None):
self._link_props(model, ['page', 'sorters', 'expanded', 'filters'], doc, root, comm)
if comm:
model.on_event('table-edit', self._comm_event)
model.on_event('cell-click', self._comm_event)
else:
model.on_event('table-edit', partial(self._server_event, doc))
model.on_event('cell-click', partial(self._server_event, doc))
return model

def _update_model(self, events, msg, root, model, doc, comm):
Expand Down Expand Up @@ -1501,3 +1515,22 @@ def on_edit(self, callback):
"""
self._on_edit_callbacks.append(callback)

def on_button_click(self, callback, column=None):
"""
Register a callback to be executed when a cell corresponding
to a column declared in the `buttons` parameter is clicked.
The callback is given a CellClickEvent declaring the column
and row of the cell that was clicked.
Arguments
---------
callback: (callable)
The callback to run on edit events.
column: (str)
Optional argument restricting the callback to a specific
column.
"""
if column not in self._on_click_callbacks:
self._on_click_callbacks[column] = []
self._on_click_callbacks[column].append(callback)

0 comments on commit 06869c5

Please sign in to comment.