1import { Controller } from '@hotwired/stimulus'; 2import * as bootstrap from 'bootstrap'; 3 4export default class extends Controller { 5 static targets = [ "modal", "offcanvas" ] 6 7 modal 8 offcanvas 9 focused 10 11 connect() { 12 // 13 // Preparar offcanvas 14 // 15 const offcanvas_selector = 'data-confidare--ui--ui-target="offcanvas"'; 16 17 document.body.insertAdjacentHTML('beforeend', ` 18 <div class="offcanvas offcanvas-end" tabindex="-1" ${offcanvas_selector}> 19 <div class="offcanvas-header"> 20 <div class="offcanvas-title w-100"></div> 21 </div> 22 <div class="offcanvas-body"> 23 </div> 24 </div> 25 `) 26 27 this.offcanvas = new bootstrap.Offcanvas(`[${offcanvas_selector}]`); 28 29 document 30 .querySelector(`[${offcanvas_selector}]`) 31 .addEventListener( 32 'shown.bs.offcanvas', 33 ev => { 34 ev.target.querySelector('button, a')?.focus(); 35 } 36 ); 37 38 // 39 // Preparar modal 40 // 41 42 const modal_selector = 'data-confidare--ui--ui-target="modal"'; 43 44 document.body.insertAdjacentHTML('beforeend', ` 45 <div class="modal" tabindex="-1" ${modal_selector}> 46 <div class="modal-dialog modal-lg modal-dialog-centered"> 47 <div class="modal-content"> 48 <div class="modal-header"> 49 <div class="modal-title"></div> 50 </div> 51 <div class="modal-body"> 52 </div> 53 </div> 54 </div> 55 </div> 56 `) 57 58 this.modal = new bootstrap.Modal(`[${modal_selector}]`); 59 60 document 61 .querySelector(`[${modal_selector}]`) 62 .addEventListener( 63 'shown.bs.modal', 64 ev => { 65 ev.target.querySelector('button, a')?.focus(); 66 } 67 ); 68 // 69 // Reaplicar búsquedas rápidas guardadas 70 // 71 document.querySelectorAll('input[type=search]').forEach((input) => { 72 const stored_search = sessionStorage.getItem( 73 this.quickSearchSessionKey( 74 input.getAttribute('data-filter-selector') 75 ) 76 ); 77 78 if (stored_search !== null) { 79 input.value = stored_search; 80 } 81 82 input.dispatchEvent(new Event('search')); 83 }); 84 85 // 86 // Observar scroll para aplicar header-not-at-top 87 // 88 if ( 89 "IntersectionObserver" in window && 90 "IntersectionObserverEntry" in window && 91 "intersectionRatio" in window.IntersectionObserverEntry.prototype 92 ) { 93 document.body.insertAdjacentHTML('afterbegin', '<div id="top"></div>'); 94 95 let observer = new IntersectionObserver(entries => { 96 if (entries[0].boundingClientRect.y < 0) { 97 document.body.classList.add("header-not-at-top"); 98 document.body.classList.remove("header-at-top"); 99 } else { 100 document.body.classList.add("header-at-top"); 101 document.body.classList.remove("header-not-at-top"); 102 } 103 }); 104 observer.observe(document.querySelector("#top")); 105 } 106 } 107 108 focus(elem) { 109 if (this.focused) { 110 this.focused.classList.remove('focus') 111 } 112 113 elem.classList.add('focus') 114 this.focused = elem 115 } 116 117 inLocalContainer(ev) { 118 const container = document.querySelector(ev.params.container) 119 container.innerText = '' 120 container.insertAdjacentHTML('beforeend', '<div class="d-flex justify-content-center"><div class="spinner-border text-primary" role="status"></div></div>'); 121 122 ev.preventDefault() 123 124 this.focus(ev.currentTarget) 125 126 const context = this 127 128 fetch(ev.currentTarget.getAttribute('href'), { 129 method: 'GET',
130 mode: 'same-origin', 131 cache: 'no-cache' 132 }) 133 .then(response => response.text()) 134 .then(data => { 135 var parser = new DOMParser() 136 var doc = parser.parseFromString(data, 'text/html') 137 138 container.innerText = '' 139 container.appendChild(doc.querySelector('.title')) 140 container.appendChild(doc.querySelector('.body')) 141 142 const popoverTriggerList = container.querySelectorAll('[data-bs-toggle="popover"]') 143 const popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl)) 144 }) 145 146 } 147 148 inModal(ev) { 149 ev.preventDefault() 150 const context = this 151 fetch(ev.currentTarget.getAttribute('href'), { 152 method: 'GET', 153 mode: 'same-origin', 154 cache: 'no-cache' 155 }) 156 .then(response => response.text()) 157 .then(data => { 158 var parser = new DOMParser() 159 var doc = parser.parseFromString(data, 'text/html') 160 161 context.modal._element.querySelector('.modal-title').innerText = '' 162 context.modal._element.querySelector('.modal-title').appendChild( 163 doc.querySelector(ev.sourceTitle ?? '.title') ?? document.createElement('div') 164 ) 165 166 context.modal._element.querySelector('.modal-body').innerText = '' 167 context.modal._element.querySelector('.modal-body').appendChild( 168 doc.querySelector(ev.sourceBody ?? '.body') ?? document.createElement('div') 169 ) 170 171 context.openModal(context.modal._element) 172 }) 173 174 } 175 176 inOffcanvas(ev) { 177 ev.preventDefault() 178 const context = this 179 fetch(ev.currentTarget.getAttribute('href'), { 180 method: 'GET', 181 mode: 'same-origin', 182 cache: 'no-cache' 183 }) 184 .then(response => response.text()) 185 .then(data => { 186 var parser = new DOMParser() 187 var doc = parser.parseFromString(data, 'text/html') 188 189 context.offcanvas._element.querySelector('.offcanvas-title').innerText = '' 190 context.offcanvas._element.querySelector('.offcanvas-title').appendChild( 191 doc.querySelector(ev.sourceTitle ?? '.title') ?? document.createElement('div') 192 ) 193 194 context.offcanvas._element.querySelector('.offcanvas-body').innerText = '' 195 context.offcanvas._element.querySelector('.offcanvas-body').appendChild( 196 doc.querySelector(ev.sourceBody ?? '.body') ?? document.createElement('div') 197 ) 198 199 context.openOffCanvas(context.offcanvas._element) 200 }) 201 202 } 203 204 openOffCanvas() { 205 this.offcanvas.show() 206 207 const popoverTriggerList = this.offcanvas._element.querySelectorAll('[data-bs-toggle="popover"]') 208 const popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl)) 209 } 210 211 openModal() { 212 this.modal.show() 213 214 const popoverTriggerList = this.offcanvas._element.querySelectorAll('[data-bs-toggle="popover"]') 215 const popoverList = [...popoverTriggerList].map(popoverTriggerEl => new bootstrap.Popover(popoverTriggerEl)) 216 } 217 218 confirm(ev) { 219 const ctx = this 220 const oc_title = this.offcanvasTarget.querySelector('.offcanvas-title') 221 const oc_body = this.offcanvasTarget.querySelector('.offcanvas-body') 222 223 ev.preventDefault() 224 225 oc_title.innerHTML = '<span class="lead">' + ev.currentTarget.getAttribute('data-confirm-title') + '</span>' 226 oc_body.innerText = ev.currentTarget.getAttribute('data-confirm-body') 227 228 var butg = document.createElement('div') 229 butg.classList.add('btn-group', 'd-flex', 'mt-3') 230 231 var btn_y = document.createElement('a') 232 btn_y.setAttribute('href', ev.currentTarget.getAttribute('href')) 233 btn_y.innerHTML = '<i class="bi bi-exclamation-triangle"></i> SÃ' 234 btn_y.classList.add('btn', 'btn-danger') 235 236 var btn_n = document.createElement('a') 237 btn_n.textContent = 'No' 238 btn_n.classList.add('btn', 'btn-outline-secondary') 239 btn_n.addEventListener('click', () => { 240 ctx.offcanvas.hide() 241 }) 242 243 butg.appendChild(btn_y) 244 butg.appendChild(btn_n) 245 246 oc_body.appendChild(butg) 247 248 this.offcanvas.show() 249 } 250 251 confirmForm(ev) { 252 const ctx = this 253 const oc_title = this.offcanvasTarget.querySelector('.offcanvas-title') 254 const oc_body = this.offcanvasTarget.querySelector('.offcanvas-body') 255 256 ev.preventDefault() 257 258 oc_title.innerHTML = '<span class="lead">' + ev.currentTarget.getAttribute('data-confirm-title') + '</span>' 259 oc_body.innerText = ev.currentTarget.getAttribute('data-confirm-body') 260 261 var butg = document.createElement('div') 262 butg.classList.add('btn-group', 'd-flex', 'mt-3') 263 264 var btn_y = document.createElement('a') 265 btn_y.setAttribute('href', '#') 266 btn_y.innerHTML = '<i class="bi bi-exclamation-triangle"></i> SÃ' 267 btn_y.classList.add('btn', 'btn-danger') 268 btn_y.addEventListener('click', () => { 269 ctx.offcanvas.hide() 270 ev.target.submit() 271 }) 272 273 var btn_n = document.createElement('a') 274 btn_n.textContent = 'No' 275 btn_n.classList.add('btn', 'btn-outline-secondary') 276 btn_n.addEventListener('click', () => { 277 ctx.offcanvas.hide() 278 }) 279 280 butg.appendChild(btn_y) 281 butg.appendChild(btn_n) 282 283 oc_body.appendChild(butg) 284 285 this.offcanvas.show() 286 } 287 288 prompt(ev) { 289 ev.preventDefault() 290 291 const ctx = this 292 const oc_title = this.offcanvasTarget.querySelector('.offcanvas-title') 293 const oc_body = this.offcanvasTarget.querySelector('.offcanvas-body') 294 const t = ev.currentTarget 295 const required = (t.getAttribute('data-prompt-required') ?? 'required') == 'required' 296 const w_attr = 'id="confidare--ui--ui--prompt--obs" name="obs" class="form-control"' 297 298 var widget; 299 switch (t.getAttribute('data-prompt-widget')) { 300 case 'input': 301 case 'text': 302 widget = `<input ${w_attr} ${required? 'required': ''}>` 303 break 304 case 'pin': 305 widget = `<input type="password" pattern="[0-9]*" autocomplete="one-time-code" pattern="[0-9]{4}" maxlength="4" inputmode="numeric" ${w_attr} ${required? 'required': ''}>` 306 break 307 default: 308 widget = `<textarea ${w_attr} ${required? 'required': ''}></textarea>` 309 break 310 } 311 312 oc_title.innerHTML = '<span class="lead">' + t.getAttribute('data-prompt-title') + '</span>' 313 oc_body.innerText = t.getAttribute('data-prompt-body') 314 oc_body.insertAdjacentHTML('beforeend', ` 315 <form action="${t.getAttribute('href')}" method="${t.getAttribute('data-prompt-method') ?? 'GET'}"> 316 <label for="confidare--ui--ui--prompt--obs" class="form-label mt-3"> 317 ${t.getAttribute('data-prompt-label')} 318 ${required? '': '<small>(opcional)</small>'} 319 </label> 320 ${widget} 321 <div class="btn-group d-flex mt-3"> 322 <button type="submit" class="btn-primary btn">Aceptar</button> 323 <a href="#" class="btn btn-outline-secondary">Cancelar</a> 324 </div> 325 </form> 326 `) 327 328 oc_body.querySelector('a').addEventListener('click', () => { 329 ctx.offcanvas.hide() 330 }) 331 332 this.offcanvas.show() 333 334 this.offcanvas._element.addEventListener( 335 'shown.bs.offcanvas', 336 ev => { 337 ev.target.querySelector('*[name="obs"]').focus(); 338 } 339 ); 340 } 341 342 filter(ev) { 343 sessionStorage.setItem( 344 this.quickSearchSessionKey(ev.currentTarget.getAttribute('data-filter-selector')), 345 ev.currentTarget.value, 346 ); 347 348 var resultados = 0; 349 350 document.querySelectorAll( 351 ev 352 .currentTarget 353 .getAttribute('data-filter-selector') 354 ).forEach((elem) => { 355 var selector; 356 var search_in; 357 358 if ((selector = ev.currentTarget.getAttribute('data-filter-text')) != null) { 359 search_in = elem.querySelector(selector).textContent; 360 } else { 361 search_in = elem.textContent; 362 } 363 364 if ( 365 ev.currentTarget.value == '' || 366 this.normalize(search_in).includes( 367 this.normalize(ev.currentTarget.value) 368 ) 369 ) { 370 elem.style.display = ''; 371 resultados++; 372 } else { 373 elem.style.display = 'none'; 374 } 375 }) 376 377 ev.currentTarget.classList.remove('border-info'); 378 ev.currentTarget.classList.remove('border-danger'); 379 380 if (ev.currentTarget.value != '') { 381 if (resultados > 0) { 382 ev.currentTarget.classList.add('border-info'); 383 } else { 384 ev.currentTarget.classList.add('border-danger'); 385 } 386 } 387 388 document.querySelector(ev.currentTarget.getAttribute('data-filter-selector')).parentElement.classList.remove('d-none'); 389 } 390 391 quickSearchSessionKey(dataFilterSelector) { 392 return 'quick_search_' + this.slugify(dataFilterSelector) + '_' + this.slugify(window.location); 393 } 394 395 normalize(string) { 396 return string 397 .toUpperCase() 398 .normalize('NFD') 399 .replace(/\p{Diacritic}/gu, ''); 400 } 401 402 slugify(string) { 403 return string 404 .toString() 405 .normalize('NFKD') 406 .toLowerCase() 407 .trim() 408 .replace(/\s+/g, '_') 409 .replace(/[^\w\_]+/g, '_') 410 .replace(/\_\_+/g, '_'); 411 } 412 413 popup(ev) { 414 const modal_id = ev.params.id; 415 const session_key = 'popup_dismissed_' + ev.params.id; 416 const once = ev.params.once; 417 418 if (!once || !sessionStorage.getItem(session_key)) { 419 const popup = new bootstrap.Modal(document.getElementById(modal_id), {}) 420 popup.show() 421 422 sessionStorage.setItem(session_key, 1); 423 } 424 } 425}
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.