Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Add simple nested editor to Tabulator #7251

Merged
merged 7 commits into from
Sep 12, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
62 changes: 59 additions & 3 deletions examples/reference/widgets/Tabulator.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -185,7 +185,7 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"The list of valid *Tabulator* formatters can be found in the [Tabulator documentation](https://tabulator.info/docs/5.5/format#format-builtin).\n",
"The list of valid *Tabulator* formatters can be found in the [Tabulator documentation](https://tabulator.info/docs/{{TABULATOR_VERSION}}/format#format-builtin).\n",
"\n",
"Note that the equivalent specification may also be applied for column titles using the `title_formatters` parameter (but does not support Bokeh `CellFormatter` types)."
]
Expand Down Expand Up @@ -265,6 +265,62 @@
"edit_table.on_edit(lambda e: print(e.column, e.row, e.old, e.value))"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"### Nested editor\n",
"Suppose you want an editor to depend on values in another cell. The `nested` type can be used. The `nested` type needs two arguments, `options` and `lookup_order`; the latter describes how the `options` should be looked up. \n",
"\n",
"Let's create a simple DataFrame with three columns, the `2` column now depends on the values in the `0` and `1` column. If the `0` is `A`, the `2` column should always be between 1 and 5. If the `0` column is `B`, the `2` column will now also depend on the `1` column. "
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"options = {\n",
" \"A\": [1, 2, 3, 4, 5],\n",
" \"B\": {\"1\": [6, 7, 8, 9, 10], \"2\": [11, 12, 13, 14, 15]},\n",
"}\n",
"tabulator_editors = {\n",
" \"0\": {\"type\": \"list\", \"values\": [\"A\", \"B\"]},\n",
" \"1\": {\"type\": \"list\", \"values\": [1, 2]},\n",
" \"2\": {\"type\": \"nested\", \"options\": options, \"lookup_order\": [\"0\", \"1\"]},\n",
"}\n",
"\n",
"nested_df = pd.DataFrame({\"0\": [\"A\", \"B\"], \"1\": [1, 2], \"2\": [None, None]})\n",
"nested_table = pn.widgets.Tabulator(nested_df, editors=tabulator_editors, show_index=False)\n",
"nested_table"
]
},
{
"cell_type": "markdown",
"metadata": {},
"source": [
"Some things to note about the `nested` editor:\n",
"- Only string keys can be used in `options` dictionary. \n",
"- Care must be taken so there is always a valid option for the `nested` editor.\n",
"- No guarantee is made that the value shown is a `nested` editor is a valid option.\n",
"\n",
"For the last point, you can use an `on_edit` callback, which either change the value or clear it. Below is an example of how to clear it."
]
},
{
"cell_type": "code",
"execution_count": null,
"metadata": {},
"outputs": [],
"source": [
"def clear_nested_column(event):\n",
" if event.column in [\"0\", \"1\"]:\n",
" nested_table.patch({\"2\": [(event.row, None)]})\n",
"\n",
"nested_table.on_edit(clear_nested_column)"
]
},
{
"cell_type": "markdown",
"metadata": {},
Expand Down Expand Up @@ -1313,9 +1369,9 @@
"cell_type": "markdown",
"metadata": {},
"source": [
"These and other available *Tabulator* options are listed at http://tabulator.info/docs/5.4/options. \n",
"These and other available *Tabulator* options are listed at http://tabulator.info/docs/{{TABULATOR_VERSION}}/options. \n",
"\n",
"Obviously not all options will work though, especially any settable callbacks and options which are set by the internal Panel tabulator module (for example the `columns` option).\n",
"Obviously not all options will work though, especially any settable callbacks and options which are set by the internal Panel tabulator module.\n",
"Additionally it should be noted that the configuration parameter is not responsive so it can only be set at instantiation time."
]
}
Expand Down
19 changes: 19 additions & 0 deletions panel/models/tabulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -302,6 +302,20 @@ const datetimeEditor = function(cell: any, onRendered: any, success: any, cancel
return input
}

