today_ai_weather/app/javascript/controllers/search_controller.js
songtianlun 80c2f9a1df feat: add loading spinner and search updates
- 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.
2025-02-12 14:47:30 +08:00

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')
}
}
}