Web Forms

Working with Select, Option, and Optgroup in HTML and JavaScript

source

The Select option and optgroup elements are used to define dropdown selection menus, the select element can take some extra attributes that alter the way that the menu is displayed, such as multiple which allows for the shift and ctrl selection of multiple values, and the size="n" attribute which defines how many options are to be shown on screen whilst the menu is open, when to revert to a scrollable view. The selected attribute preselects the given element when the page is loaded or the attribute is set.


<form action="#" name="myForm" id="myForm">
  <div class="formBox">
    <label for="thing">Username</label>
    <input type="email" id="thing" name="thing" />
  </div>
  <div class="formBox">
    <label for="flavours">Favourite Flavours</label>
    <select id="flavours" name="flavours" multiple size=6>
      <optgroup label="Maybe">
        <option value="dust">Dust</option>
        <option value="beer" selected>Beer</option>
        <option value="cheese">Cheese</option>
      </optgroup>
      <optgroup label="Maybe Not">
        <option value="cat">Cat</option>
        <option value="rust">Rust</option>
        <option value="octopus">Octopus</option>
      </optgroup>
    </select>
  </div>
  <div class="formBox buttons">
    <button id="btnSend">Send</button>
  </div>
</form>
				

When accessing a select form from javascript, there is an input event that we can use to grab the element that has been selected. However, when the multiple attribute is set, or listener will still only give us the first value of the selection each time.


function handleSelect(ev) {
  let select = ev.target; //document.getElementById('flavours');
  console.log(select.value);
}
				

We can use the obj.selectedOptions collection that is inside of the target object to retrieve all of the selected elements, we can iterate over this collection and construct from it an array of our chosen elements.


function handleSelect(ev) {
  let choices = [];
  for (let i = 0; i < select.selectedOptions.length; i++) {
    choices.push(select.selectedOptions[i].value);
  }
  console.log(choices);
}
				

There exists another way of achieving the same, this is an old hack that used to be used before the forEach loop was available. We create an empty array, using the map method for that array and we are calling it with the collection that we would like to call the map method upon.


function handleSelect(ev) {
  choices = [].map.call(select.selectedOptions, (option) => option.value);
  console.log(choices);
}
				

What is a FormData Object

source

As in time it became more common for folk to not use the form to submit data to a server, but more often to use a combination of the form with a javascript object to prepare the data to be sent by an XMLhttprequest or a fetch call to the server, when sending the data this way, a common solution was required to bundle the data for these methods. This is what the formData object is, a replication of the form in javascript; We can think of it as an array of arrays. The form content are stored inside of the object as key value pairs.

We can even append files to formData objects as name and then blobs, for most values, the data is a string. When a file is sent there is also a third optional parameter which is the file name.

We can use the Array object to output our formData to the console, and also the JSON object to stringify and print out as json.

Here we do exactly that, creating and then writing out a formObject as JSON.


document.addEventListener('DOMContentLoaded', () => {
    let fd = new FormData();
    fd.append('name', 'Bubba');
    fd.append('id', 1234);
    fd.append('created_dt', Date.now());

    console.log(Array.from(fd));

    for (let obj of fd) {
        console.log(obj)
    }

    document.querySelector('#output pre')
      .textContent = JSON.stringify(Array.from(fd), '\t', 2);
});
				

A formData object can also be sent to a server, using the following code.


let url = 'http://www.example.com/';
let req = new Request({
  url: url,
  body: fd
})
fetch(req)
  .then(response => response.json() )
  .then( data => {})
  .catch( err => {})
				

Using FormData Objects Effectively

source

The formData object can be used to very quickly and easily gather and send the form data within a form, rather than treating each field separately. For the formData object to work you have to use the name attribute inside of each of your fields, if you ascribe only an id, then the field will not be collected inside of the object.

When using the formObject we must first disable the forms natural behaviour using preventDefault() else the form will attempt to load the target page. Next all that we have to do is to pass in our target form data into a formObject, like so:


function handleForm(ev) {
  ev.preventDefault(); //stop the page reloading
  let myForm = ev.target;
  let fd = new FormData(myForm);
}
				

The fd now contains the entire form content. We can add extra data as key value pairs using the append method. The formData object has a keys() method which allows us to run a for (key of fd.keys()) {} loop, that will enable us to see all of the forms content.


for (let key of fd.keys()) {
  console.log(key, fd.get(key));
}
				

So now we can send of our form to the server. The server in this case will read the data as form data


