Is there any way to observe all data attributes in a custom component?
Asked Answered
B

3

9

I'm trying to use vanilla javascript to build a custom component which observes changes in all data attributes, e.g.:

class MyComponent extends HTMLElement {

  static get observedAttributes () {
      return ["data-one","data-two","data-three",so forth...]
  }

}

This component could in theory be assigned an arbitrary number of data attributes, so there's no way to predict exactly how many there would be, yet I need the component to do stuff every time a new data attribute is assigned to it, is there any way to do it? having to put into the array returned by "observedAttributes" the specific name of every attribute seems really restrictive

As a bonus, is there any way to observe attributes that don't have a specific name but follow a certain pattern? (e.g. they match against a regex string or something like that)

And as an extra bonus, is there any way to observe all attributes? (I know they made this not be the default behavior due to performance factors, but still it would be good to be able to enable it if needed)

Beget answered 21/7, 2021 at 3:52 Comment(3)
No, use the MutationObserver API to capture attributes that where not assigned as observed attributes when the Custom Element was created. MDN: developer.mozilla.org/en-US/docs/Web/API/MutationObserverWoebegone
Hmm, didn't know that existed, thanks. Well alright, that's one way to do it, was hoping there was a way to do it inside the observedAttributes getter that I was missing, but I guess there's no such thingBeget
@Danny'365CSI'Engelman Is there any advantage of using observedAttributes() vs creating a MutationObserver then? Seems like the MutationObserver is much more flexible.Stubbed
Q
2

sure you can do so by using one of Custom element callbacks based on this

here are all Custom element lifecycle callbacks

  • and you need to focus on this one attributeChangedCallback

    // Create a class for the element
    class MyCustomElement extends HTMLElement {
    static observedAttributes = ["color", "size"];
    
    constructor() {
      // Always call super first in constructor
      super();
    }
    
    connectedCallback() {
      console.log("Custom element added to page.");
    }
    
    disconnectedCallback() {
      console.log("Custom element removed from page.");
    }
    
    adoptedCallback() {
      console.log("Custom element moved to new page.");
    }
    
    attributeChangedCallback(name, oldValue, newValue) {
      console.log(`Attribute ${name} has changed.`);
    }
    }
    
    customElements.define("my-custom-element", MyCustomElement);
    
Quartus answered 3/1 at 20:52 Comment(4)
This is not an answer to the question of how to observe all attributes.Eskill
@ChristianFritz why do you think it's not an answer?Quartus
Because the question was about observing all attributes, including those that are unknown at implementation time, and in particular more than just color and size.Eskill
yes I agree with that, however if you have an array to include whatever the attributes you need as shown in the question, you can absolutely include more than color and size. e.g. static observedAttributes = ["data-one","data-two","data-three",so forth...]Quartus
E
1

As pointed out in comments by Danny, you can use MutationObserver to do this:

class MyCustomElement extends HTMLElement {
  constructor() {
    super();
  }

  connectedCallback() {
    const observer = new MutationObserver((mutationRecords) => {
      mutationRecords.forEach(record => {
        console.log(`${record.attributeName} has changed to ${this.getAttribute(record.attributeName)}`);
      });
    }).observe(this, { attributes: true });
  }
}
Eskill answered 19/1 at 18:38 Comment(0)
S
1

Here is a way to observe all data-attribute changes (using MutationObserver). The callback function filters the observed mutations.

See also

const observer = new MutationObserver(callback);

observer.observe(document, {
  attributes: true,
  subtree: true,
  attributeOldValue: true,
});

// change some data-attributes
const testElem = document.querySelector(`#test`);

// will be observed
testElem.dataset.new = `something else`;

// will NOT be observed
testElem.setAttribute(`title`, `added a title`);

setTimeout(_ => {
  testElem.dataset.first = "Hi";
  testElem.dataset.second = "universe!";
}, 2000);
setTimeout(_ => testElem.dataset.second = "milky way", 1000);

function callback(mutationList) {
  // filter relevant mutations (data-attributes)
  [...mutationList].filter(m => m.attributeName.startsWith(`data`))
  .forEach( mutation => {
      console.info(`✓ Attribute [${
        mutation.attributeName}] changed to '${
        mutation.target.getAttribute(mutation.attributeName)}' (was '${
        mutation.oldValue}')`);
  });
}
[data-second]:before {
  content: attr(data-first)' ';
}

[data-second]:after {
  content: attr(data-second);
}
<div id="test" data-first="hello" data-second="world"></div>
Salade answered 24/1 at 14:59 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.