<flag-icon> CE example

attributeChangedCallback runs early!

by dannye

HTML

<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");
      if (!this.img) 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 = "eu";
    flag2.setAttribute("country", "nl");
    document.body.append(flag1); // runs connectedCallback again!! Don't add another IMG!
  }
</script>

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

JavaScript

/***************************************************************/
  // chessboard Files and Ranks are drawn from A8 to H1
  const FILES = "ABCDEFGH".split(""); // create an Array, saves us typing the whole Array
  const RANKS = [8, 7, 6, 5, 4, 3, 2, 1];
  // CSS colors for squares
  const WHITE_SQUARE_COLOR = "#f0e9c5";
  const BLACK_SQUARE_COLOR = "#b58863";
  // Custom Element Name: <schaak-bord>
  const CHESS_BOARD = "schaak-bord";
  // Custom Element Name: <schaak-bord-square>
  const CHESS_BOARD_SQUARE = CHESS_BOARD + "-square";

  /***************************************************************/
  // Object Oriented Programming: All our Custom Elements will 
  // extend from this BaseClass and inherit ALL its Methods and Properties
  class GameBoardElement extends HTMLElement{
      /*========================= CHESS BOARD setProperty =======*/
      setProperty(name, value) {
        this.style.setProperty("--" + name, value);
      }
      /*========================= CHESS BOARD setProperty =======*/
      log(...args) {
        console.log("%c <schaak-bord> ","background:purple;color:gold",...args);
      }
  }
  
  /***************************************************************/
  customElements.define(
    CHESS_BOARD_SQUARE,
    class extends GameBoardElement {
      connectedCallback() {
        this.innerHTML = this.id; // show square id A8 to H1
      }
    }
  );

  /***************************************************************/
  customElements.define(
    CHESS_BOARD,
    class extends GameBoardElement {
      /*========================= CHESS BOARD CONSTRUCTOR =======*/
      constructor() {
        // A shadowDOM allows multiple <schaak-bord> Elements in ONE page
        super().attachShadow({
          mode: "open", // define an editable shadowDOM for the custom element
        });

        /*--helper functions-------------------------------------*/
        let addLayer = (id) => {
          let layer = document.createElement("div");
      ...