1

I want to submit a form and then return an object from my MongoDB database without reloading the page. Therefore when I submit my form I use an event handler that prevents the default behavior of the form to reload the page. The problem is this is interfering with my server side script and causing req.body to return null. If I remove e.preventDefault() from my client side script then req.body works fine but the page refreshes and just displays a json from my database. How would I get around this?

Here is my HTML:

    <!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="utf-8">
        <meta name="viewport" content="width=device-width,initial-scale=1,user-scalable=no">
        <title>Message in a Bottle</title>
        <link href="https://fonts.googleapis.com/css?family=Roboto+Slab&display=swap" rel="stylesheet">
        <link rel="stylesheet" type="text/css" href="styles.css">
    </head>
    <body>
        <div id = "icon">
            <img id = "iconImg" style = "width:100%"src = "icon.svg"/>
        </div>
        <div id = "form">
            <form method="post" action="/">
                <input type="text" name="sender"/>
                <textarea rows="20" cols="50" name="message"></textarea>
                <button type = "submit" onclick="onClick(event)">submit</button>
            </form>
        </div>
        <div id = "message">
            <p id = "senderName"></p>
            <p id = "response"></p>
            <button onclick="closeMessage()">New Message</button>
        </div>
    </body>
    <script type = "text/javascript" src = "packageHandler.js"></script>

</html>

Here is my client side script:

const form = document.getElementById("form");
const icon = document.getElementById("icon");
const message = document.getElementById("message");


function onClick(e){
    e.preventDefault();
    console.log("loading results");
    form.style.opacity = "0%";
    form.style.pointerEvents = "none";
    icon.style.width = "80%";
    fetch('http://localhost:3000/', {method: 'POST'})
        .then((response) => {
        icon.style.width = "33%";
        message.style.opacity = "100%";
        message.style.pointerEvents = "auto";
        return response.json();
    })
        .then((data) => {
        document.getElementById("senderName").innerHTML = data.name;
        document.getElementById("response").innerHTML = data.message;
    });
    //await results
    //fade in <div id = "message">
    //display results in <div id = "message">
}
function closeMessage(){
    console.log("message closed")
    form.style.opacity = "100%";
    form.style.pointerEvents = "auto";
    message.style.opacity = "0%";
    message.style.pointerEvents = "none";
    //fade out <div id = "message">
    //fade in <div id = "form"
}

Here is my server side script:

const express = require('express');
const MongoClient = require('mongodb').MongoClient;
require('dotenv/config');
const app = express();
const fs = require('fs');

const port = 3000;

app.use(express.urlencoded());
app.use(express.static('public'));


app.listen(port, () => console.log(`Example app listening on port ${port}!`));

//Routes
app.get('/', (req, res) => {
  fs.readFile('./views/home.html', function (err, data) {
    res.writeHead(200, { 'Content-Type': 'text/html' });
    res.write(data);
    res.end();
  });
});

app.post('/', (req, res) => {
  MongoClient.connect(process.env.DB_CONNECTION, { useUnifiedTopology: true, useNewUrlParser: true }, function (err, db) {
    if (err) throw err;
    const dbo = db.db("mydb");
    const messageTable = dbo.collection("messages");
    let myobj = 
    [{ 
      name: req.body.sender,
      message: req.body.message
    }];
    console.log(myobj)
    messageTable.insertMany(myobj, function (err, res) {
      if (err) throw err;
      console.log("1 document inserted");
    });
    var myPromise = () => {
      return new Promise((resolve, reject) => {
        messageTable.aggregate(
          [{ $sample: { size: 1 } }]
        ).toArray((err, data) => {
          err
            ? reject(err)
            : resolve(data[0]);
        });
      });
    }
    //Step 2: async promise handler
    var callMyPromise = async () => {
      var result = await (myPromise());
      //anything here is executed after result is resolved
      return result;
    };

    //Step 3: make the call
    callMyPromise().then(function (result) {
      db.close();
      res.json(result)
      });

    });


  });  //end mongo client
