DOM Nodes

document.*


    var doc_tests = document.getElementById("document_tests");
    function doc_test() {
      var doc = document;
      doc_tests.innerHTML =
        "doc.documentElement: " + doc.documentElement +
        "doc.defaultView: " + doc.defaultView +
        "doc.activeElement: " + doc.activeElement +
        "doc.scrollingElement: " + doc.scrollingElement +
        "doc.head: " + doc.head +
        "doc.contentType: " + doc.contentType +
        "doc.implementation: " + doc.implementation +
        "doc.inputEncoding: " + doc.inputEncoding +
        "doc.characterSet: " + doc.characterSet +
        "doc.domain: " + doc.domain +
        "doc.location: " + doc.location +
        "doc.baseURI: " + doc.baseURI +
        "doc.URL: " + doc.URL +
        "doc.referrer: " + doc.referrer +
        "doc.readyState: " + doc.readyState +
        "doc.lastModified: " + doc.lastModified +
        "doc.documentMode: " + doc.documentMode +
        "doc.compatMode: " + doc.compatMode +
        "doc.cookie: " + doc.cookie +
        "doc.visibilityState: " + doc.visibilityState +
        "doc.hidden: " + doc.hidden +
        "doc.fullscreenEnabled: " + doc.fullscreenEnabled +
        "doc.fullscreenElement: " + doc.fullscreenElement +
        "doc.title: " + doc.title +
        "doc.body: " + doc.body +
        "doc.hasFocus(): " + doc.hasFocus() +
        "doc.documentURI: " + doc.documentURI +
        "doc.strictErrorChecking: " + doc.strictErrorChecking +
        "doc.scripts: " + doc.scripts +
        "doc.currentScript: " + doc.currentScript +
        "doc.images: " + doc.images +
        "doc.links: " + doc.links +
        "doc.anchors: " + doc.anchors +
        "doc.forms: " + doc.forms +
        "doc.applets: " + doc.applets +
        "doc.embeds: " + doc.embeds +
        "doc.plugins: " + doc.plugins +
        "doc.fonts: " + doc.fonts +
        "doc.styleSheets: " + doc.styleSheets +
        "doc.designMode: " + doc.designMode;
    }
    setInterval(doc_test,500);
  

Element

INFO

Touch this area

    function el_info_test() {
      var el_it = document.getElementById("el_info_test");
      el_it.innerHTML =
        "el_it.tagName: " + el_it.tagName +
        "el_it.nodeName: " + el_it.nodeName +
        "el_it.nodeType: " + el_it.nodeType +
        "el_it.nodeValue: " + el_it.nodeValue +
        "el_it.offsetTop: " + el_it.offsetTop +
        "el_it.offsetLeft: " + el_it.offsetLeft +
        "el_it.offsetWidth: " + el_it.offsetWidth +
        "el_it.offsetHeight: " + el_it.offsetHeight +
        "el_it.offsetParent: " + el_it.offsetParent +
        "el_it.getBoundingClientRect: " + el_it.getBoundingClientRect +
        "el_it.ownerDocument: " + el_it.ownerDocument +
        "el_it.clientTop: " + el_it.clientTop +
        "el_it.clientLeft: " + el_it.clientLeft +
        "el_it.clientWidth: " + el_it.clientWidth +
        "el_it.clientHeight: " + el_it.clientHeight +
        "el_it.scrollWidth: " + el_it.scrollWidth +
        "el_it.scrollHeight: " + el_it.scrollHeight;
    }
    el_info_test();
  

ATTRIBUTES

Tests DIV

    <div
      id="el_attributes_test"
      class="example h-25"
      title="Attributes TEST"
      dir="ltr"
      lang="en"
      tabindex="132"
      onclick = "console.log('Dont click ME !')"
      onmouseover = "this.style.background = 'lightgray'"
      onmouseout = "this.style.background = 'white'"
      contenteditable
    >Tests DIV</div>
  

    var el_at = document.getElementById("el_attributes_test");
    function el_attributes_test() {
      var attr_thing = document.createAttribute("data-thing-two");
      attr_thing.value = "thing value two";
      el_at.innerHTML = "Array.from(el_at.attributes).map((e)=>(e.name+':'+e.value)):" +
        Array.from(el_at.attributes).map(
          (e)=>(e.name+" : "+e.value + " , specified:" + (e.specified ? "yes":"no")) ) +
        "el_at.attributes.getNamedItem('onclick').value: " +
        el_at.attributes.getNamedItem('onclick').value +
        "el_at.hasAttributes(): " + el_at.hasAttributes() +
        "el_at.hasAttribute('id'): " + el_at.hasAttribute('id') +
        "el_at.hasAttribute('data-thing'): " + el_at.hasAttribute('data-thing') +
        "el_at.getAttribute('id'): " + el_at.getAttribute('id') +
        "el_at.setAttribute('data-thing', 'thing value')" +
        "el_at.getAttribute('data-thing'): " + el_at.getAttribute('data-thing') +
        "el_at.setAttributeNode(attr_thing): " + el_at.setAttributeNode(attr_thing) +
        "el_at.getAttributeNode('data-thing-two'): " + el_at.getAttributeNode('data-thing-two') +
        "el_at.removeAttribute('data-thing')" +
        "el_at.hasAttribute('data-thing'): " + el_at.hasAttribute('data-thing') +
        "el_at.removeAttributeNode(attr_thing): " +
          el_at.removeAttributeNode(attr_thing).name + // .value
        "el_at.id: " + el_at.id +
        "el_at.title: " + el_at.title +
        "el_at.lang: " + el_at.lang +
        "el_at.dir: " + el_at.dir +
        "el_at.tabIndex: " + el_at.tabIndex;
    }
    el_attributes_test();

    el_at.attributes.removeNamedItem("onmouseout");
    var onmouseout = document.createAttribute("onmouseout");
    onmouseout.value = "this.style.background = 'white'";
    el_at.attributes.setNamedItem(onmouseout);
  

FIND

initial value

    var el_find = document.getElementById("el_find_test");
    el_find.accessKey = "e";

    var d = document;
    el_find.innerHTML =
      "d.querySelector('#el_find_test').innerHTML: " +
      d.querySelector('#el_find_test').innerHTML +
      d.querySelectorAll('h3')[1].innerHTML +
      d.getElementsByTagName('h2')[1].id +
      d.getElementsByClassName('find_test_class')[0].id +
      d.getElementById('document').nextSibling.nextSibling.tagName +
      d.getElementById('element').nextElementSibling.nextElementSibling.tagName +
      d.getElementById('el_find_test').previousSibling.previousSibling.tagName +
      d.getElementById('el_find_test').previousElementSibling.tagName
      "el_find.accessKey: " + el_find.accessKey;

    var treeWalker = document.createTreeWalker(
      document.body,
      NodeFilter.SHOW_ELEMENT,
      {
        acceptNode: function(node) {
          if ( /^H2*$/.test(node.tagName) ) {
          // if ( ! /^\s*$/.test(node.data) ) {
            return NodeFilter.FILTER_ACCEPT;
          }
        }
      },
      false
    );
    // avoid changing DOM inside NodeFilter, make it here ...
    while(treeWalker.nextNode()) {
      el_find.innerHTML += "treeWalker.currentNode.id: " + treeWalker.currentNode.id;
    }
    // OR
    // var node;
    // while ((node = iterator.nextNode())) { alert(node.data); }

    var nodeIterator = document.createNodeIterator(
        document.body,
        NodeFilter.SHOW_ELEMENT,
        function(node) {
            return node.nodeName === 'H3' ? NodeFilter.FILTER_ACCEPT : NodeFilter.FILTER_REJECT;
        }
    );
    var currentNode;
    while (currentNode = nodeIterator.nextNode()) {
      el_find.innerHTML += "nodeIterator.nextNode() innerHTML: " + currentNode.innerHTML;
    }
  

STYLE

initial value

    function el_style_test() {
      var el_style = document.getElementById("el_style_test");
      // var d = document;
      el_style.style.color = "fireBrick";
      el_style.innerHTML =
        "SET el_style.style.color = 'fireBrick'" +
        "el_style.style.color: " + el_style.style.color +
        "el_style.classList: " + el_style.classList +
        "el_style.className: " + el_style.className;
      el_style.className = "example";
      el_style.innerHTML +=
        "SET el_style.className = 'example'" +
        "el_style.classList: " + el_style.classList +
        "el_style.className: " + el_style.className;
    }
    el_style_test();
  

CHECK / PARENT / CHILD

