Buttons

For your convenience we have defined several Panels for common uses. These include "Button", "TreeExpanderButton", "SubGraphExpanderButton", "PanelExpanderButton", "ContextMenuButton", and "CheckBoxButton". "ContextMenuButton"s are typically used inside of "ContextMenu" Panels; "CheckBoxButton"s are used in the implementation of "CheckBox" Panels.

These predefined panels can be used as if they were Panel-derived classes in calls to GraphObject.make or GraphObject.build. They are implemented as simple visual trees of GraphObjects in Panels, with pre-set properties and event handlers.

You can see a copy of their definitions in this file: Buttons.js.

See samples that make use of buttons in the samples index. In addition, see the Checkboxes extension for an example of using "CheckBoxButton".

General Buttons

The most general kind of predefined Panel is "Button".


  diagram.nodeTemplate =
    new go.Node("Auto", { locationSpot: go.Spot.Center })
      .add(
        new go.Shape("Rectangle", { fill: "gold" }),
        new go.Panel("Vertical", { margin: 3 })
          .add(
            go.GraphObject.build("Button", { margin: 2, click: incrementCounter })
              .add(
                new go.TextBlock("Click me!")
              ),
            new go.TextBlock()
              .bind("text", "clickCount", c => "Clicked " + c + " times.")
          )
      );

  function incrementCounter(e, obj) {
    const node = obj.part;
    const data = node.data;
    if (data && typeof(data.clickCount) === "number") {
      node.diagram.model.commit(m => m.set(data, "clickCount", data.clickCount + 1), "clicked");
    }
  }

  diagram.model = new go.GraphLinksModel([ { clickCount: 0 } ]);

Buttons are just Panels holding a Shape that will surround whatever content you give it. The border Shape is named "ButtonBorder" so that you can easily set or bind its properties.

The event handlers defined by all "Button"s make use of additional properties, not defined in the API, but that you can see in the definition of "Button": Buttons.js. These properties parameterize the appearance of the button.


  diagram.nodeTemplate =
    new go.Node("Auto", { locationSpot: go.Spot.Center })
      .add(
        new go.Shape("Rectangle", { fill: "gold" }),
        new go.Panel("Vertical", { margin: 3 })
          .add(
            go.GraphObject.build("Button", {
                  margin: 2,
                  click: (e, button) => alert(button.findObject("ButtonBorder").fill),
                  // set properties on the border Shape of the "Button"
                  "ButtonBorder.fill": "fuchsia",
                  // set properties on the "Button" itself used by its event handlers
                  "_buttonFillOver": "pink",
                })
              .add(
                new go.TextBlock("fuchsia button\nwith pink highlight", { margin: 2, textAlign: "center" })
              ),
            go.GraphObject.build("Button", {
                  margin: 2,
                  click: (e, button) => alert(button.findObject("ButtonBorder").stroke),
                  // set properties on the border Shape of the "Button"
                  "ButtonBorder.figure": "Circle",
                  "ButtonBorder.fill": "cyan",
                  "ButtonBorder.stroke": "darkcyan",
                  "ButtonBorder.strokeWidth": 3,
                  // set properties on the "Button" itself used by its event handlers
                  "_buttonFillOver": "white",
                  "_buttonStrokeOver": "cyan",
                  "_buttonFillPressed": "lightgray"
                })
              .add(
                new go.TextBlock("Circular\nbutton", { margin: 2, textAlign: "center" })
              ),
            go.GraphObject.build("Button", {
                  margin: 2,
                  click: (e, button) => alert(button.findObject("PIC").source)
                })
              .add(
                // the button content can be anything -- it doesn't have to be a TextBlock
                new go.Picture("images/50x40.png", { name: "PIC", width: 50, height: 40 })
              ),
            go.GraphObject.build("Button", {
                  margin: 2,
                  // buttons can be disabled too, by either setting or data binding:
                  isEnabled: false,
                  click: (e, button) => alert("won't be alerted")
                })
              .add(
                new go.TextBlock("disabled", { stroke: "gray" })
              )
          )
      );

  diagram.model = new go.GraphLinksModel([ { } ]);

TreeExpanderButtons