document.addEventListener('DOMContentLoaded', () => {
  document
    .getElementById('myForm')
    .addEventListener('submit', handleForm);
});

function handleForm(ev) {
  ev.preventDefault(); //stop the page reloading
  //console.dir(ev.target);
  let myForm = ev.target;
  let fd = new FormData(myForm);

  //add more things that were not in the form
  fd.append('api-key', 'myApiKey');

  //look at all the contents
  for (let key of fd.keys()) {
    console.log(key, fd.get(key));
  }

  //send the request with the formdata
  let url = 'http://localhost:3000/';

  let req = new Request(url, {
    body: json,
    method: 'POST',
  });
  //console.log(req);
  fetch(req)
    .then((res) => res.json())
    .then((data) => {
      console.log('Response from server');
      console.log(data);
    })
    .catch(console.warn);
}
				

If we would like to send it as JSON then there is a technique for that as well, we can loop over the keys of the formData object and add each to a simple javascript object using each key as a key in the object map to store its corresponding value, once done we can then us our JSON.stringify function to serialise the object into the JSON format.


function convertFD2JSON(formData) {
  let obj = {};
  for (let key of formData.keys()) {
    obj[key] = formData.get(key);
  }
  return JSON.stringify(obj);
}
				

document.addEventListener('DOMContentLoaded', () => {
  document
    .getElementById('myForm')
    .addEventListener('submit', handleForm);
});

function handleForm(ev) {
  ev.preventDefault(); //stop the page reloading
  //console.dir(ev.target);
  let myForm = ev.target;
  let fd = new FormData(myForm);

  //add more things that were not in the form
  fd.append('api-key', 'myApiKey');

  //look at all the contents
  for (let key of fd.keys()) {
    console.log(key, fd.get(key));
  }
  let json = convertFD2JSON(fd);

  //send the request with the formdata
  let url = 'http://localhost:3000/';
  let h = new Headers();
  h.append('Content-type', 'application/json');

  let req = new Request(url, {
    headers: h,
    body: json,
    method: 'POST',
  });
  //console.log(req);
  fetch(req)
    .then((res) => res.json())
    .then((data) => {
      console.log('Response from server');
      console.log(data);
    })
    .catch(console.warn);
}

function convertfd2json(formdata) {
  let obj = {};
  for (let key of formData.keys()) {
    obj[key] = formData.get(key);
  }
  return JSON.stringify(obj);
}
				

Fieldsets and Legends in HTML and CSS

source

Fieldsets and legends can be used to group elements of a form, we could put a div and give that div a border to achieve similar, except that the fieldset is also tied to the legend element, when an element is declared within a fieldset the text content of that element becomes a title that is written upon the fieldset border.


<fieldset class="purple">
  <legend>Purple Team</legend>
  <div class="formBox">
    <label for="person">Famous Person</label>
    <input type="text" id="person" />
  </div>
  <div class="formBox">
    <label for="char">Favourite Character</label>
    <select id="char">
      <option value="rick">Rick</option>
      <option value="morty">Morty</option>
      <option value="summer">Summer</option>
      <option value="gerry">Gerry</option>
      <option value="birdman">Birdman</option>
      <option value="jessica">Jessica</option>
    </select>
  </div>
</fieldset>
				

In styling our fieldset's it can be nice to add some extra effect to differentiate our form a little. This technique to form a solid triangle is crated by adding a 0.5rem sized border to the ::before sudo element without giving it any size. It is effectively a bodiless dot with a border, the border becomes a square. To reduce this to a triangle, it is currently a square made up from four triangles, we need to make some of its border sides transparent. The remaining two sides give us our resultant triangle.


fieldset {
  padding-left: 1rem;
  margin: 0.5rem 0;
  position: relative;
}
fieldset::before {
  position: absolute;
  content: '';
  width: 0;
  height: 0;
  top: -0.5rem;
  left: 0;
  border-width: 0.5rem;
  border-color: currentColor;
  border-style: solid;
  border-bottom-color: transparent;
  border-right-color: transparent;
}
fieldset > legend {
  color: currentColor;
  padding: 0 0.5rem;
  font-size: 1rem;
  line-height: 1;
}
				

Mobile Keyboards and HTML Forms

Input with type and tab index

These element attributes require a mobile device in order to experience them.

When considering mobile devices and the entry of data into forms, there are two attributes that we need to remember, the type attribute and the tabindex. The tabindex tells the mobile device, the sequence in which it is to move through the form elements.

