Floor plan suggestions

Right, I need to have a play around and get back to you both. Thanks for the responses!

I asked this question before and perhaps I didn't understand the answer provided. so apologies in advance for asking again.

Why is it that the JS syntax in VueJS is not the same as the JS syntax in Node-RED?

For instance, in Node-RED:

device_lookup       = device_lookup.find(_=> _.hc2name == "abc!)

In VueJS:

device_lookup       = device_lookup.find(device_lookup => device_lookup.hc2name == "abc")

Ok, so it's very similar but the top code simply will not work in VueJS. It must have taken me over half an hour to figure out the bottom code.


Second question. I tried for quite some time, but couldn't get anything I found on the net to work. I am sure If/when I spend more time I'll figure it out in the end, but if someone knows it would be great to share the code.

Each light bulb that I have created, has 3 lines of HTML code:

  1. the fa-fa-icon
  2. the button (which is transparent) and sits underneath the fa-fa-icon + has a popover with the light circuit name (which pops up when you press the light bulb with your finger)
  3. the value (brightness) of the light that sits to the right of the icon

I don't want to enter the absolute positions for left and top 3x over for each row, so I tried to variabilize them and create a function to have fixed increments/decrements, but haven't got there yet:

            <div style="position:absolute; left:580px;  top:264px" >  <i id="avmain" class="fa fa-circle" aria-hidden="false" > </i>  </div>
            <div style="position:absolute; left:572px;  top:264px" >  <button class="btn btn-primary-outline" v-on:click="myClickSpot('avmain')" v-b-popover.focus.top="'AV Main'"></button> </div>
            <div style="position:absolute; left:600px;  top:264px; font-size: 90%; color: #383737"> {{values.value_avmain}}  </div>

Before you jump in. The button is there because I simply could not get the popover to work with the fa-fa-icon only... not with a touchscreen anyway. This setup does work perfectly though.


Last question, I assume in web page design that you anchor your components in a way that you pick one component and set it to be positioned using position:absolute and then you set the remaining logical components (in a logical group) to sit relative to the first component. That way, if you want to move a logical group you only have to change one set of position variables.
So extrapolating this you then just have to break your page up into x logical groups and work from there.
Is that correct?

... I know, questions, questions, questions... that's all I seem to have these days.

On this one...

device_lookup = device_lookup.find(_=> _.hc2name == "abc!)

There is a missing quote at the end of abc "abc

Regarding the other items on position, it's down to design and html experience. I would say, look into using grid or flex layout and CSS classes instead of positioning items absolutely.

Hi @Steve-Mcl

Thanks for getting back.

I didn't test the first snippet of code when I wrote it for the forum. Good pickup, but the actual tested code in Vuejs didn't have any typos.

As for using a grid, that's the main reason I went to uibuilder. To get away from a restrictive grid.

Thanks anyway. .

We need to see the complete code to fully understand why the code isnt working in Vue but it may be that you have to reference your Vue data() variables with this

this.device_lookup

Example of getting and setting a vue variable

So at a guess, you want to defined a kinda "template" and simply re-use it but setting only one set of coordinates?

In my previous post I alluded to the use of classes etc. so here is a demo of what I was meaning...

Perhaps that helps (or am i misunderstanding you?)

Click to see the html i used (if required) .mydiv { border: 1px solid blue; position: absolute; width:100px; height:100px; } .mydiv .mytitle { border: 1px solid red; width: 100%; font-weight: bold; text-align: center; background-color: black; color: lightgrey } .mydiv .my-content { border: 1px solid yellow; font-size: smaller; width: 100% } .mydiv .my-footer { position: absolute; left: 20px; bottom: 0px; font-size: 90%; color: #383737; }
title
some text
{{value}}
{{title}}
some more text
{{value}}
hello
{{could be from a payload or something}}
{{value}}
1 Like

Ok, this is the entirety of the code.

In the data section:

     device: [{hc2name:"Loft AV/AV Main 1P",          localname:"avmain",         valuename:"value_avmain"},
                {hc2name:"Loft AV/AV Spots 2P",          localname:"avspots",        valuename:"value_avspots"}
                ],

In the methods section

var device_name     = msg.topic
var device_lookup   = app1.device       
device_lookup       = device_lookup.find(device_lookup => device_lookup.hc2name == device_name)

I'm sure I tried every combination last morning with this. and app1 prefixed, but who knows. It was late at night.
In the end I got it to work.

Unless I'm missing something I don't think this answers my question.

This is the original code I pasted:

<div style="position:absolute; left:580px;  top:264px" >  <i id="avmain" class="fa fa-circle" aria-hidden="false" > </i>  </div>
<div style="position:absolute; left:572px;  top:264px" >  <button class="btn btn-primary-outline" v-on:click="myClickSpot('avmain')" v-b-popover.focus.top="'AV Main'"></button> </div>
<div style="position:absolute; left:600px;  top:264px; font-size: 90%; color: #383737"> {{values.value_avmain}}  </div>

Ok, I wrote up a bunch of pseudo code and deleted it all as I realised that by doing so I was driving the solution down a specific path. I'm going to try to explain the outcome instead as I'm looking for what's possible.

The idea is that the original HTML no longer has any hardcoded values in it for left: and top:
I have replaced the px values with hard brackets "[something goes in here]" to designate this

<div style="position:absolute; left:[];  top:[]" >  <i id="avmain" class="fa fa-circle" aria-hidden="false" > </i>  </div>
<div style="position:absolute; left:[];  top:[]" >  <button class="btn btn-primary-outline" v-on:click="myClickSpot('avmain')" v-b-popover.focus.top="'AV Main'"></button> </div>
<div style="position:absolute; left:[];  top:[]; font-size: 90%; color: #383737"> {{values.value_avmain}}  </div>

I tried a method that included making up a CSS style, where var(--b-left) is a variable:


.div.left1 {
  position: absolute;
  left: var(--b-left);
  top: 264px
 }

In the html you pass a variable (eg value) through e.g. var(--b-left) + value.
But I couldn't get the variable "value" to be correctly declared. I'm sure it's just because I don't know how to, not that it's not possible.

Like I said before, I'll figure it out eventually. Maybe some more tutorials and reading up. .

It wasnt meat to be a complete example. It was structuring the divs inside a single div that you can manipulate with values / variables / js / vue bindings

I'll look at it again. Thanks!

no bother.

TBH - I always feel I learn more when I figure it out for myself (so please feel free to ignore what I posted if its not the direction you want to take) only you know what you really want anyway.

Good luck in your endeavours Alex.

I realised I didn't even explain myself properly.

What I meant to say was that I only want to enter the top/left coordinates once and then have the other 4x sets of coordinate calculate off the first two.

I'll get back to where I was last night and then I'll ask a specific question, if I still can't figure it out myself... or else I'll just manually type all the coordinates in.

It would have been quicker to manually type them all in the first place, but then I guess I wouldn't learn anything more ... haha :smiley:

1 Like

device_name ? where is that coming from ?
is that also defined in Vue data() ?

My bad. I updated the post. It comes from msg.topic

var device_name     = msg.topic

EDIT: honestly, it's not a big deal to look into it. What I have works fine. Maybe there is another way tio do it which matches JS in Node-RED exactly. I'm not fussed anymore.

I have bigger fish to fry right now :smiley:

haha .. This raises more questions. Its not enough information
If you are willing and its not top secret :male_detective:
post the whole file

1 Like

I was planning to post ALL my code in the end when I have a working solution, so no secrets here.

I have just broken the code though as I'm upgrading the code now to move all the JS to variables.

When it's working again I'll post the code, just so you don't see my buggy code :slight_smile:

Have to deal with kids now so might not be until later tonight.

Ok, I managed to get some time.

I have finally tidied up the code. There's not much left after removing all of the original code from @TotallyInformation.

I have moved all the JS code to be variable driven rather than hard-coded, which is what I was doing during testing, however somewhere along the line I've come up with a bug that I'm trying fix... but for now I'm lost.

When I hit refresh on my browser I can see the cache nodes outside of uibuilder are working correctly. I am getting a msg.brightness value for the one light I have left active in the JS, however the HTML will not reflect the new msg.brightness value until I click on something else on the screen.

It's weird, the update of the HTML (web page) is always lagged behind the actual current value despite the current value for msg.brightness being stored correctly in the values variable.

INDEX.JS

'use strict';

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

        input_circuit:"no selection",
        input_brightness:0,
        input_colour:'#000000',
    
        devices: [{hc2name:"Loft AV/AV Main 1P",           localname:"avmain",         brightnessname:"brightness_avmain",          rgbw:false},
                  

                ],

                //{hc2name:"Loft AV/AV Spots 2P",          localname:"avspots",        brightnessname:"brightness_avspots",         rgbw:false},
                //                  {hc2name:"Corridors/Loft Sky RGB 6P",    localname:"skylight",       brightnessname:"brightness_skylight",        rgbw:true,      colourname:"colour_skylight"},

        values: [{brightness_avmain:"0"},
                 {brightness_avspots:"0"},
                 {brightness_skylight:"0"},
                 {colour_skylight:"#000000"},
                ],

    }, // --- End of data --- //

    
    methods: {


        // -- Called if a LIGHT BULB is clickced 
        clickLight: function(localname) {
           //console.log('Somebody clicked the ' + localname)
           var device_lookup       = app1.devices;       
           device_lookup           = device_lookup.find(device_lookup => device_lookup.localname == localname);
           this.input_circuit      = device_lookup.hc2name;
           this.input_brightness   = app1.values[device_lookup.brightnessname];
           if (device_lookup.rgbw === true) {this.input_colour = app1.values[device_lookup.colourname]}
        },

         
        // -- Lighting groups -- //
        clickLightGroup: function(group_name) {
            this.input_circuit= group_name;
        },


        // Called if slider value has been changed
        sliderChange: function(input_brightness)  {
        // Send Updated BRIGHTNESS to Node Red //
        uibuilder.send({
            'topic': this.input_circuit,
            'payload': Number(input_brightness),
            'brightness': Number(input_brightness),
        });
        }, // -- End of  -- //


        colourChange: function(input_colour) {
        // Send COLOUR to Node Red //
        uibuilder.send({             
            'topic': this.input_circuit,
            'payload': input_colour,
            'colour': input_colour,
            'type': 'colour'
        });
        }, // -- End of  -- //


        changeElement: function(localname,status,colour) {
            var el = document.getElementById(localname);
            let light_colour = (colour === null) ? "orange" : colour;
            if (status === true)
                {
                el.style.color =  light_colour;
                el.style.fontSize = "1.5em";
                el.style.textShadow = "0px 0px 14px #fff700";
                }
            else
                {
                el.style.color = "grey";
                el.style.fontSize = "1em";
                el.style.textShadow = "0px 0px 0px #fff700";
                }
          },

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



    // Start-up
    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){


            // prepare device specific variables 
            var device_name     = msg.topic;
            var device_lookup   = app1.devices;       
            device_lookup       = device_lookup.find(device_lookup => device_lookup.hc2name == device_name);

            // SPOT LIGHTS //
            // check to see that the msg.topic is in the table of devices and the device is not and rgbw light
            if (device_lookup  !== undefined  && device_lookup.rgbw === false)
            {
                if (msg.brightness !== undefined  ) 
                {
                    app1.values[device_lookup.brightnessname] = msg.brightness;
                    console.log("msg.bright 2= ", app1.values[device_lookup.brightnessname] + " - " + device_lookup.brightnessname);              

                    if (msg.brightness > 0) {app1.changeElement(device_lookup.localname,true,"orange")} 
                    else                    {app1.changeElement(device_lookup.localname,false)}
                }
            }


            //LIGHTING CONTROL - USER INPUT // 
           // Updates the lighting control values on incoming message to keep them syncs if a user makes a change outside of this web page
            device_name = msg.topic;
            device_name = device_name.substring(device_name.search("/")+1);

            if (device_name == app1.input_circuit) {
                if (msg.brightness !== undefined) {app1.input_brightness = msg.brightness}
                if (msg.colour     !== undefined) {app1.input_colour     = msg.colour}
            }

            //Incoming msg log
            //console.log('incoming msg', app1.isonavmain1p, app1.isonavspots1, msg)
            
        });

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


    


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

/*
            // RGBW LIGHTS // -- should merge with spot lights!
            // check to see that the msg.topic is in the table of devices and the device is an rgbw light
            console.log("up to here 1")
            if (device_lookup  !== undefined  && device_lookup.rgbw === true)
            {   console.log("up to here 2")
                if (msg.colour !== undefined ) 
                {
                    var status = ""
                    app1.values[device_lookup.colourname] = msg.colour
                    if (app1.values[device_lookup.brightnessname] > 0) {status = true} else {status = false}
                        app1.changeElement(device_lookup.localname,status,msg.colour)
                }
                if (msg.brightness !== undefined  ) 
                {
                    app1.values[device_lookup.brightnessname] = msg.brightness
                    if (msg.brightness > 0) {app1.changeElement(device_lookup.localname,true,app1.values[device_lookup.colourname] )} 
                    else                    {app1.changeElement(device_lookup.localname,false)}
                }
            }


*/


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

    <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/font-awesome/4.7.0/css/font-awesome.min.css">


    <!-- This is where the output from the Vue #app is displayed -->
    <b-container id="app" style="color: white" >

        <h1 style="text-align: center">
            SW6 Floor Plan 
        </h1>

        <div id="floorplan" style="position:relative; left: -400px; width: 1900px; height:42em; background:url(./images/groundfloor.PNG);">

            <!-- RGBW LEDS -->
            <div style="position:absolute; left:480px;  top:264px" >  <i id="skylight" class="fa fa-circle" aria-hidden="false" > </i>  </div>
            <div style="position:absolute; left:472px;  top:264px" >  <button class="btn btn-primary-outline" v-on:click="clickLight('skylight')" v-b-popover.focus.top="'Skylight RGBW'"></button> </div>
            <div style="position:absolute; left:500px;  top:264px; font-size: 90%; color: #383737"> {{values.brightness_skylight}}  </div>

            <!-- LED SPOT LIGHTS -->
            <div style="position:absolute; left:580px;  top:264px" >  <i id="avmain" class="fa fa-circle" aria-hidden="false" > </i>  </div>
            <div style="position:absolute; left:572px;  top:264px" >  <button class="btn btn-primary-outline" v-on:click="clickLight('avmain')" v-b-popover.focus.top="'AV Main'"></button> </div>
            <div style="position:absolute; left:600px;  top:264px; font-size: 90%; color: #383737"> {{values.brightness_avmain}}  </div>

            <div style="position:absolute; left:680px;  top:264px" >  <i id="avspots" class="fa fa-circle" aria-hidden="false" > </i>  </div>
            <div style="position:absolute; left:672px;  top:264px" >  <button class="btn btn-primary-outline" v-on:click="clickLight('avspots')" v-b-popover.focus.top="'AV Spot2'"></button> </div>
            <div style="position:absolute; left:700px;  top:264px; font-size: 90%; color: #383737"> {{values.brightness_avspots}}  </div>

        </div>


        <template>
            <div style="position:relative; width:200px; top:20px; left:20px;" >
                <label for="range-10">Circuit: {{ input_circuit }} </label>
                <b-form-input lazy v-on:change="sliderChange(input_brightness)" id="range-10" v-model="input_brightness" type="range" min="0" max="99"></b-form-input>
                <div class="mt-2">Brightness: {{ input_brightness }}</div>
            </div>

            <div style="position:relative; width:200px; top:60px; left:20px;">
                <input  @change="colourChange(input_colour)" v-model="input_colour" type="color"  >
                <div class="mt-2">Colour: {{ input_colour }}</div>
            </div>

            <div style="position:relative; width:50px; top:-110px; left:-100px;">
                <button v-on:click="clickLightGroup('Group Kitchen RGBW')"> All down lights </button>
            </div>

        </template>


    </b-container>
    
  
    <!-- 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 -->
    <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>

    <!-- 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.CSS`

/* Cloak elements on initial load to hide the possible display of {{ ... }} 
 * Add to the app tag or to specific tags
 * To display "loading...", change to the following:
 *    [v-cloak] > * { display:none }
 *    [v-cloak]::before { content: "loadingā€¦" }
 */
 [v-cloak]::before { content: "loadingā€¦" }

/*  Colours for Syntax Highlighted pre's */
.syntax-highlight {color:white;background-color:black;padding:5px 10px;}
.syntax-highlight > .key {color:#ffbf35}
.syntax-highlight > .string {color:#5dff39;}
.syntax-highlight > .number {color:#70aeff;}
.syntax-highlight > .boolean {color:#b993ff;}

/*Web page background colour */
body {
  background-color: #303030;
  font-family: "helvetica";
  font-size: 12px;
}

.btn-primary-outline {
  background-color: transparent;
  border-color: transparent;
}
.btn:focus {
  outline: none;
  box-shadow: none;
}

.popover {
  border: 2px transparent;
  background-color: white;
  font-size: 12px;
}
.popover .arrow {
 display: none;
}

It's the oddest thing. You can see the msg.brightness is coming through as I have logged it in the console.

You can see I have been changing the value of the light directing in my HA system and msg.brightness comes through in the console:

As soon as I click on the light or on something else interactive on the screen the value updates:
image

EDIT: When I add more lights the same issues happens, but across all the lights.
i.e. on refresh they report no value, as soon as I click on one of the lights (or another user interactive object) the (correct) values appear on all of the lights at the same time.

Is this some sort of known bug?

Nice, I like it. Do you mind if I steel this post and put it in a WIKI article? Probably very useful to many other people.

Yes, if the library has been well written. You will see various examples of this in the WIKI.

Web Components are a new standard that is only supported by the latest browsers. It is possible to mess with a Vue component and turn it into a web component. Probably. Sometimes :roll_eyes:

Actually the hardest part is the initial setup. If you can do that, the rest isn't at all hard. You just have to remember to rebuild on each change to your front-end code. The rebuild step can be partly automated as well by using something like PM2 or nodemon to run your development version of Node-RED - by configuring it to watch for changes to code files.

This is simple. You can add a link to a Dashboard menu, that is all you need to do. When you click on that link, it takes you to your uibuilder page. Just make sure to have a return link on the uibuilder page (or get clever and recreate the Dashboard menu on the uibuilder page using bootstrap-vue's navigation components.

You could make the 2 look very similar with some simple CSS.

You are on the right lines. Remember that everything is hierarchical in HTML/CSS. As Steve says, wrap things in an outer div element that does the absolute position. Then wrap that in another outer div if you need to keep that element in the "flow" of other non-absolutely positioned elements.

A short sidetrack to follow a CSS positioning tutorial will really help you.

However, also note that CSS positioning can be really tricky sometimes and positioning SVG elements can also be tricky. I thing that my original SVG demo with the "mansion" image and the lightbulbs should already have some useful examples for positioning.

If some positioning isn't giving you the expected results. First double-check all of your css and html for mistakes. Then try using extra layers of div wrappers to see if something helps. Finally check the Internet to see if you've hit some kind of exception that you need a trick to get around.

Not only that but there's no point in programming things and then hard-coding all the values!

That link should go in the head section not the body. Also, best to put it before your own index.css otherwise you'll be caught out one day and won't know why you can't change something.

Looks like you are trying to indirectly change the brightness value via a function but I don't think you need to do you? Just change the value directly and your interface should update.

I didn't mean to hard code all the positions. The idea is to group 3x different things into one and only enter one set of x and y coordinates knowing that the other 2x things needs slight, but consistent offsets. It's just about making it easier to change your mind when moving things around.

Hmmm. I'll have to think about what you are saying. I have completely changed your code. I think there are probably only a few lines left of your original svg mansion example left across the 3x files. It doesn't much like how you designed it originally.

For example, I don't want to click on a light and have it turn on / off. Even if that was the limit of the functionality, I still wouldn't implement it that way... Why? because it's too easy to fat finger it.
I prefer to select the light by clicking on it and then control brightness and colour (if appropriate) from another part of the screen.

The issue I am dealing with right now feels very odd. I have a JS variable that is updating correctly and yet the HTML file does not pick this up straight away.

I am going to go and say it. I think there are gremlins out there. I've had a few examples now of something not working yet neither Visual Studio Code in-built nor the Node-Red debug prompts pick it up and most importantly the browser debug window doesn't pick it up, but yet there is a bug there....

When I find the next one I'll document it to see if there is a logical explanation... that said, even if there is one, it's still annoying to not get prompted of the issue in the first instance.

EDIT: Is it because there is an issue with how I am now referencing the value in HTML
I have moved to a variable in JS like this:

        values: [{brightness_avmain:"0"},
                 {brightness_avspots:"0"},
                 {brightness_skylight:"0"},
                 {colour_skylight:"#000000"},
                ],

And am referencing it the HTML like this:

{{values.brightness_avmain}} 

EDIT: I think that's it. I just tested it with a new var in JS "brightness_avmain:"50"," and updated the HTML {{brightness_avmain} and the value 50 is visible in the web page on refresh!.... Ok, I now have to figure out how to reference "{{values.brightness_avmain}}" in HTML so it works properly!

Damn you something or other for being so difficult....