Uibuilder + VueJS + SVG = visual IoT floorplan

Sure, I've just come back from a business trip - hence the slight delay.

uibuilder puts all of the front-end files in a set place. Assuming you have a "standard" installation, are not using projects and haven't changed Node-RED's httpNode setting, that is likely to be at ~/.node-red/uibuilder/<url> where <url> is the name of the url field in the node's configuration.

Within that folder, there are 2 sub-folders src and dest. Start by sticking to the src folder, when you've mastered the basics, you can check the documentation and work out what the dest folder is for and how it works.

If you have easy access to the device running Node-RED, you can simply use VScode or some other editor to develop.

However, if you have never done any web development, you might start by using the editor built into uibuilder itself which you access from the configuration panel. This automatically looks at the correct folder and will automatically open the src\index.html file for you. Just use the drop-down to change between files.

Stick to just using the src folder until you get a bit more confident (you will realise the benefits and pitfalls of using more folders as you progress).

You don't! :grinning:

You don't need to worry about any of that, uibuilder does it all for you. All you need to do is to open the url you specified in the configuration. So if you are running Node-RED on your laptop and you've called the uibuilder's url wow, you can navigate to http://localhost:1880/wow and your ui will appear as if by magic.

Incidentally, if you end up creating yourself some notes on how to use uibuilder, please consider sharing them.

I get too close to how things work so it is often hard to know how to explain things to others and I'm always happy to get feedback.

Hi Julian,
many thanks for answering that quickly.
Well, I copied now the index.html of the flow example into the applicable folder.
Now, the page shows up. However, the background is missing. Checked the index.html...and, to be quite sure, ...changed the following line of code:

        <div id="floorplan" style="position:relative; width:100%; height:50em; background:url(/home/pi/svg/background.svg);">

the URL points out to a location where I would like to keep all those backgrounds centrally on my pi.
Well, it didn't worked out!
Beside that...at uibuilder installation folder dist is created rather then dest...something that needs to be considered?

It looks to me that more beside some text around the svg the only section that needs to be mainly adapted is the

 <h2>My House, Ground Floor</h2>
        <div id="floorplan" style="position:relative; width:100%; height:50em; background:url(/home/pi/svg/background.svg);">
            <bulb id="a" :color="isona ? 'red' : 'grey'" :glow="isona" :clickable="false" x="100" y="100" @bulbclicked="myClick" title="A: This one does not $
            <bulb id="b" :color="isonb ? 'red' : 'grey'" :glow="isonb" :clickable="true"  x="270" y="120" @bulbclicked="myClick" title="B"><circle cx="50" cy$
            <bulb id="c" :ison="isonc" :clickable="true"  x="650" y="120" @bulbclicked="myClick" title="C"></bulb>
            <bulb id="d" :ison="isond" :clickable="true"  x="250" y="270" @bulbclicked="myClick" :title="'D: ' + (isond ? 'ON' : 'off')"></bulb>
        </div>

Did I got it right so far? Sorry for moving ahead by that small steps...

Michael

ok, changed the url-path to the background.svg to:

  <div id="floorplan" style="position:relative; width:100%; height:50em; background:url(background.svg);">

...now th background.svg shows up....but the lamp icons do not match...they are visible (one single lamp) on the upper left corner of the svg...

for an absolute beginner of that webpage stuff it is hard to figure out dependencies...
Anyhow, one step after another :wink:

It wouldn't.

We need to back up slightly because you need to understand what is going on. It will make life a lot easier for you.

What you need to remember is that the architecture of Node-RED is that you are running a "server" (Node-RED) which creates 2 web servers. 1 is used for the admin editor and the other is used for user-facing UI's like Dashboard, http-in/out and uibuilder.

When working with web servers, you have a "front-end" (e.g. stuff that happens in the client browser) and a "back-end" (e.g. stuff that happens in the server). It doesn't really matter that much where the server and the client are, they can be the same device or thousands of miles away on different devices, it is all the same (mostly).

