How to create a sidebar tab

Hi, I currently want to create a tab in the sidebar, but I have a hard time.

If you look at the sidebar on the right side of Node Red, there is a dashboard button.
The button contains a tab(layout, site, theme), and I want to create an additional tab.
If anyone knows, I'd appreciate it if you let me know.

Hi Jisu,
Some time ago I created a sidebar tab, and indeed it was not easy the first time ...
Will try to find some time tonight to describe the basic steps, if nobody else has done it meanwhile...
Bart

Hi @zl2su

can you describe a bit more what you want to do with the sidebar you wish to create?

You mention the dashboard sidebar with its layout/site/theme tabs. You say you want to add an additional tab - do you mean you want to add an extra tab to the dashboard sidebar (next to the layout/site/theme tabs), or a new sidebar entirely (like we have the Debug/Info/Help/Dashboard tabs)?

oh, thanks for responding.

I want to add a new sidebar entirely (like we have the Debug/Info/Help/Dashboard tabs).
In fact, I want to make both later.

oh really? Thank you

It isn't documented, but you can see how the existing tabs are added if you search through the source code for RED.sidebar.addTab.

For example, here is where the Info sidebar tab is added: https://github.com/node-red/node-red/blob/master/packages/node_modules/@node-red/editor-client/src/js/ui/tab-info.js#L110

Oh, thank you

But, can I change the location of the input and output ports in the html file?

No, you cannot customise the appearance of a node without changing all of the editor code directly.

Hey Jisu,

I have only create one sidebar node (see node-red-contrib-xterm) until now, and that is already quite some time ago. So I might have forgotten some stuff, but I'm sure others will correct me where needed ...

