tabulator icon indicating copy to clipboard operation
tabulator copied to clipboard

Tracking range clipboardPasteActions in history with new spreadsheet mode

Open nmih opened this issue 1 year ago • 6 comments

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 to cell-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 🙂

nmih avatar Feb 14 '24 05:02 nmih

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.

tabulator feature req clipboard history

nmih avatar Mar 22 '24 20:03 nmih

Hello, I have the same requirements to fullfill. Thanks!

diego-palumbo avatar Mar 27 '24 17:03 diego-palumbo

Any updates on this?

LeeGrobler avatar Apr 25 '24 13:04 LeeGrobler

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

olifolkerd avatar Apr 25 '24 21:04 olifolkerd

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:

demo clipboard history

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 avatar Apr 27 '24 17:04 nmih

@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 😁

LeeGrobler avatar May 01 '24 11:05 LeeGrobler