PageSourceSearch

https://sifrcrm.up.railway.app/assets/controllers/global_search_co…896a4159e82eaf7db80f41a9507089eb8674acc8.js

js sifrcrm.up.railway.app collected 2026-10-03 12:15:44 UTC 2,814 bytes, 119 lines download raw bytes

1import { Controller } from "@hotwired/stimulus"
2
3export default class extends Controller {
4  static targets = ["input", "dropdown", "spinner", "clear"]
5  static values = {
6    minChars: Number,
7    url: String
8  }
9
10  connect() {
11    this.minCharsValue = this.minCharsValue || 2
12    this.urlValue = this.urlValue || "/searches/search"
13    this.debounceTimer = null
14    this.lastQuery = ""
15    
16    // Handle clicks outside to close dropdown
17    this.outsideClickHandler = this.handleOutsideClick.bind(this)
18    document.addEventListener('click', this.outsideClickHandler)
19  }
20  
21  disconnect() {
22    document.removeEventListener('click', this.outsideClickHandler)
23  }
24
25  search() {
26    const query = this.inputTarget.value.trim()
27    
28    // Clear button visibility
29    this.toggleClearButton(query.length > 0)
30    
31    // Don't search if too short or unchanged
32    if (query.length < this.minCharsValue) {
33      this.hideDropdown()
34      return
35    }
36    
37    if (query === this.lastQuery) return
38    
39    this.lastQuery = query
40    this.showSpinner()
41    
42    clearTimeout(this.debounceTimer)
43    this.debounceTimer = setTimeout(() => {
44      this.fetchSearchResults(query)
45    }, 300)
46  }
47  
48  async fetchSearchResults(query) {
49    try {
50      const url = `${this.urlValue}?query=${encodeURIComponent(query)}`
51      const response = await fetch(url, {
52        headers: {
53          'Accept': 'text/vnd.turbo-stream.html',
54          'X-Requested-With': 'XMLHttpRequest'
55        }
56      })
57      
58      if (response.ok) {
59        const html = await response.text()
60        this.dropdownTarget.innerHTML = html
61        this.showDropdown()
62      }
63    } catch (error) {
64      console.error("Search error:", error)
65    } finally {
66      this.hideSpinner()
67    }
68  }
69  
70  clear(event) {
71    if (event) event.preventDefault()
72    this.inputTarget.value = ""
73    this.lastQuery = ""
74    this.hideDropdown()
75    this.toggleClearButton(false)
76    this.inputTarget.focus()
77  }
78  
79  showDropdown() {
80    if (this.hasDropdownTarget) {
81      this.dropdownTarget.classList.remove("hidden")
82    }
83  }
84  
85  hideDropdown() {
86    if (this.hasDropdownTarget) {
87      this.dropdownTarget.classList.add("hidden")
88    }
89  }
90  
91  showSpinner() {
92    if (this.hasSpinnerTarget) {
93      this.spinnerTarget.classList.remove("hidden")
94    }
95  }
96  
97  hideSpinner() {
98    if (this.hasSpinnerTarget) {
99      this.spinnerTarget.classList.add("hidden")
100    }
101  }
102  
103  toggleClearButton(show) {
104    if (this.hasClearTarget) {
105      if (show) {
106        this.clearTarget.classList.remove("hidden")
107      } else {
108        this.clearTarget.classList.add("hidden")
109      }
110    }
111  }
112  
113  handleOutsideClick(event) {
114    // Don't close if clicking inside component
115    if (this.element.contains(event.target)) return
116    
117    this.hideDropdown()
118  }
119};

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.