Rounin
Rounin

Reputation: 29453

Clarifying the status of custom attributes in HTML Custom Elements in WebComponents

Until recently, whenever I've needed a custom attribute in my HTML, I've always used an HTML data-* custom attribute.

Having recently started experimenting with WebComponents and, specifically, Custom Elements, I have started thinking in terms of custom attributes which are not HTML5 data-* custom attributes.

Before inadvertently adopting any non-recommended practices, I would like to clarify the following...

In the list below we have 4 elements:

const toggleDataAttribute = (e) => {
    e.target.dataset.inverted = (e.target.dataset.inverted === 'true') ? 'false' : 'true';
}

const toggleCustomAttribute = (e) => {
  if (e.target.getAttribute('inverted') === 'true') {
    e.target.setAttribute('inverted', 'false');
  }

  else {
    e.target.setAttribute('inverted', 'true');
  }
}

const toggleInvert = (e) => {

  if (e.target.dataset.inverted) {
    toggleDataAttribute(e);
  }
  
  else {
    toggleCustomAttribute(e);
  }
}


// Attach click event TO <div> elements
let divs = [...document.getElementsByTagName('div')];

divs.forEach((div) => div.addEventListener('click', toggleInvert, false));

// Attach click event TO <my-circle> elements
let myCircles = [...document.getElementsByTagName('my-circle')];

myCircles.forEach((myCircle) => myCircle.addEventListener('click', toggleInvert, false));


// Define <my-circle> element
class myCircle extends HTMLElement {
  
  constructor() {
    super();
    this.root = this.attachShadow({mode: "open"});
  }

  connectedCallback() {
    this.root.appendChild(document.createElement('slot'));
  }
}

customElements.define('my-circle', myCircle);
aside {
  position: absolute;
  top: 0;
  right: 0;
  width: 280px;
  line-height: 24px;
}

div {
  float: left;
  margin: 0 12px 12px 0;
  width: 80px;
  height: 80px;
  line-height: 80px;
  text-align: center;
  font-size: 36px;
  border-radius: 50%;
  cursor: pointer;
}

my-circle {
  display: block;
  float: left;
  margin: 0 12px 12px 0;
  width: 80px;
  height: 80px;
  line-height: 80px;
  text-align: center;
  font-size: 36px;
  background: radial-gradient(#fff, #000);
  border-radius: 50%;
  cursor: pointer;
}

my-circle:first-of-type {
  clear: left;
}


div:nth-of-type(1) {
  background: radial-gradient(rgb(255, 255, 0), rgb(255, 0, 0));
}

div:nth-of-type(2) { 
  background: radial-gradient(rgb(255, 255, 0), rgb(0, 163, 0));
}

my-circle:nth-of-type(1) { 
  background: radial-gradient(rgb(255, 255, 0), rgb(223, 163, 0));
}

my-circle:nth-of-type(2) {
  background: radial-gradient(rgb(255, 127, 127), rgb(255, 0, 0));
}

div[data-inverted="true"],
div[inverted="true"],
my-circle[data-inverted="true"],
my-circle[inverted="true"] {
  filter: hue-rotate(180deg);
}
<div data-inverted="false">i</div>
<div inverted="false">ii</div>

<my-circle data-inverted="false">iii</my-circle>
<my-circle inverted="false">iv</my-circle>

<aside>
<p><strong>Click</strong> on each of the circles on the left to invert their backgrounds.</p>
</aside>

Although the set up above works technically, which of the following is true:


Added:

To illustrate my question above, I'd like to give an example of where custom attributes appear not to be valid:

  1. Go to: https://validator.w3.org/nu/#textarea

  2. Select text input

  3. Enter:

<!DOCTYPE html>
<html lang="">
<head>
<title>Test</title>
</head>
<body>

<div data-inverted="false">i</div>

<div inverted="false">ii</div>

</body>
</html>
  1. Check the markup

The validator returns the error:

Error: Attribute inverted not allowed on element div at this point.

From line 10, column 1; to line 10, column 22

i</div>↩↩<div inverted="false">ii</di

Though... I'm not sure if the tool at https://validator.w3.org/nu/ is outdated and / or abandoned and the Error returned should no longer be regarded as an error in 2020 (?)

Upvotes: 2

Views: 517

Answers (1)

All 4 usages work, so why should they be invalid?

data- prefix gives the added bonus they are available in element.dataset.

-- Attributes are Attributes -- , nothing special in the Custom Elements API,
apart from observedAttributes(). Yes, you can use data-* attributes there to.

note

class myCircle extends HTMLElement {
  constructor() {
    super();
    this.root = this.attachShadow({mode: "open"});
  }
  connectedCallback() {
    this.root.appendChild(document.createElement('slot'));
  }
}

can be written as:

class myCircle extends HTMLElement {
  constructor() {
    super()
      .attachShadow({mode: "open"})
      .append(document.createElement('slot'));
  }
}

because super() returns 'this'
and attachShadow both sets and returns this.shadowRoot for free
you are not doing anything with appendChild() return value, so append() (which can take multiple parameters) is enough.

Also note there is a toggleAttribute method.

Upvotes: 2

Related Questions