<div class="container-fluid">
  <header>
    <h1>Custom Element Demo</h1>
  </header>
  <hr/>
  <div class="row">
    <div class="col-sm-6">
      <div id="prefill">
        <div class="input-group">
          <span class="input-group-addon">
            Autofill this form
          </span>
          <div class="dropdown-menu dropdown-menu-right" aria-labelledby="opts">
          </div>
          <button class="btn btn-secondary dropdown-toggle form-control" type="button" id="opts" data-toggle="dropdown" aria-haspopup="true" aria-expanded="false">Select Data</button>
        </div>
      </div>
      <hr />
      <form>
      <div class="form">
        <div class="form-group">
          <label for="width">Width <small class="text-muted">(in px)</small></label>
          <input type="number" class="form-control" id="width" placeholder="Width">
        </div>
        <div class="form-group">
          <label for="alt">Alt <small class="text-muted">(can be empty)</small></label>
          <input type="text" class="form-control" id="alt" placeholder="Alt Attriibute">
        </div>
        <div class="form-group">
          <label for="src">Source Image <small class="text-muted">(src)</small></label>
          <input type="url" class="form-control" id="src" placeholder="http://.....">
        </div>
        <div class="form-group">
          <label for="caption">Caption <small class="text-muted">(figcaption)</small></label>
          <input type="text" class="form-control" id="caption" placeholder="Caption">
        </div>
      </div>
      <div>
        <button type="button" class="btn btn-primary" id="append">Add</button>
        <button type="button" class="btn btn-danger float-right" id="clear">Remove all</button>
        <button type="reset" class="btn btn-warning">Reset</button>
      </div>
      </form>
      <hr>
    </div>
    <div class="col-sm-6">
      <div id="app"></div>
    </div>
  </div>
</div>
img-figure {
  display: block;
  max-width: 400px;
  img {
    max-width: 100%;
  }
  figcaption {
    width: 100%;
    text-overflow: ellipsis;
    overflow: hidden;
    white-space: nowrap;
  }
} 
pre {
  border: 0;
  padding: 10px;
  // margin: -1.1rem;
  margin: 0;
  border-radius: 0.25rem;
}
#app > div {
  margin: 1.125rem 0;
  &:first-child {
    margin-top: 0;
  }
}
.tab-bodies {
  > div:not(.active) {
    display: none
  }
}
let elName = 'img-figure';
const template = state => `<figure>
  <img
    src="${state.src}"
    alt="${state.alt || state.caption}">
  <figcaption>${state.caption}</figcaption>
</figure>
`;

class ImgFigure extends HTMLElement {
  connectedCallback() {
    this.src = this.getAttribute("src") || null;
    this.caption = this.getAttribute("caption") || "";
    this.alt = this.getAttribute("alt") || null;
    this.render();
  }

  render() {
    this.innerHTML = template({
      src: this.src,
      alt: this.alt,
      caption: this.caption
    });
  }
}

customElements.define("img-figure", ImgFigure);

const maxWidth = 400;

const data = [
  {
    src: "//res.cloudinary.com/time2hack/image/upload/q_auto:good/goodbye-xmlhttprequest-ajax-with-fetch-api-demo.png",
    alt: "GoodBye XMLHttpRequest",
    caption: "GoodBye XMLHttpRequest; AJAX with fetch API (with Demo)"
  },
  {
    src: "//res.cloudinary.com/time2hack/image/upload/iterating-data-with-map-reduce-foreach-and-filter.png",
    alt: "Array Iteration Functions",
    caption: 'Iterating data with Map, Reduce, ForEach and Filter'
  },
  {
    src: "//res.cloudinary.com/time2hack/image/upload/q_auto:good/ways-to-host-single-page-application-spa-static-site-for-free.png",
    alt: "Free Static Hosting",
    caption:
      "Ways to host single page application (SPA) and Static Site for FREE"
  },
  {
    src: "//res.cloudinary.com/time2hack/image/upload/v1508699480/javascript-template-literals.png",
    alt: "JavaScript Template Literals",
    caption: "Benefits of JavaScript Template Literals and Tagged Templates"
  }
];
const card = data => {
  const id = `${+new Date}`;
  return `<div class="card" id="card_${id}">
  <div class="card-header">
    <ul class="nav nav-tabs card-header-tabs">
      <!--
      <li class="nav-item">
        <a class="nav-link disabled" href="#">#card_${id}</a>
      </li>
      -->
      <li class="nav-item">
        <a class="nav-link active" data-target="#card_${id} .preview" href="#">Preview</a>
      </li>
      <li class="nav-item">
        <a class="nav-link" data-target="#card_${id} .source" href="#">Source</a>
      </li>
      <li class="nav-item">
        <a class="nav-link" data-target="#card_${id} .data" href="#">Data</a>
      </li>
    </ul>
  </div>
  <div class="card-body tab-bodies">
    <div class="preview active">${elSrc(data)}</div>
    <div class="source">${source(data)}</div>
    <div class="data"><pre>${JSON.stringify(data, null, 2)}</pre></div>
  </div>
</div>
`;
};
const elSrc = data => {
  console.log(elName)
  return `<${elName}
  style="max-width: ${data.maxWidth || maxWidth}px"
  src="${data.src}"
  alt="${data.alt}"
  caption="${data.caption}"
></${elName}>`;
}