List 1: List 2: List 3: List 4:

    var check_tests = document.getElementById('check_tests');
    var check_tests_result = document.getElementById('check_tests_result');

    var list_1 = document.getElementById('myList1');
        list_1.contentEditable = true;
    var list_1_children = list_1.children;
    var list_2 = document.getElementById('myList2');
    var list_2_children = list_2.children;
    var list_3 = document.getElementById('myList3');
    var list_3_children = list_3.children;

    check_tests_result.innerHTML =
      list_1.contains(list_1_children[0]) +
      list_1.contains(list_2_children[0]) +
      list_1_children[0].isEqualNode(list_3_children[0]) +
      list_1_children[0].isEqualNode(list_2_children[0]) +
      list_1_children[0].isSameNode(list_1_children[0]) +
      list_1_children[0].isSameNode(list_3_children[0]) +
      list_1.compareDocumentPosition(list_1_children[0]) +
      list_2.compareDocumentPosition(list_3) +
      list_1.isContentEditable +
      list_3_children[1].parentNode.id +
      list_1_children[1].parentElement.id +
      list_1.childElementCount +
      list_1.firstChild.innerHTML +
      list_2.lastChild.innerHTML +
      list_3.firstElementChild.innerHTML +
      list_3.lastElementChild.innerHTML;

    // append new child to list_1
    var li_node = document.createElement("LI");
    var li_text = document.createTextNode("Juice");
    li_node.appendChild(li_text);
    list_1.appendChild(li_node);
    // then move it to the end of list_2 - appendChild
    var node = list_1.lastChild;
    list_2.appendChild(node);
    // then make it first in list_3 - insertBefore
    list_3.insertBefore(list_2.lastChild, list_3.childNodes[0])

    // remove and append same list item
    var item = document.getElementById("the_vodka");
    function removeLi() { item.parentNode.removeChild(item); }
    function appendLi() {
      list_3.appendChild(item);
    }

    // remove list item only by its ID
    // var item = document.getElementById("the_vodka");
    // item.parentNode.removeChild(item);

    // If the ul element has any child nodes, remove its first child node
    var list_4 = document.getElementById("myList4");
    if (list_4.hasChildNodes()) {
      // As long as ul has a child node, remove it
      while (list_4.hasChildNodes()) {
        list_4.removeChild(list_4.firstChild);
      }
    }

    // replace
    check_tests.replaceChild(
      document.createTextNode("No More List..."),
      check_tests.children[3]
    );

    // remove a span element from its parent and insert it to an h1 element in another document
    // var child = document.getElementById("mySpan");
    // function removeLi() { child.parentNode.removeChild(child);
    // function myFunction() {
    //   var frame = document.getElementsByTagName("IFRAME")[0]
    //   var h = frame.contentWindow.document.getElementsByTagName("H1")[0];
    //   var x = document.adoptNode(child);
    //   h.appendChild(x);
    // }

    // document.documentElement.isDefaultNamespace("http://www.w3.org/1999/xhtml")
  

EVENT






    var el_event_test = document.getElementById("el_event_test");
    var the_clicks = 0;
    var clicks_inc = (e) => {e.target.value = ++the_clicks};
    function event_focus () { el_event_test.focus(); }
    function event_blur () { el_event_test.blur(); }
    function event_click () { el_event_test.click(); }
    function event_add () {
      el_event_test.addEventListener("click", clicks_inc);
      el_event_test.addEventListener("mousemove", clicks_inc);
      el_event_test.addEventListener("touchmove", clicks_inc);
    }
    function event_remove () {
      el_event_test.removeEventListener("click", clicks_inc);
      el_event_test.removeEventListener("mousemove", clicks_inc);
      el_event_test.removeEventListener("touchmove", clicks_inc);
    }
  

SCROLL



------------------------------------------------------------------------------------------1------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------2------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------3------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------4------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------5------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------6------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------7------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------8------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------9------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------10------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------11------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------12------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------13------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------14------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------15------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------16------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------17------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------18------------------------------------------------------------------------------------------
------------------------------------------------------------------------------------------19------------------------------------------------------------------------------------------

    function scroll_into(){
      document.getElementById("scroll_btn").scrollIntoView();
    }
    function scroll_top(){
      document.getElementById("scroll_test").scrollTop += 10;
    }
    function scroll_left(){
      document.getElementById("scroll_test").scrollLeft += 10;
    }
  

HTMLCollection


    var ct = document.getElementById("coll_tests"),
        headers = document.getElementsByTagName("H2");

    ct.innerHTML = "header tags count: " + headers.length + "IDs:" +
    Array.from(headers).map((e)=>(e.id)) +
    "headers.namedItem('collection').offsetTop: " +
    headers.namedItem("collection").offsetTop;
  

Event Object

click to see event info
click to see event info

    <button onclick="eventTests(event)">Try it</button>
  

    var for_event_div = document.getElementById("for_event_div");
    for_event_div.addEventListener("click", clickTests);

    function clickTests(e) {
      e.preventDefault();
      e.stopImmediatePropagation();
      e.stopPropagation();
      e.cancelBubble = true;

      var for_event = document.getElementById("for_event");
      var for_event_result = document.getElementById("for_event_result");

      for_event_result.innerHTML = "e.type: " + e.type +
        "e.target.id: " + e.target.id +
        "e.currentTarget.id: " + e.currentTarget.id +
        "e.bubble: " + e.bubble +
        "e.cancelable: " + e.cancelable +
        "e.defaultPrevented: " + e.defaultPrevented +
        "e.eventPhase: " + e.eventPhase + " " +
        (
          !e.eventPhase ? "NONE"
          : ((e.eventPhase==1)?"CAPTURING_PHASE"
          :(e.eventPhase==2)?"AT_TARGET"
          :"BUBBLING_PHASE")
        ) +
        "e.isTrusted: " + e.isTrusted +
        "e.composedPath(): " + e.composedPath() +
        "e.timeStamp: " + e.timeStamp;

      // var x = document.createEvent("MouseEvent");
      // x.initMouseEvent("click", true, true, window, 0, 0, 0, 0, 0, false, false, false, false, 0, null);
      // for_event.dispatchEvent(x);
    }
  
#img_ok
#img_nope
#select_test
#audio_test
#form_test


fullscreen test


    var event_test_result = document.getElementById("event_test_result");

    var img_ok = document.getElementById("img_ok");
    var img_nope = document.getElementById("img_nope");
    var select_test = document.getElementById("select_test");
    var audio_test = document.getElementById("audio_test");
    var form_test = document.getElementById("form_test");
    var event_input_test = document.getElementById("event_input_test");

    img_nope.addEventListener("error", eventsTests);
    img_nope.addEventListener("load", eventsTests); // UiEvent
    img_nope.addEventListener("abort", eventsTests); // UiEvent

    select_test.addEventListener("change", eventsTests);

    audio_test.addEventListener("load", eventsTests); // UiEvent
    audio_test.addEventListener("abort", eventsTests); // UiEvent
    audio_test.addEventListener("loadstart", eventsTests);
    audio_test.addEventListener("canplaythrough", eventsTests);
    audio_test.addEventListener("canplay", eventsTests);
    audio_test.addEventListener("ended", eventsTests);
    audio_test.addEventListener("durationchange", eventsTests);
    audio_test.addEventListener("seeking", eventsTests);
    audio_test.addEventListener("seekend", eventsTests);
    audio_test.addEventListener("progress", eventsTests);
    audio_test.addEventListener("playing", eventsTests);
    audio_test.addEventListener("play", eventsTests);
    audio_test.addEventListener("pause", eventsTests);
    audio_test.addEventListener("loadedmetadata", eventsTests);
    audio_test.addEventListener("loadeddata", eventsTests);
    audio_test.addEventListener("volumechange", eventsTests);
    audio_test.addEventListener("timeupdate", eventsTests);
    audio_test.addEventListener("suspend", eventsTests);
    audio_test.addEventListener("stalled", eventsTests);

    form_test.addEventListener("reset", eventsTests);
    form_test.addEventListener("submit", eventsTests);

    event_input_test.addEventListener("invalid", eventsTests);

    function eventsTests(e) {
        event_test_result.innerHTML += e.target.id + " - " + e.type;
        e.preventDefault();
    }

    // FULLSCREEN
    document.addEventListener("fullscreenchange",eventsTests);
    document.addEventListener("mozfullscreenchange",eventsTests);
    document.addEventListener("webkitfullscreenchange",eventsTests);
    document.addEventListener("msfullscreenchange",eventsTests);
    document.addEventListener("fullscreenerror",eventsTests);
    document.addEventListener("mozfullscreenerror",eventsTests);
    document.addEventListener("webkitfullscreenerror",eventsTests);
    document.addEventListener("msfullscreenerror",eventsTests);
    // element you want displayed in fullscreen
    var doc = document.getElementById("event_tests_area");
    // open fullscreen mode
    function openFullscreen() {
      if (doc.requestFullscreen) {
        doc.requestFullscreen();
      } else if (doc.mozRequestFullScreen) { /* Firefox */
        doc.mozRequestFullScreen();
      } else if (doc.webkitRequestFullscreen) { /* Chrome, Safari & Opera */
        doc.webkitRequestFullscreen();
      } else if (doc.msRequestFullscreen) { /* IE/Edge */
        doc.msRequestFullscreen();
      }
    }
    // close fullscreen mode
    function closeFullscreen() {
      if (document.exitFullscreen) {
        document.exitFullscreen();
      } else if (document.mozCancelFullScreen) {
        document.mozCancelFullScreen();
      } else if (document.webkitExitFullscreen) {
        document.webkitExitFullscreen();
      } else if (document.msExitFullscreen) {
        document.msExitFullscreen();
      }
    }

    document.body.addEventListener("offline", eventsTests, false);
    document.body.addEventListener("online", eventsTests, false);
  

    <iframe src="../js.html" name="iframe_a" id="menu_target" allowfullscreen="true"></iframe>
  

triggering/simulating built-in events


    function simulateClick() {
      var event = new MouseEvent('click', {
        view: window,
        bubbles: true,
        cancelable: true
      });
      var cb = document.getElementById('checkbox');
      var cancelled = !cb.dispatchEvent(event);
      if (cancelled) {
        // A handler called preventDefault.
        alert("cancelled");
      } else {
        // None of the handlers called preventDefault.
        alert("not cancelled");
      }
    }
  