It is common to want to expand and collapse subtrees. It is easy to let the user control this by adding an instance of the "TreeExpanderButton" to your node template. The button calls CommandHandler.collapseTree or CommandHandler.expandTree depending on the value of Node.isTreeExpanded. The button's icon's Shape.figure changes as the value of Node.isTreeExpanded changes.


  diagram.nodeTemplate =
    new go.Node("Spot")
      .add(
        new go.Panel("Auto")
          .add(
            new go.Shape("Rectangle", { fill: "gold" }),
            new go.TextBlock("Click small button\nto collapse/expand subtree",
                              { margin: 5 })
          ),
        go.GraphObject.build("TreeExpanderButton", {
          alignment: go.Spot.Bottom, alignmentFocus: go.Spot.Top,
          visible: true
        })
      );

  diagram.layout = new go.TreeLayout({ angle: 90 });

  diagram.model = new go.GraphLinksModel(
    [ { key: 1 },
      { key: 2 } ],
    [ { from: 1, to: 2 } ] );

A "TreeExpanderButton" is a "Button" that holds a Shape displaying either a "MinusLine" or a "PlusLine" figure, depending on the value of the Node.isTreeExpanded. That shape is named "ButtonIcon", so that you can easily set or bind its properties, in addition to the properties of the "ButtonBorder" and of the "Button" itself.


  diagram.nodeTemplate =
    new go.Node("Spot")
      .add(
        new go.Panel("Auto")
          .add(
            new go.Shape("Rectangle", { fill: "gold" }),
            new go.TextBlock("Click small button\nto collapse/expand subtree",
                             { margin: 5 })
          ),
        go.GraphObject.build("TreeExpanderButton",
          {
            alignment: go.Spot.Bottom, alignmentFocus: go.Spot.Top,
            visible: true,
            // set the two additional properties used by "TreeExpanderButton"
            // that control the shape depending on the value of Node.isTreeExpanded
            "_treeExpandedFigure": "TriangleUp",
            "_treeCollapsedFigure": "TriangleDown",
            // set properties on the icon within the border
            "ButtonIcon.fill": "darkcyan",
            "ButtonIcon.strokeWidth": 0,
            // set general "Button" properties
            "ButtonBorder.figure": "Circle",
            "ButtonBorder.stroke": "darkcyan",
            "_buttonStrokeOver": "darkcyan"
          })
    );

  diagram.layout = new go.TreeLayout({ angle: 90 });

  diagram.model = new go.GraphLinksModel(
    [ { key: 1 },
      { key: 2 } ],
    [ { from: 1, to: 2 } ] );

SubGraphExpanderButtons

It is also common to want to expand and collapse groups containing subgraphs. You can let the user control this by adding an instance of the "SubGraphExpanderButton" to your group template. The button calls CommandHandler.collapseSubGraph or CommandHandler.expandSubGraph depending on the value of Group.isSubGraphExpanded. The button's icon's Shape.figure changes as the value of Group.isSubGraphExpanded changes.


  diagram.groupTemplate =
    new go.Group("Auto")
      .add(
        new go.Shape("Rectangle", { fill: "gold" }),
        new go.Panel("Vertical", { margin: 5, defaultAlignment: go.Spot.Left })
          .add(
            new go.Panel("Horizontal")
              .add(
                go.GraphObject.build("SubGraphExpanderButton",
                                     { margin: new go.Margin(0, 3, 5, 0) }),
                new go.TextBlock("Group")
              ),
            new go.Placeholder()
          )
      );

  diagram.model = new go.GraphLinksModel(
    [ { key: 0, isGroup: true },
      { key: 1, group: 0 },
      { key: 2, group: 0 },
      { key: 3, group: 0 } ] );

A "SubGraphExpanderButton" is like a "TreeExpanderButton" in its being a "Button" with a border Shape surrounding an icon Shape. That shape is named "ButtonIcon", so that you can easily set or bind its properties, in addition to the properties of the "ButtonBorder" and of the "Button" itself.


diagram.groupTemplate =
  new go.Group("Auto")
    .add(
      new go.Shape("Rectangle", { fill: "gold" }),
      new go.Panel("Vertical", { margin: 5, defaultAlignment: go.Spot.Left })
        .add(
          new go.Panel("Horizontal")
            .add(
              go.GraphObject.build("SubGraphExpanderButton", {
                  // set the two additional properties used by "SubGraphExpanderButton"
                  // that control the shape depending on the value of Group.isSubGraphExpanded
                  "_subGraphExpandedFigure": "TriangleUp",
                  "_subGraphCollapsedFigure": "TriangleDown",
                  // set other properties on the button icon
                  "ButtonIcon.scale": 1.2,
                  // and properties on the button border or the button itself
                  "ButtonBorder.opacity": 0.0
                }),
              new go.TextBlock("Group")
            ),
            new go.Placeholder()
        )
    );

  diagram.model = new go.GraphLinksModel(
    [ { key: 0, isGroup: true },
      { key: 1, group: 0 },
      { key: 2, group: 0 },
      { key: 3, group: 0 } ] );