In the case highlighted above, you've confused the location on the Node-RED server with the virtual location provided by the web server (the URL). You have to supply a physical disk location on the server device to one of the web servers that Node-RED starts up.

With Node-RED and uibuilder, you have two options for doing that. You can either supply the folder to Node-RED's httpStatic setting - or you can use the folder supported by uibuilder ~/.node-red/uibuilder/<uib url>/src by default.

If you want to keep a load of resources together and available to multiple instances of uibuilder, you can still use the httpStatic setting or you can use uibuilder's common folder.

If you may need to have multiple resource folders in different places, you should use filing system "links" to create a virtual folder within one of the folders indicated here.

As always, lots of options.

My recommendation is to create a link from your svg folder to the uibuilder src folder.

ln -s  /home/pi/svg/   ~/.node-red/uibuilder/<uib url>/src/svg/

Now in your index.html file, you can reference the image with:

        <div id="floorplan" style="position:relative; width:100%; height:50em; background:url(./svg/background.svg);">
            ...
        </div>

Note that I've started the URL with a . which tells your browser to start looking for the file at the same location of the current html file. You will sometimes see that written in web examples as svg/background.svg (no leading slash or dot) which will also work, however, I find that the leading dot is more clear and less likely to result in a mistaken leading slash.

My bad, I always do that! dist is correct (short for distribution). That folder is used if you include a "build" step such as when using something like webpack. It is able to create much more efficient code for the browser to work with so it speeds up access. However, there is a lot more complexity involved in setting things up and remembering to rebuild every time you make a change.

Looking good at first glance apart from the URL as discussed.

Not a problem, looks like you are getting there.

awesome, Julian! Thanks for sharing these details! Now I am able to handle this resource....but, what about the lamp icons...these are not overlaid the svg...do there position need to be adapted too?
Generaly spoken...does there exist some tooling that helps finding the coordinates of a icon overlaid a background picture...
As you see I am coming straight for the user side rather being a developer.
Thanks your help, very very appreciated!

I think that is to do with your CSS. You have to have the parent div (that contains the background) styled with position:relative; and the lamp images with position:absolute otherwise the lamp images will be positioned against the page rather than the parent.

Then you can position the lamps using their x and y properties.

I'm afraid not as yet. If I ever get some time, I will look at adding some drag/drop to the bulb components so that you can position them using a mouse. It shouldn't be hard to do, I just haven't had any time.

Since the position properties can be set using Vue variables, all that is needed is a Vue compatible drag and drop library that emits events containing the dropped location, you can then use that to update your position variables. You would also need to add some simple code to store the positions in the browsers localStorage capability. Again, not hard.

If someone could pick up some of the donkey-work of getting things close, I could certainly write it up and get an update out. If someone could go further and do a full example, that would be even better.

Ultimately, the plan is to have an ever updating package containing components useful to uibuilder users. But of course, anyone can do that for themselves as well.

Hi,
I tried this, and got it working with correct background image (using url as .\background.svg) and bulbs set on correct position (position x and y should have "px" appended ie: x="100px")

        <div id="floorplan" style="position:absolute; width:100%; height:50em; background:url(./background.svg);">
            <bulb id="a" :color="isona ? 'red' : 'grey'" :glow="isona" :clickable="false" x="100px" y="100px" @bulbclicked="myClick" title="A: This one does not respond to clicks"><desc>Here we use a component slot to insert some more custom svg</desc></bulb>
            <bulb id="b" :color="isonb ? 'red' : 'grey'" :glow="isonb" :clickable="true"  x="270px" y="120px" @bulbclicked="myClick" title="B: This one should be clickable!"><circle cx="50px" cy="50px" r="50px"></circle></bulb>
            <bulb id="c" :ison="isonc" :clickable="true"  x="650px" y="120px" @bulbclicked="myClick" title="C"></bulb>
            <bulb id="d" :ison="isond" :clickable="true"  x="250px" y="270px" @bulbclicked="myClick" :title="'D: ' + (isond ? 'ON' : 'off')"></bulb>

