Press "Enter" to skip to content

Reusable Custom Select Element in Javascript

reusable custom select element javascript
reusable custom select element javascript

It all started here…

In my previous post about the custom select component, I explained how to create a custom select box from the scratch using vanilla HTML, CSS and javascript. Well, life was good until I got this comment on my previous article.

Demo here

You can see it there in my previous post.

And I thought..

It got me into thinking, yeah, that makes a lot of sense. I mean what’s the point of putting all those efforts in creating a select element which is not even reusable? Duh!

So, once again I put my coding gloves on. ( And started writing something sensible this time, finally!)

So, the first thought came to my mind while thinking of a reusable component was to create a common library. Sounds heavy right? I was thinking the same at first.

Wait, it’s not hard

Yay! you created your first library!
To my surprise, I have enjoyed writing it even more than the previous one. It’s compact, pretty easy to maintain and much more readable.

The idea was to remove all external dependencies and combine everything into one file. By everything I mean HTML, CSS and Javascript.

You might or might not have noticed that the title says only Javascript. Yes, that’s pretty much what I needed to make my reusable custom select element.

I will explain in parts, so don’t panic at first.

Demo here

layout.html

 <html>
   <head>
      <script type="text/javascript" src="./custom-select.js"></script>
   </head>
   <body>
      <div>
            <custom-select options='[{"text":"One", "value":1},{"text":"Two", "value":2}]'></custom-select>
      </div>
      <div style="position: absolute; top: 100px;">
            <custom-select options='[{"text":"Three", "value":3},{"text":"Four", "value":4}]'></custom-select>
      </div>
   </body>
</html> 

How to use the custom element

It can be any page you want to use the custom select element. At the top, I have included the source file custom-select.js. 

In the body section, I have used my cool custom tags, 

<custom-select></custom-select>

Those who have worked in Angular, React or Vue is familiar to custom components. For others, I have registered my custom element here and by writing these tags (<custom-select></custom-select>), our element will appear. Pretty cool, eh! Not yet.

I have passed an attribute named options, which takes a JSON array of items. Each array item has two properties text and value. The text property is used to display the option name in the dropdown. The value property is used to pass the option value for internal usage. Clear enough? I said so. Now, let’s take a look at the JS part.

Demo here

custom-select.js

 class CustomSelect extends HTMLElement {

   constructor() {
      super();

      let isOpen = false;
      let shadow = this.attachShadow({mode: 'open'});

      this.toggleSelf = function (el) {
         isOpen = !isOpen;

         if (isOpen) {
            el.style.visibility = 'visible';
            el.focus();
         } else {
            el.blur();
            el.style.visibility = 'hidden';
         }
      }

      this.selected = function (opt) {
         valueText.innerHTML = opt.text;
         hiddenInput.val = opt.value;
         this.toggleSelf(this.selectContainer);
      }

      let wrapper = document.createElement('div');
      wrapper.setAttribute('class', 'wrapper');

      let hiddenInput = document.createElement('input');
      hiddenInput.setAttribute('type', 'hidden');
      hiddenInput.setAttribute('id', 'selectedValue');

      let displayValue = document.createElement('div');
      displayValue.setAttribute('class', 'display-value');
      displayValue.addEventListener('click', ()=> {
         this.toggleSelf(this.selectContainer);
      });

      let valueText = document.createElement('span');
      valueText.setAttribute('class', 'value-text');
      valueText.innerHTML = 'Select';
      
      let arrowIconWrapper = document.createElement('span');
      arrowIconWrapper.setAttribute('class', 'arrow arrow-down');

      this.selectContainer = document.createElement('ul');
      this.selectContainer.setAttribute('class', 'select-container');
      this.selectContainer.style.visibility = 'hidden';
      this.selectContainer.addEventListener('onblur', function() {
         this.toggleSelf(this.selectContainer);
      });

      let style = document.createElement('style');
      style.textContent = '.wrapper{position:relative;}.display-value{height:39px;width:211px;display:flex;'+
      'position:absolute;border:2px solid #666}.value-text{display:flex;padding-left:10px;'+
      'font-family:sans-serif;align-items:center;color:#666}'+
      '.arrow{left:190px;top:17px;position:absolute}.arrow.arrow-up{width:0;height:0;'+
      'border-left:6px solid transparent;border-right:6px solid transparent;'+
      'border-bottom:6px solid #000}.arrow.arrow-down{width:0;height:0;'+
      'border-left:6px solid transparent;border-right:6px solid transparent;'+
      'border-top:6px solid #000}.select-container{width:211px;padding:0;position:absolute;'+
      'visibility:hidden;margin:0;height:fit-content;border:2px solid #333;background-color:#fff;'+
      'list-style-type:none;display:block}.select-container:focus{outline:0}.select-option{'+
      'display:none;height:40px;display:flex;padding-left:10px;font-family:sans-serif;'+
      'align-items:center;color:#666}.select-option:hover{background-color:#eee}';

      shadow.appendChild(style);
      shadow.appendChild(wrapper);
      wrapper.appendChild(hiddenInput);
      wrapper.appendChild(displayValue);
      displayValue.appendChild(valueText);
      displayValue.appendChild(arrowIconWrapper);
      wrapper.appendChild(this.selectContainer);
   }

   connectedCallback() {

      if (this.hasAttribute('options')) {
         let options = this.getAttribute('options');
         options = JSON.parse(options);

         if (Array.isArray(options) && options.length > 0) {
            options.forEach((option) => {
               
               let optionElement = document.createElement('li');
               optionElement.setAttribute('class', 'select-option');
               optionElement.innerHTML = option.text;
               optionElement.addEventListener('click', ()=>{
                  this.selected(option);
               });

               this.selectContainer.appendChild(optionElement);
            });
         }
      }
    }
}

customElements.define('custom-select', CustomSelect); 

As you can see, I have made a class (CustomSelect) for the component. And at the bottom of the file, I have passed the class reference to create my custom element.

Class Constructor

The class constructor contains most of the code as this block handles the individual element creation and event binding to elements.

The constructor can be broken into 3 parts. The first part contains individual element creation and function definitions that handle click and select events.

The second part is minified CSS from the previous article (No change). Minified to reduce the number of lines.

The third part is the glue that attaches all the elements in an order to generate the final component.

connectedCallback

But, that’s not all. We have another function called connectedCallback.

This section handles the attribute value that we pass and creates options for our select input. You may ask, why we are not handling this in the constructor. That is because the constructor renders very fast and returns without reading the given attribute.

Conclusion

Importing this class file and using as per the above example will work fine. You can make customizations to make it even better. Or comment below if you have any more suggestion to enhance it. Thanks for reading my article. Your feedback is valuable and encouraging. Happy coding.

Leave a Reply

Your email address will not be published. Required fields are marked *

92 − 84 =