Using typescript for node template

Hi,

I’m new to go.js and I’m trying to use it with typescript. I have a problem when I’m trying to create a class extending go.Node and use it as a node template. I’m doing this:

class MyNodeTemplate extends go.Node {
    constructor() {
        super(go.Panel.Auto);

        this.selectionAdorned = false;
        this.resizable = true;
        this.resizeObjectName = "SHAPE";
    
        this.add(go.GraphObject.make(go.Shape,
            {
                name: "SHAPE",
                width: 60,
                height: 60
            },
            new go.Binding("figure"),
            new go.Binding("fill", "fill", null)
        ));

        this.add(go.GraphObject.make(go.TextBlock,
            {
                font: "16pt serif"
            },
            new go.Binding("text", "fill")
        ));
    }
}
var nodeTemplate = new MyNodeTemplate();

class MyDiagram extends go.Diagram {
    constructor(id?: string) {
        super(id);

        this.undoManager.isEnabled = true;
        this.nodeTemplate = nodeTemplate;
    }
}

window.onload = () => {
var myDiagram = new MyDiagram("myDiagramDiv");

    myDiagram.model.nodeDataArray = [{ figure: "Triangle", fill: "Blue"}];
};

Unfortunately the result I’m getting is not as expected (please note a black background):

Everything is working when I’m doing inheritance by hand in java script like this (background is white as expected):

var MyNodeTemplate2 = function () {
    go.Node.call(this, go.Panel.Auto);
    this.selectionAdorned = false;
    this.resizable = true;
    this.resizeObjectName = "SHAPE";
    this.add(go.GraphObject.make(go.Shape, 
        {
            name: "SHAPE",
            width: 60,
            height: 60
        }, 
        new go.Binding("figure"), 
        new go.Binding("fill", "fill", null)
    ));
    this.add(go.GraphObject.make(go.TextBlock, 
        {
            font: "16pt serif"
        }, 
        new go.Binding("text", "fill")
    ));
}
MyNodeTemplate2.prototype = Object.create(go.Node.prototype);
var nodeTemplate = new MyNodeTemplate2();

For the record, this is a js code generated by typescript:

var __extends = (this && this.__extends) || function (d, b) {
    for (var p in b) if (b.hasOwnProperty(p)) d[p] = b[p];
    function __() { this.constructor = d; }
    d.prototype = b === null ? Object.create(b) : (__.prototype = b.prototype, new __());
};
var MyNodeTemplate = (function (_super) {
    __extends(MyNodeTemplate, _super);
    function MyNodeTemplate() {
        _super.call(this, go.Panel.Auto);
        this.selectionAdorned = false;
        this.resizable = true;
        this.resizeObjectName = "SHAPE";
        this.add(go.GraphObject.make(go.Shape, {
            name: "SHAPE",
            width: 60,
            height: 60
        }, new go.Binding("figure"), new go.Binding("fill", "fill", null)));
        this.add(go.GraphObject.make(go.TextBlock, {
            font: "16pt serif"
        }, new go.Binding("text", "fill")));
    }
    return MyNodeTemplate;
}(go.Node));
var nodeTemplate = new MyNodeTemplate();

I can’t figure out why it is not working as expected. Is there anything I’m missing?

Thanks,
Maciek

That’s very odd – it’s as if the super call passing the go.Panel.Auto were being ignored. Even though the translated JavaScript call looks identical.

We’ll investigate.

The problem is that you have put the building of the node structure (i.e. the Shape and the TextBlock) in the constructor, to be executed each time the Diagram creates a Node for a model node data object. That will cause a lot of duplicate objects to be created for each node.

If you just build the template once, and let it be copied by the Diagram whenever a node needs to be created for some model data, you won’t have this problem.

import * as go from "go";

class MyNodeTemplate extends go.Node {
  constructor() {
    super(go.Panel.Auto);

    this.selectionAdorned = false;
    this.resizable = true;
    this.resizeObjectName = "SHAPE";
  }
}
var nodeTemplate = new MyNodeTemplate();
nodeTemplate.add(go.GraphObject.make(go.Shape,
  {
    name: "SHAPE",
    width: 60,
    height: 60
  },
  new go.Binding("figure"),
  new go.Binding("fill")
));
nodeTemplate.add(go.GraphObject.make(go.TextBlock,
  {
    font: "16pt serif"
  },
  new go.Binding("text", "fill")
));

class MyDiagram extends go.Diagram {
  constructor(id?: string) {
    super(id);

    this.undoManager.isEnabled = true;
    this.nodeTemplate = nodeTemplate;
  }
}

window.onload = () => {
  var myDiagram = new MyDiagram("myDiagramDiv");

  myDiagram.model.nodeDataArray = [{ figure: "Triangle", fill: "Blue" }];
};

