import 'core-js/stable' import 'regenerator-runtime/runtime' 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', 'unselect', 'top', 'bottom', 'direction', 'counter' ] connect () { // Deseleccionar this.unselectTarget.addEventListener('click', event => { event.preventDefault() event.stopPropagation() for (const r of Object.values(this.selected_rows)) { r.row.querySelector('[data-reorder-handler]').click() } }) // Enviar arriba de todo this.topTarget.addEventListener('click', 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() }) // Enviar al final this.bottomTarget.addEventListener('click', 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() }) this.rowTargets.forEach(row => { // 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. row.querySelector('[data-reorder-handler]').addEventListener('change', event => { if (event.target.checked) { this.selected_rows[row.id] = { row, order: this.rowTargets.indexOf(row) } } else { delete this.selected_rows[row.id] } this.counter() }) }) this.directionTargets.forEach(dir => { dir.addEventListener('click', event => { event.preventDefault() event.stopPropagation() if (this.empty) return this.move(dir.dataset.direction) }) }) // 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) } /* * 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 }) } sorted_rows () { return Object.values(this.selected_rows).sort((a,b) => a.order - b.order) } 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() 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 rows[0].row.scrollIntoViewIfNeeded() } counter () { this.counterTarget.innerText = Object.keys(this.selected_rows).length } }