Day 18: Forms
Lesson 3: Dynamic Forms
This is really more of a technique thing to just level up your developer abilities, let's make a form that lets us add more than one broom at a time.
Note, the code in here gets pretty chewy and isn't really something you would do in plain old JavaScript anymore. You would instead use something like React or another framework to do it. Once you've done it here though you will really appreciate the power that comes with frameworks like React, Vue etc.
Add this to your CSS
1fieldset { 2 border: none; 3 padding: 10px 0; 4}
Then change your form to this, we are wrapping each set of input fields in a <fieldset>
and a <div>
. The <div>
is so we have an element to append new rows of inputs to and the <fieldset>
is so we have a node to clone to create more inputs.
1<form id="stock-form"> 2 <div id="new-brooms"> 3 <fieldset id="first-broom"> 4 <label for="name">Broom Name</label> 5 <input type="text" name="name" value="comet" /> 6 <label for="price">Price</label> 7 <input type="number" name="price" required value="3" /> 8 <label for="quantity">Quantity</label> 9 <input type="number" name="quantity" required value="5" /> 10 </fieldset> 11 </div> 12 <button type="button" id="add-broom">Add</button> 13 <button type="submit" id="form-submit" disabled>Save</button> 14</form>
First up let's add an event handler to the new Add button.
1const addBroomButton = document.getElementById("add-broom"); 2 3const handleAddBroom = () => { 4 const fieldset = document.getElementById("new-brooms"); 5 const newBroom = document.getElementById("first-broom"); 6 7 fieldset.appendChild(newBroom.cloneNode(true)); 8}; 9 10addBroomButton.onclick = handleAddBroom;
You're familiar with the pattern now, get a DOM element, create a function, add function to element event handler. The fun bit here is that we are grabbing the fieldset with the id first-broom
, cloning it and appending it. The .cloneNode(true)
does the work there and the true
is an optional parameter to get the full tree of nodes below it (like the inputs and the labels etc).
Click Add a few times and you will see we get new rows of inputs on our form 🎉
Now for the hard bit. Change your handleSubmit
to this.
1const handleSubmit = (e) => { 2 e.preventDefault(); 3 4 const formData = new FormData(e.target); 5 const formEntries = Array.from(formData.values()); 6 7 let newBrooms = []; 8 9 for (let i = 0; i < formEntries.length; i += 3) { 10 const newBroom = { 11 name: formEntries[i], 12 price: formEntries[i + 1], 13 quantity: formEntries[i + 2], 14 }; 15 16 newBrooms.push(newBroom); 17 const newLi = createRow(newBroom); 18 ul.appendChild(newLi); 19 } 20 21 e.target.reset(); 22};
We get the formData
and then make an Array from it's values. Sadly this just gives us an array that looks like this:
1["comet", "500", "4", "nimbus", "2000", "2"];
Basically a repeating pattern of name, price and quantity. Sadly there's no easy way to get an array of objects like you would expect. You can use .entries()
which gives you the name from the element, however that actually then makes the rest of the code slightly harder.
But, we have a pattern that we can rely on, so let's abuse a for
loop.
We need to chunk that array up into groups of 3, so we do that with:
1for (let i = 0; i < formEntries.length; i += 3) { 2 const newBroom = { 3 name: formEntries[i], 4 price: formEntries[i + 1], 5 quantity: formEntries[i + 2], 6 };
Which is just like any other for loop but we jump 3 at a time. We then use i
, i + 1
, and i + 2
to access the 3 elements of the array we need for that iteration and create a new broom.
We then add that newBroom
to the array and then add it to the DOM using the same function as earlier to create an <li>
.
1newBrooms.push(newBroom); 2 const newLi = createRow(newBroom); 3 ul.appendChild(newLi); 4}
Then we reset the form.
Fill in the form and click save.
Improvements
There are lots of edge cases with this code we haven't accounted for. With a bit of Googling and some brain power see if you can solve these problems.
- We don't reset the form back to just one row of inputs
- Save button validation only works for the last row
- Adding a new row copies the content of the first with it