1/** 2 * combo_buscador.js v1.0 3𧩠¿Qué hace? 4 5Convierte cualquier <select> de un formulario (por clase o selector) en un combo visual personalizado, con buscador, integración Bootstrap 4, compatible con escritorio y móvil. Sustituye a SumoSelect. 6 7ð¦ FUNCIONALIDADES 8 9â Reemplaza visualmente <select> por un combo personalizado. 10â Compatible con múltiples selectores y llamadas repetidas. 11â Despliega lista con buscador integrado (search: true). 12â Cierra cualquier combo abierto al abrir uno nuevo. 13â Cierra el combo si haces clic fuera. 14â Se enfoca automáticamente el buscador al abrir. 15â Compatible con contenido dinámico y AJAX. 16 17*/ 18class combo_buscador 19{ 20 constructor(selectorOrElement, opciones = {}) 21 { 22 let d = {}; // Depuración global 23 24 this.config = Object.assign({ 25 placeholder: `Selecciona...`, 26 search: true, 27 multiple: false 28 }, opciones); 29 30 // Array para almacenar elementos a procesar 31 let elementos = []; 32 33 if (typeof selectorOrElement === `string`) 34 { 35 d.tipo = `string`; 36 elementos = [...document.querySelectorAll(selectorOrElement)]; 37 d.selector = selectorOrElement; 38 } 39 else if (selectorOrElement instanceof Element) 40 { 41 d.tipo = `Element`; 42 d.valorOriginal = selectorOrElement; 43 44 // Filtrar solo selectores del mismo tipo y misma clase 45 if (selectorOrElement.classList.length) 46 { 47 const clase = `.` + [...selectorOrElement.classList].join(`.`); 48 elementos = [...document.querySelectorAll(clase)] 49 .filter(el => el.tagName.toLowerCase() === `select`); 50 d.selector = clase; 51 } 52 else 53 { 54 elementos = [selectorOrElement]; 55 d.selector = `(elemento único sin clase)`; 56 } 57 } 58 59 d.total = elementos.length; 60 61 console.log(`â¶ combo_buscador - Constructor`, selectorOrElement, `\nConfiguración:`, this.config); 62 console.table(d); 63 64 elementos.forEach((el, i) => { 65 console.log(`â¡ creando combo ${i + 1}/${elementos.length}`, el); 66 this.crear(el); 67 }) 68 69 console.log(`â combo_buscador finalizado para ${elementos.length} elemento(s).`); 70 } 71 72 crear(selectEl) 73 { 74 if (!selectEl) return; 75 76 // ð Eliminar wrapper si ya existe 77 if (selectEl.nextElementSibling && selectEl.nextElementSibling.classList.contains(`combo_buscador`)) { 78 console.log(`â ï¸ Eliminando combo previo:`, selectEl); 79 selectEl.nextElementSibling.remove(); 80 } 81 82 selectEl.hidden = true; 83 84 const wrapper = document.createElement(`div`); 85 wrapper.className = `combo_buscador`; 86 87 const boton = document.createElement(`button`); 88 boton.type = `button`; 89 boton.className = `form-control form-control-sm overflow-hidden`; 90 91 const seleccionada = selectEl.options[selectEl.selectedIndex];
92 boton.textContent = (seleccionada && seleccionada.value.trim() !== ``) 93 ? seleccionada.textContent 94 : this.config.placeholder; 95 96 wrapper.appendChild(boton); 97 98 const lista = document.createElement(`div`); 99 lista.className = `combo_buscador_opciones`; 100 101 let buscador = null; 102 if (this.config.search) 103 { 104 buscador = document.createElement(`input`); 105 buscador.type = `text`; 106 buscador.placeholder = `Buscar...`; 107 buscador.className = `bg-light border border-info m-1 p-1`; 108 buscador.style.width = `calc(100% - 0.5rem)`; 109 lista.appendChild(buscador); 110 } 111 112 const ul = document.createElement(`ul`); 113 [...selectEl.options].forEach(op => { 114 const li = document.createElement(`li`); 115 li.textContent = op.textContent; 116 li.dataset.value = op.value; 117 li.addEventListener(`click`, () => { 118 selectEl.value = op.value; 119 boton.textContent = op.textContent; 120 lista.classList.remove(`visible`); 121 const evt = new Event(`change`, { bubbles: true }); 122 selectEl.dispatchEvent(evt); 123 }); 124 ul.appendChild(li); 125 }); 126 127 lista.appendChild(ul); 128 wrapper.appendChild(lista); 129 selectEl.parentNode.insertBefore(wrapper, selectEl.nextSibling); 130 131 boton.addEventListener(`click`, () => 132 { 133 // ð¹ Cerrar todos los combos abiertos 134 document.querySelectorAll(`.combo_buscador_opciones.visible`).forEach(el => { 135 if (el !== lista) el.classList.remove(`visible`); 136 }); 137 138 // ð¹ Abrir/cerrar el actual 139 const visible = lista.classList.toggle(`visible`); 140 141 // ð¹ Si se abre, enfocar el input de búsqueda (en el siguiente frame) 142 if (visible && this.config.search && buscador) 143 { 144 requestAnimationFrame(() => { 145 buscador.focus(); 146 }); 147 } 148 }); 149 150 if (this.config.search) 151 { 152 buscador.addEventListener(`input`, () => { 153 const term = buscador.value.toLowerCase(); 154 ul.querySelectorAll(`li`).forEach(li => { 155 li.style.display = li.textContent.toLowerCase().includes(term) ? `` : `none`; 156 }); 157 }); 158 } 159 160 console.log(`â combo aplicado a`, selectEl); 161 } 162} 163 164document.addEventListener('click', function(ev) 165{ 166 const combosVisibles = document.querySelectorAll('.combo_buscador_opciones.visible'); 167 168 combosVisibles.forEach(lista => 169 { 170 // Si el clic no fue dentro del combo asociado, cerramos 171 if (!lista.parentNode.contains(ev.target)) 172 { 173 lista.classList.remove('visible'); 174 } 175 }); 176});
Line numbers count LF bytes from the start of the resource, as the search results do. Vendor segments are library code the classifier recognised; they are stored but not indexed. Bytes are shown as Latin1 characters, one per byte.