30 Days Of JavaScript

Day 18: Forms

Lesson 1: Submitting forms

onsubmit

Let's make a stock list for the Broom Stick Shop in Harry Potter, just something that helps them keep track of what they have in stock.

We need some more CSS, so replace the contents of your <style> tags with this.

1body {
2  font-family: "Open Sans", "Helvetica Neue", sans-serif;
3}
4
5#container {
6  width: 800px;
7  margin: auto;
8}
9
10h1 {
11  text-align: center;
12}
13
14ul {
15  font-size: 20px;
16  line-height: 35px;
17  padding: 0;
18}
19
20form {
21  font-size: 18px;
22}
23
24button {
25  padding: 5px;
26  min-width: 80px;
27}
28
29input {
30  padding: 5px;
31  border-radius: 5px;
32  border: 1px solid gray;
33}
34
35li {
36  list-style-type: none;
37}
38
39#red-text {
40  color: red;
41}
42
43#blue-text {
44  color: blue;
45}
46
47.title-large {
48  font-size: 48px;
49  font-weight: bold;
50}
51
52.title-small {
53  font-size: 24px;
54  font-weight: bold;
55}
56
57.hidden {
58  display: none;
59}
60
61.warning {
62  color: orangered;
63}

Then add this to the contents of the <body> in your HTML:

1<div id="container">
2  <h1>The Broom Shop Stock</h1>
3  <ul id="stock-list"></ul>
4  <form id="stock-form">
5    <label for="name">Broom Name</label>
6    <input type="text" name="name" />
7    <label for="price">Price</label>
8    <input type="number" name="price" />
9    <label for="quantity">Quantity</label>
10    <input type="number" name="quantity" />
11    <button type="submit">Add</button>
12  </form>
13</div>

Then finally add this to your JavaScript:

1const createRow = (broom) => {
2  const li = document.createElement("li");
3  const name = document.createElement("span");
4  name.innerText = broom.name;
5  const price = document.createElement("span");
6  price.innerText = broom.price;
7  const quantity = document.createElement("span");
8  quantity.innerText = broom.quantity;
9
10  const currencySpacer = document.createTextNode(": $");
11  const quantitySpacer = document.createTextNode(" x ");
12
13  li.appendChild(name);
14  li.appendChild(currencySpacer);
15  li.appendChild(price);
16  li.appendChild(quantitySpacer);
17  li.appendChild(quantity);
18
19  return li;
20};
21
22const stock = [
23  { name: "Nimbus 2000", price: 1000, quantity: 8 },
24  { name: "Air Wave Gold", price: 50, quantity: 6 },
25];
26
27const ul = document.getElementById("stock-list");
28
29stock.forEach((broom) => {
30  const newLi = createRow(broom);
31  ul.appendChild(newLi);
32});

All being well when you run that code you have a list with two brooms and a form below it that let's us enter the details of another broom.

Let's just take a look at this code and see what it does.

We have a function called createRow that takes a broom property, creates an <li> element, creates some <span>s and text nodes, puts them all together and returns the <li>.

We have an array we called stock with some broom sticks in. We then grab a reference to our stock-list, loop over the stock array and create an <li> for each broom stick and then add it to the <ul>

(I bet you're glad we aren't working in playgrounds anymore right? 😉)

Why don't you fill in the form and then click add, what happens?

Nothing?

Not quite.

What actually happens is the action attribute is used to submit the form. Except our action attribute is empty so all that happens is our page reloads and the form clears.

You see the expectation of the browser is that the form will be submitted to a URL, that will take the contents of the form, do something with it and return a new webpage.

We don't want that in this case though, we just want to make our form add a new broom to our list.

Let's add a submit handler to the form.

First of all add this function to the end of your JavaScript.

1const handleSubmit = (e) => {
2  console.log("I got your form!");
3};
4
5const form = document.getElementById("stock-form");
6form.onsubmit = handleSubmit;

This creates a function called handleSubmit and then assigns that to the forms submit event. This still doesn't get us to where we need to though.

If you click add you'll see a very brief flash of the I got your form message before it goes away, this is because the browser still submits the form to the action url which causes the page to refresh.

If you click the cog/gear icon in the top right of you dev tools console you'll see a new menu drop down, one of the options is Preserve log, click that and then click the add button again, you'll see that the logs persist and you see a handy Navigated to http://localhost:5500/? to show you what's been going on.

preventDefault

So, we are fighting the default event behavior from the form. How do we stop the form redirecting the browser when we submit the form then?

Well to stop that there is a handy little method on the event called preventDefault

Change your handleSubmit function to this.

1const handleSubmit = (e) => {
2  console.log("I got your form!");
3  e.preventDefault();
4};

And now click add. Fixed 🥳

Now you will see I got your form and an ever increasing number next to it, to show how many times it's been logged (click Add many times to see what I mean)

Preventing the default form behavior is very common, this is how we stop it from happening.

Right, let's finally do something with our data.

Accessing form data

Each <input> has a name attribute, thanks to this we can get a nicely formatted object of our forms values with just two lines of code.

Update your handleSubmit to this.

1const handleSubmit = (e) => {
2  e.preventDefault();
3
4  const formData = new FormData(e.target);
5  const formProps = Object.fromEntries(formData);
6  console.log(formProps);
7};

The last time we saw the new keyword was all the way back in the first few days when making sets and maps. Whenever you see new in JavaScript you are about to execute a constructor to get a new instance of an object or class.

The object type here is FormData and it has a constructor that we pass in an HTML form and get back an object that has a whole host of methods on it for interacting with the data. Annoyingly all of those methods are a bit awkward to work with so instead of using those we just use the .fromEntries class method on Object to get a new object from FormData that has what we need 😊

Note: Don't worry if that paragraph is stretching your brain. You're still incredibly early to your JavaScript journey and things like class methods and constructors are solidly intermediate JavaScript knowledge that, in my experience, it's important to have an awareness of when you're a beginner but is something you can work up to fully understanding over the course of a few months working with the language. Remember, I'm not trying to teach you every single aspect of JavaScript, just the stuff you need to be competent enough with in order to build things.

Looking at the console.log this object should look very familiar to you.

1{
2  broom: "Comet 140",
3  price: "300",
4  quantity: "4",
5}

With the exception of the numbers being strings, this looks just like the objects in your stock array. Which means, once we deal with the whole stringified numbers issue, it's pretty easy to add it to our list. Just add these two lines to the end of the handleSubmit function.

1const newLi = createRow(formProps);
2ul.appendChild(newLi);
3e.target.reset();

And like magic your list updates. Also, the e.target.reset() resets the form. HTML Form objects have a reset() function that can be called on this, since e.target is a Form we can call that function directly and clear the form.

Outline

Go Pro?

If you upgraded to pro, sign in here

  • About
  • Blog
  • Privacy
Looking to email me? You can get me on my first name at allthecode.co