Handling POSTed data from forms

Posted on
  • I tried the demo on my Espruino WiFi 1.1 and it worked as expected. Except I am wondering about that when I click one of the check boxes that control the LEDs, then click Submit, the page is reloaded and the check box value then becomes unchecked.
    Is there a way to modify the code so that a check box stays checked when reloading the page? That could perhaps be done so that the value of the check box is saved and when reloading the page, that check box stays checked?
    that brings me to the question if the page is loaded before or after postDATA is updated?

  • The response to the post has to set render the checkbox as expected - and it most likelu doe snot do it - it just renders it by default unchecked. What's the link to the demo?

  • http://www.espruino.com/Posting+Forms
    the original file will by default render the checkbox unchecked. so what I am trying to figure out is if I make a change to the code so that I create a global variable that contains the value that postDATA contains, and I change the HTML code so that the checkbox will follow the value of the postDATA, I would expect this to happen: I connect to the Espruino over wifi. the webpage is loaded (with checkbox unchecked). I click the checkbox so it becomes checked. I click the Submit button, and take the postDATA information that contains the information that the checkbox is checked, and I store this information in a global variable. then - because I modified the HTML so that the page that is loaded now contains a checked checkbox - I would expect that the checkbox would remain checked, but it does not. So I am scratching my head trying to understand why I basically have to repeat the Submit twice (and click the checkbox twice) to make the checkbox stay checked... sorry if my explanation is confusing. I should try to share the code. The main difference from the example code is that I store checkbox status in a variable, and I change the HTML so that the page that is loaded will reflect the status of the variable. I just cannot understand why the second time the page is loaded into the browser, it shows the original unchecked status, not the checked status that the variable contains. it takes a second try (third loading of the HTML) to make the checkbox remain checked.

  • Right - I think I see what's going on...

    The flow of execution goes something like this:

    • Web Browser POSTs the request
    • onPageRequest is called - at this point the headers are received, but the POSTed data isn't
    • handlePOST is called - which sets up handlers for the data coming down the connection and then finally handles that data when the connection closes.
    • handlePOST returns
    • The remainder of onPageRequest is executed and serves up the page
    • When the connection closes, the 'close' handler from handlePOST finally gets called and updates the state.

    So actually if you want to receive all the data and then send out the new page, you can use the end event in handlePOST rather than close.

    I tweaked the main bit of code to do what I believe you're after here:

    var postData = {};
    
    function sendPage(res) {
      // We're using ES6 Template Literals here to make the HTML easy
      // to read.
      var d = `
    <html>
     <body>
      <form action="#" method="post">
        <label for="mytext">Some text to send:</label>
        <input type="text" id="mytext" name="mytext" value="Testing"/><br/>
        <label for="led1">LED1 (red):</label>
        <input type="checkbox" id="led1" name="led1" value="1" ${postData.led1?"checked":""}><br/>
        <label for="led2">LED2 (green):</label>
        <input type="checkbox" id="led2" name="led2" value="1" ${postData.led2?"checked":""}><br/>
        <button>Submit</button>
      </form>
     </body>
    </html>`;
      res.writeHead(200, {'Content-Type': 'text/html', 'Content-Length':d.length});
      res.end(d);
    }
    
    // This serves up the webpage
    function onPageRequest(req, res) {
      var a = url.parse(req.url, true);
      if (a.pathname=="/") {
        // handle the '/' (root) page...
        // If we had a POST, handle the data we're being givem
        if (req.method=="POST" && 
            req.headers["Content-Type"]=="applicatio­n/x-www-form-urlencoded")
          handlePOST(req, function() { sendPage(res); });
        else
          sendPage(res);
      } else {
        // Page not found - return 404
        res.writeHead(404, {'Content-Type': 'text/plain'});
        res.end("404: Page "+a.pathname+" not found");
      }
    }
    
    // This handles any received data from the POST request
    function handlePOST(req, callback) {
      var data = "";
      req.on('data', function(d) { data += d; });
      req.on('end', function() {
        // closed - now handle the url encoded data we got
        postData = {};
        data.split("&").forEach(function(el) {
          var els = el.split("=");
          postData[els[0]] = decodeURIComponent(els[1]);
        });
        // finally our data is in postData
        console.log(postData);
        // do stuff with it!
        console.log("We got sent the text ", postData.mytext);
        digitalWrite(LED1, postData.led1);
        digitalWrite(LED2, postData.led2);
        callback();
      });
    }
    

    (I also send a Content-Length header as it looks like on recent Espruino builds that may be needed to work nicely with Chrome)

  • @92178, give code below - version 1 - a shot (changes marked/commented with // ##### ....).

    var WIFI_NAME = "";
    var WIFI_KEY = "";
    var wifi;
    
    // ##### globals to hold on to posted data and use it in page rendering
    var led1 = 0;
    var led2 = 0;
    
    // This serves up the webpage
    function onPageRequest(req, res) {
      var a = url.parse(req.url, true);
      if (a.pathname=="/") {
        // handle the '/' (root) page...
        // If we had a POST, handle the data we're being givem
        if (req.method=="POST" && 
            req.headers["Content-Type"]=="applicatio­n/x-www-form-urlencoded")
          handlePOST(req);
        // Otherwise write the main page. We're using
        // ES6 Template Literals here to make the HTML easy
        // to read.
        res.writeHead(200, {'Content-Type': 'text/html'});
        // ##### use expressions in template to un/check checkboxes
        res.end(`
    <html>
     <body>
      <form action="#" method="post">
        <label for="mytext">Some text to send:</label>
        <input type="text" id="mytext" name="mytext" value="Testing"/><br/>
        <label for="led1">LED1 (red):</label>
        <input type="checkbox" id="led1" name="led1" value="1"${(led1)?" checked":""}><br/>
        <label for="led2">LED2 (green):</label>
        <input type="checkbox" id="led2" name="led2" value="1"${(led2)?" checked":""}><br/>
        <button>Submit</button>
      </form>
     </body>
    </html>`); // render with variables
      } else {
        // Page not found - return 404
        res.writeHead(404, {'Content-Type': 'text/plain'});
        res.end("404: Page "+a.pathname+" not found");
      }
    }
    
    // This handles any received data from the POST request
    function handlePOST(req) {
      var data = "";
      req.on('data', function(d) { data += d; });
      req.on('close', function() {
        // closed - now handle the url encoded data we got
        var postData = {};
        data.split("&").forEach(function(el) {
          var els = el.split("=");
          postData[els[0]] = decodeURIComponent(els[1]);
        });
        // finally our data is in postData
        console.log(postData);
        // do stuff with it!
        // ##### updated the globals for led statuses and used it in turning them on/off
        led1 = postData.led1;
        led2 = postData.led1;
        console.log("We got sent the text ", postData.mytext);
        digitalWrite(LED1, led1);
        digitalWrite(LED2, led2);
      });
    }
    
    // .... rest of the code
    

    This is one way to do it... another one - version 2 - would be to read the status of the leds right in the expressions in the template, which would then be the only change to the code, such as:

    ...
        <label for="led1">LED1 (red):</label>
        <input type="checkbox" id="led1" name="led1" value="1"${(digitalRead(LED1))?" checked":""}><br/>
        <label for="led2">LED2 (green):</label>
        <input type="checkbox" id="led2" name="led2" value="1"${((digitalRead(LED1))?" checked":""}><br/>
    ...
    

    Version 2 though works only when the LED1 and LED2 pins are set to some of output... In other words, if it is not working, add these two lines before line function onPageRequest(req, res) {...:

    pinMode(LED1,"output");
    pinMode(LED2,"output");
    

    The reason that both versions works is due to the fact that get and post have the same code path to respond to the request and therefore respond with the same html. Just before the response is created, post data is processed is conditionally invoked (if (req.method=="POST" && ...) in order to set variables / LED statuses to be consumed in the response (html) creation.

  • Thank you Gordon, I tried with your changes and it now works as expected. I will try to apply this to my project that involves monitoring and controlling a battery charger. I want to be able to read battery voltage and charge current, display the readings in a table in the web browser and set the charge current and charge voltage remotely. I am using for example the LED1 output to turn the charger on and off. So I was struggling with trying to understand why the webpage would show data that was not updated. I intend to share the code when it is more functional, there may be other people trying to do something similar.

  • Thank you, allObjects. I greatly appreciate your guidance and time and will try the changes you suggest. I am not much of a programmer and only barely know enough to keep my nose above water, but now and then I manage to get my code working. Kind of.

  • @Gordon, comparing your solution with mine, I notice a change in flow when acting on a request. I assumed the example responds to the post request when done with receiving all data for the request and processing it and only after that writing the response... oops... how mistaken I was. No matter what, the common behavior of (post) request and response is receiving the whole (post) request, do the processing, and only after completion - successful or failed - return the response, because the processing may have an impact not only on particular data but also on the level of the whole response for HTTP request, such as the HTTP status response code (200,500,etc).

    @user92178, my example is not doing what it is supposed to do, because the response is created prematurely (for various reasons). If you go the route of my version 1 - storing the post data - I prefer storing the individual data - led1, led2 - separately from the requestData... because multiple different posts may update different sets of data, and the way it is built right now is that post handling blows away all data. If you do not want to store the data individually, you can create a separate object and update it with what the various posts provides.

    For things like the LEDs though, I prefer solution approach 2, which reads the actual status of the LEDs (hardware), because some - other than post - event and process may change the LED status and the response / storedData will differ from reality... Therefore, in the template, use, for example, digitalRead(LED1) instead of postData.led1. With that you get always the actual status of the LED in the response upon a request - no matter whether get or post - and not some out of sync posted data.

  • @user92178 great! I'd be really interested to see how you get on.

    @allObjects yes - I need to update the docs on POSTing. I believe this behavior is what node.js does too (eg if someone is POSTing a 4gb file you don't want to have the receive the whole file before you execute your handler), but it's definitely not what you'd expect.

  • If you change the method here to get

    <form action="#" method="post">

    You can then just the parse the values of out the URL string.

  • Post a reply
    • Bold
    • Italics
    • Link
    • Image
    • List
    • Quote
    • code
    • Preview
About

Handling POSTed data from forms

Posted by Avatar for user92178 @user92178

Actions