Building Small Form-Based JS Apps: Step-by-step with ES6
In the following article, I’m going to outline the notes I used when learning how to build a form-based JS app using ES6. This particular example is a “Recipe Box” (h/t Flatiron School for the excellent, in-depth review session this was based on!).
A note before starting
Remember: always have a console.log(something) in the body of each function as your are building to make sure things are connected! Console logging as I build out a program is one of the best things I’ve learned as a developer. When I was first starting out, it seemed useless, but the more work and life experience I have, the more I realize that being meticulous matters. Folks who work the line in a kitchen have mise en place. Writers use outlines and notes. Programmers debug somehow as they go (whether it’s using debugger, console log, TDD, or any other tool, the point is really just that you’re doing something.)
Start with the HTML
Do a HTML mockup (static) of the first portion that will be the first screen that the user will be interacting with:
Until there is a javascript file, there won’t be any interactivity, so we will start with connecting the pages.
Link the JavaScript file to the document, and make sure that when the page is refreshed, you see code being executed in the console. Make sure to include <script src=”index.js”></script> in the HTML. Remember that the bottom script src has access to all the information from the sources above it, but the ones on top do NOT have access to the content of those below it. Often goes in the head, at least with apps for now
Start the index.js
Add one event listener with ‘DOMContentLoaded’. Remember that all of the body of our code for the javascript will eventually live here, inside this function. There are other functions you could use here, such as window.onload()
Begin binding event handlers to the HTML
First, create a simple event handler connected to the form. Start with something such as an event triggered upon submission. When you submit the form, console log something to see if the event handler is working.
Remember, the way that JavaScript handles events is through parents and child nodes. Any time we trigger an event, the event bubbles up, through children and parents to the top most parent, and then comes back down. What does this mean? It means that all of the parents of the element that is clicked (or has some sort of event handler attached) are going to receive that click event. This allows for event delegation.
Note: if all of a sudden there more than one event happening, or one event is triggering too many or incorrect responses, you have a problem with your event handler
Now, on form submission, write to the console the name of the ingredient in this case (or whatever the input for your form is).
This ensures that the event handler works and gets triggered at the right time. This means you can get the VALUE of the form and write it to the console. Make sure it’s the value from the form input field (it’s not the same as the form id value from the event listener!) Then clear the value after it has been logged to the console.
Rendering to the page
Since the connection has been tested, and you can be sure of the correct information, we want to dynamically generate a line item and append it to the unordered list in the HTML form on page submission.
First, we will do this in a way that is NOT keeping track of ingredients. There are no objects, etc. The first step is just getting it to appear on the page.
Make a new ingredient by creating an element of the desired type. In this example, that’s an <li>.
Then set the new instance into the value of this. Append the new ingredient to the type of element.
Essentially, the above is three steps: you’ve made the element, you have the value, now attach the value to the element.
Then, find the UL from the HTML that you created and add an id tag on the index.html file. Then, using getElementByID(), add the new element that you’ve just created, with value, into this UL.
Refactoring into a separate function
Now, we are going to separate this out into its own function, and pass that function in as an argument into the event listener at the top
Remember not to CALL the function, just refer to it as an argument. (This is one of the best things about JavaScript — pass by reference is so helpful!)
This is solving the problem in a stateless way — we add an LI element, and then that element essentially is no longer accessible. But if we want to track this, we will have to add something to keep track of state and context — we want to maintain state and the runtime to have some kind of context of how many ingredients we have…
Here is the stateless way:
If I want to have a list of ingredients, how could we represent a single ingredient? We essentially just need to know the simplest version of what an ingredient could look like. What is that? A javascript object with a name property and a string.
Thinking about data structure
How do we do this? You can either make a JavaScript “class” which generates an instance of an object, or just make an object!
What is the difference between an object and an instance of a class? The class is more like a factory. It can make many objects of that class following the same specifications over and over again.
Alternatively, you can use an array to keep track of objects.
This example is about what is the most minimal approach, the simplest way that we can access, store, and work with data. Other more complicated data structures would be appropriate for larger applications.
So in this case, make an object with a key of “name”, and store all of these objects in an array. So start by assigning the array to a constant.
A note on let vs. const
How do we determine let or const? It depends on reassignment. Whenever we say var x = that is an assignment. So if we change this, and we change where this is pointing, we should use LET. If it will never change, then we can use CONST.
Considering state
We’re also going to make a new function that will take state into consideration. Remember always to preventDefault(). Remove the reference to the previous function (addAnIngredient) and replace it with a new function that we’re making now:
Then, work on the object. This new function should create a new function, with a name, and we should log the object to the console. Create a new object, add the name property, and set it to the input value.
Here is the new refactored function using objects:
Here is an example of the object the function prints to the console using Chrome developer tools.
Now, each time an element is added, it simply creates an object. But, a new element is created, the other one kind of gets thrown away. And that’s not great! So, now, as each object is created, add it to the array. Have the function console log the entire array each time it updates, to be sure that the objects are adding correctly.
Overview:
Appending to the HTML with the new function
Now, there is a JS version of ingredients, but there needs to be an <li> that appends to the HTML for the objects to render as text on a page. To do this, iterate over that array and generate a string that represents all of the <li> in the same function.
Then, find the div in the index.html file where the text should go and replace its contents. Why replace? It is marginally less efficient, and could become extremely inefficient if the array became huge, but by re-rendering every time, it’s easier to have a single source of truth, or one variable that is managing our state.
How to write it
<li> tags always surround whatever interpolated value that is passed through, and then joined. The entire string is being passed to the inner HTML and then the HTML is reading this as an HTML file.
What would that really look like?
"<li>Salt</li><li>Pepper</li><li>Onions</li><li>Butter</li><li>Stock</li>"
This string is then passed to the index.html file, where it is read just like any other HTML. How does it get passed? Set the innerHTML of the div (as mentioned above) to the value of that string.
More refactoring!
This is a point to take a step back and refactor the code, because this big function is doing many different types of activities:
— creating an object
— adding the object to a collection
— rendering something
These are three different types of functionality! Get the most basic version running (as is all here above), and then we can worry about separation of responsibilities — why is this an easier method? All the necessary code is here, doing the right thing — splitting it it up now is quite simple.
Adding additional elements to the form
Now, move on to selecting ingredients from the pantry, adding them to a recipe (checkboxes), and adding notes (a big text field) to the recipe to make the full recipe!
Start again with a static model to make sure the checkboxes are working, a text area to describe the steps or notes, and a save recipe. This should all go in a div below the ingredients container, which you can think of as a separate section of the application.
Now that this is mocked up, iterate over the array, and add the ingredients to the checkbox list, and then, determine whether or not they are checked off.
How to do this with code? .checked() returns a boolean value!
First, dynamically generate the checkboxes HTML string.
Just like before, a function that returns a long string of text (from all of the ingredients array) with the proper html tags is all that is needed in order to generate those checkboxes.
Now pass the outcome of this function to the HTML. When adding an ingredient, make the list and make a checkbox entry.
Now, iterate through the array and find all of the checkboxes that are checked and find the matching ingredient (that is connected to the checked checkbox)
One good CSS Selector to use in this instance is document.querySelectorAll(“input[type=’checkbox’]”)) to select all of the checkboxes.
NOTE: This is not an array! it looks like an array. It is a node list — all the elements that match that query selector. We can use forEach() here, but then we will have to do something differently.
For each → we want to pass in a callback function, see if the boxes are checked, find the matching ingredient, and add the name of the matching ingredient object to an array.
See below, using anonymous arrow functions (one of the many lovely perks of ES6 in this example!)
Put this together with a JavaScript object with three keys: name, description, and ingredients (ingredients being the return value of this most recent function).
All of this comes from the input field, which allows us to return an object that represents a recipe — you might want to have a recipe or an object array of recipes.
Again, as done previously, iterate over the array to generate the HTML to generate the recipe. Set container.innerHTML = the string from the array.
This adds a recipe to the recipe collection!