Indents with IF, ELSE and other stuff

I'm going nuts trying to get this new bug worked out.

I know I am making my life difficult by using a function node and not ..... using the stock nodes.

I am doing this to try and get a better understanding of how to structure code.

So I do this bit of code - which I get:

if (something == something_else) {
    code here
}

But what about when I have an else in there?

if (something == something_else) {
    code here
} else
    if (blah ==blah_blah) {
        other code here
        } else
            if (and_again == what_ever) {
                and so on
             }

That seems excessive indenting.

if (something == something_else) {
    code here
} else if (blah ==blah_blah) {
    other code here
} else if (and_again == what_ever) {
    and so on
}

When you close a } you start again where to opening { line starts.
Whether on same line or below is a preference

2 Likes

Thanks.

I must have missed that trick.

The curly braces in JavaScript indicate a "code block". They create a new "context" as well for things like const and let. You can even use them stand-alone for the odd occasion you might want to constrain some variables - though it is generally best to use a function for that.

So any JavaScript statement, like if, that takes only a single line of code, has to use a code block in order to execute multiple lines of code. You may see this with inline arrow functions as well where, if you only need to execute a single line of code, you can omit the braces.

chrome_wU9MClvohf

1 Like

I just hope I have the right version that allows that.

Thanks Steve.

If the blocks have return in them, is the else helpful or not?

The return being either that or a bigger version that sends stuff.....
(just to cover the bases of ambiguity)

Oh, this is something else that is tripping me up.

(example)

        //==============================================================================

        //  Error acknowledgement message.
        if (msg.payload == "ERROR_ACK") {
            msg1 = { "payload": '<font color = "lime" i class="fa fa-exclamation-circle fa-2x"></i></font>', "topic": device + "/On-line", "device_ID": device, "background": "lime" }
            context.set("LOCK", 0)
            return msg1
        } else

            //==============================================================================

            //  Health message received.
            if (msg.payload == "HEALTH") {
                //  Working
                msg1 = { "payload": '<font color = "red" i class="fa fa-heartbeat fa-2x"></i></font>', "topic": device + "/Unhealthy", "device_ID": device }

                context.set("LOCK", 1)          //  Lock changes to node mode until reset.
                node.status({ fill: "red", shape: "dot", text: "HEALTH ALERT" })
                return msg1
            } else

                //==============================================================================

                if (msg.payload == "HEALTH_ACK") {
                    msg1 = { "payload": '<font color = "lime" i class="fa fa-heartbeat fa-2x"></i></font>', "topic": device + "/On-line", "device_ID": device, "background": "lime" }
                    context.set("LOCK", 0)
                    return msg1
                }

The //============ to break up the blocks.

How would I do it in this new format?

Best practice is not to include a return in a final else. It is better to simply place that after the if blocks. It isn't a big deal though as either way works, it is simply that the best practice way helps avoid some hard to track down code typo's and is easier to read.

//  Error acknowledgement message.
if (msg.payload == "ERROR_ACK") {

    msg1 = { "payload": '<font color = "lime" i class="fa fa-exclamation-circle fa-2x"></i></font>', "topic": device + "/On-line", "device_ID": device, "background": "lime" }
    context.set("LOCK", 0)
    return msg1

} else if (msg.payload == "HEALTH") {
    //  Health message received.

    //  Working
    msg1 = { "payload": '<font color = "red" i class="fa fa-heartbeat fa-2x"></i></font>', "topic": device + "/Unhealthy", "device_ID": device }

    context.set("LOCK", 1)          //  Lock changes to node mode until reset.
    node.status({ fill: "red", shape: "dot", text: "HEALTH ALERT" })
    return msg1

} else if (msg.payload == "HEALTH_ACK") {

    msg1 = { "payload": '<font color = "lime" i class="fa fa-heartbeat fa-2x"></i></font>', "topic": device + "/On-line", "device_ID": device, "background": "lime" }
    context.set("LOCK", 0)
    return msg1

}

// Are you missing a final return statement? Or don't you want to return anything if the 3 conditions are not met?

Note that consistency of code layout, while not necessary, massively helps you scan and parse code. It helps reduce mental stress and lets you train yourself to spot many errors at a glance.


Some other thoughts.

I prefer to use single quotes for strings because on the UK keyboard, you don't have to press shift. You should also use either double or single quotes rather than a mix which distracts from reading.

Similarly, it is generally best not to put object property names in quotes unless you are using a non-standard name such as one with a dash or space. Obviously though, when using JSON rather than JavaScript, double quotes are mandatory.

1 Like

I fully agree with you on that and I am still so messed up with it.

With the example you posted:
Yes, there would be more testing happening further down so that seems to be what I want.

Alas this is about 416 lines of JS that are driving me crazy.

I also realise that I am going to have to either reverse some of the testing or put returns in in the code.

I am good at digging myself into holes of code.

:wink:

Thanks.

The //============ lines...

Thoughts on how I put them in with the else if( ) structure?

Once there are more than 3 possibilities, i usually get confused and would go for a switch case statement, or an object lookup.

// AI generated:

// Define message configurations in a lookup object
const messageDefinitions = {
    "ERROR_ACK": {
        topicSuffix: "/On-line",
        payloadHTML: '<font color="lime"><i class="fa fa-exclamation-circle fa-2x"></i></font>',
        backgroundColor: "lime",
        actions: [
            { type: "contextSet", key: "LOCK", value: 0 }
        ]
    },
    "HEALTH": {
        topicSuffix: "/Unhealthy",
        payloadHTML: '<font color="red"><i class="fa fa-heartbeat fa-2x"></i></font>',
        backgroundColor: null,
        actions: [
            { type: "contextSet", key: "LOCK", value: 1 },
            { type: "nodeStatusUpdate", status: { fill: "red", shape: "dot", text: "HEALTH ALERT" } }
        ]
    },
    "HEALTH_ACK": {
        topicSuffix: "/On-line",
        payloadHTML: '<font color="lime"><i class="fa fa-heartbeat fa-2x"></i></font>',
        backgroundColor: "lime",
        actions: [
            { type: "contextSet", key: "LOCK", value: 0 }
        ]
    }
};

// Get the message configuration
const config = messageDefinitions[msg.payload];

if (config) {
    // Create the response message
    const msg1 = {
        payload: config.payloadHTML,
        topic: `${device}${config.topicSuffix}`,
        device_ID: device
    };
    
    if (config.backgroundColor) {
        msg1.background = config.backgroundColor;
    }

    // Execute actions defined in the configuration
    config.actions.forEach(action => {
        switch(action.type) {
            case "contextSet":
                context.set(action.key, action.value);
                break;
            case "nodeStatusUpdate":
                node.status(action.status);
                break;
        }
    });

    return msg1;
} else {
    // Handle unknown message payload
    node.warn("Unknown message payload:", msg.payload);
}

It becomes more reusable.

1 Like

Believe it or not, I see words, but the whole idea is foreign to me.

Sorry.

I will try to scope it and study it so I get a handle on what you mean.

Alas (IRL) I am also being flooded with things and ..... structures, procedures and all that sort of stuff.. And the terms are doing my head in.

So that is (probably) not helping me here/now.

A couple of things that might help.

Consider putting some of that code into functions - I know it seems weird to have functions inside a function node :smile: but it is often helpful to break up the code into smaller, logical chunks.

Also, you might want to consider setting up the full VS Code app. If you do, create a testbed folder to work in, initialise it as an npm module using npm init -y, then install eslint and some of its exensions. And finally add a file called eslint.config.mjs perhaps with a config something like this:

// @ts-nocheck
/**
 * https://www.npmjs.com/search?q=eslint-config
 * https://www.npmjs.com/search?q=keywords:eslint
 *
 * npm init @eslint/config@latest -- --config eslint-config-standard
 * https://eslint.org/docs/latest/rules
 *
 * npx @eslint/config-inspector@latest
 * npx eslint --debug somefile.js
 * npx eslint --print-config file.js
 */

import globals from 'globals' // https://www.npmjs.com/package/globals
// @ts-ignore
import pluginImport from 'eslint-plugin-import' // https://www.npmjs.com/package/eslint-plugin-import
import pluginPromise from 'eslint-plugin-promise' // https://www.npmjs.com/package/eslint-plugin-promise
import jsdoc from 'eslint-plugin-jsdoc'// https://github.com/gajus/eslint-plugin-jsdoc
import node from 'eslint-plugin-n' // https://www.npmjs.com/package/eslint-plugin-n, node.js only
import stylistic from '@stylistic/eslint-plugin' // https://eslint.style
import js from '@eslint/js'

/** @type {import('eslint').Linter.Config[]} */
const conf = [
    js.configs.recommended,
    jsdoc.configs['flat/recommended'],
    pluginPromise.configs['flat/recommended'],
    pluginImport.flatConfigs.recommended,
    ...node.configs['flat/recommended-script'],

    {
        plugins: {
            '@stylistic': stylistic,
        },
        rules: {
            'jsdoc/check-alignment': 'off',
            // "jsdoc/check-indentation": ["warn", {"excludeTags":['example', 'description']}],
            'jsdoc/check-indentation': 'off',
            'jsdoc/check-param-names': 'warn',
            'jsdoc/check-tag-names': ['warn', {
                definedTags: ['typicalname', 'element', 'memberOf', 'slot', 'csspart'],
            }],
            'jsdoc/multiline-blocks': ['error', {
                noZeroLineText: false,
            }],
            'jsdoc/no-multi-asterisk': 'off',
            'jsdoc/no-undefined-types': ['error', {
                'definedTypes': ['NodeListOf', 'ProxyHandler'],
            }],
            'jsdoc/tag-lines': 'off',

            '@stylistic/comma-dangle': ['error', {
                'arrays': 'only-multiline',
                'objects': 'always',
                'imports': 'never',
                'exports': 'always-multiline',
                'functions': 'never',
                'importAttributes': 'never',
                'dynamicImports': 'never',
            }],
            '@stylistic/eol-last': ['error', 'always'],
            '@stylistic/indent': ['error', 4, {
                'SwitchCase': 1,
            }],
            '@stylistic/linebreak-style': ['error', 'unix'],
            '@stylistic/lines-between-class-members': 'off',
            '@stylistic/newline-per-chained-call': ['error', {
                'ignoreChainWithDepth': 2,
            }],
            '@stylistic/no-confusing-arrow': 'error',
            '@stylistic/no-extra-semi': 'error',
            '@stylistic/no-mixed-spaces-and-tabs': 'error',
            '@stylistic/no-trailing-spaces': 'error',
            '@stylistic/semi': ['error', 'never'],
            '@stylistic/quotes': ['error', 'single', {
                'avoidEscape': true,
                'allowTemplateLiterals': 'always',
            }],

            'new-cap': 'error',
            'no-else-return': 'error',
            'no-empty': ['error', {
                allowEmptyCatch: true,
            }],
            'no-unused-vars': 'off',
            'no-useless-escape': 'off',
            'no-var': 'warn',
            'prefer-const': 'error',
        },

        settings: {
            jsdoc: {
                mode: 'jsdoc',
            },
            node: {
                version: '18.0.0',
            }
        },

        languageOptions: {
            sourceType: 'commonjs',
            // Will be overridden by the n plugin which detects the correct node.js version from package.json
            ecmaVersion: 'latest',
            // Node.js globals are provided by the n plugin
            // globals: globals.browser,
        },
    },
]

export default conf

Then you can paste you code into a file and eslint will check it for you.

Some of the rules listed are my preferences but they are easily adjusted.

I love what you are suggesting, but fear this is still above me.

Excuse the digression here:
I just got a machine updated to a newer version of NR than I was using.
V4.

Maybe I've asked, but maybe something has happened since I asked and this has been fixed.
My MAIN MACHINE (this one) is a NUC and has room to play with stuff.

But the debug window - even if set to THIS TAB - keeps wiping itself because of stuff on OTHER TABS.

Has this been addressed with the latest version?

Only if YES, then I could do a lot of it on THIS MACHINE and not flog the poor RasPis with all this testing.

If in doubt or when confused, I would always fall back to the Mozilla Developer Network (MDN). This has reasonable examples of a lot of of the JavaScript (and HTML and CSS) statements, functions and API's. It also has some pretty good tutorials and starter articles.

This stuff takes time to sink in, so don't let it get to you! :smile:

One thing that I will regularly do with my code is that, when I go back and read it after a break, if I'm struggling to work out what it is doing, I know I need to work on refactoring it to make it simpler to read. Often, we write code in a hurry and so it evolves messily, that might sometimes be fine as a one-off, but if we have to come back to it, we may need to tidy and simplify it.

You should turn off or even delete unneeded debug nodes, on other tabs.
Even though you filter to the current tab, these other messages are still filling up the "buffer" and will cause messages to be removed.

1 Like

Yes, I dug myself into this situation.

This machine has.....
Well, if I go to list tabs and the scroll window....

And they are not (all) small flows.

I am putting a lot of eggs in one basket, I know.

You might consider having a 2nd Node-RED instance that you use for testing and development. That way, you protect your "live" flows.

1 Like

I may have to learn how to do that too.

Mind poking me with how to do that?

NR 3.1.5

Well, I do my dev/test stuff on my desktop and keep my server "clean". Well clean-ish.

The how depends on whether you want to be able to run a different version of node-red for dev/test purposes. Personally, I DO want to be able to do that. But if you don't, things are a little easier to set up.

Because my dev/test instance is on my desktop, I don't bother to run it automatically, I'm using Windows 11 and I start it using PM2 with a configuration that auto-restarts when key files change. Just a convenience though, you don't need to do that particularly but when developing custom nodes, you often do lots of small iterative changes so manually restarting NR is a bit of a pain. :slight_smile:

Let me know how/where you'd like to run your dev/test version and I can give some more specific help.

1 Like

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