<!DOCTYPE html>
<html lang="en">
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <meta http-equiv="X-UA-Compatible" content="ie=edge">
  <title>GOAT group</title>
  <h1>Who's the GREATEST GROUP OF ALL TIME?!</h1>
  <input id="group-sub-input"/>
  <button id="group-sub-btn">Submit</button>
  <goat-group guess="">🤔</goat-group>
body {
      font-family: monospace; /* notice how the font crosses the shadow DOM boundary*/
h1 {
  color: green;
// light DOM stuff
const groupSubmitInput = document.querySelector('#group-sub-input');
const groupSubmitBtn = document.querySelector('#group-sub-btn');
const goatGroupEl = document.querySelector('goat-group');
groupSubmitBtn.addEventListener('click', () => {
  console.log('button clicked!', groupSubmitInput.value);
  goatGroupEl.setAttribute('guess', groupSubmitInput.value);
  groupSubmitInput.value = '';

// Listen for the Custom Event dispatched from the Web Component
window.addEventListener('guessed', (e)=>{

// Create the template element
const template = document.createElement('template');
template.innerHTML = `
<div id='verdict'></div>

//HTMLElement is the base class that powers other native elements like <input>, <button>, <video> etc.
class GoatGroup extends HTMLElement {
  // which attributes do we want to listen for changes
  static get observedAttributes() { return ['guess']; };
  constructor() {
    super(); // get all the HTMLElement goodness (methods and properties)
    // clone the Template so we can use it.
    const clone = document.importNode(template.content, true);
    // using Shadow DOM
    this.attachShadow({ mode: 'open' }); // should the shadow root's internal features be accessible via JavaScript? open or closed.
    this.addEventListener('click', () => {
      this.style.color = 'red';
  // lifecycle callback invoked each time your custom element is appended to the DOM. Can be called multiple times.
  connectedCallback() {
    console.log('goat group connected!');
    this.style.color = 'blue';
  // called when an attribute has been changed.
  attributeChangedCallback(name, oldValue, newValue) {
    console.log('attribute changed!: ', name, newValue);
    if (newValue.trim() === ''){
      this.shadowRoot.querySelector('#verdict').innerText = "";
    } else if (newValue.toLowerCase() === 'outkast'){
      this.shadowRoot.querySelector('h1').innerText = `${newValue.toUpperCase()}!!!!`;
      this.shadowRoot.querySelector('#verdict').innerText = "You're RIGHT!!";
      this.dispatchEvent(new CustomEvent('guessed', {
        detail: 'yup',
        bubbles: true, //bubble up through the component
        composed: true //allows event to cross into light DOM
    } else {
      this.shadowRoot.querySelector('h1').innerText = `${newValue}?!`;
      this.shadowRoot.querySelector('#verdict').innerText = "They're okay, I guess.";
      this.dispatchEvent(new CustomEvent('guessed', {
        detail: 'nope',
        bubbles: true, //bubble up through the component
        composed: true //allows event to cross into light DOM
  // called when your custom element is disconnected from the page. Useful to remove event listeners, intervals etc to prevent memory leaks
  disconnectedCallback() {
    console.log('goat group disconnected!');    

// connects our custom element to our class.
customElements.define('goat-group', GoatGroup);

External CSS

This Pen doesn't use any external CSS resources.

External JavaScript

This Pen doesn't use any external JavaScript resources.