sutty/app/javascript/controllers/reorder_controller.js

215 lines
4.7 KiB
JavaScript
Raw Normal View History

2020-11-19 20:44:13 +00:00
import { Controller } from 'stimulus'
/*
* Permite reordenar las filas de una tabla.
*
* Cada fila tiene un selector que permite decidir si la fila se
* mantiene en su lugar o se mueve al presionar las teclas de subir y
* bajar.
*
* Se pueden mover varias juntas.
*
* El controlador está en la tabla y cada fila es un objetivo. Dentro
* de cada fila tiene que haber un input[type=checkbox] que determina si
* está seleccionada o no y un input[type=hidden] que contiene la
* posición actual que luego será guardada.
*
* La tabla tiene que estar rodeada de un formulario para poder enviar
* los datos.
*
* El objetivo es poder mover filas en tablas de miles de elementos.
*/
export default class extends Controller {
static targets = [ 'row', 'counter' ]
2020-11-19 20:44:13 +00:00
connect () {
// Lo asociamos al documento porque en la tabla se pierde el foco
// luego del primer evento.
document.addEventListener('keydown', event => {
if (!this.codes.includes(event.keyCode)) return
if (this.empty) return
event.preventDefault()
this.move(event.keyCode === 38 ? 'up' : 'down')
})
}
get selected_rows () {
if (!this._selected_rows) this._selected_rows = {}
return this._selected_rows
}
// Arriba, abajo
get codes () {
if (!this._codes) this._codes = [ 38, 40 ]
return this._codes
}
get empty () {
return (Object.keys(this.selected_rows).length === 0)
}
/*
* Las filas siempre ordenadas
*/
get sorted_rows () {
return Object.values(this.selected_rows).sort((a,b) => a.order - b.order)
}
2020-11-19 20:44:13 +00:00
/*
* Aplica el nuevo orden en las filas y sus campos
*/
reorder () {
for (const r of Object.values(this.selected_rows)) {
this.selected_rows[r.row.id].order = this.rowTargets.indexOf(r.row)
}
const length = this.rowTargets.length
this.rowTargets.forEach((row, i) => {
row.querySelector('[data-reorder]').value = length - i
})
}
move (direction) {
if (this.empty) return
const up = direction === 'up'
const down = !up
const direction_sibling = up ? 'previousElementSibling' : 'nextElementSibling'
// Los movemos en orden
const rows = this.sorted_rows
2020-11-19 20:44:13 +00:00
if (down) rows.reverse()
for (const r of rows) {
const row = r.row
const sibling = row[direction_sibling]
// Estamos en el tope?
if (!sibling || sibling.tagName !== row.tagName) continue
if (up) {
row.parentElement.insertBefore(row, sibling)
} else {
row.parentElement.insertBefore(sibling, row)
}
}
// Reacomodamos el orden
this.reorder()
// Mantenemos el primero a la vista
2021-04-07 20:45:27 +00:00
if ("scrollIntoViewIfNeeded" in rows[0].row) {
rows[0].row.scrollIntoViewIfNeeded()
} else {
rows[0].row.scrollIntoView()
}
2020-11-19 20:44:13 +00:00
}
2020-11-19 20:53:11 +00:00
counter () {
this.counterTarget.innerText = Object.keys(this.selected_rows).length
}
// Deseleccionar todos
unselect (event) {
event.preventDefault()
event.stopPropagation()
for (const r of Object.values(this.selected_rows)) {
r.row.querySelector('[data-action="reorder#select"]').click()
}
}
// Enviar arriba de todo
top (event) {
event.preventDefault()
event.stopPropagation()
if (this.empty) return
const rows = this.sorted_rows
const first = rows[0].row.parentElement.firstElementChild
for (const r of rows) {
const row = r.row
if (row === first) continue
row.parentElement.insertBefore(row, first)
}
// Reacomodamos el orden
this.reorder()
// Mantenemos el primero a la vista
rows[0].row.scrollIntoViewIfNeeded()
}
bottom (event) {
event.preventDefault()
event.stopPropagation()
if (this.empty) return
const rows = this.sorted_rows
for (const r of rows) {
const row = r.row
row.parentElement.appendChild(row)
}
// Reacomodamos el orden
this.reorder()
// Mantenemos el primero a la vista
rows[0].row.scrollIntoViewIfNeeded()
}
/*
* Al cambiar los inputs, mantener la lista de filas actualizadas.
* Necesitamos saber la posición para poder mover las filas en orden
* en lugar del orden en que fueron seleccionadas.
*/
select (event) {
const row = event.target.closest('tr')
if (event.target.checked) {
this.selected_rows[row.id] = {
row,
order: this.rowTargets.indexOf(row)
}
} else {
delete this.selected_rows[row.id]
}
this.counter()
}
/*
* Mover hacia arriba
*/
up (event) {
event.preventDefault()
event.stopPropagation()
if (this.empty) return
this.move('up')
}
/*
* Mover hacia abajo
*/
down (event) {
event.preventDefault()
event.stopPropagation()
if (this.empty) return
this.move('down')
}
2020-11-19 20:44:13 +00:00
}