PanelExpanderButtons

It is common to want to expand and collapse a piece of a node, thereby showing or hiding details that are sometimes not needed. It is easy to let the user control this by adding an instance of the "PanelExpanderButton" to your node template. The second argument to GraphObject.make should be a string that names the element in the node whose GraphObject.visible property you want the button to toggle.


  diagram.nodeTemplate =
    new go.Node("Auto")
      .add(
        new go.Shape({ fill: "gold" }),
        new go.Panel("Table",
              { defaultAlignment: go.Spot.Top, defaultColumnSeparatorStroke: "black" })
          .add(
            new go.Panel("Table", { column: 0 })
              .add(
                new go.TextBlock("List 1", {
                    column: 0, margin: new go.Margin(3, 3, 0, 3),
                    font: "bold 12pt sans-serif"
                  }),
                go.GraphObject.build("PanelExpanderButton", { column: 1 }, "LIST1"),
                new go.Panel("Vertical",
                    { name: "LIST1", row: 1, column: 0, columnSpan: 2 })
                  .bind("itemArray", "list1")
              ),
            new go.Panel("Table", { column: 1 })
              .add(
                new go.TextBlock("List 2", {
                    column: 0, margin: new go.Margin(3, 3, 0, 3),
                    font: "bold 12pt sans-serif"
                  }),
                go.GraphObject.build("PanelExpanderButton", { column: 1 }, "LIST2"),
                new go.Panel("Vertical",
                    { name: "LIST2", row: 1, column: 0, columnSpan: 2 })
                  .bind("itemArray", "list2")
              )
          )
      );

  diagram.model = new go.GraphLinksModel([
    {
      list1: [ "one", "two", "three", "four", "five" ],
      list2: [ "first", "second", "third", "fourth" ]
    }
  ]);

A "PanelExpanderButton" is like a "TreeExpanderButton" or "SubGraphExpanderButton" in its being a "Button" with a border Shape surrounding an icon Shape. However, this panel binds the Shape.geometryString rather than the Shape.figure.


  diagram.nodeTemplate =
    new go.Node("Auto")
      .add(
        new go.Shape({ fill: "gold" }),
        new go.Panel("Table",
            { defaultAlignment: go.Spot.Top, defaultColumnSeparatorStroke: "black" })
          .add(
            new go.Panel("Table", { column: 0 })
              .add(
                new go.TextBlock("List 1", {
                    column: 0, margin: new go.Margin(3, 3, 0, 3),
                    font: "bold 12pt sans-serif"
                  }),
                go.GraphObject.build("PanelExpanderButton", {
                    column: 1, height: 16,
                    // set the two additional properties used by "PanelExpanderButton"
                    // that control the shape depending on the value of GraphObject.visible
                    // of the object named "LIST1"
                    "_buttonExpandedFigure": "M0 0 L10 0",
                    "_buttonCollapsedFigure": "M0 5 L10 5 M5 0 L5 10",
                    "ButtonIcon.stroke": "blue"
                  },
                  "LIST1"),
                new go.Panel("Vertical", {
                    name: "LIST1", row: 1, column: 0, columnSpan: 2
                  })
                  .bind("itemArray", "list1")
              ),
            new go.Panel("Table", { column: 1 })
              .add(
                new go.TextBlock("List 2", {
                    column: 0, margin: new go.Margin(3, 3, 0, 3),
                    font: "bold 12pt sans-serif"
                  }),
                go.GraphObject.build("PanelExpanderButton", {
                    column: 1,
                    // set the two additional properties used by "PanelExpanderButton"
                    // that control the shape depending on the value of GraphObject.visible
                    // of the object named "LIST1"
                    "_buttonExpandedFigure": "F M0 10 L5 0 10 10z",
                    "_buttonCollapsedFigure": "F M0 0 L10 0 5 10z",
                    "ButtonIcon.strokeWidth": 0,
                    "ButtonIcon.fill": "blue"
                  },
                  "LIST2"),
                new go.Panel("Vertical", {
                    name: "LIST2", row: 1, column: 0, columnSpan: 2
                  })
                  .bind("itemArray", "list2")
              )
          )
      );

  diagram.model = new go.GraphLinksModel([
    {
      list1: [ "one", "two", "three", "four", "five" ],
      list2: [ "first", "second", "third", "fourth" ]
    }
  ]);