// Add element in conventional way
const element = data => {
  // create element
  const i = document.createElement(elName);

  //set the required attributes
  i.setAttribute("src", data.src);
  i.setAttribute("caption", data.caption);
  i.setAttribute("alt", data.alt);
  i.style.maxWidth = data.maxWidth || maxWidth;
  return i;
};
const clear = () => (document.querySelector("#app").innerHTML = "");

const _prettify = block => {
  window.PR &&
    (function(block) {
      block.classList.add("prettyprint");
      PR.prettyPrint();
    })(block);
};
const source = data => {
  const src = elSrc(data).replace(/</ig, '&lt;').replace(/>/ig, '&gt;');
  return `<pre>${src}</pre>`;
};

const getFormData = () => {
  return {
    maxWidth: document.querySelector("#width").value,
    alt: document.querySelector("#alt").value,
    src: document.querySelector("#src").value,
    caption: document.querySelector("#caption").value
  };
};
const add = data => {
  const d = document.createElement('div');
  d.innerHTML = card(data);
  document.querySelector("#app").insertBefore(d, document.querySelector("#app").firstChild);
  Array.prototype.slice.call(document.querySelectorAll('pre:not(.prettyprinted)'))
    .forEach(el => _prettify(el))
}
//attach to the DOM
clear();
add(data[0]);

data.forEach((item, index) => {
  const container = document.querySelector('#prefill .dropdown-menu');
  const a = document.createElement('a');
  a.classList.add('dropdown-item')
  a.setAttribute('href', '#');
  a.setAttribute('data-index', index);
  a.innerHTML = `<img src="${item.src}" height="35" alt="${item.alt}" class="rounded" /> ${item.caption}`;
  container.appendChild(a);
})

document.querySelector("#append").addEventListener("click", () => {
  add(getFormData());
});
document.querySelector("#clear").addEventListener("click", () => {
  clear();
});
document.querySelector("#prefill").addEventListener("click", (e) => {
  e.preventDefault();
  if(e.target.classList.contains('dropdown-item')){
    const index = e.target.dataset.index;
    document.querySelector("#width").value = data[index].maxWidth;
    document.querySelector("#alt").value = data[index].alt
    document.querySelector("#src").value = data[index].src
    document.querySelector("#caption").value = data[index].caption;
    document.querySelector('#prefill .dropdown-menu').classList.remove('show')
  }
});
document.querySelector("#opts").addEventListener("click", (e) => {
  e.preventDefault();
  const toggle = document.querySelector('#prefill .dropdown-menu').classList.contains('show');
  document.querySelector('#prefill .dropdown-menu').classList[toggle ? 'remove' : 'add']('show');
  
});

//All the tab changing events
document.querySelector("#app").addEventListener("click", (e) => {
  e.preventDefault();
  const {target} = e;
  const a = 'active';

  if(target.classList.contains('nav-link') && target.dataset.target) {
    const t = target.dataset.target;
    const c = t.split(' ')[0];
    document.querySelector(`${c} .nav-link.${a}`).classList.remove(a);
    target.classList.add(a);
    document.querySelector(`${c} .tab-bodies > .${a}`).classList.remove(a);
    document.querySelector(target.dataset.target).classList.add(a);
  }
})

External CSS

  1. https://cdnjs.cloudflare.com/ajax/libs/twitter-bootstrap/4.0.0-beta.2/css/bootstrap.css
  2. https://cdn.rawgit.com/pankajpatel/c4889cc260bdd79007a02045c0cdbd90/raw/47a7e860ea2d3b367b533ec4ff4e351755008f14/prettify.css

External JavaScript

  1. https://cdnjs.cloudflare.com/ajax/libs/webcomponentsjs/1.0.19/webcomponents-hi-sd-ce.js
  2. //cdnjs.cloudflare.com/ajax/libs/prettify/r298/run_prettify.min.js