PageSourceSearch

https://proparcel.com/static/js/components/searchable_select.js

js proparcel.com collected 2026-09-24 14:52:15 UTC 5,230 bytes, 128 lines download raw bytes

1/*
2  Searchable Select (Vanilla JS)
3  - Seçim kutusunun üstüne bir arama input'u ekler
4  - Yazdıkça seçenekleri filtreleyip SELECT içeriğini yeniden oluşturur
5  - loadQuarters gibi dinamik doldurmalarda otomatik senkron kalması için MutationObserver kullanır
6  - Global API: window.makeSelectSearchable(selectOrSelector, { placeholder?: string })
7*/
8(function(){
9  function toArray(nodeList){ return Array.prototype.slice.call(nodeList || []); }
10
11  function captureOptions(select){
12    const opts = [];
13    toArray(select.options).forEach(function(o, idx){
14      // İlk seçenek (placeholder) hariç tut, ama metnini ve değerini koru
15      if (idx === 0) return;
16      opts.push({
17        value: o.value,
18        text: o.textContent || '',
19        disabled: !!o.disabled,
20        dataset: Object.assign({}, o.dataset),
21      });
22    });
23    return opts;
24  }
25
26  function rebuildSelect(select, originalOptions, filterText){
27    const placeholderOption = select.options[0] ? select.options[0].outerHTML : '<option value="">Seçiniz</option>';
28    const q = (filterText || '').trim().toLowerCase();
29    var html = placeholderOption;
30    originalOptions.forEach(function(opt){
31      if (!q || (opt.text || '').toLowerCase().indexOf(q) !== -1) {
32        var attrs = '';
33        if (opt.disabled) attrs += ' disabled';
34        // dataset attribute'ları geri yaz
35        if (opt.dataset) {
36          Object.keys(opt.dataset).forEach(function(k){
37            attrs += ' data-' + k.replace(/[A-Z]/g, function(m){ return '-' + m.toLowerCase(); }) + '="' + String(opt.dataset[k]).replace(/"/g, '&quot;') + '"';
38          });
39        }
40        html += '<option value="' + String(opt.value).replace(/"/g, '&quot;') + '"' + attrs + '>' + (opt.text || '') + '</option>';
41      }
42    });
43    const prevValue = select.value;
44    select.innerHTML = html;
45    // Filtre sonrası eski değer hâlâ görünürse koru, değilse placeholder'a dön
46    const exists = toArray(select.options).some(function(o){ return o.value === prevValue; });
47    select.value = exists ? prevValue : '';
48    // change event tetikleme yok; kullanıcı seçerse tetiklenecek
49  }
50
51  function ensureContainer(select){
52    if (select.__searchableContainer) return select.__searchableContainer;
53    var wrapper = document.createElement('div');
54    wrapper.className = 'pp-searchable-select';
55    wrapper.style.display = 'flex';
56    wrapper.style.flexDirection = 'column';
57    wrapper.style.gap = '6px';
58    // SELECT'in önüne yerleştir
59    select.parentNode.insertBefore(wrapper, select);
60    wrapper.appendChild(select);
61    select.__searchableContainer = wrapper;
62    return wrapper;
63  }
64
65  function createSearchBox(select, placeholder){
66    if (select.__searchInput) return select.__searchInput;
67    var input = document.createElement('input');
68    input.type = 'text';
69    input.className = 'pp-select-search-input';
70    input.placeholder = placeholder || 'Ara...';
71    input.autocomplete = 'off';
72    input.style.padding = '8px 10px';
73    input.style.border = '1px solid #d1d5db';
74    input.style.borderRadius = '6px';
75    input.style.fontSize = '14px';
76    input.style.outline = 'none';
77    input.addEventListener('focus', function(){ input.style.borderColor = '#3498db'; });
78    input.addEventListener('blur', function(){ input.style.borderColor = '#d1d5db'; });
79    select.__searchInput = input;
80    return input;
81  }
82
83  function makeSelectSearchable(selectOrSelector, opts){
84    var select = typeof selectOrSelector === 'string' ? document.querySelector(selectOrSelector) : selectOrSelector;
85    if (!select || select.tagName !== 'SELECT') return;
86
87    var placeholder = (opts && opts.placeholder) || 'Mahalle ara...';
88
89    var container = ensureContainer(select);
90    var searchBox = createSearchBox(select, placeholder);
91    // Input'u SELECT'in üstüne koy
92    if (searchBox.parentNode !== container) container.insertBefore(searchBox, select);
93
94    // Orijinal seçenekleri tut
95    var originalOptions = captureOptions(select);
96
97    // Dinamik doldurmalarda (loadQuarters) orijinali tazelemek için gözlemci
98    if (!select.__searchMutationObserver) {
99      var mo = new MutationObserver(function(mutations){
100        var needRefresh = mutations.some(function(m){ return m.type === 'childList'; });
101        if (needRefresh) {
102          originalOptions = captureOptions(select);
103          // Aktif arama metni varsa, yeniden uygula
104          rebuildSelect(select, originalOptions, searchBox.value || '');
105        }
106      });
107      mo.observe(select, { childList: true });
108      select.__searchMutationObserver = mo;
109    }
110
111    // Arama davranışı
112    var debounceTimer = null;
113    searchBox.addEventListener('input', function(){
114      if (debounceTimer) clearTimeout(debounceTimer);
115      debounceTimer = setTimeout(function(){
116        rebuildSelect(select, originalOptions, searchBox.value || '');
117      }, 60);
118    });
119
120    // İlk kurulumda mevcut metin boşsa tümünü göster
121    rebuildSelect(select, originalOptions, '');
122  }
123
124  // Global export
125  window.makeSelectSearchable = makeSelectSearchable;
126})();
127
128

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.