On this page

Attributes

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.

How it Works

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.

Attribute Data Types

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>

Property and 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);
}  

Working with Lists

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.

Monitor Events

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
observedAttributes
Once the custom element is loaded it will call the observedAttributes callback function to get the list of attributes it is interested in.
Set Attribute
observedAttributes
Setting 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
Attach Tag to Parent
Attach Parent to DOM
observedAttributes
constructor
attributeChangedCallback test, null, hello
connectedCallback
It 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
Remove Attribute
Attach Tag to Parent
Attach Parent to DOM
observedAttributes
constructor
connectedCallback
If 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
Attach Parent to DOM
Set Attribute
Detach Tag From Parent
Attach Tag To Parent
observedAttributes
constructor
connectedCallback
attributeChangedCallback test, null, hello
disconnectedCallback
connectedCallback
After the attribute has been set, if you detach and re-attach the element, the attributeChangedCallback function is not called.
Attach Tag to Parent
Attach Parent to DOM
Set Attribute
Detach Tag From Parent
Set Random Attribute
observedAttributes
constructor
connectedCallback
attributeChangedCallback test, null, hello
disconnectedCallback
attributeChangedCallback test, hello, 0.16632
Even after the element has been detached, the attributeChangedCallback function will get called if an attribute is changed.

Standard Attributes

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.