const nestedEditor = function(cell: any, editorParams: any) {
//cell - the cell component for the editable cell

const row = cell.getRow().getData()
let values = editorParams.options
for (const i of editorParams.lookup_order) {
values = row[i] in values ? values[row[i]] : []
if (Array.isArray(values)) {
break
}
}
return values ? values : []
}

function find_column(group: any, field: string): any {
if (group.columns != null) {
for (const col of group.columns) {
Expand Down Expand Up @@ -955,6 +969,11 @@ export class DataTabulatorView extends HTMLBoxView {
tab_column.editor = dateEditor
} else if (tab_column.editor === "datetime") {
tab_column.editor = datetimeEditor
} else if (tab_column.editor === "nested") {
tab_column.editorParams.valuesLookup = (cell: any) => {
return nestedEditor(cell, tab_column.editorParams)
}
tab_column.editor = "list"
}
} else if (ctype === "StringEditor") {
if (editor.completions.length > 0) {
Expand Down
43 changes: 43 additions & 0 deletions panel/tests/ui/widgets/test_tabulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -728,6 +728,49 @@
assert not exception_handler_accumulator


@pytest.mark.parametrize("opt0", ['A', 'B'])
@pytest.mark.parametrize("opt1", ["1", "2"])
def test_tabulator_editors_nested(page, opt0, opt1):
df = pd.DataFrame({"0": ["A"], "1": [1], "2": [None]})

options = {
"A": list(range(5)),
"B": { "1": list(range(5, 10)), "2": list(range(10, 15))},
}
tabulator_editors = {
"0": {"type": "list", "values": ["A", "B"]},
"1": {"type": "list", "values": [1, 2]},
"2": {"type": "nested", "options": options, "lookup_order": ["0", "1"]},
}

widget = Tabulator(df, editors=tabulator_editors, show_index=False)
serve_component(page, widget)

cells = page.locator('.tabulator-cell.tabulator-editable')
expect(cells).to_have_count(3)

# Change the 0th column
cells.nth(0).click()
item = page.locator('.tabulator-edit-list-item', has_text=opt0)
expect(item).to_have_count(1)
item.click()

# Change the 1th column
cells.nth(1).click()
item = page.locator('.tabulator-edit-list-item', has_text=opt1)
expect(item).to_have_count(1)
item.click()

# Check the last column matches
cells.nth(2).click()
items = page.locator('.tabulator-edit-list-item')
expect(items).to_have_count(5)

items_text = items.all_inner_texts()
expected = options[opt0][opt1] if opt0 == "B" else options[opt0]
assert items_text == list(map(str, expected))


@pytest.mark.parametrize('layout', Tabulator.param['layout'].objects)
def test_tabulator_column_layouts(page, df_mixed, layout):
widget = Tabulator(df_mixed, layout=layout)
Expand Down Expand Up @@ -3396,7 +3439,7 @@
assert table_values == list(df2['x'].sort_values(ascending=False))
else:
return False
wait_until(x_values, page)

Check failure on line 3442 in panel/tests/ui/widgets/test_tabulator.py

View workflow job for this annotation

GitHub Actions / ui:test-ui:ubuntu-latest

test_tabulator_sorter_default_number TimeoutError: wait_until timed out in 5000 milliseconds

Check failure on line 3442 in panel/tests/ui/widgets/test_tabulator.py

View workflow job for this annotation

GitHub Actions / ui:test-ui:ubuntu-latest

test_tabulator_sorter_default_number TimeoutError: wait_until timed out in 5000 milliseconds

Check failure on line 3442 in panel/tests/ui/widgets/test_tabulator.py

View workflow job for this annotation

GitHub Actions / ui:test-ui:ubuntu-latest

test_tabulator_sorter_default_number TimeoutError: wait_until timed out in 5000 milliseconds


def test_tabulator_update_hidden_columns(page):
Expand Down
Loading