An Implementation of Form Handling.

An Implementation of Form Handling.

Introduction

When we work with the elements of a webpage, we have to consider its structure. The hierarchical structure of html is represented by the DOM (Document Object Model). The document is built up as a "node" tree, where each "node" is equivalent to part of the document, such as each element and text. The general layout of a webpage:

<html>
    <head>
        ...
    </head>
    <body>
        ...
    </body>
</html>

gives an indication of the structure of first couple levels of the document tree. In this article, I aim to look at the code for a class I have written in Typescript that enumerates the elements, their properties and the data submitted for a form as a sort of blackbox. So, the structure of the form is read using the DOM API without the programmer needing the know anything about the form's structure. First, let's look further into how the DOM works.

The DOM

The root node in the tree represents the webpage itself, which, in the DOM API, is the Document Interface. From this interface, we have access to the elements below this level in the tree. We can get the body and head of the document through the body and head properties to the Document interface:

document.body

document.head

We can also get all the child elements of the document by invoking the children property of Document interface,

document.children

which returns an HTMLCollection. In the same way, we can also get an HTMLCollection of all the images of the document. We can also get/set the title of the document and we can append a Node or string object after the last child node of the document.

document.images

document.title

document.append()

Our Form

For our efforts in this article, we get an HTMLCollection of the forms that are included in the document, which is an HTMLFormElement. So, in the Form class below, so far, we have some fields and a constructor that define the structure of a form that we might include in our document.

Form Class

class Form {
    private formEl: HTMLFormElement;
    private fieldInputs: HTMLFormElement[];
    private submitButton: HTMLButtonElement;

    constructor(form: HTMLFormElement) {
        this.formEl = form;
        this.fieldInputs = this.inputs;
        this.submitButton = this.submitBtn;
        this.formEl.addEventListener("submit", event => {
            event.preventDefault();
            ...
        });
    }
    ...
}

In any form, we will have our inputs, the private fieldInputs field and a submit button, the private submitButton field. The purpose of a form is to produce output data, but that will not persist, so it is not represented by a field of this class. Of course, in the constructor, we have an event listener implemented, so that we can do something with the data from the form. Now, we instantiate the form and pass in the form from our document.

const form = new Form(document.forms["signup"]);

In the example above, we get access to the form by the forms property to the document, which returns an HTMLCollection of the forms on the document. Here, we use the first form on the page, a signup form, specified by passing the value of the name attribute to the forms property.

<form name="signup">
    <fieldset>
        <legend>Signup</legend>
        <label for="name"> enter name </label>
        <input
            type="text"
            name="name"
            pattern="^[a-zA-Z]+(?:\s[a-zA-Z]+)?$"
            placeholder="e.g. John Hancock"
            autocomplete="false"
        />
        <label for="username"> enter username </label>
        <input
            type="text"
            name="username"
            pattern="^[a-zA-Z][a-zA-Z0-9]{4,}$"
            placeholder="e.g. jhancock"
            autocomplete="false"
        />
        <label for="password"> enter a password </label>
        <input
            type="password"
            name="password"
            pattern="^(?!.*[#!])(?=.*[A-Z])(?=.*[0-9]).{8,}$"
            placeholder="e.g. p4sswOrd"
            autocomplete="false"
        />
        <label for="confirm_password"> confirm your password </label>
        <input
            type="password"
            name="confirm_password"
            pattern="^(?!.*[#!])(?=.*[A-Z])(?=.*[0-9]).{8,}$"
            placeholder="e.g. p4sswOrd"
            autocomplete="false"
        />
        <label for="email"> enter an email address</label>
        <input
            type="email"
            name="email"
            pattern="^\w+[\+\.\w-]*@([\w-]+\.)*\w+[\w-]*\.([a-z]{2,4}|\d+)$"
            placeholder="e.g. user@hotmail.com"
            autocomplete="false"
        />
        <button type="submit" class="btn"> Submit </button>
    </fieldset>
</form>

Getters/Setters

Now, we have access to our form in the document, and, from the form, we can get access to the elements of the form. Below, in particular, we gain access to the inputs of the form. In the form defined above, there are five inputs. These are inputs to get the data for the name, username, password, confirm password, and email fields, respectively. Much like how the forms property gives an HTMLCollection of the forms of the document, the elements property of HTMLFormElement gives an HTMLFormControlsCollection of the elements of a form. Then, we can filter these elements by node name for input, so we can get those elements that are inputs rather than other types of elements.

get inputs(): HTMLFormElement[] {
    const fieldEls: HTMLFormControlsCollection = this.formEl.elements;
    let fieldInputs: Element[] = [];

    for(let i = 0; i < fieldEls.length; i++)
        if(fieldEls[i].nodeName === "INPUT")
            fieldInputs = [...fieldInputs, fieldEls[i]];

    return fieldInputs as HTMLFormElement[];
}

In the constructor above, we have this line:

this.fieldInputs = this.inputs;

, which calls the inputs getter function above, returns the inputs of the form and sets the fieldInputs field. At that point, we have access to reference all the inputs of the form and use the properties get what we might need from the input. In a similar way, we can gain access to the submit button.

Finally, we get to the purpose of the Form class. We need a simple way to get the data from inputs of the form. In the values getter function below, we take the values of each input. Here, we iterate through the HTMLFormElement array and apply the value property to that element. Each of these values in then added to either a string or number array.

get values(): (string | number)[] {
    let values: (string | number)[] = [];
    for(let i = 0; i < this.fieldInputs.length; i++)
        values = [...values, this.fieldInputs[i].value];

    return values;
}

Then, we get an array of the values of the name attribute of the inputs. We apply the attributes property on each fieldInput to get all the attributes of the input. We further filter the attributes by the name attribute to get its value. These values are mapped to an array which is, then, returned by the function.

get inputsNames(): (string | undefined)[] {
    return this.fieldInputs.map(fieldInput => {
        return fieldInput.attributes.getNamedItem("name")?.value;
    });
}

The elements of each array can be combined to create an object with name/value pairs as each member of the object.

Why are we doing all this?

Previously, any data handling I have written has always needed to be aware of the inputs of the form beforehand in order to be accessed to get the data. With this class, I have generalized gaining access to the inputs with no requirement of knowing any id or name of the inputs. Usually, we will see programmers using a chain of getElementById or querySelector statements in order to access each input in the form. We have abstracted and encapsulated all of that to a general behind the scenes sort of implementation. Thus, we have foregone creating that chain of statements each time, which allows the programmer to get straight to the point of what the programmer is trying to get done.

Conclusion

Now, we can doing anything we need to do using the Form class to access inputs and the submit button. The Form class could be further generalized to simply get access to the inputs and the submit button. Further consideration could be lent to allow the programmer to extend that functionality specifically to the particular sort of form that might be needed in any way that might be necessary.