tabulator
tabulator copied to clipboard
Tracking range clipboardPasteActions in history with new spreadsheet mode
Is your feature request related to a problem? Please describe. The new cell range selection mode combined with the clipboard enhancements makes for an amazing spreadsheet mode, many thanks for getting that released. I am hoping that it is possible for the history module to subscribe to changes to user edits when in this new mode, so undo and redo actions are available to revert edits.
Describe the solution you'd like
- When user copies one cell range, and pastes it to another, the history module should capture these cell changes in the history.
- Undo should revert all edited cells in the cell range to previous values, while redo should do the opposite.
Describe alternatives you've considered I don't see a way to capture this action and inject it into the history stack that Tabulator maintains, so the only alternative I see would be to manage history on our own, or develop a new module that tracks clipboard actions history.
Additional context
- JSFiddle at https://jsfiddle.net/nathan_mih/yetpfmk8/ - copy and paste of cell ranges is shown to not be in the history.
- The root issue seems to lie at the internal event being dispatched. Clipboard range pasting ends up dispatching
cell-value-changed
(https://github.com/olifolkerd/tabulator/blob/master/src/js/core/cell/Cell.js#L187) whereas the History module subscribes tocell-value-updated
(https://github.com/olifolkerd/tabulator/blob/master/src/js/modules/History/History.js#L21). I don't know the difference between these two internal events though to fully understand 🙂
A follow up on this - I realized that this is not as simple as using the cell-value-updated event to track this, because what we are really looking for is to undo/redo the entire range of edits at once. Thus, this request is more about a new function to track history of edited ranges, very much like Excel/Google Sheets does.
Hello, I have the same requirements to fullfill. Thanks!
Any updates on this?
Nope, will get round to it at some point but it is a complex feature requirement that only applies to a small subset of user so is not the top of the to-do list.
Very happy to accept a PR of someone wants to add it sooner
Given the complexity of this request I would like to present a workaround, but note that it depends on a much smaller feature request which I just opened up a PR for (https://github.com/olifolkerd/tabulator/pull/4485). It doesn't get us exactly to Excel/Sheets-like undo/redo functionality because it will only undo/redo edits one-by-one, instead of as a large chunk of changes. Screengrab:
With the aforementioned PR we would be able to manually mark a cell as edited (cell.setEdited()
) and override the default clipboardPasteAction
as documented in https://tabulator.info/docs/6.2/clipboard#paste-action. The only change that needs to be made to the default is the way cell data is updated. Right now it is using the row.updateData
function. My workaround is to grab the cell data for that row then trigger cell.setValue
function in the loop instead, and set the cell as edited like so:
rows.forEach((row, i) => {
const cellsNewData = data[i % dataLength];
Object.entries(cellsNewData).forEach(([field, value]) => {
const cell = row.getCell(field);
if (cell) {
cell.setValue(value);
cell.component.setEdited(); // Requires https://github.com/olifolkerd/tabulator/pull/4485
}
});
});
All together it looks like this as a tabulator config option:
clipboardPasteAction: function(data){
var rows = [],
range = this.table.modules.selectRange.activeRange,
singleCell = false,
bounds, startCell, startRow, rowWidth, dataLength;
dataLength = data.length;
if(range){
bounds = range.getBounds();
startCell = bounds.start;
if(bounds.start === bounds.end){
singleCell = true;
}
if(startCell){
rows = this.table.rowManager.activeRows.slice();
startRow = rows.indexOf(startCell.row);
if(singleCell){
rowWidth = data.length;
}else{
rowWidth = (rows.indexOf(bounds.end.row) - startRow) + 1;
}
if(startRow >-1){
this.table.blockRedraw();
rows = rows.slice(startRow, startRow + rowWidth);
rows.forEach((row, i) => {
const cellsNewData = data[i % dataLength];
Object.entries(cellsNewData).forEach(([field, value]) => {
const cell = row.getCell(field);
if (cell) {
cell.setValue(value);
cell.component.setEdited(); // Requires https://github.com/olifolkerd/tabulator/pull/4485
}
});
});
this.table.restoreRedraw();
}
}
}
return rows;
}
@nmih Thanks for that, it seems to work as you described, even with that cell.component.setEdited();
line removed so I'm super psyched about this 😁