Last active
April 1, 2022 15:10
-
-
Save spin0us/fb81680d5761bfb810446005b2fe6e6c to your computer and use it in GitHub Desktop.
A pure JavaScript combobox
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
/*! | |
* A pure JavaScript combobox v1.3 | |
* https://gist.github.com/spin0us/fb81680d5761bfb810446005b2fe6e6c | |
* | |
* Author: Spin0us | |
* | |
* Date: 2022-04-01 | |
*/ | |
'use strict'; | |
document.querySelectorAll('select[data-search="true"]').forEach(elm => { | |
const prefix = 'cust'; | |
const width = elm.offsetWidth; | |
// Define style if not already defined | |
if(!document.querySelector(`style#${prefix}Combobox`)){ | |
const styleSheet = document.createElement('style'); | |
styleSheet.id = prefix+"Comboxbox"; | |
styleSheet.type = "text/css"; | |
styleSheet.innerHTML = ` | |
.${prefix}SelectWrapper | |
{ display:inline-block; padding:0; margin:0; } | |
.${prefix}SelectWrapper>button | |
{ display:grid; grid-template-columns:1fr auto; align-items:center; cursor:pointer; border:1px solid #aaa; border-radius:.25rem; padding:0; text-align:left; } | |
.${prefix}SelectWrapper>button input | |
{ background-color:#fff; margin:0; padding:1px .5rem; border:0; border-right:1px solid #aaa; outline:none; } | |
.${prefix}SelectWrapper.active>button input | |
{ background-image:url("data:image/svg+xml,%3Csvg xmlns='http://www.w3.org/2000/svg' viewBox='0 0 30 30'%3E%3Ccircle cx='11.8' cy='11.8' r='10' fill='none' stroke='%23ddd' stroke-width='3.5'/%3E%3Cline x1='19.5' y1='19.5' x2='27.5' y2='27.5' stroke='%23ddd' stroke-width='5' stroke-linecap='round'/%3E%3C/svg%3E"); background-position: calc(100% - 2px) center; background-repeat: no-repeat; background-size: auto 90%; padding-right: 20px;} | |
.${prefix}SelectWrapper>button small | |
{ color:#555; font-size:.6rem; padding:1px .25rem; } | |
.${prefix}SelectWrapper>ul | |
{ display:none; position:absolute; margin:0; padding:0; background-color:#fff; max-height:10rem; overflow-y:auto; border:1px solid #aaa; border-top:0; } | |
.${prefix}SelectWrapper.active>ul | |
{ display:inherit !important; } | |
.${prefix}SelectWrapper>ul>li | |
{ cursor:pointer; list-style-type:none; padding:.25rem .5rem; font-family:Tahoma; font-size:.8rem; -ms-user-select:none; -moz-user-select:none; -webkit-user-select:none; -webkit-touch-callout: none; -khtml-user-select: none; user-select:none; } | |
.${prefix}SelectWrapper>ul>li[data-selected="true"] | |
{ background-color:#d9e0ff; } | |
.${prefix}SelectWrapper>ul>li:hover | |
{ background-color:#39f; color:#fff; } | |
`; | |
document.head.appendChild(styleSheet); | |
} | |
// Create wrapper element | |
const spn = document.createElement('span'); | |
spn.id = elm.id; | |
spn.classList.add(`${prefix}SelectWrapper`); | |
spn.innerHTML = '<button type="button"><input type="text" value=""/><small>▼</small></button><ul></ul>'; | |
spn.value = elm.options[0].value; | |
spn.dataset.value = elm.options[0].value; | |
spn.dataset.text = elm.options[0].textContent; | |
if(elm.attributes.onchange) spn.setAttribute(elm.attributes.onchange.nodeName, elm.attributes.onchange.nodeValue); | |
// Replace select by the wrapper | |
elm.parentNode.replaceChild(spn,elm); | |
// Retreive components | |
const btn = document.querySelector(`#${elm.id} button`); | |
const inp = document.querySelector(`#${elm.id} input`); | |
const sml = document.querySelector(`#${elm.id} small`); | |
const lst = document.querySelector(`#${elm.id} ul`); | |
// Resize button | |
btn.style.width = (width + document.querySelector(`#${elm.id} small`).offsetWidth + 2) + 'px'; | |
// Create select list | |
lst.style.width = width + 'px'; | |
// Build list content based on options | |
for(let i=0; i<elm.options.length; i++){ | |
let li = document.createElement('li'); | |
li.dataset.value = elm.options[i].value; | |
li.innerHTML = elm.options[i].textContent; | |
li.classList = elm.options[i].classList; | |
lst.appendChild(li); | |
if(elm.options[i].hasAttribute('selected')){ | |
li.dataset.selected = true; | |
spn.value = elm.options[i].value; | |
spn.dataset.text = elm.options[i].textContent; | |
} | |
} | |
// Init. input | |
inp.value = spn.dataset.text; | |
inp.placeholder = spn.dataset.text; | |
const filter = function(val=''){ | |
let rows = document.querySelectorAll(`#${elm.id} li`), | |
reg = RegExp(val, 'i'); | |
rows.forEach( row => { row.style.display = reg.test(row.textContent.replace(/\s+/g, ' ')) ? '' : 'none'; }); | |
}; | |
// Manage selec events | |
const toggleActive = function(){ | |
spn.classList.toggle('active'); | |
if(spn.classList.contains('active')){ | |
inp.value = ''; | |
filter(); | |
let li = document.querySelector(`#${elm.id} li[data-value="${spn.value}"]`); | |
lst.scrollTo(0,li.offsetTop); | |
setTimeout(function(){inp.focus()}, 50); | |
} else { | |
inp.value = spn.dataset.text; | |
inp.placeholder = spn.dataset.text; | |
if(spn.value != spn.dataset.value){ | |
spn.dispatchEvent(new Event('change')); | |
spn.dataset.value = spn.value; | |
} | |
} | |
}; | |
inp.addEventListener('keyup', function(){ | |
filter(this.value.trim()); | |
}); | |
document.addEventListener('mousedown', function(e){ | |
if(e.target && e.target.closest(`#${elm.id}`)){ | |
if(e.target.nodeName == 'INPUT' && !spn.classList.contains('active')) toggleActive(); | |
if(e.target.nodeName == 'SMALL') toggleActive(); | |
if(e.target.nodeName == 'LI'){ | |
document.querySelector(`#${elm.id} li[data-value="${spn.value}"]`).dataset.selected = false; | |
spn.value = e.target.dataset.value; | |
spn.dataset.text = e.target.textContent; | |
e.target.dataset.selected = true; | |
toggleActive(); | |
} | |
} | |
else if(spn.classList.contains('active')) toggleActive(); | |
}); | |
}); |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment