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

Upgrade Tabulator to 5.x #2898

Merged
merged 5 commits into from
Nov 9, 2021
Merged
Show file tree
Hide file tree
Changes from 4 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
13 changes: 9 additions & 4 deletions examples/reference/widgets/Tabulator.ipynb
Original file line number Diff line number Diff line change
Expand Up @@ -376,7 +376,7 @@
"metadata": {},
"outputs": [],
"source": [
"pn.widgets.Tabulator(df, header_align='center', text_align={'str': 'right', 'bool': 'center' }, widths=200)"
"pn.widgets.Tabulator(df, header_align='center', text_align={'str': 'right', 'bool': 'center'}, widths=200)"
]
},
{
Expand Down Expand Up @@ -711,7 +711,7 @@
"metadata": {},
"outputs": [],
"source": [
"large_df = pd._testing.makeCustomDataframe(100000, 5) "
"large_df = pd._testing.makeCustomDataframe(100000, 5)"
]
},
{
Expand Down Expand Up @@ -739,6 +739,7 @@
"outputs": [],
"source": [
"%%time\n",
"medium_df = pd._testing.makeCustomDataframe(10000, 5)\n",
"paginated_table = pn.widgets.Tabulator(large_df, pagination='local', page_size=10)\n",
"paginated_table"
]
Expand Down Expand Up @@ -1150,7 +1151,7 @@
"Panel does not expose all options available from Tabulator, if a desired option is not natively supported, it can be set via the `configuration` argument. \n",
"This dictionary can be seen as a base dictionary which the tabulator object fills and passes to the Tabulator javascript-library.\n",
"\n",
"As an example, we can turn off sorting and resizing of columns by disabling the `headerSort` and `resizableColumn` options."
"As an example, we can turn off sorting and resizing of columns by disabling the `headerSort` and `resizable` options."
]
},
{
Expand All @@ -1167,7 +1168,11 @@
" 'date': [dt.date(2019, 1, 1), dt.date(2020, 1, 1), dt.date(2020, 1, 10)]\n",
"}, index=[1, 2, 3])\n",
"\n",
"df_widget = pn.widgets.Tabulator(df, configuration={\"headerSort\": False, \"resizableColumns\": False})\n",
"df_widget = pn.widgets.Tabulator(df, configuration={'columnDefaults': {\n",
" 'resizable': False,\n",
" 'headerSort': True\n",
"}})\n",
"\n",
"df_widget.servable()"
]
},
Expand Down
6 changes: 3 additions & 3 deletions panel/models/tabulator.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,10 +15,10 @@
from ..io.resources import bundled_files
from ..util import classproperty

JS_SRC = "https://unpkg.com/tabulator-tables@4.9.3/dist/js/tabulator.js"
MOMENT_SRC = "https://unpkg.com/moment@2.27.0/moment.js"
JS_SRC = "https://unpkg.com/tabulator-tables@5.0.7/dist/js/tabulator.js"
MOMENT_SRC = "https://cdn.jsdelivr.net/npm/luxon/build/global/luxon.min.js"
philippjfr marked this conversation as resolved.
Show resolved Hide resolved

