UI builder - Using data from msg.payload in computed classes?

Hi,

first to say I'm not a developer :frowning: so my experience with VueJS etc. is next to none.

What I would like to achieve is using information from msg.payload (eg. msg.payload.icon = "fa-trash-alt", msg.payload.reminder = "fa-spin") to build a computed class looking like:

<i :class="fad fa-trash-alt fa-4x fa-spin"></I>

What I have right now is (index.js)

'use strict';
var app1 = new Vue({
	el: '#muelltonne',
	data: {
		msg: '',
	},
	computed: {
	    muelltonneIcon() {
        let newIcon = '';
      // Test with fixed values is computed class is working in general
        newIcon = "fad fa-trash-alt fa-4x fa-spin";
      // More complicated logic to determine what
      // class should be applied

      return newIcon;
	  },
	},
	methods: {},

	mounted: function() {
		uibuilder.start();
		var vueApp = this;

		uibuilder.onChange('msg', function(msg) {
			vueApp.msg = msg.payload;
		});

		// send message back to node-red
		// uibuilder.send({payload:'some message'})

		// control message from node-red
		//uibuilder.onChange('ctrlMsg', function(msg) {
		//    console.log(msg)
		//})
	},
});

and the part (code snipped of index.html)

<i :class="muelltonneIcon"></i>

