daemon-idc/public/js/admin/server/switchAutoComplete.js
2026-02-09 16:48:11 +09:00

148 lines
4.5 KiB
JavaScript

class SwitchAutoComplete {
/**
* @param {HTMLElement|string} input
* @param {HTMLElement|string} panel
* @param {string[]} switchArray
* @param {object} opts
*/
constructor(input, panel, switchArray = [], opts = {}) {
this.inputEl = typeof input === 'string' ? document.querySelector(input) : input;
this.panelEl = typeof panel === 'string' ? document.querySelector(panel) : panel;
if (!this.inputEl || !this.panelEl) throw new Error('Invalid input/panel element');
this.opts = Object.assign({
previewOnFocus: true,
previewCount: 30,
resultLimit: 1000
}, opts);
this.setArray(switchArray);
this.activeIndex = -1;
// 이벤트 바인딩
this.onInput = this._handleInput.bind(this);
this.onFocus = this._handleFocus.bind(this);
this.onKeydown = this._handleKeydown.bind(this);
this.onClickOutside = this._handleClickOutside.bind(this);
this.inputEl.addEventListener('input', this.onInput);
this.inputEl.addEventListener('focus', this.onFocus);
this.inputEl.addEventListener('keydown', this.onKeydown);
document.addEventListener('mousedown', this.onClickOutside);
}
destroy() {
this.inputEl.removeEventListener('input', this.onInput);
this.inputEl.removeEventListener('focus', this.onFocus);
this.inputEl.removeEventListener('keydown', this.onKeydown);
document.removeEventListener('mousedown', this.onClickOutside);
this.hidePanel();
}
setArray(arr) {
this.switchArray = Array.from(new Set((arr || []).filter(Boolean)));
}
_handleInput(e) {
const q = (e.target.value || '').trim();
this._filterAndRender(q);
}
_handleFocus() {
if (!this.inputEl.value && this.opts.previewOnFocus) {
this._renderPanel(this.switchArray.slice(0, this.opts.resultLimit));
this.showPanel();
}
}
_handleKeydown(e) {
if (this.panelEl.hidden) return;
const items = this._items();
if (!items.length) return;
if (e.key === 'ArrowDown') { e.preventDefault(); this._move(1); }
else if (e.key === 'ArrowUp') { e.preventDefault(); this._move(-1); }
else if (e.key === 'Enter') {
if (this.activeIndex >= 0) { e.preventDefault(); this._pick(items[this.activeIndex].dataset.value); }
} else if (e.key === 'Escape') { this.hidePanel(); }
}
_handleClickOutside(e) {
if (!this.inputEl.closest('.ac-wrap')?.contains(e.target)) this.hidePanel();
}
_filterAndRender(query) {
if (!query) {
this.hidePanel();
return;
}
const q = query.toLowerCase();
let results = this.switchArray.filter(s => s.toLowerCase().includes(q));
if (this.opts.resultLimit > 0) results = results.slice(0, this.opts.resultLimit);
if (results.length) { this._renderPanel(results); this.showPanel(); }
else { this.hidePanel(); }
}
_renderPanel(list) {
this.panelEl.innerHTML = '';
this.activeIndex = -1;
const frag = document.createDocumentFragment();
list.forEach((sw) => {
const item = document.createElement('div');
item.className = 'ac-item';
item.textContent = sw;
item.dataset.value = sw;
item.addEventListener('mousedown', (ev) => { ev.preventDefault(); this._pick(sw); });
frag.appendChild(item);
});
this.panelEl.appendChild(frag);
}
_items() { return Array.from(this.panelEl.querySelectorAll('.ac-item')); }
_move(delta) {
const items = this._items();
if (!items.length) return;
this.activeIndex = (this.activeIndex + delta + items.length) % items.length;
items.forEach(el => el.classList.remove('active'));
const el = items[this.activeIndex];
el.classList.add('active');
el.scrollIntoView({ block: 'nearest' });
}
_pick(value) {
this.inputEl.value = value;
this.hidePanel();
}
showPanel() { this.panelEl.hidden = false; }
hidePanel() { this.panelEl.hidden = true; this.activeIndex = -1; }
}
/* switchData(JSON) 읽기 */
function parseEmbeddedJson(id) {
const el = document.getElementById(id);
if (!el) return [];
let txt = el.textContent || '';
txt = txt
.replace(/\/\*[\s\S]*?\*\//g, '')
.replace(/(^|[^:])\/\/.*$/mg, '$1')
.replace(/,\s*([}\]])/g, '$1');
try {
const data = JSON.parse(txt);
return Array.isArray(data) ? data : [];
} catch (e) {
console.error('switchData parse error', e);
return [];
}
}
/* 초기화 */
document.addEventListener('DOMContentLoaded', () => {
const switches = parseEmbeddedJson('switchData');
const ac = new SwitchAutoComplete('#switchInput', '#switchPanel', switches, {
previewOnFocus: true,
resultLimit: 2000
});
});