Dumb question about editing a node

Hi,

I installed a node red node the other day and noticed some buggy behavior. I've never worked on a node before, let alone a project in javascript, yet I was able to successfully make changes to the server.js script in the node's directory in /home/pi/.node-red/node_modules/.... I have copied those changes to a fork of the node on github.

However, I then set about seeing if I could add a config option to the companion .js and .html file. All I did was try to add a select list. First I tried a dynamic select list using oneditprepare, but not only did I not see the list appear in the node's edit panel, I don't even see the label or any indication that I've changed the html file at all.

I restarted my whole raspberry pi and still I see no differences. I understand I'm probably not using a proper development strategy, but is there a way to make changes to the node's edit interface show?

The node is still functioning properly and still reflects the changes I made to server.js.

That asked, what's the best way to tinker with a node's code? Should I create a whole separate copy somehow and give it a different name? How do I deploy changes for testing?

OK. I figured out that I needed to empty my browser cache.

I have another dumb question. I'm trying to add a dynamically populated select list in the node's edit pane (the node's name is location) via a RED.httpAdmin.get('/location/:cmd', RED.auth.needsPermission('location.read'), function(req, res){... method in location.js, but to get the values, I need to grab them from the configuration node (named server).

At first, I got an error when I just tried to access the server via node.server (I had guessed it was saved as a member of the location node object based on the constructor code (I'm not well versed in javascript). So I copied the code from the constructor that grabs the server:

node.server = RED.nodes.getNode(node.config.server);

However, when that line executes, it yields this error in the node red error log:

TypeError: Cannot read property 'config' of null

How do I get data from the configuration node for the select list in the location node's edit panel?

Hi Robert,
if you have a look at this discussion, you can see that the client-side code differs a little bit from the server-side code:

var configNode = RED.nodes.node(configNodeId);

There are other people who had the same questions as you in the past (inclusive me ....).
It is always useful to search on Discourse when you are stuck, because there are already quite some code snippets available.

Good luck with it!
Bart

Thanks! I'll take a look. I did search a decent bit for this before asking, but as with my first dumb question, I appear to be completely inept at knowing what terms will reveal the relevant results.

1 Like

OK. So I tried out how I interpreted that thread, but it seems I don't have it quite right. When I tried this:

var configNode = RED.nodes.getNode(req.params.config_node_id);
var server = RED.nodes.getNode(configNode.server);
let circles = server.updateLife360();

I got this:

TypeError: Cannot read property 'server' of null

So I guessed maybe configNode was server and tried this:

var server = RED.nodes.getNode(req.params.config_node_id);
//var server = RED.nodes.getNode(configNode.server);
let circles = server.updateLife360();

and got this:

TypeError: Cannot read property 'updateLife360' of null

How do I get back the server node so that I can use its methods to retrieve data? Is there a good documentation source for this type of thing?

I don't completely understand the entire setup at the moment. Are my assumptions correct:

  1. You are building a node-red-contrib-location node.
  2. In the node's edit panel you have a dropdown, which you want to fill automatically by getting the values from your http admin endpoint on the server.
  3. In the node you also have a config node, which is named "server".
  4. You call the endpoint from the edit panel, and you pass the config node id as input parameter.
  5. On the server side you want to get the config node instance, and call its updateLife360 function.

Is that correct?

It seems that even setting the node returns null:

RED.httpAdmin.get('/location/:cmd', RED.auth.needsPermission('location.read'), function(req, res){
    var node = RED.nodes.getNode(req.params.id);
    node.warn("Does this work?");

I get:

TypeError: Cannot read property 'warn' of null

I was typing up the above and got your response, so here are the answers:

  1. You are building a node-red-contrib-location node.

I'm adding features to an existing disto (fork with testing on my NR instance). It doesn't currently have dynamically populated options in the location node edit panel. I am attempting to add options.

  1. In the node's edit panel you have a dropdown, which you want to fill automatically by getting the values from your http admin endpoint on the server.

I am not sure I understand the question because I don't know this topic deeply enough to be precise about where things are happening. This is my first node red project to mess with the underlying code. So let me point you to my fork: https://github.com/hepcat72/node-red-contrib-life/tree/invld_null_loc_names

My fork has successful edits in it. This attempt to add the dynamic content is only on my laptop.

  1. In the node you also have a config node, which is named "server".

The location node has a member named server, which I understand is the config node defined in server.js.

  1. You call the endpoint from the edit panel, and you pass the config node id as input parameter.

Not sure I understand. The location.html has this call:

    oneditprepare: function() {
        var node = this;

        // Load the available categories from the server
        $.getJSON('location/circles', function(data) {
            // The response is a json hash, containing all the available circles

            $("<option value='sanity'> check </option>").appendTo("#node-input-circle");

            // Show all available circles in the dropdown
            let selectedCircle = "";
            for (const [circleId, circleName] of Object.entries(data)) {
                selectedCircle = circleId;
                $("<option value='" + circleId + "'> " + circleName + "</option>").appendTo("#node-input-circle");
            }

            // When a circle is available, make sure it is selected in the dropdown
            if (selectedCircle) {
                $("#node-input-circle").val(selectedCircle);
                $('#node-input-circle').trigger('change');
            }
        });

And the location.js has this code, which does seem to work. I just can't seem to call the updateLife360 method in server.js:

// Make all the available circle info accessible for the node's config screen
RED.httpAdmin.get('/location/:cmd', RED.auth.needsPermission('location.read'), function(req, res){
    //Does not work:
    var node = RED.nodes.getNode(req.params.id);

    //Does not work:
    node.warn("Does this work?");

    //These static tests work when I use them to update the select list in the edit dialog
    let test = {};
    test['sanity2'] = 'check2';
    let circles_select = {};

    //These to not work:
    var server = RED.nodes.getNode(req.params.config_node_id);
    //var server = RED.nodes.getNode(configNode.server);
    let circles = server.updateLife360();
  1. On the server side you want to get the config node instance, and call its updateLife360 function.

Yes

Corrections: the changes only exist on my rPi. Whenever I get something working my trial and error, I update my repo.

By "this topic", I mean node development and JavaScript in general. My only prior experiences with JavaScript other than some simple function nodes lately was With form validation in the early 2000s.

My goal with these edits is to basically reproduce these IFTTT triggers in its Life360 service (which is going away on Nov 2): first circle member to arrive and last circle member to leave a named place.

I ultimately need to query the server node in 2 contexts: once to get the circles and a second time to get the places in the selected circle.

When do this:

RED.httpAdmin.get('/location/:cmd', RED.auth.needsPermission('location.read'), function(req, res){
   console.log("req.params.id = " + req.params.id);
   ...

Does it write to your log the correct config node id that you expect?

I don't know what the config node ID should be, but I will report back what it says...

Will that output to ~/.pm2/logs/node-red-out.txt?

It says:

req.params.id = undefined

Ugh. Must be something fundamental wrong.

Don't know about pm2. Should appear where all your other Node-RED logs appear...

When you look e.g. at the mqtt nodes, you will see how a config node input element looks like:

image

As soon as you create such a config node (via the dropdown):

image

You can see the node id of that config node in the sidebar:

image

1 Like

Then you need to add some debugging code in your edit panel client side code:

oneditprepare: function() {
     debugger;
}

By adding a debugger statement, you can open the developer tools of your browser (where the flow editor is being displayed) and debug your code. You should pass the correct config node id to your endpoint on the server, otherwise it will never work ...

I suspect that when you say anything to do or is happening "client side", you're referring to code that's in the node's HTML file, e.g. location.html. Assuming that's correct, I then interpret "pass the correct config node id" to mean that in this call in the oneditprepare section: $.getJSON('location/circles', function(data) {, I need to pass the server's ID.

I believe that call (above) is calling this in location.js?: RED.httpAdmin.get('/location/:cmd', RED.auth.needsPermission('location.read'), function(req, res){. I'm not sure where it goes in the call, but even then, I'm not sure:

  1. whether there's some alias to that ID
  2. If I'm passing the actual ID as a string, what the ID is. Because I don't see a correlating "flow" section:

I also assume that "to your endpoint on the server" refers to what's in that location.js code. If I click on the server edit button, I assume this contains the ID I need?:

BTW, I really appreciate your patience and help.

Yes server is the node field that refers to your config node. So I assume you need to pass your config node id as an url parameter:

$.getJSON('location/circles/' + configNodeId, function(data) {

Remark: I assume configNodeId is server.id ....

And then you need to intercept that in your endpoint url:

RED.httpAdmin.get('/location/:cmd/:config_node_id', RED.auth.needsPer... {

Then you should be able to use it in your endpoint as ´req.params.config_node_id´.

I now see that you use in your endpoint code both req.params.id and req.params.config_node_id. But if you don't define them in your url (as :id and :config_node_id) and pass those parameters via getJSON, those values can never be passed to your endpoint on the server ...

My time is up for today ...

THANK YOU! If you have a venmo, I'll buy you a beer! You've been so helpful.

Well, I now have access to the updateLife360 method, but now I have a new problem. It doesn't return the circles. It updates the circles and triggers changes when a member's location has changed. It just doesn't return the circles... I'll ruminate on this via google for awhile.

I'm so close! I created a method in server.js to return the circles. It's just returning something else. Here' the method I'm calling from the client that's in server.js:

    getCircles() {
        var node = this;
        return node.updateSession(function (session) {
            return life360.circles(session)
                .then(circles => {
                    console.log("this gets called after the end of the main stack. the value received and returned is: " + JSON.stringify(circles));
                    return circles;
                })
        });
    }

The data I want is what's printed in that console message, but what the client is receiving is:

{"isFulfilled":false,"isRejected":false}

This is the console message from the server getCirclesMethod:

this gets called after the end of the main stack. the value received and returned is: [{"id":"xxxxxxxx","name":"Rob's Homes","color":"da22d5","type":"basic","createdAt":"1379025809","memberCount":"4","unreadMessages":"0","unreadNotifications":"0","features":{"ownerId":null,"skuId":null,"premium":"0","locationUpdatesLeft":0,"priceMonth":"0","priceYear":"0","skuTier":null}},{"id":"xxxxxxxx","name":"Rob's Family","color":"f05929","type":"basic","createdAt":"1393782349","memberCount":"4","unreadMessages":"0","unreadNotifications":"0","features":{"ownerId":null,"skuId":null,"premium":"0","locationUpdatesLeft":0,"priceMonth":"0","priceYear":"0","skuTier":null}},{"id":"xxxxxxxx","name":"Leach Family","color":"7f26c2","type":"basic","createdAt":"1393785316","memberCount":"6","unreadMessages":"0","unreadNotifications":"0","features":{"ownerId":null,"skuId":null,"premium":"0","locationUpdatesLeft":0,"priceMonth":"0","priceYear":"0","skuTier":null}}]

How do I get that data back to the client?

Perhaps you have found the answer meanwhile already?
I assume you need to do this:

RED.httpAdmin.get('/location/:cmd', RED.auth.needsPermission('location.read'), function(req, res){
   ...
   // Based on the http req(uest) you write your json to the http res(ult)
   res.json(circles);
   ...
})

I have not. Still stuck there. I was just speaking with a work colleague about it (who also does not know javascript client/server stuff). He's as baffled as I am, though he did inspire me to question what I thought was the client and what was the server. I think that the problem might be that this whole paradigm I'm following (that the original author created) was for the client being "server.js" and the server being "Life360.com".

Because clearly, server.js has the data (because I can print it to the raspberry pi's node red log file). Hmmm... location.js also prints to the log file on the rPi. I was about to posit that it was running in the browser on my laptop. The web inspector of my browser does have a copy of that script, but if it's printing to the rPi log, maybe it's running server-side?

I feel fairly confident that the oneditprepare stuff is running on the browser and that's what's calling the method in location.js...

I don't know. My head's kind of spinning. My colleague suggested adding a breakpoint and stepping through things. I've only ever done that with java code before. Maybe that would reveal something. I may try to look up how to do that in javascript tonight. I should also push out what I've got thus far so I can share it.