diff --git a/docs/examples/getting_started/app.py b/docs/examples/getting_started/app.py index fa1695e..6d7e018 100644 --- a/docs/examples/getting_started/app.py +++ b/docs/examples/getting_started/app.py @@ -15,7 +15,7 @@ def tabulator(): df = pd.read_csv( "https://raw.githubusercontent.com/datasciencedojo/datasets/master/titanic.csv" ) - return Tabulator(df, table_options={"height": 311}) + return Tabulator(df, options={"height": 311}) @render.code async def txt(): diff --git a/get-py-bindings.sh b/get-py-bindings.sh new file mode 100755 index 0000000..e3da277 --- /dev/null +++ b/get-py-bindings.sh @@ -0,0 +1,4 @@ +#!/bin/sh +branch=${1:-dev} +# curl -O https://raw.githubusercontent.com/eodaGmbH/tabulator-bindings/${branch}/r-bindings/rtabulator.js +curl -o pytabulator/srcjs/pytabulator.js https://raw.githubusercontent.com/eodaGmbH/tabulator-bindings/refs/heads/feature/typescript/py-bindings/pytabulator.js diff --git a/pytabulator/shiny_bindings.py b/pytabulator/shiny_bindings.py index 341b5b5..07fc315 100644 --- a/pytabulator/shiny_bindings.py +++ b/pytabulator/shiny_bindings.py @@ -33,10 +33,10 @@ def tabulator_dep() -> HTMLDependency: tabulator_bindings_dep = HTMLDependency( - "tabulator-bindings", + "pytabulator", "0.1.0", source={"package": "pytabulator", "subdir": "srcjs"}, - script={"src": "tabulator-bindings.js", "type": "module"}, + script={"src": "pytabulator.js", "type": "module"}, all_files=False, ) diff --git a/pytabulator/srcjs/pytabulator.js b/pytabulator/srcjs/pytabulator.js new file mode 100644 index 0000000..0078270 --- /dev/null +++ b/pytabulator/srcjs/pytabulator.js @@ -0,0 +1,129 @@ +"use strict"; +(() => { + // built/utils.js + function convertToDataFrame(data) { + const res = {}; + if (data.length === 0) { + return res; + } + const keys = Object.keys(data[0]); + keys.forEach((key) => res[key] = data.map((item) => item[key])); + return res; + } + + // built/events.js + function addEventListeners(tabulatorWidget) { + const table = tabulatorWidget.getTable(); + const elementId = tabulatorWidget.getElementId(); + const bindingLang = tabulatorWidget.getBindingLang(); + console.log("binding lang", bindingLang); + table.on("rowClick", function(e, row) { + const inputName = `${elementId}_row_clicked`; + console.log(inputName, row.getData()); + Shiny.onInputChange(inputName, row.getData()); + }); + table.on("rowClick", (e, row) => { + const inputName = bindingLang === "r" ? `${elementId}_data:rtabulator.data` : `${elementId}_data`; + const data = table.getSelectedRows().map((row2) => row2.getData()); + console.log(inputName, data); + Shiny.onInputChange(inputName, { data: convertToDataFrame(data) }); + }); + table.on("cellEdited", function(cell) { + const inputName = `${elementId}_cell_edited`; + console.log(inputName, cell.getData()); + Shiny.onInputChange(inputName, cell.getData()); + }); + table.on("dataFiltered", function(filters, rows) { + const inputName = bindingLang === "r" ? `${elementId}_data:rtabulator.data` : `${elementId}_data`; + const data = rows.map((row) => row.getData()); + console.log(inputName, data); + Shiny.onInputChange(inputName, { data: convertToDataFrame(data) }); + }); + } + + // built/widget.js + function run_calls(tabulatorWidget, calls) { + const table = tabulatorWidget.getTable(); + const elementId = tabulatorWidget.getElementId(); + const bindingLang = tabulatorWidget.getBindingLang(); + console.log("binding lang", bindingLang); + calls.forEach(([method_name, options]) => { + if (method_name === "getData") { + const inputName = bindingLang === "r" ? `${elementId}_data:rtabulator.data` : `${elementId}_data`; + console.log("custom call", inputName); + Shiny.setInputValue(inputName, { data: convertToDataFrame(table.getData()) }, { priority: "event" }); + return; + } + if (method_name === "deleteSelectedRows") { + console.log("custom call"); + const rows = table.getSelectedRows(); + rows.forEach((row) => { + console.log(row.getIndex()); + table.deleteRow(row.getIndex()); + }); + return; + } + if (method_name === "getSheetData") { + const inputName = bindingLang === "r" ? `${elementId}_sheet_data:rtabulator.sheet_data` : `${elementId}_sheet_data`; + console.log("custom call", inputName); + Shiny.setInputValue(inputName, { data: table.getSheetData() }, { priority: "event" }); + return; + } + console.log(method_name, options); + table[method_name](...options); + }); + } + var TabulatorWidget = class { + constructor(container, data, options, bindingOptions) { + options.data = data; + this._container = container; + this._bindingOptions = bindingOptions; + console.log("columns", options.columns); + if (data !== null && options.columns == null) { + options.autoColumns = true; + } + if (options.spreadsheet && options.spreadsheetData == null) { + options.spreadsheetData = []; + } + this._table = new Tabulator(this._container, options); + if (typeof Shiny === "object") { + addEventListeners(this); + this._addShinyMessageHandler(); + } + } + _addShinyMessageHandler() { + const messageHandlerName = `tabulator-${this._container.id}`; + Shiny.addCustomMessageHandler(messageHandlerName, (payload) => { + console.log(payload); + run_calls(this, payload.calls); + }); + } + getTable() { + return this._table; + } + getElementId() { + return this._container.id; + } + getBindingLang() { + return this._bindingOptions.lang; + } + }; + + // built/index-py.js + var TabulatorOutputBinding = class extends Shiny.OutputBinding { + find(scope) { + return scope.find(".shiny-tabulator-output"); + } + renderValue(el, payload) { + console.log("payload", payload); + const widget = new TabulatorWidget(el, payload.data, payload.options, payload.bindingOptions); + const table = widget.getTable(); + table.on("tableBuilt", function() { + if (payload.options.columnUpdates != null) { + console.log("column updates", payload.options.columnUpdates); + } + }); + } + }; + Shiny.outputBindings.register(new TabulatorOutputBinding(), "shiny-tabulator-output"); +})(); diff --git a/pytabulator/srcjs/tabulator-bindings.js b/pytabulator/srcjs/tabulator-bindings.js deleted file mode 100644 index 81b0324..0000000 --- a/pytabulator/srcjs/tabulator-bindings.js +++ /dev/null @@ -1 +0,0 @@ -(()=>{function c(s,e){s.on("rowClick",function(n,t){let o=`${e.id}_row_clicked`;console.log(o,t.getData()),Shiny.onInputChange(o,t.getData())}),s.on("rowClick",(n,t)=>{let o=`${e.id}_rows_selected`,a=s.getSelectedRows().map(i=>i.getData());console.log(o,a),Shiny.onInputChange(o,a)}),s.on("cellEdited",function(n){let t=`${e.id}_row_edited`;console.log(t,n.getData()),Shiny.onInputChange(t,n.getData())}),s.on("dataFiltered",function(n,t){let o=`${e.id}_data_filtered`,a=t.map(i=>i.getData());console.log(o,a),Shiny.onInputChange(o,a)})}function r(s,e,n){n.forEach(([t,o])=>{if(t==="getData"){console.log("custom call"),Shiny.onInputChange(`${s.id}_data`,e.getData());return}if(t==="deleteSelectedRows"){console.log("custom call"),e.getSelectedRows().forEach(i=>{console.log(i.getIndex()),e.deleteRow(i.getIndex())});return}console.log(t,o),e[t](...o)})}var l=class{constructor(e,n,t){t.data=n,this._container=e,console.log("columns",t.columns),t.columns==null&&(t.autoColumns=!0),this._table=new Tabulator(this._container,t),typeof Shiny=="object"&&(c(this._table,this._container),this._addShinyMessageHandler())}_addShinyMessageHandler(){let e=`tabulator-${this._container.id}`;Shiny.addCustomMessageHandler(e,n=>{console.log(n),r(this._container,this._table,n.calls)})}getTable(){return this._table}};var u=class extends Shiny.OutputBinding{find(e){return e.find(".shiny-tabulator-output")}renderValue(e,n){console.log("payload",n),new l(e,n.data,n.options).getTable().on("tableBuilt",function(){n.options.columnUpdates!=null&&console.log("column updates",n.options.columnUpdates)})}};Shiny.outputBindings.register(new u,"shiny-tabulator-output");})(); diff --git a/pytabulator/tabulator.py b/pytabulator/tabulator.py index b9b9613..efba098 100644 --- a/pytabulator/tabulator.py +++ b/pytabulator/tabulator.py @@ -70,7 +70,11 @@ def set_options(self, **kwargs) -> Self: return self def to_dict(self) -> dict: - # TODO: Rename 'data' to ??? + # TODO: Rename 'data' to ??? model!? data = df_to_dict(self.df) data["options"] = self._options.to_dict() + data["bindingOptions"] = dict(lang = "python") return data + + def to_html(self): + pass