Include functions file in function node possible?

Hi!

I made a function that removes spaces, sets to lower case and split on , of the msg.payload
Now every time i need to do this i copy this function into my function node.
But is it possible to put this in a file together with other functions and then include the file into the function node? So i do not need to copy paste it every time in a new function node?

you could google for "javascript import" or "require"

Thanks @krambriw
I found require but i found on the forum that require isn't working
And i could not get i to work

Hi @RogierQ

no it is not possible to import or require external files into your Function node, despite what @krambriw suggests.

There is no built-in way to store your Function node code in external files. There is a contrib node that lets you do that, but it is very old and has long since fallen behind the functionality of the core Function node - so I really wouldn't recommend it.

Thanks @knolleary!

What a pitty...
Any plans to implement this in the future?
Would save a lot of copy pasting the same things

It is a pain point we're aware of for a number of reasons. For example, if you are using the projects feature, the Function code appears in a single line in the JSON which makes it very hard to see the changes when comparing versions.

So it is something to address in the future. You do lose the portability of having your flows in a single file, but there are trade-offs to be made.

Clear, thanks
I continue copying for now! :wink:

Hi .. you could always create a Subflow from that piece of logic so instead of copy/pasting it you can drag and drop the subflow where you need it .. dunno if its any faster or cleaner for a few lines of code

Subflows - Node-RED Essentials

Sorry for misleading :wink:

Haha no need to say sorry for trying to help :wink:

Actually, there is a work around but it requires you to put the code into an external module file rather than into a flow.

I do that with some utility functions. You then require the module in your settings.js file and it is available as a global. so you can global.get it in your function nodes.

You can use a Function Node as a repository for often used functions. This is an example of how I have done it;

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

// recursive function to clone an object. If a non object parameter
// is passed in, that parameter is returned and no recursion occurs.
function cloneObject(object) {
    if (object === null || typeof object !== 'object') {
        return object;

    }

    var newObject = object.constructor(); // give temp the original obj's constructor

    for (var key in object) {
        newObject[key] = cloneObject(object[key]);

    }

    return newObject;

}

// add properties of Object 2 to Object 1
function concatObject(object_1, object_2) {
    var properties;
    for ( properties in object_2 ) {
        if ( object_2.hasOwnProperty(properties) && !object_1[properties] ) {
            object_1[properties] = object_2[properties];

        }

    }

}

// Returns a number as a string wth a specified number of preceding zeros
function padZero(number, padSize = 0) {
    return number.toString().padStart(padSize, 0);
    
}

function ordinal(number, inclusive = true) {
    let x = number;

    if (inclusive) {
        return number + ["th", "st", "nd", "rd"][(x =~~ (x < 0 ? -x : x) % 100) > 10 && x < 14 || (x %= 10) > 3 ? 0 : x];

    } else {
        return ["th", "st", "nd", "rd"][(x =~~ (x < 0 ? -x : x) % 100) > 10 && x < 14 || (x %= 10) > 3 ? 0 : x];

    }

}

function isArray(myArray) {
    return myArray.constructor === Array;

}

/**
* Represents a search through an array of objects.
* @function findArrayProperty
* @param {Array} array - The array you want to search through
* @param {string} [property] - The property name to search
* @param {string} key - The key to search for
* 
* Return: The object where the key was found, or 'false' if not found
*/

function findArrayProperty(array, property, key){
    // Set foundProperty to default
    var foundObject = false;    

    for (var i = 0; i < array.length; i++) {
        if (array[i][property] === key) {
            foundObject = array[i];

        }

    }

    return foundObject;

}


return {
    cloneObject: cloneObject,
    padZero: padZero,
    concatObject: concatObject,
    ordinal: ordinal,
    isArray: isArray,
    findArrayProperty: findArrayProperty

};

}());

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

(The functions are possibly not the best but you get the idea). I use an Inject node to initialise the global.set. When you want to use one of the functions you just include let utility = global.get('utility', 'file'); and then reference the required function as (for example) utility.padZero(60, 3) etc.

This is not exactly what you were after - an external file - but does only have to be added once for each Node-red instance. I usually put the two nodes in the first Flow. Not ideal but it works.

Hope I understood the question correctly and that this work around works (Not my original idea I hasten to add)

Sounds good but also a little difficult how to do.
Can you explain a little bit more please?

This is also a simple example to start from

Create a file demo.js and put it in /home/pi with this content:

module.exports = {
    firstName: 'James',
    lastName: 'Bond'
}

Open settings.js in .node-red folder and add this line to the section

    functionGlobalContext: {
        demo:require('/home/pi/demo.js'),

Restart Node-RED

Create a simple flow like below

Click on the inject button and you should see the name of the agent

Here is some more info how to export functions and others

2 Likes

That's what I had in mind yes. However, I would use a relative reference in settings.js as that lets you move the whole folder and everything would still work.

    functionGlobalContext: {
        demo:require('./demo.js'),
1 Like

@Buckskin Good Idea.
Now I'm using node-red-contrib-config to store only data....
But if I store an object ... :slight_smile:

  1. functions (methods) are grouped together, end with copy/paste
  2. no external files

I don't like the 'standard' method: If I share the flow, the instructions about setting.js, the library file... too much :frowning:

I will try it soon, Thanks
m.s.

Strange but it works:
The global variable 'bigobj': (

{
some[]more[]{dx:"here"} , // array of objects with  array of objects. The 'Config' node requires JSON.
.....
 "get_dx": "function( d,p) {  return this.some[d].more[p].dx; }"
}

i.e. a JSON version of a function.... (all in one line :frowning: )

USE:
Inside a function node:

var bigO = global.get("bigobj");

function callJSONMethod(obj, fname, a, b, c, d){
    // see: https://stackoverflow.com/questions/49125059/how-to-pass-parameters-to-an-eval-based-function-injavascript
var wrap = s => "{ return " + obj[fname] + " };" //return the block having function expression
var func = new Function(wrap(obj[fname]));
return func.call( null ).call( obj, a, b, c, d); //invoke the function using arguments
}

msg.payload =callJSONMethod(bigO, "get_dx", 2, 2); 
return msg:

returns "here", unbelieve!
i.e I must add the function callJSONMethod() to any function node using bigobj.....
maybe acceptable.

Best regards

1 Like