Custom UI node - do stuff after rendering

Hi folks,

I'm developing a new custom UI node, which displays an SVG drawing that fills the available space:

function HTML(config) { 
    var configAsJson = JSON.stringify(config);

    var html = String.raw`
        <svg id="mySvgId" height="100%" width="100%" ng-init='init(` + configAsJson + `)'"></svg> 
    `;

In that init function (triggered by ng-init) I need to calculate a.o. the real height of the SVG drawing:

initController: function($scope, events) {
    $scope.init = function (config) {
        var svgHeight = document.getElementById("mySvgId").getBBox().height;
    }
}

The svgHeight is at this moment 150. But as soon as the browser has rendered the SVG drawing, the final SVG height will be larger:
image
So I need to move my code from the ng-init to some handler that is triggered after the SVG has got its final size. Have tried lots of experiments, but they all failed :weary:

Now I have found a post-render-callback mechanism on stackoverflow, but if I try this:

initController: function($scope, events) {
    $scope.init = function (config) {
        $timeout(function () {
             var svgHeight = document.getElementById("mySvgId").getBBox().height;
        });
    }
}

Then I get this in my browser console:

app.min.js:148 ReferenceError: $timeout is not defined

Does anybody know how I can solve this, to get the final SVG height?

Thanks !!!!
Bart

Basically you can calculate sizes from known values exposed by API
size in direction = 1x1 Widget Size * units in direction - widget gap
Should cover most situations but may need some adjustments.
Things to keep in mind is that your widget should be aligned similar to others and this is kind of challenge.

Thanks @hotNipi,
So you propose to predict/calculate the size myself (e.g. in front of the rendering), instead of getting the real size after the rendering. That indeed could be an alternative solution.

From your profile picture I assume I'm not talking to my boss, but you certainly use the same language :rofl:
Can you explain that a bit more please?

And do you have any clue how I could use that $timeout in a UI node? It would be too weird if AngularJs wouldn't offer any after-rendering callback somehow...

P.S. I have tried to use a normal Javascript setTimeout (with 0 time interval), but as soon as that is being triggered the height is still 150 ...

Dont know the requirements of your drawing you do but if you try to render svg with 100% * 100% be prepared for blurry edges. So exact size has some advantages.

Can explain more of course but to cover everything it takes too much. Lets go in deep in details from your side questions
Angular is not my best friend so sorry about that :smiley:

1 Like

@BartButenaers out of curiosity, what will this nodes function be? To provide drawing capabilities? To just render SVG? For SVG animations & feedback to node red of SVG events (like click etc)?

@Steve-Mcl,
No not a general drawing node...
I'm developing a camera-viewer UI widget. When a user e.g. wants to have PTZ control buttons on top of the camera images, then I need calculate the location of those buttons. And to do that I need the real dimensions of the SVG drawing.

You can peek into ui-level code if you haven't already :wink: , you may find some useful ideas about the pre calculated size option. https://github.com/hotNipi/node-red-contrib-ui-level/blob/master/ui-level.js
And I suggest to use unique id-s for html elements in early state of dev. Save your time later on...You'll need to do it anyway.

Damn busted.
But it is already a few weeks ago, so will have to do my homework again ...

Evening guys,

Found at last a trick to detect in pure Javascript when the DOM tree is loaded:

$scope.init = function (config) {
    // Wait until the DOM tree is loaded, so all DOM elements have their final size
    var stateCheck = setInterval(() => {
        if (document.readyState === 'complete') {
            clearInterval(stateCheck);
             // Do your stuff here ...
        }
    }, 100);
}

When I add a breakpoint at the clearInterval statement, the DOM element seems indeed to have received its final size:

image
So when I start adding SVG elements (for my PTZ control) there, everything is nicely drawn at the correct location inside the SVG ...

Don't know if it works always. Have tested it on Firefox / Chrome / Edge and it seems to be fine. Fingers crossed ...

2 Likes

Remark: if somebody ever wants to use the above code snippet, it might be useful to read this article. Summarized: when you change $scope variables from within the setInterval/setTimeout, it seems that AngularJs is not aware of those changes (so those changes will not be handled by AngularJs). In that case you would have to do it like this:

$scope.init = function (config) {
    // Wait until the DOM tree is loaded, so all DOM elements have their final size
    var stateCheck = setInterval(() => {
        if (document.readyState === 'complete') {
            clearInterval(stateCheck);
                $scope.$apply(function () {
                    // Change your variables on $scope here, so AngularJs is aware of the changes ...
                });
        }
    }, 100);
}

The difference between setTimeout and $timeout (from AngularJs), is that $timeout handles the $scope.$apply already automatically ...