Every HTML tag can have attributes. Take a look at the below HTML.
<input id="first-name" type="text" placeholder="enter first name" required> </input>
Attributes are those parts inside the first tag that contain information and settings for the element. Some attributes are standard for all elements (id and style for example), some are only used for specific tags (placeholder for input elements), and some attributes you can create yourself.
You start the attribute with its name, followed by an equals character, and then ending with a value. In the example above I have surrounded the value with double quotation marks, but it is possible to use single quotation marks, or none at all (if there are no spaces in the value).
Some attributes do not require any value. The "required" attribute above is used like a boolean value. If the attribute exists then the input is required, but if the attribute does not exist then the input is not required.
You can add your own attributes to a custom element.
The first part is to tell the browser the list of attributes you have created.
This is done using the static callback function observedAttributes
. Take a look at the example below.
class UserInfo extends HTMLElement { static get observedAttributes() { return ['first-name', 'last-name']; } }
The function needs to return a list of attribute names. In this example our custom element is telling the browser that we have the attributes "first-name" and "last-name" and that we want to know when they are changed.
Notice that the function is written using the keywords static
and get
.
The static
part makes the function link to the UserInfo class directly and not part of any instance of the class.
Look at it as a special global function that only belongs to the class.
The get
part is used as part of the getter and setter syntax used by JavaScript classes.
You could call the function yourself with the following code.
const attributeList = UserInfo.observedAttributes;
Because the function is a getter, you do not treat it like a function, so there is no parentheses (...)
needed on the end.
Also, because it is static, you need to prefix it with the name of the class.
The custom element can also be told when the values of the attributes change.
This is done using the callback function attributeChangedCallback
.
This is called when the custom element is first created with the attribute values already set.
The function is also called whenever the attribute values change afterwards too.
Take a look at the example below.
attributeChangedCallback(name, oldValue, newValue) { // Get first and last name attributes const firstName = this.getAttribute('first-name'); const lastName = this.getAttribute('last-name'); // Set the inner HTML to show the user name this.innerHTML = '<b>Name:</b> ' + firstName + ' ' + lastName; }
The callback function has some parameters.
The name parameter is the name of the attribute that was changed.
This will only be one of the attributes we have listed in the observedAttributes
function from before.
The oldValue and newValue parameters will give the previous and updated values of the attribute.
If there was no previous value then the oldValue will be null.
If the attribute is removed from the element then the newValue will be null.
In the above example, we get the current values of the attributes and then set the internal HTML to show the first and last names together.
Below are some of the useful functions that can be used with attributes, not just for custom elements but for all elements.
hasAttribute | Tests if the attribute exists. |
getAttribute | Gets the current value of the attribute. |
setAttibute | Sets a new value for the attribute. |
removeAttibute | Removes the attribute and its value. |
Because attribute values are written in HTML as text, the only data type they have is text (string). But what if we want to have numbers, dates, and other data types? Well, you will need to convert the attribute text value into whatever data type you need. Lets take an example to see what can be done.
class DataTypeAttributes extends HTMLElement { // Return list of attributes static get observedAttributes() { return ['text', 'integer', 'number', 'date', 'boolean']; } }
The custom element is working with 5 attributes for the different data types, text, integer, number, date and boolean (true/false). From the tag and HTML point of view these attributes will always be text values, but inside our custom element we will want to convert them into their own data types.
_getText() { // Get text attribute value const textValue = this.getAttribute('text'); // If nothing if (!textValue) return ''; // Return the text value return textValue; }
To get the text data type is very simple. Because the attribute will always be text we can just use it as it is. There may be issues if you want to include special characters, new lines, HTML tags and other things. You do need to check the attribute value exists and is not empty. An empty or missing attribute has the value of null.
_getInteger() { // Get integer attribute value const integerValue = this.getAttribute('integer'); // If nothing then return default if (!integerValue) return 0; // Convert into an integer const integer = parseInt(integerValue); // If not a valid number if (isNaN(integer) === true) return 0; // Return the integer return integer; }
For integers we need to convert the text value into an integer using the parseInt
function.
We again have to check the attribute value is not empty or missing.
It maybe that the attribute value contains some invalid text, the text "two" for example, therefore you
would need to check the integer value before returning it.
_getNumber() { // Get number attribute value const numberValue = this.getAttribute('number'); // If nothing then return default if (!numberValue) return 0; // Convert into a number const number = parseFloat(numberValue); // If not a valid number if (isNaN(number) === true) return 0; // Return the number return number; }
For numbers we are basically doing the same thing as the integer data type, but we are instead using
the parseFloat
function to convert the attribute text value into a floating point number.
_getDate() { // Get date attribute value const dateValue = this.getAttribute('date'); // If nothing then return default if (!dateValue) return null; // Parse the ISO date into milliseconds const dateMilliseconds = Date.parse(dateValue); // Convert into a date const date = new Date(dateMilliseconds); // Return the date return date; }
When it comes to dates, things can get a little more complex. Choose a format structure to store the date as text, like YYYY-MM-DD. You could get into problems with time-zone and day light savings too. So in this example I am only go to handle date and times in UTC using the ISO 8601 standard (YYYY-MM-DDTHH:mm:ss.sssZ). JavaScript has functions to help us convert a string in this format into a date.
_getBoolean() { // If has attribute then return true value if (this.hasAttribute('boolean') === true) return true; // Otherwise return false value return false; }
Boolean values are handled differently. We either have the attribute there (though empty) which means true (for yes) or the attribute does not exist which means false (for no). Therefore all we need to do is check to see if the attribute exists or not. If it does exist then the value is true. If the attribute does not exist then the value is false.
Now, when it comes to converting the values, in their own data types, back into attribute text values, we need to convert them into text. In the example we will see later, we have some functions that set the data type variables into some random values. These are then converted into text and their attributes are updated.
// Set text attribute this.setAttribute('text', randomText); // Set integer attribute this.setAttribute('integer', randomInteger.toString()); // Set number attribute this.setAttribute('number', randomNumber.toFixed(4)); // Set date attribute this.setAttribute('date', randomDate.toISOString()); // If the attribute exists if (this.hasAttribute('boolean') === true) { // Remove the attribute this.removeAttribute('boolean'); } else { // Add the attribute (without a value) this.setAttribute('boolean', ''); }
The random text is just text so that does not need any converting.
With the integer value you can use the toString
function to convert it into a string.
You can convert the number using the toFixed
function to set the number of decimal points.
You need to make sure when converting the date to text that you can read it back in again, so that the date format
outputted is the same as what you are expecting when you later read the attribute again.
The date format is really up to you, there is no standard.
The boolean variable is handled differently. If the value is true then we need to make sure the attribute exists, though it will need to be empty, and if the value is false then we have to remove the attribute. To set an empty attribute, just use an empty string value.
After playing with the above example, have a look at the elements and find the <data-type-attributes>
tag.
Take a look at the attributes and how they change when the buttons are pressed.
<data-type-attributes id="test" text="shpdszjres" integer="141137" number="627541.9251" date="2022-05-30-T19:10:41.493Z" boolean>...</data-type-attribute>
An instance of the custom element class, that is linked to the HTML tag, becomes the element object that is returned
using the document.getElementById
function.
Remember that the custom element is derived from HTMLElement
, so the custom element is just a normal element but with
all the extra bits we add to it.
We can add whatever we want to the element, extra functions, new types of data, and so on. You can also add something called properties. These are more of a JavaScript class thing than a HTML custom element thing, but it may come in useful for you.
In JavaScript, in a class, you can add getter and setting functions. These allow you to get and set a value in a class. Lets look at an example.
// Name getter function get name() { // Return the name value return this._name; } // Name setter function set name(value) { // Set name this._name = value; }
Both behave like a function. Before we move on, lets see how they are used.
// Get element const element = document.getElementById('test'); // Set name element.name = 'Bob Green'; // Get name const name = element.name;
First we need to get the element.
This will be an instance of the custom element.
We can use "name" as a property, without using it like a function, so there are no (...)
parentheses.
Whenever the property is used it calls the class's getter or setting function.
This gives us the means to do some extra processing when the properties are used.
The values used are in the data types you designed them to be in too (not fixed to only text like attributes).
This allows you to get and set information to and from the custom element without relying on attributes only. There will be times when you can use both to do the same thing, giving you the option to use either. You can mix properties and attributes but you will need to convert values to and from text like before. Lets see what this can look like with an integer age property.
// Age getter function get age() { // If no age attribute if (this.hasAttribute('age') === false) return 0; // Get age attribute value const ageValue = this.getAttribute('age'); // Convert age value and return it return parseInt(ageValue); } // Age setter function set age(value) { // Set default age value let age = '0'; // If the value is not null if (value !== null) age = value.toString(); // Set age attribute value this.setAttribute('age', age); }
When getting the age value, as a property, we are storing the age value as an attribute, therefore we need to first check it exists. If it does not exist then we return some default age value. The age is stored as text in the attribute so we convert it into an integer and then return it.
When setting the age value, as a property, we need to convert it into text before setting the attribute. You may need to perform some checks to make sure the set value parameter is valid first.
In this example you can set or get the name and age using the property or the attribute.
// Age getter function get age() { // If no age attribute if (this.hasAttribute('age') === false) return 0; // Get age attribute value const ageValue = this.getAttribute('age'); // Convert age value and return it return parseInt(ageValue); } // Age setter function set age(value) { // Set default age value let age = '0'; // If the value is not null if (value !== null) age = value.toString(); // Set age attribute value this.setAttribute('age', age); }
With some custom elements you will want to give it a list of things. We cannot really use attributes for this, but we can use properties. Lets take a look at what the custom element code would look like.
class WorkingWithLists extends HTMLElement { constructor() { // We must always call super in the constructor super(); // Create the list this._list = []; } // List getter function get list() { // Return the list object return this._list; } // List setter function set list(value) { // Set the list this._list = value; } }
The constructor is the best location to set the _list
member.
It is set to an empty array.
Next we have the lists getter and setter functions.
These are called whenever the list
property is used. Lets see how we would use them.
// Get working with lists element const workingWithListsElement = document.getElementById('test'); // Add to list workingWithListsElement.list.push('new list item'); // Reset list to an empty one workingWithListsElement.list = []; // Create list let newList = [ 'New Item 1', 'New Item 2', 'New Item 3' ]; // Reset list to the new one workingWithListsElement.list = newList;
The first thing we need to do is get the element object for the custom element.
We can now use the list
property just like any other.
In the first part we are using the list's push
function to add a new string item.
In the second part we are emptying the list by resetting it to a new empty list.
The final part creates a new list of items and sets it to the element, replacing the old list with the new one.
We are changing the custom element's list member like any other JavaScript property.
But after doing all these things we still need to tell the custom element that the list has been updated and
it needs to redraw or recreate some internal part with the new list of items.
To do this we have added a method called update
.
After changing the list we call this function and it goes off and recreates the internal HTML with its list of items.
In the example above you can add to, empty and reset the list. Each time you press one of the buttons it will ask the custom element to update the internal HTML.
When do the different custom element events get called? It can help to understand when the events are fired so you know what needs doing in your custom element, or when trying to fix an issues you have. Take a look at the example below.
Here you can add the custom element to a parent element and add that to the DOM. You can also set a test attribute to a random value. Below is a list of some interesting findings.
Loaded |
observedAttributesOnce the custom element is loaded it will call the observedAttributes callback function to get the list of attributes it is interested in. |
Set Attribute |
observedAttributesSetting the custom elements attribute does nothing to start with. If the element is not attached to the DOM, then the attributeChangedCallback function is not called.
|
Set Attribute |
observedAttributesIt is only when the custom element is attached to the DOM (through the parent element) that the constructor , attributeChangedCallback , and connectedCallback functions are
called, one after the other.
|
Set Attribute |
observedAttributesIf you add an attribute but then remove it, so no attributes exist, and then attach the element to the DOM, it will end up that, the attributeChangedCallback function will not be called.
|
Attach Tag to Parent |
observedAttributesAfter the attribute has been set, if you detach and re-attach the element, the attributeChangedCallback function is not called.
|
Attach Tag to Parent |
observedAttributesEven after the element has been detached, the attributeChangedCallback function will get called if an attribute is changed.
|
You can get updated when other standard attributes get changed too.
We have used the observedAttributes
callback function to tell the browse which attributes we have, but we can
use it to monitor any attributes, including standard ones, like id
, class
, style
and others.
static get observedAttributes() { return ['id', 'class', 'style']; }
Just add the attribute names to the other attributes you have created.
Then any changes to those attributes are reported through the attributeChangedCallback
function.
Take a look at the example below.