But I have no idea how to put the values from msg.payload into my computed class (instead of the fixed values I'm using right now.

Can everybody give me a hint where I can find some examples?

Many thanks

Michael

Hi Michael,

I dont think you need a computed value in this Vue example.
You could just create a data value.

'use strict';
var app1 = new Vue({
	el: '#muelltonne',
	data: {
		msg: '',
        muelltonneIcon: ''
	},
	
	methods: {},

	mounted: function() {
		uibuilder.start();
		var vueApp = this;

		uibuilder.onChange('msg', function(msg) {
			vueApp.msg = msg.payload;
            vueApp.muelltonneIcon= `fad fa-4x ${msg.payload.icon} ${msg.payload.reminder}`;

		});

		
	},
});

More information on binding classes with Vue at this detailed video

1 Like

and i assume you might want to send other messages for other purposes - so I'd suggest you also use a topic to identify the purpose of the message

e.g...

		uibuilder.onChange('msg', function(msg) {
			vueApp.msg = msg.payload;
			if(msg.topic === "muelltonneIcon") {
				vueApp.muelltonneIcon= `fad fa-4x ${msg.payload.icon} ${msg.payload.reminder}`;
			}
		});
2 Likes

Hmmm...

First of all: many thanks to your replies... but I don't get it to work...

Maybe now it's a problem of my html... The relevant part (only static content) looks like this:

<span class="fa-layers fa-fw" style="background: transparent">
    <i class="fad fa-trash-alt fa-4x fa-spin" style="margin-top: 10px;  --fa-primary-color:blue; --fa-primary-opacity: 1.0"></i>
    <span class="fa-layers-counter fa-5x" style="background: red">{{ msg.remainingdays }}</span>
</span> 

What I try to, is to get the "fad fa-trash-alt fa-4x fa-spin" part of the html changed on base of info from out of msg.payload.

When I change the code html to

<span class="fa-layers fa-fw" style="background: transparent">
    <i :class="muelltonneIcon" style="margin-top: 10px;  --fa-primary-color:blue; --fa-primary-opacity: 1.0"></i>
    <span class="fa-layers-counter fa-5x" style="background: red">{{ msg.remainingdays }}</span>
</span> 

where msg.payload.icon = "fa-trash-alt" and msg.payload.reminder = "fa-spin",
it does not work (even with the changed index.js code from above). The Icon is not displayed :frowning:

Have you loaded the Font awesome in the Head section of your Html page ?

FA cdn link .. use the first one

<link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.2/css/all.min.css" integrity="sha512-HK5fgLBL+xu6dm/Ii3z4xhlSUyZgTT9tuc/hSrtw6uzJOvgRr2a9jyxxT1ely+B+xFAmJKVSTbpM/CuL7qxO8w==" crossorigin="anonymous" />

Also try to change fad with fas

vueApp.muelltonneIcon= `fas fa-4x ${msg.payload.icon} ${msg.payload.reminder}`;
1 Like

@ UnborN

Hi. Yes. I load font awesome in the header section. I'm a font awesome pro subscriber. So in the header I load the kit (name of the .js is changed as this is part of my subscription secret)

<script src="https://kit.fontawesome.com/dcfxxxxxxxxx.js" crossorigin="anonymous"></script>

I can use the fad (dual tone icons) and it works when I put in all the info as static code and not via function. (first code snipped in my last post). With static html the result is (in real life the trash can is spinning :wink: ):

When I'm using the function (second code snipped of my last post) then I'm missing the icon itself and it looks like:

I think this shows that the font awesome integration is working.

Greetz,
Michael

.... another question on my way to find a solution...

As the plain static html code is working and I can access data coming in from node red (as in the example msg.remainingdays) when the variable is accessed without double quotes around:

<span class="fa-layers fa-fw" style="background: transparent">
    <i class="fad fa-trash-alt fa-4x fa-spin" style="margin-top: 10px;  --fa-primary-color:blue; --fa-primary-opacity: 1.0"></i>
    <span class="fa-layers-counter fa-5x" style="background: red">{{ msg.remainingdays }}</span>
</span>

is there a way of using the data from node red in quoted parts of the html like:

<span class="fa-layers fa-fw" style="background: transparent">
    <i class="`fad ${msg.icon} fa-4x ${msg.reminder}`" style="margin-top: 10px;  --fa-primary-color:blue; --fa-primary-opacity: 1.0"></i>
    <span class="fa-layers-counter fa-5x" style="background: red">{{ msg.remainingdays }}</span>
</span

Maybe there is a way around the double quoting problem...

Thanks,
Michael

Hi. ok .. i wasnt sure what that fad was and i had to look it up on the net since i didnt use FA before. So its duotone .. cool :wink:

By reading your second post i think the problem is that the code i shared was taking in consideration that the classes for the icon were under msg.payload

but in your last post you wrote the example as msg.icon and msg.reminder.
What are you sending in Node-red to the uibuilder node ?
Do a console.log(msg) in uibuilder.onChange('msg', function(msg) and open your browser's devtools to check the correct stucture of the msg.

Hi,

the incoming msg.payload is (example):

{"summary":"Restmüll Test","date":"Heute","reminder":"fa-spin","remainingdays":1,"icon":"fa-trash-alt","iconcolor":"blue","description":"Wertstoffhof im Bereich Birkenweg | Info: 09:00 - 13:00 Uhr, Wertstoffhof Eppstein-Bremthal, Valterweg 4 - 5"}

I adapted the code in index.js not to access the complete msg object, as I'm only interested in the payload itself to:

...
uibuilder.onChange('msg', function(msg) {
			vueApp.msg = msg.payload;
            vueApp.muelltonneIcon = `fad ${msg.payload.icon} fa-4x ${msg.payload.reminder}`;
...

This one works, as e.g. the value of {{ msg.summary }} (here: Restmüll Test" will be shown in my browser.

I've tried if the substitiution works when not used in a class= attribute like:

<div class="card-header" style="text-align: center">{{ muelltonneIcon }}</div>

This works, too. The browser shows "fad fa-trash-alt fa-4x fa spin".

I think this is a problem with substitution within a class= attribute

Yes. for sure. But what html are you using now ? This works for me .. i replicated the example and it shows the icon when a msg is received.

<span class="fa-layers fa-fw" style="background: transparent">
    <i :class="muelltonneIcon" style="margin-top: 10px;  --fa-primary-color:blue; --fa-primary-opacity: 1.0"></i>
    <span class="fa-layers-counter fa-5x" style="background: red">{{ msg.remainingdays }}</span>
</span>

ps. send us the complete html (remove your Subscription details) ,and the js


Here is the test i did that works

Flow:

[{"id":"106fa901.54cdcf","type":"uibuilder","z":"ac0f61dd.69e26","name":"","topic":"","url":"icon","fwdInMessages":false,"allowScripts":false,"allowStyles":false,"copyIndex":false,"showfolder":false,"useSecurity":false,"sessionLength":432000,"tokenAutoExtend":false,"x":1150,"y":540,"wires":[[],[]]},{"id":"724407fb.66f5a8","type":"inject","z":"ac0f61dd.69e26","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"icon\":\"fa-trash\",\"reminder\":\"fa-spin\"}","payloadType":"json","x":950,"y":540,"wires":[["106fa901.54cdcf"]]}]

Index.html

<!doctype html>
<html lang="en"><head>

    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge">
    <meta name="viewport" content="width=device-width, initial-scale=1">

    <title>Node-RED UI Builder - VueJS + bootstrap-vue default template</title>
    <meta name="description" content="Node-RED UI Builder - VueJS + bootstrap-vue default template">

    <link rel="icon" href="./images/node-blue.ico">
    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/5.15.2/css/all.min.css" integrity="sha512-HK5fgLBL+xu6dm/Ii3z4xhlSUyZgTT9tuc/hSrtw6uzJOvgRr2a9jyxxT1ely+B+xFAmJKVSTbpM/CuL7qxO8w==" crossorigin="anonymous" />
    <link type="text/css" rel="stylesheet" href="../uibuilder/vendor/bootstrap/dist/css/bootstrap.min.css" />
    <link type="text/css" rel="stylesheet" href="../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.css" />
    <!-- Your own CSS -->
    <link type="text/css" rel="stylesheet" href="./index.css" media="all">

</head><body>

    <div id="app" v-cloak>
      
        <span class="fa-layers fa-fw" style="background: transparent">
            <i :class="muelltonneIcon" style="margin-top: 10px;  --fa-primary-color:blue; --fa-primary-opacity: 1.0"></i>
            <span class="fa-layers-counter fa-5x" style="background: red">{{ msg.remainingdays }}</span>
        </span> 

    </div>

    <!-- These MUST be in the right order. Note no leading / -->

    <!-- REQUIRED: Socket.IO is loaded only once for all instances. Without this, you don't get a websocket connection -->
    <script src="../uibuilder/vendor/socket.io/socket.io.js"></script>

    <!-- Vendor Libraries - Load in the right order, use minified, production versions for speed -->
    <script src="../uibuilder/vendor/vue/dist/vue.js"></script> <!-- dev version with component compiler -->
    <!-- <script src="../uibuilder/vendor/vue/dist/vue.min.js"></script>   prod version with component compiler -->
    <!-- <script src="../uibuilder/vendor/vue/dist/vue.runtime.min.js"></script>   prod version without component compiler -->
    <script src="../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.js"></script> <!-- Dev version -->
    <!-- <script src="../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.min.js"></script>   Prod version -->

    <!-- REQUIRED: Sets up Socket listeners and the msg object -->
    <script src="./uibuilderfe.js"></script> <!-- dev version -->
    <!-- <script src="./uibuilderfe.min.js"></script>     prod version -->

    <!-- OPTIONAL: You probably want this. Put your custom code here -->
    <script src="./index.js"></script>

</body></html>

Index.js

'use strict';
var app1 = new Vue({
	el: '#app',
	data: {
		msg: '',
        muelltonneIcon: ''
	},
	
	methods: {},

	mounted: function() {
		uibuilder.start();
		var vueApp = this;

		uibuilder.onChange('msg', function(msg) {
			debugger
			vueApp.msg = msg.payload;
            vueApp.muelltonneIcon= `fas fa-4x ${msg.payload.icon} ${msg.payload.reminder}`;

		});

		
	},
});

Hi,

here are my parts.

First of all the flow itself (its like yours but with some additional data, that is injected):

[{"id":"724407fb.66f5a8","type":"inject","z":"8f99c157.f53c08","name":"","props":[{"p":"payload"},{"p":"topic","vt":"str"}],"repeat":"","crontab":"","once":false,"onceDelay":0.1,"topic":"","payload":"{\"summary\":\"Restmüll Test\",\"date\":\"Heute\",\"icon\":\"fa-trash\",\"reminder\":\"fa-spin\",\"iconcolor\":\"blue\",\"remainingdays\":1,\"description\":\"Wertstoffhof im Bereich Birkenweg | Info: 09:00 - 13:00 Uhr, Wertstoffhof Eppstein-Bremthal, Valterweg 4 - 5\"}","payloadType":"json","x":370,"y":120,"wires":[["d672d186.244fc"]]}]

This is my index.html:

<!DOCTYPE html>
<html>
    <head>
        <!-- Place your kit's code here -->
        <meta http-equiv="content-type" content="text/html; charset=utf-8"> 
        <script src="https://kit.fontawesome.com/xxxxxxxxxxxxx.js" crossorigin="anonymous"></script>
        
        <meta charset="utf-8">
        <meta http-equiv="X-UA-Compatible" content="IE=edge">
        <meta name="viewport" content="width=device-width, minimum-scale=1, initial-scale=1, user-scalable=yes">

        <title>Mülltest</title>
        <meta name="description" content="Testseite für den Müllkalender">

        <link type="text/css" rel="stylesheet" href="../uibuilder/vendor/bootstrap/dist/css/bootstrap.min.css" />
        <link type="text/css" rel="stylesheet" href="../uibuilder/vendor/bootstrap-vue/dist/bootstrap-vue.css" />
    
        <link rel="stylesheet" href="./index.css" media="all">
    </head>
    
    <body>
        <div id="muelltonne" v-cloak>
            <div class="card-deck">
                <!-- Demo with full static data-->
                <div class="card">
  		            <div class="card-header" style="text-align: center">Restmüll Test</div>
  		            <div class="card-body" style="text-align: center">
                        <span class="fa-layers fa-fw" style="background: transparent">
            	            <i class="fad fa-trash-alt fa-4x fa-spin" style="margin-top: 10px;  --fa-primary-color:blue; --fa-primary-opacity: 1.0"></i>
               	            <span class="fa-layers-counter fa-5x" style="background: red">1</span>
                        </span>  
                    </div>
                    <div class="card-body" style="margin-top: 45px ; background-color: transparent; font-weight: bold; font-size: small; text-align: center">Heute</div>
  		            <div class="card-footer" style="background-color: transparent; font-size: xx-small; text-align: center">Wertstoffhof im Bereich Birkenweg | Info: 09:00 - 13:00 Uhr, Wertstoffhof Eppstein-Bremthal, Valterweg 4 - 5</div>
	            </div>
                <!-- Demo with dynamic data from msg.payload" -->
                <div class="card">
  		            <div class="card-header" style="text-align: center">{{ msg.summary }}</div>
  		            <div class="card-body" style="text-align: center">
                        <span class="fa-layers fa-fw" style="background: transparent">
                            <i :class="muelltonneIcon" style="margin-top: 10px;  --fa-primary-color:blue; --fa-primary-opacity: 1.0"></i>
          	                <span class="fa-layers-counter fa-5x" style="background: red">{{ msg.remainingdays }}</span>
                        </span>  
                    </div>
                    <div class="card-body" style="margin-top: 45px ; background-color: transparent; font-weight: bold; font-size: small; text-align: center">{{ msg.date }}</div>
  		            <div class="card-footer" style="background-color: transparent; font-size: xx-small; text-align: center">{{ msg.description }}</div>
	            </div>
	        </div>
        </div>
        
        <script src="../uibuilder/vendor/socket.io/socket.io.js"></script>
		<script src="../uibuilder/vendor/vue/dist/vue.js"></script>
		<script src="./uibuilderfe.min.js"></script>
		<script src="./index.js"></script>
        
    </body>
</html>    

And this one is my index.js

'use strict';
var app1 = new Vue({
	el: '#muelltonne',
	data: {
		msg: '',
        muelltonneIcon: ''
	},
	
	methods: {},

	mounted: function() {
		uibuilder.start();
		var vueApp = this;

		uibuilder.onChange('msg', function(msg) {
			debugger
			vueApp.msg = msg.payload;
            vueApp.muelltonneIcon= `fas fa-4x ${msg.payload.icon} ${msg.payload.reminder}`;

		});

		
	},
});

The html contains two bootstrap cards. One completely static (to test if my html and the font awesome part works) and a second identical card with the usage of "dynamic" parts out of msg.payload

Your code looks fine and it works for me
(except the hardcoded icon on the left because i dont have the licensed icons)

If it still doesnt work for you then the only difference we have in code is how we include the FA stylesheet library. Look into that

Many, many thanks.

You put me in the right direction :slight_smile: It seems that it's not a problem with pro vs. free icons and not with the way how to include fa (CDN vs. KIT). If I'm not using the fa kit but the CDN, I have the same problem.

What I use in my html is fa's ability to stack icons with the so called "Layering Text and Counters". The icon is the base layer, on top is the red circle and then the number with the remaining days.

This "Layering Text and Counters" only works with fa's SVG & JS framework, but not with their Web Fonts & CSS framework.

It looks as this SVG & JS framework is somehow conflicting with ui builder, vue.js or bootstrap.

I'm not sure what to do next... Maybe I have to overthink my design and get rid of the "Layering Text and Counters"-part.

Hmmm...

But even there is no solution for me yet, I will mark this thread as solved.

Again: many, many thanks!!!!

1 Like

The bring the story to an end and for everybody who want's to use Font Awesome Pro features with UI builder.

At least it seems that I tricked myself :frowning:

In my index.js I referenced to the payload and not to the complete msg-object, because I thought I only need access to the payload itself.

...
vueApp.msg = msg.payload;
....

I played a bit around and changed this back to reference to the complete msg-object (as in the ui-builder example)

...
vueApp.msg = msg;
....

Then I've changed the relevant parts of my index.html like so:

...
<span class="fa-layers fa-fw" style="background: transparent">
      <i :class="`fad ${msg.payload.icon} fa-3x ${msg.payload.reminder}`" :style="`margin-top: 10px;  --fa-primary-color:${msg.payload.iconcolor}; --fa-primary-opacity: 1.0`"></i>
      <span class="fa-layers-counter fa-4x" style="background: red">{{ msg.payload.remainingdays }}</span>
</span> 
....

...and voila... it works even with Font Awesome Pro Kit and all the Power Transforms, layering, stacking, etc. that are part of the SVG & JS framework of Font Awesome

Hi Mike,

Nice research .. very useful.
I was looking a bit into this also and you are right .. Vue doesnt play very nice with the update / replacement of the svg icons.

My findings and how i approached a workaround

  1. What Font Awesome cdn library you load does make a difference.
    Initially I used the plain css and obviously it wasnt working for your stacked example
    (sorry for misleading in my last post .. i was insisting that it was ok and obviously from the screenshots it wasnt :wink: doh )
    As per this article Section 5, stacking / layering / text / counter etc we need the js library.

  2. Regarding the icon update with Vue ... i used a v-if="muelltonneIcon" statement to check whether to show the span or not, if there's a value in muelltonneIcon.

<span v-if="muelltonneIcon" class="fa-layers fa-fw" style="background: transparent">
 <i :class="muelltonneIcon" style="margin-top: 10px;  --fa-primary-color:blue; --fa-primary-opacity: 1.0"></i>
 <span class="fa-layers-counter fa-5x" style="background: red">{{ msg.remainingdays }}</span>
</span>  

And in onChange i forced the code with vue nextTick command to wait for the icon to be cleared and then on the next update of the DOM (Tick) to show the new icon.

uibuilder.onChange('msg', function(msg) {
            vueApp.msg = msg.payload;
            vueApp.muelltonneIcon = "";
			Vue.nextTick(function () {
                // DOM updated
                vueApp.muelltonneIcon= `fas fa-4x ${msg.payload.icon} ${msg.payload.reminder}`;
              })
		});

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