1 Like

Hi and welcome to the forum.

Glad you got this working and hope you find it useful.

Interesting. onClick (turning bulb on/off) does not work in Firefox, but it works with Chrome.

Do you get any errors/warnings in the developer console? Also, have you installed the Vue developer extension? That lets you trace more of what is going on in Vue itself.

Bulbs do not seem to show up for me. I have the background but bulbs do not appear

any help would be greatly appreciated.

        <div id="floorplan" style="position:relative; width:100%; height:50em; background:url(./background.svg);">
            <bulb id="a" :color="isona ? 'red' : 'grey'" :glow="isona" :clickable="false" x="100px" y="100px" @bulbclicked="myClick" title="A: This one does not respond to clicks"><desc>Here we use a component slot to insert some more custom svg</desc></bulb>
            <bulb id="b" :color="isonb ? 'red' : 'grey'" :glow="isonb" :clickable="true"  x="270px" y="120px" @bulbclicked="myClick" title="B: This one should be clickable!"><circle cx="50px" cy="50px" r="50px"></circle></bulb>
            <bulb id="c" :ison="isonc" :clickable="true"  x="650px" y="120px" @bulbclicked="myClick" title="C"></bulb>
            <bulb id="d" :ison="isond" :clickable="true"  x="250px" y="270px" @bulbclicked="myClick" :title="'D: ' + (isond ? 'ON' : 'off')"></bulb>
        </div>

This works for me:

        <div id="floorplan"
            style="position:relative; width:100%; height:50em; background:url(./background.svg);">
            <bulb id="a" :color="isona ? 'red' : 'grey'" :glow="isona" :clickable="false" x="100" y="100"
                @bulbclicked="myClick" title="A: This one does not respond to clicks">
                <desc>Here we use a component slot to insert some more custom svg</desc>
            </bulb>
            <bulb id="b" :color="isonb ? 'red' : 'grey'" :glow="isonb" :clickable="true" x="270" y="120"
                @bulbclicked="myClick" title="B">
                <circle cx="50" cy="50" r="50" />
            </bulb>
            <bulb id="c" :ison="isonc" :clickable="true" x="650" y="120" @bulbclicked="myClick" title="C"></bulb>
            <bulb id="d" :ison="isond" :clickable="true" x="250" y="270" @bulbclicked="myClick"
                :title="'D: ' + (isond ? 'ON' : 'off')"></bulb>
        </div>
1 Like

Showing an error at < /bulb>. Any idea why?

Even if I remove background.svg, the bulbs do not show up. Do i need to download a bulb image?

Sorry but i'm new to node red, css, html, and js. lol

The Bulb tag is a Vue component that is defined partly by the <script type="text/x-template" id="bulb-template"> line in the html and partly by Vue.component('bulb', { ... }) in the js file. So no, you don't need any other file or image.

Can you check out a few things?

  • Firstly, can you check what version of VueJS you have installed? Recently, Vue changed its default from Vue 2 to Vue 3 which throws off a lot of people. You need Vue v2.
  • Then, assuming you have Vue 2 installed, can you try temporarily removing lines 77-80 then saving to see if that fixes the issue?

FAiling that, can you post the actual code from your index.html and index.js files.

I installed Vue 2.0.0 and I also tried removed line 77-80 but I am still having issues.

Thank you for helping me. I've added the files below.

The code is copied exactly from the files provided in this project. I've only modified the floorplan line to display background.svg.

