-2

https://html.spec.whatwg.org/multipage/custom-elements.html#custom-elements-autonomous-example:htmlelement

In the specification they've provided an example for Creating an autonomous custom element. However, they've left _updateRendering() method implementation for the readers.

class FlagIcon extends HTMLElement {
  constructor() {
    super();
    this._countryCode = null;
  }

  static observedAttributes = ["country"];

  attributeChangedCallback(name, oldValue, newValue) {
    // name will always be "country" due to observedAttributes
    this._countryCode = newValue;
    this._updateRendering();
  }
  connectedCallback() {
    this._updateRendering();
  }

  get country() {
    return this._countryCode;
  }
  set country(v) {
    this.setAttribute("country", v);
  }

  _updateRendering() {
    // Left as an exercise for the reader. But, you'll probably want to
    // check this.ownerDocument.defaultView to see if we've been
    // inserted into a document with a browsing context, and avoid
    // doing any work if not.
  }
}

An issue has been raised to provide the remaining implementation for better understanding of the topic and quickly move on.
Issue: https://github.com/whatwg/html/issues/3029

What code can we put there to get the required functionality?

the Hutt
  • 16,980
  • 2
  • 14
  • 44
  • SO is a Question and Answer platform and not a tutorial site. You should post this in the form of a question. [ask] In addition, your complaint is more appropriate for the WHATWG site and not here. People are more likely to look for this solution on their site. [What topics can I ask about here?](https://stackoverflow.com/help/on-topic) – Rob Oct 17 '21 at 09:43
  • I’m voting to close this question because it's not a question but a tutorial. – Rob Oct 17 '21 at 09:44
  • @DonaldDuck That's not why I voted to close it. – Rob Oct 17 '21 at 16:28
  • Hey buddy, not going to downvote. But this is something we do not expect often on stackoverflow. Anyways, I will prepare a youtube video on this for you with deeper explanation and link it back for you in this comment by next week. WHY? That function has lot of missing things, which should have been covered in the spec. And some parts are updated, so need a time to collect those things – Abir Jan 21 '23 at 14:34

2 Answers2

0

Here is the complete code to achieve the same requirements:

<!DOCTYPE html>
<html lang="en">
  <body>
    <flag-icon country="in"></flag-icon><br>
    <flag-icon country="nl"></flag-icon><br>
    <flag-icon country="us"></flag-icon><br>
  </body>

  <script>
    class FlagIcon extends HTMLElement {
      constructor() {
        super();
        this._countryCode = null;
      }

      static observedAttributes = ["country"];

      attributeChangedCallback(name, oldValue, newValue) {
        // name will always be "country" due to observedAttributes
        this._countryCode = newValue;
        this._updateRendering();
      }
      connectedCallback() {
        this._updateRendering();
      }

      get country() {
        return this._countryCode;
      }
      set country(v) {
        this.setAttribute("country", v);
      }

      _updateRendering() {
        //**remaining code**
        if (this.ownerDocument.defaultView && !this.hasChildNodes()) {
          var flag = document.createElement("img");
          flag.src = "https://flagcdn.com/24x18/" + this._countryCode + ".png";
          this.appendChild(flag);
        }
      }
    }
    customElements.define("flag-icon", FlagIcon);
  </script>
</html>

Note: images may take time to load depending on the internet speed.

Let me know if I've missed anything.
the Hutt
  • 16,980
  • 2
  • 14
  • 44
0

Your solution fails, once the flag is set you can never change the value.

That "exercise" is old.. very old.. and contrived to show everything Custom Elements can do.

And it is plain wrong.. key is WHEN attributeChanged runs, and what the old/new values are

And attributeChangedCallback runs BEFORE connectedCallback; that is why https://developer.mozilla.org/en-US/docs/Web/API/Node/isConnected was added.

Your code gets 3 parameters in attributeChangedCallback, but you can't do anything with them, because execution always goes to the _updateRendering method.

If the point of the exercise is to learn when Observed attributes change I would use:

Code also available in JSFiddle: https://jsfiddle.net/dannye/43ud1wvn/

<script>
  class FlagIcon extends HTMLElement {
    static observedAttributes = ["country"];
    log(...args) {
      document.body.appendChild(document.createElement("div"))
              .innerHTML = `${this.id} - ${args.join` `}`;
    }
    attributeChangedCallback(name, oldValue, newValue) {
      this.log("<b>attributeChangedCallback:</b>", `("${name}" , "${oldValue}", "${newValue}" )`);
      if (this.isConnected) {
        if (newValue == oldValue) this.log(`Don't call SETTER ${name} again!`);
        else this[name] = newValue; // call SETTER
      } else this.log("is not a DOM element yet!!!");
    }
    connectedCallback() {
      this.log("<b>connectedCallback</b>, this.img:", this.img || "not defined");
      this.img = document.createElement("img");
      this.append(this.img); // append isn't available in IE11
      this.country = this.getAttribute("country") || "EmptyCountry";
    }
    get country() { // the Attribute is the truth, no need for private variables
      return this.getAttribute("country");
    }
    set country(v) {
      this.log("SETTER country:", v);
      // Properties and Attributes are in sync, 
      // but setAttribute will trigger attributeChanged one more time!
      this.setAttribute("country", v);
      if (this.img) this.img.src = `//flagcdn.com/20x15/${v}.png`;
      else this.log("can't set country", v);
    }
  }
  customElements.define("flag-icon", FlagIcon);

  document.body.onclick = () => {
    flag1.country = "nl";
    flag2.setAttribute("country", "nl");
  }
</script>

<flag-icon id="flag1" country="in"></flag-icon><br>
<flag-icon id="flag2" country="us"></flag-icon><br>

This is just one way, it all depends on what/when/how your Custom Elements needs to do updates.

It also matters WHEN the CustomElement is defined; before or after the DOM is parsed. Most developers just whack deferred or method on their scripts, without understanding what it implies.

Always test your Web Component with code that defines the Custom Element BEFORE it is used in the DOM.

A Real World <flag-icon> Web Component

Would be optimized:

<script>
  customElements.define("flag-icon", class extends HTMLElement {
    static observedAttributes = ["country"];
    attributeChangedCallback() {
      this.isConnected && this.connectedCallback();
    }
    connectedCallback() {
      this.img = this.img || this.appendChild(document.createElement("img"));
      this.img.src = `//flagcdn.com/120x90/${this.country}.png`;
    }
    get country() {
      return this.getAttribute("country") || console.error("Missing country attribute",this);
    }
    set country(v) {
      this.setAttribute("country", v);
    }
  });
</script>

<flag-icon id="flag1" country="gb"></flag-icon>
<flag-icon id="flag2" country="eu"></flag-icon>

Or NO External Images at all

Using the FlagMeister Web Component which creates all SVG client-side

<script src="//flagmeister.github.io/elements.flagmeister.min.js"></script>

<div style="display:grid;grid-template-columns: repeat(3,1fr);gap:1em">
  <flag-jollyroger></flag-jollyroger>
  <flag-un></flag-un>
  <flag-lgbt></flag-lgbt>
</div>
Danny '365CSI' Engelman
  • 16,526
  • 2
  • 32
  • 49
  • I think it's bad practice to provide `observedAttributes` as a data property rather than an accessor property, because it allows changing its value from the outside if someone has access to the class (which anyone with access to an instance has via `instance.constructor`). So unless that is intentional I recommend you change that to a static getter, or at least define it using `Object.defineProperty(FlagIcon, 'observedAttributes', { configurable: false, writable: false })`. – connexo Oct 17 '21 at 16:40
  • The latter should of course have been `Object.defineProperty(FlagIcon, 'observedAttributes', { configurable: false, writable: false, value: 'country', enumerable: true })`. – connexo Oct 17 '21 at 16:47
  • Yup, its just shorter notation for ``static get observedAttributes() { return["country"] }`` – Danny '365CSI' Engelman Oct 17 '21 at 16:48
  • With the problems mentioned, yes. – connexo Oct 17 '21 at 16:50
  • And I haven't tested this.. but I think you can not change _observedAttributes_ **after** the Custom Element is defined.. that would be cool... dynamic observedAttributes – Danny '365CSI' Engelman Oct 17 '21 at 16:50
  • You can obviously, whether it affects existing instances I don't know (but would expect), but for sure it affects instances created after the change. And I don't think that's a cool feature, especially not when suggested as a response to someone asking for a basic tutorial. – connexo Oct 17 '21 at 16:51
  • Please show how in a JSFiddle. AFAIK those attributes are processed when the Custom Element is **defined**, so they trigger the ``attributeChangedCallback``. Changing those attributes **later** on the instance does not process them again as observedAttributes. As I said, would be very cool. – Danny '365CSI' Engelman Oct 17 '21 at 16:54
  • If you want to verify that, give it a ride yourself, I don'Ät have the time to do it right now. Nonetheless defining `observedAttributes` as a writable and configurable data property is not what's described in the relevant documentation. – connexo Oct 17 '21 at 16:58
  • 1
    I have been down this road before.. some 3 years ago.. And since Web Components is a religion, not a framework; I (still) say ``observedAttributes`` are processed by the Custom Element **define** proces, you can't CRUD **observed**attributes afterwards. But it would be cool. – Danny '365CSI' Engelman Oct 17 '21 at 17:04
  • Your expectation seems correct: https://codepen.io/connexo/pen/XWadJEP?editors=1111 Nonetheless I still don't agree modeling `observedAttributes` as a data property, even if it may (at this point) not be harmful. – connexo Oct 17 '21 at 17:07
  • LOL, like I said, Web Components are religious statements. – Danny '365CSI' Engelman Oct 17 '21 at 17:10
  • *Always test your Web Component with code that defines the Custom Element BEFORE it is used in the DOM.* I also don't agree with that. Having worked primarily with custom elements for the past 4 years, making use of the `upgrade` process fixes some custom element shortcomings (like custom elements not offering a hook that guarantees the custom element is fully parsed, see https://github.com/WebReflection/html-parsed-element). – connexo Oct 17 '21 at 17:10
  • *Web Components are religious statements.* No. Standards are, period. There is no valid *but it works* argument that allows deviating from the standard, because that observation is only valid for any given specific point in time, and could be bound to change any time. – connexo Oct 17 '21 at 17:10
  • Whenever I hear "hook" I get a Vision of Dan Abromov dressed as Voldemort screaming at me _"they are not global pointers!"_ The "parsed" issue can be _worked-around_ with https://stackoverflow.com/questions/61971919/wait-for-element-upgrade-in-connectedcallback-firefox-and-chromium-differences Note that WebReflection/Andrea uses the same "Empty EventLoop" trick in his code with ``requestAnimationFrame`` – Danny '365CSI' Engelman Oct 17 '21 at 17:16
  • Thanks for pointing out, I was lucky as to be a an original co-author of that module, involved in the discussion that led to its creation. – connexo Oct 17 '21 at 17:30