On mobile devices, the style and form of the keyboard is different depending upon the input type, for example, if the field is specified as being that of email, you will also get the @ key on the first layer of keys that are available. The keyboard also has arrows for the tab navigation of the tabindex order.

Using inputmode to Display Mobile Keyboards

source

<form id="myForm" action="#">
	<label for="email">Email</label>
	<input type="email" id="email" inputmode="email" />

	<label for="phone">Mobile</label>
	<input type="tel" id="phone" inputmode="tel" />

	<label for="age">Age</label>
	<input type="number" id="age" inputmode="numeric" />

	<label for="search">Search</label>
	<input type="search" id="search" inputmode="search" />

	<label for="url">URL</label>
	<input type="url" id="url" inputmode="url" />

	<label for="none">None</label>
	<input type="text" id="none" inputmode="none" placeholder="default" />
</form>
				

Enhanced Mobile Keyboards with EnterKeyHint Attribute

source

These element attributes require a mobile device in order to experience them.

Whenever you have an element that is user editable, form elements or textarea elements, or if you add contenteditable to the attributes of a div, making it editable also. Then we can add the attribute enterkeyhint="", this will allow us to change the label that shows up on the enter key. The available values that we can use are enter done go next previous search send. Where as the input mode type attribute will control the type of keyboard that will display, the enterkeyhint will just change the label that shows upon the key.

In the following code, the enterkeyhint is set upon each of the form fields, corresponding to their type.

HTML5 URL and SearchParams Objects

source

The URL object splits up a URL into all of its component parts, making it a much simpler thing to interact with, where as prior to its existence we would have had to have gotten out all of the specific data ourselves.

We can pass either our img element or anchor element into a URL object, extracting all kinds of properties from them, as we see in the following code example. We can either list all the properties using the forEach method, else access them individually using the get method.


parent.window.document.getElementById("btn-forms-07")
  .addEventListener('click', () => {
  let a = document.getElementById('link');
  let img = a.querySelector('img');
  let aHref = a.href;
  let imgHref = img.src;
  console.log(typeof aHref, aHref);
  console.log(typeof imgHref, imgHref);

  let iURL = new URL(imgHref);
  console.log(iURL);

  let aURL = new URL(aHref);
  console.log(aURL);

  aURL.searchParams.forEach((val, name) => {
    console.log(name, '::', val);
  });

  let val = aURL.searchParams.get('ikwid');
  console.log(val);
});

/*
URL properties
hash - The part that starts with `#` and refers to an id
host - Same as hostname except a specified port would be included
hostname - the domain part without the http://
href - the full url. Same as using .toString() method
origin - Same as host prefixed with the protocol
password - https://username:password@www.example.com
pathname - after the domain. Folder and file name
port - :80  :443
protocol - http or https usually
search - Begins with `?`. The query string
searchParams - An object built from the parsed query string
username - https://username:password@www.example.com
*/
				

JavaScript Unicode Characters and Escape Sequences

Within our code we are saving some strings as variables, notice that at the input of those strings into variables we have numerous instances of \u which when followed by a four digit number represents a unicode code point and this it's corresponding character.

The key commands for accessing unicode code points are var.charCodeAt(n) and String.fromCharCode(0x0434)

What is the Difference between encodeURI and encodeURIComponent

source

encodeURI and encodeURIComponent are two methods that we can use in JavaScript that allow us to convert text into its URI encoded version, meaning that any characters that have additional meaning within a URI are escaped by their URI encoded representation.

On our encoding section below you can see that abcd/+=&? is not altered by encodeURI but that it is changed to abcd%2F%2B%3D%26%3F by encodeURIComponent.