Some facts about such a sidebar tab:

  • It depends on a single configuration node.

  • Keep in mind that the user can remove that config node without you knowing it.
    [EDIT] For example the config node of my xterm terminal sidebar can be found here:

    image

    The user can remove the config node there. Or the user can open your config node's config screen (after double clicking on it) and change the data of your config node. And so on ... Just to say that your sidebar panel screen needs to be aware of this, and always be in sync with these config node updates!

  • So you need to ensure that there exist always a single instance of that config node. Which means you have to create that config node instance (if it doesn't exist yet) in the following cases:

    • When the flow editor is being opened, i.e. when your node is being added (by Node-RED) to the palette.
    • Every time you need to read data from that config node, or to save data into that config node.
  • For a normal node you only have to specify one single data template, which specifies how the (config) node's config screen looks like. However now you will have to specify extra a second data template, to specify how the sidebar panel looks like.

The javascript file looks rather similar to that of a normal node, but the html file is a bit different (based on the list of facts we just described). Below you can find an example of an html file, with lots of explanation inside. This code might contain syntax/logical errors, but it is just to give you an idea of how I think it works:

<script type="text/javascript">    
    (function ($) {
        // Keep a reference to the config node, the last time you have found it ...
        var myConfigNode = null;
        
        // Function to ensure that a single instance of myConfigNode (still) exists.
        // Call this function always before you try to access the config node!!!!
        // Caution: this function cannot be called from onpaletteadd, because then the RED.nodes are not filled yet!!!
        function ensureMyConfigNodeExists() {
            // If we have found the config node previously, check if it has been deleted by the user meanwhile
            if (myConfigNode !== null) {
                var configNode = RED.nodes.node(myConfigNode.id);
                if (configNode === null) { myConfigNode = null; }
            }
                
            // If not found previously, check if it has been created meanwhile
            if (myConfigNode === null) {
                var configNodes = [];
                RED.nodes.eachConfig(function(configNode) {
                    if (configNode.type === 'my_unique_config_node_name') { 
                        configNodes.push(configNode); 
                    }
                });
                
                // Make sure we only have 1 config node
                while (configNodes.length > 1) {
                    var configNode = configNodes.pop();
                    RED.nodes.remove(configNode.id);
                    RED.nodes.dirty(true);
                }
                
                // When we found a config node, let's use that one
                if (configNodes.length === 1) { myConfigNode = configNodes[0]; }
            }
            
            // When the config node doesn't exist yet, let's create a new instance of it
            if (myConfigNode === null) {
                myConfigNode = {
                    // Ask Node-RED to generate a new unique node id for your new config node
                    id: RED.nodes.id(),
                    // Ask Node-RED the type of your node
                    _def: RED.nodes.getType("my_unique_config_node_name"),
                    // Specify your node type
                    type: "my_unique_config_node_name",
                    // This config node is not used by another node, so it will appear with status "unused" in the "config nodes" sidebar.
                    // But that is very confusing, since the config node is used by your sidebar screen.
                    // To solve that set 'hasUsers' to false (see https://github.com/node-red/node-red/blob/master/CHANGELOG.md#0161-maintenance-release).
                    // Note that the 'hasUsers' needs also to be specified in the RED.nodes.registerType statement!
                    hasUsers: false, 
                    users: [],
                    label: function() { return this.name || "My node label"},
                    name: "My node name",
                    // Specify here all your config node properties here (with their default values)
                    // ...
                }
                
                // Add the new config node to the collection of Node-RED nodes
                RED.nodes.add(myConfigNode);
                
                // Make sure the "Deploy" button becomes active, by letting Node-RED know that something has changed
                RED.nodes.dirty(true);
            }
        }
        
        function sidebarResizeEventHandler() {
            // Resize the widgets inside your sidebar screen if required
		}

        RED.nodes.registerType('my_unique_config_node_name', {
            category: 'config',
            hasUsers: false,
            defaults: {
                name: { value: "My default node name" }
                // Specify here again all your config node properties (with the same default values as above)
                // ...
            },
            paletteLabel: 'My node palette label',
            label: function () {
                return this.name;
            },
            onpaletteremove: function () {
                // Remove the sidebar with id = sidebar-mysidebarname (which we have added a few lines further ...)
                RED.sidebar.removeTab("sidebar-mysidebarname");
                RED.events.off("sidebar:resize", sidebarResizeEventHandler);
            },
            onpaletteadd: function () {                
                // The html content of the sidebar page has been specified a a data-template, from where it can be loaded:
                var htmlContent = $($('script[type="text/x-red"][data-template-name="my_unique_config_node_name_sidebar"]').i18n().html());
                
                // Add a new "MySidebarName" tabsheet to the right sidebar in the flow editor, where you show the html content
                RED.sidebar.addTab({
                    id: "sidebar-mysidebarname",
                    label: "mysidebarlabel",
                    name: "MySidebarName",
                    content: htmlContent,
                    closeable: true,
                    disableOnEdit: true,
                    // Select here your own FontAwesome icon that needs to be displayed on the tabsheet in the sidepanel
                    iconClass: "fa fa-terminal"
                });
                
                RED.events.on("sidebar:resize", sidebarResizeEventHandler);
                
                // Here you can now initialise and setup your sidebar screen.
                // For example suppose that a value is changed in your sidebar panel, you might want to update that value in your config node.
                $("#some_element_id").on("change", function() {
                    // As said before, make sure your config node instance exists!
                    ensureMyConfigNodeExists();
                
                    // myConfigNode will point to your config node instance, so you can start manipulating it ...
                    myConfigNode.someProperty = "someValue";

                })            
            }            
        });
    })(jQuery);
</script>

<!-- YOUR FIRST DATA TEMPLATE TO DISPLAY THE CONFIG SCREEN WHEN THE CONFIG NODE IS DISPLAYED -->
<script type="text/x-red" data-template-name="my_unique_config_node_name">
    <div class="form-row">
        <label for="node-config-input-name"><i class="icon-tag"></i> Name</label>
        <input type="text" id="node-config-input-name" class="xterm-setting" placeholder="Name">
    </div>

    <!-- Add here a form-row for every input field that needs to be displayed in the sidebar-->
     
</script>

<!-- YOUR SECOND DATA TEMPLATE TO DISPLAY IN THE RIGHT SIDEBAR -->
<script type="text/x-red" data-template-name="my_unique_config_node_name_sidebar">
    <div style="position: relative; height: 100%;">
        <div style="position: absolute; top: 1px; bottom: 2px; left: 1px; right: 1px; overflow-y: scroll; padding: 10px;"> 
            <form class="dialog-form">
                
                <!-- Add here the html elements that needs to be displayed inside your sidebar panel -->
                
            </form>
        </div>
    </div>
</script>

<!-- YOUR INFO PANEL WITH HELP INFORMATION -->
<script type="text/x-red" data-help-name="my_unique_config_node_name">
    <p>Define here the html that needs to be displayed in the info sidebar panel.</p>
</script>

Hopefully this makes a bit clear how it works, so you can get started with your development...

Good luck with it!!
Bart

I see now there is an issue with my code snippet above...

Depending on my code comments from the time being, it is not possible to call the ensureMyConfigNodeExists in the onpaletteadd event. But that wasn't an issue for my xterm sidebar node, because my sidebar panel screen also contained two tabsheets.

  • In the first tabsheet I didn't need the config node (and the data stored in it):

    image

  • But as soon as you clicked the second tabsheet I called - via the tab change event - the ensureMyConfigNodeExists function to show the config data:

    image

    And I added a change event handler to every of those fields, where I called the ensureMyConfigNodeExists function and afterwards set the data entered into the config node. Because - in contradiction to a normal node's config screen - the input fields are not mapped automatically to your config node! You have to do that yourself.

But I'm not sure how you have to do it when there is only one screen on your tabsheet. If I remember correctly, you have no tab switch event to call custom code at the moment that your tabsheet becomes visible (where you can call the ensureMyConfigNodeExists function to get the config node data and show it into your sidebar panel screen). I'm not sure anymore about that.

[EDIT] I mean that I'm not sure if something like this is possible:

image

So would be nice if somebody could step in here to explain how to solve this!

Thank you so much, I am crying for your kind explanation.
I have succeeded in adding a sidebar tab and creating the content of the sidebar tab.

However, it was difficult to create tabs in tabs (terminal_xterm >> terminal, settings),
so I used 'script' now.

I'm referencing the xterm code you made, and you created a detail tab using "globalTabsheets.addTab", but it doesn't work out when you use it.
Is it correct to create and use globalTabsheets?
Could you tell me if there is any other way?

Hi, was not online today ...
Did you make any progress meanwhile?

What exactly doesn't work out?

Another way to achieve what exactly? Be aware that we don't know anything about what you are trying to achieve ...

The globalTabsheets are the tabsheets within my sidebar screen:

image

I don't know whether you need tabsheet inside your sidebar? If not, then you can ignore that part of my code.

Oh my god, i'm so sorry
Yes, a lot of progress has been made thanks to your code.

You used globalTabsheets to put tab sheets in the sidebar as shown.
So I also declared globalTabsheets to use globalTabSheets and did globalTabsheets.addTab, but it does not run.

I've currently made it like this using html code.

image

Yes but what do you mean with this? Does the content of those tabsheets not appear? Or is there another problem with it?

Oh~ Sorry, I think I did the wrong explanation.

This is your code.
image

So, This is my code created by reference.
image

GlobalTabsheets doesn't work. What am I writing wrong? :sob:

What is the gg.addTab ?? Shouldn't that be globalTabsheets.addTab ?

oh, My mistake..

Ignore the gg.tab because I just made it

When I run the code, the added content tab is not visible.

If you can share a link to your Github repository, then people can have a look about what might go wrong. Otherwise it is nearly impossible to guess what could be the problem...
You don't have any issues in your browser console log, I assume?