UiEvent


    var ui_test = document.getElementById("ui_test");
    var ui_test_result = document.getElementById("ui_test_result");

    // <body onbeforeunload="return myFunction()">
    // OR
    // window.addEventListener("beforeunload", function(event) {
    //     event.returnValue = "Write something clever here..";
    // });

    ui_test.addEventListener("dblclick", uiFunction);

    document.getElementsByTagName("BODY")[0].addEventListener("load", uiFunction);
    document.getElementsByTagName("BODY")[0].addEventListener("select", uiFunction);

    //ui_test_result.addEventListener("scroll", uiFunction);
    window.addEventListener("resize", uiFunction);
    document.getElementsByTagName("BODY")[0].addEventListener("beforeunload", uiFunction);
    document.getElementsByTagName("BODY")[0].addEventListener("unload", uiFunction);

    function uiFunction(e) {
      ui_test_result.innerHTML = "e.type: " + e.type +
      "e.detail: " + e.detail +
      ( !e.view ? ""
        : (
            "e.view: " + e.view +
            "e.view.outerWidth: " + e.view.outerWidth +
            "e.view.outerHeight: " + e.view.outerHeight
          ) + ("e.view === window: " + (e.view===window))
      ) +
      ( !window ? ""
        : (
            "window: " + window +
            "window.outerWidth: " + window.outerWidth +
            "window.outerHeight: " + window.outerHeight
          )
      ) +
      "----------------------------------------";
    }
    // <body onload="checkCookies()">
    //
    // function checkCookies() {
    //   var text = "";
    //   if (navigator.cookieEnabled == true) {
    //     text = "Cookies are enabled.";
    //   } else {
    //     text = "Cookies are not enabled.";
    //   }
    //   document.getElementById("demo").innerHTML = text;
    // }
  

KeyboardEvent


    var kb_test = document.getElementById("kb_test");
    var kb_test_result = document.getElementById("kb_test_result");
    kb_test.addEventListener("keydown", kbFunction);
    kb_test.addEventListener("keypress", kbFunction);
    kb_test.addEventListener("keyup", kbFunction);
    function kbFunction(e) {
      kb_test_result.innerHTML += "e.type: " + e.type +
      "e.altKey: " + e.altKey +
      "e.ctrlKey: " + e.ctrlKey +
      "e.shiftKey: " + e.shiftKey +
      "e.metaKey: " + e.metaKey +
      "e.charCode: " + e.charCode +
      "e.code: " + e.code +
      "e.getModifierState('Shift'): " + e.getModifierState('Shift') +
      "e.key (keyboard key): " + e.key +
      "e.keyCode: " + e.keyCode +
      "e.location: " + e.location +
      "e.repeat: " + e.repeat +
      "e.which: " + e.which +
      "String.fromCharCode: " + String.fromCharCode( (e.which||e.keyCode) ) +
      "-----------------------------------------------";
      var x = e.which || e.keyCode;
      if (x == 27) {  // 27 is the ESC key
          alert ("You pressed the Escape key!");
      }
    }
  

MouseEvent


    var mouse_test = document.getElementById("mouse_test");

    mouse_test.addEventListener("click", mouseFunction);
    mouse_test.addEventListener("contextmenu", mouseFunction);
    mouse_test.addEventListener("dblclick", mouseFunction);
    mouse_test.addEventListener("mousedown", mouseFunction);
    mouse_test.addEventListener("mouseenter", mouseFunction);
    mouse_test.addEventListener("mouseleave", mouseFunction);
    mouse_test.addEventListener("mousemove", mouseFunction);
    mouse_test.addEventListener("mouseout", mouseFunction);
    mouse_test.addEventListener("mouseover", mouseFunction);
    mouse_test.addEventListener("mouseup", mouseFunction);

    function mouseFunction(e) {
      mouse_test.innerHTML = "e.type: " + e.type +
      "e.altKey: " + e.altKey +
      "e.ctrlKey: " + e.ctrlKey +
      "e.shiftKey: " + e.shiftKey +
      "e.metaKey: " + e.metaKey +
      "e.getModifierState('Shift'): " + e.getModifierState('Shift') +

      "e.button: " + e.button +
      "e.buttons: " + e.buttons +
      "-----------------------------------------------" +
      "e.screenX: " + e.screenX +
      "e.screenY: " + e.screenY +
      "-----------------------------------------------" +
      "e.clientX: " + e.clientX +
      "e.clientY: " + e.clientY +
      "-----------------------------------------------" +
      "e.pageX: " + e.pageX +
      "e.pageY: " + e.pageY +
      "-----------------------------------------------" +
      "e.offsetX: " + e.offsetX +
      "e.offsetY: " + e.offsetY +
      "-----------------------------------------------" +
      "e.movementX: " + e.movementX +
      "e.movementY: " + e.movementY +
      "-----------------------------------------------" +
      "e.region: " + e.region +
      "e.relatedTarget: " + e.relatedTarget +

      "e.which: " + e.which;

      var x = e.which || e.keyCode;
      if (x == 27) {  // 27 is the ESC key
          alert ("You pressed the Escape key!");
      }
    }
  

WheelEvent


    var wheel_test = document.getElementById("wheel_test");
    wheel_test.addEventListener("wheel", wheelFunction);
    function wheelFunction(e) {
      wheel_test.innerHTML = "e.type: " + e.type +
      "e.deltaX: " + e.deltaX +
      "e.deltaY: " + e.deltaY +
      "e.deltaZ: " + e.deltaZ +
      "e.deltaMode: " + e.deltaMode + " = "+
      (
        !e.deltaMode ? "pixels" : ( (e.deltaMode==1) ? "lines" : "pages")
      );
    }
  

TouchEvent

Touch this area

    var touch_test = document.getElementById("touch_test");
    var touch_test_result = document.getElementById("touch_test_result");

    touch_test.addEventListener("touchstart", touchFunction);
    touch_test.addEventListener("touchmove", touchFunction);
    touch_test.addEventListener("touchend", touchFunction);
    touch_test.addEventListener("touchcancel", touchFunction);

    function touchFunction(e) {
      touch_test_result.innerHTML += "e.type: " + e.type +
      "e.targetTouches: " + e.targetTouches +
      "e.targetTouches.length: " + e.targetTouches.length +
      "e.changedTouches: " + e.changedTouches +
      "e.changedTouches.length: " + e.changedTouches.length +
      "e.touches: " + e.touches +
      "e.touches.length: " + e.touches.length +
      "e.touches[0].target.tagName: " + e.touches[0].target.tagName +
      "-------------------------------------";
    }

    // since calling preventDefault() on a touchstart
    // or the first touchmove event of a series
    // prevents the corresponding mouse events from firing
    // it's common to call preventDefault() on touchmove rather than touchstart
    // that way, mouse events can still fire and things like links will continue to work
    // alternatively, some frameworks have taken to refiring touch events as mouse events
    // this example is oversimplified and may result in strange behavior
    function onTouch(evt) {
      evt.preventDefault();
      if (evt.touches.length > 1 || (evt.type == "touchend" && evt.touches.length > 0))
        return;
      var newEvt = document.createEvent("MouseEvents");
      var type = null;
      var touch = null;
      switch (evt.type) {
        case "touchstart":
          type = "mousedown";
          touch = evt.changedTouches[0];
          break;
        case "touchmove":
          type = "mousemove";
          touch = evt.changedTouches[0];
          break;
        case "touchend":
          type = "mouseup";
          touch = evt.changedTouches[0];
          break;
      }
      newEvt.initMouseEvent(type, true, true, evt.originalTarget.ownerDocument.defaultView, 0,
        touch.screenX, touch.screenY, touch.clientX, touch.clientY,
        evt.ctrlKey, evt.altKey, evt.shiftKey, evt.metaKey, 0, null);
      evt.originalTarget.dispatchEvent(newEvt);
    }
  

draw on canvas with touch

Your browser does not support canvas element

    function startup() {
      cnv = document.getElementById("touch_canvas");
      ctx = cnv.getContext("2d");
      cnv.addEventListener("touchstart", handleStart, false);
      cnv.addEventListener("touchend", handleEnd, false);
      cnv.addEventListener("touchcancel", handleCancel, false);
      cnv.addEventListener("touchmove", handleMove, false);
      log("initialized.");
    }
    function log(msg) {
      var p = document.getElementById('touch_canvas_log');
      p.innerHTML = msg + " - " + p.innerHTML;
    }
    var ongoingTouches = [],
        cnv = null,
        ctx = null;
    function handleStart(evt) {
      // keep the browser from continuing to process the touch event
      // (this also prevents a mouse event from also being delivered)
      evt.preventDefault();
      log("touchstart.");
      var touches = evt.changedTouches;
      for (var i = 0; i < touches.length; i++) {
        log("touchstart:" + i + "...");
        ongoingTouches.push(copyTouch(touches[i]));
        var color = colorForTouch(touches[i]);
        // 4-pixel wide line
        ctx.beginPath();
        ctx.arc(touches[i].clientX, touches[i].clientY, 4, 0, 2 * Math.PI, false);
        ctx.fillStyle = color;
        ctx.fill();
        log("touchstart:" + i + ".");
      }
    }
    function handleMove(evt) {
      evt.preventDefault();
      var touches = evt.changedTouches;
      // console.log(touches)
      for (var i = 0; i < touches.length; i++) {
        var color = colorForTouch(touches[i]);
        var idx = ongoingTouchIndexById(touches[i].identifier);
        if (idx >= 0) {
          log("continuing touch "+idx);
          ctx.beginPath();
          log("ctx.moveTo(" + ongoingTouches[idx].clientX + ", " + ongoingTouches[idx].clientY + ");");
          ctx.moveTo(ongoingTouches[idx].clientX, ongoingTouches[idx].clientY);
          log("ctx.lineTo(" + touches[i].clientX + ", " + touches[i].clientY + ");");
          ctx.lineTo(touches[i].clientX, touches[i].clientY);
          ctx.lineWidth = 4;
          ctx.strokeStyle = color;
          ctx.stroke();
          ongoingTouches.splice(idx, 1, copyTouch(touches[i]));  // swap in the new touch record
          log(".");
        } else {
          log("can't figure out which touch to continue");
        }
      }
    }
    function handleEnd(evt) {
      evt.preventDefault();
      log("touchend");
      var touches = evt.changedTouches;
      for (var i = 0; i < touches.length; i++) {
        var color = colorForTouch(touches[i]);
        var idx = ongoingTouchIndexById(touches[i].identifier);
        if (idx >= 0) {
          ctx.lineWidth = 4;
          ctx.fillStyle = color;
          ctx.beginPath();
          ctx.moveTo(ongoingTouches[idx].clientX, ongoingTouches[idx].clientY);
          ctx.lineTo(touches[i].clientX, touches[i].clientY);
          ctx.fillRect(touches[i].clientX - 4, touches[i].clientY - 4, 8, 8);  // and a square at the end
          ongoingTouches.splice(idx, 1);  // remove touch, we're done
        } else {
          log("can't figure out which touch to end");
        }
      }
    }
    function handleCancel(evt) {
      evt.preventDefault();
      log("touchcancel.");
      var touches = evt.changedTouches;
      for (var i = 0; i < touches.length; i++) {
        var idx = ongoingTouchIndexById(touches[i].identifier);
        ongoingTouches.splice(idx, 1);  // remove it; we're done
      }
    }
    function colorForTouch(touch) {
      var r = touch.identifier % 16;
      var g = Math.floor(touch.identifier / 3) % 16;
      var b = Math.floor(touch.identifier / 7) % 16;
      r = r.toString(16); // make it a hex digit
      g = g.toString(16); // make it a hex digit
      b = b.toString(16); // make it a hex digit
      var color = "#" + r + g + b;
      log("color for touch with identifier " + touch.identifier + " = " + color);
      return color;
    }
    // some browsers (mobile Safari, for one) re-use touch objects between events
    // so it's best to copy the bits you care about, rather than referencing the entire object
    function copyTouch(touch) {
      return { identifier: touch.identifier, clientX: touch.clientX, clientY: touch.clientY };
    }
    // scans through the ongoingTouches array to find the touch matching the given identifier
    // then returns that touch's index into the array
    function ongoingTouchIndexById(idToFind) {
      for (var i = 0; i < ongoingTouches.length; i++) {
        var id = ongoingTouches[i].identifier;
        if (id == idToFind) { return i; }
      }
      return -1;    // not found
    }
  

