How to expand node's size dynamically when the text in its textBlock is being entered?

I want to dynamically expand/shrink the size of Node depending upon the length of characters being entered in the textBlock of Node. Currently when I focus textblock, an HTML kind of text element appear over the Node and as I type larger number of characters, the textblock expands out of the Node. And only when I focus out of the textBlock does the Node itself increase in size.

I want to expand the Node’s size as I enter the text in textBlock and not just after the focus is out.

Kindly let me know if my question is not clear.

Thanks

I think this can be done by automatically updating the TextBlock.text itself.

However, I now notice a bug in the implementation of http://gojs.net/latest/extensions/TextEditor.js, so it may take a little while for us to figure this out.

OK, here’s an updated extensions/TextEditor.js file that has been customized to keep the actual TextBlock.text updated with the text being entered into the HTML textarea. I have named this file TextEditorRealtime.js.

Note that your requirement of having the edited TextBlock continuously be changing means that the undo/redo behavior will be different than with normal editing. With TextEditorRealtime.js each entered character will modify the diagram; whereas normally nothing in the diagram is modified until the text is accepted. Furthermore, text validation won’t happen until after all of the changes have already been applied to the edited TextBlock.

But I assume these are acceptable compromises in order to get your required behavior, or you wouldn’t be asking for it.

"use strict";
/*
*  Copyright (C) 1998-2017 by Northwoods Software Corporation. All Rights Reserved.
*/

// HTML + JavaScript text editor menu, made with HTMLInfo
// This is a re-implementation of the default text editor
// This file exposes one instance of HTMLInfo, window.TextEditor
// See also TextEditor.html
(function(window) {
  var textarea = document.createElement('textarea');
  textarea.id = "myTextArea";

  textarea.addEventListener('input', function(e) {
    var tool = TextEditor.tool;
    if (tool.textBlock === null) return;
    tool.diagram.startTransaction();
    tool.textBlock.text = this.value;
    tool.diagram.commitTransaction("input text");
    var tempText = tool.measureTemporaryTextBlock(this.value);
    var scale = this.textScale;
    this.style.width = 20 + tempText.measuredBounds.width * scale + 'px';
    this.style.height = 10 + tempText.measuredBounds.height * scale + "px";
    this.rows = tempText.lineCount;
  }, false);

  textarea.addEventListener('keydown', function(e) {
    var tool = TextEditor.tool;
    if (tool.textBlock === null) return;
    var keynum = e.which;
    if (keynum === 13) { // Enter
      if (tool.textBlock.isMultiline === false) e.preventDefault();
      tool.acceptText(go.TextEditingTool.Enter);
      return;
    } else if (keynum === 9) { // Tab
      tool.acceptText(go.TextEditingTool.Tab);
      e.preventDefault();
      return;
    } else if (keynum === 27) { // Esc
      var tb = tool.textBlock;
      tool.doCancel();
      tb.diagram.startTransaction();
      tb.text = TextEditor.originalString;
      tb.diagram.commitTransaction("cancel text edit");
      if (tool.diagram !== null) tool.diagram.doFocus();
    }
  }, false);

  // handle focus:
  textarea.addEventListener('focus', function(e) {
    var tool = TextEditor.tool;
    if (tool.currentTextEditor === null) return;

    if (tool.state === go.TextEditingTool.StateActive) {
      tool.state = go.TextEditingTool.StateEditing;
    }

    if (tool.selectsTextOnActivate) {
      textarea.select();
      textarea.setSelectionRange(0, 9999);
    }
  }, false);

  // Disallow blur.
  // If the textEditingTool blurs and the text is not valid,
  // we do not want focus taken off the element just because a user clicked elsewhere.
  textarea.addEventListener('blur', function(e) {
    var tool = TextEditor.tool;
    if (tool.currentTextEditor === null) return;

    textarea.focus();

    if (tool.selectsTextOnActivate) {
      textarea.select();
      textarea.setSelectionRange(0, 9999);
    }
  }, false);


  var TextEditor = new go.HTMLInfo();

  TextEditor.valueFunction = function() { return textarea.value; }

  TextEditor.mainElement = textarea; // to reference it more easily

  // used to be in doActivate
  TextEditor.show = function(textBlock, diagram, tool) {
    if (!(textBlock instanceof go.TextBlock)) return;

    TextEditor.tool = tool;  // remember the TextEditingTool for use by listeners
    TextEditor.originalString = textBlock.text;

    // This is called during validation, if validation failed:
    if (tool.state === go.TextEditingTool.StateInvalid) {
      textarea.style.border = '3px solid red';
      textarea.focus();
      return;
    }

    // This part is called during initalization:

    var loc = textBlock.getDocumentPoint(go.Spot.Center);
    var pos = diagram.position;
    var sc = diagram.scale;
    var textscale = textBlock.getDocumentScale() * sc;
    if (textscale < tool.minimumEditorScale) textscale = tool.minimumEditorScale;
    // Add slightly more width/height to stop scrollbars and line wrapping on some browsers
    // +6 is firefox minimum, otherwise lines will be wrapped improperly
    var textwidth = (textBlock.naturalBounds.width * textscale) + 6;
    var textheight = (textBlock.naturalBounds.height * textscale) + 2;
    var left = (loc.x - pos.x) * sc;
    var top = (loc.y - pos.y) * sc;

    textarea.value = textBlock.text;
    // the only way you can mix font and fontSize is if the font inherits and the fontSize overrides
    // in the future maybe have textarea contained in its own div
    diagram.div.style['font'] = textBlock.font;

    var paddingsize = 1;

    textarea.style.cssText =
    'position: absolute;' +
    'z-index: 100;' +
    'font: inherit;' +
    'fontSize: ' + (textscale * 100) + '%;' +
    'lineHeight: normal;' +
    'width: ' + (textwidth) + 'px;' +
    'height: ' + (textheight) + 'px;' +
    'left: ' + ((left - (textwidth / 2) | 0) - paddingsize) + 'px;' +
    'top: ' + ((top - (textheight / 2) | 0) - paddingsize) + 'px;' +
    'textAlign: ' + textBlock.textAlign + ';' +
    'margin: 0;' +
    'padding: ' + paddingsize + 'px;' +
    'border: 0;' +
    'outline: none;' +
    'white-space: pre-wrap;' +
    'overflow: hidden;' // for proper IE wrap

    textarea.textScale = textscale; // attach a value to the textarea, for convenience

    // Show:
    diagram.div.appendChild(textarea);

    // After adding, focus:
    textarea.focus();
    if (tool.selectsTextOnActivate) {
      textarea.select();
      textarea.setSelectionRange(0, 9999);
    }
  };

  TextEditor.hide = function(diagram, tool) {
    diagram.div.removeChild(textarea);
    TextEditor.tool = null;  // forget reference to TextEditingTool
  }

  window.TextEditor = TextEditor;
})(window);

