diff --git a/docs/dist/idocs.compiler.umd.js b/docs/dist/idocs.compiler.umd.js
index a257c689..ae51754d 100644
--- a/docs/dist/idocs.compiler.umd.js
+++ b/docs/dist/idocs.compiler.umd.js
@@ -371,8 +371,8 @@ ${content}
break;
}
case "table": {
- const { dataSourceName, variableId, tabulatorOptions } = element;
- const tableSpec = { dataSourceName, variableId, tabulatorOptions };
+ const { dataSourceName, variableId, tabulatorOptions, editable } = element;
+ const tableSpec = { dataSourceName, variableId, tabulatorOptions, editable };
addSpec("tabulator", tableSpec);
break;
}
diff --git a/docs/dist/idocs.editor.umd.js b/docs/dist/idocs.editor.umd.js
index 97b62e58..a3bd9532 100644
--- a/docs/dist/idocs.editor.umd.js
+++ b/docs/dist/idocs.editor.umd.js
@@ -373,8 +373,8 @@ ${content}
break;
}
case "table": {
- const { dataSourceName, variableId, tabulatorOptions } = element;
- const tableSpec = { dataSourceName, variableId, tabulatorOptions };
+ const { dataSourceName, variableId, tabulatorOptions, editable } = element;
+ const tableSpec = { dataSourceName, variableId, tabulatorOptions, editable };
addSpec("tabulator", tableSpec);
break;
}
@@ -853,6 +853,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
function pluginClassName(pluginName2) {
return \`chartifact-plugin-\${pluginName2}\`;
}
+ const newId = () => [...Date.now().toString(36) + Math.random().toString(36).slice(2)].sort(() => 0.5 - Math.random()).join("");
/*!
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
@@ -1882,17 +1883,30 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
...flaggableJsonPlugin(pluginName$3, className$3, inspectTabulatorSpec, { style: "box-sizing: border-box;" }),
hydrateComponent: async (renderer, errorHandler, specs) => {
const tabulatorInstances = [];
+ const deleteFieldname = newId();
for (let index2 = 0; index2 < specs.length; index2++) {
const specReview = specs[index2];
if (!specReview.approvedSpec) {
continue;
}
const container = renderer.element.querySelector(\`#\${specReview.containerId}\`);
+ if (!container) {
+ continue;
+ }
+ const spec = specReview.approvedSpec;
+ const buttons = spec.editable ? \`
+
+
+
\` : "";
+ container.innerHTML = \`\`;
+ const nestedDiv = container.querySelector(".tabulator-nested");
if (!Tabulator && index2 === 0) {
errorHandler(new Error("Tabulator not found"), pluginName$3, index2, "init", container);
continue;
}
- const spec = specReview.approvedSpec;
if (!spec.dataSourceName || !spec.variableId) {
errorHandler(new Error("Tabulator requires dataSourceName and variableId"), pluginName$3, index2, "init", container);
continue;
@@ -1908,8 +1922,19 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
if (spec.tabulatorOptions && Object.keys(spec.tabulatorOptions).length > 0) {
options = spec.tabulatorOptions;
}
- const table = new Tabulator(container, options);
- const tabulatorInstance = { id: \`\${pluginName$3}-\${index2}\`, spec, table, built: false };
+ const selectableRows = !!(options == null ? void 0 : options.selectableRows) || false;
+ if (spec.editable && selectableRows) {
+ delete options.selectableRows;
+ }
+ const table = new Tabulator(nestedDiv, options);
+ const tabulatorInstance = {
+ id: \`\${pluginName$3}-\${index2}\`,
+ spec,
+ container,
+ table,
+ built: false,
+ selectableRows
+ };
table.on("tableBuilt", () => {
table.off("tableBuilt");
tabulatorInstance.built = true;
@@ -1917,65 +1942,141 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
tabulatorInstances.push(tabulatorInstance);
}
const instances = tabulatorInstances.map((tabulatorInstance, index2) => {
- var _a;
+ const { container, spec, table, selectableRows } = tabulatorInstance;
const initialSignals = [{
- name: tabulatorInstance.spec.dataSourceName,
+ name: spec.dataSourceName,
value: null,
priority: -1,
isData: true
}];
- if ((_a = tabulatorInstance.spec.tabulatorOptions) == null ? void 0 : _a.selectableRows) {
+ if (selectableRows || spec.editable) {
initialSignals.push({
- name: tabulatorInstance.spec.variableId,
+ name: spec.variableId,
value: [],
priority: -1,
isData: true
});
}
+ const outputData = () => {
+ let data;
+ if (selectableRows) {
+ data = table.getSelectedData();
+ } else {
+ data = table.getData();
+ }
+ data.forEach((row) => {
+ delete row[deleteFieldname];
+ });
+ const value = structuredClone(data);
+ const batch = {
+ [spec.variableId]: {
+ value,
+ isData: true
+ }
+ };
+ renderer.signalBus.log(tabulatorInstance.id, "sending batch", batch);
+ renderer.signalBus.broadcast(tabulatorInstance.id, batch);
+ };
+ const setData = (data) => {
+ table.setData(data).then(() => {
+ let columns = table.getColumnDefinitions().filter((cd) => cd.field !== deleteFieldname).filter((cd) => cd.formatter !== "rowSelection");
+ if (spec.editable) {
+ columns.unshift({
+ headerSort: false,
+ title: "Delete",
+ field: deleteFieldname,
+ titleFormatter: "tickCross",
+ width: 40,
+ formatter: "tickCross",
+ cellClick: (e, cell) => {
+ cell.getRow().delete();
+ outputData();
+ }
+ });
+ columns = columns.map((col) => {
+ if (col.editor === void 0) {
+ const type = col.type || col.sorter;
+ if (type === "number") {
+ return { ...col, editor: "number" };
+ }
+ if (type === "date" || type === "datetime") {
+ return { ...col, editor: "date" };
+ }
+ if (type === "boolean") {
+ return { ...col, editor: "tickCross" };
+ }
+ return { ...col, editor: "input" };
+ }
+ return col;
+ });
+ }
+ table.setColumns(columns);
+ outputData();
+ }).catch((error) => {
+ console.error(\`Error setting data for Tabulator \${spec.variableId}:\`, error);
+ });
+ };
+ if (spec.editable) {
+ const addRowBtn = container.querySelector(".tabulator-add-row");
+ const resetBtn = container.querySelector(".tabulator-reset");
+ if (addRowBtn) {
+ addRowBtn.onclick = () => {
+ table.addRow({}).then((row) => {
+ row.scrollTo();
+ });
+ };
+ }
+ if (resetBtn) {
+ resetBtn.onclick = () => {
+ const value = renderer.signalBus.signalDeps[spec.dataSourceName].value;
+ if (Array.isArray(value)) {
+ setData(value);
+ }
+ };
+ }
+ }
return {
...tabulatorInstance,
initialSignals,
- recieveBatch: async (batch) => {
- var _a2;
- const newData = (_a2 = batch[tabulatorInstance.spec.dataSourceName]) == null ? void 0 : _a2.value;
+ recieveBatch: async (batch, from) => {
+ var _a;
+ const newData = (_a = batch[spec.dataSourceName]) == null ? void 0 : _a.value;
if (newData) {
if (!tabulatorInstance.built) {
- tabulatorInstance.table.off("tableBuilt");
- tabulatorInstance.table.on("tableBuilt", () => {
+ table.off("tableBuilt");
+ table.on("tableBuilt", () => {
tabulatorInstance.built = true;
- tabulatorInstance.table.off("tableBuilt");
- tabulatorInstance.table.setData(newData);
+ table.off("tableBuilt");
+ setData(newData);
});
} else {
- tabulatorInstance.table.setData(newData);
+ setData(newData);
}
}
},
beginListening(sharedSignals) {
- var _a2;
- if ((_a2 = tabulatorInstance.spec.tabulatorOptions) == null ? void 0 : _a2.selectableRows) {
- for (const { isData, signalName } of sharedSignals) {
- if (isData) {
- const matchData = signalName === tabulatorInstance.spec.variableId;
- if (matchData) {
- tabulatorInstance.table.on("rowSelectionChanged", (e, rows) => {
- const selectedData = tabulatorInstance.table.getSelectedData();
- const batch = {
- [tabulatorInstance.spec.variableId]: {
- value: selectedData,
- isData: true
- }
- };
- renderer.signalBus.log(tabulatorInstance.id, "sending batch", batch);
- renderer.signalBus.broadcast(tabulatorInstance.id, batch);
- });
- }
- }
+ if (selectableRows) {
+ const hasMatchingSignal = sharedSignals.some(
+ ({ isData, signalName }) => isData && signalName === spec.variableId
+ );
+ if (hasMatchingSignal) {
+ table.on("rowSelectionChanged", (e, rows) => {
+ outputData();
+ });
}
}
+ if (spec.editable) {
+ table.on("cellEdited", (cell) => {
+ outputData();
+ });
+ }
},
getCurrentSignalValue() {
- return tabulatorInstance.table.getSelectedData();
+ if (selectableRows) {
+ return tabulatorInstance.table.getSelectedData();
+ } else {
+ return tabulatorInstance.table.getData();
+ }
},
destroy: () => {
tabulatorInstance.table.destroy();
diff --git a/docs/dist/idocs.host.umd.js b/docs/dist/idocs.host.umd.js
index 2368f244..0666800f 100644
--- a/docs/dist/idocs.host.umd.js
+++ b/docs/dist/idocs.host.umd.js
@@ -373,8 +373,8 @@ ${content}
break;
}
case "table": {
- const { dataSourceName, variableId, tabulatorOptions } = element;
- const tableSpec = { dataSourceName, variableId, tabulatorOptions };
+ const { dataSourceName, variableId, tabulatorOptions, editable } = element;
+ const tableSpec = { dataSourceName, variableId, tabulatorOptions, editable };
addSpec("tabulator", tableSpec);
break;
}
@@ -853,6 +853,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
function pluginClassName(pluginName2) {
return \`chartifact-plugin-\${pluginName2}\`;
}
+ const newId = () => [...Date.now().toString(36) + Math.random().toString(36).slice(2)].sort(() => 0.5 - Math.random()).join("");
/*!
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
@@ -1882,17 +1883,30 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
...flaggableJsonPlugin(pluginName$3, className$3, inspectTabulatorSpec, { style: "box-sizing: border-box;" }),
hydrateComponent: async (renderer, errorHandler, specs) => {
const tabulatorInstances = [];
+ const deleteFieldname = newId();
for (let index2 = 0; index2 < specs.length; index2++) {
const specReview = specs[index2];
if (!specReview.approvedSpec) {
continue;
}
const container = renderer.element.querySelector(\`#\${specReview.containerId}\`);
+ if (!container) {
+ continue;
+ }
+ const spec = specReview.approvedSpec;
+ const buttons = spec.editable ? \`
+
+
+
\` : "";
+ container.innerHTML = \`\`;
+ const nestedDiv = container.querySelector(".tabulator-nested");
if (!Tabulator && index2 === 0) {
errorHandler(new Error("Tabulator not found"), pluginName$3, index2, "init", container);
continue;
}
- const spec = specReview.approvedSpec;
if (!spec.dataSourceName || !spec.variableId) {
errorHandler(new Error("Tabulator requires dataSourceName and variableId"), pluginName$3, index2, "init", container);
continue;
@@ -1908,8 +1922,19 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
if (spec.tabulatorOptions && Object.keys(spec.tabulatorOptions).length > 0) {
options = spec.tabulatorOptions;
}
- const table = new Tabulator(container, options);
- const tabulatorInstance = { id: \`\${pluginName$3}-\${index2}\`, spec, table, built: false };
+ const selectableRows = !!(options == null ? void 0 : options.selectableRows) || false;
+ if (spec.editable && selectableRows) {
+ delete options.selectableRows;
+ }
+ const table = new Tabulator(nestedDiv, options);
+ const tabulatorInstance = {
+ id: \`\${pluginName$3}-\${index2}\`,
+ spec,
+ container,
+ table,
+ built: false,
+ selectableRows
+ };
table.on("tableBuilt", () => {
table.off("tableBuilt");
tabulatorInstance.built = true;
@@ -1917,65 +1942,141 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
tabulatorInstances.push(tabulatorInstance);
}
const instances = tabulatorInstances.map((tabulatorInstance, index2) => {
- var _a;
+ const { container, spec, table, selectableRows } = tabulatorInstance;
const initialSignals = [{
- name: tabulatorInstance.spec.dataSourceName,
+ name: spec.dataSourceName,
value: null,
priority: -1,
isData: true
}];
- if ((_a = tabulatorInstance.spec.tabulatorOptions) == null ? void 0 : _a.selectableRows) {
+ if (selectableRows || spec.editable) {
initialSignals.push({
- name: tabulatorInstance.spec.variableId,
+ name: spec.variableId,
value: [],
priority: -1,
isData: true
});
}
+ const outputData = () => {
+ let data;
+ if (selectableRows) {
+ data = table.getSelectedData();
+ } else {
+ data = table.getData();
+ }
+ data.forEach((row) => {
+ delete row[deleteFieldname];
+ });
+ const value = structuredClone(data);
+ const batch = {
+ [spec.variableId]: {
+ value,
+ isData: true
+ }
+ };
+ renderer.signalBus.log(tabulatorInstance.id, "sending batch", batch);
+ renderer.signalBus.broadcast(tabulatorInstance.id, batch);
+ };
+ const setData = (data) => {
+ table.setData(data).then(() => {
+ let columns = table.getColumnDefinitions().filter((cd) => cd.field !== deleteFieldname).filter((cd) => cd.formatter !== "rowSelection");
+ if (spec.editable) {
+ columns.unshift({
+ headerSort: false,
+ title: "Delete",
+ field: deleteFieldname,
+ titleFormatter: "tickCross",
+ width: 40,
+ formatter: "tickCross",
+ cellClick: (e, cell) => {
+ cell.getRow().delete();
+ outputData();
+ }
+ });
+ columns = columns.map((col) => {
+ if (col.editor === void 0) {
+ const type = col.type || col.sorter;
+ if (type === "number") {
+ return { ...col, editor: "number" };
+ }
+ if (type === "date" || type === "datetime") {
+ return { ...col, editor: "date" };
+ }
+ if (type === "boolean") {
+ return { ...col, editor: "tickCross" };
+ }
+ return { ...col, editor: "input" };
+ }
+ return col;
+ });
+ }
+ table.setColumns(columns);
+ outputData();
+ }).catch((error) => {
+ console.error(\`Error setting data for Tabulator \${spec.variableId}:\`, error);
+ });
+ };
+ if (spec.editable) {
+ const addRowBtn = container.querySelector(".tabulator-add-row");
+ const resetBtn = container.querySelector(".tabulator-reset");
+ if (addRowBtn) {
+ addRowBtn.onclick = () => {
+ table.addRow({}).then((row) => {
+ row.scrollTo();
+ });
+ };
+ }
+ if (resetBtn) {
+ resetBtn.onclick = () => {
+ const value = renderer.signalBus.signalDeps[spec.dataSourceName].value;
+ if (Array.isArray(value)) {
+ setData(value);
+ }
+ };
+ }
+ }
return {
...tabulatorInstance,
initialSignals,
- recieveBatch: async (batch) => {
- var _a2;
- const newData = (_a2 = batch[tabulatorInstance.spec.dataSourceName]) == null ? void 0 : _a2.value;
+ recieveBatch: async (batch, from) => {
+ var _a;
+ const newData = (_a = batch[spec.dataSourceName]) == null ? void 0 : _a.value;
if (newData) {
if (!tabulatorInstance.built) {
- tabulatorInstance.table.off("tableBuilt");
- tabulatorInstance.table.on("tableBuilt", () => {
+ table.off("tableBuilt");
+ table.on("tableBuilt", () => {
tabulatorInstance.built = true;
- tabulatorInstance.table.off("tableBuilt");
- tabulatorInstance.table.setData(newData);
+ table.off("tableBuilt");
+ setData(newData);
});
} else {
- tabulatorInstance.table.setData(newData);
+ setData(newData);
}
}
},
beginListening(sharedSignals) {
- var _a2;
- if ((_a2 = tabulatorInstance.spec.tabulatorOptions) == null ? void 0 : _a2.selectableRows) {
- for (const { isData, signalName } of sharedSignals) {
- if (isData) {
- const matchData = signalName === tabulatorInstance.spec.variableId;
- if (matchData) {
- tabulatorInstance.table.on("rowSelectionChanged", (e, rows) => {
- const selectedData = tabulatorInstance.table.getSelectedData();
- const batch = {
- [tabulatorInstance.spec.variableId]: {
- value: selectedData,
- isData: true
- }
- };
- renderer.signalBus.log(tabulatorInstance.id, "sending batch", batch);
- renderer.signalBus.broadcast(tabulatorInstance.id, batch);
- });
- }
- }
+ if (selectableRows) {
+ const hasMatchingSignal = sharedSignals.some(
+ ({ isData, signalName }) => isData && signalName === spec.variableId
+ );
+ if (hasMatchingSignal) {
+ table.on("rowSelectionChanged", (e, rows) => {
+ outputData();
+ });
}
}
+ if (spec.editable) {
+ table.on("cellEdited", (cell) => {
+ outputData();
+ });
+ }
},
getCurrentSignalValue() {
- return tabulatorInstance.table.getSelectedData();
+ if (selectableRows) {
+ return tabulatorInstance.table.getSelectedData();
+ } else {
+ return tabulatorInstance.table.getData();
+ }
},
destroy: () => {
tabulatorInstance.table.destroy();
diff --git a/docs/dist/idocs.sandbox.umd.js b/docs/dist/idocs.sandbox.umd.js
index e225188f..f4924cb8 100644
--- a/docs/dist/idocs.sandbox.umd.js
+++ b/docs/dist/idocs.sandbox.umd.js
@@ -464,6 +464,7 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
function pluginClassName(pluginName2) {
return \`chartifact-plugin-\${pluginName2}\`;
}
+ const newId = () => [...Date.now().toString(36) + Math.random().toString(36).slice(2)].sort(() => 0.5 - Math.random()).join("");
/*!
* Copyright (c) Microsoft Corporation.
* Licensed under the MIT License.
@@ -1493,17 +1494,30 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
...flaggableJsonPlugin(pluginName$3, className$3, inspectTabulatorSpec, { style: "box-sizing: border-box;" }),
hydrateComponent: async (renderer, errorHandler, specs) => {
const tabulatorInstances = [];
+ const deleteFieldname = newId();
for (let index2 = 0; index2 < specs.length; index2++) {
const specReview = specs[index2];
if (!specReview.approvedSpec) {
continue;
}
const container = renderer.element.querySelector(\`#\${specReview.containerId}\`);
+ if (!container) {
+ continue;
+ }
+ const spec = specReview.approvedSpec;
+ const buttons = spec.editable ? \`
+
+
+
\` : "";
+ container.innerHTML = \`\`;
+ const nestedDiv = container.querySelector(".tabulator-nested");
if (!Tabulator && index2 === 0) {
errorHandler(new Error("Tabulator not found"), pluginName$3, index2, "init", container);
continue;
}
- const spec = specReview.approvedSpec;
if (!spec.dataSourceName || !spec.variableId) {
errorHandler(new Error("Tabulator requires dataSourceName and variableId"), pluginName$3, index2, "init", container);
continue;
@@ -1519,8 +1533,19 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
if (spec.tabulatorOptions && Object.keys(spec.tabulatorOptions).length > 0) {
options = spec.tabulatorOptions;
}
- const table = new Tabulator(container, options);
- const tabulatorInstance = { id: \`\${pluginName$3}-\${index2}\`, spec, table, built: false };
+ const selectableRows = !!(options == null ? void 0 : options.selectableRows) || false;
+ if (spec.editable && selectableRows) {
+ delete options.selectableRows;
+ }
+ const table = new Tabulator(nestedDiv, options);
+ const tabulatorInstance = {
+ id: \`\${pluginName$3}-\${index2}\`,
+ spec,
+ container,
+ table,
+ built: false,
+ selectableRows
+ };
table.on("tableBuilt", () => {
table.off("tableBuilt");
tabulatorInstance.built = true;
@@ -1528,65 +1553,141 @@ var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "sy
tabulatorInstances.push(tabulatorInstance);
}
const instances = tabulatorInstances.map((tabulatorInstance, index2) => {
- var _a;
+ const { container, spec, table, selectableRows } = tabulatorInstance;
const initialSignals = [{
- name: tabulatorInstance.spec.dataSourceName,
+ name: spec.dataSourceName,
value: null,
priority: -1,
isData: true
}];
- if ((_a = tabulatorInstance.spec.tabulatorOptions) == null ? void 0 : _a.selectableRows) {
+ if (selectableRows || spec.editable) {
initialSignals.push({
- name: tabulatorInstance.spec.variableId,
+ name: spec.variableId,
value: [],
priority: -1,
isData: true
});
}
+ const outputData = () => {
+ let data;
+ if (selectableRows) {
+ data = table.getSelectedData();
+ } else {
+ data = table.getData();
+ }
+ data.forEach((row) => {
+ delete row[deleteFieldname];
+ });
+ const value = structuredClone(data);
+ const batch = {
+ [spec.variableId]: {
+ value,
+ isData: true
+ }
+ };
+ renderer.signalBus.log(tabulatorInstance.id, "sending batch", batch);
+ renderer.signalBus.broadcast(tabulatorInstance.id, batch);
+ };
+ const setData = (data) => {
+ table.setData(data).then(() => {
+ let columns = table.getColumnDefinitions().filter((cd) => cd.field !== deleteFieldname).filter((cd) => cd.formatter !== "rowSelection");
+ if (spec.editable) {
+ columns.unshift({
+ headerSort: false,
+ title: "Delete",
+ field: deleteFieldname,
+ titleFormatter: "tickCross",
+ width: 40,
+ formatter: "tickCross",
+ cellClick: (e, cell) => {
+ cell.getRow().delete();
+ outputData();
+ }
+ });
+ columns = columns.map((col) => {
+ if (col.editor === void 0) {
+ const type = col.type || col.sorter;
+ if (type === "number") {
+ return { ...col, editor: "number" };
+ }
+ if (type === "date" || type === "datetime") {
+ return { ...col, editor: "date" };
+ }
+ if (type === "boolean") {
+ return { ...col, editor: "tickCross" };
+ }
+ return { ...col, editor: "input" };
+ }
+ return col;
+ });
+ }
+ table.setColumns(columns);
+ outputData();
+ }).catch((error) => {
+ console.error(\`Error setting data for Tabulator \${spec.variableId}:\`, error);
+ });
+ };
+ if (spec.editable) {
+ const addRowBtn = container.querySelector(".tabulator-add-row");
+ const resetBtn = container.querySelector(".tabulator-reset");
+ if (addRowBtn) {
+ addRowBtn.onclick = () => {
+ table.addRow({}).then((row) => {
+ row.scrollTo();
+ });
+ };
+ }
+ if (resetBtn) {
+ resetBtn.onclick = () => {
+ const value = renderer.signalBus.signalDeps[spec.dataSourceName].value;
+ if (Array.isArray(value)) {
+ setData(value);
+ }
+ };
+ }
+ }
return {
...tabulatorInstance,
initialSignals,
- recieveBatch: async (batch) => {
- var _a2;
- const newData = (_a2 = batch[tabulatorInstance.spec.dataSourceName]) == null ? void 0 : _a2.value;
+ recieveBatch: async (batch, from) => {
+ var _a;
+ const newData = (_a = batch[spec.dataSourceName]) == null ? void 0 : _a.value;
if (newData) {
if (!tabulatorInstance.built) {
- tabulatorInstance.table.off("tableBuilt");
- tabulatorInstance.table.on("tableBuilt", () => {
+ table.off("tableBuilt");
+ table.on("tableBuilt", () => {
tabulatorInstance.built = true;
- tabulatorInstance.table.off("tableBuilt");
- tabulatorInstance.table.setData(newData);
+ table.off("tableBuilt");
+ setData(newData);
});
} else {
- tabulatorInstance.table.setData(newData);
+ setData(newData);
}
}
},
beginListening(sharedSignals) {
- var _a2;
- if ((_a2 = tabulatorInstance.spec.tabulatorOptions) == null ? void 0 : _a2.selectableRows) {
- for (const { isData, signalName } of sharedSignals) {
- if (isData) {
- const matchData = signalName === tabulatorInstance.spec.variableId;
- if (matchData) {
- tabulatorInstance.table.on("rowSelectionChanged", (e, rows) => {
- const selectedData = tabulatorInstance.table.getSelectedData();
- const batch = {
- [tabulatorInstance.spec.variableId]: {
- value: selectedData,
- isData: true
- }
- };
- renderer.signalBus.log(tabulatorInstance.id, "sending batch", batch);
- renderer.signalBus.broadcast(tabulatorInstance.id, batch);
- });
- }
- }
+ if (selectableRows) {
+ const hasMatchingSignal = sharedSignals.some(
+ ({ isData, signalName }) => isData && signalName === spec.variableId
+ );
+ if (hasMatchingSignal) {
+ table.on("rowSelectionChanged", (e, rows) => {
+ outputData();
+ });
}
}
+ if (spec.editable) {
+ table.on("cellEdited", (cell) => {
+ outputData();
+ });
+ }
},
getCurrentSignalValue() {
- return tabulatorInstance.table.getSelectedData();
+ if (selectableRows) {
+ return tabulatorInstance.table.getSelectedData();
+ } else {
+ return tabulatorInstance.table.getData();
+ }
},
destroy: () => {
tabulatorInstance.table.destroy();
diff --git a/docs/schema/idoc_v1.d.ts b/docs/schema/idoc_v1.d.ts
index e2ef81a3..4a3214a3 100644
--- a/docs/schema/idoc_v1.d.ts
+++ b/docs/schema/idoc_v1.d.ts
@@ -215,6 +215,7 @@ interface TableElement extends TableElementProps {
interface TableElementProps extends VariableControl {
/** Name of the data source to use for incoming data (output data is available via the variableId of this table element) */
dataSourceName: string;
+ editable?: boolean;
/** Tabulator options (must be JSON stringify-able, so no callbacks allowed) */
tabulatorOptions?: object;
}
diff --git a/docs/schema/idoc_v1.json b/docs/schema/idoc_v1.json
index b012e28c..9df07570 100644
--- a/docs/schema/idoc_v1.json
+++ b/docs/schema/idoc_v1.json
@@ -2824,6 +2824,9 @@
"description": "Name of the data source to use for incoming data (output data is available via the variableId of this table element)",
"type": "string"
},
+ "editable": {
+ "type": "boolean"
+ },
"label": {
"description": "optional label if the variableId is not descriptive enough",
"type": "string"
diff --git a/package.json b/package.json
index 3bcd52dc..220e5c93 100644
--- a/package.json
+++ b/package.json
@@ -5,6 +5,7 @@
"scripts": {
"clean": "npm run clean --workspaces --if-present",
"build": "npm run build:00 --workspaces --if-present && npm run build:01 --workspaces --if-present && npm run build:02 --workspaces --if-present && npm run build:03 --workspaces --if-present && npm run build:04 --workspaces --if-present && npm run build:05 --workspaces --if-present && npm run build:06 --workspaces --if-present && npm run build:07 --workspaces --if-present && npm run build:08 --workspaces --if-present",
+ "build-host": "npm run build -w @microsoft/interactive-document-markdown && npm run build -w @microsoft/chartifact-sandbox && npm run build -w @microsoft/interactive-document-host",
"start": "concurrently \"npm run dev -w common\" \"npm run dev -w @microsoft/interactive-document-markdown\" \"npm run dev -w schema\" \"npm run dev -w @microsoft/interactive-document-compiler\" \"npm run dev -w editor\"",
"package": "npm run package --workspaces --if-present"
},
diff --git a/packages/compiler/src/md.ts b/packages/compiler/src/md.ts
index 1e798396..9f229d73 100644
--- a/packages/compiler/src/md.ts
+++ b/packages/compiler/src/md.ts
@@ -174,8 +174,8 @@ function groupMarkdown(group: ElementGroup, variables: Variable[], vegaScope: Ve
break;
}
case 'table': {
- const { dataSourceName, variableId, tabulatorOptions } = element;
- const tableSpec: Plugins.TabulatorSpec = { dataSourceName, variableId, tabulatorOptions };
+ const { dataSourceName, variableId, tabulatorOptions, editable } = element;
+ const tableSpec: Plugins.TabulatorSpec = { dataSourceName, variableId, tabulatorOptions, editable };
addSpec('tabulator', tableSpec);
break;
}
diff --git a/packages/markdown/src/plugins/tabulator.ts b/packages/markdown/src/plugins/tabulator.ts
index 9c633974..d6666569 100644
--- a/packages/markdown/src/plugins/tabulator.ts
+++ b/packages/markdown/src/plugins/tabulator.ts
@@ -5,7 +5,7 @@
import { Batch, IInstance, Plugin, RawFlaggableSpec } from '../factory.js';
import { Tabulator as TabulatorType, Options as TabulatorOptions } from 'tabulator-tables';
-import { pluginClassName } from './util.js';
+import { newId, pluginClassName } from './util.js';
import { TableElementProps } from 'schema';
import { flaggableJsonPlugin } from './config.js';
import { PluginNames } from './interfaces.js';
@@ -13,8 +13,10 @@ import { PluginNames } from './interfaces.js';
interface TabulatorInstance {
id: string;
spec: TabulatorSpec;
+ container: Element;
table: TabulatorType;
built: boolean;
+ selectableRows: boolean;
}
export interface TabulatorSpec extends TableElementProps {
@@ -38,20 +40,39 @@ export const tabulatorPlugin: Plugin = {
...flaggableJsonPlugin(pluginName, className, inspectTabulatorSpec, { style: 'box-sizing: border-box;' }),
hydrateComponent: async (renderer, errorHandler, specs) => {
const tabulatorInstances: TabulatorInstance[] = [];
+
+ // Generate a unique field name for the delete column, used for all tables in this hydration
+ const deleteFieldname = newId();
+
for (let index = 0; index < specs.length; index++) {
const specReview = specs[index];
if (!specReview.approvedSpec) {
continue;
}
const container = renderer.element.querySelector(`#${specReview.containerId}`);
+ if (!container) {
+ continue;
+ }
+
+ const spec: TabulatorSpec = specReview.approvedSpec;
+ const buttons = spec.editable
+ ? `
+
+
+
`
+ : '';
+
+ container.innerHTML = ``;
+ const nestedDiv = container.querySelector('.tabulator-nested');
if (!Tabulator && index === 0) {
errorHandler(new Error('Tabulator not found'), pluginName, index, 'init', container);
continue;
}
- const spec: TabulatorSpec = specReview.approvedSpec;
-
if (!spec.dataSourceName || !spec.variableId) {
errorHandler(new Error('Tabulator requires dataSourceName and variableId'), pluginName, index, 'init', container);
continue;
@@ -71,8 +92,21 @@ export const tabulatorPlugin: Plugin = {
options = spec.tabulatorOptions;
}
- const table = new Tabulator(container as HTMLElement, options);
- const tabulatorInstance: TabulatorInstance = { id: `${pluginName}-${index}`, spec, table, built: false };
+ const selectableRows = !!options?.selectableRows || false;
+ if (spec.editable && selectableRows) {
+ delete options.selectableRows; //remove selectableRows from options if editable
+ }
+
+ const table = new Tabulator(nestedDiv as HTMLElement, options);
+
+ const tabulatorInstance: TabulatorInstance = {
+ id: `${pluginName}-${index}`,
+ spec,
+ container,
+ table,
+ built: false,
+ selectableRows,
+ };
table.on('tableBuilt', () => {
table.off('tableBuilt');
tabulatorInstance.built = true;
@@ -80,63 +114,169 @@ export const tabulatorPlugin: Plugin = {
tabulatorInstances.push(tabulatorInstance);
}
const instances: IInstance[] = tabulatorInstances.map((tabulatorInstance, index) => {
+ const { container, spec, table, selectableRows } = tabulatorInstance;
const initialSignals = [{
- name: tabulatorInstance.spec.dataSourceName,
+ name: spec.dataSourceName,
value: null,
priority: -1,
isData: true,
}];
- if (tabulatorInstance.spec.tabulatorOptions?.selectableRows) {
+ if (selectableRows || spec.editable) {
initialSignals.push({
- name: tabulatorInstance.spec.variableId,
+ name: spec.variableId,
value: [],
priority: -1,
isData: true,
});
}
+ const outputData = () => {
+ let data: object[];
+ if (selectableRows) {
+ data = table.getSelectedData();
+ } else {
+ data = table.getData();
+ }
+
+ //remove the delete column if it exists
+ data.forEach(row => {
+ delete row[deleteFieldname];
+ });
+
+
+ // Use structuredClone to ensure deep copy
+ // vega may efficiently have symbols on data to cache a datum's values
+ // so this needs to appear to be new data
+ const value = structuredClone(data);
+
+ const batch: Batch = {
+ [spec.variableId]: {
+ value,
+ isData: true,
+ },
+ };
+ renderer.signalBus.log(tabulatorInstance.id, 'sending batch', batch);
+ renderer.signalBus.broadcast(tabulatorInstance.id, batch);
+ }
+ const setData = (data: object[]) => {
+ table.setData(data).then(() => {
+
+ // Get current column definitions
+ let columns = table.getColumnDefinitions()
+ .filter(cd => cd.field !== deleteFieldname)
+ .filter(cd => cd.formatter !== 'rowSelection');
+
+ if (spec.editable) {
+
+ //inject a column at the beginning for delete
+ columns.unshift({
+ headerSort: false,
+ title: "Delete",
+ field: deleteFieldname,
+ titleFormatter: 'tickCross',
+ width: 40,
+ formatter: "tickCross",
+ cellClick: (e, cell) => {
+ cell.getRow().delete();
+ outputData();
+ }
+ });
+
+ columns = columns.map(col => {
+ // Only set editor if not already defined
+ if (col.editor === undefined) {
+ // Prefer explicit type, fallback to sorter
+ const type = (col as any).type || col.sorter;
+ if (type === "number") {
+ return { ...col, editor: "number" };
+ }
+ if (type === "date" || type === "datetime") {
+ return { ...col, editor: "date" };
+ }
+ if (type === "boolean") {
+ return { ...col, editor: "tickCross" };
+ }
+ // Add more types as needed
+ return { ...col, editor: "input" };
+ }
+ return col;
+ });
+ }
+ table.setColumns(columns);
+
+
+ outputData();
+
+ }).catch((error) => {
+ console.error(`Error setting data for Tabulator ${spec.variableId}:`, error);
+ // errorHandler(error, pluginName, index, 'setData', container);
+ });
+ };
+
+ if (spec.editable) {
+ const addRowBtn = container.querySelector('.tabulator-add-row') as HTMLButtonElement;
+ const resetBtn = container.querySelector('.tabulator-reset') as HTMLButtonElement;
+
+ if (addRowBtn) {
+ addRowBtn.onclick = () => {
+ table.addRow({}).then((row) => {
+ row.scrollTo();
+ });
+ };
+ }
+ if (resetBtn) {
+ resetBtn.onclick = () => {
+ const value = renderer.signalBus.signalDeps[spec.dataSourceName].value;
+ if (Array.isArray(value)) {
+ setData(value);
+ }
+ };
+ }
+ }
+
return {
...tabulatorInstance,
initialSignals,
- recieveBatch: async (batch) => {
- const newData = batch[tabulatorInstance.spec.dataSourceName]?.value as object[];
+ recieveBatch: async (batch, from) => {
+ const newData = batch[spec.dataSourceName]?.value as object[];
if (newData) {
//make sure tabulator is ready before setting data
if (!tabulatorInstance.built) {
- tabulatorInstance.table.off('tableBuilt');
- tabulatorInstance.table.on('tableBuilt', () => {
+ table.off('tableBuilt');
+ table.on('tableBuilt', () => {
tabulatorInstance.built = true;
- tabulatorInstance.table.off('tableBuilt');
- tabulatorInstance.table.setData(newData);
+ table.off('tableBuilt');
+ setData(newData);
});
} else {
- tabulatorInstance.table.setData(newData);
+ setData(newData);
}
}
},
beginListening(sharedSignals) {
- if (tabulatorInstance.spec.tabulatorOptions?.selectableRows) {
- for (const { isData, signalName } of sharedSignals) {
- if (isData) {
- const matchData = signalName === tabulatorInstance.spec.variableId;
- if (matchData) {
- tabulatorInstance.table.on('rowSelectionChanged', (e, rows) => {
- const selectedData = tabulatorInstance.table.getSelectedData();
- const batch: Batch = {
- [tabulatorInstance.spec.variableId]: {
- value: selectedData,
- isData: true,
- },
- };
- renderer.signalBus.log(tabulatorInstance.id, 'sending batch', batch);
- renderer.signalBus.broadcast(tabulatorInstance.id, batch);
- });
- }
- }
+ if (selectableRows) {
+ const hasMatchingSignal = sharedSignals.some(({ isData, signalName }) =>
+ isData && signalName === spec.variableId
+ );
+
+ if (hasMatchingSignal) {
+ table.on('rowSelectionChanged', (e, rows) => {
+ outputData();
+ });
}
}
+ if (spec.editable) {
+ table.on('cellEdited', (cell) => {
+ outputData();
+ });
+ }
},
getCurrentSignalValue() {
- return tabulatorInstance.table.getSelectedData();
+ if (selectableRows) {
+ return tabulatorInstance.table.getSelectedData();
+ } else {
+ // When editable, return all data since that's what gets broadcast
+ return tabulatorInstance.table.getData();
+ }
},
destroy: () => {
tabulatorInstance.table.destroy();
diff --git a/packages/markdown/src/plugins/util.ts b/packages/markdown/src/plugins/util.ts
index 58fff829..e063e9a6 100644
--- a/packages/markdown/src/plugins/util.ts
+++ b/packages/markdown/src/plugins/util.ts
@@ -28,3 +28,5 @@ export function getJsonScriptTag(container: Element, errorHandler: (error: Error
export function pluginClassName(pluginName: string) {
return `chartifact-plugin-${pluginName}`;
}
+
+export const newId = () => ([...Date.now().toString(36) + Math.random().toString(36).slice(2)]).sort(() => 0.5 - Math.random()).join('');
diff --git a/packages/schema/src/interactive.ts b/packages/schema/src/interactive.ts
index 85840666..b631e66f 100644
--- a/packages/schema/src/interactive.ts
+++ b/packages/schema/src/interactive.ts
@@ -135,6 +135,8 @@ export interface TableElementProps extends VariableControl {
/** Name of the data source to use for incoming data (output data is available via the variableId of this table element) */
dataSourceName: string;
+
+ editable?: boolean;
/** Tabulator options (must be JSON stringify-able, so no callbacks allowed) */
tabulatorOptions?: object;