DEV Community

Bryan Ollendyke
Bryan Ollendyke

Posted on

twitter-embed in LitElement and VanillaJS

I've been needing a bit of a pallet cleanse from a work perspective so I dug into our issue queue and found what I figured would be easy: Embedding twitter conversations #370

Then as I started into it I was like "hmm.. this might make a useful tutorial" so I asked what people wanted it in:

Here's the full video walk through, below I'll post code and some things covered

Links in the video

VanillaJS

Here's the code for the Vanilla version, it's 100ish lines

class TwitterEmbed extends HTMLElement {
  static get tag() {
    return "twitter-embed";
  }
  /**
   * HTMLElement spec / class based architecture in general
   */
  constructor() {
    super();
    this.dataWidth = this.getAttribute("data-width")
      ? this.getAttribute("data-width")
      : "550px";
    this.dataTheme = this.getAttribute("data-theme")
      ? this.getAttribute("data-theme")
      : "light";
    this.tweet = this.getAttribute("tweet") ? this.getAttribute("tweet") : null;
    this.tweetId = this.getAttribute("tweet-id")
      ? this.getAttribute("tweet-id")
      : null;
    this.allowPopups = this.getAttribute("no-popups") ? "" : "allow-popups";
  }
  /**
   * HTMLElement spec
   */
  static get observedAttributes() {
    return ["tweet", "data-width", "data-theme", "tweet-id", "no-popups"];
  }
  /**
   * HTMLElement spec
   */
  attributeChangedCallback(attr, oldValue, newValue) {
    if (attr == "tweet" && newValue && newValue.includes("twitter.com")) {
      this.tweetId = newValue.split("/").pop();
    }
    if (attr == "no-popups") {
      this.allowPopups =
        newValue == "no-popups" ||
        newValue == "" ||
        !newValue ||
        newValue == null ||
        newValue == "null"
          ? ""
          : "allow-popups";
    }
    if (["no-popups", "tweet-id", "data-width", "data-theme"].includes(attr)) {
      this.innerHTML = this.html;
    }
  }
  get dataWidth() {
    return this.getAttribute("data-width");
  }
  set dataWidth(value) {
    if (value == null || !value) {
      this.removeAttribute("data-width");
    } else {
      this.setAttribute("data-width", value);
    }
  }
  get dataTheme() {
    return this.getAttribute("data-theme");
  }
  set dataTheme(value) {
    if (!value || !["dark", "light"].includes(value)) {
      this.dataTheme = "light";
    } else {
      this.setAttribute("data-theme", value);
    }
  }
  get tweetId() {
    return this.getAttribute("tweet-id");
  }
  set tweetId(value) {
    if (value == null) {
      this.removeAttribute("tweet-id");
    } else {
      this.setAttribute("tweet-id", value);
    }
  }
  get tweet() {
    return this.getAttribute("tweet");
  }
  set tweet(value) {
    if (value == null) {
      this.removeAttribute("tweet");
    } else {
      this.setAttribute("tweet", value);
    }
  }
  /**
   * my own convention, easy to remember
   */
  get html() {
    return `
    <div
      class="twitter-tweet twitter-tweet-rendered"
      style="display: flex; max-width: ${
        this.dataWidth
      }; width: 100%; margin-top: 10px; margin-bottom: 10px;">
      <iframe
        sandbox="allow-same-origin allow-scripts ${this.allowPopups}"
        scrolling="no"
        frameborder="0"
        loading="lazy"
        allowtransparency="true"
        allowfullscreen="true"
        style="position: static; visibility: visible; width: ${
          this.dataWidth
        }; height: 498px; display: block; flex-grow: 1;"
        title="Twitter Tweet"
        src="https://platform.twitter.com/embed/index.html?dnt=true&amp&amp;frame=false&amp;hideCard=false&amp;hideThread=false&amp;id=${
          this.tweetId
        }&amp;lang=en&amp;origin=http%3A%2F%2Flocalhost%3A8000%2Felements%2Ftwitter-embed%2Fdemo%2Findex.html&amp;theme=${
      this.dataTheme
    }&amp;widgetsVersion=223fc1c4%3A1596143124634&amp;width=${this.dataWidth}"
        data-tweet-id="${this.tweetId}">
      </iframe>
    </div>`;
  }
}
customElements.define(TwitterEmbed.tag, TwitterEmbed);
export { TwitterEmbed };
Enter fullscreen mode Exit fullscreen mode

