Data Attribute in One Node Changes ContextMenu of Another Node

The following snippet defines a header named “Note” on a contextMenu of a node template:

            $(
              go.TextBlock,
              'Note',
              {
                font: this.defaultFont,
                stroke: this.colors.textColor,
                desiredSize: new go.Size(102, 25),
                alignmentFocus: go.Spot.Left,
                margin: new go.Margin(10, 0, 0, 15)
              },
              new go.Binding('stroke', 'disableNotes', c =>
                c ? 'lightgray' : this.colors.textColor
              )
            ),

when I dragged this node onto the canvas:

    {
      id: 21,
      text: 'Log Message',
...
      notes: ''
    },

Since disableNotes is not defined, the disableNotes Binding is not evaluated, and “Note” is of stroke this.colors.textColor (black) - correct behaviour.

Problem occurs when I dragged the following node onto the canvas:

    {
      id: 12,
      text: 'Execute Powershell',
      ...
      disableNotes: true
    }

When I clicked the contextMenu of ‘Log Message’ node, the “Note” heading becomes lightgray!

It appears that ‘Execute Powershell’ and ‘Log Message’ are sharing the same disableNotes attribute. This makes no sense, as they each have their own data object as I have shown above.

Setting “disableNotes” of ‘Execute Powershell’ would inadvertently cause the Binding of ‘Log Message’ to reevaluate. How do I ensure “Note” binding maps to the respective data objects of ‘Log Message’ and ‘Execute Powershell’?

“Bindings will be shared by all copies of the template’s GraphObjects.”
https://gojs.net/latest/api/symbols/Binding.html

The context menu is being shared by all of the nodes that use that template (and maybe by other templates too – that depends on how you defined the contextMenu).

So as long as the node data doesn’t have a data.disableNotes property (or if the value is undefined) the TextBlock.stroke will remain at its initial value of “black”.

Then the context menu is shown for a node data object that does have a property value for data.disableNotes, and the Binding is evaluated and its conversion function returns either “lightgray” or “black”. (Assuming this.colors.textColor hasn’t changed.) In the case for id===12, that would be “lightgray”.

But then if you show the context menu for a node such as id===21, that Binding is not evaluated because the data.disableNotes property is not present or is undefined. So the TextBlock.stroke remains whatever it had been – “lightgray” in your case.

The problem is that with no value for that data property, bindings that use that property as a source do not evaluate. Furthermore, whatever had been the initial value for TextBlock.stroke has been lost, having been overwritten any number of times.

The obvious solution is to make sure that the “disableNotes” property is defined on each node data object.

If that is not feasible, you could customize the showing or the hiding of the context menu to reset the TextBlock.stroke to “black”. For example you could override ContextMenuTool.hideContextMenu to see if the ContextMenuTool.currentContextMenu is one that you want to reset. If so, reset it. Then call the super method for the standard behavior.

Thanks Walter for replying on a weekend.

Is there a way to for a target property to bind to two source properties? I have the following bindings for the “Note” contextMenu header:

            $(
              go.TextBlock,
              'Note',
              {
                font: this.defaultFont,
                stroke: this.colors.textColor,
...
              },
              new go.Binding('stroke', 'disableNotes', c =>
                c ? 'lightgray' : this.colors.textColor
              )
            ),

Then for the “Add” button, I have the following bindings:

              $(
                go.TextBlock,
                'Add',
                {
...
                  stroke: 'lightgray'
                },
                new go.Binding('stroke', 'notes', c =>
                  c ? 'lightgray' : this.colors.textColor
                ),
                new go.Binding('stroke', 'disableNotes', c =>
                  c ? 'lightgray' : this.colors.textColor
                )
              )


As you can see from the animated .gif, after I add the “testing” note, the “Add” button is still enabled (i.e. of this.colors.textColor - black). The correct behaviour is to disable the “Add” button once there is a note.

I now have both “notes” and “disableNotes” defined on each node data object:

    {
      id: 12,
      text: 'Execute Powershell',
...
      notes: '',
      disableNotes: true
    },
    {
      id: 21,
      text: 'Log Message',
...
      notes: '',
      disableNotes: false
    },

But the “Add” button should be enabled only when:

  • disableNotes: false and there is an empty value in “notes” - ‘’ evaluates to falsy

I have obviously tried numerous combinations of input data and ways to arrange the “notes” and “disableNotes” Bindings. I would either break the code by incorrectly enabling the “Add” in id 12 with disableNotes: true ; or break the code like I did in the animated .gif as I explained above.

I came to the conclusion that “stroke” should be bind to both “notes” and “disableNotes”, in one conversion function, one Binding. Can you please provide some examples of how this is done in GoJS? If you can use my input and properties even better. Also I am forced to use arrow functions so for my conversion function I cannot use function © { … } .

Thanks again for your help.

Use the empty string as the source property name.
https://gojs.net/latest/api/symbols/Binding.html
https://gojs.net/latest/api/symbols/Binding.html#sourceProperty

I am now binding to the whole data object:

              $(
                go.TextBlock,
                'Add',
                {
...
                  stroke: this.colors.textColor
                },

                // tslint:disable-next-line: only-arrow-functions
                new go.Binding('stroke', '', function(c) {
                  if (!c.notes && !c.disableNotes) {
                    return this.colors.textColor;
                  } else {
                    return 'lightgray';
                  }
                })
              )

Again, the data object inputs are:

    {
      id: 12,
      text: 'Execute Powershell',
...
      notes: '',
      disableNotes: true
    }
    {
      id: 21,
      text: 'Log Message',
...
      notes: '',
      disableNotes: false
    }

Once I expanded the contextMenu of id 12, the “Add” Bindings evaluates to lightgray. When I re-opened the contextMenu of id 21, since notes is ‘’ (falsy) so !notes is true; and disableNotes remains false so !disableNotes is true, so my expectation was “Add” of id 21 should be this.colors.textColor ; but as you can see “Add” Binding evaluates to ‘lightgray’.

Why is this the case?
And what would you advise to be a fix?

Thanks.

Did you implement that correctly? The use of this inside that function is very likely quite wrong.