PageSourceSearch

https://www.roninwear.com/theme/roninwear/content/js/autocompleter.js?v=1768821480

js roninwear.com collected 2026-09-28 13:21:25 UTC 8,023 bytes, 232 lines download raw bytes

1(function () {
2  /* ---------- Utilidades básicas ---------- */
3  const $ = id => document.getElementById(id);
4  const extend = (target, src) => Object.assign({}, target, src);
5  const isFunction = fn => typeof fn === 'function';
6
7  /* ---------- Clase base Autocompleter ---------- */
8  class Autocompleter {
9    constructor(element, options = {}) {
10        this.element = typeof element === 'string' ? $(element) : element;
11        this.options = extend({
12            minLength: 1,
13            delay: 200,
14            width: 'auto',
15            overflow: false,
16            className: 'autocompleter-choices',
17            onSelection: null
18        }, options);
19
20        this.timer = null;
21        this.queryValue = '';
22        this.build();
23        this.bindEvents();
24
25       // hide suggestions on window resize / orientation change and when input size changes
26        this._handleResize = () => this.hideChoices();
27        window.addEventListener('resize', this._handleResize);
28        window.addEventListener('orientationchange', this._handleResize);
29
30        // observe direct size changes of the input (e.g. CSS/layout changes)
31        if (typeof ResizeObserver !== 'undefined') {
32        this._resizeObserver = new ResizeObserver(() => this.hideChoices());
33        this._resizeObserver.observe(this.element);
34        } else {
35        // fallback: if ResizeObserver no está disponible, se usa scroll/resize global
36        this._fallbackResize = () => this.hideChoices();
37        window.addEventListener('scroll', this._fallbackResize, true);
38        }
39    }
40
41    build() {
42      this.choices = document.createElement('ul');
43      this.choices.className = this.options.className;
44      this.choices.style.position = 'absolute';
45      this.choices.style.display = 'none';
46       this.choices.style.zIndex = '999';
47      this.choices.style.width =
48        this.options.width === 'auto' ? this.element.offsetWidth + 'px' : this.options.width + 'px';
49      document.body.appendChild(this.choices);
50    }
51
52    bindEvents() {
53      this.element.addEventListener('keyup', e => this.onKeyUp(e));
54      document.addEventListener('click', e => {
55        if (!this.choices.contains(e.target) && e.target !== this.element) this.hideChoices();
56      });
57    }
58
59    onKeyUp(e) {
60      const value = this.element.value.trim();
61      if (value.length < this.options.minLength) {
62        this.hideChoices();
63        return;
64      }
65      clearTimeout(this.timer);
66      this.timer = setTimeout(() => this.query(value), this.options.delay);
67    }
68
69    query(value) {
70      this.queryValue = value;
71      // Método que se redefine en las subclases
72    }
73
74   /*showChoices() {
75      const rect = this.element.getBoundingClientRect();
76      const computed = window.getComputedStyle(this.element);
77
78      // Calcula ancho real incluyendo padding y borde
79      const width =
80        this.element.offsetWidth -
81        parseFloat(computed.paddingLeft) -
82        parseFloat(computed.paddingRight);
83
84      this.choices.style.left = rect.left + window.scrollX + 'px';
85      this.choices.style.top = rect.bottom + window.scrollY + 'px';
86      this.choices.style.width = width + 'px'; // 🔹 ancho igual al input
87      this.choices.style.display = '';
88    }*/
89
90   showChoices() {
91      const rect = this.element.getBoundingClientRect();
92      const form = this.element.closest('form'); // 🔹 busca el formulario padre
93      const ref = form || this.element; // usa el form si existe, si no, el input
94
95      const refRect = ref.getBoundingClientRect();
96
97      this.choices.style.left = refRect.left + window.scrollX + 'px';
98      this.choices.style.top = rect.bottom + window.scrollY + 'px';
99      this.choices.style.width = ref.offsetWidth + 'px'; // 🔹 mismo ancho que el formulario
100      this.choices.style.display = '';
101    }
102
103    hideChoices() {
104      this.choices.style.display = 'none';
105    }
106
107    addChoiceEvents(choice) {
108      choice.addEventListener('click', () => {
109        this.element.value = choice.inputValue;
110        this.hideChoices();
111        if (isFunction(this.options.onSelection)) this.options.onSelection(choice);
112      });
113    }
114
115    markQueryValue(str) {
116      if (!this.queryValue) return str;
117      const regex = new RegExp('(' + this.queryValue.escapeRegExp() + ')', 'gi');
118      return str.replace(regex, '<span class="autocompleter-highlight">$1</span>');
119    }
120
121    destroy(){
122        // remove resize/observer listeners
123        if (this._handleResize) {
124            window.removeEventListener('resize', this._handleResize);
125            window.removeEventListener('orientationchange', this._handleResize);
126            this._handleResize = null;
127        }
128        if (this._resizeObserver) {
129            this._resizeObserver.disconnect();
130            this._resizeObserver = null;
131        }
132        if (this._fallbackResize) {
133            window.removeEventListener('scroll', this._fallbackResize, true);
134            this._fallbackResize = null;
135        }
136    }
137  }
138
139  /* ---------- Utilidad para escapar expresiones ---------- */
140  String.prototype.escapeRegExp = function () {
141    return this.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
142  };
143
144  /* ---------- Autocompleter.Request ---------- */
145  class AutocompleterRequest extends Autocompleter {
146    constructor(element, options = {}) {
147      super(element, options);
148      this.ajaxOptions = extend({
149        url: '',
150        method: 'GET',
151        headers: { 'X-Requested-With': 'XMLHttpRequest' }
152      }, options.ajaxOptions || {});
153    }
154  }
155
156  /* ---------- Autocompleter.Request.HTML ---------- */
157  class AutocompleterRequestHTML extends AutocompleterRequest {
158    constructor(element, url, options = {}) {
159      if (typeof url === 'object') {
160        options = url;
161        url = options.url || '';
162      }
163      options.ajaxOptions = extend({
164        url: url || '',
165        method: 'GET',
166        headers: { 'X-Requested-With': 'XMLHttpRequest' },
167        postVar: 'value'
168      }, options.ajaxOptions || {});
169      super(element, options);
170    }
171
172    query(value) {
173      this.queryValue = value;
174      if (!this.ajaxOptions.url) return;
175
176      const controller = new AbortController();
177      this.lastController = controller;
178      const url =
179        (this.ajaxOptions.method || 'GET').toUpperCase() === 'GET'
180          ? this.ajaxOptions.url +
181            (this.ajaxOptions.url.includes('?') ? '&' : '?') +
182            encodeURIComponent(this.ajaxOptions.postVar || 'value') +
183            '=' +
184            encodeURIComponent(value)
185          : this.ajaxOptions.url;
186
187      const fetchOpts = {
188        method: "POST",
189        headers: this.ajaxOptions.headers,
190        signal: controller.signal
191      };
192      if (fetchOpts.method !== 'GET') {
193        fetchOpts.body = new URLSearchParams({ [this.ajaxOptions.postVar || 'value']: value });
194      }
195
196      fetch(url, fetchOpts)
197        .then(r => {
198          if (!r.ok) throw new Error('Error en la red');
199          return r.text();
200        })
201        .then(html => {
202          const tmp = document.createElement('div');
203          tmp.innerHTML = html.trim();
204          const list = tmp.querySelectorAll('li');
205          this.choices.innerHTML = '';
206          list.forEach(choice => {
207            if (isFunction(this.options.injectChoice)) {
208              this.options.injectChoice.call(this, choice);
209            } else {
210              const text = choice.textContent.trim();
211              choice.inputValue = text;
212              choice.innerHTML = this.markQueryValue(text);
213              this.addChoiceEvents(choice);
214            }
215            this.choices.appendChild(choice);
216          });
217          this.showChoices();
218        })
219        .catch(() => {
220          this.hideChoices();
221        })
222        .finally(() => {
223          this.lastController = null;
224        });
225    }
226  }
227
228  /* ---------- Export global ---------- */
229  window.Autocompleter = Autocompleter;
230  window.Autocompleter.Request = AutocompleterRequest;
231  window.Autocompleter.Request.HTML = AutocompleterRequestHTML;
232})();

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.