@iamproperty/components 3.4.6 → 3.4.7

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (149) hide show
  1. package/assets/css/components/accordion.css.map +1 -1
  2. package/assets/css/components/applied-filters.css +1 -0
  3. package/assets/css/components/applied-filters.css.map +1 -0
  4. package/assets/css/components/card.css +1 -1
  5. package/assets/css/components/card.css.map +1 -1
  6. package/assets/css/components/charts.css +1 -1
  7. package/assets/css/components/charts.css.map +1 -1
  8. package/assets/css/components/container.css +1 -1
  9. package/assets/css/components/container.css.map +1 -1
  10. package/assets/css/components/dialog.css +1 -0
  11. package/assets/css/components/dialog.css.map +1 -0
  12. package/assets/css/components/forms.css +1 -1
  13. package/assets/css/components/forms.css.map +1 -1
  14. package/assets/css/components/lists.css +1 -1
  15. package/assets/css/components/lists.css.map +1 -1
  16. package/assets/css/components/nav.css +1 -1
  17. package/assets/css/components/nav.css.map +1 -1
  18. package/assets/css/components/pagination.css +1 -0
  19. package/assets/css/components/pagination.css.map +1 -0
  20. package/assets/css/components/table.css +1 -0
  21. package/assets/css/components/table.css.map +1 -0
  22. package/assets/css/core.min.css +1 -1
  23. package/assets/css/core.min.css.map +1 -1
  24. package/assets/css/style.min.css +1 -1
  25. package/assets/css/style.min.css.map +1 -1
  26. package/assets/js/bundle.js +18 -11
  27. package/assets/js/components/accordion/accordion.component.js +6 -0
  28. package/assets/js/components/accordion/accordion.component.min.js +3 -3
  29. package/assets/js/components/accordion/accordion.component.min.js.map +1 -1
  30. package/assets/js/components/applied-filters/applied-filters.component.js +26 -0
  31. package/assets/js/components/card/card.component.js +91 -0
  32. package/assets/js/components/card/card.component.min.js +21 -0
  33. package/assets/js/components/card/card.component.min.js.map +1 -0
  34. package/assets/js/components/filterlist/filterlist.component.js +49 -0
  35. package/assets/js/components/filterlist/filterlist.component.min.js +23 -0
  36. package/assets/js/components/filterlist/filterlist.component.min.js.map +1 -0
  37. package/assets/js/components/header/header.component.js +6 -0
  38. package/assets/js/components/header/header.component.min.js +2 -2
  39. package/assets/js/components/header/header.component.min.js.map +1 -1
  40. package/assets/js/components/pagination/pagination.component.js +34 -0
  41. package/assets/js/components/table/table.component.js +104 -0
  42. package/assets/js/components/table/table.component.min.js +24 -0
  43. package/assets/js/components/table/table.component.min.js.map +1 -0
  44. package/assets/js/components/tabs/tabs.component.js +6 -0
  45. package/assets/js/components/tabs/tabs.component.min.js +17 -0
  46. package/assets/js/components/tabs/tabs.component.min.js.map +1 -0
  47. package/assets/js/dynamic.js +7 -18
  48. package/assets/js/dynamic.min.js +2 -53
  49. package/assets/js/dynamic.min.js.map +1 -1
  50. package/assets/js/flat-components.js +27 -9
  51. package/assets/js/modules/applied-filters.js +100 -0
  52. package/assets/js/modules/data-layer.js +45 -0
  53. package/assets/js/modules/filterlist.js +32 -0
  54. package/assets/js/modules/helpers.js +77 -49
  55. package/assets/js/modules/pagination.js +33 -0
  56. package/assets/js/modules/table.js +507 -420
  57. package/assets/js/modules/tabs.js +6 -0
  58. package/assets/js/modules/youtubevideo.js +53 -61
  59. package/assets/js/scripts.bundle.js +77 -62
  60. package/assets/js/scripts.bundle.js.map +1 -1
  61. package/assets/js/scripts.bundle.min.js +2 -2
  62. package/assets/js/scripts.bundle.min.js.map +1 -1
  63. package/assets/js/tests/filterlist.spec.js +22 -0
  64. package/assets/js/tests/pagination.spec.js +15 -0
  65. package/assets/js/tests/table.spec.js +147 -0
  66. package/assets/sass/_components.scss +1 -2
  67. package/assets/sass/_corefiles.scss +5 -4
  68. package/assets/sass/_functions/utilities.scss +16 -0
  69. package/assets/sass/_functions/variables.scss +32 -18
  70. package/assets/sass/_tests/colours.spec.scss +1 -1
  71. package/assets/sass/components/applied-filters.scss +65 -0
  72. package/assets/sass/components/card.scss +177 -233
  73. package/assets/sass/components/charts.scss +4 -0
  74. package/assets/sass/components/container.scss +7 -2
  75. package/assets/sass/components/dialog.scss +202 -0
  76. package/assets/sass/components/forms.scss +37 -5
  77. package/assets/sass/components/lists.scss +15 -0
  78. package/assets/sass/components/nav.scss +5 -1
  79. package/assets/sass/components/pagination.scss +140 -0
  80. package/assets/sass/components/table.scss +419 -0
  81. package/assets/sass/foundations/icons.scss +1 -1
  82. package/assets/sass/{components → foundations}/links.scss +26 -0
  83. package/assets/sass/foundations/reboot.scss +19 -13
  84. package/assets/svg/illustrations/table.svg +165 -0
  85. package/assets/ts/bundle.ts +23 -12
  86. package/assets/ts/components/accordion/accordion.component.ts +7 -0
  87. package/assets/ts/components/applied-filters/README.md +5 -0
  88. package/assets/ts/components/applied-filters/applied-filters.component.ts +33 -0
  89. package/assets/ts/components/card/README.md +22 -0
  90. package/assets/ts/components/card/card.component.ts +117 -0
  91. package/assets/ts/components/filterlist/README.md +17 -0
  92. package/assets/ts/components/filterlist/filterlist.component.ts +60 -0
  93. package/assets/ts/components/header/header.component.ts +8 -0
  94. package/assets/ts/components/pagination/README.md +11 -0
  95. package/assets/ts/components/pagination/pagination.component.ts +45 -0
  96. package/assets/ts/components/table/README.md +23 -0
  97. package/assets/ts/components/table/table.component.ts +128 -0
  98. package/assets/ts/components/tabs/tabs.component.ts +7 -0
  99. package/assets/ts/dynamic.ts +12 -19
  100. package/assets/ts/flat-components.ts +37 -9
  101. package/assets/ts/modules/applied-filters.ts +146 -0
  102. package/assets/ts/modules/data-layer.ts +58 -0
  103. package/assets/ts/modules/filterlist.ts +46 -0
  104. package/assets/ts/modules/helpers.ts +90 -60
  105. package/assets/ts/modules/pagination.ts +44 -0
  106. package/assets/ts/modules/table.ts +598 -433
  107. package/assets/ts/modules/tabs.ts +8 -1
  108. package/assets/ts/modules/youtubevideo.ts +58 -63
  109. package/assets/ts/tests/filterlist.spec.ts +29 -0
  110. package/assets/ts/tests/pagination.spec.ts +21 -0
  111. package/assets/ts/tests/table.spec.ts +191 -0
  112. package/dist/components.es.js +1264 -1296
  113. package/dist/components.umd.js +70 -65
  114. package/dist/style.css +1 -1
  115. package/package.json +7 -5
  116. package/src/components/AppliedFilters/AppliedFilters.vue +20 -0
  117. package/src/components/AppliedFilters/README.md +5 -0
  118. package/src/components/Card/Card.vue +11 -112
  119. package/src/components/Card/README.md +16 -18
  120. package/src/components/Carousel/Carousel.vue +49 -10
  121. package/src/components/Chart/Chart.vue +46 -4
  122. package/src/components/Filterlist/Filterlist.vue +20 -0
  123. package/src/components/Filterlist/README.md +17 -0
  124. package/src/components/Pagination/Pagination.vue +30 -0
  125. package/src/components/Pagination/README.md +11 -0
  126. package/src/components/Table/README.md +29 -44
  127. package/src/components/Table/Table.spec.js +5 -37
  128. package/src/components/Table/Table.vue +16 -91
  129. package/src/foundations/YoutubeVideo/YoutubeVideo.vue +1 -1
  130. package/src/index.js +3 -2
  131. package/assets/css/components/cardDeck.css +0 -1
  132. package/assets/css/components/cardDeck.css.map +0 -1
  133. package/assets/css/components/links.css +0 -1
  134. package/assets/css/components/links.css.map +0 -1
  135. package/assets/css/components/modal.css +0 -1
  136. package/assets/css/components/modal.css.map +0 -1
  137. package/assets/css/components/tables.css +0 -1
  138. package/assets/css/components/tables.css.map +0 -1
  139. package/assets/js/modules/modal.js +0 -69
  140. package/assets/sass/components/cardDeck.scss +0 -108
  141. package/assets/sass/components/modal.scss +0 -136
  142. package/assets/sass/components/tables.scss +0 -291
  143. package/assets/ts/modules/modal.ts +0 -91
  144. package/src/components/CardDeck/CardDeck.spec.js +0 -99
  145. package/src/components/CardDeck/CardDeck.vue +0 -77
  146. package/src/components/CardDeck/README.md +0 -25
  147. package/src/components/Modal/Modal.spec.js +0 -22
  148. package/src/components/Modal/Modal.vue +0 -43
  149. package/src/components/Modal/README.md +0 -20
