Last event after layout is loaded

Hi All,

I saw a very strange behaviour after typical diagram events in loading groups, I try to explain:
I put a recursive function that is called in the ‘InitialLayoutCompleted’ event, and it navigates into groups creating a formula contained in a string. The problem is when it starts navigate thru the last groups inserted and children of other groups, because it finds these groups void, not containing other objects. I put also this recursive function in the mouseDrop event of the group template, called when you move anything in the group. Well, when I move something (I did it in the debug mode of firefox) it finds all objects in the group, as normal. Now my question is: is there a final event in diagram when all groups are loaded properly and all is in the right place? I see also that “containingGroupChanged” event in group is called very often during the initial loading of the viewport…
Here the group’s appearance:


PS: just above the formula created after the “mousedrop” event, that is right
Thx in advance

I hope it’s clear that it would not be natural to have an event that happens after everything else happens (or before everything happens) to be declared on any GraphObject or Part or Node or Group – it would need to be on the Diagram.

And in this case that would be the “InitialLayoutCompleted” DiagramEvent. https://gojs.net/latest/intro/events.html#InitialLayoutCompleted

Ok I guess it’s slightly unrealistic to have such this event but how can I handle all the modifications that succeed when the model is loaded? I saw that a lot of transactions happens during this phase, and probably this is the reason why groups are not completely loaded…so why in the initialLayoutCompleted a group (loaded from db json, with two nodes inside) is still void? Is there a trick, let me say, to manage this?

That should not be the case after setting Diagram.model or calling Diagram.delayInitialization. Unless your code is doing that explicitly. Are you modifying the model then?

I did not know that there was this possibility, so I don’t know how to do that. What I can imagine is that the event “containingGroupChanged” is called a lot of time before “initialLayoutLoaded”, during initialization.
I guessed that changing ports when this event is fired could be the reason of this, but also disabling the code in group changed event the diagram is not fully loaded and the last group loaded is void. Is there a test that I can do to check this? Or to determine why groups are not fully loaded after “initialLayoutLoaded” event.
the sequence of actions is like that:

  1. InitialLayout->check ports (only checks not modifying) of each group containing nodes (all group are containing nodes…)
  2. containingGroupChanged->rebuild all ports of groups changed
    Here’s the diagram model:
      myDiagram =
        $gj(go.Diagram, "myDiagramDiv",
          {
            initialContentAlignment: go.Spot.Top,
            initialViewportSpot: go.Spot.Top,
            initialAutoScale: go.Diagram.UniformToFill,
            autoScale: go.Diagram.Uniform,
            validCycle: go.Diagram.CycleNotDirected,  // don't allow loops
            maxSelectionCount :1,
            "ModelChanged": function(e) {
                if (e.isTransactionFinished) {
                    showModel();
                }
              },
            "InitialLayoutCompleted": function(e) {
            	// if not all Nodes have real locations, force a layout to happen
            	
            	repositionGroup(jQuery.parm_conf);
            	e.diagram.findTopLevelGroups().each(function(g) 
				{
            		if(g.data.rightArray.length>0 && g.data.group==undefined){
            			e.diagram.centerRect(g.actualBounds);
            		}
				});
            	checkIfUniqueGroup();
            	
            },
            "linkingTool.linkValidation":function(fromnode, fromport, tonode, toport, link) {
            	return onlyparm(fromnode, fromport, tonode, toport);
            },
            "relinkingTool.linkValidation":function(fromnode, fromport, tonode, toport, link) {
            	return onlyparm(fromnode, fromport, tonode, toport);
            },
            "LinkDrawn": function(e){
            	CheckAllInfoTab(false);
            },
			"SelectionDeleted": function(e) { 
				e.subject.each(function(p) 
					{
					if(countAllObject()>0){
						ricalcolaGruppo(p.part);
					}else{CheckAllInfoTab(false)}
					})
			},
            layout:$gj(go.TreeLayout, 
            { 
              isInitial: false,
              isOngoing: false, 
              angle: 0,
              nodeSpacing:90
            }),
            allowTextEdit: true,
            "undoManager.isEnabled": true
          }
        );

