Highlighting substrings within a single TextBlock

With GOJS tree view, I need to provide the search functionality. Whenever user types something for search, the search text appearing in various nodes needs to be highlighted.

From fourm & API , understands that with GOJS it is not possible to support html text for TextBlock.

I guess, expected functionality can be achieved by -
making binding of array of text with help of converter ( The converter splits the text in multiple values depending on search)

I would like to know to which template/object that can be binded.
Can you provide me template/object of GOJS where array of text can be binded and it gets displayed in single row.

I wonder if it might be possible to measure the start and end points of the searched-for text in the rendered TextBlock. Then you can draw your own highlighting – perhaps a colored rectangle behind that text, or perhaps a colored line underneath that text. That would avoid having to break up the TextBlock into multiple TextBlocks.

We will need to investigate this.

My original tree view was implemented using HTML.
With that when I search for “o” then “Northwoods Software” shows all “o” highlighted.
As I type “oft” then only the alphabets from “Software” gets highlighted.

Customer expects similar behaviour with Tree View implemented using goJS.

OK, what I am giving here works in a limited number of circumstances.

First you’ll need to wrap the TextBlock with a “Position” Panel named “TEXTPANEL”.

    myDiagram.nodeTemplate =
      $(go.Node, . . .,
        // wrap each TextBlock with this Panel, which may hold some Shapes acting as highlight blocks
        $(go.Panel, "Position",
          { name: "TEXTPANEL", . . . },
          $(go.TextBlock,
            { name: "TEXTBLOCK", . . . })
        )
      );

Then you can call highlightString:

  function highlightString(str) {
    var oldskips = myDiagram.skipsUndoManager;
    myDiagram.skipsUndoManager = true;
    myDiagram.startTransaction();
    myDiagram.nodes.each(function(n) {
      var tb = n.findObject("TEXTBLOCK");
      var pan = n.findObject("TEXTPANEL");
      if (tb === null || pan === null) return;
      // clear out any old highlight blocks
      while (pan.elements.count > 1) {
        if (pan.elt(0).name !== "TEXTBLOCK") {
          pan.removeAt(0);
        }
      }
      //??? doesn't handle wrapping in the middle of a search string
      var strwidth = getStringWidth(str, tb.font);
      var fontheight = getFontHeight(tb.font);
      var metrics = tb.metrics;
      var strArray = metrics.arrText;
      var y = tb.spacingAbove;
      for (var li = 0; li < strArray.length; li++) {
        var line = strArray[li];
        if (line.indexOf(str) >= 0) {
          var split = line.split(str);
          var x = 0;
          for (var si = 0; si < split.length-1; si++) {
            x += getStringWidth(split[si], tb.font);
            var lite = new go.Shape();
            lite.strokeWidth = 0;
            lite.fill = "yellow";
            lite.width = strwidth;
            lite.height = fontheight;
            //??? doesn't work for "start" and "end" in RTL languages
            var indent = 0;
            if (tb.textAlign === "center") indent = (metrics.maxLineWidth - metrics.arrSize[li]) / 2;
            else if (tb.textAlign === "right" || tb.textAlign === "end") indent = (metrics.maxLineWidth - metrics.arrSize[li]);
            lite.position = new go.Point(x + indent, y);
            pan.insertAt(0, lite);
            x += strwidth;
          }
        }
        y += fontheight + tb.spacingBelow + tb.spacingAbove;
      }
    });
    myDiagram.commitTransaction("highlightString");
    myDiagram.skipsUndoManager = oldskips;
  }

  var SharedCtx = document.createElement("canvas").getContext("2d");
  var SharedCtxFont = null;

  function getStringWidth(str, font) {
    if (str === "") return 0;
    if (SharedCtxFont !== font) {
      SharedCtx.font = font;
      SharedCtxFont = font;
    }
    return SharedCtx.measureText(str).width;
  };

  function getFontHeight(font) {
    var metrics = SharedCtx.measureText('M');
    return metrics.width * 1.3; //??? SUPER ARBITRARY 30%
  }

Note that this code does not handle line wrapping within the search string. If you want that, you can adapt this code.

Also I didn’t bother handling RTL languages. Or multiple TextBlocks within a Node. Or any TextBlock within a Link or a plain Part.

Another restriction: you have to call highlightString no earlier than in an “InitialLayoutCompleted” DiagramEvent listener.

live example:

Can you please do it for the RTL languages or give me a hint to change it.
@simon @walter