Bindings in templates, and when they update

In our project we have nodes which we want to display different text depending on the situation, either the their name or the port names. This has been working just fine as up to now we have been displaying port names when the node is selected and the node name otherwise using the node isSelected property in the binding. However, it has now been decided that when we select a link we don’t want to select the nodes at each end of the link, but we do still want the port names to be shown (previously we had also just programmatically selected the nodes when we selected the link).

To achieve this I tried extending the Node class:

export class BlockNode extends go.Node {
displayPortNames = false;
}

and then switched the binding references to the new property. For example:

                    new go.Binding('visible', 'isSelected', function (isSelected) { return !isSelected; }).ofObject('/')

to:

                    new go.Binding('visible', 'displayPortNames', function (displayPortNames) { return !displayPortNames; }).ofObject('/')

The problem now is that the visual changes triggered by the bindings are no longer reliable. For example, when I click to select a link we have the click property of the link template set to:

            click: function (e, obj) {
                const fNode = obj.fromNode as BlockNode;
                const tNode = obj.toNode as BlockNode;
                fNode.displayPortNames = true;
                tNode.displayPortNames = true;
            },

previously:

            click: function (e, obj) {
                obj.fromNode.isSelected = true;
                obj.toNode.isSelected = true;
            },

Anothee example is the selectionChanged property, which does something equivalent:

    const fromNode = link.fromNode as BlockNode;
    const toNode = link.toNode as BlockNode;
    fromNode.displayPortNames = link.isSelected;
    toNode.displayPortNames = link.isSelected;

However, in both these cases, whilst the displayPortNames value is successfully updated, the display changes based on the bindings aren’t happening, which they did when using isSelected.

The bindings do work under other circumstances though, such as when a new link is created or a link is deleted then the bindings do seem to be triggered and all the nodes in the canvas update to the state that they should be.

As I understood the docs, the binding should trigger whenever the property value is changed, but that doesn’t seem to be happening. I’m thinking that changing isSelected is triggering an event which is then triggering the binding, but I’m not clear which one. I’m guessing I could then do something similar using a getter/setter pair for the property.

Can you clarify the expected behaviour for me and whether I’m thinking about this right?

Thanks.

Yes, that’s right – binding is only supported on the GraphObject and RowColumnDefinition classes and the predefined subclasses of GraphObject because all of those settable properties have setters that do something like:

function(newval) {
  var old = this._someProperty;
  if (old !== newval) {  // maybe use a different comparison, depending on type
    // if (Debug) ... check newval for type and valid range ...
    this._someProperty = newval;
    this.raiseChanged("someProperty", old, newval);
  }
}

The GraphObject.raiseChanged method is undocumented, but you can call it.

Thanks. That worked perfectly.

Having said that, now that I’ve moved beyond purely manual use and have started putting it through our build system, it’s failing because it thinks raiseChanged doesn’t exist on the class. Can you suggest a way around this? We’re using GoJS within angular/typescript.

I did say it wasn’t documented. Although some undocumented things are declared in the go.d.ts file, that method isn’t.

True. I’m a little surprised given that its absence makes it hard to extend GraphObject classes for use with Bindings, which are a very useful concept.

I have got to build to work by adding in a definition into go.d.ts, but that doesn’t feel right to me given that from our pov it means altering 3rd party code.

Yes, that’s intentional – a lot of potentially useful things are undocumented so we don’t have programmers unfamiliar with GoJS trying all kinds of crazy things. It makes support much harder. And it’s already hard enough trying to guess what they are doing and what they want to do instead.

In TypeScript you can always get around problems with types, as any or calling by indexed string.

The indexed string got round the issue. Should have thought of that: I’ve used it a few times in the unit tests.