Web Forms
Working with Select, Option, and Optgroup in HTML and JavaScript
sourceThe 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
sourceAs 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
sourceThe 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
sourceFieldsets 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
sourceThese 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
sourceThe 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
sourceencodeURI
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", () => {
document
.getElementById("btnEncode")
.addEventListener("click", doEncode);
document
.getElementById("btnDecode")
.addEventListener("click", doDecode);
});
let code = parent.window.document.getElementById("code-09");
code.addEventListener("click", () => {
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
sourceWhen 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
sourceEverything You Need to Know About Form Events
sourceThe 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');
}