import $ from '../../shared/jquery.js'; import { AjaxReq, fetchHeaders } from './OMGUtils'; const EDIT_ACTION = 'edit'; const EDITABLE_COLUMN_ATTRIBUTE = 'aria-editable'; const EDIT_ACTIVE_CLASS = 'shadow rounded bg-dark text-light'; const EDIT_ACTIVE_ROW_CLASS = 'bg-info'; const FORM_CONTENT_TYPE = 'application/x-www-form-urlencoded'; const NON_EDITABLE_VALUE = 'false'; let tableSelector = null; let controllerUrl = ''; /** * Returns the editable table column names. * * @returns {string[]} Column names. * @sideEffects Reads the document. */ function getTableFieldNames() { return $(`${tableSelector} thead th`).map(function () { return this.innerText; }).get(); } /** * Returns an attribute from a table column definition. * * @param {number} columnIndex Column index. * @param {string} attributeName Attribute name. * @returns {string|undefined} Attribute value. * @sideEffects Reads the document. */ function getColumnAttribute(columnIndex, attributeName) { return $(`${tableSelector} thead th`).eq(columnIndex).attr(attributeName); } /** * Cancels the edit of a table cell and restores its previous value. * * @param {HTMLElement} cell Table cell. * @returns {void} * @sideEffects Updates the edited table cell. */ function cancelEdit(cell) { cell.innerText = $(cell).data('oldvalue'); $(cell).parent('tr').removeClass(`dt-editing-cell ${EDIT_ACTIVE_ROW_CLASS}`); $(cell).removeClass(EDIT_ACTIVE_CLASS); $('body').focus(); cell.contentEditable = 'false'; } /** * Shows a request error without discarding diagnostic context. * * @param {unknown} error Request failure. * @returns {void} * @sideEffects Logs the failure and displays an alert. */ function showRequestError(error) { console.error('Editable table request failed.', error); alert('Unable to save the table changes.'); } /** * Runs a table command using form-urlencoded data. * * @param {string} action Server action. * @returns {void} * @sideEffects Performs a network request and displays an alert. */ function executeTableAction(action) { AjaxReq(controllerUrl, { headers: { ...fetchHeaders, 'Content-Type': FORM_CONTENT_TYPE }, method: 'POST', body: `action=${action}`, }).fetchPromise() .then(response => alert(`${response.code} - ${response.err}`)) .catch(showRequestError); } /** * Sends the currently edited row to the server. * * @param {HTMLElement} cell Edited cell. * @returns {void} * @sideEffects Performs a network request and refreshes the DataTable. */ function saveEdit(cell) { const formData = new FormData(); const fields = getTableFieldNames(); $('.dt-editing-cell td').each(function (index) { formData.append(fields[index], this.innerText); }); formData.append('action', EDIT_ACTION); AjaxReq(controllerUrl, { method: 'POST', body: formData }).fetchPromise() .then(response => { $(tableSelector).DataTable().ajax.reload(); alert(`${response.code} - ${response.err}`); }) .catch(showRequestError) .finally(() => { cell.contentEditable = 'false'; }); } /** * Initializes an editable DataTable. * * @param {string} tableReference DataTable selector. * @param {string} controllerUrlReference Server endpoint. * @returns {void} * @sideEffects Initializes the table and registers DOM event handlers. */ function initializeEditableTable(tableReference, controllerUrlReference) { tableSelector = tableReference; controllerUrl = controllerUrlReference; const tableBody = $(`${tableSelector} tbody`); $(tableSelector).DataTable({ ajax: { url: controllerUrl, type: 'POST', data: { action: 'getData' }, dataSrc(data) { $(tableSelector).closest('.dt-container').parent().removeClass('d-none'); return data; }, }, lengthChange: false, dom: 'frtBip', order: [[0, 'asc']], buttons: [ { text: 'Completa lista', action: () => executeTableAction('closelist') }, { text: 'Annulla lista', action: () => executeTableAction('cancellist') }, ], }); tableBody.on('click', 'td', function () { if (getColumnAttribute(this.cellIndex, EDITABLE_COLUMN_ATTRIBUTE) === NON_EDITABLE_VALUE) { return; } this.contentEditable = 'true'; $(this).data('oldvalue', this.innerText); $(this).parent('tr').addClass(`dt-editing-cell ${EDIT_ACTIVE_ROW_CLASS}`); $(this).addClass(EDIT_ACTIVE_CLASS); this.focus(); }); tableBody.on('blur', 'td', function () { cancelEdit(this); }); tableBody.on('keydown', 'td', function (event) { if (event.code === 'Escape' || event.code === 'Tab') { cancelEdit(this); return false; } if (event.code === 'Enter') { $(this).data('oldvalue', this.innerText); saveEdit(this); return false; } }); } export default initializeEditableTable;