document.addEventListener("DOMContentLoaded", () =&gt; {
  document
    .getElementById("btnEncode")
    .addEventListener("click", doEncode);
  document
    .getElementById("btnDecode")
    .addEventListener("click", doDecode);
});
let code = parent.window.document.getElementById("code-09");
  code.addEventListener("click", () =&gt; {
  document.getElementById("start").value = code.innerText;
  doEncode();
});
function doEncode() {
  let txt = document.getElementById("start").value;
  let areaEncoded = document.getElementById("encoded");
  let areaEncodedComp = document.getElementById("encodedComp");
  areaEncoded.value = encodeURI(txt);
  areaEncodedComp.value = encodeURIComponent(txt);

  let lnk = document.getElementById("lnk");
  lnk.href = encodeURI(txt);
}
function doDecode() {
  let txt = document.getElementById("start").value;
  let areaEncoded = document.getElementById("encoded");
  let areaEncodedComp = document.getElementById("encodedComp");
  areaEncoded.value = decodeURI(txt);
  areaEncodedComp.value = decodeURIComponent(txt);
}
/**
encodeURIComponent - encodes all characters except:
  A-Z a-z 0-9 - _ . ! ~ * ' ( )
Encode everything. When you want to send some data that is encoded.
Useful for Base-64 data A-Z a-z 0-9 + /

encodeURI - encodes  all characters except the previous list AND
  ; , / ? : @ & = + $ #
Keeps the URL as a usable URL.

Sample text
http://user@pass:www.example.com:5000/path/file?name=value&name2=value2#someId

😀 😁 😂 🤣 😃 😄 😅
**/
				

The essential difference between the two methods is that encodeURLComponent can not encode a URL that is to function as a URL it can only encode components of a URL that exist between URL characters, where as encode URL can be used to output full functioning URL's.

The tabindex Attribute in HTML

The tabindex element can be used upon any html element, typically it is used upon anchor tags, buttons and form elements. In essence the tabindex makes the element focusable.

The value given to the tabindex attribute defines the order in which tab navigation occurs, first 1, then 2 then 3, finally any elements that have 0 assigned to them are iterated over in the order that they appear on the page. Any elements that have a negative tabindex are ignored.


<body>
  <main>
    <h1 tabindex="0">This is Focusable [0]</h1>
    <section>
      <p><a href="#" tabindex="3">Focusable Link [3]</a></p>
    </section>
    <form>
      <div>
        <label for="name">Name [2]</label>
        <input type="text" tabindex="2" id="name" />
      </div>
      <div>
        <label for="email">Email [1]</label>
        <input type="email" tabindex="1" id="email" />
      </div>
      <div>
        <label for="state">State [-1]</label>
        <select id="state" tabindex="-1">
          <option value="Assam">Assam</option>
          <option value="Bihar">Bihar</option>
          <option value="Chhattisgarh">Chhattisgarh</option>
          <option value="Goa">Goa</option>
          <option value="Gujarat">Gujarat</option>
          <option value="Haryana">Haryana</option>
        </select>
      </div>
      <div>
        <label for="comments">Comments [0]</label>
        <textarea tabindex="0" id="comments"></textarea>
      </div>
    </form>
  </main>
  <!--
    Accessibility Concerns
    - avoid using tabindex over 1 and changing the order
    - do not use tabindex on elements that are replacing real interactive elements. Eg: div instead of a button
  -->
</body>
				

HTML Dropdown Lists and DataLists

source

When setting up dropdown or option lists, we can set the attribute size="n" to turn the list from a dropdown list into a scrollable list, the size denotes the number of options that are to be shown at any one time, additionally we can use the attribute multiple to allow the user to select more than one value in one entry.


<select id="region" name="region" size="4" multiple>
				

The red entry is the that which has selected set as an attribute, it is styled in the css using the option:default sudo class.


<script>
  <option selected value="tasmania">Tasmania</option>
</script>

<style>
  .form.box option:default {
    color: red;
  }
</style>
				

The input box can be styled by adding a span element across the select element and then styling both accordingly.


select {
  background-color: transparent;
  border: none;
  outline: none;
  line-height: 1.2rem;
  width: 100%;
  color: white;
}
.list {
  background-color: deepskyblue;
  border-radius: 1rem;
  overflow: hidden;
  height: 4rem;
  padding: 0 1rem;
  flex-basis: 80%;
}
				

Saving User Input in JS Objects

source

Everything You Need to Know About Form Events

source

The most common events that we use for forms are input and submit, if you have a submit button which is to say within a form element you have one button that will send a submit signal as the most common way of submitting a form; We can either put a submit button on the form, or we can put a click event upon the button, either way we can pick up and treat the pressing of a button for form submission. The input event is triggered as you are typing, these are the two most commonly used events.


<input type="submit" id="btnSend" enterkeyhint="send" />
				
Or

<button id="btnSend">Send</button>
				
