Unleashing the Hidden Power of HTML Forms
Tags: html, javascript, webdev, tutorial
Published: 2024-05-12 07:27:11

HTML forms are powerful beasts, but I feel a lot of its übercool features are often overlooked.

Typically seen just as tools for data submission, forms offer rich functionalities beyond inputs and validation.

## Collections
All the inputs and outputs you put in a form, belongs to it's **elements**-collection:

- `<button>`
- `<fieldset>`
- `<input>` (except `type="image"`)
- `<output>`
- `<select>`
- `<textarea>`
- form-associated custom elements

This collection is _live_, meaning you can add and remove members, and it will be reflected in `elements.length`. Within the elements-collection you have `namedItem`. If you have a basic input within a form:

```html
<form>
  <input name="firstname" value="John">
</form>
```

You can grab it's value with:

```js
form.elements.firstname.value
// or
form.elements.namedItem('firstname').value
```

> **DISCLAIMER:** To simplify the examples and make them more focused on what I write about, I haven't included `<label>`-elements, which you of course always should. Also, I don't use `getElementById`, but refer directly to elements with their id, which you shouldn't do IRL.

--- 

### RadioNodeList
Another type of collection is `RadioNodeList`.
If you have a group of radio-buttons:

```html
<form>
  <input type="radio" name="group" value="1">
  <input type="radio" name="group" value="2" checked>
  <input type="radio" name="group" value="3">
</form>
```

You can simply grab the selected value with:

```js
form.elements.group.value
```

That's because the elements-collection detects that the item `group` is a `RadioNodeList`, and thus `value` returns the `checked` item.

---

## Fieldsets
A `<fieldset>` has an **elements**-collection too.

```html
<form id="customer">
  <fieldset name="delivery">
    <input name="firstname" value="John">
    <input name="lastname" value="Doe">
  </fieldset>
  <fieldset name="invoice">
    <input name="firstname" value="Jane">
    <input name="lastname" value="Smith">
  </fieldset>
</form>
```

In this case, `customer.elements.firstname.value` will return nothing, because there's a conflict in naming with 2 elements matching `name="firstname"`, but if we use:

```js
customer.delivery.elements.firstname.value
customer.invoice.elements.firstname.value
```

— we get "John" and "Jane", because we're now targetting the `elements`-collection within the **fieldsets** instead of the form.

> **DISCLAIMER:** You shouldn't have multiple elements with the same `name` within a form, unless it's a radio-group. The example above is to illustrate the power of fieldsets and their elements-collection.

---

## The Power of the `form` Attribute

The `form`-attribute allows you to physically place a form-control in **one** form, but make it belong to **another** form:

```html
<form id="app">
  <input name="volume" type="range" value="85">
  <input form="prefs" name="store" type="checkbox" value="1">
</form>

<form id="prefs"></form>
```

Now, go to a console and type:

```js
app.elements.length //1
prefs.elements.length //1
```

This way you can create "state collections" with a bunch of empty forms, while having all the inputs _physically_ in the same "master" form (or "main app").

Add an `oninput` event-listener to the main form, and detect which form the current input belongs to :

```js
app.addEventListener('input', (event) => {
  const input = event.target;
  switch(input.form) {
    case app:
      // Do something
      break;
    case prefs:
      // Do something else
      break;
  }
});
```

---

## Values

We already saw how `value` could return the value of the checked item, if the collection was `RadioNodeList`. But `value` has a few more tricks up it's sleeve. 

Take this example:

```html
<form id="app">
  <input name="firstname" value="John">
</form>
```

When interacting with this input, we can retrieve **two values:**

```js
app.elements.firstname.value
app.elements.firstname.defaultValue
```

`defaultValue` returns the value that was **set in HTML** when the form was created, thus always giving you a "default state" to return to, should you need it.

When you reset a form with `app.reset()` or `<button type="reset">`, this is what a field is reset to.

---

### valueAsNumber

All HTML attributes are strings, but if you're working with numeric form inputs, you can use `valueAsNumber` to directly get the input's numeric value:

```html
<form id="app">
  <input name="height" type="range" min="50" max="230" value="180">
</form>
```

Then, in JavaScript:

```js
app.height.valueAsNumber //180
```

If you use `valueAsNumber` on a non-numeric input, you'll get `NaN` (not a number).

---

### valueAsDate

Similar to numeric inputs, date-type inputs have `valueAsDate`:

```html
<form id="app">
  <input name="dob" type="date" value="2000-01-01">
</form>
```

In JavaScript:
```js
app.dob.valueAsDate
// Thu Jan 1 2000 01:00:00 GMT+0100 (Central European Standard Time)
```

(you'll get a different timezone, depending on your location)

… and you can use the Date-object methods directly:

```js
app.dob.valueAsDate.getUTCFullYear();
```

---

## Conclusion

In summary, HTML forms are not just containers for gathering user input but are dynamic interfaces with a wealth of underutilized capabilities. 

Will you use the `elements`-collection in multiple forms or fieldsets to manage complex data-structures?