Create custom navigation bar from json file on uibuilder

Hi,

How can I create an html navigation bar automatically from reading json file wich looks like :

{
    "html": {
        "menu3": {
            "sub_menu3_1": {
                "data1": "none",
                "data2": "1",
                "data3": "false",
            }
        },
        "menu1": {
            "sub_menu1_1": {
                "data1": "none",
                "data2": "5",
                "data3": "true",
            },
			"sub_menu1_2": {
                "data1": "door",
                "data2": "7",
                "data3": "true",
            }
        },
        "menu2": {
            "sub_menu2_1": {
                "data1": "none",
                "data2": 4",
                "data3": "false",
            },
            "sub_menu2_2": {
                "data1": "none",
                "data2": 8",
                "data3": "ture",
            }
        }
    }
}

I want the next code will be make automatically by reading the json file.

<div class="navbar">
  <div class="dropdown">
    <button class="dropbtn">menu1 
      <i class="fa fa-caret-down"></i>
    </button>
    <div class="dropdown-content">
      <a href="#">sub_menu1_1</a>
      <a href="#">sub_menu1_2</a>
    </div>
  </div> 
  <div class="dropdown">
    <button class="dropbtn">menu2 
      <i class="fa fa-caret-down"></i>
    </button>
    <div class="dropdown-content">
      <a href="#">sub_menu2_1</a>
	  <a href="#">sub_menu2_2</a>
    </div>
  </div> 
  <div class="dropdown">
    <button class="dropbtn">menu3 
      <i class="fa fa-caret-down"></i>
    </button>
    <div class="dropdown-content">
      <a href="#">sub_menu3_1</a>
    </div>
  </div> 
</div>

Any solution do do this with uibuilder ?

That all depends on what you are using with uibuilder. If you do it with plain JavaScript, you can do it through a simple loop reading in that json. If you use jquery you can create the elements as DOM nodes and update them while looping through the JSON input. If you use Vue, React or one of the other frameworks, they all can do that too, but usually come with options to loop directly in the templating. Either way, this question isn’t node-red or uibuilder specific and a search on StackOverflow could give you the answer.

Lastly, your input file isn’t valid JSON, the menu2 thing has a quote missing in front of the 4” part.

Thank you for the answer,

ok, it's only an example for the topic it's not my real json file.

First you need to get the json into your front-end. Use a file-in node to get the file onto a msg.payload and pass that to your uibuilder node.

Then in your front-end code, in index.js, you will use the uibuilder.onChange('msg',function(msg){....}) function to watch for the incoming msg, assign the payload to a Vue data variable.

Then on the HTML side, you will want to mix and match some VueJS and some bootstrap-vue, both of which are available to you by default and are included in the default template.

The VueJS directive you need is v-for and the bootstrap-vue component you need is <navbar>


v-for lets you "walk" over an array and will let you automatically build html and component elements from that array. You can use an object like an array as well with v-for, the details are in the Vue docs that I've linked to.

I recommend trying the example in the docs first so that you get a feel for it and then build up in complexity until you get what you want. I would begin by outputting a simple list and I expect that you will find examples on the web. You should then have the knowledge to replace the list tags with navigation tags from bootstrap-vue.

Of course, there are many ways to use uibuilder as Lena has alluded to. Instead of using the default framework of Vue and bootstrap-vue, you could switch instead to jQuery and build the menu more traditionally or indeed any other framework you fancy. You could even write your own DOM manipulation if you like :rofl:

1 Like

Thank you a lot for these informations and for your sympathy !
I will try this step by step. I will come back here if I need more informations or if I got issues.

1 Like

Hi,

