1

I'd like to run a server in JavaScript on Raspbian that does not just provides the .html content to the browser but also be able to get input from it, let's say, triggered by a button event. The .html and .js files are in the same directory, but to make sure the path is good, I used absolute path.

The other thing I realized is if I drag-n-drop the .html into the browser (so not via the .js), I can replace a given html code by external .js file implemented code. That makes me believe the html is aware of the .js so I don't get why the html cannot find the js's myfunction(). I also tested the myfunction() in the .js by calling it from the 'requestListener()' and it works, no error because of the function.

Please note I don't want to use inline javascript because the main .js will communicate on other interface that provides some important data.

I have a buttonHtml.html which is loaded by the .js on request, shown below:

<!DOCTYPE html>
    <head>  
        
    </head>  
    <body>  
        <p>Click the following button to see the function in action</p>  
        <script type = "text/javascript" src="/home/pi/htmlFile.js"></script>
        <input type = "button" value = "Display" onclick = "myfunction()" > 
    </body> 
</html>  

The JS that runs the server and is supposed to process the button action ( myfunction() )looks like this:

const http = require("http");
const fs = require('fs').promises;

const host = '192.168.1.115';
const port = 8000;

let indexFile;


const requestListener = function (req, res) {
        res.setHeader("Content-Type", "text/html");
        res.writeHead(200);
        res.end(indexFile);
        console.log('request rx');
        myfunction();
        };

const server = http.createServer(requestListener);

fs.readFile(__dirname + "/buttonHtml.html")
    .then(contents => {
        indexFile = contents;
        server.listen(port, host, () => {
            console.log(`Server is running on http://${host}:${port}`);
        });
    })
    .catch(err => {
        console.error(`Could not read buttonHtml.html file: ${err}`);
        process.exit(1);
    });


function myfunction()   
{   
    console.log('btn pressed');
    //would be nice to feedback by a pop up message in the browser, maybe alert()?!?!
}

(Let me note I'm coming from embedded (pure C) and have no clue about .js or web programming in general, but I need a GUI to communicate with an MCU :/ )

whenever I press the button in the browser, I get the following error and the expected log on the console does not show up.

iddqd
  • 21
  • 2
  • https://stackoverflow.com/questions/13840429/what-is-the-difference-between-client-side-and-server-side-programming – Teemu Aug 13 '23 at 15:36

1 Answers1

0

Practically, your main issue is you're not actually serving the javascript file from your server in any way. A bigger potential issue, though, is that you're not clear on the difference between server-side and client-side code. So, I'm going to try to explain what's happening in your two examples, and what you should do, rather than just how to get this to work.

What's going on when you drag the HTML file directly into the browser?

Please note I am guessing on a lot of these file paths.

In this case, there is no server, so there's only one javascript engine running — Chromium's.

When you drag /home/pi/buttonHtml.html into your browser, the browser shows a URI of file:///home/pi/buttonHtml.html. When this script is loaded

<script type = "text/javascript" src="/home/pi/htmlFile.js"></script>

it loads the javascript from file:///home/pi/htmlFile.js, which is the javascript file that you wrote, and has myFunction in it.

Generally, URIs that start with / keep the thing before the ://, and any domain name or IP address that follows it, and just replace everything after the next /.

What's going on when you run the javascript file?

When you run the file /home/pi/htmlFile.js as a server (presumably with node /home/pi/htmlFile.js or something like it), there are now two javascript engines running — Node's and Chromium's. These do not touch each other at all. This can be confusing since they are both JavaScript, but that's just for convenience and doesn't actually connect them. Imagine you wrote the server in another language. If the backend was written in C, you wouldn't expect the webpage to be able to do onclick="malloc(4)".

So what actually ends up happening?

Initially, Node runs your htmlFile.js file. This creates the myFunction function in Node (not in Chromium), as well as reading in buttonHtml.html and listening on a port.

Now, when you open http://192.168.1.115:8000, Chromium sends a request to Node, and Node sends the contents of buttonHtml.html back to Chromium, which Chromium shows as a webpage.

On the webpage, you have this:

<script type = "text/javascript" src="/home/pi/htmlFile.js"></script>

Since the webpage is now at http://192.168.1.115:8000, it decides "/home/pi/htmlFile.js" means to send a request to http://192.168.1.115:8000/home/pi/htmlFile.js. This sends a second request to the Node server.

res.setHeader("Content-Type", "text/html");
res.writeHead(200);
res.end(indexFile);

This code here does not check what file we're asking the Node server for. No matter what, it sends indexFile, which has the contents of buttonHtml.html in it. So the script tag now tries to load buttonHtml.html as javascript, which obviously doesn't work, causing Unexpected token: '<' error.

When we try to call myFunction() from Chromium, it can't find the function. That's because the function only exists in Node, and Chromium hasn't seen it before.

What if we fix the server to properly serve htmlFile.js

This might fix your problem with myFunction() not working, but it will be weird and cause other issues. For example:

const http = require("http");

When you run this on the server in Node, it works how you expect. However, if you run this in Chromium, it will say Uncaught ReferenceError: require is not defined. This is because Node is different than Chromium. The both run JavaScript, and they're similar, but they aren't the same thing.

What should we do instead?

Instead, you should have two javascript files (or two groups of javascript files, as they grow) — ones for the client, and ones for the server. Don't try to send your whole backend code to the client, since they aren't the same thing.

Once you've made a separate javascript file with myFunction in it, you'll need to get your server to properly serve this file. I won't include a tutorial here — there's a hundred different ways to, and you already seem to know how to serve a file, generally.

You can check the javascript is being served properly by going to http://192.168.1.115:8000/wherever/you/decide/to/serve/it.js and seeing all your javascript (and nothing but your javascript). Then you can load it on the webpage properly with your script tag.

But I need to share data between the server and the client!

If you do this:

let value = "lorem";

function serverFunction() {
    value = "ipsum";
}

function clientFunction() {
    console.log(value);
}

and you call serverFunction() on the server, then clientFunction() on the client, it will log "lorem". The server and the client are running completely separate instances of JavaScript and have two completely separate value variables. If you need to send data from the client to the server (or vice versa), you need encode the data & send an HTTP request (or communicate in some other way). The client does not have direct access to the server's code in any way.

So, when you say:

Please note I don't want to use inline javascript because the main .js will communicate on other interface that provides some important data.

I expect you'll run into the problem you're worried about here whether you use inline javascript or not.

Summary

You can fix this issue by making sure the server properly servers htmlFile.js, but your confusion regarding client vs. server-side javascript is likely to cause you bigger issues, so you should split those up too.

Half
  • 5,078
  • 2
  • 10
  • 20