<SELECT ..></SELECT>에서 출력되는 것이 많을때
<form id="myForm" method="post">
<div class="autocomplete_select" data-form-id="myForm">
<button type="button" class="autocomplete_select__btn" id="autocomplete_select_btn">
선택하세요
</button>
<div class="autocomplete_select__panel" id="autocomplete_select_panel">
<input type="text" class="autocomplete_select__search" id="autocomplete_select_search" placeholder="검색">
<div class="autocomplete_select__list" id="autocomplete_select_list"></div>
</div>
<!-- 선택 결과를 담을 hidden -->
<input type="hidden" name="selected_id" id="autocomplete_select_value" value="">
<input type="hidden" name="selected_meta1" id="autocomplete_select_meta1" value="">
<input type="hidden" name="selected_meta2" id="autocomplete_select_meta2" value="">
</div>
</form>.autocomplete_select{ position:relative; display:inline-block; min-width:140px; }
.autocomplete_select__btn{
width:100%;
padding:6px 10px;
border:1px solid #cfcfcf;
border-radius:8px;
background:#fff;
cursor:pointer;
text-align:left;
white-space:nowrap;
overflow:hidden;
text-overflow:ellipsis;
}
.autocomplete_select__panel{
display:none;
width:320px;
border:1px solid #cfcfcf;
border-radius:10px;
background:#fff;
padding:8px;
box-shadow:0 6px 18px rgba(0,0,0,.08);
box-sizing:border-box;
z-index:99999;
}
.autocomplete_select__search{
width:100%;
padding:6px 8px;
border:1px solid #cfcfcf;
border-radius:8px;
box-sizing:border-box;
}
.autocomplete_select__list{
margin-top:8px;
max-height:240px;
overflow:auto;
border:1px solid #e9e9e9;
border-radius:8px;
}
.autocomplete_select__item{
padding:7px 9px;
cursor:pointer;
border-bottom:1px solid #f0f0f0;
white-space:nowrap;
overflow:hidden;
text-overflow:ellipsis;
}
.autocomplete_select__item:last-child{ border-bottom:none; }
.autocomplete_select__item:hover{ background:#f7f7f7; }
.autocomplete_select__muted{ color:#777; padding:8px; }/** 데이터만 바꿔서 재사용 */
window.AUTOCOMPLETE_SELECT_DATA = window.AUTOCOMPLETE_SELECT_DATA || [
{ id: 'all', label: '전체', meta1: '', meta2: '' },
// { id: '1', label: '예시 항목', meta1: 'x', meta2: 'y' },
];
(function autocomplete_select_init(opts){
const cfg = Object.assign({
rootSelector: '.autocomplete_select',
btnId: 'autocomplete_select_btn',
panelId: 'autocomplete_select_panel',
searchId: 'autocomplete_select_search',
listId: 'autocomplete_select_list',
valueId: 'autocomplete_select_value',
meta1Id: 'autocomplete_select_meta1',
meta2Id: 'autocomplete_select_meta2',
data: window.AUTOCOMPLETE_SELECT_DATA,
panelWidth: 320,
gap: 6,
autoSubmit: true,
}, opts || {});
const root = document.querySelector(cfg.rootSelector);
if(!root) return;
const formId = root.dataset.formId || '';
const form = formId ? document.getElementById(formId) : root.closest('form');
const btn = document.getElementById(cfg.btnId);
const panel = document.getElementById(cfg.panelId);
const input = document.getElementById(cfg.searchId);
const list = document.getElementById(cfg.listId);
const hidVal = document.getElementById(cfg.valueId);
const hidM1 = document.getElementById(cfg.meta1Id);
const hidM2 = document.getElementById(cfg.meta2Id);
if(!btn || !panel || !input || !list || !hidVal) return;
// panel을 body로 떼서 fixed로 띄우면 overflow 영향을 덜 받음
if(panel.parentElement !== document.body){
document.body.appendChild(panel);
}
function place(){
const r = btn.getBoundingClientRect();
let left = r.left;
const maxLeft = window.innerWidth - cfg.panelWidth - 8;
if(left > maxLeft) left = Math.max(8, maxLeft);
if(left < 8) left = 8;
panel.style.position = 'fixed';
panel.style.left = left + 'px';
panel.style.top = (r.bottom + cfg.gap) + 'px';
panel.style.width = cfg.panelWidth + 'px';
}
function render(keyword){
list.innerHTML = '';
const kw = (keyword || '').toLowerCase().trim();
const filtered = (cfg.data || []).filter(it =>
String(it.label || '').toLowerCase().includes(kw)
);
if(filtered.length === 0){
const div = document.createElement('div');
div.className = 'autocomplete_select__muted';
div.textContent = '검색 결과가 없습니다.';
list.appendChild(div);
return;
}
filtered.forEach(it => {
const div = document.createElement('div');
div.className = 'autocomplete_select__item';
div.textContent = it.label;
div.addEventListener('click', function(e){
e.preventDefault();
e.stopPropagation();
hidVal.value = it.id ?? '';
if(hidM1) hidM1.value = it.meta1 ?? '';
if(hidM2) hidM2.value = it.meta2 ?? '';
btn.textContent = it.label || '선택됨';
panel.style.display = 'none';
if(cfg.autoSubmit && form) form.submit();
});
list.appendChild(div);
});
}
function openPanel(){
place();
panel.style.display = 'block';
render(input.value);
setTimeout(() => input.focus(), 0);
}
function closePanel(){ panel.style.display = 'none'; }
btn.addEventListener('click', function(e){
e.preventDefault();
e.stopPropagation();
if(panel.style.display === 'block') closePanel();
else openPanel();
});
btn.addEventListener('keydown', function(e){
if(e.key === 'Enter'){
e.preventDefault();
e.stopPropagation();
if(panel.style.display === 'block') closePanel();
else openPanel();
}
});
input.addEventListener('input', function(){ render(this.value); });
panel.addEventListener('click', e => e.stopPropagation());
document.addEventListener('click', function(e){
if(e.target !== btn && !panel.contains(e.target)) closePanel();
});
window.addEventListener('scroll', function(){ if(panel.style.display === 'block') place(); }, true);
window.addEventListener('resize', function(){ if(panel.style.display === 'block') place(); });
closePanel();
})();