Thank You Walter.

I’m getting this error when setting the editor.

Error: TextEditingTool.defaultTextEditor value is not an instance of Element: undefined
at Object.k (https://cdnjs.cloudflare.com/ajax/libs/gojs/1.6.7/go-debug.js:31:489)
at Object.jc (https://cdnjs.cloudflare.com/ajax/libs/gojs/1.6.7/go-debug.js:33:441)
at Object.l (https://cdnjs.cloudflare.com/ajax/libs/gojs/1.6.7/go-debug.js:32:204)
at hi.<anonymous> (https://cdnjs.cloudflare.com/ajax/libs/gojs/1.6.7/go-debug.js:669:87)
at init (http://localhost:10697/app/components/Orgchart/OrgchartController2.js:90:69)
at activate (http://localhost:10697/app/components/Orgchart/OrgchartController2.js:1327:21)
at new OrgchartController (http://localhost:10697/app/components/Orgchart/OrgchartController2.js:1335:9)
at e (https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js:39:394)
at Object.instantiate (https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js:40:9)
at https://ajax.googleapis.com/ajax/libs/angularjs/1.4.8/angular.min.js:80:442 <div flex="" layout="row" ui-view="" class="ng-cloak ng-scope" data-ng-animate="1">

Can you please help me figure out this issue!

Thanks

Are you using GoJS version 1.7? Oh – apparently not, given the cdnjs location.

Uncaught TypeError: go.HTMLInfo is not a constructor
at TextEditorRealtime.js:80
at TextEditorRealtime.js:158

The above error is showing prior to the other one.

I’m using 1.6.7

Do I need to change my gojs version?

Thanks walter, it works with 1.7 version.

A new introduced issue is that when a new node is created the textblock is out of the node.

The texblock does not animate with the rest of node.

Yes, the Diagram does not know about the HTML textarea element. Even if it did, that does not automatically mean that it should scroll the element, since some HTML elements would be expected not to scroll or zoom.

I suggest that you only start editing until a “LayoutCompleted” DiagramEvent occurs after you have added the new node and committed the transaction.

Thanks Walter,

An other issue I’m facing now is the call to this function:

        myDiagram.commandHandler.doKeyDown = function () 
           var e = myDiagram.lastInput;
            console.log(e.gr.code);
            if ((e.gr.code === 'Enter' || e.gr.code === 'NumpadEnter') && myDiagram.selection.count == 1) {

When I press a key, it shows this error:

Uncaught TypeError: Cannot read property 'code' of undefined
at ta.myDiagram.commandHandler.doKeyDown (OrgchartController2.js:977)
at Kh.doKeyDown (go-debug.js:721)
at C.doKeyDown (go-debug.js:794)
at HTMLCanvasElement.C.mJ (go-debug.js:819)

And this error only appears after I include the TextEditorRealtime.js file in the script.
Or maybe it’s to do with the 1.7 version of gojs.

What I understand is that the ‘keydown’ event is now associated with the HTML textarea.
So what is the safe work around for this?

Thanks

I’m able to receive events for the most keys using this

myDiagram.lastInput.e.key

But many of my functionality depend upon the keys ‘Enter’ and ‘Tab’ and in these cases I only receive the input event as blank. So I can’'t differentiate which key is pressed between Tab and Enter.

Thanks

What’s e.gr? That looks like you are trying to access a minified property – which is documented as not supported. GoJS Introduction -- Northwoods Software

You must use properties and methods which are documented in the API, GoJS API, or at least in http://gojs.net/latest/release/go.d.ts.

Or look at the “keydown” listener in the code that I gave you, above.

using doKeydown of command Handler, I can’t catch the ‘enter’ and ‘tab’ keys.

can you look into it?

Thanks

Edited…

I’ve found a work around of it but still I’d like to have your say on it.

Thanks again

One issue with the defaultTextEditor I’m having is

When a parent Node is large in width and as we type text in the defaultTextEditor of child node, the child while expanding, centers itself with respect to the parent, and in that case the sub-node grows in both direction while expanding but defaultTextEditor over it remain fixated on the left side and only grow on the right side, which results in defaultTextEditor going out of the node.

One solution is to keep the sub-node fixated at its position and move the rest of diagram to center the parent node with respect to its child. The other solution is to expand the defaultTextEditor in both directions just as its node does.

What do you think, which of the solution is possible to do?

Thanks

Try this. Pardon me if there are any errors in it.

  textarea.addEventListener('input', function(e) {
    var tool = TextEditor.tool;
    if (tool.textBlock === null) return;
    var textBlock = tool.textBlock;
    var diagram = tool.diagram;
    diagram.startTransaction();
    textBlock.text = this.value;
    diagram.commitTransaction("input text");
    var tempText = tool.measureTemporaryTextBlock(this.value);
    var scale = this.textScale;
    var loc = textBlock.getDocumentPoint(go.Spot.Center);
    var pos = diagram.position;
    var sc = diagram.scale;
    var textscale = textBlock.getDocumentScale() * sc;
    if (textscale < tool.minimumEditorScale) textscale = tool.minimumEditorScale;
    // Add slightly more width/height to stop scrollbars and line wrapping on some browsers
    // +6 is firefox minimum, otherwise lines will be wrapped improperly
    var textwidth = (textBlock.naturalBounds.width * textscale) + 6;
    var textheight = (textBlock.naturalBounds.height * textscale) + 2;
    var left = (loc.x - pos.x) * sc;
    var top = (loc.y - pos.y) * sc;
    var paddingsize = 1;
    this.style.width = 20 + tempText.measuredBounds.width * scale + "px";
    this.style.height = 10 + tempText.measuredBounds.height * scale + "px";
    this.style.left = ((left - (textwidth / 2) | 0) - paddingsize) + "px";
    this.style.top = ((top - (textheight / 2) | 0) - paddingsize) + "px";
    this.rows = tempText.lineCount;
  }, false);

Thanks Walter, it worked!

Hi Walter,

Thank You so much for the help.

One more thing I want to ask.
When the diagram is zoomed out a little and then if the text is edited, the textarea appear the same size and does not shrink with the node. I know it’s not supposed to do that.

But is it possible to shrink and expand it with the node size when the diagram is zoom in or out?

Thanks

Edit…

One way that is coming to my mind is to programmatically change the minSize of textBlock upon zooming in and out but I want to have your word on it.