THEME_PATH = "tabulator-tables@4.9.3/dist/css/"
THEME_PATH = "tabulator-tables@5.0.7/dist/css/"
THEME_URL = f"https://unpkg.com/{THEME_PATH}"
PANEL_CDN = f'https://cdn.jsdelivr.net/npm/@holoviz/panel/dist/bundled/{THEME_PATH}'
TABULATOR_THEMES = [
Expand Down
166 changes: 71 additions & 95 deletions panel/models/tabulator.ts
Original file line number Diff line number Diff line change
Expand Up @@ -107,13 +107,11 @@ function group_data(records: any[], columns: any[], indexes: string[], aggregato
}


// The view of the Bokeh extension/ HTML element
// Here you can define how to render the model as well as react to model changes or View events.
export class DataTabulatorView extends PanelHTMLBoxView {
model: DataTabulator;
tabulator: any;
_tabulator_cell_updating: boolean=false
_data_updating: boolean = true
_updating_page: boolean = true
_selection_updating: boolean =false
_styled_cells: any[] = []
_styles: any;
Expand All @@ -132,9 +130,7 @@ export class DataTabulatorView extends PanelHTMLBoxView {
this.tabulator.download(ftype, this.model.filename)
})

this.connect(this.model.properties.children.change, () => {
this._render_children()
})
this.connect(this.model.properties.children.change, () => this._render_children())

this.connect(this.model.properties.expanded.change, () => {
for (const row of this.tabulator.rowManager.getRows()) {
Expand All @@ -143,34 +139,20 @@ export class DataTabulatorView extends PanelHTMLBoxView {
}
})

this.connect(this.model.properties.hidden_columns.change, () => {
this.hideColumns()
})

this.connect(this.model.properties.page_size.change, () => {
this.setPageSize();
})

this.connect(this.model.properties.page.change, () => {
this.setPage();
})

this.connect(this.model.properties.max_page.change, () => {
this.setMaxPage();
})

this.connect(this.model.properties.frozen_rows.change, () => {
this.freezeRows()
})

this.connect(this.model.properties.styles.change, () => {
this._styles = this.model.styles
this.updateStyles()
})

this.connect(this.model.source.properties.data.change, () => {
this.setData()
this.connect(this.model.properties.hidden_columns.change, () => this.hideColumns())
this.connect(this.model.properties.page_size.change, () => this.setPageSize())
this.connect(this.model.properties.page.change, () => {
if (!this._updating_page)
this.setPage()
})
this.connect(this.model.properties.max_page.change, () => this.setMaxPage())
this.connect(this.model.properties.frozen_rows.change, () => this.freezeRows())
this.connect(this.model.source.properties.data.change, () => this.setData())
this.connect(this.model.source.streaming, () => this.addData())
this.connect(this.model.source.patching, () => this.updateOrAddData())
this.connect(this.model.source.selected.change, () => this.updateSelection())
Expand All @@ -185,6 +167,29 @@ export class DataTabulatorView extends PanelHTMLBoxView {
this.invalidate_layout()
}

init_callbacks(): void {
this.tabulator.on("tableBuilding", () => this.tableInit())
this.tabulator.on("tableBuilt", () => this.tableBuilt())
this.tabulator.on("rowSelectionChanged", (data: any, rows: any) => this.rowSelectionChanged(data, rows))
this.tabulator.on("rowClick", (e: any, row: any) => this.rowClicked(e, row))
this.tabulator.on("cellEdited", (cell: any) => this.cellEdited(cell))
this.tabulator.on("selectableCheck", (row: any) => {
const selectable = this.model.selectable_rows
return (selectable == null) || (selectable.indexOf(row._row.data._index) >= 0)
})
this.tabulator.on("tooltips", (cell: any) => {
return cell.getColumn().getField() + ": " + cell.getValue();
})
this.tabulator.on("scrollVertical", debounce(() => {
this.updateStyles()
}, 50, false))
this.tabulator.on("rowFormatter", (row: any) => this._render_row(row))
this.tabulator.on("dataFiltering", () => {
if (this.tabulator != null)
this.model.filters = this.tabulator.getHeaderFilters()
})
}

render(): void {
super.render()
const wait = this.setCSS()
Expand All @@ -197,17 +202,27 @@ export class DataTabulatorView extends PanelHTMLBoxView {
let configuration = this.getConfiguration()

this.tabulator = new Tabulator(container, configuration)
this.init_callbacks()
this._render_children()

// Swap pagination mode
if (this.model.pagination === 'remote') {
this.tabulator.options.pagination = this.model.pagination
this.tabulator.modules.page.mode = 'remote'
}

this.setGroupBy()
this.hideColumns()

this.el.appendChild(container)
}

tableInit(): void {
// Patch the ajax request and page data parsing methods
const ajax = this.tabulator.modules.ajax
ajax.sendRequest = () => {
return this.requestPage(ajax.params.page, ajax.params.sort)
}
this.tabulator.modules.page._parseRemoteData = (): boolean => {
return false
}
}

tableBuilt(): void {
// Set up page
if (this.model.pagination) {
this.setMaxPage()
Expand All @@ -216,27 +231,23 @@ export class DataTabulatorView extends PanelHTMLBoxView {
} else {
this.freezeRows()
}
this.el.appendChild(container)
}

tableInit(view: DataTabulatorView, tabulator: any): void {
// Patch the ajax request and page data parsing methods
const ajax = tabulator.modules.ajax
ajax.sendRequest = () => {
return view.requestPage(ajax.params.page, ajax.params.sorters)
}
tabulator.modules.page._parseRemoteData = () => {}
this.tabulator.redraw(true)
this.updateStyles()
this.updateSelection()
this._initializing = false
}

requestPage(page: number, sorters: any[]): Promise<void> {
return new Promise((resolve: any, reject: any) => {
try {
if (page != null && sorters != null) {
if (this._data_updating)
this._data_updating = false
else
this.model.sorters = sorters
this.model.page = page || 1
this.model.sorters = sorters
this._updating_page = true
try {
this.model.page = page || 1
} finally {
this._updating_page = false
}
}
resolve([])
} catch(err) {
Expand All @@ -245,16 +256,6 @@ export class DataTabulatorView extends PanelHTMLBoxView {
})
}

renderComplete(): void {
// Only have to set up styles after initial render subsequent
// styling is handled by change event on styles property
if (this._initializing) {
this.updateStyles()
this.updateSelection()
}
this._initializing = false
}

freezeRows(): void {
for (const row of this.model.frozen_rows)
this.tabulator.getRow(row).freeze()
Expand All @@ -277,44 +278,23 @@ export class DataTabulatorView extends PanelHTMLBoxView {
}

getConfiguration(): any {
const pagination = this.model.pagination == 'remote' ? 'local': (this.model.pagination || false)
// Only use selectable mode if explicitly requested otherwise manually handle selections
let selectable = this.model.select_mode === 'toggle' ? true : NaN
const that = this
let configuration = {
...this.model.configuration,
index: "_index",
nestedFieldSeparator: false,
selectable: selectable,
tableBuilding: function() { that.tableInit(that, this) },
renderComplete: () => this.renderComplete(),
rowSelectionChanged: (data: any, rows: any) => this.rowSelectionChanged(data, rows),
rowClick: (e: any, row: any) => this.rowClicked(e, row),
cellEdited: (cell: any) => this.cellEdited(cell),
columns: this.getColumns(),
layout: this.getLayout(),
pagination: pagination,
pagination: this.model.pagination != null,
paginationMode: this.model.pagination,
paginationSize: this.model.page_size,
paginationInitialPage: 1,
selectableCheck: (row: any) => {
const selectable = this.model.selectable_rows
return (selectable == null) || (selectable.indexOf(row._row.data._index) >= 0)
},
tooltips: (cell: any) => {
return cell.getColumn().getField() + ": " + cell.getValue();
},
scrollVertical: debounce(() => {
this.updateStyles()
}, 50, false),
rowFormatter: (row: any) => this._render_row(row),
dataFiltering: () => {
if (this.tabulator != null)
this.model.filters = this.tabulator.getHeaderFilters()
}
}
if (pagination) {
if (this.model.pagination === "remote") {
configuration['ajaxURL'] = "http://panel.pyviz.org"
configuration['ajaxSorting'] = true
configuration['sortMode'] = "remote"
}
const cds: any = this.model.source;
let data: any[]
Expand Down Expand Up @@ -525,25 +505,21 @@ export class DataTabulatorView extends PanelHTMLBoxView {
return view.inputEl
}

after_layout(): void {
super.after_layout()
if (this.tabulator != null)
this.tabulator.redraw(true)
this.updateStyles()
}

// Update table

setData(): void {
let data = transform_cds_to_records(this.model.source, true);
getData(): any[] {
let data = transform_cds_to_records(this.model.source, true)
if (this.model.configuration.dataTree)
data = group_data(data, this.model.columns, this.model.indexes, this.model.aggregators)
this._data_updating = true
return data
}

setData(): void {
const data = this.getData()
if (this.model.pagination != null)
this.tabulator.rowManager.setData(data, true, false)
else {
this.tabulator.setData(data)
this._data_updating = false
}
this.freezeRows()
this.updateSelection()
Expand Down