TDonnally
  • 67
  • 10

2 Answers2

2

When you make a request with fetch the data from your form is not included.

To include the data your must manually gather the data and send it with the request.

Super lazy example:

EDIT: changed to FormData based of this answer

EDIT 2: FormData sends as multipart/form-data so we need to url-encode it first. (Thanks @Phil)

var forms = document.querySelectorAll('form');

  forms.forEach((form) => form.onsubmit = onsubmit);

function onsubmit(e) {
  e.preventDefault();
  var form = e.target;
  var formData = new FormData(form);
  var encodedData = new URLSearchParams(formData).toString()
  // log form values
  console.log(encodedData);
  
  fetch('/', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded'
    },
    body: encodedData
  })
  .then(result => {
  
  });
  
}
<form method="post" action="/">
    <input type="text" name="sender"/>
    <textarea rows="20" cols="50" name="message"></textarea>
    <button type = "submit">submit</button>
</form>
Phil
  • 157,677
  • 23
  • 242
  • 245
Trobol
  • 1,210
  • 9
  • 12
  • OP would need to add `app.use(express.json())` for this to work. Also, you mention the `Content-type` header but you have not added any headers to the request – Phil Mar 15 '20 at 22:54
  • 2
    Fun fact about submitting a `FormData` instance via `fetch`; this will post a `multipart/form-data` request body – Phil Mar 15 '20 at 22:58
  • And the second fun fact. FormData class is great for sending an image or file. – Pavel B. Mar 15 '20 at 23:12
  • Another (actually) fun fact, if you set the request body to an instance of `URLSearchParams` (instead of a string), the content-type will default to `application/x-www-form-urlencoded` so you can omit the `headers` part (also, it's [`headers`](https://developer.mozilla.org/en-US/docs/Web/API/Request/headers), not `header`) – Phil Mar 15 '20 at 23:12
  • Finally, I wouldn't recommend using the `onsubmit` property. It's usually better to add event listeners – Phil Mar 15 '20 at 23:17
0

This is happening because you are using the wrong event.

Try using onsubmit event on the form and instead of using event.preventDefault(), return false in the onSubmit function.

It will look like something like this:

<form onsubmit="onSubmit()">
  <button type="submit"></button>
</form>

Then the code:

function onSubmit() {
  ... do your thing ...
  return false;
}

It should work, but If doesn't, then You will have to add formData to the body yourself. (I will tell you how if needed).

Update

  1. Add id to the form (you don't have to, but it makes things easier.

    <form id="myForm">
      <button></button>
    </form>
    
  2. get the form data:

    var formData = new FormData(document.getElementById('myForm'))
    
  3. Pass them to the fetch as the request body.

    fetch('url', {
      method: 'POST',
      headers: {
        'Content-Type': 'multipart/form-data'
      },
      body: formData
    })
    
Phil
  • 157,677
  • 23
  • 242
  • 245
Pavel B.
  • 843
  • 1
  • 7
  • 17
  • I tried your suggestion and now it reloads again. – TDonnally Mar 15 '20 at 22:36
  • @TDonnally ok, then you have to do it the hard way. Give me a sec – Pavel B. Mar 15 '20 at 22:38
  • 1
    OP doesn't have a JSON request body parser so you shouldn't be posting JSON. Also, you cannot `JSON.stringify()` a `FormData` instance – Phil Mar 15 '20 at 22:53
  • 1
    Also, if you're using the `onsubmit` attribute, you need to use `onsubmit="return onSubmit()"` for the event default to be prevented – Phil Mar 15 '20 at 22:55
  • 1
    @Phil thanks, I don't use onsubmit normally. I'm using click event like every other dummy and I don't even use form tag most of the time. – Pavel B. Mar 15 '20 at 23:03
  • I'd say it's much better to use `document.querySelector('#form form').addEventListener('submit', ...)` – Phil Mar 15 '20 at 23:11