Pros

  • 0 dependencies
  • platform level code only, all valid conventions
  • should theoretically work forever and in any platform

Cons

  • verbose to get data binding to be what's expected
  • some weird constructor things / default value stuff
  • re-renders are heavy handed

LitElement

LitElement is a popular

import { LitElement, html } from "lit-element/lit-element.js";
class TwitterEmbedLit extends LitElement {
  static get tag() {
    return "twitter-embed-lit";
  }
  /**
   * HTMLElement spec
   */
  constructor() {
    super();
    this.dataWidth = "550px";
    this.dataTheme = "light";
    this.tweet = null;
    this.tweetId = null;
    this.allowPopups = "allow-popups";
  }
  /**
   * LitElement properties definition
   */
  static get properties() {
    return {
      tweet: {
        type: String
      },
      dataWidth: {
        type: String,
        attribute: "data-width"
      },
      dataTheme: {
        type: String,
        attribute: "data-theme"
      },
      tweetId: {
        type: String,
        attribute: "tweet-id"
      },
      noPopups: {
        type: Boolean,
        attribute: "no-popups"
      },
      allowPopups: {
        type: String
      }
    };
  }
  /**
   * LitElement equivalent of attributeChangedCallback
   */
  updated(changedProperties) {
    changedProperties.forEach((oldValue, propName) => {
      if (propName === "noPopups") {
        if (this[propName]) {
          this.allowPopups = "";
        } else {
          this.allowPopups = "allow-popups";
        }
      }
      if (
        propName === "tweet" &&
        this[propName] &&
        this[propName].includes("twitter.com")
      ) {
        this.tweetId = this[propName].split("/").pop();
      }
    });
  }
  /**
   * Popular convention / LitElement
   */
  render() {
    return html`
      <div
        class="twitter-tweet twitter-tweet-rendered"
        style="display: flex; max-width: ${this
          .dataWidth}; width: 100%; margin-top: 10px; margin-bottom: 10px;"
      >
        <iframe
          sandbox="allow-same-origin allow-scripts ${this.allowPopups}"
          scrolling="no"
          frameborder="0"
          loading="lazy"
          allowtransparency="true"
          allowfullscreen
          style="position: static; visibility: visible; width: ${this
            .dataWidth}; height: 498px; display: block; flex-grow: 1;"
          title="Twitter Tweet"
          src="https://platform.twitter.com/embed/index.html?dnt=true&amp;frame=false&amp;hideCard=false&amp;hideThread=false&amp;id=${this
            .tweetId}&amp;lang=en&amp;origin=http%3A%2F%2Flocalhost%3A8000%2Felements%2Ftwitter-embed%2Fdemo%2Findex.html&amp;theme=${this
            .dataTheme}&amp;widgetsVersion=223fc1c4%3A1596143124634&amp;width=${this
            .dataWidth}"
          data-tweet-id="${this.tweetId}"
        >
        </iframe>
      </div>
    `;
  }
}

customElements.define(TwitterEmbedLit.tag, TwitterEmbedLit);
export { TwitterEmbedLit };
Enter fullscreen mode Exit fullscreen mode

Cons

  • 1 dependency
  • Some library specific conventions

Pros

  • conventions are simple
  • lit-html template rewriting conventions may become platform level code in the future
  • tidy code, slightly less to write, easier to read
  • faster re-render, though very small to see in this example

Top comments (0)