Dumb question about editing a node

What if you just modify this first attempt to get the data from the Promise, does it help at all? Something like:

RED.httpAdmin.get('/location/:cmd/:id', RED.auth.needsPermission('location.read'), function(req, res){
    const node = RED.nodes.getNode(req.params.id);
    node.server.getCircles().then((circles) => {
        let circles_select = {};
        for (let circle of circles) {
            circles_select[circle.id] = circle.name;
        }
        if (req.params.cmd === "circles") {
            // Return a hash of all available circle IDs/names
            res.json(circles_select);
        }
      }).catch((err) => console.log(err));
    }

That's nice and clean and explains a lot that seemed like magic to me. I will definitely try that next time I touch the code. I wasn't inclined to think that using things like res and req like that inside the asynchronous .then would work because I honestly have no clue how that's connected to the code in the html file that has that anonymous function(data) method. I had tried using other variables inside a .then, but I was still in a synchronous mindset. Either way, it was clear to me that the code in that anonymous function(data) function (defined in the html file) runs on the value you supply to res.json(). So is res.json() a call to function(data) (the anonymous function that's supplied to $.getJSON)? Because the html that that constructs clearly operates on the contents of the circles_select hash(/associative array(?)). If res.json is a call to that function, then this makes sense. It's just baffling to me why those are connected, because none of these functions involved have the same number of parameters. They're visually different, which makes it hard to follow what's going on. It's like the arguments to function calls and the functions' parameters are different. It's rather disorienting.

I in turn have slight issues following your message as there's no line breaks :wink:

But anyway. it should help you understand what's going on if you read Express documentation which Node-RED uses internally for HTTP API's.

The parameters request and response come with an incoming request passed by the Express framework. Response parameter acts as a callback so the client sending the request will only get a response from Express until it's called. At least this is how I would simplify it.

That's really helpful. And yeah, I'm not the best communicator, but thanks for your patience.

Your code works! At first, I thought it wasn't working, but I learned something that seems to explain why it didn't initially. My hacky code does the same thing.

Neither attempt works until there's been a deploy of the location node. I figure that's because there's no real node returned from RED.nodes.getNode(req.params.id); until after a deploy. Or, perhaps it's because the node's constructor hasn't run? - because the error I see in the log is TypeError: Cannot read property 'server' of null.

So I tried calling node = new LocationNode(req.config); in a conditional, but I get the error:

TypeError: Cannot read property 'id' of undefined

referring to this line (line 19, column 23) in the constructor:

            RED.nodes.createNode(this, config);

So I take it that 'id' is referring to the this. Am I calling the constructor incorrectly - or am I calling it inappropriately (like in the wrong context)?

Can't help with this as I haven't developed any nodes myself, I just know JavaScript. But I'm pretty confident you'll figure it out sooner or later yourself. :slightly_smiling_face:

1 Like

Thanks for the vote oC.

So I found this post which is pretty much about the same thing and I tried to see how it's done. Basically, they're looking up ports using the "server". Following the code, it looks like the ports come from here:

...and ports are returned by serialp. I'm guessing that serialp is the analog of my server. However, that serialp "server" via a require. There's no constructor in the file. I read that require is a node red function to basically import modules.

What I don't understand is what is list() here returning?:

My take-away is that I need to provide access to the server in the same way serialp is providing access in the example and that I need to have a function(?) akin to list that returns circles? Is that on the right track? Because trying to instantiate the location class isn't setting the server data member inside the location object.

Yes this code is now on the server/backend side. So “all” it now needs to do is whatever you need to do to get your list of circles and then set that json object into res.json() to return it. (And handle any errors that may occur if you fail to get anything.

Ah! I figured out how to access the server node from an undeployed node's edit interface. I had to look at Bart's repo for the thing I was missing:

I just had to access the value from the form.

Still making progress. Slowly, but surely.

It's a brave new world. Lot to learn but rewarding when the light bulb lights up!

1 Like

It took me awhile to get the next select list to autopopulate (people), but I just finished that and it's updating whenever the circle selection changes now. Next is places. Then I'll need to figure out how to save the selections. Places should go rather quickly because it's the same basic process as people was.

Yep. Just finished places. So now there's:

  1. Save edit selections
  2. Implement logic to trigger node output when person, place, and event type all match the detected event

I will likely squash the current message type or maybe make a different node.

I'm also a bit wary of the possibility that multiple copies of the same node with different settings may cause problems. I've had issues with copies of other node types.

Another dumb question. I've been editing code inside ~/.node-red/node_modules/node-red-contrib-life/nodes and restarting node red to see the effects. It seems this is insufficient for saving the new inputs I've been adding.

At first I thought I needed to do something explicit to save selections from the edit dialog, but I looked at the pre-existing inputs before I started changing things and there appears to not be anything that saves those inputs, yet changes are saved.

What do I need to do to save selections from the edit dialog?

The doc I've read talks about installing a node, but I've been tweaking an already installed node. I know I've jumped right in the middle. How is development normally done?

Hmmm, grepping files, it looks like the settings are saved in flows_raspberrypi.json. I guess I need to explicitly set the select lists based on the values.

OK. I figured out that my confusion before was due to change event handlers were being triggered when I didn't expect them to and the fact that (and correct me if I'm wrong):