change focus blur
The change event fires when after entering some data into one field you leave it for another having modified or entered data, however the event is not fired if the data has not been altered. Blur is triggered whenever an element is entirely dropped from focus and focus when an element is first selected, selecting another element when you previously have had focus upon and element will trigger the blur event on that element.
click mousedown mouseup
As we click on an element the mousedown event is fired, and then the focus, the order is important here as it is always the same, when we release the mouse button the mouseup and then the click event both happen.
keydown keyup keypress
When we begin to type within a field we trigger the keydown, then the keypress after that the input, then finally the keyup. This is happening every single time that we press upon a key. Again should we now leave the element then the change event will fire, with a shift with the blur and then the new focus as the change is taking place.

We have all of these events that we can use, most of the time we will be using them to perform some sort of validation on the form. So depending upon whether we want to check as the user is typing, say for some sort of formatting whilst they are typing, then the input event is probably the best choice. If you want to evaluate that the entirety of the input that was typed matches against some logical pattern, then you could either use the blur upon leaving the filed or on change meaning that you have both changed it and you have left it. If you want to evaluate that the entirety of the input that was typed matches against some logical pattern, then you could either use the blur upon leaving the filed or on change meaning that you have both changed it and you have left it. If you want to evaluate that the entirety of the input that was typed matches against some logical pattern, then you could either use the blur upon leaving the filed or on change meaning that you have both changed it and you have left it.

When we place a name attribute upon a form element, it means that we can address that form directly by that name as a property of the document object.


<form name="sampleForm" action="#" method="POST">
<script>
  let form = document.sampleForm;
</script>
				

We can add a functionality for form submission by checking for the enter key on keypress. It is worthy of note that ev.which has been depreciated from the standard, but there is a chance that the code may still be out there in the wild as such it is good to be aware of it, that said, with this code we can test for the enter key having been pressed with keyboard events.


function handleKey(ev) {
  console.log(ev.type.toUpperCase(), ev.target.id, ev.target.value);
  let code = ev.keyCode || ev.which;
  if (code == 10 || code == 13) {
    //return or enter key
    handleSend(ev);
  }
}
				

document.addEventListener('DOMContentLoaded', () => {
  //add event listeners to form elements
  //name email phone pass lang btnSend
  let name = document.getElementById('name');
  let email = document.getElementById('email');
  let phone = document.getElementById('phone');
  let pass = document.getElementById('pass');
  let lang = document.getElementById('lang');
  let btn = document.getElementById('btnSend');
  let form = document.sampleForm;

  name.addEventListener('change', handleChange);
  name.addEventListener('input', handleInput);
  //input: better for validation than change in most cases
  name.addEventListener('keypress', handleKey);
  name.addEventListener('keydown', handleKey);
  name.addEventListener('keyup', handleKey);
  name.addEventListener('focus', handleFocus);
  name.addEventListener('blur', handleBlur);
  //blur: used to be used like placeholder
  //blur or change: can be used for validation when pattern needed
  name.addEventListener('click', handleMouse);
  name.addEventListener('mousedown', handleMouse);
  name.addEventListener('mouseup', handleMouse);

  email.addEventListener('focus', handleFocus);
  email.addEventListener('blur', handleBlur);
  phone.addEventListener('focus', handleFocus);
  phone.addEventListener('blur', handleBlur);
  pass.addEventListener('blur', handleBlur);

  lang.addEventListener('change', handleChange);
  lang.addEventListener('input', handleInput);

  //handle form submission
  form.addEventListener('submit', handleSend);
  btn.addEventListener('click', handleSend);
  //even with enter key
  document.addEventListener('keypress', (ev) => {
    console.log({ ev });
  });
});

function handleChange(ev) {
  console.log(ev.type.toUpperCase(), ev.target.id, ev.target.value);
}
function handleInput(ev) {
  console.log(ev.type.toUpperCase(), ev.target.id, ev.target.value);
}

function handleFocus(ev) {
  console.log(ev.type.toUpperCase(), ev.target.id, ev.target.value);
}
function handleBlur(ev) {
  console.log(ev.type.toUpperCase(), ev.target.id, ev.target.value);
}

function handleMouse(ev) {
  console.log(ev.type.toUpperCase(), ev.target.id, ev.target.value);
}
function handleKey(ev) {
  console.log(ev.type.toUpperCase(), ev.target.id, ev.target.value);
  let code = ev.keyCode || ev.which;
  if (code == 10 || code == 13) {
    //return or enter key
    handleSend(ev);
  }
}

function handleSend(ev) {
  ev.preventDefault();
  //stop the form from submitting and reloading the page
  console.log(ev.type.toUpperCase(), 'Submitting the Form');
}