PointerEvent

various device button states
Device Button State button buttons
Mouse move with no buttons pressed -1 0
Left Mouse, Touch Contact, Pen contact (with no modifier buttons pressed) 0 1
Middle Mouse 1 4
Right Mouse, Pen contact with barrel button pressed 2 2
X1 (back) Mouse 3 8
X2 (forward) Mouse 4 16
Pen contact with eraser button pressed 5 32
Interact for changes

    var pointer_test = document.getElementById("pointer_test");
    var pointer_test_inner = document.getElementById("pointer_test_inner");
    var pointer_test_result = document.getElementById("pointer_test_result");

    pointer_test.addEventListener("pointerenter", pointerFunction);
    pointer_test.addEventListener("pointerover", pointerFunction);
    pointer_test.addEventListener("pointermove", pointerFunction);
    pointer_test.addEventListener("pointerdown", pointerFunction);
    pointer_test.addEventListener("pointerup", pointerFunction);
    pointer_test.addEventListener("pointercancel", pointerFunction);
    pointer_test.addEventListener("pointerout", pointerFunction);
    pointer_test.addEventListener("pointerleave", pointerFunction);
    pointer_test.addEventListener("gotpointercapture", pointerFunction);
    pointer_test.addEventListener("lostpointercapture", pointerFunction);

    var prev_action = "";
    function pointerFunction(e) {
      if (prev_action != e.type) {
        pointer_test_inner.innerHTML = e.type + pointer_test_inner.innerHTML;
        if(e.type == "pointerleave" || e.type == "pointerout") {
          pointer_test_inner.innerHTML = "------------" + pointer_test_inner.innerHTML;
        }
      }
      pointer_test_result.innerHTML = "e.type: " + e.type +
      "e.target.id: " + e.target.id +
      "e.pointerType: " + (e.pointerType?e.pointerType:"unsuported") +
      "e.pointerId: " + e.pointerId +
      "e.isPrimary: " + e.isPrimary +
      "e.width: " + e.width +
      "e.height: " + e.height +
      "e.width*e.height: " + (e.width*e.height) +
      "e.pressure: " + e.pressure +
      "e.tiltX: " + e.tiltX +
      "e.tiltY: " + e.tiltY +
      "e.screenX: " + e.screenX +
      "e.screenY: " + e.screenY +
      "e.clientX: " + e.clientX +
      "e.clientY: " + e.clientY +
      "e.pageX: " + e.pageX +
      "e.pageY: " + e.pageY +
      "e.offsetX: " + e.offsetX +
      "e.offsetY: " + e.offsetY +
      "e.movementX: " + e.movementX +
      "e.movementY: " + e.movementY +
      "e.altKey: " + e.altKey +
      "e.ctrlKey: " + e.ctrlKey +
      "e.shiftKey: " + e.shiftKey +
      "e.metaKey: " + e.metaKey +
      "e.getModifierState('Shift'): " + e.getModifierState('Shift') +
      "e.button: " + e.button +
      "e.buttons: " + e.buttons +
      "e.region: " + e.region +
      "e.relatedTarget: " + e.relatedTarget +
      "e.which: " + e.which;
      var x = e.which || e.keyCode;
      if (x == 27) {  // 27 is the ESC key
          alert ("You pressed the Escape key!");
      }
      prev_action = e.type;
    }

    function downHandler(ev) {
      var el=document.getElementById("target");
      // Element "target" will receive/capture further events
      el.setPointerCapture(ev.pointerId);
    }
    function cancelHandler(ev) {
      var el=document.getElementById("target");
      // Release the pointer capture
      el.releasePointerCapture(ev.pointerId);
    }
    function init() {
      var el=document.getElementById("target");
      // Register pointerdown and pointercancel handlers
      el.onpointerdown = downHandler;
      el.onpointercancel = cancelHandler;
    }
  

InputEvent


    var input_test = document.getElementById("input_test");
    var input_test_result = document.getElementById("input_test_result");
    input_test.addEventListener("input", inputFunction);
    function inputFunction(event) {
      input_test_result.innerHTML += "event.data: " + event.data +
      "event.dataTransfer: " + event.dataTransfer +
      // "event.getTargetRanges: " + event.getTargetRanges().toString() +
      "event.inputType: " + event.inputType +
      "-----------------------------------------------";
    }
  

FocusEvent




    var focus_test = document.getElementById("focus_test_2");
    var focus_test_result = document.getElementById("focus_test_result");

    focus_test.addEventListener("focusin", focusFunction);
    focus_test.addEventListener("focus", focusFunction, true);
    focus_test.addEventListener("focusout", focusFunction);
    focus_test.addEventListener("blur", focusFunction, true);

    function focusFunction(event) {
      focus_test_result.innerHTML += event.type+
        "relatedTarget: " + event.relatedTarget.id +
        "target: " +  event.target.id;
    }
  

ClipboardEvent


    <input
      id="clipboard_test"
      type="text"
      value="Copy, Cut, Paste values"
    />
     <span id="clipboard_test_result"></span>
  

    var clipboard_test = document.getElementById("clipboard_test");
    clipboard_test.addEventListener("copy", clipboardFunction);
    clipboard_test.addEventListener("cut", clipboardFunction);
    clipboard_test.addEventListener("paste", clipboardFunction);
    var clipboard_test_result = document.getElementById("clipboard_test_result");
    function clipboardFunction(event) {
        clipboard_test_result.innerHTML = event.type;
    }
  

DragEvent

Dragable_1
Dragable_2
Dragable_3
  
    <div
      class="droptarget"
      ondragenter="dragEnter(event)"
      ondragover="dragOver(event)"
      ondragleave="dragLeave(event)"
      ondrop="drop(event)"
      style="width:200px;height:300px"
    >
        <div
          id="dragtarget_1"
          class="dragtarget"
          draggable="true"
          ondragstart="dragStart(event)"
          ondrag="dragging(event)"
          ondragend="dragEnd(event)"
        >Dragable_1</div>

        // dragable elements ?

    </div>

    <div
      class="droptarget"
      ondragenter="dragEnter(event)"
      ondragover="dragOver(event)"
      ondragleave="dragLeave(event)"
      ondrop="drop(event)"
      style="width:200px;height:300px"
    >

        // dragable elements ?

    </div>
  

    var drag_info = document.getElementById("drag_info");
    var drag_info_2 = document.getElementById("drag_info_2");

    // dragable element
    function dragStart(event) {
        // set data type and the value of the dragged data
        event.dataTransfer.setData("Text", event.target.id);
        drag_info_2.innerHTML = event.target.id+" drag started";
        event.target.style.opacity = "0.4";
    }
    function dragging(event) {
        //drag_info.innerHTML += "element is dragged";
        drag_info_2.innerHTML = event.target.id+" element is draged";
        drag_info_2.style.color = "red";
    }
    function dragEnd(event) {
        drag_info.innerHTML += event.target.id+" drag ended";
        drag_info_2.innerHTML = event.target.id+" drag ended";
        event.target.style.opacity = "1";
    }

    // containers
    function dragEnter(event) {
        drag_info.innerHTML += event.target.id+" container entrance";
        drag_info_2.innerHTML = event.target.id+" container entrance";
        if ( event.target.className == "droptarget" ) {
          event.target.style.border = "3px dotted red";
        }
    }
    function dragOver(event) {
        // allow drop inside element (disabled by default for data/elements)
        event.preventDefault();
        //drag_info.innerHTML += "element dragging is over";
        drag_info_2.innerHTML = event.target.id+" is under draggable";
    }
    function dragLeave(event) {
        event.preventDefault();
        drag_info.innerHTML += event.target.id+" out of container";
        drag_info_2.innerHTML = event.target.id+" out of container";
        if ( event.target.className == "droptarget" ) {
          event.target.style.border = "";
        }
    }
    function drop(event) {
      // prevent default handling (open as link on drop) of the data
      event.preventDefault();
      drag_info.innerHTML += event.target.id+" received dragable";
      drag_info_2.innerHTML = event.target.id+" received dragable";
      if ( event.target.className == "droptarget" ) {
        drag_info_2.style.color = "";
        event.target.style.border = "";
        // get dragged data with the dataTransfer.getData() method
        var data = event.dataTransfer.getData("Text");
        // append the dragged element into the drop element
        event.target.appendChild(document.getElementById(data));
      }
    }
  

