HTMX/React-like component Tag with Vue.js-like refresh

A React like component.
You can embed them in the files you call as well at a cost of construction time.

Syntax:
<component-tag src="./comp.html" timeout="100" reloads="yes"/> <!-- 100 * 1000 = 100,000  -->
comp.html:
<main>
  <h1>Hello World</h1>
</main>

EZ!


// Task: Create a tag that fetches HTML from a URL and inserts it into the DOM.
// 
// Requirements:
// 
// - Tag must be able to fetch HTML from a URL
// - Tag must be able to insert HTML into the DOM

let ce = window.customElements;

class ComponentTag extends HTMLElement {
  constructor() {
    super();
    this.attachShadow({ mode: 'open' });
    this.reloads = 'yes';

    this.paths = {};

    this.fetchHTML = this.fetchHTML.bind(this);
    this.insertHTML = this.insertHTML.bind(this);
    this.render = this.render.bind(this);

    this.timeout = this.getAttribute('timeout') * 1000 || false;
    this.reloads = this.getAttribute('reloads') || 'yes';

    this.attachInternals();

    this.__timer = setInterval(() => {
      // console.log("[REP]  " + this.constructor.name + " - Timeout reached.");
      if (this.reloads === 'yes' || this.reloads === true) this.render();
    }, this.timeout || 5000);
  }

  attachInternals() {
    let shadowRoot = this.shadowRoot;
    this.__shadowRoot = shadowRoot;

    let template = document.createElement('template');
    template.innerHTML = `
      <style>
        :host, slot {
          display: block;
        }
        
        :host([loading]), slot([loading]) {
          opacity: 0;
        }
        
        :host([loaded]), slot([loaded]) {
          opacity: 1;
        }
      </style>
      <slot></slot>
    `;

    this.slotted = shadowRoot.appendChild(template.content.cloneNode(true));

    let slot = shadowRoot.querySelector('slot');

    this.paths = {
      slot,
    };
  }

  fetchHTML(url) {
    return new Promise((resolve, reject) => {
      let xhr = new XMLHttpRequest();
      xhr.open('GET', url);
      xhr.onload = () => {
        if (xhr.status === 200) {
          this.lastReq = xhr;
          resolve(xhr.responseText);
        } else {
          reject(xhr.statusText);
        }
      };
      xhr.onerror = () => {
        reject(xhr.statusText);
      };
      xhr.send();
    });
  }

  insertHTML(html) {
    let slot = this.paths.slot;
    slot.innerHTML = html;
    slot.__innerHTML = html;
    // console.log(this.paths);
  }

  connectedCallback() {
    let url = this.getAttribute('src');

    this.fetchHTML(url)
      .then(html => {
        this.insertHTML(html);
        this.render()
      })
      .catch(err => {
        console.error(err);
      });
  }

  render(html) {

    this.timeout = this.getAttribute('timeout') * 1000 || false
    this.reloads = this.getAttribute('reloads') ?? 'yes';

    // Next.JS like fetching
    let url = this.getAttribute('src');

    let pass = false;
    let xhr = new XMLHttpRequest();

    // We want to check if the HTML has changed since the last time we rendered
    // If it hasn't changed, we don't need to fetch the HTML again
    // Otherwise, we need to fetch the HTML again

    new Promise((resolve, reject) => {
      xhr.open('HEAD', url);

      xhr.onload = () => {
        if (xhr?.status === 200) {
          // this.lastReq = xhr;
          resolve(xhr);
        } else {
          reject(false);
        }
      };

      xhr.onerror = () => {
        reject(false);
      };

      xhr.send();

    })

      .then((res) => {
        if (
          this.lastReq.getResponseHeader('content-length') != res.getResponseHeader('content-length')
        ) {
          xhr = new XMLHttpRequest();

          xhr.open('GET', url);
          xhr.onload = () => {
            if (xhr.status === 200) {
              this.lastReq = xhr;
              this.shadowRoot.innerHTML = xhr.responseText;
              this.render();
            }

          };

          xhr.onerror = () => {

          }

          xhr.send();
        }

      })

      .finally(() => {
        if (this.innerHTML != this.shadowRoot.innerHTML) {
          // Create a template
          let template = document.createElement('template');

          // Set the innerHTML of the template to the SR or html
          template.innerHTML = this.shadowRoot.innerHTML || html;

          this.innerHTML = template.innerHTML
        }
      })
  }
}

ce.define('component-tag', ComponentTag);


2 Likes