# Create custom navigation bar from json file on uibuilder

**URL:** <https://discourse.nodered.org/t/create-custom-navigation-bar-from-json-file-on-uibuilder/17595>\
**Category:** General\
**Created:** [6 November 2019 14:53 UTC](https://discourse.nodered.org/t/create-custom-navigation-bar-from-json-file-on-uibuilder/17595 "2019-11-06T14:53:43Z")\
**Posts on this page:** 20\
**Page:** 1

<div class="post-metadata">

**Author:** ![emerichrv](https://avatars.discourse-cdn.com/v4/letter/e/2bfe46/32.png) [@emerichrv](https://discourse.nodered.org/u/emerichrv)\
**Post date:** [6 November 2019 14:53 UTC](https://discourse.nodered.org/t/create-custom-navigation-bar-from-json-file-on-uibuilder/17595/1 "2019-11-06T14:53:44Z")

</div>

Hi,

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

```auto
{
    "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.

```auto
<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 ?

---

<div class="post-metadata">

**Author:** ![afelix](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/afelix/32/9743_2.png) [@afelix](https://discourse.nodered.org/u/afelix)\
**Post date:** [6 November 2019 15:14 UTC](https://discourse.nodered.org/t/create-custom-navigation-bar-from-json-file-on-uibuilder/17595/2 "2019-11-06T15:14:40Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![emerichrv](https://avatars.discourse-cdn.com/v4/letter/e/2bfe46/32.png) [@emerichrv](https://discourse.nodered.org/u/emerichrv)\
**Post date:** [6 November 2019 15:30 UTC](https://discourse.nodered.org/t/create-custom-navigation-bar-from-json-file-on-uibuilder/17595/3 "2019-11-06T15:30:38Z")

</div>

Thank you for the answer,

> [@afelix](#):
>
> Lastly, your input file isn’t valid JSON, the menu2 thing has a quote missing in front of the `4”` part.

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

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [6 November 2019 20:35 UTC](https://discourse.nodered.org/t/create-custom-navigation-bar-from-json-file-on-uibuilder/17595/4 "2019-11-06T20:35:15Z")

</div>

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>`

> **[List Rendering | Vue.js](https://vuejs.org/guide/essentials/list.html)**
>
> Vue.js - The Progressive JavaScript Framework

[https://bootstrap-vue.js.org/docs/components/navbar/](https://bootstrap-vue.js.org/docs/components/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](https://vuejs.org/v2/guide/list.html#v-for-with-an-Object).

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.

> [@emerichrv](#):
>
> Any solution do do this with uibuilder ?

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 🤣

---

<div class="post-metadata">

**Author:** ![emerichrv](https://avatars.discourse-cdn.com/v4/letter/e/2bfe46/32.png) [@emerichrv](https://discourse.nodered.org/u/emerichrv)\
**Post date:** [7 November 2019 07:43 UTC](https://discourse.nodered.org/t/create-custom-navigation-bar-from-json-file-on-uibuilder/17595/5 "2019-11-07T07:43:18Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![emerichrv](https://avatars.discourse-cdn.com/v4/letter/e/2bfe46/32.png) [@emerichrv](https://discourse.nodered.org/u/emerichrv)\
**Post date:** [7 November 2019 15:58 UTC](https://discourse.nodered.org/t/create-custom-navigation-bar-from-json-file-on-uibuilder/17595/6 "2019-11-07T15:58:25Z")

</div>

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"

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

```

Example :

```auto
'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...

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [7 November 2019 17:37 UTC](https://discourse.nodered.org/t/create-custom-navigation-bar-from-json-file-on-uibuilder/17595/7 "2019-11-07T17:37:28Z")

</div>

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.

```auto
...
   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.

```auto
...
   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.

---

<div class="post-metadata">

**Author:** ![emerichrv](https://avatars.discourse-cdn.com/v4/letter/e/2bfe46/32.png) [@emerichrv](https://discourse.nodered.org/u/emerichrv)\
**Post date:** [8 November 2019 09:28 UTC](https://discourse.nodered.org/t/create-custom-navigation-bar-from-json-file-on-uibuilder/17595/8 "2019-11-08T09:28:53Z")

</div>

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 :

```auto
{
    "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

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [8 November 2019 09:43 UTC](https://discourse.nodered.org/t/create-custom-navigation-bar-from-json-file-on-uibuilder/17595/9 "2019-11-08T09:43:12Z")

</div>

> [@emerichrv](#):
>
> That my project, but does it's possible do do it with uibuilder ?

Yes, absolutely.

> [@emerichrv](#):
>
> some advices to do it please

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.

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [8 November 2019 17:36 UTC](https://discourse.nodered.org/t/create-custom-navigation-bar-from-json-file-on-uibuilder/17595/10 "2019-11-08T17:36:11Z")

</div>

So here is an example.

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

```auto
            <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

```auto
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:

```auto
[{"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](https://us1.discourse-cdn.com/flex026/uploads/nodered/original/2X/a/a7e268daebccce8d13248154db0fa22116e036cd.png)

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [8 November 2019 18:25 UTC](https://discourse.nodered.org/t/create-custom-navigation-bar-from-json-file-on-uibuilder/17595/11 "2019-11-08T18:25:46Z")

</div>

Quick upgrade 🕶

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.

```auto
 [{"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:

```auto
            <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.

---

<div class="post-metadata">

**Author:** ![emerichrv](https://avatars.discourse-cdn.com/v4/letter/e/2bfe46/32.png) [@emerichrv](https://discourse.nodered.org/u/emerichrv)\
**Post date:** [12 November 2019 15:58 UTC](https://discourse.nodered.org/t/create-custom-navigation-bar-from-json-file-on-uibuilder/17595/12 "2019-11-12T15:58:24Z")

</div>

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

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [12 November 2019 17:35 UTC](https://discourse.nodered.org/t/create-custom-navigation-bar-from-json-file-on-uibuilder/17595/13 "2019-11-12T17:35:40Z")

</div>

> [@emerichrv](#):
>
> - Chambre {{rmName}}

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

```auto
<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.

> [@emerichrv](#):
>
> And how can I display only a value from the v-for ?

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

> [@emerichrv](#):
>
> 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.

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

Have a look at `v-if` for example.

> [@emerichrv](#):
>
> I want to use the ID to detect click on my element, then display elements according to the element's id I clicked.

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).

> [@emerichrv](#):
>
> I don't know if it's the best way to do it

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!

---

<div class="post-metadata">

**Author:** ![emerichrv](https://avatars.discourse-cdn.com/v4/letter/e/2bfe46/32.png) [@emerichrv](https://discourse.nodered.org/u/emerichrv)\
**Post date:** [13 November 2019 15:37 UTC](https://discourse.nodered.org/t/create-custom-navigation-bar-from-json-file-on-uibuilder/17595/14 "2019-11-13T15:37:44Z")

</div>

Thank you,

> [@TotallyInformation](#):
>
> 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.

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 :

```auto
    <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` :

```auto
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 ?

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [13 November 2019 21:45 UTC](https://discourse.nodered.org/t/create-custom-navigation-bar-from-json-file-on-uibuilder/17595/15 "2019-11-13T21:45:44Z")

</div>

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.

---

<div class="post-metadata">

**Author:** ![emerichrv](https://avatars.discourse-cdn.com/v4/letter/e/2bfe46/32.png) [@emerichrv](https://discourse.nodered.org/u/emerichrv)\
**Post date:** [15 November 2019 08:38 UTC](https://discourse.nodered.org/t/create-custom-navigation-bar-from-json-file-on-uibuilder/17595/16 "2019-11-15T08:38:54Z")

</div>

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 :

```auto
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 ?

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [15 November 2019 11:09 UTC](https://discourse.nodered.org/t/create-custom-navigation-bar-from-json-file-on-uibuilder/17595/17 "2019-11-15T11:09:13Z")

</div>

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

```auto
props: {
   rooms
}

```

Then, when you add your component to your html:

```auto
<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.

---

<div class="post-metadata">

**Author:** ![emerichrv](https://avatars.discourse-cdn.com/v4/letter/e/2bfe46/32.png) [@emerichrv](https://discourse.nodered.org/u/emerichrv)\
**Post date:** [18 November 2019 13:51 UTC](https://discourse.nodered.org/t/create-custom-navigation-bar-from-json-file-on-uibuilder/17595/18 "2019-11-18T13:51:34Z")

</div>

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

My html `body` :

```auto
<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` :

```auto
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 --- //

```

---

<div class="post-metadata">

**Author:** ![TotallyInformation](https://sea2.discourse-cdn.com/flex026/user_avatar/discourse.nodered.org/totallyinformation/32/31_2.png) [@TotallyInformation](https://discourse.nodered.org/u/TotallyInformation)\
**Post date:** [18 November 2019 17:55 UTC](https://discourse.nodered.org/t/create-custom-navigation-bar-from-json-file-on-uibuilder/17595/19 "2019-11-18T17:55:41Z")

</div>

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:

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

```

And in your script

```javascript
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:

```auto
		<div class="plans">
			<plan :room="rooms[stair][name]" :stair="stair" :name="name"></plan>
		</div>

```

```javascript
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.

---

<div class="post-metadata">

**Author:** ![emerichrv](https://avatars.discourse-cdn.com/v4/letter/e/2bfe46/32.png) [@emerichrv](https://discourse.nodered.org/u/emerichrv)\
**Post date:** [19 November 2019 09:39 UTC](https://discourse.nodered.org/t/create-custom-navigation-bar-from-json-file-on-uibuilder/17595/20 "2019-11-19T09:39:02Z")

</div>

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...

[Next page](https://discourse.nodered.org/t/create-custom-navigation-bar-from-json-file-on-uibuilder/17595.md?page=2)