ProgressEvent

TransitionEvent

Hover for changes

    #transition_test {
      width: 100px;
      height: 100px;
      background: red;
      padding:1em;
      -webkit-transition: all 1s; /* For Safari 3.1 to 6.0 */
      transition: all 1s;
    }
    #transition_test:hover {
      width: 400px;
      opacity: 0.3;
    }
  

    var transition_test = document.getElementById("transition_test");

    transition_test.addEventListener("transitionend", transitionFunction);
    transition_test.addEventListener("webkitTransitionEnd", transitionFunction);
    //transition_test.addEventListener("mozTransitionEnd", touchFunction);
    //transition_test.addEventListener("oTransitionEnd", touchFunction);

    transition_test.addEventListener("transitioncancel", transitionFunction);

    function transitionFunction(e) {
      transition_test.innerHTML = "e.type: " + e.type +
      "e.propertyName: " + e.propertyName +
      "e.elapsedTime: " + e.elapsedTime +
      "e.pseudoElement: " + e.pseudoElement;
    }
  

AnimationEvent

Click to start

    #animation_test_div {
      width: 250px;
      height: 100px;
      background: orange;
      position: relative;
      font-size: 20px;
      padding:1em;
    }

    /* Chrome, Safari, Opera */
    @-webkit-keyframes my_test_div_move {
      from {left: 0px;}
      to {left: 200px;}
    }

    @keyframes my_test_div_move {
      from {left: 0px;}
      to {left: 200px;}
    }
  

    var animation_test_div = document.getElementById("animation_test_div");

    // Start the animation with JavaScript
    function test_div_move() {
      // Chrome, Safari and Opera
      animation_test_div.style.WebkitAnimation = "my_test_div_move 4s 5";
      // standard syntax
      animation_test_div.style.animation = "my_test_div_move 4s 5";
    }

    // Code for Chrome, Safari and Opera
    animation_test_div.addEventListener("webkitAnimationStart", myStartFunction);
    animation_test_div.addEventListener("webkitAnimationIteration", myRepeatFunction);
    animation_test_div.addEventListener("webkitAnimationEnd", myEndFunction);
    // Standard syntax
    animation_test_div.addEventListener("animationstart", myStartFunction);
    animation_test_div.addEventListener("animationiteration", myRepeatFunction);
    animation_test_div.addEventListener("animationend", myEndFunction);

    function myStartFunction() {
      this.innerHTML = "animationstart";
      this.style.backgroundColor = "pink";
    }
    var iteration = 1;
    function myRepeatFunction(event) {
      iteration++;
      this.innerHTML = "animationiteration: " + iteration +
        ", elapsed: " + event.elapsedTime + " seconds";
      this.style.backgroundColor = "lightblue";
    }
    function myEndFunction() {
      this.innerHTML = "animationend";
      this.style.backgroundColor = "lightgray";
    }
  

HashChangeEvent