ContextMenuButtons and ContextMenus

Although you can implement context menus in any way you choose, it is common to use the predefined "ContextMenuButton".


  diagram.nodeTemplate =
    new go.Node("Auto")
      .add(
        new go.Shape("Rectangle", { fill: "gold" }),
        new go.TextBlock("Use ContextMenu!", { margin: 5 })
      );

  diagram.nodeTemplate.contextMenu =
    go.GraphObject.build("ContextMenu")
      .add(
        go.GraphObject.build("ContextMenuButton",
            { click: (e, obj) => shiftNode(obj, -20) })
          .add(
            new go.TextBlock("Shift Left")
          ),
        go.GraphObject.build("ContextMenuButton",
            { click: (e, obj) => shiftNode(obj, +20) })
          .add(
            new go.TextBlock("Shift Right")
          )
      );

  function shiftNode(obj, dist) {
    const adorn = obj.part;
    const node = adorn.adornedPart;
    node.diagram.commit(d => {
      const pos = node.location.copy();
      pos.x += dist;
      node.location = pos;
    }, "Shift");
  }

  diagram.model = new go.GraphLinksModel([ { key: 1 } ] );

For an example of defining context menus using HTML, see the Custom ContextMenu sample.

A "ContextMenuButton" is just a "Button" with a few properties set. One of those properties is GraphObject.stretch, which is set to go.Stretch.Horizontal so that all of the "ContextMenuButton"s in a "ContextMenu" will be stretch to the same width. But you can set all of the usual properties on both its "ButtonBorder" Shape as well as on the button itself.


  diagram.nodeTemplate =
    new go.Node("Auto")
      .add(
        new go.Shape("Rectangle", { fill: "gold" }),
        new go.TextBlock("Use ContextMenu!", { margin: 5 })
      );

  diagram.nodeTemplate.contextMenu =
    go.GraphObject.build("ContextMenu")
      .add(
        go.GraphObject.build("ContextMenuButton",
            {
              click: (e, obj) => shiftNode(obj, -20),
              "ButtonBorder.fill": "yellow",
              "_buttonFillOver": "cyan",
              "_buttonFillPressed": "lime"
            })
          .add(
            new go.TextBlock("Shift Left")
          ),
        go.GraphObject.build("ContextMenuButton",
            {
              click: (e, obj) => shiftNode(obj, +20),
              "ButtonBorder.fill": "yellow",
              "_buttonFillOver": "cyan",
              "_buttonFillPressed": "lime"
            })
          .add(
            new go.TextBlock("Shift Right")
          ),
        go.GraphObject.build("ContextMenuButton",
            { isEnabled: false,
              click: (e, obj) => alert("won't be alerted") })
          .add(
            new go.TextBlock("Disabled", { stroke: "gray" })
          )
      );

  function shiftNode(obj, dist) {
    const adorn = obj.part;
    const node = adorn.adornedPart;
    node.diagram.commit(d => {
      const pos = node.location.copy();
      pos.x += dist;
      node.location = pos;
    }, "Shift");
  }

  diagram.model = new go.GraphLinksModel([ { key: 1 } ] );

See also the fancier round context menu implemented in Radial Context Menu.

CheckBoxButtons and CheckBoxes

A "CheckBoxButton" is a "Button" that is configured to toggle the boolean value of a data property. By default the button is clear when the value is false and shows a check mark when the value is true, but a great deal of customization is possible.

The first argument when defining a "CheckBoxButton" should be a string that names the data property holding the checked state of the "CheckBoxButton". If you do not want clicking on the button to toggle the value of a data property, specify a data property name that is the empty string.

A "CheckBoxButton" is used in the definition of the "CheckBox" Panel, which is a convenient way to associate any GraphObject as a label for the "CheckBoxButton".

Many examples of "CheckBox"es with various customizations are shown in the CheckBoxes sample.

You can find the definition of a "TriStateCheckBoxButton" in the Tri-State CheckBox Tree sample.

Button Definitions

The implementation of all predefined buttons is provided in Buttons.js in the Extensions directory. You may wish to copy and adapt these definitions when creating your own buttons. You may also wish to theme your own buttons, described on the theming intro page.

Note that the definitions of those buttons makes use of the GraphObject.defineBuilder static function. That extends the behavior of GraphObject.make and GraphObject.build to allow the creation of fairly complex visual trees by name with optional arguments. You can find the definitions of various kinds of builders in the samples and extensions, such as at: