- Implement loading state in the search input with spinner. - Optimize the search request to handle pending requests and cancels. - Add dynamic response handling for Turbo frames to load search results. - Create a new partial for city search results. - Update the cities controller to support Turbo stream responses. These enhancements improve user experience during searches by showing a loading spinner and addressing potential issues with overlapping requests, ensuring that the application remains responsive and functional when fetching city search results.
72 lines
2.3 KiB
JavaScript
72 lines
2.3 KiB
JavaScript
// app/javascript/controllers/search_controller.js
|
|
import { Controller } from "@hotwired/stimulus"
|
|
|
|
export default class extends Controller {
|
|
static targets = ["input", "clearButton", "spinner", "statusIcon"]
|
|
|
|
connect() {
|
|
this.pendingRequest = null
|
|
}
|
|
|
|
submit() {
|
|
clearTimeout(this.timeout)
|
|
this.timeout = setTimeout(() => {
|
|
// 如果有待处理的请求,则中止它
|
|
if (this.pendingRequest) {
|
|
this.pendingRequest.abort()
|
|
}
|
|
|
|
const form = this.element
|
|
const searchInput = this.inputTarget
|
|
const encodedValue = encodeURIComponent(searchInput.value)
|
|
|
|
// 更新 URL
|
|
const url = new URL(window.location)
|
|
url.searchParams.set('query', encodedValue)
|
|
window.history.pushState({}, '', url)
|
|
|
|
// 显示加载状态
|
|
this.showLoadingState()
|
|
|
|
// 发送请求
|
|
this.pendingRequest = new AbortController()
|
|
fetch(form.action + '?' + new URLSearchParams(new FormData(form)), {
|
|
headers: {
|
|
'Accept': 'text/vnd.turbo-stream.html',
|
|
'Turbo-Frame': 'cities_results'
|
|
},
|
|
signal: this.pendingRequest.signal
|
|
})
|
|
.then(response => response.text())
|
|
.then(html => {
|
|
Turbo.renderStreamMessage(html)
|
|
})
|
|
.catch(error => {
|
|
if (error.name === 'AbortError') return
|
|
console.error('Search error:', error)
|
|
})
|
|
.finally(() => {
|
|
this.hideLoadingState()
|
|
this.pendingRequest = null
|
|
})
|
|
}, 300)
|
|
}
|
|
|
|
showLoadingState() {
|
|
if (this.hasClearButtonTarget) {
|
|
this.clearButtonTarget.classList.add('hidden')
|
|
}
|
|
if (this.hasSpinnerTarget) {
|
|
this.spinnerTarget.classList.remove('hidden')
|
|
}
|
|
}
|
|
|
|
hideLoadingState() {
|
|
if (this.hasClearButtonTarget) {
|
|
this.clearButtonTarget.classList.remove('hidden')
|
|
}
|
|
if (this.hasSpinnerTarget) {
|
|
this.spinnerTarget.classList.add('hidden')
|
|
}
|
|
}
|
|
} |