Uibuilder - passing data from webpage to node-red with out a submit button

I presently have worked my way through the uibuilder getting started and all seems to be working well. For my project I am interested in sending data from the webpage to node-red and have been working with uibuilder.send, however both methods of sending seem to require a submit button. Is there a way around this - say by simply having as jQuery do the submission when a task completes.

Hi, you can send from any HTML DOM event so you don't need a button. For example, most DOM elements will allow a click event or any input will allow a change event.

The uibuilder.eventSend(event) is designed to be super quick but of course limits what is sent. The uibuilder.send will send whatever you like.

What kind of task are you thinking about?

something simple might be clicking a list item to toggle it as done/not-done. Something more complex might be sending after a drag/drop.

If you can give a more concrete example, I can help with some more concrete code.

I am looking at controlling a servo-controlled turntable(s) - they vary from 1' diameter to 30'. It may have to control one, or several. So the idea is to have a jQuery active dial, representing each turntable. Instructions will activate how many revolutions it will turn, and eventually where it will stop. So the webpage needs to take the instructions and send them to the servo via modbus or canbus, and it also needs to poll from the servo how the progress is going. I originally had built this using node-red and dashboard, but it became too complicated for dashboard, and it's on the way out I understand. I also developed something useful using a webpage and jQuery and NodeJS which works OK, but I need to re-write it in a more organized way. So I need, on a timed basis, to send position data to the servo from the web application.

The examples in your IIFE, show only button examples. Can you show me a simple example utilizing uibuilder.send(event) that would not require pushing a button?

Let me see if I've got this straight. You have 1 or more turntables that you want to get data from and to control.

The modbus exchange will both receive and send the data I assume and this will be done via Node-RED.