@@ -1,451 +1,538 @@
1
1
  // @ts-nocheck
2
- import { zeroPad, isNumeric } from "./helpers.js";
3
- function table(tableElement) {
4
- if (typeof tableElement != "object")
5
- return false;
6
- const thead = tableElement.querySelector('thead');
7
- const tbody = tableElement.querySelector('tbody');
8
- const storedData = tbody.cloneNode(true);
9
- const sortedEvent = new Event('sorted');
10
- const filteredEvent = new Event('filtered');
11
- const reorderedEvent = new Event('reordered');
12
- const randID = 'table_' + Math.random().toString(36).substr(2, 9); // Random to make sure IDs created are unique
13
- let draggedRow;
14
- tableElement.setAttribute('id', randID);
15
- // #region Sortable
16
- const sortTable = function (sortBy, sort) {
17
- // Create an array from the table rows, the index created is then used to sort the array
18
- let tableArr = [];
19
- Array.from(tbody.querySelectorAll('tr')).forEach((tableRow, index) => {
20
- let rowIndex = tableRow.querySelector('td[data-label="' + sortBy + '"], th[data-label="' + sortBy + '"]').textContent;
21
- if (isNumeric(rowIndex))
22
- rowIndex = zeroPad(rowIndex, 10);
23
- const dataRow = {
24
- index: rowIndex,
25
- row: tableRow
26
- };
27
- tableArr.push(dataRow);
28
- });
29
- // Sort array
30
- tableArr.sort((a, b) => (a.index > b.index) ? 1 : -1);
31
- // Reverse if descending
32
- if (sort == "descending")
33
- tableArr = tableArr.reverse();
34
- // Create a string to return and populate the tbody
35
- let strTbody = '';
36
- tableArr.forEach((tableRow, index) => {
37
- strTbody += tableRow.row.outerHTML;
38
- });
39
- tbody.innerHTML = strTbody;
40
- // Dispatch the sortable event
41
- tableElement.dispatchEvent(sortedEvent);
42
- };
43
- // Declare event handlers
44
- tableElement.addEventListener('click', function (e) {
45
- for (var target = e.target; target && target != this; target = target.parentNode) {
46
- if (target.matches('[data-sortable]')) {
47
- // Get current sort order
48
- let sort = target.getAttribute('aria-sort') == "ascending" ? "descending" : "ascending";
49
- // unset sort attributes
50
- Array.from(tableElement.querySelectorAll('[data-sortable]')).forEach((col, index) => {
51
- col.setAttribute('aria-sort', 'none');
52
- });
53
- // Set the sort order attribute
54
- target.setAttribute('aria-sort', sort);
55
- // Save the sort options on the table element so that it can be re-sorted later
56
- tableElement.setAttribute('data-sort', sort);
57
- tableElement.setAttribute('data-sortBy', target.textContent);
58
- // Sort the table
59
- sortTable(target.textContent, sort);
60
- Array.from(tableElement.querySelectorAll('tr[draggable]')).forEach((tableRow, index) => {
61
- tableRow.removeAttribute('draggable');
62
- });
63
- break;
64
- }
65
- }
66
- }, false);
67
- // On page load check if the table should be pre-sorted, if so trigger a click
68
- if (tableElement.getAttribute('data-sortBy')) {
69
- let sort = tableElement.getAttribute('data-sort') == "ascending" ? "descending" : "ascending";
70
- Array.from(tableElement.querySelectorAll('[data-sortable]')).forEach((col, index) => {
71
- if (col.textContent == tableElement.getAttribute('data-sortBy')) {
72
- col.setAttribute('aria-sort', sort);
73
- col.click();
74
- }
75
- });
76
- }
77
- // #endregion Sortable
78
- // #region Filters
79
- const createFilterForm = function (count) {
80
- // Create wrapper div
81
- const form = document.createElement("div");
82
- form.classList.add('table__filters');
83
- form.classList.add('row');
84
- form.classList.add('pt-1');
85
- form.classList.add('pb-3');
86
- // Create the filter options array
87
- const filterColumns = Array.from(tableElement.querySelectorAll('th[data-filterable]'));
88
- // Populate a list of searchable terms from the cells of the columns that could be used as a filter
89
- let searchableTerms = {};
90
- filterColumns.forEach((columnHeading, index) => {
91
- Array.from(tableElement.querySelectorAll('td[data-label="' + columnHeading.textContent + '"]')).forEach((label, index) => {
92
- searchableTerms[label.textContent] = label.textContent;
93
- });
94
- });
95
- // Create the form
96
- const filterTitle = filterColumns.length == 1 ? "Filter by " + filterColumns[0].textContent : "Filter"; // Update title if only one filter is chosen
97
- const checkboxClass = filterColumns.length == 1 ? "d-none" : "d-sm-flex"; // Hide controls when only one filter is chosen
98
- form.innerHTML = `<div class="col-sm-6 col-md-4 pb-3">
99
- <div class="form-control__wrapper form-control-inline mb-0">
100
- <label for="${randID}_filter" class="form-label">${filterTitle}:</label>
101
- <input type="search" name="${randID}_filter" id="${randID}_filter" class="form-control form-control-sm" placeholder="" list="${randID}_list" />
102
- </div>
103
- <datalist id="${randID}_list">
104
- ${Object.keys(searchableTerms).map(term => `<option value="${term}"></option>`).join("")}
105
- </datalist>
106
- </div>
107
- <div class="col-md-8 align-items-center pb-3 ${checkboxClass}">
108
- ${`<span class="pe-3 text-nowrap h5 mb-0">Filter by: </span>` + filterColumns.map(column => `<div class="form-check pe-3 mt-0 mb-0"><input class="form-check-input" type="checkbox" id="${randID}_${column.textContent.replace(' ', '_').toLowerCase()}" checked="checked" /><label class="form-check-label text-nowrap" for="${randID}_${column.textContent.replace(' ', '_').toLowerCase()}">${column.textContent}</label></div>`).join("")}
109
- </div>`;
110
- // Add before the actual table
111
- tableElement.prepend(form);
112
- };
113
- const filterTable = function (searchTerm) {
114
- // Create an array of rows that match the search term
115
- let tableArr = [];
116
- Array.from(storedData.querySelectorAll('tr')).forEach((tableRow, index) => {
117
- // We want one long search string per row including each filterable table cell
118
- let rowSearchString = '';
119
- Array.from(tableElement.querySelectorAll('[type="checkbox"]:checked + label')).forEach((label, index) => {
120
- rowSearchString += tableRow.querySelector('td[data-label="' + label.textContent + '"]').textContent + ' | ';
121
- });
122
- // Check if the table row search string contains the search term
123
- if (rowSearchString.indexOf(searchTerm) >= 0) {
124
- const dataRow = { row: tableRow };
125
- tableArr.push(dataRow);
126
- }
127
- });
128
- // Create a string to return and populate the tbody
129
- let strTbody = '';
130
- tableArr.forEach((tableRow, index) => {
131
- strTbody += tableRow.row.outerHTML;
132
- });
133
- tbody.innerHTML = strTbody;
134
- // Dispatch the filter event.
135
- tableElement.dispatchEvent(filteredEvent);
136
- };
137
- const createFilterList = function () {
138
- // Check which options are checked
139
- let filterOptions = [];
140
- Array.from(tableElement.querySelectorAll('[type="checkbox"]:checked + label')).forEach((label, index) => {
141
- filterOptions.push(label.textContent);
142
- });
143
- // Build up the list of searchable terms
144
- let searchableTerms = [];
145
- filterOptions.forEach((option, index) => {
146
- Array.from(tableElement.querySelectorAll('td[data-label="' + option + '"]')).forEach((label, index) => {
147
- searchableTerms[label.textContent] = label.textContent;
148
- });
149
- });
150
- // Rebuild the list
151
- let dataList = tableElement.querySelector('datalist');
152
- dataList.innerHTML = Object.keys(searchableTerms).map(term => `<option value="${term}"></option>`).join("");
153
- };
154
- // On page load check if filters are needed
155
- if (Array.from(tableElement.querySelectorAll('[data-filterable]')).length) {
156
- // Create the filter options
157
- createFilterForm(tableElement, Array.from(tableElement.querySelectorAll('[data-filterable]')).length);
158
- // Add event handlers for the filter options
159
- tableElement.addEventListener('keyup', function (e) {
160
- for (var target = e.target; target && target != this; target = target.parentNode) {
161
- if (target.matches('input[type="search"]')) {
162
- const searchTerm = target.value;
163
- filterTable(searchTerm);
2
+ import { zeroPad, isNumeric, ucfirst } from "./helpers.js";
3
+ import createPaginationButttons from "./pagination.js";
4
+ // Basic functionality needed
5
+ export const addDataAttributes = (table) => {
6
+ const colHeadings = Array.from(table.querySelectorAll('thead th'));
7
+ const colRows = Array.from(table.querySelectorAll('tbody tr'));
8
+ colRows.forEach((row, index) => {
9
+ const cells = Array.from(row.querySelectorAll('th, td'));
10
+ const statuses = ['Low', 'Medium', 'High', 'N/A', 'Pending', 'Verified', 'Incomplete', 'Completed', 'Requires approval'];
11
+ cells.forEach((cell, cellIndex) => {
12
+ const heading = colHeadings[cellIndex];
13
+ if (typeof heading != "undefined") {
14
+ let tempDiv = document.createElement("div");
15
+ tempDiv.innerHTML = heading.innerHTML;
16
+ let headingText = tempDiv.textContent || tempDiv.innerText || "";
17
+ cell.setAttribute('data-label', headingText);
18
+ if (heading.hasAttribute('class'))
19
+ cell.setAttribute('class', heading.getAttribute('class'));
20
+ if (heading.hasAttribute('data-format')) {
21
+ cell.setAttribute('data-format', heading.getAttribute('data-format'));
22
+ cell.innerHTML = formatCell('date', cell.textContent.trim()); //Make sure date format is consistent
164
23
  }
165
- }
166
- });
167
- tableElement.addEventListener('change', function (e) {
168
- for (var target = e.target; target && target != this; target = target.parentNode) {
169
- if (target.matches('input[type="search"]')) {
170
- const searchTerm = target.value;
171
- filterTable(searchTerm);
24
+ if (statuses.includes(cell.textContent.trim())) {
25
+ cell.setAttribute('data-content', cell.textContent.trim());
172
26
  }
173
27
  }
174
28
  });
175
- tableElement.addEventListener('change', function (e) {
176
- for (var target = e.target; target && target != this; target = target.parentNode) {
177
- if (target.matches('input[type="checkbox"]')) {
178
- const searchTerm = tableElement.querySelector('input[type="search"]').value;
179
- filterTable(searchTerm);
180
- createFilterList();
181
- }
182
- }
29
+ });
30
+ };
31
+ export const getLargestLastColWidth = (table) => {
32
+ let largestWidth = 0;
33
+ Array.from(table.querySelectorAll('tr')).forEach((row, index) => {
34
+ let htmlStyles = window.getComputedStyle(document.querySelector('html'));
35
+ let lastColChild = row.querySelector(':scope > *:last-child > *:first-child');
36
+ if (lastColChild) {
37
+ let responsiveWidth = lastColChild.offsetWidth / parseFloat(htmlStyles.fontSize);
38
+ responsiveWidth += 1.5;
39
+ largestWidth = largestWidth > responsiveWidth ? largestWidth : responsiveWidth;
40
+ }
41
+ let rowHeight = row.offsetHeight / parseFloat(htmlStyles.fontSize);
42
+ row.style.setProperty("--row-height", `${rowHeight}rem`);
43
+ });
44
+ return largestWidth;
45
+ };
46
+ export const createMobileButton = (table) => {
47
+ Array.from(table.querySelectorAll('tbody tr')).forEach((row, index) => {
48
+ let firstCol = row.querySelector(':scope > :is(td,th):first-child');
49
+ let colContent = firstCol.textContent;
50
+ firstCol.innerHTML = `<span class="td__content">${colContent}</span><button type="button" class="d-none">${colContent}</button>`;
51
+ });
52
+ };
53
+ export const addTableEventListeners = (table) => {
54
+ table.addEventListener('click', (event) => {
55
+ if (event && event.target instanceof HTMLElement && event.target.closest('tr > :is(td,th):first-child button')) {
56
+ let firstCol = event.target.closest('tr > :is(td,th):first-child button');
57
+ let tableRow = firstCol.parentNode.closest('tr');
58
+ if (tableRow.getAttribute('data-view') == "full")
59
+ tableRow.setAttribute('data-view', 'default');
60
+ else
61
+ tableRow.setAttribute('data-view', 'full');
62
+ firstCol.blur();
63
+ }
64
+ ;
65
+ });
66
+ };
67
+ // Filters
68
+ export const createSearchDataList = (table, form) => {
69
+ let searchInput = form.querySelector('[data-search]');
70
+ if (!searchInput)
71
+ return false;
72
+ const searchID = searchInput.getAttribute('id');
73
+ const searchableColumns = searchInput.getAttribute('data-search').split(',');
74
+ let inputWrapper = searchInput.parentNode;
75
+ let searchableTerms = {};
76
+ searchableColumns.forEach((columnHeading, index) => {
77
+ Array.from(table.querySelectorAll('td[data-label="' + columnHeading.trim() + '"]')).forEach((td, index) => {
78
+ if (td.querySelector('.td__content'))
79
+ searchableTerms[td.querySelector('.td__content').textContent] = td.querySelector('.td__content').textContent;
80
+ else
81
+ searchableTerms[td.textContent] = td.textContent;
183
82
  });
184
- }
185
- // #endregion Filters
186
- // #region Pagination
187
- const paginateRows = function (show, page) {
188
- // Create some inline CSS to control what is viewed on the table, unline the filters we are just hiding the rable rows not removing them from the DOM.
189
- let style = document.getElementById(randID + '_style');
190
- if (style == null) {
191
- style = document.createElement("style");
192
- style.setAttribute('id', randID + '_style');
83
+ });
84
+ searchInput.setAttribute('list', `${searchID}_list`);
85
+ searchInput.setAttribute('autocomplete', 'off');
86
+ if (!inputWrapper.querySelector('datalist'))
87
+ inputWrapper.innerHTML += `<datalist id="${searchID}_list"></datalist>`;
88
+ inputWrapper.querySelector('datalist').innerHTML = `${Object.keys(searchableTerms).map(term => `<option value="${term}"></option>`).join("")}`;
89
+ };
90
+ export const addFilterEventListeners = (table, form, pagination, wrapper, savedTableBody) => {
91
+ var timer;
92
+ // Check what conditions are set on the table to see what the form actions are
93
+ let formSubmit = function () {
94
+ if (form.hasAttribute('data-ajax'))
95
+ loadAjaxTable(table, form, pagination, wrapper);
96
+ else if (form.hasAttribute('data-submit'))
97
+ form.submit();
98
+ else {
99
+ filterTable(table, form, wrapper);
100
+ createPaginationButttons(wrapper, pagination);
101
+ }
102
+ };
103
+ form.addEventListener('keyup', (event) => {
104
+ clearTimeout(timer);
105
+ if (event && event.target instanceof HTMLElement && event.target.closest('[data-search]')) {
106
+ timer = setTimeout(function () {
107
+ formSubmit();
108
+ }, 500);
109
+ }
110
+ ;
111
+ });
112
+ form.addEventListener('change', (event) => {
113
+ clearTimeout(timer);
114
+ if (event && event.target instanceof HTMLElement && event.target.closest('[data-sort]')) {
115
+ if (!form.hasAttribute('data-submit'))
116
+ sortTable(table, form, savedTableBody);
117
+ formSubmit();
118
+ }
119
+ if (event && event.target instanceof HTMLElement && event.target.closest('[data-search]')) {
120
+ formSubmit();
121
+ }
122
+ if (event && event.target instanceof HTMLElement && event.target.closest('[data-filter]') && !event.target.closest('form dialog')) {
123
+ formSubmit();
124
+ }
125
+ if (event && event.target instanceof HTMLElement && event.target.closest('[data-show]')) {
126
+ formSubmit();
127
+ }
128
+ });
129
+ form.addEventListener('click', (event) => {
130
+ clearTimeout(timer);
131
+ if (event && event.target instanceof HTMLElement && event.target.closest('dialog button:not([type="button"])')) {
132
+ let button = event.target.closest('dialog button:not([type="button"])');
133
+ let modal = button.closest('dialog');
134
+ modal.close();
135
+ }
136
+ // Prevent the form from submitting
137
+ if (event && event.target instanceof HTMLElement && event.target.closest('.dialog__close')) {
138
+ event.preventDefault();
139
+ event.stopPropagation();
193
140
  }
194
- const startShowing = (show * (page - 1)) + 1;
195
- const stopShowing = show * (page);
196
- style.innerHTML = `
197
- #${randID} tbody tr {
198
- display: none;
141
+ if (event && event.target instanceof HTMLElement && event.target.closest('[data-clear]')) {
142
+ form.reset();
143
+ if (!form.hasAttribute('data-submit'))
144
+ sortTable(table, form, savedTableBody);
145
+ formSubmit();
146
+ }
147
+ });
148
+ form.addEventListener('submit', (event) => {
149
+ clearTimeout(timer);
150
+ if (!form.hasAttribute('data-submit'))
151
+ event.preventDefault();
152
+ formSubmit();
153
+ });
154
+ };
155
+ export const sortTable = (table, form, savedTableBody) => {
156
+ if (form.getAttribute('data-ajax')) {
157
+ return false;
199
158
  }
200
- #${randID} tbody tr:nth-child(${startShowing}),
201
- #${randID} tbody tr:nth-child(${startShowing}) ~ tr{
202
- display: block;
159
+ let tbody = table.querySelector('tbody');
160
+ let select = form.querySelector('[data-sort]');
161
+ let selectedOption = select.querySelector(`option:nth-child(${select.selectedIndex + 1})`);
162
+ let sortBy = selectedOption.getAttribute('data-sort');
163
+ let order = selectedOption.getAttribute('data-order');
164
+ let format = selectedOption.getAttribute('data-format');
165
+ if (!sortBy) {
166
+ tbody.innerHTML = savedTableBody.innerHTML;
167
+ addDataAttributes(table);
168
+ return false;
203
169
  }
204
- @media screen and (min-width: 36em) {
205
- #${randID} tbody tr:nth-child(${startShowing}),
206
- #${randID} tbody tr:nth-child(${startShowing}) ~ tr{
207
- display: table-row;
208
- }
170
+ let orderArray = [];
171
+ if (!['asc', 'desc', 'descending'].includes(order)) {
172
+ orderArray = order.split(',');
209
173
  }
210
- #${randID} tbody tr:nth-child(${stopShowing}) ~ tr{
211
- display: none;
174
+ // Create an array from the table rows, the index created is then used to sort the array
175
+ let tableArr = [];
176
+ Array.from(tbody.querySelectorAll('tr')).forEach((tableRow, index) => {
177
+ let rowIndex = tableRow.querySelector('td[data-label="' + sortBy + '"], th[data-label="' + sortBy + '"]').textContent.trim();
178
+ // If a predefined order set replace the search term with an ordered numeric value so it can be sorted
179
+ if (orderArray.length && orderArray.includes(rowIndex)) {
180
+ rowIndex = orderArray.indexOf(rowIndex);
181
+ }
182
+ if (isNumeric(rowIndex))
183
+ rowIndex = zeroPad(rowIndex, 10);
184
+ // If the sort format is date then lets transform the index to a sortable date (this is never displayed)
185
+ if (format && format == "date")
186
+ rowIndex = new Date(rowIndex);
187
+ const dataRow = {
188
+ index: rowIndex,
189
+ row: tableRow
190
+ };
191
+ tableArr.push(dataRow);
192
+ });
193
+ // Sort array alphabetically
194
+ tableArr.sort((a, b) => (a.index > b.index) ? 1 : -1);
195
+ // Reverse if descending
196
+ if (order == "descending" || order == "desc")
197
+ tableArr = tableArr.reverse();
198
+ // Create a string to return and populate the tbody
199
+ let strTbody = '';
200
+ tableArr.forEach((tableRow, index) => {
201
+ strTbody += tableRow.row.outerHTML;
202
+ });
203
+ tbody.innerHTML = strTbody;
204
+ };
205
+ export const filterTable = (table, form, wrapper) => {
206
+ table.classList.remove('table--filtered');
207
+ let filters = [];
208
+ let searches = [];
209
+ let matched = 0;
210
+ let page = form.querySelector('[data-pagination]') ? parseInt(form.querySelector('[data-pagination]').value) : 1;
211
+ let showRows = form.querySelector('[data-show]') ? parseInt(form.querySelector('[data-show]').value) : 15;
212
+ // Filter
213
+ let filterInputs = Array.from(form.querySelectorAll('[data-filter]'));
214
+ filterInputs.forEach((filterInput, index) => {
215
+ // Ignore uncked radio inputs
216
+ if (filterInput.type == 'radio' && !filterInput.checked) {
217
+ return;
218
+ }
219
+ if (filterInput.type == 'checkbox' && !filterInput.checked) {
220
+ return;
221
+ }
222
+ if (filterInput.getAttribute('data-filter') == "multi") {
223
+ for (const [key, value] of Object.entries(JSON.parse(filterInput.value))) {
224
+ filters[filterInput.getAttribute('data-filter')].push(value);
225
+ }
226
+ }
227
+ else if (filterInput.value) {
228
+ if (!filters[filterInput.getAttribute('data-filter')])
229
+ filters[filterInput.getAttribute('data-filter')] = new Array();
230
+ filters[filterInput.getAttribute('data-filter')].push(filterInput.value);
231
+ }
232
+ });
233
+ // Add search columns too
234
+ if (form.querySelector('[data-search]')) {
235
+ let searchInput = form.querySelector('[data-search]');
236
+ let searchColumns = form.querySelector('[data-search]').getAttribute('data-search').split(',');
237
+ searchColumns.forEach((column, index) => {
238
+ searches.push({ 'column': `${column.trim()}`, 'value': `${searchInput.value}` });
239
+ });
212
240
  }
213
- `;
214
- tableElement.append(style);
215
- };
216
- // On page load check if the table should be paginated
217
- if (tableElement.getAttribute('data-show')) {
218
- const show = parseInt(tableElement.getAttribute('data-show'));
219
- const page = parseInt(tableElement.getAttribute('data-page')) ? parseInt(tableElement.getAttribute('data-page')) : 1;
220
- const totalRows = tableElement.querySelectorAll('tbody tr').length;
221
- if (show < totalRows) {
222
- paginateRows(show, page);
223
- createPaginationForm(randID, tableElement, show, page, totalRows);
224
- createPaginationButttons(randID, tableElement, show, page, totalRows);
225
- tableElement.addEventListener('change', function (e) {
226
- for (var target = e.target; target && target != this; target = target.parentNode) {
227
- if (target.matches('.table__pagination input[type="number"]')) {
228
- paginateRows(target.value, page);
229
- createPaginationButttons(randID, tableElement, target.value, page, totalRows);
230
- tableElement.setAttribute('data-show', target.value);
231
- }
241
+ //Display the filter count
242
+ Array.from(form.querySelectorAll('[data-filter-count]')).forEach((element, index) => {
243
+ element.innerHTML = '';
244
+ });
245
+ if (filters.length) {
246
+ Array.from(form.querySelectorAll('[data-filter-count]')).forEach((element, index) => {
247
+ element.innerHTML += `(${filters.length})`;
248
+ });
249
+ }
250
+ // Stop function if no filters identified
251
+ if (!searches.length && !filters.length)
252
+ return false;
253
+ table.classList.add('table--filtered');
254
+ // Reset
255
+ Array.from(table.querySelectorAll('tbody tr')).forEach((row, index) => {
256
+ row.classList.remove('filtered');
257
+ row.classList.remove('filtered--matched');
258
+ row.classList.remove('filtered--show');
259
+ row.removeAttribute('data-filtered-by');
260
+ });
261
+ // Filter the table
262
+ for (const [key, filterValue] of Object.entries(filters)) {
263
+ console.log(filterValue);
264
+ Array.from(table.querySelectorAll('tbody tr:not(.filtered)')).forEach((row, index) => {
265
+ let isMatched = false;
266
+ filterValue.forEach((filter, index) => {
267
+ let filterTd = row.querySelector(`[data-label="${key}"]`);
268
+ // Dynamic values
269
+ if (filter && filter == "$today")
270
+ filter = formatCell('date', new Date());
271
+ else if (filter && filter == "$yesterday") {
272
+ let yesterday = new Date();
273
+ yesterday.setDate(yesterday.getDate() - 1);
274
+ filter = formatCell('date', yesterday);
232
275
  }
233
- });
234
- tableElement.addEventListener('click', function (e) {
235
- for (var target = e.target; target && target != this; target = target.parentNode) {
236
- if (target.matches('.page-item:not(.active):not(.disabled) .page-link')) {
237
- paginateRows(tableElement.getAttribute('data-show'), target.getAttribute('data-page'));
238
- createPaginationButttons(randID, tableElement, tableElement.getAttribute('data-show'), target.getAttribute('data-page'), totalRows);
276
+ else if (filter && (filter == "$thisWeek" || filter == "$lastWeek")) {
277
+ let today = new Date();
278
+ let mondayThisWeek = new Date(today.setDate(today.getDate() - (today.getDay() - 1)));
279
+ let sundayThisWeek = new Date(today.setDate(today.getDate() - today.getDay() + 7));
280
+ let checkDate = new Date(filterTd.textContent.toLowerCase());
281
+ today.setHours(0, 0, 0, 0);
282
+ mondayThisWeek.setHours(0, 0, 0, 0);
283
+ sundayThisWeek.setHours(0, 0, 0, 0);
284
+ checkDate.setHours(0, 0, 0, 0);
285
+ if (filter == "$thisWeek") {
286
+ isMatched = (checkDate >= mondayThisWeek && checkDate <= sundayThisWeek);
239
287
  }
240
- }
241
- }, false);
242
- tableElement.addEventListener('change', function (e) {
243
- for (var target = e.target; target && target != this; target = target.parentNode) {
244
- if (target.matches('.table__pagination select')) {
245
- paginateRows(tableElement.getAttribute('data-show'), target.value);
246
- createPaginationButttons(randID, tableElement, tableElement.getAttribute('data-show'), target.value, totalRows);
288
+ else {
289
+ let mondayLastWeek = new Date(mondayThisWeek.setDate(mondayThisWeek.getDate() - 7));
290
+ let sundayLastWeek = new Date(sundayThisWeek.setDate(sundayThisWeek.getDate() - 7));
291
+ mondayLastWeek.setHours(0, 0, 0, 0);
292
+ sundayLastWeek.setHours(0, 0, 0, 0);
293
+ isMatched = (checkDate >= mondayLastWeek && checkDate <= sundayLastWeek);
247
294
  }
248
295
  }
249
- });
250
- }
251
- }
252
- // #endregion Pagination
253
- // #region Reorderable
254
- // Set the row thats being dragged and copy the row
255
- function setDraggedRow(e) {
256
- e.dataTransfer.setData("text/plain", e.target.id);
257
- draggedRow = e.target;
258
- e.target.classList.add('tr--dragging');
259
- }
260
- // Create the order column and event handler for rows
261
- const setReorderRows = function () {
262
- Array.from(tbody.querySelectorAll('tr')).forEach((tableRow, index) => {
263
- // Create column if not already created
264
- if (tableRow.querySelector('[data-label="Order"]') == null) {
265
- const orderColumn = document.createElement('th');
266
- orderColumn.innerHTML = index + 1;
267
- orderColumn.setAttribute('data-label', 'Order');
268
- tableRow.prepend(orderColumn);
269
- }
270
- // Make draggable
271
- tableRow.setAttribute('id', randID + '_row_' + (index + 1));
272
- tableRow.setAttribute('data-order', index + 1);
273
- tableRow.setAttribute('draggable', 'true');
274
- tableRow.addEventListener("dragstart", setDraggedRow);
275
- });
276
- };
277
- if (tableElement.getAttribute('data-reorder') && tableElement.getAttribute('data-reorder') != "false") {
278
- // Add column heading
279
- const orderHeading = document.createElement('th');
280
- orderHeading.innerHTML = 'Order';
281
- orderHeading.title = 'Click here to enable re-ordering via drag and drop';
282
- orderHeading.classList.add('table-order-reset');
283
- thead.querySelector('tr').prepend(orderHeading);
284
- setReorderRows();
285
- // Reset order button
286
- tableElement.addEventListener('click', function (e) {
287
- for (var target = e.target; target && target != this; target = target.parentNode) {
288
- if (target.matches('.table-order-reset')) {
289
- // unset sort attributes
290
- Array.from(tableElement.querySelectorAll('[data-sortable]')).forEach((col, index) => {
291
- col.setAttribute('aria-sort', 'none');
292
- });
293
- // Save the sort options on the table element so that it can be re-sorted later
294
- tableElement.removeAttribute('data-sort');
295
- tableElement.removeAttribute('data-sortBy');
296
- // Sort the table
297
- sortTable('Order', 'ascending');
298
- Array.from(tableElement.querySelectorAll('tbody tr')).forEach((tableRow, index) => {
299
- tableRow.setAttribute('draggable', 'true');
300
- });
301
- break;
296
+ else if (filter && filter == "$thisMonth") {
297
+ let today = new Date(), year = today.getFullYear(), month = today.getMonth();
298
+ var firstDayMonth = new Date(year, month, 1);
299
+ var lastDayMonth = new Date(year, month + 1, 0);
300
+ let checkDate = new Date(filterTd.textContent.toLowerCase());
301
+ firstDayMonth.setHours(0, 0, 0, 0);
302
+ lastDayMonth.setHours(0, 0, 0, 0);
303
+ checkDate.setHours(0, 0, 0, 0);
304
+ isMatched = (checkDate >= firstDayMonth && checkDate <= lastDayMonth);
302
305
  }
303
- }
304
- }, false);
305
- document.addEventListener("dragover", function (e) {
306
- // prevent default to allow drop
307
- e.preventDefault();
308
- }, false);
309
- document.addEventListener("dragenter", function (e) {
310
- // prevent default to allow drop
311
- e.preventDefault();
312
- e.dataTransfer.dropEffect = "move";
313
- for (var target = e.target; target && target != this; target = target.parentNode) {
314
- if (target.matches('[data-reorder] tbody tr')) {
315
- target.classList.add('tr--dropable');
316
- }
317
- }
318
- }, false);
319
- document.addEventListener("dragleave", function (e) {
320
- // prevent default to allow drop
321
- e.preventDefault();
322
- for (var target = e.target; target && target != this; target = target.parentNode) {
323
- if (target.matches('[data-reorder] tbody tr')) {
324
- target.classList.remove('tr--dropable');
306
+ else if (filter && filter == "$lastMonth") {
307
+ let today = new Date(), year = today.getFullYear(), month = today.getMonth();
308
+ var firstDayLastMonth = new Date(year, month - 1, 1);
309
+ var lastDayLastMonth = new Date(year, month, 0);
310
+ let checkDate = new Date(filterTd.textContent.toLowerCase());
311
+ firstDayLastMonth.setHours(0, 0, 0, 0);
312
+ lastDayLastMonth.setHours(0, 0, 0, 0);
313
+ checkDate.setHours(0, 0, 0, 0);
314
+ isMatched = (checkDate >= firstDayLastMonth && checkDate <= lastDayLastMonth);
325
315
  }
326
- }
327
- }, false);
328
- document.addEventListener("drop", function (e) {
329
- e.preventDefault();
330
- for (var target = e.target; target && target != this; target = target.parentNode) {
331
- if (target.matches('[data-reorder] tbody tr')) {
332
- if (target.parentNode != null && draggedRow.parentNode != null && target != draggedRow) {
333
- draggedRow.parentNode.removeChild(draggedRow);
334
- if (draggedRow.getAttribute('data-order') > target.getAttribute('data-order'))
335
- target.parentNode.insertBefore(draggedRow, target);
336
- else
337
- target.parentNode.insertBefore(draggedRow, target.nextElementSibling);
338
- // Re label the rows
339
- Array.from(tbody.querySelectorAll('tr')).forEach((tableRowOrder, index) => {
340
- tableRowOrder.classList.remove('tr--dragging');
341
- tableRowOrder.classList.remove('tr--dropable');
342
- tableRowOrder.querySelector('th').innerHTML = index + 1;
343
- tableRowOrder.setAttribute('data-order', index + 1);
344
- });
345
- tableElement.dispatchEvent(reorderedEvent);
346
- }
347
- break;
316
+ if (filterTd && filterTd.textContent.toLowerCase().includes(filter.toLowerCase())) {
317
+ isMatched = true;
348
318
  }
319
+ });
320
+ if (!isMatched) {
321
+ row.classList.add('filtered');
322
+ row.setAttribute('data-filtered-by', key);
349
323
  }
350
- }, false);
324
+ });
351
325
  }
352
- // #endregion Reorderable
353
- // Watch for the filterable event and re-sort the tbody
354
- tableElement.addEventListener('filtered', function (e) {
355
- if (tableElement.getAttribute('data-sortBy') && tableElement.getAttribute('data-sort'))
356
- sortTable(tableElement.getAttribute('data-sortBy'), tableElement.getAttribute('data-sort'));
357
- if (tableElement.getAttribute('data-show')) {
358
- const show = parseInt(tableElement.getAttribute('data-show'));
359
- const totalRows = tableElement.querySelectorAll('tbody tr').length;
360
- const tablePagination = tableElement.querySelector('.table__pagination');
361
- if (tablePagination != null)
362
- tablePagination.remove();
363
- if (show < totalRows) {
364
- paginateRows(show, 1);
365
- createPaginationForm(randID, tableElement, show, 1, totalRows);
366
- createPaginationButttons(randID, tableElement, show, 1, totalRows);
326
+ // Search whats left of the table after filtering
327
+ Array.from(table.querySelectorAll('tbody tr:not(.filtered)')).forEach((row, index) => {
328
+ let isSearched = searches.length > 0 && searches[0].value.length >= 3 ? false : true;
329
+ searches.forEach((search, index) => {
330
+ let searchTd = row.querySelector(`[data-label="${search.column}"]`);
331
+ if (searchTd && search.value.length >= 3 && searchTd.textContent.toLowerCase().includes(search.value.toLowerCase())) {
332
+ isSearched = true;
367
333
  }
334
+ });
335
+ if (!isSearched)
336
+ row.classList.add('filtered');
337
+ });
338
+ // Work out what to display after pagination
339
+ Array.from(table.querySelectorAll('tbody tr:not(.filtered')).forEach((row, index) => {
340
+ matched++;
341
+ row.classList.add('filtered--matched');
342
+ // pagination bit
343
+ if (Math.ceil(matched / showRows) == parseInt(page))
344
+ row.classList.add('filtered--show');
345
+ });
346
+ if (wrapper) {
347
+ wrapper.setAttribute('data-page', page);
348
+ wrapper.setAttribute('data-pages', Math.ceil(matched / showRows));
349
+ wrapper.setAttribute('data-total', matched);
350
+ wrapper.setAttribute('data-show', showRows);
351
+ }
352
+ populateDataQueries(table, form);
353
+ };
354
+ export const populateDataQueries = (table, form) => {
355
+ const dataQueries = Array.from(form.querySelectorAll('[data-query]'));
356
+ dataQueries.forEach((queryElement, index) => {
357
+ let query = queryElement.getAttribute('data-query');
358
+ let numberOfMatchedRows;
359
+ if (query == 'total') {
360
+ numberOfMatchedRows = table.classList.contains('table--filtered') ? table.querySelectorAll('tbody tr:not(.filtered)').length : table.querySelectorAll('tbody tr').length;
368
361
  }
369
- if (tableElement.getAttribute('data-reorder')) {
370
- setReorderRows();
362
+ else if (!query.includes(' == ') && query.includes(' & ')) {
363
+ let queries = query.split(' & ');
364
+ let selector = '';
365
+ queries.forEach(element => {
366
+ selector += `:not([data-filtered-by="${element}"])`;
367
+ });
368
+ console.log(selector);
369
+ numberOfMatchedRows = Array.from(table.querySelectorAll(`tbody tr${selector}`)).length;
370
+ }
371
+ else if (!query.includes(' == ')) {
372
+ numberOfMatchedRows = Array.from(table.querySelectorAll(`tbody tr:not([data-filtered-by="${query}"])`)).length;
371
373
  }
372
- }, false);
373
- tableElement.addEventListener('sorted', function (e) {
374
- if (tableElement.getAttribute('data-reorder')) {
375
- setReorderRows();
374
+ else if (query.includes(' && ')) {
375
+ let queries = query.split(' && ');
376
+ numberOfMatchedRows = Array.from(table.querySelectorAll(`tbody tr:not(.filtered)`)).filter(function (row) {
377
+ let matched = true;
378
+ for (const [index, value] of Object.entries(queries)) {
379
+ let queryParts = value.split(' == ');
380
+ if (!row.querySelector(`td[data-label="${queryParts[0]}"]`) || row.querySelector(`td[data-label="${queryParts[0]}"]`).textContent != `${queryParts[1]}`)
381
+ matched = false;
382
+ }
383
+ return matched;
384
+ }).length;
376
385
  }
377
- }, false);
378
- tableElement.addEventListener('populated', function (e) {
379
- var tableFilter = tableElement.querySelector('.table__filters');
380
- tableFilter.remove();
381
- var tablePagination = tableElement.querySelector('.table__pagination');
382
- tablePagination.remove();
383
- var newTable = tableElement.cloneNode(true);
384
- tableElement.parentNode.replaceChild(newTable, tableElement);
385
- table(newTable);
386
- }, false);
387
- }
388
- export const createPaginationForm = function (randID, tableElement, show, page, totalRows) {
389
- const form = document.createElement("div");
390
- form.classList.add('table__pagination');
391
- form.classList.add('row');
392
- form.classList.add('pt-3');
393
- form.classList.add('pb-3');
394
- // Create the form and create a container div to hold the pagination buttons
395
- form.innerHTML = `<div class="col mw-fit-content mb-3">
396
- <div class="form-control__wrapper form-control-inline mb-0">
397
- <label for="${randID}_showing" class="form-label">Showing:</label>
398
- <input type="number" name="${randID}_showing" id="${randID}_showing" class="form-control form-control-sm showing-input-field" placeholder="" list="${randID}_pagination" value="${show}" min="1" max="${totalRows}" />
399
- </div>
400
- <datalist id="${randID}_pagination">
401
- <option value="5">5</option>
402
- ${totalRows > 10 ? `<option value="10">10</option>` : ''}
403
- ${totalRows > 20 ? `<option value="20">20</option>` : ''}
404
- <option value="${totalRows}">${totalRows}</option>
405
- </datalist>
406
- </div>
407
- <div class="col mw-fit-content me-auto d-flex align-items-center mb-3"><span class="label">per page</span></div>
408
- <div class="col mw-fit-content d-sm-flex justify-content-end align-items-center" id="${randID}_paginationBtns"></div>`;
409
- // Add after the actual table
410
- tableElement.append(form);
386
+ else {
387
+ let queryParts = query.split(' == ');
388
+ console.log(queryParts[0]);
389
+ numberOfMatchedRows = Array.from(table.querySelectorAll(`tbody tr.filtered--matched td[data-label="${queryParts[0]}"], tbody tr[data-filtered-by="${queryParts[0]}"] td[data-label="${queryParts[0]}"]`)).filter(function (element) {
390
+ return element.textContent === queryParts[1];
391
+ }).length;
392
+ }
393
+ if (queryElement.hasAttribute('data-total'))
394
+ queryElement.setAttribute('data-total', numberOfMatchedRows);
395
+ else
396
+ queryElement.innerHTML = numberOfMatchedRows;
397
+ });
398
+ };
399
+ // Pagination
400
+ export const addPaginationEventListeners = function (table, form, pagination, wrapper) {
401
+ pagination.addEventListener('click', (event) => {
402
+ if (event && event.target instanceof HTMLElement && event.target.closest('[data-page]')) {
403
+ event.preventDefault();
404
+ let paginationInput = form.querySelector('[data-pagination]');
405
+ let newPage = event.target.closest('[data-page]').getAttribute('data-page');
406
+ paginationInput.value = newPage;
407
+ wrapper.setAttribute('data-page', newPage);
408
+ form.dispatchEvent(new Event("submit"));
409
+ const url = new URL(location);
410
+ url.searchParams.set("page", newPage);
411
+ history.pushState({ 'type': 'pagination', 'form': form.getAttribute('id'), 'page': newPage }, "", url);
412
+ }
413
+ if (event && event.target instanceof HTMLElement && event.target.closest('[data-show]')) {
414
+ event.preventDefault();
415
+ let showInput = form.querySelector('[data-show]');
416
+ let showRows = event.target.closest('[data-show]').getAttribute('data-show');
417
+ showInput.value = showRows;
418
+ wrapper.setAttribute('data-show', showRows);
419
+ form.dispatchEvent(new Event("submit"));
420
+ }
421
+ });
411
422
  };
412
- export const createPaginationButttons = function (randID, tableElement, show, page, totalRows) {
413
- const paginationButtonsWrapper = document.getElementById(randID + '_paginationBtns');
414
- if (paginationButtonsWrapper == null)
423
+ // Export CSV Data
424
+ export const addExportEventListeners = (button, table) => {
425
+ if (!button)
415
426
  return false;
416
- const numberPages = Math.ceil(totalRows / show);
417
- if (numberPages == 1) { // Remore the buttons or dont display any if we dont need them
418
- paginationButtonsWrapper.innerHTML = '';
419
- }
420
- else if (numberPages < 5) { // If less than 5 pages (which fits comfortably on mobile) we display buttons
421
- let strButtons = '';
422
- for (let i = 1; i <= numberPages; i++) {
423
- if (i == page)
424
- strButtons += `<li class="page-item active" aria-current="page"><span class="page-link">${i}</span></li>`;
425
- else
426
- strButtons += `<li class="page-item"><button class="page-link" data-page="${i}">${i}</button></li>`;
427
+ button.addEventListener('click', (event) => {
428
+ exportAsCSV(table);
429
+ });
430
+ };
431
+ export const exportAsCSV = function (table) {
432
+ var csvData = [];
433
+ // Get each row data
434
+ var rows = table.getElementsByTagName('tr');
435
+ for (var i = 0; i < rows.length; i++) {
436
+ // Get each column data
437
+ var cols = rows[i].querySelectorAll('td,th');
438
+ // Stores each csv row data
439
+ var csvRow = [];
440
+ for (var j = 0; j < cols.length; j++) {
441
+ // Get the text data of each cell of a row and push it to csvrow
442
+ csvRow.push(`"${cols[j].textContent}"`);
427
443
  }
428
- paginationButtonsWrapper.innerHTML = `<span class="pe-2 mb-3">Page: </span><ul class="pagination mb-3">
429
- ${page == 1 ? `<li class="page-item disabled"><span class="page-link">Previous</span></li>` : `<li class="page-item"><button class="page-link" data-page="${parseInt(page) - 1}">Previous</button></li>`}
430
- ${strButtons}
431
- ${page == numberPages ? `<li class="page-item disabled"><span class="page-link">Next</span></li>` : `<li class="page-item"><button class="page-link" data-page="${parseInt(page) + 1}">Next</button></li>`}
432
- </ul>`;
444
+ // Combine each column value with comma
445
+ csvData.push(csvRow.join(","));
433
446
  }
434
- else { // If more than 5 lets show a select field instead so that we dont have loads and loads of buttons
435
- let strOptions = '';
436
- for (let i = 1; i <= numberPages; i++) {
437
- if (i == page)
438
- strOptions += `<option value="${i}" selected>Page ${i}</option>`;
439
- else
440
- strOptions += `<option value="${i}">Page ${i}</option>`;
447
+ // Combine each row data with new line character
448
+ csvData = csvData.join('\n');
449
+ // Create CSV file object and feed our csvData into it
450
+ let CSVFile = new Blob([csvData], {
451
+ type: "text/csv"
452
+ });
453
+ // Create to temporary link to initiate download process
454
+ var tempLink = document.createElement('a');
455
+ tempLink.download = "export.csv";
456
+ var url = window.URL.createObjectURL(CSVFile);
457
+ tempLink.href = url;
458
+ // This link should not be displayed
459
+ tempLink.style.display = "none";
460
+ document.body.appendChild(tempLink);
461
+ // Automatically click the link to trigger download
462
+ tempLink.click();
463
+ document.body.removeChild(tempLink);
464
+ };
465
+ // After table is loaded
466
+ export const makeTableFunctional = function (table, form, pagination, wrapper) {
467
+ createMobileButton(table);
468
+ addDataAttributes(table);
469
+ populateDataQueries(table, form);
470
+ // Work out the largest width of the CTA's in the last column
471
+ if (wrapper && wrapper.classList.contains('table--cta')) {
472
+ const largestWidth = getLargestLastColWidth(table);
473
+ wrapper.style.setProperty("--cta-width", `${largestWidth}rem`);
474
+ }
475
+ };
476
+ export const loadAjaxTable = function (table, form, pagination, wrapper) {
477
+ const resolvePath = (object, path, defaultValue) => path.split(/[\.\[\]\'\"]/).filter(p => p).reduce((o, p) => o ? o[p] : defaultValue, object);
478
+ let queryString = new URLSearchParams(new FormData(form)).toString();
479
+ let columns = table.querySelectorAll('thead tr th');
480
+ let tbody = table.querySelector('tbody');
481
+ fetch(form.getAttribute('data-ajax'), {
482
+ method: 'get',
483
+ credentials: 'same-origin',
484
+ headers: new Headers({
485
+ 'Content-Type': 'application/json',
486
+ Accept: 'application/json',
487
+ 'X-Requested-With': 'XMLHttpRequest'
488
+ })
489
+ }).then((response) => response.json()).then((response) => {
490
+ if (response.data) {
491
+ tbody.innerHTML = '';
492
+ response.data.forEach((row, index) => {
493
+ var table_row = document.createElement('tr');
494
+ columns.forEach((col, index) => {
495
+ let cellOutput = '';
496
+ var table_cell = document.createElement('td');
497
+ // Add some data to help with the mobile layout design
498
+ table_cell.setAttribute('data-label', col.innerText);
499
+ if (col.getAttribute('data-output')) {
500
+ var cellTemplate = col.getAttribute('data-output');
501
+ // Use a regex to replace {var} with actual values from the json data
502
+ cellOutput = cellTemplate.replace(new RegExp(/{(.*?)}/, "gm"), function (matched) { return resolvePath(row, matched.replace('{', '').replace('}', '')); });
503
+ }
504
+ if (col.hasAttribute('data-format')) {
505
+ cellOutput = formatCell(col.getAttribute('data-format'), cellOutput);
506
+ }
507
+ table_cell.innerHTML = cellOutput;
508
+ table_row.appendChild(table_cell);
509
+ });
510
+ tbody.appendChild(table_row);
511
+ });
512
+ createSearchDataList(table, form);
513
+ // Add data to the pagination
514
+ makeTableFunctional(table, form, pagination, wrapper);
515
+ wrapper.setAttribute('data-total', (response.meta.total ? response.meta.total : 1));
516
+ wrapper.setAttribute('data-page', (response.meta.current_page ? response.meta.current_page : 1));
517
+ wrapper.setAttribute('data-pages', Math.ceil(wrapper.getAttribute('data-total') / wrapper.getAttribute('data-show')));
518
+ createPaginationButttons(wrapper, pagination);
519
+ if (response.data.length == 0) {
520
+ tbody.innerHTML = '<tr><td colspan="100%"><span class="h4 m-0">No results found</span></td></tr>';
521
+ }
522
+ }
523
+ else {
524
+ tbody.innerHTML = '<tr><td colspan="100%"><span class="h6 m-0">Error loading table</span></td></tr>';
441
525
  }
442
- paginationButtonsWrapper.innerHTML = `
443
- <div class="form-control__wrapper page-number mb-2">
444
- <select class="form-select">
445
- ${strOptions}
446
- </select>
447
- </div>
448
- `;
526
+ });
527
+ };
528
+ export const formatCell = (format, cellOutput) => {
529
+ switch (format) {
530
+ case 'date':
531
+ cellOutput = new Date(cellOutput).toLocaleDateString('en-gb', { year: "2-digit", month: "long", day: "numeric" });
532
+ break;
533
+ case 'capitalise':
534
+ cellOutput = ucfirst(cellOutput);
535
+ break;
449
536
  }
537
+ return cellOutput;
450
538
  };
451
- export default table;