[8.3.2] Copy/Paste plugin .paste() method throwing exception

Tags: #<Tag:0x00007f8b2b01be50>

The .paste() method of the copy/paste plugin is throwing an exception. The stack trace and exception message are below. The .copy() and .cut() methods call this.focusableElement.focus() after creating the focusable element. Should this be happening in the .paste() method as well?

TypeError: Cannot read property ‘getTopLeftCorner’ of undefined
at CopyPaste.populateValues (http://localhost/TDDev/TDNext/Scripts/web-plan-manager/vendor.js?v=20210503143700:179561:45)
at CopyPaste.onPaste (http://localhost/TDDev/TDNext/Scripts/web-plan-manager/vendor.js?v=20210503143700:179742:39)
at CopyPaste.paste (http://localhost/TDDev/TDNext/Scripts/web-plan-manager/vendor.js?v=20210503143700:179472:12)

Hi @joeysamons,
Could you add a demo/share your configuration or anything that might help us reproduce the issue?

Essentially I have two buttons, copy and paste. When the copy button is clicked we call copyIntoHot() and when the paste button is clicked we call pasteIntoHot(). I also have methods bound to the afterCopy and afterPaste events. Those four methods are below.

Of note, keyboard copy/paste works as expected.

afterCopy(changes: any[], coords: any[]) {
  this.clipboardCache = this.sheetclip.stringify(changes);
}

afterPaste(changes: any[], coords: any[]) {
  if (!isNullOrUndefined(changes)) {
    this.clipboardCache = this.sheetclip.stringify(changes);
  } else {
    this.clipboardCache = '';
  }
}

pasteIntoHot() {
  if (this.clipboardCache.length !== 0) {
    const plugin = this.hot.getPlugin('copyPaste');
    this.hot.listen();
    plugin.paste(this.clipboardCache);
  }
}

copyIntoHot() {
  const plugin = this.hot.getPlugin('copyPaste');
  plugin.setCopyableText();
  plugin.copy();
}

The problem lies in the fact that the “paste” action requires having at least one selected cell.

Internally we call CopyPaste.populateValues where the second argument is, by default, the last selected range. Calling only listen() does not set any selection. Try to modify your paste function into something like:

pasteIntoHot() {
  if (this.clipboardCache.length !== 0) {
    const plugin = this.hot.getPlugin('copyPaste');
    this.hot.selectCell(0, 0);
    plugin.paste(this.clipboardCache);
    this.hot.deselectCell(); // to revert selection state
  }
}

Can you explain why the copy functionality works without manually selecting a cell? It also looks at the last selected range, but it seems to be able to identify the last selected range based solely on the last cells the user selected.

The series of events is user selects cell, they click the copy button, the value in the selected cell is copied to the internal clipboard. I have confirmed this works and I have no additional logic that manually sets the selected cell in the HOT instance. Then the user selects a different cell and clicks the paste button. At this point the error is thrown. So I’m confused about why copying works without manually setting the selected cell but paste does not considering both paths run through this.hot.getSelectedRangeLast().

It works that way because copy prepares “copyableRanges” in a selection-related hook in the CopyPaste plugin.

Following the stack, we have to look at:
In this line: https://github.com/handsontable/handsontable/blob/master/src/plugins/copyPaste/copyPaste.js#L462

priv.isTriggeredByCopy is true because we set this flag in another line, here: https://github.com/handsontable/handsontable/blob/master/src/plugins/copyPaste/copyPaste.js#L202

Pasting, on the other hand, does not base on copyableRanges but cell selection.

In this line: https://github.com/handsontable/handsontable/blob/master/src/plugins/copyPaste/copyPaste.js#L577 we call populateValues with its default selection: https://github.com/handsontable/handsontable/blob/master/src/plugins/copyPaste/copyPaste.js#L393.

These differences result from conditions to keep backward compatibility of the plugin and assumptions we had in the refactoring/rewriting project in 2017/2018.

The bit about backwards compatibility is strange because this code worked with HOT v7.1 but not after I upgraded to HOT v8.3.2. And pasting via keyboard commands still works so it does seem like the plugin has the ability to identify the currently highlighted cells.

Regardless, the paste destination is based on the cell the user has selected. The paste button is not tied to any specific cell, so your suggestion doesn’t quite work. So is it the call to this.hot.listen() that causes the HOT instance to lose track of the currently selected cells? Because you are saying I need to manually set the selected cells in the HOT instance when pasting. But I will need to call this.hot.getSelected() to find the currently selected cells. And if this.hot.getSelectedRangeLast() is returning undefined then it seems likely that this.hot.getSelected() will also return undefined.

Oh I see. I have the HOT instance configured to deselect cells on outside clicks. So the act of clicking the paste button is causing the cell to be deselected. I think this can be considered answered. Thank you for your assistance.