How can I put a variable in my condition for uibuilder.onChange('msg',function(newVal){...} ? like :

if (newVal.topic == room+"/alarme") where "room" is a variable wich contain the string "002"

uibuilder.onChange('msg', function(newVal){
			//var value=room+"/alarme";
			if (newVal.topic == room+"/alarme"){
			vueApp.alarme = newVal.payload;
			}
        })

Example :

'use strict'
var app1 = new Vue({
    el: '#app',
    data: {
        startMsg    : 'Vue has started, waiting for messages',
		version		: 'version',
        room  		: "002",
		stair 		: "RDC",
		alarme    	: '[Nothing]',
    }, // --- End of data --- //
[...]
mounted: function(){
		
        uibuilder.start()
        var vueApp = this

   		uibuilder.onChange('msg', function(newVal){
			if (newVal.topic == 'version'){
			vueApp.version = newVal.payload;
			}
        })
		
		uibuilder.onChange('msg', function(newVal){
			//var value=room+"/alarme";
			if (newVal.topic == room+"/alarme"){
			vueApp.alarme = newVal.payload;
			}
        })
[...]

	} // --- End of mounted hook --- //

	}) // --- End of app1 --- //

// EOF

I don't know how to explain my request it's difficult to put words on it, especially because i'm not english ^^
I hope you will understand what I mean...

You only need a single onChange function. You can have multiple but you really don't need them.

Where does the variable come from? Is it defined in your front-end code? If so, add it to the app's data definitions if you want it to be responsive - e.g. you want to use it do help display something or control the display if it changes.

If it comes from Node-RED, simply add it to the msg you pass to uibiulder.

Other than having 2 onChange functions, I'm not sure what your issue is since the code looks OK. It looks to me as though you have the possibility of two different types of incoming message and that you are differentiating them by topic which is a good way to do it.

The only other thing I can think of is whether you are asking about multiple room numbers? If you want to test which room you are getting data about?

If so, normally, I would arrange my topics so that all of the room data has a parent topic: rooms/02/alarm, rooms/02/temperature, etc. One easy way to handle that is to use JavaScript's split string function.

...
   data: {
      ...
      roomNumber: '',
      roomAlarm: '',
     ...
   mounted: {
      ...
      uibuilder.onChange('msg', function(newVal){
         ...
         var splitTopic = newVal.topic.split('/')
         if ( splitTopic[0] === 'rooms' ) {
            vueApp.roomNumber = splitTopic[1]
            vueApp.roomAlarm = splitTopic[2]
         }
         ...
      }
      ...
   }
}

If you are needing to keep track of multiple rooms (which seems certain), you can change from using those 2 variables to an object that is key'd on the room number. If you do that, you are better off prefixing the number with a letter (e.g. R02) so that it is easier to use as a key otherwise it will get confused between a key and a subscript.

...
   data: {
      ...
      rooms: {},
     ...
   mounted: {
      ...
      uibuilder.onChange('msg', function(newVal){
         ...
         var splitTopic = newVal.topic.split('/')
         if ( splitTopic[0] === 'rooms' ) {
            var roomNum = 'R' + splitTopic[1]

            // We need an object in an object so if the room
            // doesn't exist yet, we need to create it - JavaScript fun!
            if ( ! vueApp.rooms.hasOwnProperty(roomNum) {
               vueApp.rooms[roomNum] = {}
            }

            vueApp.rooms[roomNum].alarm = splitTopic[2]

         }
         ...
      }
      ...
   }
}

So now you have an object that you can use in a VueJS v-for loop.

Thank you for the answer, I can't change my topic wich come from MQTT, but I made a js node wich store my incomming MQTT data in my global context (global.json), I would like to define my global.json file as a app's data.

My global.json file :

{
    "devices": {
        "R+2": {
            "216": {
                "alarme": "none",
                "etat_systeme": "0",
                "adresse_ip": "192.168.1.5",
                "SoftVersion": "A.05",
                "capteur_lit": "0",
                "capteur_porte": "0",
                "nom": "M LETOURNEUR",
                "trame_vie": "1",
                "num_chambre": "216"
            }
        },
        "RDC": {
            "018": {
                "alarme": "none",
                "etat_systeme": "1",
                "adresse_ip": "192.168.1.7",
                "SoftVersion": "A.06",
                "capteur_lit": "0",
                "capteur_porte": "0",
                "nom": "Mme MARTIN",
                "trame_vie": "1",
                "num_chambre": "018"
            },
            "002": {
                "alarme": "none",
                "etat_systeme": "1",
                "adresse_ip": "192.168.1.4",
                "SoftVersion": "A.06",
                "capteur_lit": "0",
                "capteur_porte": "0",
                "nom": "TEST",
                "trame_vie": "1",
                "num_chambre": "002"
            }
        },
        "R+1": {
            "103": {
                "alarme": "none",
                "etat_systeme": "0",
                "adresse_ip": "192.168.1.6",
                "SoftVersion": "A.05",
                "capteur_lit": "0",
                "capteur_porte": "0",
                "nom": "Mme LE ROY",
                "trame_vie": "1",
                "num_chambre": "103"
            }
        }
    }
}

Where "R+2" , "RDC" and "R+1" are building stairs and "018" , "216" , "103" and "002" are rooms numbers inside there are MQTT datas incomming from a device.

I would like to make my navigation bar from this file where my main menus are my stairs and my sub menus are rooms. When I click on a room it open a new page wich contains the MQTT datas incomming from the device.

That my project, but does it's possible do do it with uibuilder ? If yes can you give me some advices to do it please ?

Thank you

Yes, absolutely.

You are already partly there.

First thing to do is to send your global data to uibuilder when someone opens a new browser tab to the url. The 2nd output of uibuilder is triggered when this happens so you can use that to loop back to a change node to grab the data which is then connected to the uibuilder input.

Use if statements in your uibuilder.onChange function to differentiate incoming msg's, generally by topic or some other unique property/value in the msg.

Use that initial input to populate a data variable in Vue. I think that you will be fine using the same format as your global. So you will just have a single data object.

Start with a simple html structure using vue. You will need 2 nested v-for loops, you can just start by putting the v-for on <div> tags. Use that to build a temporary ui so you get a feel for how Vue and v-for work. Then you can use some better tags to get a nicer output.

Once you have that initial display working, you need to think about how you will send updates to your front-end. You can either send the whole global variable over when it is updated or you could send an individual room over.

Sending the whole thing is possibly easier because you may not need any more code in your front end however, it isn't very efficient either for the amount of data you are sending over the wire nor very efficient for Vue as it will have to rebuild the whole ui each time.

So I recommend sending over a single room when any of its data changes. In the onChange function, you will need, as previously mentioned, an if statement to differentiate the type of data incoming. Then all you need to do is update the data variable with the new room.

If I get some time later, I'll have a go with your data and see if I can knock something together.

So here is an example.

In index.html, replace the content between the <b-container> tags with:

            <div v-for="(floor, stair) in rooms">
                <h2>Stair: {{stair}}</h2>
                <div v-for="room in floor">
                        <pre v-html="room" class="syntax-highlight"></pre>
                </div>
            </div>

And in index.js

var app1 = new Vue({
    el: '#app',
    data: {

        // The "global" variable from Node-RED
        rooms: {},

    }, // --- End of data --- //
    computed: {

    }, // --- End of computed --- //
    methods: {

    }, // --- End of methods --- //

    // Available hooks: init,mounted,updated,destroyed
    mounted: function(){
        //console.debug('[indexjs:Vue.mounted] app mounted - setting up uibuilder watchers')

        uibuilder.start()

        uibuilder.onChange('msg', function(msg){

            // We received a full copy of the 'global' variable
            if ( msg.topic === 'global' ) {
                app1.rooms = msg.payload.devices
            }

        })

    } // --- End of mounted hook --- //

}) // --- End of app1 --- //

And here is the simple flow:

[{"id":"754d4186.5057b","type":"inject","z":"63281c77.40a064","name":"","topic":"global","payload":"{\"devices\":{\"R+2\":{\"216\":{\"alarme\":\"none\",\"etat_systeme\":\"0\",\"adresse_ip\":\"192.168.1.5\",\"SoftVersion\":\"A.05\",\"capteur_lit\":\"0\",\"capteur_porte\":\"0\",\"nom\":\"M LETOURNEUR\",\"trame_vie\":\"1\",\"num_chambre\":\"216\"}},\"RDC\":{\"018\":{\"alarme\":\"none\",\"etat_systeme\":\"1\",\"adresse_ip\":\"192.168.1.7\",\"SoftVersion\":\"A.06\",\"capteur_lit\":\"0\",\"capteur_porte\":\"0\",\"nom\":\"Mme MARTIN\",\"trame_vie\":\"1\",\"num_chambre\":\"018\"},\"002\":{\"alarme\":\"none\",\"etat_systeme\":\"1\",\"adresse_ip\":\"192.168.1.4\",\"SoftVersion\":\"A.06\",\"capteur_lit\":\"0\",\"capteur_porte\":\"0\",\"nom\":\"TEST\",\"trame_vie\":\"1\",\"num_chambre\":\"002\"}},\"R+1\":{\"103\":{\"alarme\":\"none\",\"etat_systeme\":\"0\",\"adresse_ip\":\"192.168.1.6\",\"SoftVersion\":\"A.05\",\"capteur_lit\":\"0\",\"capteur_porte\":\"0\",\"nom\":\"Mme LE ROY\",\"trame_vie\":\"1\",\"num_chambre\":\"103\"}}}}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":170,"y":4080,"wires":[["5b00da78.23a744"]]},{"id":"5b00da78.23a744","type":"uibuilder","z":"63281c77.40a064","name":"","topic":"","url":"uibuilder","fwdInMessages":false,"allowScripts":false,"allowStyles":false,"copyIndex":true,"showfolder":false,"x":340,"y":4080,"wires":[["8a286149.41b2b"],["e54ac468.dee4f8"]]},{"id":"8a286149.41b2b","type":"debug","z":"63281c77.40a064","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":490,"y":4060,"wires":[]},{"id":"e54ac468.dee4f8","type":"debug","z":"63281c77.40a064","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":490,"y":4100,"wires":[]}]

Gets you some output already though it looks a bit rough:
image

Quick upgrade :sunglasses:

This flow is the same but now include a cache so that new or reloaded tabs get data straight away. It also lets you clear the cache.

 [{"id":"754d4186.5057b","type":"inject","z":"63281c77.40a064","name":"","topic":"global","payload":"{\"devices\":{\"R+2\":{\"216\":{\"alarme\":\"none\",\"etat_systeme\":\"0\",\"adresse_ip\":\"192.168.1.5\",\"SoftVersion\":\"A.05\",\"capteur_lit\":\"0\",\"capteur_porte\":\"0\",\"nom\":\"M LETOURNEUR\",\"trame_vie\":\"1\",\"num_chambre\":\"216\"}},\"RDC\":{\"018\":{\"alarme\":\"none\",\"etat_systeme\":\"1\",\"adresse_ip\":\"192.168.1.7\",\"SoftVersion\":\"A.06\",\"capteur_lit\":\"0\",\"capteur_porte\":\"0\",\"nom\":\"Mme MARTIN\",\"trame_vie\":\"1\",\"num_chambre\":\"018\"},\"002\":{\"alarme\":\"none\",\"etat_systeme\":\"1\",\"adresse_ip\":\"192.168.1.4\",\"SoftVersion\":\"A.06\",\"capteur_lit\":\"0\",\"capteur_porte\":\"0\",\"nom\":\"TEST\",\"trame_vie\":\"1\",\"num_chambre\":\"002\"}},\"R+1\":{\"103\":{\"alarme\":\"none\",\"etat_systeme\":\"0\",\"adresse_ip\":\"192.168.1.6\",\"SoftVersion\":\"A.05\",\"capteur_lit\":\"0\",\"capteur_porte\":\"0\",\"nom\":\"Mme LE ROY\",\"trame_vie\":\"1\",\"num_chambre\":\"103\"}}}}","payloadType":"json","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":170,"y":4080,"wires":[["bb55bf3.22ed24"]]},{"id":"5b00da78.23a744","type":"uibuilder","z":"63281c77.40a064","name":"","topic":"","url":"uibuilder","fwdInMessages":false,"allowScripts":false,"allowStyles":false,"copyIndex":true,"showfolder":false,"x":500,"y":4080,"wires":[["8a286149.41b2b"],["e54ac468.dee4f8","a61fc74c.9fae18"]]},{"id":"8a286149.41b2b","type":"debug","z":"63281c77.40a064","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":670,"y":4040,"wires":[]},{"id":"e54ac468.dee4f8","type":"debug","z":"63281c77.40a064","name":"","active":true,"tosidebar":true,"console":false,"tostatus":false,"complete":"true","targetType":"full","x":730,"y":4100,"wires":[]},{"id":"bb55bf3.22ed24","type":"function","z":"63281c77.40a064","name":"cache","func":"var cacheVarName = 'uib_cache_1'\n\n// saved context\nvar ui_msgs = context.get(cacheVarName) || {}\n\n// Handle cache control messages\nif (msg.hasOwnProperty('cacheControl')) {\n    // Replay cache if requested\n    if (msg.cacheControl === 'REPLAY') {\n        for (var name in ui_msgs) {\n            node.send({\n                \"topic\": name,\n                \"payload\": ui_msgs[name],\n                \"_socketId\": msg._socketId\n            })\n        }\n        return null\n    } else if (msg.cacheControl === 'CLEAR') {\n        // Or clear the cach (no msg sent)\n        ui_msgs = {}\n        context.set(cacheVarName, ui_msgs)\n        return null\n    } else {\n        // or do nothing\n        return null\n    }\n}\n\n// ignore other uibuilder control messages\nif (msg.hasOwnProperty('uibuilderCtrl')) return null\n\n// Keep the last msg.payload by topic\nif ( msg.hasOwnProperty('payload') ) {\n    ui_msgs[msg.topic] = msg.payload\n    \n    // save context for next time\n    context.set(cacheVarName, ui_msgs)\n    \n    msg._socketId = null\n} else {\n    node.warn(\"no payload\")\n    msg = null\n}\n\n// Show number of cached msgs in status\nnode.status({fill:'green',shape:'ring',text:'Cached: ' + Object.keys(ui_msgs).length})\n\nreturn msg;\n","outputs":1,"noerr":0,"x":330,"y":4080,"wires":[["5b00da78.23a744"]]},{"id":"b887a56.6c23e58","type":"link in","z":"63281c77.40a064","name":"","links":["a61fc74c.9fae18","d15ffb42.533388"],"x":195,"y":4040,"wires":[["bb55bf3.22ed24"]]},{"id":"a61fc74c.9fae18","type":"link out","z":"63281c77.40a064","name":"","links":["b887a56.6c23e58"],"x":635,"y":4120,"wires":[]},{"id":"a8213cd.c0242c","type":"inject","z":"63281c77.40a064","name":"","topic":"","payload":"","payloadType":"str","repeat":"","crontab":"","once":false,"onceDelay":0.1,"x":115,"y":4160,"wires":[["6c70f767.94a6f8"]],"l":false},{"id":"6c70f767.94a6f8","type":"change","z":"63281c77.40a064","name":"Clear Cache","rules":[{"t":"set","p":"cacheControl","pt":"msg","to":"CLEAR","tot":"str"}],"action":"","property":"","from":"","to":"","reg":false,"x":210,"y":4160,"wires":[["d15ffb42.533388"]]},{"id":"d15ffb42.533388","type":"link out","z":"63281c77.40a064","name":"","links":["b887a56.6c23e58"],"x":315,"y":4160,"wires":[]}]

And here is slightly improved html:

            <div v-for="(floor, stair) in rooms">
                <h2>Stair: {{stair}}</h2>
                <div v-for="(room, rmName) in floor">
                    <h3>Room: {{rmName}}</h3>
                    <dl v-for="(val, prop) in room">
                        <dt>{{prop}}</dt>
                        <dd>{{val}}</dd>
                    </dl>
                </div>
            </div>

You should be able to extract from that the code needed to produce a navigation menu.

Thank you a lot for your help ! I successfully create my "automatic" nav-bar. I still have some issues like, how can I put a dynamic id ?

I try that :

<li v-for="(room, rmName) in floor"><a id="btn-{{ rmName }}">Chambre {{rmName}}</a></li>

but id="btn-{{rmName}}" doesn't work, it don't take my "variable" {{rmName}}.

And how can I display only a value from the v-for ?

I would like to display for exemple only the value of "alarme" prop in my room "018" stair "R+1" where "alarme", "018" and "R+1" are known constants.

I want to use the ID to detect click on my element, then display elements according to the element's id I clicked.

I don't know if it's the best way to do it or if I need to make a href to a new page like /stair/room.html which contains the values and will be the same template for all rooms

When using most front-end frameworks, you cannot use mustaches inside an attribute. The correct approach is:

<li v-for="(room, rmName) in floor"><a :id="'btn-' + rmName">Chambre {{rmName}}</a></li>

The :id (leading colon) tells Vue to treat the content as JavaScript.

If you only want the value, just use a single variable instead of 2.

You need to read the VueJS documentation. These are all basic capabilities of Vue.

Have a look at v-if for example.

Again, it is all in the Vue documentation. You can assign a method (function) to an element event @clicked="methodName". The method is passed the event data which contains the ID.

If you look at the default uibuilder template, it contains a button that does exactly that (though admittedly, I don't think that it uses the event data in that case, perhaps I should update it to do so).

That is the correct way to do it if you want to keep everything on 1 page. The advantage is that it is better for your users (no page swapping). The disadvantage is that the page is larger so I may be a little slower to start up.

The startup issue can be generally solved by using some more advanced HTML features. Notably, have a look in the template index.html file. At the top you will see an alternative <html> statement with a manifest on it. Do some research on using manifest files to see how it helps.

The other way to improve startup sizes and speed is to use a build step to "compile" all of your templates and code to the most efficient set of files. That is another step along your journey to web mastery!

Thank you,

I try to use this to display datas of a room when I click on this room in my navbar, I use the next code to create my navbar sub-menu :

    <div id="app" v-cloak>
		<b-container id="app_container">
		
		<nav>
				<ul>
				<li class="menu-html" v-for="(floor, stair) in rooms" :id="'btn-' + stair">{{stair}}
					<ul class="submenu">
						<li v-for="(room, rmName) in floor" :id="'btn-' + rmName" @click="btn_clicked(rmName)">Chambre {{rmName}}</li>
					</ul>
				</li>
				</ul>
		</nav>
	<div id="main"></div>
        </b-container>
    </div>

So when I click on a sub-menu it start the next function in index.js :

methods: {
		btn_clicked: function(msg) {
			var lines = '<div v-for="(floor, stair) in rooms">';
			lines += '<div v-for="(room, rmName) in floor" v-if="rmName===\''+msg+'\'">';
			lines += '<h1> Chambre {{rmName}}</h1>';
			lines += '<div v-for="(room, rmName) in floor" v-if="rmName===\''+msg+'\'">';
			lines += '<iframe class="iframe" v-for="(val, prop) in room" v-if="prop===\'adresse_ip\'" :id="\'ifrm-\' + rmName" scrolling="auto" frameborder="1" :src="\'http://\' + val + \'/index.html\'"></iframe>';
			lines += '<div class="box-info">';
			lines += '<h2> Informations</h2>';
			lines += '<div v-for="(val, prop) in room" v-if="prop===\'nom\'">';
			lines += '<h3>Nom :</h3>';
			lines += '<h4>{{val}}</h4>';
			lines += '</div>';
			lines += '<div v-for="(val, prop) in room" v-if="prop===\'alarme\'">';
			lines += '<h3>Alarme :</h3>';
			lines += '<h4 v-if="val===\'none\'">Pas d\'Alarme</h4>';
			lines += '<h4 v-else>True</h4>';
			lines += '</div>';
			lines += '</div>';
			lines += '</div>';
			document.getElementById("main").innerHTML=lines;
			}		
    },

When I click my function is well played but it display doesn't display my values, it display the {{rmName}} or {{val}} but if I directly write the html code in index.html and changing the msg by a room number it works...

My html code will be more more longer does it still the best solution to switch between my rooms ?

It is better to write the html directly and let Vue take care of whether it is displayed or not using v-if, etc.

If you want to split things up, take a look at Vue components.

I took a look at vue components and I think it's what I need, but my components can't read Vue my data ()

I got this error : [Vue warn]: Property or method "rooms" is not defined on the instance but referenced during render. Make sure that this property is reactive, either in the data option, or for class-based components, by initializing the property

My index.js :

Vue.component('plan', {
  template: '#plan-template',
})

var app = new Vue({
    el: '#app',
    data: {
	        // The "global" variable from Node-RED
        rooms: {},
    }, // --- End of data --- //
    computed: {

    }, // --- End of computed --- //
    methods: {
		// delete_room: function() {
            // uibuilder.send( {
                // 'topic': "deleteBtn",
                // 'payload': this.room,
             // } )
		
		
    }, // --- End of methods --- //

	
    // Available hooks: init,mounted,updated,destroyed
    mounted: function(){
        //console.debug('[indexjs:Vue.mounted] app mounted - setting up uibuilder watchers')

        uibuilder.start()

        uibuilder.onChange('msg', function(msg){

            // We received a full copy of the 'global' variable
            if ( msg.topic === 'global' ) {
                app.rooms = msg.payload
            }

        })

    } // --- End of mounted hook --- //

}) // --- End of app1 --- //

How my component can read my object rooms which is in my vue App data object ?

OK, you need to look at props.

In common with other frameworks, data flow is top-down in order to control not only the security of it but also to minimise rendering edge-cases.

To consume data in your components, you need to define props so the simple approach is to define

props: {
   rooms
}

Then, when you add your component to your html:

<plan :rooms="rooms" />

That way, you are passing the rooms object down to the component.

You can refine the rooms prop with some additional metadata so that it is constrained to an Object and has a default value if you like, details in the Vue docs.

Sorry but I can't do this, I try but without succes... i'm stuck, can you help me please ?

My html body :

<body>
    <div id="app" v-cloak>
		<b-container id="app_container">
		
		<nav>
				<ul>
				<li class="img-html"><img class="logo" src="./img/logo.png"></li>
				<li class="menu-html" id="btn-General" style="cursor:pointer;">GÉNÉRAL</li>
				<li class="menu-html" v-for="(floor, stair) in rooms" :id="'btn-' + stair">{{stair}}
					<ul class="submenu">
						<li v-for="(room, rmName) in floor" :id="'btn-' + rmName" @click="btn_clicked(rmName)">Chambre {{rmName}}</li>
					</ul>
				</li>
				<li class="menu-html" id="btn-Historique" style="cursor: pointer">HISTORIQUE</li>
				</ul>
		</nav>
	
		<div class="plans">
			<plan  name="018"></plan>
		</div>
			
			<script type="text/x-template" id="plan-template">
				<div class='plan'>
					<div v-for="(floor, stair) in rooms">
						<div v-for="(room, rmName) in floor" v-if="rmName==='018'">
							<h1> Chambre {{rmName}}</h1>
								<div v-for="(room, rmName) in floor" v-if="rmName==='018'">
									<iframe class="iframe" v-for="(val, prop) in room" v-if="prop==='adresse_ip'" :id="'ifrm-' + rmName" scrolling="auto" frameborder="1" :src="'http://' + val + '/index.html'"></iframe>
								</div>
						</div>
					</div>
				</div>
			</script>
							
        </b-container>
    </div>

<script src="../uibuilder/vendor/socket.io/socket.io.js"></script>
    <script src="../uibuilder/vendor/vue/dist/vue.js"></script>   
    <script src="../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.js"></script>
    <script src="./uibuilderfe.min.js"></script>
    <script src="./index.js"></script>
	
</body>

my index.js :

Vue.component('plan', {
   template: '#plan-template',
   props: {
   rooms
	}
 })


var app = new Vue({
    el: '#app',

    data: {
	        // The "global" variable from Node-RED
        rooms: {},
    }, // --- End of data --- //
    computed: {

    }, // --- End of computed --- //
    methods: {
		// delete_room: function() {
            // uibuilder.send( {
                // 'topic': "deleteBtn",
                // 'payload': this.room,
             // } )
		
		
    }, // --- End of methods --- //

	
    // Available hooks: init,mounted,updated,destroyed
    mounted: function(){
        //console.debug('[indexjs:Vue.mounted] app mounted - setting up uibuilder watchers')

        uibuilder.start()

        uibuilder.onChange('msg', function(msg){

            // We received a full copy of the 'global' variable
            if ( msg.topic === 'global' ) {
                app.rooms = msg.payload
            }

        })

    } // --- End of mounted hook --- //

}) // --- End of app1 --- //

You aren't passing or accepting the full rooms object to your component as you'd previously intimated. In fact, you are only passing a fixed number.

That means that the component has nothing to work with other than the number string you've passed.

Remember, the component has no knowledge of the data in your "app". While you have defined the component in the same files as your app, that doesn't mean it has access to any other data, it doesn't.

In your html example, you would have to put something like:

		<div class="plans">
			<plan  :rooms="rooms" stair="RDC" name="018"></plan>
		</div>

And in your script

Vue.component('plan', {
   template: '#plan-template',
   props: {
      rooms,
      name,
      stair,
   }
 })

Then you can simplify the component html because you don't need to loop through everything in there, you can use your stair and room variables to get direct to the inner data this.rooms[this.stair][this.room] in javascript.

You may realise that passing the whole rooms object isn't terribly efficient. You can simplfy the component if you want by only passing the correct room object data and leaving the selection of the room in the app. Then your html to call the component becomes something like:

		<div class="plans">
			<plan  :room="rooms[stair][name]" :stair="stair" :name="name"></plan>
		</div>
Vue.component('plan', {
   template: '#plan-template',
   props: {
      room,
      name,
      stair,
   }
 })

And you will define stair and name as data variables in your app. When you click the button, update the variables and the component html should update itself.

You still have an iframe with a different web page in it inside your component html and this is unnecessary. Whatever is in that html should be in the component's html.

I don't understand, how can I define my props rooms to be my app.rooms data ??

When I use the code you gave me I got an error index.js:4 Uncaught ReferenceError: rooms is not defined at index.js:4 and got a blank page. Even my navbar does not appear...