change page hash (# anchor in tab link, or select other links in this chapter) to see changes

    <body onhashchange="hashFunction(event)">
  

    // window.onhashchange = hashFunction;
    function hashFunction(event) {
      document.getElementById("hash_test_result").innerHTML += "location.hash: " + location.hash +
      "event.newURL: " + event.newURL +
      "event.oldURL: " + event.oldURL +
      "-----------------------------------------------";
    }
  

PageTransitionEvent


    window.addEventListener("pageshow", transitionFunction);
    window.addEventListener("pagehide", transitionFunction);
    function transitionFunction(e) {
      console.log(["e.type: "+e.type,"e.persisted: "+e.persisted]);
    }
  

PopStateEvent


    window.onpopstate = function(event) {
      console.log([
        "location: " + document.location,
        "state: " + JSON.stringify(event.state)
      ]);
    };

    history.pushState({page: 1}, "keyboard", "#keyboard");
    history.pushState({page: 2}, "mouse", "#mouse");
    history.replaceState({page: 3}, "pop", "#pop");
    history.back();
    history.back();
    history.go(2);
  

StorageEvent

Click to Change a Storage Item

    var storage_test_result = document.getElementById("storage_test_result");
    window.addEventListener("storage", storageFunction);
    function storageFunction(e) {
      storage_test_result.innerHTML = "e.key: " + e.key +
      "e.newValue: " + e.newValue +
      "e.oldValue: " + e.oldValue +
      "e.storageArea: " + e.storageArea +
      "e.url: " + e.url;
    }
    function storageValue() {
      var w = window.open("", "myWindow", "width=200,height=100");
      if (w.localStorage.clickcount) {
        w.localStorage.clickcount = Number(w.localStorage.clickcount)+1;
      } else {
        w.localStorage.clickcount = 1;
      }
      w.close();
    }
  

Custom events


    var event = new Event('build');
    // Listen for the event.
    elem.addEventListener('build', function (e) { /* ... */ }, false);
    // Dispatch the event.
    elem.dispatchEvent(event);

    // Adding custom data – CustomEvent()
    var event = new CustomEvent(
      'build',
      { detail: elem.dataset.time }
    );
    function eventHandler(e) {
      console.log('The time is: ' + e.detail);
    }

    // The old-fashioned way
    // Create the event.
    var event = document.createEvent('Event');
    // Define that the event name is 'build'.
    event.initEvent('build', true, true);
    // Listen for the event.
    elem.addEventListener('build', function (e) {
      // e.target matches elem
    }, false);
    // target can be any Element or other EventTarget.
    elem.dispatchEvent(event);

    // Event bubbling
    <form>
    <textarea></textarea>
    <//form>
    // -----
    const form = document.querySelector('form');
    const textarea = document.querySelector('textarea');
    // Create a new event, allow bubbling
    // and provide any data you want to pass to the "details" property
    const eventAwesome = new CustomEvent('awesome', {
      bubbles: true,
      detail: { text: () => textarea.value }
    });
    // The form element listens for the custom "awesome" event
    // and then consoles the output of the passed text() method
    form.addEventListener('awesome', e => console.log(e.detail.text()));
    // As the user types, textarea inside the form dispatches/triggers the event to fire
    // and uses itself as the starting point
    textarea.addEventListener('input', e => e.target.dispatchEvent(eventAwesome));
  

Selection/Range


    var selection_tr = document.getElementById("selection_tests_result");
    var sel = window.getSelection();
    function selectionFunction () {
      selection_tr.innerHTML =
        "sel.toString(): " + sel.toString() +
        "sel.anchorNode.tagName: " + sel.anchorNode +
        "sel.anchorOffset: " + sel.anchorOffset +
        "sel.focusNode.tagName: " + sel.focusNode +
        "sel.focusOffset: " + sel.focusOffset +
        "sel.isCollapsed: " + sel.isCollapsed +
        "sel.type: " + sel.type +
        "sel.rangeCount: " + sel.rangeCount;

        for (var i=0; i<sel.rangeCount; i++) {
          var rng = sel.getRangeAt(i);
          selection_tr.innerHTML += "---------------------" +
            "rng["+i+"].toString(): " + rng.toString() +
            "rng["+i+"].collapsed: " + rng.collapsed +
            "rng["+i+"].startContainer: " + rng.startContainer +
            " - rng["+i+"].endContainer: " + rng.endContainer +
            "rng["+i+"].startOffset: " + rng.startOffset +
            " - rng["+i+"].endOffset: " + rng.endOffset +
            "rng["+i+"].cloneContents(): " + rng.cloneContents() +
            "rng["+i+"].cloneRange(): " + rng.cloneRange() +
            "rng["+i+"].getBoundingClientRect(): " + rng.getBoundingClientRect() +
            "rng["+i+"].getClientRects(): " + rng.getClientRects() +
            "rng["+i+"].commonAncestorContainer: " + rng.commonAncestorContainer;
        }

    }
    setInterval(selectionFunction, 500);
  

Debounce/Throttle

mousemove/touchmove over this area

    var helpers = {
      // debouncing, executes the function if there was no new event in $wait milliseconds
      debounce: function (func, wait, scope) {
        var timeout;
        return function () {
          var context = scope || this, args = arguments;
          var later = function () {
            timeout = null;
            func.apply(context, args);
          };
          clearTimeout(timeout);
          timeout = setTimeout(later, wait);
        };
      },
      // in case of a "storm of events", this executes once every $threshold
      throttle: function (fn, threshhold, scope) {
        threshhold || (threshhold = 250);
        var last,
            deferTimer;
        return function () {
          var context = scope || this;
          var now = +new Date,
            args = arguments;
          if (last && now < last + threshhold) {
            // hold on to it
            clearTimeout(deferTimer);
            deferTimer = setTimeout(function () {
              last = now;
              fn.apply(context, args);
            }, threshhold);
          } else {
            last = now;
            fn.apply(context, args);
          }
        };
      }
    }

    function NIM_demo(){
      this.canvas =   document.getElementById("paintonme");
      this.context =  this.canvas.getContext("2d");
      this.movearea = document.getElementById("moveonme");
      this.canvasTimeScale = 5 * 1000;
      this.paintColors = ["#bbd","#464","#d88"];
      this.totalLanes =  this.paintColors.length;
      this.leftMargin = 100;
      var self = this;
      this.init = function(){
        this.canvas.width = window.innerWidth - 250;
        this.flush();
        this.movearea.addEventListener(
          "mousemove",this.regularHandler);
        this.movearea.addEventListener(
          "mousemove",helpers.debounce(self.debounceHandler,100,this));
        this.movearea.addEventListener(
          "mousemove",helpers.throttle(self.throttleHander,100,this));

        this.movearea.addEventListener(
          "touchmove",this.regularHandler);
        this.movearea.addEventListener(
          "touchmove",helpers.debounce(self.debounceHandler,100,this));
        this.movearea.addEventListener(
          "touchmove",helpers.throttle(self.throttleHander,100,this));
      }
      // painting the rectangle / line
      this.paintRect = function(lane,time){
        if(time > this.canvasTimeScale){
          this.startTime += time;
          time = 0;
          this.flush()
        }
        // console.log(lane,time);
        this.context.fillStyle = this.paintColors[lane];
        var x = (this.canvas.width - this.leftMargin) / this.canvasTimeScale * time + this.leftMargin;
        var y = this.canvas.height / this.totalLanes * lane;
        var height = this.canvas.height / this.totalLanes;
        var width = 1;
        this.context.fillRect(x,y,width,height);
      }
      this.flush = function(){
        this.context.fillStyle = "#ffffff";
        this.context.fillRect(0,0,this.canvas.width,this.canvas.height);
        this.context.font = "200 18px Roboto,Helvetica,Arial";
        this.context.fillStyle = this.paintColors[0];
        this.context.fillText("Regular", 0, 30);
        this.context.fillStyle = this.paintColors[1];
        this.context.fillText("debounce", 0, 80);
        this.context.fillStyle = this.paintColors[2];
        this.context.fillText("throttle", 0, 130);
      }
      // get the time difference
      this.getTimeDiff = function(){
        var time = new Date().getTime();
        if(!this.startTime){ this.startTime = time; }
        time -= this.startTime;
        return time;
      }
      this.regularHandler = function(){
        self.paintRect(0,self.getTimeDiff());
      }
      this.debounceHandler = function(){
        self.paintRect(1,self.getTimeDiff());
      }
      this.throttleHander = function(){
        self.paintRect(2,self.getTimeDiff());
      }
    }

    var demo = new NIM_demo();
    demo.init();
  

Web Components

composed and composedPath, access to shadow root with JS


    <open-shadow text="I have an open shadow root, "></open-shadow>
    <closed-shadow text="I have a closed shadow root"></closed-shadow>
  

    customElements.define('open-shadow',
      class extends HTMLElement {
        constructor() {
          super();
          const pElem = document.createElement('p');
          pElem.textContent = this.getAttribute('text');
          const shadowRoot = this.attachShadow({mode: 'open'});
          shadowRoot.appendChild(pElem);
    }});
    customElements.define('closed-shadow',
      class extends HTMLElement {
        constructor() {
          super();
          const pElem = document.createElement('p');
          pElem.textContent = this.getAttribute('text');
          const shadowRoot = this.attachShadow({mode: 'closed'});
          shadowRoot.appendChild(pElem);
    }});
    document.querySelector('#webc_result').addEventListener('click', e => {
      // console.log(e.composed);
      // console.log(e.composedPath());
    });
  

:defined pseudo-class

Standard paragraph example text


    simple-custom {
      background: cyan;
    }
    :defined {
      font-style: italic;
    }
    simple-custom:not(:defined) {
      display: none;
    }
    simple-custom:defined {
      display: block;
    }
  

    <simple-custom text="Custom element example text"></simple-custom>
    <p>Standard paragraph example text</p>
  

    customElements.define('simple-custom',
      class extends HTMLElement {
        constructor() {
          super();
          const divElem = document.createElement('div');
          divElem.textContent = this.getAttribute('text');
          const shadowRoot = this.attachShadow({mode: 'open'});
          shadowRoot.appendChild(divElem);
    }});
  

editable word

Morgan Stanley

36 Accountant

My name is Chris, the man said.


    <template id="person-template">
      <div>
        <h2>Personal ID Card</h2>
        <slot name="person-name">NAME MISSING</slot>
        <ul>
          <li><slot name="person-age">AGE MISSING</slot></li>
          <li><slot name="person-occupation">OCCUPATION MISSING</slot></li>
        </ul>
      </div>
    </template>
    <person-details>
      <p slot="person-name"><edit-word>Morgan</edit-word> Stanley</p>
      <span slot="person-age">36</span>
      <span slot="person-occupation">Accountant</span>
    </person-details>
    <p>My name is <edit-word>Chris</edit-word>, the man said.</p>
  

    customElements.define('person-details',
      class extends HTMLElement {
        constructor() {
          super();
          const template = document.getElementById('person-template');
          const templateContent = template.content;
          const shadowRoot = this.attachShadow({mode: 'open'});
          const style = document.createElement('style');
          style.textContent = `
            div { padding: 10px; border: 1px solid gray; width: 200px; margin: 10px; }
            h2 { margin: 0 0 10px; }
            ul { margin: 0; }
            p { margin: 10px 0; }
          `;
          shadowRoot.appendChild(style);
          shadowRoot.appendChild(templateContent.cloneNode(true));
    }});
    customElements.define('edit-word',
      class extends HTMLElement {
        constructor() {
          super();
          const shadowRoot = this.attachShadow({mode: 'open'});
          const form = document.createElement('form');
          const input = document.createElement('input');
          const span = document.createElement('span');
          const style = document.createElement('style');
          style.textContent = 'span { background-color: #eef; padding: 0 2px }';
          shadowRoot.appendChild(style);
          shadowRoot.appendChild(form);
          shadowRoot.appendChild(span);
          span.textContent = this.textContent;
          input.value = this.textContent;
          form.appendChild(input);
          form.style.display = 'none';
          span.style.display = 'inline-block';
          input.style.width = span.clientWidth + 'px';
          this.setAttribute('tabindex', '0');
          input.setAttribute('required', 'required');
          this.style.display = 'inline-block';
          this.addEventListener('click', () => {
            span.style.display = 'none';
            form.style.display = 'inline-block';
            input.focus();
            input.setSelectionRange(0, input.value.length)
          });
          form.addEventListener('submit', e => {
            updateDisplay();
            e.preventDefault();
          });
          input.addEventListener('blur', updateDisplay);
          function updateDisplay() {
            span.style.display = 'inline-block';
            form.style.display = 'none';
            span.textContent = input.value;
            input.style.width = span.clientWidth + 'px';
    }}});
  

editable list


    <editable-list
      title="TODO"
      list-item-0="First item on the list"
      list-item-1="Second item on the list"
      list-item-2="Third item on the list"
      list-item-3="Fourth item on the list"
      list-item-4="Fifth item on the list"
      listItem="This will not appear"
      add-item-text="Add new list item:"
    >
    </editable-list>
  

    (function() {
      class EditableList extends HTMLElement {
        constructor() {
          // establish prototype chain
          super();
          // attaches shadow tree and returns shadow root reference
          // https://developer.mozilla.org/en-US/docs/Web/API/Element/attachShadow
          const shadow = this.attachShadow({ mode: 'open' });
          // creating a container for the editable-list component
          const editableListContainer = document.createElement('div');
          // get attribute values from getters
          const title = this.title;
          const addItemText = this.addItemText;
          const listItems = this.items;
          // adding a class to our container for the sake of clarity
          editableListContainer.classList.add('editable-list');
          // creating the inner HTML of the editable list element
          editableListContainer.innerHTML = `
            <style>
              li, div > div {
                display: flex;
                align-items: center;
                justify-content: space-between;
              }
              .icon {
                background-color: #fff;
                border: none;
                cursor: pointer;
                float: right;
                font-size: 1.8rem;
              }
            </style>
            <h3>${title}</h3>
            <ul class="item-list">
              ${listItems.map(item => `
                <li>${item}
                  <button class="editable-list-remove-item icon">⊖</button>
                </li>
              `).join('')}
            </ul>
            <div>
              <label>${addItemText}</label>
              <input class="add-new-list-item-input" type="text"></input>
              <button class="editable-list-add-item icon">⊕</button>
            </div>
          `;
          // binding methods
          this.addListItem = this.addListItem.bind(this);
          this.handleRemoveItemListeners = this.handleRemoveItemListeners.bind(this);
          this.removeListItem = this.removeListItem.bind(this);
          // appending the container to the shadow DOM
          shadow.appendChild(editableListContainer);
        }
        // add items to the list
        addListItem(e) {
          const textInput = this.shadowRoot.querySelector('.add-new-list-item-input');
          if (textInput.value) {
            const li = document.createElement('li');
            const button = document.createElement('button');
            const childrenLength = this.itemList.children.length;
            li.textContent = textInput.value;
            button.classList.add('editable-list-remove-item', 'icon');
            button.innerHTML = '⊖';
            this.itemList.appendChild(li);
            this.itemList.children[childrenLength].appendChild(button);
            this.handleRemoveItemListeners([...this.itemList.children]);
            textInput.value = '';
          }
        }
        // fires after the element has been attached to the DOM
        connectedCallback() {
          const removeElementButtons = [...this.shadowRoot.querySelectorAll('.editable-list-remove-item')];
          const addElementButton = this.shadowRoot.querySelector('.editable-list-add-item');

          this.itemList = this.shadowRoot.querySelector('.item-list');

          this.handleRemoveItemListeners(removeElementButtons);
          addElementButton.addEventListener('click', this.addListItem, false);
        }
        // gathering data from element attributes
        get title() {
          return this.getAttribute('title') || '';
        }
        get items() {
          const items = [];
          [...this.attributes].forEach(attr =< {
            if (attr.name.includes('list-item')) {
              items.push(attr.value);
            }
          });
          return items;
        }
        get addItemText() {
          return this.getAttribute('add-item-text') || '';
        }
        handleRemoveItemListeners(arrayOfElements) {
          arrayOfElements.forEach(element =< {
            element.addEventListener('click', this.removeListItem, false);
          });
        }
        removeListItem(e) {
          e.target.parentNode.remove();
        }
      }
      // let the browser know about the custom element
      customElements.define('editable-list', EditableList);
    })();
  

element-details - web component using template and slot

slot A placeholder inside a web component that users can fill with their own markup, with the effect of composing different DOM trees together.
name
The name of the slot.
template A mechanism for holding client- side content that is not to be rendered when a page is loaded but may subsequently be instantiated during runtime using JavaScript.

    dl { margin-left: 6px; }
    dt { font-weight: bold; color: #217ac0; font-size: 110% }
    dt { font-family: Consolas, "Liberation Mono", Courier }
    dd { margin-left: 16px }
  

    <template id="element-details-template">
      <style>
      details {font-family: "Open Sans Light",Helvetica,Arial}
      .name {font-weight: bold; color: #217ac0; font-size: 120%}
      h4 { margin: 10px 0 -8px 0; }
      h4 span { background: #217ac0; padding: 2px 6px 2px 6px }
      h4 span { border: 1px solid #cee9f9; border-radius: 4px }
      h4 span { color: white }
      .attributes { margin-left: 22px; font-size: 90% }
      .attributes p { margin-left: 16px; font-style: italic }
      </style>
      <details>
        <summary>
          <span>
            <code class="name"><<slot name="element-name">NEED NAME</slot>></code>
            <i class="desc"><slot name="description">NEED DESCRIPTION</slot></i>
          </span>
        </summary>
        <div class="attributes">
          <h4><span>Attributes</span></h4>
          <slot name="attributes"><p>None</p></slot>
        </div>
      </details>
      <hr>
    </template>
    <element-details>
      <span slot="element-name">slot</span>
      <span slot="description">A placeholder inside a web
        component that users can fill with their own markup,
        with the effect of composing different DOM trees
        together.</span>
      <dl slot="attributes">
        <dt>name</dt>
        <dd>The name of the slot.</dd>
      </dl>
    </element-details>
    <element-details>
      <span slot="element-name">template</span>
      <span slot="description">A mechanism for holding client-
        side content that is not to be rendered when a page is
        loaded but may subsequently be instantiated during
        runtime using JavaScript.</span>
    </element-details>
  

    customElements.define('element-details',
      class extends HTMLElement {
        constructor() {
          super();
          const template = document
            .getElementById('element-details-template')
            .content;
          const shadowRoot = this.attachShadow({mode: 'open'})
            .appendChild(template.cloneNode(true));
    }});
  

expanding list (click to open indention)


    ul {
      list-style-type: none;
    }
    li::before {
      display:inline-block;
      width: 1rem;
      height: 1rem;
      margin-right: 0.25rem;
      content:"";
    }
    .open::before, .closed::before {
      background-size: 1rem 1rem;
      position: relative;
      top: 0.25rem;
      opacity: 0.3;
    }
    .open::before {
      background-image: url(images/down.png);
    }
    .closed::before {
      background-image: url(images/right.png);
    }
    .closed .closed::before, .closed .open::before {
      display: none;
    }
  

    <ul is="expanding-list">
      <li>UK
      <ul>
        <li>Yorkshire
        <ul>
          <li>Leeds
          <ul>
            <li>Train station</li>
            <li>Town hall</li>
            <li>Headrow</li>
          </ul>
          </li>
          <li>Bradford</li>
          <li>Hull</li>
        </ul>
        </li>
      </ul>
      </li>
      <li>USA
      <ul>
        <li>California
        <ul>
          <li>Los Angeles</li>
          <li>San Francisco</li>
          <li>Berkeley</li>
        </ul>
        </li>
        <li>Nevada</li>
        <li>Oregon</li>
      </ul>
      </li>
    </ul>
  

    // Create a class for the element
    class ExpandingList extends HTMLUListElement {
      constructor() {
        // Always call super first in constructor
        super();
        window.onload = function() {
          const uls = Array.from(document.querySelectorAll(':root ul'));
          const lis = Array.from(document.querySelectorAll(':root li'));
          uls.slice(1).forEach(ul => {
            ul.style.display = 'none';
          });
          lis.forEach(li => {
            const childText = li.childNodes[0];
            const newSpan = document.createElement('span');
            newSpan.textContent = childText.textContent;
            childText.parentNode.insertBefore(newSpan, childText);
            childText.parentNode.removeChild(childText);
          });
          const spans = Array.from(document.querySelectorAll(':root span'));
          spans.forEach(span => {
            if (span.nextElementSibling) {
              span.style.cursor = 'pointer';
              span.parentNode.setAttribute('class', 'closed');
              span.onclick = showul;
            }
          });
          function showul(e) {
            const nextul = e.target.nextElementSibling;
            if (nextul.style.display == 'block') {
              nextul.style.display = 'none';
              nextul.parentNode.setAttribute('class', 'closed');
            } else {
              nextul.style.display = 'block';
              nextul.parentNode.setAttribute('class', 'open');
            }
          }
        };
      }
    }
    // Define the new element
    customElements.define('expanding-list', ExpandingList, { extends: 'ul' });
  

Host selectors


    ...
    <body>
      <header>
        <h1>Host selectors <a href="#"><context-span>example</context-span></a></h1>
      </header>
      <main>
        <article>
          <h2>This is my first article.</h2>
          <p>This article is rather lovely and exciting — it is all about animals,
          including <a href="#"><context-span>Beavers</context-span></a>,
          <a href="#"><context-span>Bears</context-span></a>,
          and <a href="#"><context-span>Wolves</context-span></a>.
          I love animals and I'm sure you will too;
          please let us know what your favorite animals are. Woo hoo!</p>
        </article>
        <article>
          <h2>This is my second article.</h2>
          <p>This article is also quite exciting — it is all about colors,
          including <a href="#"><context-span>Red</context-span></a>,
          <a href="#"><context-span>Blue</context-span></a>,
          and <a href="#"><context-span>Pink</context-span></a>.
          A true joy indeed — funky exciting colors make the world go round.
          No more gray days for us.</p>
        </article>
        <aside>
          <h2>Some links about web components</h2>
          <ul>
            <li><a href="#"><context-span>Custom elements</context-span></a></li>
            <li><a href="#"><context-span>Shadow DOM</context-span></a></li>
            <li><a href="#"><context-span>Templates and slots</context-span></a></li>
          </ul>
        </aside>
      </main>
      <footer>
        <p>Copyright nobody; example written by
        <a href="#"><context-span class="footer">Chris Mills</context-span></a>
        </p>
      </footer>
    </body>
  

    class ContextSpan extends HTMLElement {
      constructor() {
        super();
        const style = document.createElement('style');
        const span = document.createElement('span');
        span.textContent = this.textContent;
        const shadowRoot = this.attachShadow({mode: 'open'});
        shadowRoot.appendChild(style);
        shadowRoot.appendChild(span);
        style.textContent = `
          span:hover { text-decoration: underline; }
          :host-context(h1) { font-style: italic; }
          :host-context(h1):after { content: " - no links in headers!" }
          :host-context(article, aside) { color: gray; }
          :host(.footer) { color : red; }
          :host { background: rgba(0,0,0,0.1); padding: 2px 5px; }
        `;
      }
    }
    // Define the new element
    customElements.define('context-span', ContextSpan);
  

Life cycle callbacks


    <div>
      <button class="add">Add custom-square to DOM</button>
      <button class="update">Update attributes</button>
      <button class="remove">Remove custom-square from DOM</button>
    </div>
  

    // Create a class for the element
    class Square extends HTMLElement {
      // Specify observed attributes so that
      // attributeChangedCallback will work
      static get observedAttributes() {
        return ['c', 'l'];
      }
      constructor() {
        // Always call super first in constructor
        super();
        const shadow = this.attachShadow({mode: 'open'});
        const div = document.createElement('div');
        const style = document.createElement('style');
        shadow.appendChild(style);
        shadow.appendChild(div);
      }
      connectedCallback() {
        console.log('Custom square element added to page.');
        updateStyle(this);
      }
      disconnectedCallback() {
        console.log('Custom square element removed from page.');
      }
      adoptedCallback() {
        console.log('Custom square element moved to new page.');
      }
      attributeChangedCallback(name, oldValue, newValue) {
        console.log('Custom square element attributes changed.');
        updateStyle(this);
      }
    }
    customElements.define('custom-square', Square);
    function updateStyle(elem) {
      const shadow = elem.shadowRoot;
      const childNodes = Array.from(shadow.childNodes);
      childNodes.forEach(childNode => {
        if (childNode.nodeName === 'STYLE') {
          childNode.textContent = `
            div {
              width: ${elem.getAttribute('l')}px;
              height: ${elem.getAttribute('l')}px;
              background-color: ${elem.getAttribute('c')};
            }
          `;
        }
      });
    }
    const add = document.querySelector('.add');
    const update = document.querySelector('.update');
    const remove = document.querySelector('.remove');
    let square;
    update.disabled = true;
    remove.disabled = true;
    function random(min, max) {
      return Math.floor(Math.random() * (max - min + 1) + min);
    }
    add.onclick = function() {
      // Create a custom square element
      square = document.createElement('custom-square');
      square.setAttribute('l', '100');
      square.setAttribute('c', 'red');
      document.getElementById("lcc").appendChild(square);
      update.disabled = false;
      remove.disabled = false;
      add.disabled = true;
    };
    update.onclick = function() {
      // Randomly update square's attributes
      square.setAttribute('l', random(50, 200));
      square.setAttribute('c', `rgb(${random(0, 255)}, ${random(0, 255)}, ${random(0, 255)})`);
    };
    remove.onclick = function() {
      // Remove the square
      document.getElementById("lcc").removeChild(square);
      update.disabled = true;
      remove.disabled = true;
      add.disabled = false;
    };
  

popup info box widget








    <form>
      <div>
        <label for="cvc">
        Enter your CVC
        <popup-info
          img="img/alt.png"
          data-text="Your card..."
        >
        </label>
        <input type="text" id="cvc">
      </div>
    </form>
  

    // Create a class for the element
    class PopUpInfo extends HTMLElement {
      constructor() {
        // Always call super first in constructor
        super();

        // Create a shadow root
        const shadow = this.attachShadow({mode: 'open'});

        // Create spans
        const wrapper = document.createElement('span');
        wrapper.setAttribute('class', 'wrapper');

        const icon = document.createElement('span');
        icon.setAttribute('class', 'icon');
        icon.setAttribute('tabindex', 0);

        const info = document.createElement('span');
        info.setAttribute('class', 'info');

        // Take attribute content and put it inside the info span
        const text = this.getAttribute('data-text');
        info.textContent = text;

        // Insert icon
        let imgUrl;
        if(this.hasAttribute('img')) {
          imgUrl = this.getAttribute('img');
        } else {
          imgUrl = 'img/default.png';
        }

        const img = document.createElement('img');
        img.src = imgUrl;
        icon.appendChild(img);

        // Create some CSS to apply to the shadow dom
        const style = document.createElement('style');
        console.log(style.isConnected);

        style.textContent = `
          .wrapper {
            position: relative;
          }
          .info {
            font-size: 0.8rem;
            width: 200px;
            display: inline-block;
            border: 1px solid black;
            padding: 10px;
            background: white;
            border-radius: 10px;
            opacity: 0;
            transition: 0.6s all;
            position: absolute;
            bottom: 20px;
            left: 10px;
            z-index: 3;
          }
          img {
            width: 1.2rem;
          }
          .icon:hover + .info, .icon:focus + .info {
            opacity: 1;
          }
        `;
        // Attach the created elements to the shadow dom
        shadow.appendChild(style);
        console.log(style.isConnected);
        shadow.appendChild(wrapper);
        wrapper.appendChild(icon);
        wrapper.appendChild(info);
      }
    }
    // Define the new element
    customElements.define('popup-info', PopUpInfo);
  

simple template

Let's have some different text!

    <template id="my-paragraph">
      <style>
        p {
          color: white;
          background-color: #666;
          padding: 5px;
        }
      </style>
      <p><slot name="my-text">My default text</slot></p>
    </template>
    <my-paragraph>
      <span slot="my-text">Let's have some different text!</span>
    </my-paragraph>
    <my-paragraph>
      <ul slot="my-text">
        <li>Let's have some different text!</li>
        <li>In a list!</li>
      </ul>
    </my-paragraph>
  

    customElements.define('my-paragraph',
      class extends HTMLElement {
        constructor() {
          super();
          const template = document.getElementById('my-paragraph');
          const templateContent = template.content;
          this.attachShadow({mode: 'open'}).appendChild(
            templateContent.cloneNode(true)
          );
        }
      }
    );
    const slottedSpan = document.querySelector('my-paragraph span');
    console.log(slottedSpan.assignedSlot);
    console.log(slottedSpan.slot);
  

slotchange event

A common, sweet, crun... or yellow in color.

A fairly common, sweet, u...it, usually softer than Apples.

A long, curved, yellow f... fairly gentle flavor.

Orange in color, usually s... sharp, often contains pips.

An orange fruit with big...le, and sweet, juicy flesh.

A red fruit with yell... sweet flavor and a pretty shape.

They are berries and they are blue; swee...und.


    <summary-display>
      <ul slot="master-list">
        <li>Apples</li>
        <li>Pears</li>
        <li>Bananas</li>
        <li>Oranges</li>
        <li>Peaches</li>
        <li>Strawberries</li>
        <li>Blueberries</li>
      </ul>
      <p data-name="Apples">A common, sweet, crunchy fruit, usually green or yellow in color.</p>
      <p data-name="Pears">A fairly common, sweet, usually green fruit, usually softer than Apples.</p>
      <p data-name="Bananas">A long, curved, yellow fruit, with a fairly gentle flavor.</p>
      <p data-name="Oranges">Orange in color, usually sweet but can be sharp, often contains pips.</p>
      <p data-name="Peaches">An orange fruit with big stone in the middle, and sweet, juicy flesh.</p>
      <p data-name="Strawberries">A red fruit with yellow seeds on the outside; has a sweet flavor and a pretty shape.</p>
      <p data-name="Blueberries">They are berries and they are blue; sweet in flavor, small in size, round.</p>
    </summary-display>
    <template id="summary-display-template">
      <article>
        <div>
          <slot name="master-list"></slot>
        </div>
        <div>
          <slot name="choice"></slot>
        </div>
      </article>
    </template>
  

    customElements.define('summary-display',
      class extends HTMLElement {
        constructor() {
          super();
          const template = document.getElementById('summary-display-template');
          const templateContent = template.content;
          const shadowRoot = this.attachShadow({mode: 'open'});
          shadowRoot.appendChild(templateContent.cloneNode(true));
          const items = Array.from(this.querySelectorAll('li'));
          const descriptions = Array.from(this.querySelectorAll('p'));
          items.forEach(item => {
            handleClick(item);
          });
          function handleClick(item) {
            item.addEventListener('click', function() {
              items.forEach(item => {
                item.style.backgroundColor = 'white';
              });
              descriptions.forEach(description => {
                updateDisplay(description, item);
              });
            });
          }
          function updateDisplay(description, item) {
            description.removeAttribute('slot');
            if(description.getAttribute('data-name') === item.textContent) {
              description.setAttribute('slot', 'choice');
              item.style.backgroundColor = '#bad0e4';
            }
          }
          const slots = this.shadowRoot.querySelectorAll('slot');
          slots[1].addEventListener('slotchange', function(e) {
            const nodes = slots[1].assignedNodes();
            console.log(`Element in Slot "${slots[1].name}" changed to "${nodes[0].outerHTML}".`);
          });
        }
      }
    );
  

slotted pseudo-class

Morgan Stanley

36 Accountant

Dr. Shazaam

Immortal Superhero

Boris

27 Time traveller

    <template id="person-template">
      <div>
        <h2>Personal ID Card</h2>
        <slot name="person-name">NAME MISSING</slot>
        <ul>
          <li><slot name="person-age">AGE MISSING</slot></li>
          <li><slot name="person-occupation">OCCUPATION MISSING</slot></li>
        </ul>
      </div>
    </template>
    <person-details>
      <p slot="person-name">Morgan Stanley</p>
      <span slot="person-age">36</span>
      <span slot="person-occupation">Accountant</span>
    </person-details>
    <person-details>
      <p slot="person-name">Dr. Shazaam</p>
      <span slot="person-age">Immortal</span>
      <span slot="person-occupation">Superhero</span>
    </person-details>
    <person-details>
      <p slot="person-name">Boris</p>
      <span slot="age">27</span>
      <span slot="i-am-awesome">Time traveller</span>
    </person-details>
  

    customElements.define('person-details',
      class extends HTMLElement {
        constructor() {
          super();
          const template = document.getElementById('person-template');
          const templateContent = template.content;
          const shadowRoot = this.attachShadow({mode: 'open'});
          const style = document.createElement('style');
          style.textContent = `
            div { padding: 10px; border: 1px solid gray; width: 200px; margin: 10px; }
            h2 { margin: 0 0 10px; }
            ul { margin: 0; }
            p { margin: 10px 0; }
            ::slotted(*) { color: gray; font-family: sans-serif; }
          `;
          shadowRoot.appendChild(style);
          shadowRoot.appendChild(templateContent.cloneNode(true));
      }
    });
  

word count

Sample heading

Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nunc pulvinar sed justo sed viverra. Aliquam ac scelerisque tellus. Vivamus porttitor nunc vel nibh rutrum hendrerit. Donec viverra vestibulum pretium. Mauris at eros vitae ante pellentesque bibendum. Etiam et blandit purus, nec aliquam libero. Etiam leo felis, pulvinar et diam id, sagittis pulvinar diam. Nunc pellentesque rutrum sapien, sed faucibus urna sodales in. Sed tortor nisl, egestas nec egestas luctus, faucibus vitae purus. Ut elit nunc, pretium eget fermentum id, accumsan et velit. Sed mattis velit diam, a elementum nunc facilisis sit amet.

Pellentesque ornare tellus sit amet massa tincidunt congue. Morbi cursus, tellus vitae pulvinar dictum, dui turpis faucibus ipsum, nec hendrerit augue nisi et enim. Curabitur felis metus, euismod et augue et, luctus dignissim metus. Mauris placerat tellus id efficitur ornare. Cras enim urna, vestibulum vel molestie vitae, mollis vitae eros. Sed lacinia scelerisque diam, a varius urna iaculis ut. Nam lacinia, velit consequat venenatis pellentesque, leo tortor porttitor est, sit amet accumsan ex lectus eget ipsum. Quisque luctus, ex ac fringilla tincidunt, risus mauris sagittis mauris, at iaculis mauris purus eget neque. Donec viverra in ex sed ullamcorper. In ac nisi vel enim accumsan feugiat et sed augue. Donec nisl metus, sollicitudin eu tempus a, scelerisque sed diam.


    <article contenteditable="">
      <h2>Sample heading</h2>
      <p>Lorem ipsum dolor sit amet, ...erisque sed diam.</p>
      <p is="word-count"></p>
    </article>
  

    class WordCount extends HTMLParagraphElement {
      constructor() {
        // Always call super first in constructor
        super();
        // count words in element's parent element
        const wcParent = this.parentNode;
        function countWords(node){
          const text = node.innerText || node.textContent;
          return text.split(/\s+/g).length;
        }
        const count = `Words: ${countWords(wcParent)}`;
        // Create a shadow root
        const shadow = this.attachShadow({mode: 'open'});
        // Create text node and add word count to it
        const text = document.createElement('span');
        text.textContent = count;
        // Append it to the shadow root
        shadow.appendChild(text);
        // Update count when element content changes
        setInterval(function() {
          const count = `Words: ${countWords(wcParent)}`;
          text.textContent = count;
        }, 200);
      }
    }
    // Define the new element
    customElements.define('word-count', WordCount, { extends: 'p' });
  

Back to Main Page