edittable.js 4.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171
  1. import $ from '../../shared/jquery.js';
  2. import { AjaxReq, fetchHeaders } from './OMGUtils';
  3. const EDIT_ACTION = 'edit';
  4. const EDITABLE_COLUMN_ATTRIBUTE = 'aria-editable';
  5. const EDIT_ACTIVE_CLASS = 'shadow rounded bg-dark text-light';
  6. const EDIT_ACTIVE_ROW_CLASS = 'bg-info';
  7. const FORM_CONTENT_TYPE = 'application/x-www-form-urlencoded';
  8. const NON_EDITABLE_VALUE = 'false';
  9. let tableSelector = null;
  10. let controllerUrl = '';
  11. /**
  12. * Returns the editable table column names.
  13. *
  14. * @returns {string[]} Column names.
  15. * @sideEffects Reads the document.
  16. */
  17. function getTableFieldNames() {
  18. return $(`${tableSelector} thead th`).map(function () {
  19. return this.innerText;
  20. }).get();
  21. }
  22. /**
  23. * Returns an attribute from a table column definition.
  24. *
  25. * @param {number} columnIndex Column index.
  26. * @param {string} attributeName Attribute name.
  27. * @returns {string|undefined} Attribute value.
  28. * @sideEffects Reads the document.
  29. */
  30. function getColumnAttribute(columnIndex, attributeName) {
  31. return $(`${tableSelector} thead th`).eq(columnIndex).attr(attributeName);
  32. }
  33. /**
  34. * Cancels the edit of a table cell and restores its previous value.
  35. *
  36. * @param {HTMLElement} cell Table cell.
  37. * @returns {void}
  38. * @sideEffects Updates the edited table cell.
  39. */
  40. function cancelEdit(cell) {
  41. cell.innerText = $(cell).data('oldvalue');
  42. $(cell).parent('tr').removeClass(`dt-editing-cell ${EDIT_ACTIVE_ROW_CLASS}`);
  43. $(cell).removeClass(EDIT_ACTIVE_CLASS);
  44. $('body').focus();
  45. cell.contentEditable = 'false';
  46. }
  47. /**
  48. * Shows a request error without discarding diagnostic context.
  49. *
  50. * @param {unknown} error Request failure.
  51. * @returns {void}
  52. * @sideEffects Logs the failure and displays an alert.
  53. */
  54. function showRequestError(error) {
  55. console.error('Editable table request failed.', error);
  56. alert('Unable to save the table changes.');
  57. }
  58. /**
  59. * Runs a table command using form-urlencoded data.
  60. *
  61. * @param {string} action Server action.
  62. * @returns {void}
  63. * @sideEffects Performs a network request and displays an alert.
  64. */
  65. function executeTableAction(action) {
  66. AjaxReq(controllerUrl, {
  67. headers: { ...fetchHeaders, 'Content-Type': FORM_CONTENT_TYPE },
  68. method: 'POST',
  69. body: `action=${action}`,
  70. }).fetchPromise()
  71. .then(response => alert(`${response.code} - ${response.err}`))
  72. .catch(showRequestError);
  73. }
  74. /**
  75. * Sends the currently edited row to the server.
  76. *
  77. * @param {HTMLElement} cell Edited cell.
  78. * @returns {void}
  79. * @sideEffects Performs a network request and refreshes the DataTable.
  80. */
  81. function saveEdit(cell) {
  82. const formData = new FormData();
  83. const fields = getTableFieldNames();
  84. $('.dt-editing-cell td').each(function (index) {
  85. formData.append(fields[index], this.innerText);
  86. });
  87. formData.append('action', EDIT_ACTION);
  88. AjaxReq(controllerUrl, { method: 'POST', body: formData }).fetchPromise()
  89. .then(response => {
  90. $(tableSelector).DataTable().ajax.reload();
  91. alert(`${response.code} - ${response.err}`);
  92. })
  93. .catch(showRequestError)
  94. .finally(() => {
  95. cell.contentEditable = 'false';
  96. });
  97. }
  98. /**
  99. * Initializes an editable DataTable.
  100. *
  101. * @param {string} tableReference DataTable selector.
  102. * @param {string} controllerUrlReference Server endpoint.
  103. * @returns {void}
  104. * @sideEffects Initializes the table and registers DOM event handlers.
  105. */
  106. function initializeEditableTable(tableReference, controllerUrlReference) {
  107. tableSelector = tableReference;
  108. controllerUrl = controllerUrlReference;
  109. const tableBody = $(`${tableSelector} tbody`);
  110. $(tableSelector).DataTable({
  111. ajax: {
  112. url: controllerUrl,
  113. type: 'POST',
  114. data: { action: 'getData' },
  115. dataSrc(data) {
  116. $(tableSelector).closest('.dt-container').parent().removeClass('d-none');
  117. return data;
  118. },
  119. },
  120. lengthChange: false,
  121. dom: 'frtBip',
  122. order: [[0, 'asc']],
  123. buttons: [
  124. { text: 'Completa lista', action: () => executeTableAction('closelist') },
  125. { text: 'Annulla lista', action: () => executeTableAction('cancellist') },
  126. ],
  127. });
  128. tableBody.on('click', 'td', function () {
  129. if (getColumnAttribute(this.cellIndex, EDITABLE_COLUMN_ATTRIBUTE) === NON_EDITABLE_VALUE) {
  130. return;
  131. }
  132. this.contentEditable = 'true';
  133. $(this).data('oldvalue', this.innerText);
  134. $(this).parent('tr').addClass(`dt-editing-cell ${EDIT_ACTIVE_ROW_CLASS}`);
  135. $(this).addClass(EDIT_ACTIVE_CLASS);
  136. this.focus();
  137. });
  138. tableBody.on('blur', 'td', function () {
  139. cancelEdit(this);
  140. });
  141. tableBody.on('keydown', 'td', function (event) {
  142. if (event.code === 'Escape' || event.code === 'Tab') {
  143. cancelEdit(this);
  144. return false;
  145. }
  146. if (event.code === 'Enter') {
  147. $(this).data('oldvalue', this.innerText);
  148. saveEdit(this);
  149. return false;
  150. }
  151. });
  152. }
  153. export default initializeEditableTable;