<!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, minimum-scale=1, initial-scale=1, user-scalable=yes">

    <title>Node-RED UI Builder: SVG Test</title>
    <meta name="description" content="Node-RED UI Builder - SVG Test with VueJS + bootstrap-vue">

    <link rel="icon" href="./images/node-blue.ico">

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

    <script type="text/x-template" id="bulb-template">
        <svg @click="doClick" :style="ciconstyle" :height="height" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">
            <defs>
                <filter id="shadow">
                    <feDropShadow dx="1" dy="1" stdDeviation="5" flood-opacity="50%" />
                </filter>
                <filter id="glow" filterUnits="userSpaceOnUse"
                        x="-50%" y="-50%" width="200%" height="200%">
                    <!-- blur the text at different levels-->
                    <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur5"/>
                    <feGaussianBlur in="SourceGraphic" stdDeviation="10" result="blur10"/>
                    <feGaussianBlur in="SourceGraphic" stdDeviation="20" result="blur20"/>
                    <feGaussianBlur in="SourceGraphic" stdDeviation="30" result="blur30"/>
                    <feGaussianBlur in="SourceGraphic" stdDeviation="50" result="blur50"/>
                    <!-- merge all the blurs except for the first one -->
                    <feMerge result="blur-merged">
                        <feMergeNode in="blur10"/>
                        <feMergeNode in="blur20"/>
                        <feMergeNode in="blur30"/>
                        <feMergeNode in="blur50"/>
                    </feMerge>
                    <!-- recolour the merged blurs red-->
                    <feColorMatrix result="red-blur" in="blur-merged" type="matrix"
                                    values="1 0 0 0 0
                                            0 0.06 0 0 0
                                            0 0 0.44 0 0
                                            0 0 0 1 0" />
                    <feMerge>
                        <!--<feMergeNode in="red-blur"/>        largest blurs coloured red -->
                        <feMergeNode in="blur-merged"/>
                        <feMergeNode in="blur5"/>          <!-- smallest blur left white -->
                        <feMergeNode in="SourceGraphic"/>  <!-- original -->
                    </feMerge>
                </filter>
                <slot name="filter"></slot>
            </defs>
            <title>{{title}}</title>
            <slot name="icon"><path :fill="cfillcolor" d="M511.549861 803.293331H408.419043a73.232959 73.232959 0 0 1-67.1862-41.991375 59.795719 59.795719 0 0 1-6.71862-30.569722 207.60536 207.60536 0 0 0-33.593101-113.88061 196.519637 196.519637 0 0 0-27.882273-33.5931A463.248853 463.248853 0 0 1 217.274302 504.314738a399.086031 399.086031 0 0 1-36.95241-75.248544 242.542184 242.542184 0 0 1-15.116895-77.264131 349.032312 349.032312 0 0 1 8.062344-84.990544 314.76735 314.76735 0 0 1 51.733375-114.888403A367.172586 367.172586 0 0 1 361.724634 34.011334 327.532728 327.532728 0 0 1 433.949799 8.144647 369.524103 369.524103 0 0 1 528.682342 0.418234a333.579486 333.579486 0 0 1 126.310057 29.225997 326.860866 326.860866 0 0 1 70.881442 44.678824A382.625412 382.625412 0 0 1 808.848799 168.383736a314.095488 314.095488 0 0 1 41.991375 105.146403 312.751764 312.751764 0 0 1 6.382689 92.045095 275.799353 275.799353 0 0 1-20.15586 76.256338 449.139751 449.139751 0 0 1-61.139443 107.16199 497.513815 497.513815 0 0 1-33.5931 39.639858 160.575019 160.575019 0 0 0-31.241583 48.038134 215.331773 215.331773 0 0 0-18.812136 55.428615c-1.679655 11.757585 0 23.179239-2.687448 33.5931a171.660742 171.660742 0 0 1-3.695241 25.194826 69.873649 69.873649 0 0 1-33.593101 40.647651 74.576683 74.576683 0 0 1-39.639858 10.07793zM490.050277 88.768088c-11.085723 0-22.171446 2.351517-33.5931 4.031172a210.96467 210.96467 0 0 0-74.240752 26.538549 244.221839 244.221839 0 0 0-55.428616 44.342893 222.386324 222.386324 0 0 0-43.335099 63.82689 230.784599 230.784599 0 0 0-19.483998 94.732543 28.218204 28.218204 0 0 0 33.5931 28.890066 28.890066 28.890066 0 0 0 22.171446-26.202618v-13.773171a167.965501 167.965501 0 0 1 9.406068-49.045927 184.762052 184.762052 0 0 1 64.834684-83.98275 167.965501 167.965501 0 0 1 93.72475-33.593101 142.770676 142.770676 0 0 0 18.140274 0 23.851101 23.851101 0 0 0 19.148067-15.452826 33.5931 33.5931 0 0 0 0-19.483998 23.51517 23.51517 0 0 0-20.491791-18.140274 122.950747 122.950747 0 0 0-15.116895 0zM647.601917 943.040628a15.788757 15.788757 0 0 1-13.773171 15.116895H400.356699a17.468412 17.468412 0 0 1-16.460619-8.734206 18.812136 18.812136 0 0 1 0-20.15586 16.124688 16.124688 0 0 1 16.460619-4.703034h227.089358a19.148067 19.148067 0 0 1 19.148067 20.827722zM405.731595 899.369598a18.140274 18.140274 0 0 1-16.460619-12.765378 17.804343 17.804343 0 0 1 15.452826-23.851101H635.508401a18.812136 18.812136 0 0 1 17.804343 13.773171 19.819929 19.819929 0 0 1-10.749792 21.499584 24.187032 24.187032 0 0 1-8.734206 0H423.535938zM437.64504 1022.992207a17.132481 17.132481 0 0 1-15.452826-9.406068 18.140274 18.140274 0 0 1 15.116895-26.202618h139.411367a19.819929 19.819929 0 0 1 19.483998 17.804343 16.124688 16.124688 0 0 1-8.734206 15.788757 19.148067 19.148067 0 0 1-9.741999 3.023379H442.348074z" /></slot>
            <slot name="default"></slot>
        </svg>
    </script>
    
    <b-container id="app">

        <h1>
            UIbuilder + SVG + Vue.js + bootstrap-vue for Node-RED
        </h1>
        <p>
            This is a uibuilder example looking at how easy it is to create a dynamic view of IoT devices in a building
            using SVG images both for the background (floor-plan) and device indicators.
        </p>

        <h2>My House, Ground Floor</h2>
        <div id="floorplan"
            style="position:relative; width:100%; height:50em; background:url(./background.svg);">
            <bulb id="a" :color="isona ? 'red' : 'grey'" :glow="isona" :clickable="false" x="100" y="100"
                @bulbclicked="myClick" title="A: This one does not respond to clicks">
                <desc>Here we use a component slot to insert some more custom svg</desc>
            </bulb>
            <bulb id="b" :color="isonb ? 'red' : 'grey'" :glow="isonb" :clickable="true" x="270" y="120"
                @bulbclicked="myClick" title="B">
                <circle cx="50" cy="50" r="50" />
            </bulb>
            <bulb id="c" :ison="isonc" :clickable="true" x="650" y="120" @bulbclicked="myClick" title="C"></bulb>
            <bulb id="d" :ison="isond" :clickable="true" x="250" y="270" @bulbclicked="myClick"
                :title="'D: ' + (isond ? 'ON' : 'off')"></bulb>
        </div>

    </b-container>
    
    <!-- Load the front-end libraries & code -->
    <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>

