Link validation not working

Case 1: Link validations is being triggered (working) for the following case

Case 2: but its not triggering (not working) for the below case

For case 1, when we console log fromnode and tonode its prints, (we are using that info to validate whether they are connected or not);
but in case 2, its not triggering at all, nothing is getting printed in linkValidations,

Our case: we need to show to the user that case 2 connection is not valid connection, for that to happen we need fromnode and tonode, info while snapping the connection, but since its not showing anything, we cant do that. Any solution?

Where and how did you define your link validation predicate? Have you customized the LinkingTool in any way? What are the port-related properties of those port objects?

link validation code for one shape others also have same code for other shapes, only the number is different

    linkValidation: (
          fromnode: go.Node,
          fromport: go.GraphObject,
          tonode: go.Node,
          toport: go.GraphObject
        ) => {
          if (fromnode.data.category === CanvasShapesCategory.ACTIVITY) {
            return (
              fromnode.findLinksOutOf().count < 10
              &&
              tonode.findLinksInto().count < 1
            );
          } else {
            return true;
          }
        }

No we didnt modify the linkingTool, its same as it is

we didnt provide any port information to the shape, as port is acting as whole node.

So you have set Node.linkValidation. OK. Do you only have one node template? How is it defined at top-level (I don’t care about the inside of the node)?


Do u mean this one?

export class ActivityShape extends BaseShape {

  constructor(data: MinShapeConstructionProperties<ActivityShape>) {
     super(data);
    Object.assign(this, { ...data });
    this.category = CanvasShapesCategory.ACTIVITY;
  }

