Cookbook Home ~ alanwsmith.com ~ links ~ podcast ~ twitter

Web Component - [Not Currently Possible] - Custom Input Form Control Element

Deatils

EXAMPLE

HTML

<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>
<ul>
  <li>Not</li>
  <li>an</li>
  <li>expanding</li>
  <li>list</li>
</ul>

<script>
  // Create a class for the element
  class ExpandingList extends HTMLUListElement {
    constructor() {
      // Always call super first in constructor
      // Return value from super() is a reference to this element
      self = super()

      // Get ul and li elements that are a child of this custom ul element
      // li elements can be containers if they have uls within them
      const uls = Array.from(self.querySelectorAll('ul'))
      const lis = Array.from(self.querySelectorAll('li'))

      console.log('HERERERE')
      // Hide all child uls
      // These lists will be shown when the user clicks a higher level container
      uls.forEach((ul) => {
        ul.style.display = 'none'
      })

      // Look through each li element in the ul
      lis.forEach((li) => {
        // If this li has a ul as a child, decorate it and add a click handler
        if (li.querySelectorAll('ul').length > 0) {
          // Add an attribute which can be used  by the style
          // to show an open or closed icon
          li.setAttribute('class', 'closed')

          // Wrap the li element's text in a new span element
          // so we can assign style and event handlers to the span
          const childText = li.childNodes[0]
          const newSpan = document.createElement('span')

          // Copy text from li to span, set cursor style
          newSpan.textContent = childText.textContent
          newSpan.style.cursor = 'pointer'

          // Add click handler to this span
          newSpan.onclick = self.showul

          // Add the span and remove the bare text node from the li
          childText.parentNode.insertBefore(newSpan, childText)
          childText.parentNode.removeChild(childText)
        }
      })
    }

    // li click handler
    showul = function (e) {
      // next sibling to the span should be the ul
      const nextul = e.target.nextElementSibling

      // Toggle visible state and update class attribute on ul
      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' })
</script>

CSS

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(img/down.png);
  */
}

.closed::before {
  /*
  background-image: url(img/right.png);
  */
}

.closed .closed::before,
.closed .open::before {
  display: none;
}

JavaScript

// class CustomInput extends HTMLInputElement {
//     constructor() {
//         super()
//         this.attachShadow({ mode: 'open' })
//         console.log('opened')
//         // const input = document.createElement('input')
//         // input.name = 'CUSTOM'
//         // input.value = 'testing'
//         // this.shadowRoot.append(input)
//     }
// }

// class CustomHidden extends HTMLInputElement {
//     constructor() {
//         super()
//         this.attachShadow({ mode: 'open' })
//         const input = document.createElement('input')
//         input.name = 'CUSTOM'
//         input.value = 'testing'
//         this.shadowRoot.append(input)
//     }
// }

// class CustomBravo extends HTMLInputElement {
//     constructor() {
//         super()
//         // this.attachShadow({ mode: 'open' })
//         console.log('Hit BRAVO')
//     }
// }
// customElements.define('custom-bravo', CustomBravo, { extends: 'input' })

// customElements.define('custom-hidden', CustomHidden)
// customElements.define('custom-input', CustomInput)
//
//
//

References