So what I’m missing?

Yes, the Part.containingGroupChanged event handler will be called for each Part that is added as a member of a Group.

Oh, you have disabled running the Layout initially, because you have set Layout.isInitial to false. You should do this if you are assured that all of the non-Link Parts have real locations. Of course any Parts that do not have real Part.location values will not be drawn anywhere.

I tried modifying Layout.isInitial to true but the result is unchanged. Is this also related to another strange behaviour? when I edit a textblock the new value is written in the json string instantly, but when I try to read it with classic code for (var mit = n.memberParts; mit.next(); ) { var part = mit.value; if (part instanceof go.Node) { ... }}... it extracts last value edited, not the new one. Is a problem of committing in both cases? In the first case probably not because it’s positioning objects from json code and I guess there’s no need to commit anything, but in the second case why the new value is uncommitted?

For the first problem I’ve found a tricky solution: when in initialLayoutCompleted event phase I disable with a flag the code inside containingGroupChanged that I think is big part of the problems. For the second problem I don’t know what to do… How can I commit changes on a textblock? Putting commit on text validation?:

              $gj(go.TextBlock, 
                {
                  editable: true,
                  margin: new go.Margin(2, 2, 2, 10),
                  stroke: "white",
                  font: "bold 12pt sans-serif",
                  isMultiline: false,
                  textValidation: okValo	  
                },
                  new go.Binding("text", "valo").makeTwoWay()
				)

this is the code part for the textblock.
(Do have I open a new topic?)

A separate topic would have been better, but if it doesn’t go on for long, never mind.

What do you mean by “edit a textblock”? Interactive editing by the user? That needs to be a separate transaction. I suggest that you start the editing process in a setTimeout function. There are some examples of this in the samples. For example:

              e.diagram.select(newnode);
              setTimeout(function() {
                e.diagram.commandHandler.editTextBlock();
              }, 20);

Sorry I mean interactive editing by user. But I don’t understand why start an editing processing with a setTimeout function…a textblock needs to be modified within a range of time to be committed? Can you show me an example in the samples?

The TextEditingTool executes its own transaction when the edit is accepted. But because it is interactive, showing some <input> element and letting the user type for as long as they like, it really must run separately from other transactions that occur, for example when loading a model.

That’s true for all of the Tools that perform transactions.