  public static getTemplate(contextMenu: go.Adornment | go.HTMLInfo): go.Node {
    const $ = go.GraphObject.make;
    return $(
      go.Node,
      "Spot",
      {
        shadowColor: "rgb(0,0,0,0.2)",
        shadowBlur: 6,
        shadowOffset: new go.Point(0, 2),
        isShadowed: true,
        contextMenu,
        linkValidation: GRAPH_CONFIG.LINK_VALIDATIONS.ACTIVITY,
      },
      new go.Binding("location", "position", parseNodeLocation).makeTwoWay(
        convertNodeLocationPoint
      ),
      {
        locationSpot: go.Spot.Center,
      },
      new go.Binding("isSelected", "isSelected").makeTwoWay(),
      blurIfPlaceholder(),
      toolTipHandler(),
      $(
        go.Panel,
        "Auto",
        {
          padding: 2,
        },
        $(
          go.Shape,
          "RoundedRectangle",
          {
            fill: GRAPH_CONFIG.SHAPE_COLORS.whiteColor,
            width: 220,
            height: 70,
            name: GRAPH_CONFIG.ERROR_BLOCK_NAME.SHAPE_BLOCK,
            shadowVisible: true,
            margin: new go.Margin(0),
          },
          new go.Binding("figure", "shape"),
          new go.Binding("stroke", "", (node: go.Node) => {
            const { isSelected } = node;
            const { syncState } = node?.data;
            if (syncState?.status !== CanvasEntityState.Invalid) {
              return isSelected
                ? GRAPH_CONFIG.SHAPE_COLORS.blueColor
                : GRAPH_CONFIG.SHAPE_COLORS.whiteColor;
            }
          }).ofObject(),
          new go.Binding("fill", "", (node: go.Node) => {
            const { isSelected } = node;
            const { syncState } = node?.data;
            if (syncState?.status !== CanvasEntityState.Invalid) {
              return isSelected
                ? GRAPH_CONFIG.SHAPE_COLORS.activityHighlightColor
                : GRAPH_CONFIG.SHAPE_COLORS.whiteColor;
            }
          }).ofObject(),
          new go.Binding("height", "isLooped", (isLoooped: boolean) => {
            return isLoooped ? 80 : 70;
          }),
          canvasValidationHandler("errorRedColor", "whiteColor", "stroke"),
          canvasValidationHandler("lightRedColor", "whiteColor", "fill"),
        ),

        $(
          go.TextBlock,
          {
            alignment: go.Spot.Left,
            margin: new go.Margin(-20, 0, 0, 10),
            stroke: "#060322",
            font: "600 14px sans-serif",
            editable: true,
            isActionable: true,
            isMultiline: true,
            width: 188,
            shadowVisible: false,
            overflow: go.TextBlock.OverflowEllipsis,
            maxLines: 1,
            textValidation: GRAPH_CONFIG.TEXT_VALIDATIONS.ACTIVITY,
            textEdited: GRAPH_CONFIG.TEXT_EDITED.ACTIVITY,
            text: "Untitled Activity",
          },
          new go.Binding("text", "name").makeTwoWay(),
          new go.Binding("margin", "isLooped", (isLoooped: boolean) => {
            return isLoooped
              ? new go.Margin(-26, 0, 0, 10)
              : new go.Margin(-20, 0, 0, 10);
          }),
          hideIfPlaceholder()
        ),

        //#region AHT
        $(
          go.Panel,
          {
            padding: 0,
            alignment: go.Spot.Left,
            margin: new go.Margin(28, 0, 0, 5),
          },
          new go.Binding("margin", "isLooped", (isLoooped: boolean) => {
            return isLoooped
              ? new go.Margin(14, 0, 0, 5)
              : new go.Margin(28, 0, 0, 5);
          }),
          $(go.Picture, {
            maxSize: new go.Size(11, 11),
            margin: new go.Margin(5, 0, 0, 5),
            source: "assets/sds/images/gojs/schedule.svg",
          }),
          $(
            go.TextBlock,
            {
              alignment: go.Spot.Left,
              margin: new go.Margin(5, 0, 0, 17),
              stroke: "#060322",
              font: "400 12px sans-serif",
              editable: true,
              shadowVisible: false,
              isActionable: true,
              isMultiline: true,
              maxLines: 1,
              textValidation: GRAPH_CONFIG.TEXT_VALIDATIONS.ACTIVITY,
              textEdited: GRAPH_CONFIG.TEXT_EDITED.ACTIVITY,
            },
            new go.Binding("text", "aht", (data: string) => {
              return data + " " + (parseFloat(data) > 1 ? "mins" : "min");
            })
          ),
          new go.Binding("visible", "aht", (data: number) => !!data),
          hideIfPlaceholder()
        ),
        //#endregion

        //#region COST
        $(
          go.Panel,
          {
            padding: 0,
            margin: new go.Margin(28, 0, 0, 75),
            alignment: go.Spot.Left,
          },
          new go.Binding("margin", "isLooped", (isLoooped: boolean) => {
            return isLoooped
              ? new go.Margin(14, 0, 0, 75)
              : new go.Margin(28, 0, 0, 75);
          }),
          $(go.Picture, {
            maxSize: new go.Size(11, 11),
            margin: new go.Margin(5, 0, 0, 5),
            source: "assets/sds/images/gojs/activity_coin.svg",
          }),

          $(
            go.TextBlock,
            {
              alignment: go.Spot.Left,
              margin: new go.Margin(5, 0, 0, 17),
              stroke: "#060322",
              font: "400 12px sans-serif",
              editable: true,
              shadowVisible: false,
              isActionable: true,
              isMultiline: true,
              maxLines: 1,
              textValidation: GRAPH_CONFIG.TEXT_VALIDATIONS.ACTIVITY,
              textEdited: GRAPH_CONFIG.TEXT_EDITED.ACTIVITY,
            },
            new go.Binding("text", "cost", (data: string) => "$ " + data)
          ),
          new go.Binding("visible", "cost", (data: number) => !!data),
          hideIfPlaceholder()
        ),
        //#endregion

        //#region Loop icon
        $(
          go.Panel,
          {
            alignment: go.Spot.BottomCenter,
            margin: new go.Margin(4, 0, 3, 0),
            visible: false,
          },
          $(go.Picture, {
            maxSize: new go.Size(13, 13),
            source: "assets/sds/images/gojs/loop_icon.svg",
          }),
          new go.Binding("visible", "isLooped")
        )
        //#endregion
      ),
      ...ENTITY_PORTS.ACTIVITY,
      GRAPH_CONFIG.REMOVE_DEFAULT_BORDER
    );
  }
}

let me know if you were asking for this code or not.

I was wondering if those two “Untitled Activity” nodes in your screenshots were of different templates, since they seem to have different behaviors (even beyond the issue about link validation).

What is BaseShape? Why do you define ActivityShape as a class?

Hi Walter,
Base shape is used for all the shapes which have common properties,
and activity shape is used for the template of that specific shape,
Base shape doesnt have anything to do with shape design or anything, it has only the attrivutes being used in the project, all the design of activity shape is mentioned in the above code.
And furthermore, we made everything OOP based for ease of convenience.
But still not clear why link validation is not being triggered for the above image, Imean gojs is not giving us any callback while i try to link those connections. is that a bug in gojs or anything else?

Simplified query:
When we connected from inclusive to activity we got the callback of fromnode and tonode,
image

when we did the reverse it doesnt give any callback, although they are connected but we need to show user that nodes are already connected, for that we need that fromnode and tonode data.
image

Hi Walter,
just an update,
this piece of code was preventing the cycle and hence there was no callback from linkValidation. After commenting it, the callback was coming,
Hence we can move forward accordingly

 this.diagram.validCycle = go.Diagram.CycleNotDirected;