</html>


/* jshint browser: true, esversion: 5, asi: true */
/*globals Vue, uibuilder */
// @ts-nocheck
/*
  Copyright (c) 2019 Julian Knight (Totally Information)

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

  http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
*/
'use strict'

//#region ---- Components ---- //
Vue.component('bulb', {
    // NB: prop defined as 'home-data' because it is used as an HTML attribute. BUT use as variable 'homeData'
    //props: ['home-data', 'switches'],
    template: '#bulb-template',
    // template: `
    //     <svg @click="doClick" :style="ciconstyle" :height="height" viewBox="0 0 1024 1024" version="1.1" xmlns="http://www.w3.org/2000/svg">
    //         <defs>
    //             <filter id="shadow">
    //                 <feDropShadow dx="1" dy="1" stdDeviation="5" flood-opacity="50%" />
    //             </filter>
    //             <filter id="glow" filterUnits="userSpaceOnUse"
    //                     x="-50%" y="-50%" width="200%" height="200%">
    //                 <!-- blur the text at different levels-->
    //                 <feGaussianBlur in="SourceGraphic" stdDeviation="5" result="blur5"/>
    //                 <feGaussianBlur in="SourceGraphic" stdDeviation="10" result="blur10"/>
    //                 <feGaussianBlur in="SourceGraphic" stdDeviation="20" result="blur20"/>
    //                 <feGaussianBlur in="SourceGraphic" stdDeviation="30" result="blur30"/>
    //                 <feGaussianBlur in="SourceGraphic" stdDeviation="50" result="blur50"/>
    //                 <!-- merge all the blurs except for the first one -->
    //                 <feMerge result="blur-merged">
    //                     <feMergeNode in="blur10"/>
    //                     <feMergeNode in="blur20"/>
    //                     <feMergeNode in="blur30"/>
    //                     <feMergeNode in="blur50"/>
    //                 </feMerge>
    //                 <!-- recolour the merged blurs red-->
    //                 <feColorMatrix result="red-blur" in="blur-merged" type="matrix"
    //                                 values="1 0 0 0 0
    //                                         0 0.06 0 0 0
    //                                         0 0 0.44 0 0
    //                                         0 0 0 1 0" />
    //                 <feMerge>
    //                     <!--<feMergeNode in="red-blur"/>        largest blurs coloured red -->
    //                     <feMergeNode in="blur-merged"/>
    //                     <feMergeNode in="blur5"/>          <!-- smallest blur left white -->
    //                     <feMergeNode in="SourceGraphic"/>  <!-- original -->
    //                 </feMerge>
    //             </filter>
    //             <slot name="filter"></slot>
    //         </defs>
    //         <title>{{title}}</title>
    //         <slot name="icon"><path :fill="cfillcolor" d="M511.549861 803.293331H408.419043a73.232959 73.232959 0 0 1-67.1862-41.991375 59.795719 59.795719 0 0 1-6.71862-30.569722 207.60536 207.60536 0 0 0-33.593101-113.88061 196.519637 196.519637 0 0 0-27.882273-33.5931A463.248853 463.248853 0 0 1 217.274302 504.314738a399.086031 399.086031 0 0 1-36.95241-75.248544 242.542184 242.542184 0 0 1-15.116895-77.264131 349.032312 349.032312 0 0 1 8.062344-84.990544 314.76735 314.76735 0 0 1 51.733375-114.888403A367.172586 367.172586 0 0 1 361.724634 34.011334 327.532728 327.532728 0 0 1 433.949799 8.144647 369.524103 369.524103 0 0 1 528.682342 0.418234a333.579486 333.579486 0 0 1 126.310057 29.225997 326.860866 326.860866 0 0 1 70.881442 44.678824A382.625412 382.625412 0 0 1 808.848799 168.383736a314.095488 314.095488 0 0 1 41.991375 105.146403 312.751764 312.751764 0 0 1 6.382689 92.045095 275.799353 275.799353 0 0 1-20.15586 76.256338 449.139751 449.139751 0 0 1-61.139443 107.16199 497.513815 497.513815 0 0 1-33.5931 39.639858 160.575019 160.575019 0 0 0-31.241583 48.038134 215.331773 215.331773 0 0 0-18.812136 55.428615c-1.679655 11.757585 0 23.179239-2.687448 33.5931a171.660742 171.660742 0 0 1-3.695241 25.194826 69.873649 69.873649 0 0 1-33.593101 40.647651 74.576683 74.576683 0 0 1-39.639858 10.07793zM490.050277 88.768088c-11.085723 0-22.171446 2.351517-33.5931 4.031172a210.96467 210.96467 0 0 0-74.240752 26.538549 244.221839 244.221839 0 0 0-55.428616 44.342893 222.386324 222.386324 0 0 0-43.335099 63.82689 230.784599 230.784599 0 0 0-19.483998 94.732543 28.218204 28.218204 0 0 0 33.5931 28.890066 28.890066 28.890066 0 0 0 22.171446-26.202618v-13.773171a167.965501 167.965501 0 0 1 9.406068-49.045927 184.762052 184.762052 0 0 1 64.834684-83.98275 167.965501 167.965501 0 0 1 93.72475-33.593101 142.770676 142.770676 0 0 0 18.140274 0 23.851101 23.851101 0 0 0 19.148067-15.452826 33.5931 33.5931 0 0 0 0-19.483998 23.51517 23.51517 0 0 0-20.491791-18.140274 122.950747 122.950747 0 0 0-15.116895 0zM647.601917 943.040628a15.788757 15.788757 0 0 1-13.773171 15.116895H400.356699a17.468412 17.468412 0 0 1-16.460619-8.734206 18.812136 18.812136 0 0 1 0-20.15586 16.124688 16.124688 0 0 1 16.460619-4.703034h227.089358a19.148067 19.148067 0 0 1 19.148067 20.827722zM405.731595 899.369598a18.140274 18.140274 0 0 1-16.460619-12.765378 17.804343 17.804343 0 0 1 15.452826-23.851101H635.508401a18.812136 18.812136 0 0 1 17.804343 13.773171 19.819929 19.819929 0 0 1-10.749792 21.499584 24.187032 24.187032 0 0 1-8.734206 0H423.535938zM437.64504 1022.992207a17.132481 17.132481 0 0 1-15.452826-9.406068 18.140274 18.140274 0 0 1 15.116895-26.202618h139.411367a19.819929 19.819929 0 0 1 19.483998 17.804343 16.124688 16.124688 0 0 1-8.734206 15.788757 19.148067 19.148067 0 0 1-9.741999 3.023379H442.348074z" /></slot>
    //         <slot name="default"></slot>
    //     </svg>
    // `,

    props: {
        //b1color: '#F5BC1A',
        color: {
            type: String,
            required: false,
            default: 'grey',
        },
        height: {
            type: String,
            required: false,
            default: '32',
        },
        x: {
            type: String,
            required: false,
            default: '0',
        },
        y: {
            type: String,
            required: false,
            default: '0',
        },
        title: {
            type: String,
            required: false,
            default: '',
        },
        clickable: {
            type: Boolean,
            required: false,
            default: false,
        },
        glow: {
            type: Boolean,
            required: false,
            default: false,
        },
        ison: {
            type: Boolean,
            required: false,
            default: false,
        },
    },

    data: function() { return {
    }}, // -- End of data -- //

    computed: {

        /** Compute the current fill colour */
        cfillcolor: function() {
            return this.ison ? '#d4af37' : this.color
        },

        /** Compute the current style */
        ciconstyle: function() {
            return {
                //color: this.cfillcolor, //this.ison ? '#d4af37' : this.color,
                top: this.y + 'px',
                left: this.x + 'px',
                cursor: this.clickable ? 'pointer' : 'default',
                position: 'absolute',
                filter: this.glow || this.ison ? 'url(#glow)' : 'url(#shadow)',
                transition: 'filter 2s ease-in-out', //'filter 10s',
            }
        },
    }, // -- End of methods -- //

    methods: {

        /** When the icon is clicked, emit an event
         *  and optionally return some data to the parent
         * In the parent: <bulb @bulbclicked="myClick"></bulb>
         * and define method `myclick: function(event, returnData){}`
         */
        doClick: function(event) {
            if ( !this.clickable ) return
            
            var returnData = { 
                'srcId': '',
                'event': event,
            }

            // Try to find which icon was clicked (have to have an id attrib on the <bulb> tag)
            //var whichIcon = this.findWithAttr(event.path,'tagName','svg')

            //if ( whichIcon !== -1 ) {
                // Report back the src tag id
                //returnData.srcId = event.path[whichIcon].id
            //}
returnData.srcId = this.$vnode.elm.id

            this.$emit('bulbclicked', returnData)

            //console.log('Icon clicked! (COMPONENT)', event)
        }, // - End of doClick() - //

        /** Find array element by object attribute or return -1
         * @param {Array} array The array to search
         * @param {string} prop The property used for searching
         * @returns {number} Array offset or -1
         */
        findWithAttr: function(array, prop, value) {
            for(var i = 0; i < array.length; i += 1) {
                if(array[i][prop] === value) {
                    return i;
                }
            }
            return -1;
        }, // - End of findWithAttr() - //

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

}) // --- End of bulb component --- //
//#endregion ---- Components ---- //

// Define the VueJS app
var app1 = new Vue({
    el: '#app',
    data: {

        isonb: false,
        isona: false,
        isonc: false,
        isond: false,

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

        // Called if one of the bulb icons is clicked
        myClick: function(returnedData) {

            // returnedData.srcId contains the element id, returnedData.event contains the click event object
            console.log('Somebody clicked the ' + returnedData.srcId + ' icon!', returnedData)

            var out

            if ( returnedData.srcId === 'a' ) {
                out = this.isona = !this.isona
            }
            if ( returnedData.srcId === 'b' ) {
                out = this.isonb = !this.isonb
            }
            if ( returnedData.srcId === 'c' ) {
                out = this.isonc = !this.isonc
            }
            if ( returnedData.srcId === 'd' ) {
                out = this.isond = !this.isond
            }

            // Lets tell Node-RED
            uibuilder.send({
                'topic': returnedData.srcId.toUpperCase(),
                'payload': out,
            })

        }, // -- End of myClick -- //

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

    // Get things started
    mounted: function(){

        /** **REQUIRED** Start uibuilder comms with Node-RED */
        uibuilder.start()

        // msg is updated when a standard msg is received from Node-RED
        uibuilder.onChange('msg', function(msg){

            if ( msg.payload.hasOwnProperty('A') ) {
                app1.isona = msg.payload.A
            }
            if ( msg.payload.hasOwnProperty('B') ) {
                app1.isonb = msg.payload.B
            }
            if ( msg.payload.hasOwnProperty('C') ) {
                app1.isonc = msg.payload.C
            }
            if ( msg.payload.hasOwnProperty('D') ) {
                app1.isond = msg.payload.D
            }

            console.log('incoming msg', app1.isona, app1.isonb, app1.isonc, app1.isond, msg)
        })

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

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

// EOF

I have just tried your code and it is working fine for me.

Are there any errors in the browser console (F12 to open then view the console tab)?

Also, what browser/version are you using?

With bulb A and B turned on (I'm using dark-mode so things look a bit strange):
image

1 Like

Good catch. I am using chrome Version 99.0.4844.

In the console I see many errors. I am new to html, css, and js. Do you know what these mean?

image

I did a reboot and it now works!

BUT i still get the console errors. Doesn't matter if it works :slight_smile:

Thank you very much for your help. It's been years since you posted this and you have been very responsive. I really appreciate it!

You should not be getting those. I don't see them in my version with your code. I'm amazed that anything is working.

The unsupported stylesheet issues are usually because you've not got type="text/css" rel="stylesheet" on your stylesheet links in the HTML. Can you please switch to the network view, click on the entry for one of those and post an image of the details. Also, why are you getting the CSS errors twice - once at the start and once at the end?

The 404 errors are catastrophic. They should be causing all sorts of issues. Again please go to the network tab, click on one of the 404 entries and show the details. Without these, you cannot be showing the bulb images because they are build using Vue.

It really does because something else is happening that I can't see.

Pleasure.