findNodeForKey returning null for copied node

Hi,

I’m having the following issue:

  • I copy a diagram node (using localstorage as clipboard) and I paste it on the same diagram.
  • this magic bit of code runs (don’t think this is causing the issue but who knows…)
this.diagram.addDiagramListener('ClipboardPasted', (e) => {
      const selectedParts = e.subject.toArray();
      const clipboard = this.localStorageService.getDiagramClipboard();
      for (const part of selectedParts) {
        if (part instanceof go.Node) {
          const nodeData = part.data;
          const clipboardNode = clipboard.nodeDataArray.find(x => x.factor.resourceIdentifier === nodeData.factor.resourceIdentifier);
          if (clipboardNode) {
            this.diagram.model.setDataProperty(nodeData, '$originalNodeData', clipboardNode);
            // this.diagram.model.setDataProperty(nodeData, 'key', 'copy_of_' + nodeData.key);
            nodeData.key = 'copy_of_' + nodeData.key;
          }
        }
      }
    });
  • the transaction manager is then responsible for making an API call, which upon success, updates the node key using setKeyForNodeData.

All seems to work fine up to this point. The issue is that after this, findNodeForKey() or findPartForKey() always return null.
However, if I iterate over the diagram.nodes and filter on the node key, I get what I’m looking for.

// const node = this.diagram.findNodeForKey(newPdf.resourceIdentifier);
const node = this.diagram.nodes.filter(x => x.key === newPdf.resourceIdentifier)?.first();

Any idea why findNodeForKey is not working in this scenario?

Thanks,
Nuno

You cannot just set the “key” property of the data, just as you cannot just set any other property of any data. For specifically changing the “key” value, call Model.setKeyForNodeData.
Model | GoJS API

More generally, call Model.set.
Model | GoJS API

Hi Walter,

I started with that, but that leads to another “unexpected” behavior, which made me try different things including setting the node key in different ways.
if in the example above I replace the key setting with

this.diagram.model.setKeyForNodeData(nodeData, ‘copy_of_’ + nodeData.key);

then the following happens:

while handling the transaction (to perform API calls on undo / redos and such) inside “this.diagram.model.addChangedListener()”:

(e.object as go.Transaction)?.changes?.each((change: go.ChangedEvent) => {
      if (change.propertyName === 'nodeDataArray') {
        const newNodeData = change.newValue as go.ObjectData;
        ....

I was expecting that newNodeData to be the same regardless of the transaction being a normal transaction or an undo transaction but in the “normal transaction” I get the modified nodeData (with the copy_of_xxx key and the $originalNodeData property, but on undo, I get the original nodeData (without any of those properties). change.oldValue is also at this point.

so how can I get the modified nodeData while undoing a copy paste transaction?

If you call Model.setKeyForNodeData and pass it some string, there is no guarantee that that node [data] will actually get that string as its key value. If that key is already in use by the model, it will use a different key instead that is unique.

Yes, the node data objects that are held in the Model.nodeDataArray remain the same references, unless you explicitly remove them and maybe add other data. So all of the ChangedEvents will always refer to the same data objects.

And undo or a redo will “replay” any property changes backwards or forwards, thereby modifying those data objects.

In your code that checks the ChangedEvent.propertyName, you also need to check whether the ChangedEvent.change is an Insert or a Remove or just a Property change.

ok, so, analyzing your response:

If you call Model.setKeyForNodeData and pass it some string, there is no guarantee that that node [data] will actually get that string as its key value. If that key is already in use by the model, it will use a different key instead that is unique.

I understand that. in this case it is unique, I’m only pasting it once.

Yes, the node data objects that are held in the Model.nodeDataArray remain the same references, unless you explicitly remove them and maybe add other data. So all of the ChangedEvent s will always refer to the same data objects.

so what does that mean? should I be replacing the corresponding nodeData in Model.nodeDataArray in my ClipboardPasted event, with my “modified nodeData”?

In your code that checks the ChangedEvent.propertyName , you also need to check whether the ChangedEvent.change is an Insert or a Remove or just a Property change.

I’ve just checked this and ChangedEvent.change = “Insert” on undo() after a Paste…does that make sense?

What are you trying to do? You hadn’t mentioned pasting from the clipboard before, although that is only one of many ways in which a node could be added. If you want to change some properties, just call Model.set on that data object.

The GoJS design depends on reference identity; that is why it is possible to modify the key of a node or a link. Maybe reading this will help explain: GoJS Using Models -- Northwoods Software

I did write in the original post:

  • I copy a diagram node (using localstorage as clipboard) and I paste it on the same diagram.

Anyway, I’ve just found an alternative way to do what I need using model.copyNodeDataFunction instead of hooking onto the ClipboardPaste event…I had tried it already, but for some reason I couldn’t get it to work before…it’s working now so, thanks!

piggy-backing on this thread: Now I’m facing a follow up issue:

  • In some situations when I paste a node, I need to change its category (after the API call)
  • to do that I’m using setCategoryForNodeData() which apparently “works” but the shape in the diagram doesn’t get updated to the new category template…am I missing something?

That should work. Maybe you have the wrong node data object, or you’re not making the change within a transaction.

Alternatively you can set the Part.category of that Node.

Thanks again Walter. It’s fixed now. it was an unrelated issue but I was “feeling burned” with the findNodeForKey not returning the correct thing so my first reaction was that it could be related.