For the data display, that is simply a matter of adding a uibuilder.onChange or uibuilder.onTopic` listener callback function to your index.js that updates the display when you get data from a turntable that you want to show in the browser.

For the controls side, you need a pair of inputs for each turntable. 1 for the number of revolutions and 1 for the stop point (an angle?).

I'll assume for now that you know how to set up the inputs in HTML? If not, we can go through that.

Lets just have a really simplistic html example for a single turntable:

<label for="tt1-revs"># Revs</label>
<input id="tt1-revs" name="tt1-revs" type="number" />

You could add an onchange attribute to the input:

<input id="tt1-revs" name="tt1-revs" type="number" onchange="uibuilder.eventSend(event)" />

That will send a message to uibuilder every time a user makes a change to that input. Then you could use node-red flows to handle all the data. This is the simplest in terms of front-end code and doesn't even require any javascript. However, you may want to think about de-bouncing the changes because you may just get too many outputs. Lots of ways of doing that.

Instead of adding the attribute, you can also attach a callback using JavaScript. If you want to use jQuery, thats fine and their documentation is excellent and will tell you how to do it. Otherwise you can do it with some JavaScript that looks like:

$('#tt1-revs').addEventListener('change', function(event) {
    console.log('CHANGED', event)
    uibuilder.send({topic: 'table changed', payload: event.target.value})
})

Which uses uibuilder's $ function which is similar to jQuery's - indeed you could use the same code with jQuery.

Thank you for that. Yes, you got the concept of the project right on. The webpage side will also have a table with cues featuring 'revolutions, speed, cue number' Operator will simply press 'go' and cues will either execute one after the other, or will be executed one at a time, by the operator.

The first three lines of your code work fine.

The lower piece of code I tried and got an error - and you will have to forgive some of ignorance of javascript here, but I wasn't able to troubleshoot this yet;

circle1/:37 Uncaught TypeError: $(...).addEventListener is not a function
at circle1/:37:24

(that's from the chrome console)

The javascript does come after the input tags (which I understand is necessary) for what it's worth. Again, forgive my ignorance, but how come I can't just do a;

uibuilder.send({topic: 'table changed', payload: "some payload"})

for which I get the error 
circle1/:37 Uncaught ReferenceError: uibuilder is not defined;

Not a problem, it all takes time.

Ah, in the interest of brevity, I didn't wrap the code in a test to make sure that the $ function actually found anything - which is good practice of course.

const tt1Revs = $('#tt1-revs')

if (tt1Revs) {
  tt1Revs.addEventListener('change', function(event) {
     console.log('CHANGED', event)
     uibuilder.send({topic: 'table changed', payload: event.target.value})
  })
} else console.error('Oops, tt1-revs was not found')

Of course, everything is hard-coded there and that isn't sustainable nor is it making good use of the power of JavaScript.

As a hint, using $$ instead of $ will always return an array (even if empty) and so you can do something like:

// find all turntable revs inputs (use a class name `ttrev`)
const allRevs = $$('.ttref')
allRevs.forEach( (entry) => {
  entry.addEventListener('change', function(event) {
     uibuilder.send({topic: 'table revs changed', payload: event.target.value, tt: event.target.id})
  })
})

Don't forget to give each input a unique id.

Oh, one other thing to watch out for. If you are creating your table of inputs dynamically, you may need to leave a small delay before adding the listeners to make sure that they've actually been created on the DOM.

PPS: Instead of using uibuilder.send, try using uibuilder.eventSend(event) and see what the difference is. eventSend puts together a bunch of data for you and even more if you wrap your inputs in a form - in that case it will get all the form's inputs and send them together. You could have a form for each turntable if you wanted. Lots of things to try. :slight_smile:

Thank you for attention to this and your time. I am off to the races for the time being.

One more issue.

I am still getting an error in the console like so;

Uncaught TypeError: tt1Revs.addEventListener is not a function

The code as prescribed, follows;

<label for="tt1-revs"># Revs</label>
<input id="tt1-revs" name="tt1-revs" type="number" />

<script>
const tt1Revs = $('#tt1-revs');

if (tt1Revs) {
tt1Revs.addEventListener('change', function(event) {
console.log('CHANGED', event);
uibuilder.send({topic: 'table changed', payload: event.target.value})
})
} else console.error('Oops, tt1-revs was not found')
</script>

I have been experimenting and I notice if I replace

const tt1Revs = $('#tt1-revs');

with

const tt1Revs = document.getElementById('tt1-revs');

then it sends to Node-red, although just the sole number from the input tag. What is the nature of the tt1Revs supposed to be - is that a json object?

Are you using jQuery?

Without jQuery, I've just added the following to a standard uibuilder template:

added to index.html

    <div>
        <label for="tt1-revs"># Revs</label>
        <input id="tt1-revs" name="tt1-revs" type="number" />
    </div>

Added to index.js

const tt1Revs = $('#tt1-revs')

if (tt1Revs) {
  tt1Revs.addEventListener('change', function(event) {
     console.log('CHANGED', event)
     uibuilder.send({topic: 'table changed', payload: event.target.value})
  })
} else console.error('Oops, tt1-revs was not found')

When I put a number into the input and then click at some other part of the page, the change listener fires as expected:
image

What uibuilder's $ function is simply an alias for document.querySelector so it returns an Element object which certainly has the addEventListener method.

#tt1-revs - is a CSS selector that finds an element with the id attribute called tt1-revs.

This is pretty much how jQuery's $ function also works except that it also adds tons of additional methods as well.

This is all documented in the uibuilder docs.

Not really, it is a JavaScript Object (very similar but not quite the same). If you add console.log(typeof tt1Revs), you will get the output "object".

If you do console.log(Object.getPrototypeOf(tt1Revs)), you will see this in the console:
image
And if you expand that, you will see that it shows you all of the properties of the HTML element.

OK. I get the same as you with a standard template, interesting though that I only get the payload value coming into Node-red and not the topic value.

and yes I am using jQuery. So that's the problem it appears. The problem is that I need jQuery for my other code.

Thank you for the tt1Revs explanation at the bottom of your post.

jQuery should work the same. Let me quickly test.

Ah, now I understand. jQuery doesn't do quite what I expected.

Here is the jQuery equivalent:

$('#tt1-revs').on('change', function(event) {
    console.log('CHANGED', event)
    uibuilder.send({topic: 'table changed', payload: event.target.value})
})

Sorry, I should have realised that before.

Incidentally, if using jQuery and you still want to use uibuilder's $ or $$ functions, you can simply do uibuilder.$(...). You can also use uib instead of the full uibuilder as one is an alias of the other.

That jQuery eqiivalent worked! Thank you for your assistance.

1 Like