New selections (if they exist) are in: $("#node-input-whatever").val();
Previous selections (if they exist) are in: node.whatever

And I was also seeing some asynchronous settings of the selected items because I was populating the select list and setting the selected item in different threads.

I thought I had worked that all out and moved all manipulations (clearing, populating, setting, and triggering) into the callback function, however now the select list selections I'm setting are not getting set and the select lists always present with no selected item (no default and no previously selected item and I can't seem to figure out why.

Of the 3 lists I'm working on currently (circles, places, and people), the one that does have a selected item is the circles select list. The other 2 are populated but don't show their previously set values. And another thing that confuses me is that making selections doesn't usually cause the deploy button to activate.

So let me ask a few questions:

  1. When the edit dialog comes up, the only things that should be saved WRT select lists is the value of the previously saved selection. The other items in the list need to be re-populated and the previously selected one has to be explicitly selected. Is that all correct?
  2. What has to happen in order for the deploy button to activate? Why can I make select list selections and click done and the deploy button is still grayed out?
  3. When is a call to an input's change function triggered other than calling ...trigger('change')? And why would it be called before the running of the top level code in oneditprepare?
  4. Does ...trigger('change') need to be called to show the previously selected select list item?

According to my console prints in the browser's web inspector, I'm setting values in $("#node-input-whatever").val('value here') and those values are in the list I populated before setting the selection, so I don't understand why the lists initially show no selection. Do I need to change like, the default or something? I wouldn't think I do because my simpler node-input-event select list and my node-input-circle select lists show the correct selections based on my calls to .val(...).

One thing I suspect may be a source of one problem is that in oneditprepare and in the change function for circles, I'm not only making the server call to get the values to populate the circles select list and set its selection, but I'm also making 2 other server calls to populate the people and places given the selected circle. I.e...

        oneditprepare: function() {
            var node = this;
            var configNodeId = $("#node-input-server").val();
            $.getJSON('location/circles/' + node.id + '/' + configNodeId + '/none/none', function(data) {

                ...

                $.getJSON('location/places/' + node.id + '/' + configNodeId + '/' + circleId + '/' + placeId, function(data) {

This does populate everything correctly. It's just the selections and the deploy button activation that doesn't seem to work...

OK. I've gotten places and circles to fully work, but it seems overly complex. I'll post what I've got once I'm done to see if you guys have any tips for simplification.

Congrats! I'm guessing the reason you haven't been receiving as much help as you could is the fact you seem to be just hacking away some existing node code for your own purposes.

Two questions to clarify the above:

  1. Are you working on a general purpose improvement to an existing contrib node that others could also benefit from?
  2. If so, are you planning on contributing the improvements to the repository of said node or perhaps thinking of forking it to another name?

If planning on contributing back to the existing code, I would hope you'd get better assistance by creating a feature request issue on its repository and letting the developer know what you've already accomplished.

Edit: Is this https://flows.nodered.org/node/node-red-contrib-life the node you're working with? If it is, then what I said doesn't apply as it doesn't seem to have published its repository URL.

What I see is that it's source code is GPL-3.0 licensed, so if creating a public fork, you'd need to be careful to include the licence, copyright etc. information required a GPL-3.0.

Yeah. I'm sure that's not the only reason. My questions haven't been very well constructed.

  1. Yes.
  2. Yes. I have a fork. I branched and created a PR from that branch into my own master and tagged the original author for review. The branch currently only has the bug fixes. Basically, I was testing the waters to see if he'd like a PR to his repo. I'd created a couple issues on his repo, but he responded that he didn't have time to work on it, which is why I decided to take a stab at it.

And yes, that is the node. It does exist on github. I thought I'd posted the link, but maybe I haven't:

Rob

1 Like

Is there a way to distinguish whether a change has been triggered by explicit user selection versus trigger from a call to $("#node-input-place").trigger('change') (from node-input-circle's change code) or do you have to make the change to node-input-place inside the circle change code and only trigger place (so it can trigger subsequent changes) when you actually change its selected value? Or should I change the children and grandchildren in the change code of the parent?

Because I'm currently triggering in every case and determining what to do to each select list based on what is different when looking at the undeployed value and the selected value of "myself" and my "parent", and when neither are different or both are different, the only way I can know what to do is to know how the change was triggered. E.g.: the user changes the circle from 'any' to a specific circle, which triggers place to repopulate its select list (adds places based on the circle), then they change the circle back to 'any'. The place change code could base what it does differently based on whether it was a place selection change or a circle selection change, but since both match the original pre-deployed values, it doesn't have enough information to know that it needs to clear out its list of places.

I suppose I could additionally check the length of the select list options, but I thought I'd ask if there's a way to know how the change was triggered... What's the conventional way of dealing with this? How should I be doing it? I suspect this is one reason why my code seems too complex.

Does this might help you?

That's exactly what I was looking for! Thanks!

1 Like