feat: manual sorting of additional services
This commit is contained in:
@@ -0,0 +1,90 @@
|
||||
import { Controller } from '@hotwired/stimulus'
|
||||
import Sortable from 'sortablejs'
|
||||
|
||||
export default class extends Controller {
|
||||
|
||||
static targets = [ 'list', 'item', 'handle', 'positionField', 'positionLabel', 'status' ]
|
||||
|
||||
static values = {
|
||||
url: String,
|
||||
}
|
||||
|
||||
connect() {
|
||||
this.abortController = null
|
||||
|
||||
// The sortable container may be nested (e.g. a tbody) when the controller element also
|
||||
// has to wrap elements that must not be dragged, like the status message.
|
||||
new Sortable(this.hasListTarget ? this.listTarget : this.element, {
|
||||
draggable: '[data-sortable-target="item"]',
|
||||
handle: '[data-sortable-target="handle"]',
|
||||
sort: true,
|
||||
fallbackOnBody: true,
|
||||
onEnd: e => {
|
||||
this.updateOrdering()
|
||||
this.persist()
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
disconnect() {
|
||||
this.abortController?.abort()
|
||||
}
|
||||
|
||||
updateOrdering() {
|
||||
this.positionFieldTargets.forEach((field, index) => {
|
||||
field.value = index + 1
|
||||
})
|
||||
this.positionLabelTargets.forEach((label, index) => {
|
||||
label.innerText = index + 1
|
||||
})
|
||||
}
|
||||
|
||||
/**
|
||||
* Sends the new order to the server. Only active when a url value is present, so the
|
||||
* controller stays usable for plain form ordering.
|
||||
*/
|
||||
persist() {
|
||||
if (!this.hasUrlValue || this.urlValue === '') {
|
||||
return
|
||||
}
|
||||
|
||||
// A drag while the previous request is still running makes that request obsolete.
|
||||
this.abortController?.abort()
|
||||
this.abortController = new AbortController()
|
||||
|
||||
const ids = this.itemTargets
|
||||
.map(item => Number(item.dataset.sortableId))
|
||||
.filter(id => Number.isInteger(id) && id > 0)
|
||||
|
||||
this.setStatus('speichert …')
|
||||
|
||||
fetch(this.urlValue, {
|
||||
method: 'POST',
|
||||
credentials: 'include',
|
||||
signal: this.abortController.signal,
|
||||
headers: {
|
||||
'Content-Type': 'application/json',
|
||||
},
|
||||
body: JSON.stringify({ ids }),
|
||||
})
|
||||
.then(response => {
|
||||
if (!response.ok) {
|
||||
throw new Error(`Unexpected response ${response.status}`)
|
||||
}
|
||||
this.setStatus('Reihenfolge gespeichert')
|
||||
})
|
||||
.catch(error => {
|
||||
if (error.name === 'AbortError') {
|
||||
return
|
||||
}
|
||||
console.error('Failed to persist ordering', error)
|
||||
this.setStatus('Speichern fehlgeschlagen')
|
||||
})
|
||||
}
|
||||
|
||||
setStatus(message) {
|
||||
this.statusTargets.forEach(status => {
|
||||
status.innerText = message
|
||||
})
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user