Ok. So if this limit is not set the transaction won’t be performed? I saw an example of text validation in samples (https://gojs.net/latest/intro/validation.html) but there’s nothing regarding this limit for committing the text…I saw that json has changed but the transaction is not performed anyway…Well how to apply your example to the node’s template? I need also a validation on that textblock

I don’t understand the situation that you are asking about. And I’m not sure what you mean about “json has changed”. The TextEditingTool.acceptText method, if TextEditingTool.isValidText returns true, will set TextBlock.text within a transaction. If you have a TwoWay Binding on that property, presumably it will update the Part.data’s property value.

by “json has changed” I mean that part.data property value’s been set.
Now, all things you perfectly described have been set:

The TextEditingTool.acceptText method (that I guess is implicitly declared when you set a textblock as editable)
TextEditingTool.isValidText returns true (from validation)
I have a TwoWay Binding on that property
so after that why transaction is not performed after the editing?
the validation function:

	function okValo(textblock, oldstr, newstr) {
		var ret=false;
		ret=newstr.length >= 1 && /^-?[0-9][0-9,]*$/.test(newstr);
		if(!ret){
			$("#btnsalva").attr("disabled", true);
		}else{
			CheckAllInfoTab(false);
		}
		return ret;
	};

and the code in the template:

              $gj(go.TextBlock, 
                {
                  editable: true,
                  margin: new go.Margin(2, 2, 2, 10),
                  stroke: "white",
                  font: "bold 12pt sans-serif",
                  isMultiline: false,
                  textValidation: okValo	  
                },
                  new go.Binding("text", "valo").makeTwoWay()
				)

That’s all. What I’m missing in that code?

The TextEditingTool calls TextEditingTool.acceptText when the user types Tab or Enter or loses focus from the input element.

acceptText then calls TextEditingTool.isValidText, which in your case means calling okValo.

If TextEditingTool.isValidText returns true, it starts a transaction, sets TextBlock.text, and commits the transaction.

Actually, here’s the code. NOTE: this code does refer to some internal things, so you cannot just call this code, even if you thought you wanted to.

  private doAcceptText(): void {
    const tb = this.textBlock;
    const diagram = this.diagram;
    const editor = this.currentTextEditor;
    if (tb !== null && editor !== null) {
      const oldstring = tb.text;
      let newstring = '';
      if (editor.valueFunction !== null) newstring = editor.valueFunction();

      if (!this.isValidText(tb, oldstring, newstring)) {
        this._state = TextEditingTool.StateInvalid;
        this.doError(oldstring, newstring);
        // call show, with the knowledge that this.state changed
        editor.show(tb, diagram, this);
        return;
      }

      this.startTransaction(this.name);
      this._state = TextEditingTool.StateValidated;

      // set the TransactionResult before raising events, in case they change the result or cancel the tool
      this.transactionResult = this.name;
      // update the TextBlock.text
      tb.text = newstring;
      // raise events
      this.doSuccess(oldstring, newstring);
      if (diagram !== null) diagram.raiseDiagramEvent('TextEdited', tb, oldstring);
      this.stopTransaction();

      // all done with this tool
      this.stopTool();
      if (diagram !== null) diagram.doFocus();
    }
  }

ehm, let me understand (probably it’s my bad english comprehension): this code is hidden into the code of gojs, so I can’t use this? But what I do with my code is activating this code, is right? I’m keeping to not understand what’s the solution.
What you described above is exactly what happens when I interactively edit the textblock: I select the field, write new text that is validated from okvalo. When it loses focus the new text appears in part.data. But the transaction is not performed. The code you posted is behind the starting of transaction? What can I do with this? You are probably overestimating my ability to understand this code…

I was just telling you how it is implemented. Ignore it if you don’t want to know. It seems you understand it well enough. Note the calls to startTransaction and stopTransaction, which calls commitTransaction.

It is interesting, I do not want to be misunderstood, but in reality there is nothing weird in what the application does, especially because I have followed precisely the indications of the example. What your code shows is what is expected using the example. Maybe something can interfere with this?
Also this property is set in the diagram: allowTextEdit: true. Without this I couldn’t be able to edit interactively the textblock, but this is the only difference between my code and the example. What can invalidate the state of the texteditingtool without any message log?

Does this help?
https://gojs.net/latest/intro/permissions.html#InPlaceTextEditing

Note that Diagram.allowTextEdit defaults to true.

After a lot of tries I can consider this a kind of bug. I followed all rules, examples, setting also default values (that in case of allowTextEdit didn’t allow edit without setting this), I don’t know in which collateral conditions this happens, maybe it’s a delay between the part.data updating and the real commited action but when I try to get value from edited text (after editing is ended obviously) I get old value instead of new just modified. This are all settings regarding the edit:
diagram:

  • allowTextEdit: true
  • myDiagram.toolManager.textEditingTool.defaultTextEditor = window.TextEditor

node:

  • editable: true,
  • isMultiline: false,
  • textValidation: okValo
  • new go.Binding(“text”, “valo”).makeTwoWay()

validation:

	function okValo(textblock, oldstr, newstr) {
		var ret=false;
		ret=newstr.length >= 1 && /^-?[0-9][0-9,]*$/.test(newstr);
		if(!ret){
			[do something...]
		}else{
			[do something...]
		}
		return ret;
	};

We can keep on discuss about surrounding settings, but this, if you don’t have any suggestion on what can be the reason of this behaviour, is clearly an oddity. Is there an overriding that I can do on the texteditingtool to commit validated values? Or to show a specific console log to investigate.
Just one final key question: if I put a check in the function of validation, after the real validation, the transaction’s not been yet committed?