HowTo define and use global utillity function in V3.x and higher

I found a post here for this question, but it is 2 years ago and there were remarks to the solution, that it is not good and perhaps not usable in future. Also I found a solution in stackoverflow, but also more a workaround.

So my question is, how to do this in Node-Red V3 in an easy way. I did some programming, but don't be perfect. So please explain it step by step and perhaps with an example.

Is there a plan to integrate it in the user interface? I think nearly everybody will need something like this.

Why do you want a globally defined function?

The visual equivalent is the link-call node.

image

Demo Flow...

[{"id":"e8031839f82a4fac","type":"link in","z":"65b88f90c5129bc8","name":"mult * 10","links":[],"x":255,"y":600,"wires":[["e7680a651c3e6672"]]},{"id":"9f01e7a2880c059b","type":"link out","z":"65b88f90c5129bc8","name":"link out 1","mode":"return","links":[],"x":465,"y":600,"wires":[]},{"id":"e7680a651c3e6672","type":"function","z":"65b88f90c5129bc8","name":"mult * 10","func":"msg.payload = msg.payload * 10\nreturn msg;","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":360,"y":600,"wires":[["9f01e7a2880c059b"]]},{"id":"342498501c490f97","type":"inject","z":"65b88f90c5129bc8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"2","payloadType":"num","x":180,"y":480,"wires":[["29aa1920221cce50"]]},{"id":"29aa1920221cce50","type":"link call","z":"65b88f90c5129bc8","name":"","links":["e8031839f82a4fac"],"linkType":"static","timeout":"30","x":340,"y":480,"wires":[["cea61592b52d6752"]]},{"id":"cea61592b52d6752","type":"debug","z":"65b88f90c5129bc8","name":"expect 20","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":530,"y":480,"wires":[]},{"id":"967c68d72db42af5","type":"link call","z":"65b88f90c5129bc8","name":"","links":["e8031839f82a4fac"],"linkType":"static","timeout":"30","x":340,"y":520,"wires":[["1153c860b9b37a59"]]},{"id":"fe1d85d82704835b","type":"inject","z":"65b88f90c5129bc8","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"4","payloadType":"num","x":180,"y":520,"wires":[["967c68d72db42af5"]]},{"id":"1153c860b9b37a59","type":"debug","z":"65b88f90c5129bc8","name":"expect 40","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"payload","targetType":"msg","statusVal":"","statusType":"auto","x":530,"y":520,"wires":[]}]
2 Likes

I do not think there are any better ways than those mentioned but this is a way that I got round the problem (not my original idea) if looking to store more than 1 function.

Use a function node to keep the required functions, with an inject node set to inject at startup

An example

const utility = (function () {
    'use strict';

    /*
    *   Functions Available
    *
    *       scale,
    *       ordinal,
    *       isString,

    *
    */

    /**
    * 
    * @param   {number}    number  Number of range to scale
    * @param   {number}    inMin   Minimum of range to scale
    * @param   {number}    inMax   Maximum of range to scale
    * @param   {number}    outMin  Minimum of range to be scaled to
    * @param   {number}    outMax  Maximum of range to be scaled to
    * 
    * Example scale(20, 0, 100, 15, 30)    Results in a number in range [0 - 100] being ouput as a number in range [15 - 30]
    * 
    */

    function scale (number, inMin, inMax, outMin, outMax) {
        return (number - inMin) * (outMax - outMin) / (inMax - inMin) + outMin

    } // End Function scale()


    /**
    * Description   
    * 
    * @param  {string | number} number      - The item to check
    * @param  {boolean}         inclusive   - Append ordinal to number if true. Defaults to true
    * 
    * @return {string}                      - The ordinal suffix for provided number. If inclusive is true returns number + ordinal
    *
    * @Example let monthDay = ordinal(21, true); => 21st
    */

    function ordinal(number, inclusive = true) {
        let x = Number(number)
        const ORDINAL = ["th", "st", "nd", "rd"][(x = ~~(x < 0 ? -x : x) % 100) > 10 && x < 14 || (x %= 10) > 3 ? 0 : x]

        return (inclusive) ? number + ORDINAL : ORDINAL

    } // End Function ordinal()


    /**
    * Description   Determines whether or not supplied argument is of type 'string'.
    * 
    * @param  {*}       strValue    - The argument to check.
    * 
    * @return {boolean}             - True if the argument is of type 'string', otherwise false.
    */

    function isString(strValue) {
        return ((typeof strValue === 'string') || (strValue instanceof String))

    } // End Function isString()


    return {

        scale,
        ordinal,
        isString,

    }

}())

global.set('utility', utility, 'file')

Functions are used by calling;

const utility = global.get('utility', 'file')

let possibleString = 'jdf'
let isString = utility.isString(possibleString)

Simple example flow

[{"id":"6d067867ce6a2d06","type":"function","z":"8428164b1645b4b8","name":"Library","func":"const utility = (function () {\n    'use strict';\n\n    /*\n    *   Functions Available\n    *\n    *       scale,\n    *       ordinal,\n    *       isString,\n\n    *\n    */\n\n    /**\n    * \n    * @param   {number}    number  Number of range to scale\n    * @param   {number}    inMin   Minimum of range to scale\n    * @param   {number}    inMax   Maximum of range to scale\n    * @param   {number}    outMin  Minimum of range to be scaled to\n    * @param   {number}    outMax  Maximum of range to be scaled to\n    * \n    * Example scale(20, 0, 100, 15, 30)    Results in a number in range [0 - 100] being ouput as a number in range [15 - 30]\n    * \n    */\n\n    function scale(number, inMin, inMax, outMin, outMax) {\n        return (number - inMin) * (outMax - outMin) / (inMax - inMin) + outMin\n\n    } // End Function scale()\n\n\n\n    /**\n    * Description   \n    * \n    * @param  {string | number} number      - The item to check\n    * @param  {boolean}         inclusive   - Append ordinal to number if true. Defaults to true\n    * \n    * @return {string}                      - The ordinal suffix for provided number. If inclusive is true returns number + ordinal\n    *\n    * @Example let monthDay = ordinal(21, true); => 21st\n    */\n\n    function ordinal(number, inclusive = true) {\n        let x = Number(number)\n        const ORDINAL = [\"th\", \"st\", \"nd\", \"rd\"][(x = ~~(x < 0 ? -x : x) % 100) > 10 && x < 14 || (x %= 10) > 3 ? 0 : x]\n\n        return (inclusive) ? number + ORDINAL : ORDINAL\n\n    } // End Function ordinal()\n\n\n    /**\n    * Description   Determines whether or not supplied argument is of type 'string'.\n    * \n    * @param  {*}       strValue    - The argument to check.\n    * \n    * @return {boolean}             - True if the argument is of type 'string', otherwise false.\n    */\n\n    function isString(strValue) {\n        return ((typeof strValue === 'string') || (strValue instanceof String))\n\n    } // End Function isString()\n\n\n    return {\n\n        scale,\n        ordinal,\n        isString,\n\n    }\n\n}())\n\nglobal.set('utility', utility, 'file')","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":310,"y":1180,"wires":[["42d772a9a7b90346"]]},{"id":"79fbe6817cbb7300","type":"function","z":"8428164b1645b4b8","name":"Test Library","func":"const utility = global.get('utility', 'file')\n\nfunction isString(strValue) {\n    return ((typeof strValue === 'string') || (strValue instanceof String))\n}\n\nlet possibleString = 'jdf'\nlet ifString = utility.isString(possibleString)\n\nmsg.payload = ifString\n\nreturn msg","outputs":1,"noerr":0,"initialize":"","finalize":"","libs":[],"x":350,"y":1240,"wires":[["4797811e083e1e8b"]]},{"id":"9341e34e33086a04","type":"inject","z":"8428164b1645b4b8","name":"Startup","props":[],"repeat":"","crontab":"","once":true,"onceDelay":0.1,"topic":"","x":120,"y":1180,"wires":[["6d067867ce6a2d06"]]},{"id":"42d772a9a7b90346","type":"debug","z":"8428164b1645b4b8","name":"Library Out","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":550,"y":1180,"wires":[]},{"id":"2859e1c24558295a","type":"inject","z":"8428164b1645b4b8","name":"Trigger Test","props":[],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","x":130,"y":1240,"wires":[["79fbe6817cbb7300"]]},{"id":"4797811e083e1e8b","type":"debug","z":"8428164b1645b4b8","name":"Test Output","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","statusVal":"","statusType":"auto","x":590,"y":1240,"wires":[]}]
2 Likes

Thanks @Steve-Mcl ! I saw the link-call node, but doesn't understand it. Now it's clear. A good choice to use a function-node several times. I will store it in my memory :).

The second one from @Buckskin is what I need at the moment. A reusable function inside my function node. This is more - like you show - for a function library. I got the warning:
("Unbekannter Kontextspeicher 'file ' angegeben. Standard-Kontextspeicher wird verwendet.")
Unknown contextmemory 'file'. Default context memory will be used

But I think it is something to do in the settings, so the context-memory is stored in a file and not in the RAM? I have to look for.

Also special thanks for the examples. I tried both and the understanding is much more clear than only a descripton.

I think I can only mark one as a solution, so I don't mark any because both are valid.

This topic was automatically closed 60 days after the last reply. New replies are no longer allowed.