A Javascript value object representing an HTML form control ID, making sure the ID generation is deterministic and unique in a document.
According to the W3C Web Accessibility Tutorials website:
Whenever possible, use the label element to associate text with form elements explicitly. The
for
attribute of the label must exactly match the id of the form control.
Usually, the exact ID is a detail that is not important for a project specification. It might be anything, assuming it's unique and valid.
Also, writing HTML forms can be tedious, crufty and error-prone. It might be better to abstract the repeatitive code, for instance, using a component. If you're using, for instance, Vuejs, and you're required to specificy an ID, you'd have to do something like this:
<checkbox
id="form_colors_red"
label="Colors"
name="colors"
value="red"
v-model="form.colors" />
If someone is implementing form controls dynamically (ex: a component for form input fields), it might be a good idea to generate the IDs automatically instead, especially if it's part of a library.
It might seem a good idea to generate IDs randomly or sequentially, but there's a collision risk that might cause undesirable consequences which are very hard to be detected. Also, if the result is not deterministic, it's hard to retrieve a specific element by its ID. And it would be nice if the IDs are user-friendly and valid ID in the HTML document.
InputId
was implemented to solve all those hurdles.
- Deterministic ID generation based on HTML element attributes
- Unique ID generation in a document
- ID sanitization according to the document type
- Removal of characters that might be problematic in CSS selectors
- ID generation to find an element by its ID attribute or its related labels
Using npm:
npm install inputid
An InputId
instantiation is a value object representing a form control ID. It should be immutable and can be used as a String.
It doesn't require any arguments or configuration:
const InputId = require('inputid');
labelElement.htmlFor = inputElement.id = new InputId();
But you'd get better results if an HTML element or options are provided as an argument.
If the InputId
is instantiated with a HTMLElement instance as an argument, it will generate a unique element ID in the element's document which can be used as the element's ID attribute:
inputElement.id = new InputId(inputElement);
labelElement.htmlFor = element.id;
It's possible to loop several elements in a document and set their id
attribute values with InputId
:
const elements = document.getElementsByTagName('input');
elements.forEach(element => {
element.id = new InputId(element);
element.parentNode.getElementsByTagName('label')[0].htmlFor = element.id;
});
InputId
also accepts several options as an argument:
inputElement.id = new InputId({
prefix: 'form-personal-info',
type: 'radio',
name: 'gender',
value: 'male'
});
labelElement.htmlFor = element.id;
The type
value is the element type
attribute value or the element tagName
. The type
and value
options are redundant if the element is neither a radio button, nor a checkbox, nor an option element.
The prefix
should be the form ID attribute value, but that is not enforced.
Usually, a form control has at a least name:
const element = document.createElement('input');
element.dataset.name = 'cpr';
element.id = new InputId(element);
But sometimes there are good reasons to not have a name. For instance, some value must be a number, but the input field shown to the user should be a formatted text. In that case, the formatted text shouldn't be submitted, and form controls lacking a name are not submitted. It's easy to handle those case by providing a name in the InputId
construction arguments besides the element which lacks the name
attribute:
const hiddenElement = document.createElement('input');
hiddenElement.name = 'cpr';
const visibleElement = document.createElement('input');
hiddenElement.type = 'text';
visibleElement.id = new InputId({
element: visibleElement,
name: 'cpr'
});
It's easy to find an element by its ID if the options used to instantiate InputId is known (but forceUniqueness
must not be true
):
const inputId = new InputId({
prefix: 'form-personal-info',
type: 'radio',
name: 'gender',
value: 'male'
});
const element = inputId.getElement();
const label = inputId.getLabels()[0];
There shouldn't exist elements with the same name and value in a form, but if (for some reason) that's a possibility, make sure "the forceUniqueness
option value is true
:
inputElement.id = new InputId({
prefix: 'form-personal-info',
type: 'radio',
name: 'gender',
value: 'male',
forceUniqueness: true
});
labelElement.htmlFor = element.id;
If an HTML element is not provided (as the constructor argument or an option), forceUniqueness
is false
by default.
Each InputId instantiation is immutable, but it can produce modified copies with method chaining:
const baseId = new InputId({prefix: 'form-personal-info'})
.forceUniqueness();
nameElement.id = new InputId({name: 'name'});
genderElement.id = baseId
.withType('radio')
.withName('gender')
.withValue('male');
In HTML4, an "id" attribute value must:
begin with a letter ([A-Za-z]) and may be followed by any number of letters, digits ([0-9]), hyphens ("-"), underscores ("_"), colons (":"), and periods (".")
To conform to that specification, if the document is not an HTML5 document, all the invalid characters are removed or replaced. If the first character is not a letter, a prefix is added.
In any case, some characters are always replaced to avoid possible issues in CSS selectors. It's also guaranteed the generated ID is not empty at least using a fallback (by default it's the letter "f"), which can be changed:
const element = inputElement.id = new InputId({
fallback: 'hidden_name'
});
Improvements and suggestions are welcome.
Before sending a pull request, make sure all the tests pass after changing the code:
npm test
The test implementations are inside the ./tests
folder.
ISC (see the LICENSE.txt file)