Of all the hats JavaScript can wear, its form-processing features are among the most sought and used. Learn how to use JavaScript for form processing, validation, and more. Credit: Dmitry Baranovskiy Forms are an essential part of HTML pages, and developers typically use JavaScript to elaborate on how they function. You can best understand the benefits of using JavaScript with HTML forms by first studying how forms work with straight HTML. The essence of a form is to accept input from the user and then submit it to a server of some kind. The server may be the back end that generated the HTML, but not necessarily. The lifecycle of a simple HTML form is to allow the user to input data into its fields, then submit the data and reload the page. JavaScript lets us create a smoother alternative to this clunky flow of events. We can take the information from the form and submit it quietly in different ways; for example, we might update the data incrementally as the user updates the form. This technique is part of the Ajax (Asynchronous JavaScript and XML) approach, which was a big deal when it first appeared. The mechanisms have been refined over the years but the basic idea remains the same. The ability to handle data and communication dynamically revolutionized web application front ends and it continues to enable single-page and multi-page application-style web interfaces. Using JavaScript in HTML forms Beyond using JavaScript to alter the lifecycle of form data, we can use it for a variety of other services like validating input and enabling more sophisticated features in our form pages, like sliders and shuttles. Let’s begin by looking at an HTML form and some vanilla JavaScript to go with it, as shown in Listing 1. You can also see this example running in a live fiddle. Listing 1. A simple HTML form with JavaScript <form name="myForm" action="" method="GET"> Enter something in the box: <br> <input type="text" name="inputbox" value=""> <input type="button" name="button" value="Click" onClick="testResults(this.form)"> </form> function testResults (form) { var inputValue = form.inputbox.value; alert ("You typed: " + inputValue); } Listing 1 is very simple but it has all the basic elements of an HTML form with JavaScript. In this case, the JavaScript takes the input value and displays it in an alert popup. Here’s an overview of the parts of the page: declares a new form: name="myForm" names the form. Elsewhere in the JavaScript you can reference this form by the name myForm. The name you give your form is up to you, but it should comply with JavaScript’s standard rules for naming variables and functions (no spaces, no weird characters except the underscore, etc.). action="" defines where you want the browser to send the form info. This field will be a URL when defined. In this case, it is blank because we aren’t actually sending the form anywhere. method="GET" defines how the method data is passed to the action destination. The possible values are GET and POST, meaning URL-encoded or body-encoded, respectively. starts an input element: type="text" defines the type of input. name = “inputbox” gives a name to the input, which we’ll use later to access it. defines a button object. If the type were “submit” the button would automatically submit the form: onClick="testResults(this.form)" is an event handler. It tells the browser to invoke the given JavaScript function when the button is clicked, then pass in an object representing the form. It is also possible to create an input with type=”submit”, then capture the onSubmit event, prevent the default submit behavior, and then proceed as usual. The testResults() function is defined in our JavaScript. It obtains the value in the input field by taking the form that was passed as an argument and then looking at inputbox.value. This is the standard browser object model (BOM) for the form object: the form has a field by the name of each input, and the value holds the value for that input. Form action in JavaScript Let’s take a minute to discuss the action attribute on the form. When handled by the browser’s default behavior, the action field is a URL telling the browser where to send the data. When we take control of the data with JavaScript and Ajax, we manually specify where we are going to send the info (one way is by using the data field in with a fetch call, which I’ll demonstrate shortly). Sometimes, you’ll see the URL set on the form, and then the JavaScript will programmatically pull the value from the form action field to use as a destination for an Ajax request. You can always find the form action by looking at the action field; for example: document.getElementById(myForm).action. Using Ajax in an HTML form Now let’s do something a little more interesting with the data in our form. As a start, we can send it off to a remote API for some simple Ajax. We’ll use the endpoint https://echo.zuplo.io, which takes whatever it gets and echoes it back. We’ll modify our JavaScript to look like Listing 2, and we don’t need to change the HTML at all. (You can also check the live version here.) Listing 2. Form handling with a remote echo function testResults (form) { var inputValue = form.inputbox.value; fetch("https://echo.zuplo.io/", { method: "PUT", body: "Very interesting: " + inputValue }).then((response) => response.text()) .then((responseText) => { alert(responseText); }) .catch((error) => { console.error("foo: " + error) }) } In Listing 2, we do the same thing at first by grabbing the value off the form input. Instead of just displaying it in an alert, though, we use the Fetch API to send it to our remote service. We then use the fetch promises (via the .then fluent chain) to do something with the response. In our case, we just open an alert. But we have here seen the essence of Ajax and asynchronous form data handling. The ability to take fine-grained control of the data and network like this is the definitive feature enabling modern front ends. The Fetch API The Fetch API and its fetch() method are built into browsers. Coming to it as a beginner, the name “fetch” is a bit misleading. This API doesn’t just fetch; it does almost any kind of communication you need including using the HTTP PUT method as we are doing here. The Fetch API is the modern successor of the earlier XMLHttpRequest(). Not only does it do a good job superseding that API, it is powerful enough to avoid the need for a third-party library like Axios in many cases. Working with data formats in HTML When we send a request over the wire with fetch or any other mechanism in the browser, we can choose how to format the data we glean from the form. In practice, a common approach is to use JSON, especially with RESTful services. In that approach, you marshal the JSON by converting the fields and their values into a JavaScript object, then send it as a string in the body of the request. (It’s possible to incorporate some information in the URL as query parameters, where it must be URL encoded, but for complex data, the request body is more useful.) Assuming we have decided upon a JSON body, the question becomes: how do we turn the form into JSON? We could transform it by hand but that quickly becomes painstaking and error prone. Two approaches with vanilla JavaScript are to use a FormData object to wrap the form or use the Object.fromEntries() method. Both options are shown in Listing 3, and the live version is here. (Note that hereafter I won’t show actually submitting the data since you already know how to do that. We would put the marshaled JSON into the request body.) Listing 3. Use FormData or Object.fromEntries to turn a form into JSON <form name="myform" action="https://echo.zuplo.io/" method="POST"> <input type="text" name="inputbox" value="" /> <input type="range" name="range" min"0" max="100"> <input type="color" name="color"> <input type="button" name="button" value="Click" onClick="testResults(this.form)" /> </form> function testResults (form) { let inputValue = form.inputbox.value; let formData = new FormData(form); let object = {}; formData.forEach(function(value, key){ object[key] = value; }); var json = JSON.stringify(object); alert(json); alert(JSON.stringify(Object.fromEntries(formData))); } You might notice in Listing 3 that I added a couple of new controls—a range and color picker—to make the JSON more interesting. If you click the button now you’ll get alerts with text like so: {"inputbox":"Merry Christmas","range":"50","color":"#000000"}. These approaches create valid JSON with minimal fuss but run into problems with more complex forms. For example, multi-select inputs will break with Object.fromEntries, and they require extra handling using FormData. See Stack Overflow for a good discussion of these issues. You may also encounter issues with file uploads. File inputs actually send binary data in their field. It’s also possible to have a multi-select file input, which sends an array of binary chunks. See How to easily convert HTML Form to JSON for a good description of dealing with this issue and others like it (such as multiple fields with the same name) when manually constructing JSON out of FormData. Validation You’ve had a taste of dealing with the data in forms. Back in early days, validating forms was one of JavaScript’s main callings. Browsers have introduced fairly robust validation for forms since then, but there is still a place for using JavaScript. For example, the browser can handle many validation needs like email, numeric ranges, or even free-form regular expressions. But what about something more involved? Let’s say we wanted to support a numeric input whose maximum value was defined by another field? We could handle such a requirement with JavaScript. Even more fundamentally, the browser cannot do back-end validation checks. That is to say, what if we have a username field whose validation says it can’t reuse an existing username? This kind of thing requires a round trip to the server, which we can accomplish with an Ajax request. The user enters a value, we send off a request with it, and the server compares the value against what’s in the database, then sends a response letting us know whether the username is valid. We could perform this kind of validation in a couple of places. We could do it when the user submits the form, or when the username field is blurred (meaning that it loses focus), or even as the user types (for this, we would use some kind of throttling or de-bounce to ensure it was not choppy). Listing 4 and the live version of the code on fiddle will give you a sense of how a simple validation works. Listing 4. Simple validation of text box while typing // HTML Enter something in the box: <br> <input type="text" name="inputbox" id="inputBox" value="" /> <p id="msg"> // JS let inputBox = document.querySelector('#inputBox'); inputBox.addEventListener('input', (event) => { if (["frodo","bilbo","sam"].includes(event.target.value)){ document.querySelector('#msg').innerHTML="Already have that Hobbit"; } else { document.querySelector('#msg').innerHTML=""; } }) Listing 4 introduces a few new ideas. There is now a paragraph element with the ID of “msg”, which we’ll use to display the error message if the user picks an existing username. In the JavaScript, we attach a listener to the inputBox field programmatically using the addEventListener, whereas before we did it declaratively in the HTML. By listening to the input event, we’ll be notified every time the user types (if we used the change event, we’d be notified when the user commits the change by clicking Enter). Instead of sending the value of the input (event.target.value) off to a server to check for a duplicate username, we can just check it here against an array (in our case, we have three Hobbit names already registered). If we find the name already in our array, we set a message on the UI by finding the “msg” paragraph and setting its text, or clearing the error message if the name is not a duplicate. (Again, in practice we’d throttle the events to prevent stuttering in the UI as round-trip requests were made to the server.) Typeahead, autosuggest, and autocomplete You just saw a simple example of doing back-end validation. Now let’s turn our attention for a moment to the common requirement of typeahead (also known as an auto suggest or autocomplete). Google’s incorporation of this feature has made it enormously popular. The validation example from Listing 4 is actually pretty close to the basics of a typeahead implementation. Instead of checking for duplicate usernames, we’d search for results that could fulfill what has been typed so far, and then present them in a dropdown or directly in the text field. Frameworks and libraries for JavaScript in forms Thus far we have restricted ourselves to creating forms with plain old JavaScript. In reality, there are many frameworks and libraries that we could use for form handling, ranging from dead simple to very elaborate. We could pick a library dedicated to handling forms, or we could let a generalist framework guide our choices. Let’s look at some of the better options. jQuery Of the non-reactive libraries, jQuery is the champion and despite its age it remains powerful and up-to-date. (Many of the good ideas about CSS selectors now found in browsers originated with jQuery.) jQuery puts some extra form handling capabilities at your disposal, including plugins and a set of components that can be used to build forms. jQuery is like vanilla JavaScript with superpowers. It’s a utility library that helps make things easier and helps you avoid having to write the same code over and over. How things work in jQuery is very similar to what you’ve seen with the examples so far. Reactive frameworks: React, Vue, Angular, and Svelte In contrast with a library like jQuery, reactive libraries are quite different from vanilla JavaScript. A reactive library will manage the connection between the UI and the state, which goes for forms, as well. This might seem like a small shift but it turns out to be incredibly powerful. Listing 5 has a small React form example with some of the ideas we have seen so far implemented in a reactive fashion (you can also check the live version). Listing 5. A JavaScript form built using React function MyForm () { const [username, setUsername] = React.useState(''); const [msg, setMsg] = React.useState(''); const [isValid, setValid] = React.useState(true); React.useEffect(() => { if (isValid === false) { setMsg("Already have that Hobbit!"); } else { setMsg(""); } }, [isValid]); function handleUsernameChange (e) { setUsername(e.target.value); } function validateUsername (e){ if (["frodo","bilbo","sam"].includes(e.target.value)){ setValid(false); } else { setValid(true); } } function onSubmit(event) { event.preventDefault(); if (isValid){ alert('Form submitted. firstName: ' + username); } else { alert('Invalid username'); } }2 function handlePasswordChange (e) { setPassword(e.target.value) } return ( <form onSubmit={onSubmit}> Enter something in the box: <input type="text" value={username} onChange={handleUsernameChange} onInput={validateUsername}/> {msg}valid: {JSON.stringify(isValid)} <button type='submit'>Submit</button> </form> ) } ReactDOM.render(<MyForm />, document.querySelector("#app")) The first thing you’ll notice is that the markup for the form is no longer in the HTML pane: it now lives inside the JavaScript, in a format called JSX. JSX is like HTML with special JavaScript abilities that link it to the reactive library. (Notice, also, that I switched the “language” to React in the JavaScript panel of the JSFiddle, as shown in Figure 1.) IDG Figure 1. A JavaScript form created with React. React takes the single line of markup (<div id="app"/>) and replaces it with the reactive UI defined by the return value of function MyForm (a functional component). If you look at that markup, you’ll see the text input has value={username} defined as an attribute—this is a value binding, a basic form of reactivity. React will ensure that whatever is in the username variable will be set on the input. The username value is handled by the useState “hook” (a hook is a fancy kind of function that performs specialized jobs within a functional component), like so: const [username, setUsername] = React.useState('');. This says: give me a reactive variable, “username,” that is set with “setUsername” and initialize it to a blank string. This is generally called the state of the component. To add our keystroke validation, we use an event listener defined like so: onInput={validateUsername}. This says: when the onInput event triggers, call the validateUsername function. That function was defined earlier in the body of our component, and what it does is set the “isValid” variable according to the same logic of whether the username is already in use (in our real application, we’d call the back-end service). So, in this scenario, isValid is another state variable. We then use another hook, useEffect, to watch what happens to isValid. Depending on the state, useEffect will set the value for the msg variable (yet another state variable). If you look at the markup for the component, you can see the msg variable is displayed with this line: {msg} (the curly braces indicate a JavaScript expression to be interpolated into the markup). When the form is submitted (via the Submit button) we hijack the process with this event listener on the form: <form onSubmit={onSubmit}>. event.preventDefault() ensures the browser default behavior doesn’t occur, and then we use the isValid variable to determine if the form can be submitted or not. Conclusion This was a whirlwind tour of JavaScript and some of its applications to forms. The most fundamental power is that JavaScript allows you to take fine-grained control of how form data is handled. Moreover, you can do a variety of useful things like validation and typeahead. Modern applications rely heavily on JavaScript for form processing, often using a reactive framework. Related content feature 14 great preprocessors for developers who love to code Sometimes it seems like the rules of programming are designed to make coding a chore. Here are 14 ways preprocessors can help make software development fun again. By Peter Wayner Nov 18, 2024 10 mins Development Tools Software Development feature Designing the APIs that accidentally power businesses Well-designed APIs, even those often-neglected internal APIs, make developers more productive and businesses more agile. By Jean Yang Nov 18, 2024 6 mins APIs Software Development news Spin 3.0 supports polyglot development using Wasm components Fermyon’s open source framework for building server-side WebAssembly apps allows developers to compose apps from components created with different languages. By Paul Krill Nov 18, 2024 2 mins Microservices Serverless Computing Development Libraries and Frameworks news Go language evolving for future hardware, AI workloads The Go team is working to adapt Go to large multicore systems, the latest hardware instructions, and the needs of developers of large-scale AI systems. By Paul Krill Nov 15, 2024 3 mins Google Go Generative AI Programming Languages Resources Videos