You can see that the only substantive change that I made is to move the calls to Panel.add out of the constructor and into making the template.

I think it’s better to build the template in a functional/structural fashion, i.e. with nested calls to make. There is no need for classes or local variables in most cases.

However I recognize that editors will not be smart about checking and auto-completion as you are typing when doing that, so I can understand why you might want to declare all of those local variables and assign properties to them in separate statements.

So if you want to do that, just write the code to build each template in a separate function, to be executed only time.

If I recall correctly, there is at least one sample that has a function that constructs a bunch of templates, each parameterized in different fashions, and adds them to the Diagram.nodeTemplateMap. But in this case where you’re just building one template, there’s no need for parameterization – so just build it once and save it as the node template.

Hi,

thanks for looking into this. You’re right - the change you’ve proposed fixes the rendering problem. What I still don’t understand is what is wrong with original code. As far as I can tell the working example I’ve provided (MyNodeTemplate2) is doing exactly the same - calls add inside a constructor. Or am I missing something?

I’m not saying what I’m doing it is a right thing to do;-) - at the moment I’m only playing with go.js and looking at options to find one that suits me most. Actual sample app I’m building is more involved than what I’ve put in here and I’m looking for ways to share templates between parts of it (like for example between Palette and actual Diagram). I could create a template, store it in a variable somewhere and use it in both places. Turning this template into class and creating its instance where needed (or maybe inject it) just looks clearer to me.

In your answer you’ve said:

The problem is that you have put the building of the node structure (i.e. the Shape and the TextBlock) in the constructor, to be executed each time the Diagram creates a Node for a model node data object. That will cause a lot of duplicate objects to be created for each node.

If you just build the template once, and let it be copied by the Diagram whenever a node needs to be created for some model data, you won’t have this problem.

I’m a little lost here. I thought that my constructor will be called only once and I’ll get a Node instance that I can safely assign as a template. If I understand you correctly this is not a case and my constructor will be called every time the Diagram creates a Node. What is different after your change that template is copied not duplicated?

The way templates work is that for each object in the Model.nodeDataArray a deep copy is made of a template.

So the Diagram.nodeTemplate starts off as:

MyNodeTemplate  // not Node!
    Shape
    TextBlock

Then when the copy is made it calls the MyNodeTemplate constructor to create:

MyNodeTemplate
    Shape  // created by the constructor
    TextBlock

and then deep copy the elements from the template:

MyNodeTemplate
    Shape  // created by the constructor
    TextBlock
    Shape  // added by the copier
    TextBlock

My point is that you really shouldn’t bother with any MyNodeTemplate class, unless you really need to override some methods. We’ve tried hard to reduce the need for overriding any methods.

The way most of the samples share templates is to just share templates, and usually by sharing template Maps:

    myPalette.nodeTemplateMap = myDiagram.nodeTemplateMap;

But you can mix and match if you like. Templates and Geometrys and Brushes can be shared. GraphObjects (whether in templates or not) cannot be shared.

Ok. I think I now see the problem with my code. Thanks a lot for your help. I guess what I was really trying to achieve was some kind of ‘base template’ which I’d be able to easily inherit from and customize. Like - for example - I have a same template for Diagram and Palette, but for Diagram I have to add mouse events which I don’t want in Palette. There are other ways to do it.

What I still don’t understand is why this is working:

var MyNodeTemplate2 = function () {
    go.Node.call(this, go.Panel.Auto);
    this.selectionAdorned = false;
    this.resizable = true;
    this.resizeObjectName = "SHAPE";
    this.add(go.GraphObject.make(go.Shape, 
        {
            name: "SHAPE",
            width: 60,
            height: 60
        }, 
        new go.Binding("figure"), 
        new go.Binding("fill", "fill", null)
    ));
    this.add(go.GraphObject.make(go.TextBlock, 
        {
            font: "16pt serif"
        }, 
        new go.Binding("text", "fill")
    ));
}
MyNodeTemplate2.prototype = Object.create(go.Node.prototype);
var nodeTemplate = new MyNodeTemplate2();

it is only an academic question and feel free not to answer as this is probably only me not understanding java script good enough:-). But it would be great to understand what is different about these two approaches.

If you want a “base” template that is slightly different in two different diagrams, or even within the same diagram as differently named templates in the Diagram.nodeTemplateMap, then I suggest you do what I suggested earlier – have a function that constructs and returns a template with different values. In your case you could have a boolean parameter that decides whether or not to include setting various mouse event handlers.

I think in your other case the template really is a Node and not a subclass of Node. If you aren’t using TypeScript subclassing/inheritance, you should be calling Diagram.inherit: go.Diagram.inherit(MyNodeTemplate, go.Node);.