@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,586 +1,751 @@
1
1
  // @ts-nocheck
2
- import { zeroPad, isNumeric } from "./helpers";
2
+ import { zeroPad, isNumeric, ucfirst } from "./helpers";
3
+ import createPaginationButttons from "./pagination";
3
4
 
4
- function table(tableElement) {
5
+ // Basic functionality needed
6
+ export const addDataAttributes = (table) => {
5
7
 
6
- if(typeof tableElement != "object")
7
- return false;
8
+ const colHeadings = Array.from(table.querySelectorAll('thead th'));
9
+ const colRows = Array.from(table.querySelectorAll('tbody tr'));
8
10
 
9
- const thead = tableElement.querySelector('thead');
10
- const tbody = tableElement.querySelector('tbody');
11
- const storedData = tbody.cloneNode(true);
12
- const sortedEvent = new Event('sorted');
13
- const filteredEvent = new Event('filtered');
14
- const reorderedEvent = new Event('reordered');
15
- const randID = 'table_'+Math.random().toString(36).substr(2, 9); // Random to make sure IDs created are unique
16
- let draggedRow;
11
+ colRows.forEach((row, index) => {
17
12
 
18
- tableElement.setAttribute('id',randID)
13
+ const cells = Array.from(row.querySelectorAll('th, td'));
14
+ const statuses = ['Low','Medium','High','N/A','Pending','Verified','Incomplete','Completed','Requires approval'];
15
+
16
+ cells.forEach((cell, cellIndex) => {
19
17
 
20
- // #region Sortable
21
- const sortTable = function(sortBy,sort){
18
+ const heading = colHeadings[cellIndex];
19
+ if(typeof heading != "undefined"){
22
20
 
23
- // Create an array from the table rows, the index created is then used to sort the array
24
- let tableArr = [];
25
- Array.from(tbody.querySelectorAll('tr')).forEach((tableRow, index) => {
21
+ let tempDiv = document.createElement("div");
22
+ tempDiv.innerHTML = heading.innerHTML;
23
+ let headingText = tempDiv.textContent || tempDiv.innerText || "";
24
+ cell.setAttribute('data-label',headingText);
26
25
 
27
- let rowIndex = tableRow.querySelector('td[data-label="'+sortBy+'"], th[data-label="'+sortBy+'"]').textContent;
26
+ if(heading.hasAttribute('class'))
27
+ cell.setAttribute('class',heading.getAttribute('class'))
28
28
 
29
- if(isNumeric(rowIndex))
30
- rowIndex = zeroPad(rowIndex,10)
29
+ if(heading.hasAttribute('data-format')){
30
+ cell.setAttribute('data-format',heading.getAttribute('data-format'))
31
+ cell.innerHTML = formatCell('date',cell.textContent.trim()); //Make sure date format is consistent
32
+ }
31
33
 
32
- const dataRow = {
33
- index: rowIndex,
34
- row: tableRow
34
+ if(statuses.includes(cell.textContent.trim())){
35
+ cell.setAttribute('data-content',cell.textContent.trim());
36
+ }
35
37
  }
36
- tableArr.push(dataRow);
37
38
  });
39
+ });
40
+ }
38
41
 
39
- // Sort array
40
- tableArr.sort((a, b) => (a.index > b.index) ? 1 : -1)
42
+ export const getLargestLastColWidth = (table) => {
43
+
44
+ let largestWidth = 0;
41
45
 
42
- // Reverse if descending
43
- if(sort == "descending")
44
- tableArr = tableArr.reverse();
46
+ Array.from(table.querySelectorAll('tr')).forEach((row, index) => {
45
47
 
46
- // Create a string to return and populate the tbody
47
- let strTbody = '';
48
- tableArr.forEach((tableRow, index) => {
49
- strTbody += tableRow.row.outerHTML;
50
- });
51
- tbody.innerHTML = strTbody;
48
+ let htmlStyles = window.getComputedStyle(document.querySelector('html'));
49
+ let lastColChild = row.querySelector(':scope > *:last-child > *:first-child');
52
50
 
53
- // Dispatch the sortable event
54
- tableElement.dispatchEvent(sortedEvent);
55
- }
51
+ if(lastColChild){
56
52
 
57
- // Declare event handlers
58
- tableElement.addEventListener('click', function(e){
59
- for (var target = e.target; target && target != this; target = target.parentNode) {
60
- if (target.matches('[data-sortable]')) {
53
+ let responsiveWidth = lastColChild.offsetWidth/parseFloat(htmlStyles.fontSize);
54
+ responsiveWidth += 1.5;
55
+ largestWidth = largestWidth > responsiveWidth ? largestWidth : responsiveWidth;
56
+ }
61
57
 
62
- // Get current sort order
63
- let sort = target.getAttribute('aria-sort') == "ascending" ? "descending" : "ascending";
58
+ let rowHeight = row.offsetHeight/parseFloat(htmlStyles.fontSize);
59
+ row.style.setProperty("--row-height", `${rowHeight}rem`);
60
+ });
64
61
 
65
- // unset sort attributes
66
- Array.from(tableElement.querySelectorAll('[data-sortable]')).forEach((col, index) => {
67
- col.setAttribute('aria-sort','none');
68
- });
62
+ return largestWidth;
63
+ }
69
64
 
70
- // Set the sort order attribute
71
- target.setAttribute('aria-sort', sort);
65
+ export const createMobileButton = (table) => {
72
66
 
73
- // Save the sort options on the table element so that it can be re-sorted later
74
- tableElement.setAttribute('data-sort', sort);
75
- tableElement.setAttribute('data-sortBy', target.textContent);
67
+ Array.from(table.querySelectorAll('tbody tr')).forEach((row, index) => {
68
+ let firstCol = row.querySelector(':scope > :is(td,th):first-child');
69
+ let colContent = firstCol.textContent;
70
+ firstCol.innerHTML =`<span class="td__content">${colContent}</span><button type="button" class="d-none">${colContent}</button>`;
71
+ });
72
+ }
76
73
 
77
- // Sort the table
78
- sortTable(target.textContent, sort);
74
+ export const addTableEventListeners = (table) => {
79
75
 
80
- Array.from(tableElement.querySelectorAll('tr[draggable]')).forEach((tableRow, index) => {
76
+ table.addEventListener('click', (event) => {
81
77
 
82
- tableRow.removeAttribute('draggable');
83
- });
84
- break;
85
- }
86
- }
87
- }, false);
78
+ if (event && event.target instanceof HTMLElement && event.target.closest('tr > :is(td,th):first-child button')){
88
79
 
89
- // On page load check if the table should be pre-sorted, if so trigger a click
90
- if(tableElement.getAttribute('data-sortBy')){
80
+ let firstCol = event.target.closest('tr > :is(td,th):first-child button');
81
+ let tableRow = firstCol.parentNode.closest('tr');
91
82
 
92
- let sort = tableElement.getAttribute('data-sort') == "ascending" ? "descending" : "ascending";
83
+ if(tableRow.getAttribute('data-view') == "full")
84
+ tableRow.setAttribute('data-view','default');
85
+ else
86
+ tableRow.setAttribute('data-view','full');
93
87
 
94
- Array.from(tableElement.querySelectorAll('[data-sortable]')).forEach((col, index) => {
95
- if(col.textContent == tableElement.getAttribute('data-sortBy')){
96
- col.setAttribute('aria-sort',sort)
97
- col.click();
98
- }
99
- });
100
- }
88
+ firstCol.blur();
89
+ };
90
+ });
91
+ }
101
92
 
102
- // #endregion Sortable
93
+ // Filters
94
+ export const createSearchDataList = (table, form) => {
103
95
 
104
- // #region Filters
105
- const createFilterForm = function(count){
96
+ let searchInput = form.querySelector('[data-search]');
106
97
 
107
- // Create wrapper div
108
- const form = document.createElement("div");
109
- form.classList.add('table__filters');
110
- form.classList.add('row');
111
- form.classList.add('pt-1');
112
- form.classList.add('pb-3');
98
+ if(!searchInput)
99
+ return false;
113
100
 
114
- // Create the filter options array
115
- const filterColumns = Array.from(tableElement.querySelectorAll('th[data-filterable]'));
101
+ const searchID = searchInput.getAttribute('id');
102
+ const searchableColumns = searchInput.getAttribute('data-search').split(',');
103
+ let inputWrapper = searchInput.parentNode;
116
104
 
117
- // Populate a list of searchable terms from the cells of the columns that could be used as a filter
118
- let searchableTerms = {};
119
- filterColumns.forEach((columnHeading, index) => {
120
- Array.from(tableElement.querySelectorAll('td[data-label="'+columnHeading.textContent+'"]')).forEach((label, index) => {
105
+ let searchableTerms = {};
106
+ searchableColumns.forEach((columnHeading, index) => {
121
107
 
122
- searchableTerms[label.textContent] = label.textContent;
123
- });
108
+ Array.from(table.querySelectorAll('td[data-label="'+columnHeading.trim()+'"]')).forEach((td, index) => {
109
+
110
+ if(td.querySelector('.td__content'))
111
+ searchableTerms[td.querySelector('.td__content').textContent] = td.querySelector('.td__content').textContent;
112
+ else
113
+ searchableTerms[td.textContent] = td.textContent;
124
114
  });
115
+ });
116
+
117
+ searchInput.setAttribute('list',`${searchID}_list`);
118
+ searchInput.setAttribute('autocomplete','off');
119
+
120
+ if(!inputWrapper.querySelector('datalist'))
121
+ inputWrapper.innerHTML += `<datalist id="${searchID}_list"></datalist>`;
122
+
123
+ inputWrapper.querySelector('datalist').innerHTML = `${Object.keys(searchableTerms).map(term => `<option value="${term}"></option>`).join("")}`;
124
+ }
125
+
126
+ export const addFilterEventListeners = (table, form, pagination, wrapper, savedTableBody) => {
125
127
 
126
- // Create the form
127
- const filterTitle = filterColumns.length == 1 ? "Filter by "+filterColumns[0].textContent : "Filter"; // Update title if only one filter is chosen
128
- const checkboxClass = filterColumns.length == 1 ? "d-none" : "d-sm-flex"; // Hide controls when only one filter is chosen
129
-
130
- form.innerHTML = `<div class="col-sm-6 col-md-4 pb-3">
131
- <div class="form-control__wrapper form-control-inline mb-0">
132
- <label for="${randID}_filter" class="form-label">${filterTitle}:</label>
133
- <input type="search" name="${randID}_filter" id="${randID}_filter" class="form-control form-control-sm" placeholder="" list="${randID}_list" />
134
- </div>
135
- <datalist id="${randID}_list">
136
- ${Object.keys(searchableTerms).map(term => `<option value="${term}"></option>`).join("")}
137
- </datalist>
138
- </div>
139
- <div class="col-md-8 align-items-center pb-3 ${checkboxClass}">
140
- ${`<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("")}
141
- </div>`;
142
-
143
- // Add before the actual table
144
- tableElement.prepend(form)
128
+ var timer;
129
+
130
+ // Check what conditions are set on the table to see what the form actions are
131
+ let formSubmit = function(){
132
+
133
+ if(form.hasAttribute('data-ajax'))
134
+ loadAjaxTable(table, form, pagination,wrapper);
135
+ else if(form.hasAttribute('data-submit'))
136
+ form.submit();
137
+ else {
138
+ filterTable(table, form, wrapper);
139
+ createPaginationButttons(wrapper,pagination);
140
+ }
145
141
  }
146
142
 
147
- const filterTable = function(searchTerm){
143
+ form.addEventListener('keyup', (event) => {
148
144
 
149
- // Create an array of rows that match the search term
150
- let tableArr = [];
151
- Array.from(storedData.querySelectorAll('tr')).forEach((tableRow, index) => {
145
+ clearTimeout(timer);
152
146
 
153
- // We want one long search string per row including each filterable table cell
154
- let rowSearchString = '';
155
- Array.from(tableElement.querySelectorAll('[type="checkbox"]:checked + label')).forEach((label, index) => {
156
- rowSearchString += tableRow.querySelector('td[data-label="'+label.textContent+'"]').textContent+' | ';
157
- });
147
+ if (event && event.target instanceof HTMLElement && event.target.closest('[data-search]')){
158
148
 
159
- // Check if the table row search string contains the search term
160
- if(rowSearchString.indexOf(searchTerm) >= 0){
149
+ timer = setTimeout(function(){
150
+ formSubmit();
151
+ }, 500);
152
+ };
153
+ });
161
154
 
162
- const dataRow = { row: tableRow }
163
- tableArr.push(dataRow);
164
- }
165
- });
155
+ form.addEventListener('change', (event) => {
166
156
 
167
- // Create a string to return and populate the tbody
168
- let strTbody = '';
169
- tableArr.forEach((tableRow, index) => {
170
- strTbody += tableRow.row.outerHTML;
171
- });
172
- tbody.innerHTML = strTbody;
157
+ clearTimeout(timer);
158
+
159
+ if (event && event.target instanceof HTMLElement && event.target.closest('[data-sort]')){
160
+
161
+ if(!form.hasAttribute('data-submit'))
162
+ sortTable(table, form, savedTableBody);
173
163
 
174
- // Dispatch the filter event.
175
- tableElement.dispatchEvent(filteredEvent);
176
- }
164
+ formSubmit();
165
+ }
177
166
 
178
- const createFilterList = function(){
167
+ if (event && event.target instanceof HTMLElement && event.target.closest('[data-search]')){
168
+
169
+ formSubmit();
170
+ }
179
171
 
180
- // Check which options are checked
181
- let filterOptions = [];
182
- Array.from(tableElement.querySelectorAll('[type="checkbox"]:checked + label')).forEach((label, index) => {
183
- filterOptions.push(label.textContent);
184
- });
172
+ if (event && event.target instanceof HTMLElement && event.target.closest('[data-filter]') && !event.target.closest('form dialog')){
173
+
174
+ formSubmit();
175
+ }
185
176
 
186
- // Build up the list of searchable terms
187
- let searchableTerms = [];
188
- filterOptions.forEach((option, index) => {
189
- Array.from(tableElement.querySelectorAll('td[data-label="'+option+'"]')).forEach((label, index) => {
190
- searchableTerms[label.textContent] = label.textContent;
191
- });
192
- });
177
+ if (event && event.target instanceof HTMLElement && event.target.closest('[data-show]')){
178
+
179
+ formSubmit();
180
+ }
181
+ });
193
182
 
194
- // Rebuild the list
195
- let dataList = tableElement.querySelector('datalist');
196
- dataList.innerHTML = Object.keys(searchableTerms).map(term => `<option value="${term}"></option>`).join("");
197
- }
198
183
 
199
- // On page load check if filters are needed
200
- if(Array.from(tableElement.querySelectorAll('[data-filterable]')).length){
184
+ form.addEventListener('click', (event) => {
201
185
 
202
- // Create the filter options
203
- createFilterForm(tableElement,Array.from(tableElement.querySelectorAll('[data-filterable]')).length);
186
+ clearTimeout(timer);
187
+
188
+ if (event && event.target instanceof HTMLElement && event.target.closest('dialog button:not([type="button"])')){
189
+
190
+ let button = event.target.closest('dialog button:not([type="button"])');
191
+ let modal = button.closest('dialog');
204
192
 
205
- // Add event handlers for the filter options
206
- tableElement.addEventListener('keyup', function(e){
207
- for (var target = e.target; target && target != this; target = target.parentNode) {
208
- if (target.matches('input[type="search"]')) {
193
+ modal.close();
194
+ }
209
195
 
210
- const searchTerm = target.value;
211
- filterTable(searchTerm)
212
- }
213
- }
214
- });
196
+ // Prevent the form from submitting
197
+ if (event && event.target instanceof HTMLElement && event.target.closest('.dialog__close')){
198
+
199
+ event.preventDefault();
200
+ event.stopPropagation();
201
+ }
215
202
 
216
- tableElement.addEventListener('change', function(e){
217
- for (var target = e.target; target && target != this; target = target.parentNode) {
218
- if (target.matches('input[type="search"]')) {
203
+ if (event && event.target instanceof HTMLElement && event.target.closest('[data-clear]')){
204
+
205
+ form.reset();
219
206
 
220
- const searchTerm = target.value;
221
- filterTable(searchTerm)
222
- }
223
- }
224
- });
207
+ if(!form.hasAttribute('data-submit'))
208
+ sortTable(table, form, savedTableBody);
225
209
 
226
- tableElement.addEventListener('change', function(e){
227
- for (var target = e.target; target && target != this; target = target.parentNode) {
228
- if (target.matches('input[type="checkbox"]')) {
210
+ formSubmit();
211
+ }
212
+ });
229
213
 
230
- const searchTerm = tableElement.querySelector('input[type="search"]').value;
231
- filterTable(searchTerm)
232
- createFilterList()
233
- }
234
- }
235
- });
214
+ form.addEventListener('submit', (event) => {
215
+
216
+ clearTimeout(timer);
217
+
218
+ if(!form.hasAttribute('data-submit'))
219
+ event.preventDefault();
220
+
221
+ formSubmit();
222
+ });
223
+ }
224
+
225
+ export const sortTable = (table, form, savedTableBody) => {
226
+
227
+ if(form.getAttribute('data-ajax')){
228
+ return false;
229
+ }
230
+
231
+ let tbody = table.querySelector('tbody');
232
+ let select = form.querySelector('[data-sort]');
233
+ let selectedOption = select.querySelector(`option:nth-child(${select.selectedIndex + 1})`);
234
+
235
+ let sortBy = selectedOption.getAttribute('data-sort');
236
+ let order = selectedOption.getAttribute('data-order');
237
+ let format = selectedOption.getAttribute('data-format');
238
+
239
+ if(!sortBy){
240
+
241
+ tbody.innerHTML = savedTableBody.innerHTML;
242
+ addDataAttributes(table);
243
+ return false;
244
+ }
245
+
246
+ let orderArray = [];
247
+ if(!['asc','desc','descending'].includes(order)){
248
+ orderArray = order.split(',');
236
249
  }
237
- // #endregion Filters
238
250
 
239
- // #region Pagination
240
- const paginateRows = function(show, page){
251
+ // Create an array from the table rows, the index created is then used to sort the array
252
+ let tableArr = [];
253
+ Array.from(tbody.querySelectorAll('tr')).forEach((tableRow, index) => {
241
254
 
242
- // 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.
243
- let style = document.getElementById(randID+'_style');
255
+ let rowIndex = tableRow.querySelector('td[data-label="'+sortBy+'"], th[data-label="'+sortBy+'"]').textContent.trim();
244
256
 
245
- if(style == null){
246
- style = document.createElement("style");
247
- style.setAttribute('id',randID+'_style')
257
+ // If a predefined order set replace the search term with an ordered numeric value so it can be sorted
258
+ if(orderArray.length && orderArray.includes(rowIndex)){
259
+ rowIndex = orderArray.indexOf(rowIndex);
248
260
  }
249
261
 
250
- const startShowing = (show*(page-1))+1;
251
- const stopShowing = show*(page);
262
+ if(isNumeric(rowIndex))
263
+ rowIndex = zeroPad(rowIndex,10)
264
+
265
+ // If the sort format is date then lets transform the index to a sortable date (this is never displayed)
266
+ if(format && format == "date")
267
+ rowIndex = new Date(rowIndex);
268
+
269
+ const dataRow = {
270
+ index: rowIndex,
271
+ row: tableRow
272
+ }
273
+ tableArr.push(dataRow);
274
+ });
275
+
276
+ // Sort array alphabetically
277
+ tableArr.sort((a, b) => (a.index > b.index) ? 1 : -1)
278
+
279
+ // Reverse if descending
280
+ if(order == "descending" || order == "desc")
281
+ tableArr = tableArr.reverse();
282
+
283
+ // Create a string to return and populate the tbody
284
+ let strTbody = '';
285
+ tableArr.forEach((tableRow, index) => {
286
+ strTbody += tableRow.row.outerHTML;
287
+ });
288
+ tbody.innerHTML = strTbody;
289
+ }
290
+
291
+ export const filterTable = (table, form, wrapper) => {
252
292
 
253
- style.innerHTML = `
254
- #${randID} tbody tr {
255
- display: none;
293
+ table.classList.remove('table--filtered');
294
+
295
+ let filters = [];
296
+ let searches = [];
297
+ let matched = 0;
298
+ let page = form.querySelector('[data-pagination]') ? parseInt(form.querySelector('[data-pagination]').value) : 1;
299
+ let showRows = form.querySelector('[data-show]') ? parseInt(form.querySelector('[data-show]').value) : 15;
300
+
301
+ // Filter
302
+ let filterInputs = Array.from(form.querySelectorAll('[data-filter]'));
303
+
304
+ filterInputs.forEach((filterInput, index) => {
305
+
306
+ // Ignore uncked radio inputs
307
+ if(filterInput.type == 'radio' && !filterInput.checked){
308
+ return;
256
309
  }
257
- #${randID} tbody tr:nth-child(${startShowing}),
258
- #${randID} tbody tr:nth-child(${startShowing}) ~ tr{
259
- display: block;
310
+
311
+ if(filterInput.type == 'checkbox' && !filterInput.checked){
312
+ return;
260
313
  }
261
- @media screen and (min-width: 36em) {
262
- #${randID} tbody tr:nth-child(${startShowing}),
263
- #${randID} tbody tr:nth-child(${startShowing}) ~ tr{
264
- display: table-row;
314
+
315
+
316
+ if(filterInput.getAttribute('data-filter') == "multi"){
317
+
318
+ for (const [key, value] of Object.entries(JSON.parse(filterInput.value))) {
319
+ filters[filterInput.getAttribute('data-filter')].push(value);
265
320
  }
266
321
  }
267
- #${randID} tbody tr:nth-child(${stopShowing}) ~ tr{
268
- display: none;
322
+ else if (filterInput.value) {
323
+
324
+ if(!filters[filterInput.getAttribute('data-filter')])
325
+ filters[filterInput.getAttribute('data-filter')] = new Array();
326
+
327
+ filters[filterInput.getAttribute('data-filter')].push(filterInput.value);
269
328
  }
270
- `;
271
329
 
272
- tableElement.append(style);
330
+ });
331
+
332
+
333
+ // Add search columns too
334
+ if(form.querySelector('[data-search]')){
335
+ let searchInput = form.querySelector('[data-search]');
336
+ let searchColumns = form.querySelector('[data-search]').getAttribute('data-search').split(',');
337
+
338
+ searchColumns.forEach((column, index) => {
339
+
340
+ searches.push({'column':`${column.trim()}`,'value':`${searchInput.value}`});
341
+ });
273
342
  }
274
343
 
275
- // On page load check if the table should be paginated
276
- if(tableElement.getAttribute('data-show')){
344
+ //Display the filter count
345
+ Array.from(form.querySelectorAll('[data-filter-count]')).forEach((element, index) => {
346
+ element.innerHTML = '';
347
+ });
348
+ if(filters.length) {
349
+
350
+ Array.from(form.querySelectorAll('[data-filter-count]')).forEach((element, index) => {
351
+ element.innerHTML += `(${filters.length})`;
352
+ });
353
+ }
277
354
 
278
- const show = parseInt(tableElement.getAttribute('data-show'));
279
- const page = parseInt(tableElement.getAttribute('data-page')) ? parseInt(tableElement.getAttribute('data-page')) : 1;
280
- const totalRows = tableElement.querySelectorAll('tbody tr').length;
355
+ // Stop function if no filters identified
356
+ if(!searches.length && !filters.length)
357
+ return false;
358
+
359
+ table.classList.add('table--filtered');
360
+
361
+ // Reset
362
+ Array.from(table.querySelectorAll('tbody tr')).forEach((row, index) => {
363
+ row.classList.remove('filtered');
364
+ row.classList.remove('filtered--matched');
365
+ row.classList.remove('filtered--show');
366
+
367
+ row.removeAttribute('data-filtered-by');
368
+ });
369
+
370
+ // Filter the table
371
+
372
+ for (const [key, filterValue] of Object.entries(filters)) {
373
+
374
+ console.log(filterValue)
375
+ Array.from(table.querySelectorAll('tbody tr:not(.filtered)')).forEach((row, index) => {
376
+
377
+ let isMatched = false;
378
+ filterValue.forEach((filter, index) => {
379
+
380
+ let filterTd = row.querySelector(`[data-label="${key}"]`)
381
+
382
+ // Dynamic values
383
+ if(filter && filter == "$today")
384
+ filter = formatCell('date', new Date());
385
+ else if(filter && filter == "$yesterday"){
386
+
387
+ let yesterday = new Date();
388
+ yesterday.setDate(yesterday.getDate() - 1);
389
+ filter = formatCell('date', yesterday);
390
+ }
391
+ else if(filter && (filter == "$thisWeek" || filter == "$lastWeek")){
281
392
 
282
- if(show < totalRows){
283
- paginateRows(show,page);
284
- createPaginationForm(randID,tableElement,show,page,totalRows);
285
- createPaginationButttons(randID,tableElement,show,page,totalRows);
393
+ let today = new Date();
394
+ let mondayThisWeek = new Date(today.setDate(today.getDate() - (today.getDay()-1)));
395
+ let sundayThisWeek = new Date(today.setDate(today.getDate() - today.getDay() + 7));
396
+ let checkDate = new Date(filterTd.textContent.toLowerCase());
286
397
 
287
- tableElement.addEventListener('change', function(e){
288
- for (var target = e.target; target && target != this; target = target.parentNode) {
289
- if (target.matches('.table__pagination input[type="number"]')) {
398
+ today.setHours(0, 0, 0, 0);
399
+ mondayThisWeek.setHours(0, 0, 0, 0);
400
+ sundayThisWeek.setHours(0, 0, 0, 0);
401
+ checkDate.setHours(0, 0, 0, 0);
290
402
 
291
- paginateRows(target.value,page);
292
- createPaginationButttons(randID,tableElement,target.value,page,totalRows);
293
- tableElement.setAttribute('data-show',target.value)
403
+ if(filter == "$thisWeek"){
404
+ isMatched = (checkDate >= mondayThisWeek && checkDate <= sundayThisWeek);
294
405
  }
295
- }
296
- });
406
+ else {
407
+ let mondayLastWeek = new Date(mondayThisWeek.setDate(mondayThisWeek.getDate() - 7));
408
+ let sundayLastWeek = new Date(sundayThisWeek.setDate(sundayThisWeek.getDate() - 7));
297
409
 
298
- tableElement.addEventListener('click', function(e){
299
- for (var target = e.target; target && target != this; target = target.parentNode) {
300
- if (target.matches('.page-item:not(.active):not(.disabled) .page-link')) {
410
+ mondayLastWeek.setHours(0, 0, 0, 0);
411
+ sundayLastWeek.setHours(0, 0, 0, 0);
301
412
 
302
- paginateRows(tableElement.getAttribute('data-show'),target.getAttribute('data-page'));
303
- createPaginationButttons(randID,tableElement,tableElement.getAttribute('data-show'),target.getAttribute('data-page'),totalRows);
413
+ isMatched = (checkDate >= mondayLastWeek && checkDate <= sundayLastWeek);
304
414
  }
305
415
  }
306
- }, false);
416
+ else if(filter && filter == "$thisMonth"){
307
417
 
308
- tableElement.addEventListener('change', function(e){
309
- for (var target = e.target; target && target != this; target = target.parentNode) {
310
- if (target.matches('.table__pagination select')) {
418
+ let today = new Date(), year = today.getFullYear(), month = today.getMonth();
311
419
 
312
- paginateRows(tableElement.getAttribute('data-show'),target.value);
313
- createPaginationButttons(randID,tableElement,tableElement.getAttribute('data-show'),target.value,totalRows);
314
- }
420
+ var firstDayMonth = new Date(year, month, 1);
421
+ var lastDayMonth = new Date(year, month + 1, 0);
422
+ let checkDate = new Date(filterTd.textContent.toLowerCase());
423
+
424
+ firstDayMonth.setHours(0, 0, 0, 0);
425
+ lastDayMonth.setHours(0, 0, 0, 0);
426
+ checkDate.setHours(0, 0, 0, 0);
427
+
428
+ isMatched = (checkDate >= firstDayMonth && checkDate <= lastDayMonth);
315
429
  }
316
- });
317
- }
318
- }
319
- // #endregion Pagination
320
-
321
- // #region Reorderable
322
- // Set the row thats being dragged and copy the row
323
- function setDraggedRow(e) {
324
- e.dataTransfer.setData("text/plain", e.target.id);
325
- draggedRow = e.target;
326
- e.target.classList.add('tr--dragging');
327
- }
430
+ else if(filter && filter == "$lastMonth"){
328
431
 
329
- // Create the order column and event handler for rows
330
- const setReorderRows = function(){
432
+ let today = new Date(), year = today.getFullYear(), month = today.getMonth();
331
433
 
332
- Array.from(tbody.querySelectorAll('tr')).forEach((tableRow, index) => {
434
+ var firstDayLastMonth = new Date(year, month - 1, 1);
435
+ var lastDayLastMonth = new Date(year, month, 0);
436
+ let checkDate = new Date(filterTd.textContent.toLowerCase());
333
437
 
334
- // Create column if not already created
335
- if(tableRow.querySelector('[data-label="Order"]') == null){
438
+ firstDayLastMonth.setHours(0, 0, 0, 0);
439
+ lastDayLastMonth.setHours(0, 0, 0, 0);
440
+ checkDate.setHours(0, 0, 0, 0);
336
441
 
337
- const orderColumn = document.createElement('th');
338
- orderColumn.innerHTML = index + 1;
339
- orderColumn.setAttribute('data-label','Order');
340
- tableRow.prepend(orderColumn);
341
- }
442
+ isMatched = (checkDate >= firstDayLastMonth && checkDate <= lastDayLastMonth);
443
+ }
444
+
445
+ if(filterTd && filterTd.textContent.toLowerCase().includes(filter.toLowerCase())){
446
+ isMatched = true;
447
+ }
342
448
 
343
- // Make draggable
344
- tableRow.setAttribute('id',randID+'_row_'+(index+1));
345
- tableRow.setAttribute('data-order',index+1);
346
- tableRow.setAttribute('draggable','true');
347
- tableRow.addEventListener("dragstart", setDraggedRow);
348
- });
349
- }
449
+ });
350
450
 
351
- if(tableElement.getAttribute('data-reorder') && tableElement.getAttribute('data-reorder') != "false"){
451
+ if(!isMatched){
352
452
 
353
- // Add column heading
354
- const orderHeading = document.createElement('th');
355
- orderHeading.innerHTML = 'Order';
356
- orderHeading.title = 'Click here to enable re-ordering via drag and drop';
357
- orderHeading.classList.add('table-order-reset');
358
- thead.querySelector('tr').prepend(orderHeading);
453
+ row.classList.add('filtered');
454
+ row.setAttribute('data-filtered-by',key)
455
+ }
456
+
457
+ });
458
+ }
459
+ // Search whats left of the table after filtering
460
+ Array.from(table.querySelectorAll('tbody tr:not(.filtered)')).forEach((row, index) => {
461
+
462
+ let isSearched = searches.length > 0 && searches[0].value.length >= 3 ? false : true;
463
+
464
+ searches.forEach((search, index) => {
359
465
 
360
- setReorderRows();
466
+ let searchTd = row.querySelector(`[data-label="${search.column}"]`);
361
467
 
362
- // Reset order button
363
- tableElement.addEventListener('click', function(e){
364
- for (var target = e.target; target && target != this; target = target.parentNode) {
365
- if (target.matches('.table-order-reset')) {
468
+ if(searchTd && search.value.length >= 3 && searchTd.textContent.toLowerCase().includes(search.value.toLowerCase())){
469
+ isSearched = true;
470
+ }
366
471
 
367
- // unset sort attributes
368
- Array.from(tableElement.querySelectorAll('[data-sortable]')).forEach((col, index) => {
369
- col.setAttribute('aria-sort','none');
370
- });
472
+ });
371
473
 
372
- // Save the sort options on the table element so that it can be re-sorted later
373
- tableElement.removeAttribute('data-sort');
374
- tableElement.removeAttribute('data-sortBy');
474
+ if(!isSearched)
475
+ row.classList.add('filtered');
476
+ });
375
477
 
376
- // Sort the table
377
- sortTable('Order', 'ascending');
478
+ // Work out what to display after pagination
479
+ Array.from(table.querySelectorAll('tbody tr:not(.filtered')).forEach((row, index) => {
378
480
 
379
- Array.from(tableElement.querySelectorAll('tbody tr')).forEach((tableRow, index) => {
481
+ matched++;
380
482
 
381
- tableRow.setAttribute('draggable','true');
382
- });
483
+ row.classList.add('filtered--matched');
484
+ // pagination bit
485
+ if(Math.ceil(matched/showRows) == parseInt(page))
486
+ row.classList.add('filtered--show');
487
+ });
383
488
 
384
- break;
385
- }
386
- }
387
- }, false);
489
+ if(wrapper){
490
+ wrapper.setAttribute('data-page',page);
491
+ wrapper.setAttribute('data-pages',Math.ceil(matched/showRows));
492
+ wrapper.setAttribute('data-total',matched);
493
+ wrapper.setAttribute('data-show',showRows);
494
+ }
388
495
 
496
+ populateDataQueries(table,form);
497
+ }
389
498
 
390
- document.addEventListener("dragover", function( e ) {
391
- // prevent default to allow drop
392
- e.preventDefault();
393
- }, false);
499
+ export const populateDataQueries = (table,form) => {
394
500
 
395
- document.addEventListener("dragenter", function( e ) {
396
- // prevent default to allow drop
397
- e.preventDefault();
398
- e.dataTransfer.dropEffect = "move";
501
+ const dataQueries = Array.from(form.querySelectorAll('[data-query]'));
399
502
 
400
- for (var target = e.target; target && target != this; target = target.parentNode) {
401
- if (target.matches('[data-reorder] tbody tr')) {
503
+ dataQueries.forEach((queryElement, index) => {
402
504
 
403
- target.classList.add('tr--dropable')
404
- }
405
- }
406
- }, false);
505
+ let query = queryElement.getAttribute('data-query');
506
+ let numberOfMatchedRows: 0;
407
507
 
408
- document.addEventListener("dragleave", function( e ) {
409
- // prevent default to allow drop
410
- e.preventDefault();
411
- for (var target = e.target; target && target != this; target = target.parentNode) {
412
- if (target.matches('[data-reorder] tbody tr')) {
508
+ if(query == 'total'){
509
+ numberOfMatchedRows = table.classList.contains('table--filtered') ? table.querySelectorAll('tbody tr:not(.filtered)').length : table.querySelectorAll('tbody tr').length;
510
+ }
511
+ else if(!query.includes(' == ') && query.includes(' & ')){
413
512
 
414
- target.classList.remove('tr--dropable')
415
- }
416
- }
417
- }, false);
513
+ let queries = query.split(' & ');
514
+ let selector = '';
418
515
 
419
- document.addEventListener("drop", function(e) {
516
+ queries.forEach(element => {
517
+ selector += `:not([data-filtered-by="${element}"])`;
518
+ });
420
519
 
421
- e.preventDefault();
520
+ console.log(selector)
521
+ numberOfMatchedRows = Array.from(table.querySelectorAll(`tbody tr${selector}`)).length;
522
+ }
523
+ else if(!query.includes(' == ')){
422
524
 
423
- for (var target = e.target; target && target != this; target = target.parentNode) {
424
- if (target.matches('[data-reorder] tbody tr')) {
525
+ numberOfMatchedRows = Array.from(table.querySelectorAll(`tbody tr:not([data-filtered-by="${query}"])`)).length;
526
+ }
527
+ else if(query.includes(' && ')){
425
528
 
426
- if(target.parentNode != null && draggedRow.parentNode != null && target != draggedRow){
529
+ let queries = query.split(' && ');
427
530
 
428
- draggedRow.parentNode.removeChild( draggedRow );
531
+ numberOfMatchedRows = Array.from(table.querySelectorAll(`tbody tr:not(.filtered)`)).filter(function(row){
429
532
 
430
- if(draggedRow.getAttribute('data-order') > target.getAttribute('data-order'))
431
- target.parentNode.insertBefore(draggedRow, target);
432
- else
433
- target.parentNode.insertBefore(draggedRow, target.nextElementSibling);
533
+ let matched = true;
434
534
 
435
- // Re label the rows
436
- Array.from(tbody.querySelectorAll('tr')).forEach((tableRowOrder, index) => {
437
- tableRowOrder.classList.remove('tr--dragging')
438
- tableRowOrder.classList.remove('tr--dropable')
439
- tableRowOrder.querySelector('th').innerHTML = index + 1;
440
- tableRowOrder.setAttribute('data-order',index+1);
441
- });
535
+ for (const [index, value] of Object.entries(queries)) {
536
+
537
+ let queryParts = value.split(' == ');
442
538
 
443
- tableElement.dispatchEvent(reorderedEvent);
444
- }
445
- break;
539
+ if(!row.querySelector(`td[data-label="${queryParts[0]}"]`) || row.querySelector(`td[data-label="${queryParts[0]}"]`).textContent != `${queryParts[1]}`)
540
+ matched = false;
446
541
  }
447
- }
448
- }, false);
449
542
 
450
- }
451
- // #endregion Reorderable
543
+ return matched;
544
+
545
+ }).length;
546
+ }
547
+ else {
452
548
 
453
- // Watch for the filterable event and re-sort the tbody
454
- tableElement.addEventListener('filtered', function (e) {
549
+ let queryParts = query.split(' == ');
550
+ console.log(queryParts[0]);
551
+ 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){
552
+ return element.textContent === queryParts[1];
553
+ }).length;
554
+ }
455
555
 
456
- if(tableElement.getAttribute('data-sortBy') && tableElement.getAttribute('data-sort'))
457
- sortTable(tableElement.getAttribute('data-sortBy'), tableElement.getAttribute('data-sort'));
556
+ if(queryElement.hasAttribute('data-total'))
557
+ queryElement.setAttribute('data-total', numberOfMatchedRows);
558
+ else
559
+ queryElement.innerHTML = numberOfMatchedRows;
560
+ });
561
+ }
458
562
 
459
- if(tableElement.getAttribute('data-show')){
563
+ // Pagination
564
+ export const addPaginationEventListeners = function(table, form, pagination, wrapper){
460
565
 
461
- const show = parseInt(tableElement.getAttribute('data-show'));
462
- const totalRows = tableElement.querySelectorAll('tbody tr').length;
463
- const tablePagination = tableElement.querySelector('.table__pagination');
566
+ pagination.addEventListener('click', (event) => {
464
567
 
465
- if(tablePagination != null)
466
- tablePagination.remove();
568
+ if (event && event.target instanceof HTMLElement && event.target.closest('[data-page]')){
569
+
570
+ event.preventDefault();
467
571
 
468
- if(show < totalRows){
572
+ let paginationInput = form.querySelector('[data-pagination]');
573
+ let newPage = event.target.closest('[data-page]').getAttribute('data-page');
574
+ paginationInput.value = newPage;
575
+ wrapper.setAttribute('data-page', newPage);
576
+ form.dispatchEvent(new Event("submit"));
469
577
 
470
- paginateRows(show,1);
471
- createPaginationForm(randID,tableElement,show,1,totalRows);
472
- createPaginationButttons(randID,tableElement,show,1,totalRows);
473
- }
578
+ const url = new URL(location);
579
+ url.searchParams.set("page", newPage);
580
+ history.pushState({'type':'pagination','form':form.getAttribute('id'),'page':newPage}, "", url)
474
581
  }
475
582
 
476
- if(tableElement.getAttribute('data-reorder')){
583
+ if (event && event.target instanceof HTMLElement && event.target.closest('[data-show]')){
477
584
 
478
- setReorderRows();
585
+ event.preventDefault();
586
+ let showInput = form.querySelector('[data-show]');
587
+ let showRows = event.target.closest('[data-show]').getAttribute('data-show');
588
+ showInput.value = showRows;
589
+ wrapper.setAttribute('data-show', showRows);
590
+ form.dispatchEvent(new Event("submit"));
479
591
  }
480
- }, false);
592
+ });
593
+ }
481
594
 
482
- tableElement.addEventListener('sorted', function (e) {
595
+ // Export CSV Data
596
+ export const addExportEventListeners = (button, table) => {
483
597
 
484
- if(tableElement.getAttribute('data-reorder')){
598
+ if(!button)
599
+ return false;
485
600
 
486
- setReorderRows();
487
- }
488
- }, false);
601
+ button.addEventListener('click', (event) => {
602
+ exportAsCSV(table);
603
+ });
604
+ }
605
+
606
+ export const exportAsCSV = function(table){
607
+
608
+ var csvData = [];
609
+ // Get each row data
610
+ var rows = table.getElementsByTagName('tr');
611
+ for (var i = 0; i < rows.length; i++) {
489
612
 
490
- tableElement.addEventListener('populated', function (e) {
613
+ // Get each column data
614
+ var cols = rows[i].querySelectorAll('td,th');
491
615
 
492
- var tableFilter = tableElement.querySelector('.table__filters')
493
- tableFilter.remove();
616
+ // Stores each csv row data
617
+ var csvRow = [];
618
+ for (var j = 0; j < cols.length; j++) {
494
619
 
495
- var tablePagination = tableElement.querySelector('.table__pagination')
496
- tablePagination.remove();
620
+ // Get the text data of each cell of a row and push it to csvrow
621
+ csvRow.push(`"${cols[j].textContent}"`);
622
+ }
497
623
 
498
- var newTable = tableElement.cloneNode(true);
499
- tableElement.parentNode.replaceChild(newTable, tableElement);
624
+ // Combine each column value with comma
625
+ csvData.push(csvRow.join(","));
626
+ }
500
627
 
501
- table(newTable);
502
- }, false);
628
+ // Combine each row data with new line character
629
+ csvData = csvData.join('\n');
630
+
631
+ // Create CSV file object and feed our csvData into it
632
+ let CSVFile = new Blob([csvData], {
633
+ type: "text/csv"
634
+ });
635
+
636
+ // Create to temporary link to initiate download process
637
+ var tempLink = document.createElement('a');
638
+ tempLink.download = "export.csv";
639
+ var url = window.URL.createObjectURL(CSVFile);
640
+ tempLink.href = url;
641
+
642
+ // This link should not be displayed
643
+ tempLink.style.display = "none";
644
+ document.body.appendChild(tempLink);
645
+
646
+ // Automatically click the link to trigger download
647
+ tempLink.click();
648
+ document.body.removeChild(tempLink);
503
649
  }
504
650
 
505
- export const createPaginationForm = function(randID,tableElement,show,page,totalRows){
506
-
507
- const form = document.createElement("div");
508
- form.classList.add('table__pagination');
509
- form.classList.add('row');
510
- form.classList.add('pt-3');
511
- form.classList.add('pb-3');
512
-
513
- // Create the form and create a container div to hold the pagination buttons
514
- form.innerHTML = `<div class="col mw-fit-content mb-3">
515
- <div class="form-control__wrapper form-control-inline mb-0">
516
- <label for="${randID}_showing" class="form-label">Showing:</label>
517
- <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}" />
518
- </div>
519
- <datalist id="${randID}_pagination">
520
- <option value="5">5</option>
521
- ${totalRows > 10 ? `<option value="10">10</option>` : ''}
522
- ${totalRows > 20 ? `<option value="20">20</option>` : ''}
523
- <option value="${totalRows}">${totalRows}</option>
524
- </datalist>
525
- </div>
526
- <div class="col mw-fit-content me-auto d-flex align-items-center mb-3"><span class="label">per page</span></div>
527
- <div class="col mw-fit-content d-sm-flex justify-content-end align-items-center" id="${randID}_paginationBtns"></div>`;
528
-
529
- // Add after the actual table
530
- tableElement.append(form)
651
+ // After table is loaded
652
+ export const makeTableFunctional = function(table, form, pagination, wrapper){
653
+
654
+ createMobileButton(table);
655
+ addDataAttributes(table);
656
+ populateDataQueries(table, form);
657
+
658
+ // Work out the largest width of the CTA's in the last column
659
+ if(wrapper && wrapper.classList.contains('table--cta')){
660
+
661
+ const largestWidth = getLargestLastColWidth(table);
662
+ wrapper.style.setProperty("--cta-width", `${largestWidth}rem`);
663
+ }
531
664
  }
532
665
 
533
- export const createPaginationButttons = function(randID,tableElement,show,page,totalRows){
666
+ export const loadAjaxTable = function (table, form, pagination, wrapper){
534
667
 
535
- const paginationButtonsWrapper = document.getElementById(randID+'_paginationBtns')
668
+ const resolvePath = (object, path, defaultValue) => path.split(/[\.\[\]\'\"]/).filter(p => p).reduce((o, p) => o ? o[p] : defaultValue, object);
536
669
 
537
- if(paginationButtonsWrapper == null)
538
- return false;
670
+ let queryString = new URLSearchParams(new FormData(form)).toString();
671
+ let columns = table.querySelectorAll('thead tr th');
672
+ let tbody = table.querySelector('tbody');
539
673
 
540
- const numberPages = Math.ceil(totalRows / show)
674
+ fetch(form.getAttribute('data-ajax'), {
675
+ method: 'get',
676
+ credentials: 'same-origin',
677
+ headers: new Headers({
678
+ 'Content-Type': 'application/json',
679
+ Accept: 'application/json',
680
+ 'X-Requested-With': 'XMLHttpRequest'
681
+ })
682
+ }).then((response) => response.json()).then((response) => {
541
683
 
542
- if(numberPages == 1){ // Remore the buttons or dont display any if we dont need them
543
- paginationButtonsWrapper.innerHTML = '';
544
- }
545
- else if(numberPages < 5){ // If less than 5 pages (which fits comfortably on mobile) we display buttons
684
+ if (response.data) {
546
685
 
547
- let strButtons = '';
686
+ tbody.innerHTML = '';
548
687
 
549
- for (let i = 1; i <= numberPages; i++) {
688
+ response.data.forEach((row, index) => {
550
689
 
551
- if(i == page)
552
- strButtons += `<li class="page-item active" aria-current="page"><span class="page-link">${i}</span></li>`;
553
- else
554
- strButtons += `<li class="page-item"><button class="page-link" data-page="${i}">${i}</button></li>`;
555
- }
690
+ var table_row = document.createElement('tr');
556
691
 
557
- paginationButtonsWrapper.innerHTML = `<span class="pe-2 mb-3">Page: </span><ul class="pagination mb-3">
558
- ${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>`}
559
- ${strButtons}
560
- ${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>`}
561
- </ul>`;
692
+ columns.forEach((col, index) => {
562
693
 
563
- }
564
- else { // If more than 5 lets show a select field instead so that we dont have loads and loads of buttons
694
+ let cellOutput = '';
695
+ var table_cell = document.createElement('td');
696
+ // Add some data to help with the mobile layout design
697
+ table_cell.setAttribute('data-label',col.innerText);
565
698
 
566
- let strOptions = '';
699
+ if(col.getAttribute('data-output')){
700
+ var cellTemplate = col.getAttribute('data-output');
701
+ // Use a regex to replace {var} with actual values from the json data
702
+ cellOutput = cellTemplate.replace( new RegExp(/{(.*?)}/,"gm"), function(matched){ return resolvePath(row, matched.replace('{','').replace('}','')); });
703
+ }
567
704
 
568
- for (let i = 1; i <= numberPages; i++) {
705
+ if(col.hasAttribute('data-format')){
706
+ cellOutput = formatCell(col.getAttribute('data-format'),cellOutput);
707
+ }
569
708
 
570
- if(i == page)
571
- strOptions += `<option value="${i}" selected>Page ${i}</option>`;
572
- else
573
- strOptions += `<option value="${i}">Page ${i}</option>`;
709
+ table_cell.innerHTML = cellOutput;
710
+ table_row.appendChild(table_cell)
711
+ });
712
+
713
+ tbody.appendChild(table_row)
714
+
715
+ });
716
+
717
+ createSearchDataList(table, form)
718
+ // Add data to the pagination
719
+ makeTableFunctional(table, form, pagination, wrapper);
720
+
721
+ wrapper.setAttribute('data-total', (response.meta.total ? response.meta.total : 1));
722
+ wrapper.setAttribute('data-page', (response.meta.current_page ? response.meta.current_page : 1));
723
+ wrapper.setAttribute('data-pages', Math.ceil(wrapper.getAttribute('data-total') / wrapper.getAttribute('data-show')));
724
+
725
+ createPaginationButttons(wrapper, pagination);
726
+
727
+ if(response.data.length == 0){
728
+ tbody.innerHTML = '<tr><td colspan="100%"><span class="h4 m-0">No results found</span></td></tr>';
729
+ }
730
+
731
+ }
732
+ else {
733
+ tbody.innerHTML = '<tr><td colspan="100%"><span class="h6 m-0">Error loading table</span></td></tr>';
574
734
  }
575
735
 
576
- paginationButtonsWrapper.innerHTML = `
577
- <div class="form-control__wrapper page-number mb-2">
578
- <select class="form-select">
579
- ${strOptions}
580
- </select>
581
- </div>
582
- `;
583
- }
736
+ });
584
737
  }
585
738
 
586
- export default table
739
+ export const formatCell = (format, cellOutput) => {
740
+
741
+ switch (format) {
742
+ case 'date':
743
+ cellOutput = new Date(cellOutput).toLocaleDateString('en-gb', { year:"2-digit", month:"long", day: "numeric"});
744
+ break;
745
+ case 'capitalise':
746
+ cellOutput = ucfirst(cellOutput);
747
+ break;
748
+ }
749
+
750
+ return cellOutput;
751
+ }