@iamproperty/components 3.4.6 → 3.5.0

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 (304) hide show
  1. package/assets/bootstrap/LICENSE +22 -0
  2. package/assets/bootstrap/README.md +246 -0
  3. package/assets/bootstrap/js/src/alert.js +87 -0
  4. package/assets/bootstrap/js/src/base-component.js +85 -0
  5. package/assets/bootstrap/js/src/button.js +72 -0
  6. package/assets/bootstrap/js/src/carousel.js +475 -0
  7. package/assets/bootstrap/js/src/collapse.js +302 -0
  8. package/assets/bootstrap/js/src/dom/data.js +55 -0
  9. package/assets/bootstrap/js/src/dom/event-handler.js +320 -0
  10. package/assets/bootstrap/js/src/dom/manipulator.js +71 -0
  11. package/assets/bootstrap/js/src/dom/selector-engine.js +83 -0
  12. package/assets/bootstrap/js/src/dropdown.js +454 -0
  13. package/assets/bootstrap/js/src/modal.js +377 -0
  14. package/assets/bootstrap/js/src/offcanvas.js +283 -0
  15. package/assets/bootstrap/js/src/popover.js +97 -0
  16. package/assets/bootstrap/js/src/scrollspy.js +294 -0
  17. package/assets/bootstrap/js/src/tab.js +305 -0
  18. package/assets/bootstrap/js/src/toast.js +225 -0
  19. package/assets/bootstrap/js/src/tooltip.js +633 -0
  20. package/assets/bootstrap/js/src/util/backdrop.js +149 -0
  21. package/assets/bootstrap/js/src/util/component-functions.js +34 -0
  22. package/assets/bootstrap/js/src/util/config.js +66 -0
  23. package/assets/bootstrap/js/src/util/focustrap.js +115 -0
  24. package/assets/bootstrap/js/src/util/index.js +336 -0
  25. package/assets/bootstrap/js/src/util/sanitizer.js +118 -0
  26. package/assets/bootstrap/js/src/util/scrollbar.js +114 -0
  27. package/assets/bootstrap/js/src/util/swipe.js +146 -0
  28. package/assets/bootstrap/js/src/util/template-factory.js +160 -0
  29. package/assets/bootstrap/package.json +181 -0
  30. package/assets/bootstrap/scss/_accordion.scss +149 -0
  31. package/assets/bootstrap/scss/_alert.scss +71 -0
  32. package/assets/bootstrap/scss/_badge.scss +38 -0
  33. package/assets/bootstrap/scss/_breadcrumb.scss +40 -0
  34. package/assets/bootstrap/scss/_button-group.scss +142 -0
  35. package/assets/bootstrap/scss/_buttons.scss +207 -0
  36. package/assets/bootstrap/scss/_card.scss +234 -0
  37. package/assets/bootstrap/scss/_carousel.scss +226 -0
  38. package/assets/bootstrap/scss/_close.scss +40 -0
  39. package/assets/bootstrap/scss/_containers.scss +41 -0
  40. package/assets/bootstrap/scss/_dropdown.scss +249 -0
  41. package/assets/bootstrap/scss/_forms.scss +9 -0
  42. package/assets/bootstrap/scss/_functions.scss +302 -0
  43. package/assets/bootstrap/scss/_grid.scss +33 -0
  44. package/assets/bootstrap/scss/_helpers.scss +10 -0
  45. package/assets/bootstrap/scss/_images.scss +42 -0
  46. package/assets/bootstrap/scss/_list-group.scss +192 -0
  47. package/assets/bootstrap/scss/_maps.scss +54 -0
  48. package/assets/bootstrap/scss/_mixins.scss +43 -0
  49. package/assets/bootstrap/scss/_modal.scss +237 -0
  50. package/assets/bootstrap/scss/_nav.scss +172 -0
  51. package/assets/bootstrap/scss/_navbar.scss +278 -0
  52. package/assets/bootstrap/scss/_offcanvas.scss +144 -0
  53. package/assets/bootstrap/scss/_pagination.scss +109 -0
  54. package/assets/bootstrap/scss/_placeholders.scss +51 -0
  55. package/assets/bootstrap/scss/_popover.scss +196 -0
  56. package/assets/bootstrap/scss/_progress.scss +59 -0
  57. package/assets/bootstrap/scss/_reboot.scss +610 -0
  58. package/assets/bootstrap/scss/_root.scss +73 -0
  59. package/assets/bootstrap/scss/_spinners.scss +85 -0
  60. package/assets/bootstrap/scss/_tables.scss +164 -0
  61. package/assets/bootstrap/scss/_toasts.scss +73 -0
  62. package/assets/bootstrap/scss/_tooltip.scss +120 -0
  63. package/assets/bootstrap/scss/_transitions.scss +27 -0
  64. package/assets/bootstrap/scss/_type.scss +106 -0
  65. package/assets/bootstrap/scss/_utilities.scss +647 -0
  66. package/assets/bootstrap/scss/_variables.scss +1634 -0
  67. package/assets/bootstrap/scss/bootstrap-grid.scss +64 -0
  68. package/assets/bootstrap/scss/bootstrap-reboot.scss +9 -0
  69. package/assets/bootstrap/scss/bootstrap-utilities.scss +18 -0
  70. package/assets/bootstrap/scss/bootstrap.scss +51 -0
  71. package/assets/bootstrap/scss/forms/_floating-labels.scss +75 -0
  72. package/assets/bootstrap/scss/forms/_form-check.scss +175 -0
  73. package/assets/bootstrap/scss/forms/_form-control.scss +194 -0
  74. package/assets/bootstrap/scss/forms/_form-range.scss +91 -0
  75. package/assets/bootstrap/scss/forms/_form-select.scss +71 -0
  76. package/assets/bootstrap/scss/forms/_form-text.scss +11 -0
  77. package/assets/bootstrap/scss/forms/_input-group.scss +132 -0
  78. package/assets/bootstrap/scss/forms/_labels.scss +36 -0
  79. package/assets/bootstrap/scss/forms/_validation.scss +12 -0
  80. package/assets/bootstrap/scss/helpers/_clearfix.scss +3 -0
  81. package/assets/bootstrap/scss/helpers/_color-bg.scss +10 -0
  82. package/assets/bootstrap/scss/helpers/_colored-links.scss +12 -0
  83. package/assets/bootstrap/scss/helpers/_position.scss +36 -0
  84. package/assets/bootstrap/scss/helpers/_ratio.scss +26 -0
  85. package/assets/bootstrap/scss/helpers/_stacks.scss +15 -0
  86. package/assets/bootstrap/scss/helpers/_stretched-link.scss +15 -0
  87. package/assets/bootstrap/scss/helpers/_text-truncation.scss +7 -0
  88. package/assets/bootstrap/scss/helpers/_visually-hidden.scss +8 -0
  89. package/assets/bootstrap/scss/helpers/_vr.scss +8 -0
  90. package/assets/bootstrap/scss/mixins/_alert.scss +15 -0
  91. package/assets/bootstrap/scss/mixins/_backdrop.scss +14 -0
  92. package/assets/bootstrap/scss/mixins/_banner.scss +9 -0
  93. package/assets/bootstrap/scss/mixins/_border-radius.scss +78 -0
  94. package/assets/bootstrap/scss/mixins/_box-shadow.scss +18 -0
  95. package/assets/bootstrap/scss/mixins/_breakpoints.scss +127 -0
  96. package/assets/bootstrap/scss/mixins/_buttons.scss +70 -0
  97. package/assets/bootstrap/scss/mixins/_caret.scss +64 -0
  98. package/assets/bootstrap/scss/mixins/_clearfix.scss +9 -0
  99. package/assets/bootstrap/scss/mixins/_color-scheme.scss +7 -0
  100. package/assets/bootstrap/scss/mixins/_container.scss +11 -0
  101. package/assets/bootstrap/scss/mixins/_deprecate.scss +10 -0
  102. package/assets/bootstrap/scss/mixins/_forms.scss +152 -0
  103. package/assets/bootstrap/scss/mixins/_gradients.scss +47 -0
  104. package/assets/bootstrap/scss/mixins/_grid.scss +151 -0
  105. package/assets/bootstrap/scss/mixins/_image.scss +16 -0
  106. package/assets/bootstrap/scss/mixins/_list-group.scss +24 -0
  107. package/assets/bootstrap/scss/mixins/_lists.scss +7 -0
  108. package/assets/bootstrap/scss/mixins/_pagination.scss +10 -0
  109. package/assets/bootstrap/scss/mixins/_reset-text.scss +17 -0
  110. package/assets/bootstrap/scss/mixins/_resize.scss +6 -0
  111. package/assets/bootstrap/scss/mixins/_table-variants.scss +24 -0
  112. package/assets/bootstrap/scss/mixins/_text-truncate.scss +8 -0
  113. package/assets/bootstrap/scss/mixins/_transition.scss +26 -0
  114. package/assets/bootstrap/scss/mixins/_utilities.scss +97 -0
  115. package/assets/bootstrap/scss/mixins/_visually-hidden.scss +29 -0
  116. package/assets/bootstrap/scss/utilities/_api.scss +47 -0
  117. package/assets/bootstrap/scss/vendor/_rfs.scss +354 -0
  118. package/assets/css/components/accordion.css +1 -1
  119. package/assets/css/components/accordion.css.map +1 -1
  120. package/assets/css/components/admin-panel.css +1 -0
  121. package/assets/css/components/admin-panel.css.map +1 -0
  122. package/assets/css/components/alert.css +1 -1
  123. package/assets/css/components/alert.css.map +1 -1
  124. package/assets/css/components/applied-filters.css +1 -0
  125. package/assets/css/components/applied-filters.css.map +1 -0
  126. package/assets/css/components/card.css +1 -1
  127. package/assets/css/components/card.css.map +1 -1
  128. package/assets/css/components/carousel.css +1 -1
  129. package/assets/css/components/carousel.css.map +1 -1
  130. package/assets/css/components/charts.css +1 -1
  131. package/assets/css/components/charts.css.map +1 -1
  132. package/assets/css/components/container.css +1 -1
  133. package/assets/css/components/container.css.map +1 -1
  134. package/assets/css/components/dialog.css +1 -0
  135. package/assets/css/components/dialog.css.map +1 -0
  136. package/assets/css/components/forms.css +1 -1
  137. package/assets/css/components/forms.css.map +1 -1
  138. package/assets/css/components/header.css +1 -1
  139. package/assets/css/components/header.css.map +1 -1
  140. package/assets/css/components/lists.css +1 -1
  141. package/assets/css/components/lists.css.map +1 -1
  142. package/assets/css/components/nav.css +1 -1
  143. package/assets/css/components/nav.css.map +1 -1
  144. package/assets/css/components/pagination.css +1 -0
  145. package/assets/css/components/pagination.css.map +1 -0
  146. package/assets/css/components/property-searchbar.css +1 -1
  147. package/assets/css/components/property-searchbar.css.map +1 -1
  148. package/assets/css/components/stepper.css +1 -1
  149. package/assets/css/components/stepper.css.map +1 -1
  150. package/assets/css/components/table.css +1 -0
  151. package/assets/css/components/table.css.map +1 -0
  152. package/assets/css/components/tabs.css +1 -1
  153. package/assets/css/components/tabs.css.map +1 -1
  154. package/assets/css/components/tooltips.css +1 -1
  155. package/assets/css/components/tooltips.css.map +1 -1
  156. package/assets/css/core.min.css +1 -1
  157. package/assets/css/core.min.css.map +1 -1
  158. package/assets/css/style.min.css +1 -1
  159. package/assets/css/style.min.css.map +1 -1
  160. package/assets/js/bundle.js +18 -11
  161. package/assets/js/components/accordion/accordion.component.js +6 -0
  162. package/assets/js/components/accordion/accordion.component.min.js +3 -3
  163. package/assets/js/components/accordion/accordion.component.min.js.map +1 -1
  164. package/assets/js/components/applied-filters/applied-filters.component.js +26 -0
  165. package/assets/js/components/card/card.component.js +91 -0
  166. package/assets/js/components/card/card.component.min.js +21 -0
  167. package/assets/js/components/card/card.component.min.js.map +1 -0
  168. package/assets/js/components/filterlist/filterlist.component.js +49 -0
  169. package/assets/js/components/filterlist/filterlist.component.min.js +23 -0
  170. package/assets/js/components/filterlist/filterlist.component.min.js.map +1 -0
  171. package/assets/js/components/header/header.component.js +6 -0
  172. package/assets/js/components/header/header.component.min.js +5 -5
  173. package/assets/js/components/header/header.component.min.js.map +1 -1
  174. package/assets/js/components/pagination/pagination.component.js +34 -0
  175. package/assets/js/components/table/table.component.js +108 -0
  176. package/assets/js/components/table/table.component.min.js +24 -0
  177. package/assets/js/components/table/table.component.min.js.map +1 -0
  178. package/assets/js/components/tabs/tabs.component.js +6 -0
  179. package/assets/js/components/tabs/tabs.component.min.js +17 -0
  180. package/assets/js/components/tabs/tabs.component.min.js.map +1 -0
  181. package/assets/js/dynamic.js +7 -18
  182. package/assets/js/dynamic.min.js +2 -53
  183. package/assets/js/dynamic.min.js.map +1 -1
  184. package/assets/js/flat-components.js +27 -9
  185. package/assets/js/modules/applied-filters.js +100 -0
  186. package/assets/js/modules/data-layer.js +45 -0
  187. package/assets/js/modules/filterlist.js +32 -0
  188. package/assets/js/modules/helpers.js +102 -49
  189. package/assets/js/modules/pagination.js +33 -0
  190. package/assets/js/modules/table.js +506 -420
  191. package/assets/js/modules/tabs.js +6 -0
  192. package/assets/js/modules/youtubevideo.js +53 -61
  193. package/assets/js/scripts.bundle.js +77 -62
  194. package/assets/js/scripts.bundle.js.map +1 -1
  195. package/assets/js/scripts.bundle.min.js +2 -2
  196. package/assets/js/scripts.bundle.min.js.map +1 -1
  197. package/assets/js/tests/filterlist.spec.js +22 -0
  198. package/assets/js/tests/pagination.spec.js +15 -0
  199. package/assets/js/tests/table.spec.js +149 -0
  200. package/assets/sass/_components.scss +1 -2
  201. package/assets/sass/_corefiles.scss +20 -19
  202. package/assets/sass/_forms.scss +7 -7
  203. package/assets/sass/_functions/functions.scss +1 -1
  204. package/assets/sass/_functions/mixins.scss +19 -21
  205. package/assets/sass/_functions/utilities.scss +67 -9
  206. package/assets/sass/_functions/variables.scss +109 -55
  207. package/assets/sass/_tests/colours.spec.scss +8 -22
  208. package/assets/sass/components/accordion.scss +13 -0
  209. package/assets/sass/components/admin-panel.scss +106 -0
  210. package/assets/sass/components/alert.scss +22 -0
  211. package/assets/sass/components/applied-filters.scss +65 -0
  212. package/assets/sass/components/card.scss +177 -233
  213. package/assets/sass/components/carousel.scss +72 -0
  214. package/assets/sass/components/charts.scss +41 -1
  215. package/assets/sass/components/container.scss +8 -3
  216. package/assets/sass/components/dialog.scss +208 -0
  217. package/assets/sass/components/forms.scss +37 -5
  218. package/assets/sass/components/lists.scss +29 -0
  219. package/assets/sass/components/nav.scss +6 -2
  220. package/assets/sass/components/pagination.scss +140 -0
  221. package/assets/sass/components/stepper.scss +3 -3
  222. package/assets/sass/components/table.scss +423 -0
  223. package/assets/sass/components/tabs.scss +20 -7
  224. package/assets/sass/components/tooltips.scss +1 -1
  225. package/assets/sass/foundations/buttons.scss +366 -0
  226. package/assets/sass/foundations/icons.scss +1 -1
  227. package/assets/sass/foundations/links.scss +125 -0
  228. package/assets/sass/foundations/media.scss +1 -1
  229. package/assets/sass/foundations/reboot.scss +21 -17
  230. package/assets/sass/foundations/root.scss +9 -29
  231. package/assets/sass/foundations/type.scss +1 -1
  232. package/assets/svg/illustrations/table.svg +165 -0
  233. package/assets/ts/bundle.ts +23 -12
  234. package/assets/ts/components/accordion/accordion.component.ts +7 -0
  235. package/assets/ts/components/applied-filters/README.md +5 -0
  236. package/assets/ts/components/applied-filters/applied-filters.component.ts +33 -0
  237. package/assets/ts/components/card/README.md +22 -0
  238. package/assets/ts/components/card/card.component.ts +117 -0
  239. package/assets/ts/components/filterlist/README.md +17 -0
  240. package/assets/ts/components/filterlist/filterlist.component.ts +60 -0
  241. package/assets/ts/components/header/header.component.ts +8 -0
  242. package/assets/ts/components/pagination/README.md +11 -0
  243. package/assets/ts/components/pagination/pagination.component.ts +45 -0
  244. package/assets/ts/components/table/README.md +23 -0
  245. package/assets/ts/components/table/table.component.ts +133 -0
  246. package/assets/ts/components/tabs/tabs.component.ts +7 -0
  247. package/assets/ts/dynamic.ts +12 -19
  248. package/assets/ts/flat-components.ts +37 -9
  249. package/assets/ts/modules/applied-filters.ts +146 -0
  250. package/assets/ts/modules/data-layer.ts +58 -0
  251. package/assets/ts/modules/filterlist.ts +46 -0
  252. package/assets/ts/modules/helpers.ts +129 -58
  253. package/assets/ts/modules/pagination.ts +44 -0
  254. package/assets/ts/modules/table.ts +598 -433
  255. package/assets/ts/modules/tabs.ts +8 -1
  256. package/assets/ts/modules/youtubevideo.ts +58 -63
  257. package/assets/ts/tests/filterlist.spec.ts +29 -0
  258. package/assets/ts/tests/pagination.spec.ts +21 -0
  259. package/assets/ts/tests/table.spec.ts +194 -0
  260. package/dist/components.es.js +1267 -1295
  261. package/dist/components.umd.js +70 -65
  262. package/dist/style.css +1 -1
  263. package/package.json +8 -5
  264. package/src/components/AppliedFilters/AppliedFilters.vue +20 -0
  265. package/src/components/AppliedFilters/README.md +5 -0
  266. package/src/components/Card/Card.vue +11 -112
  267. package/src/components/Card/README.md +16 -18
  268. package/src/components/Carousel/Carousel.vue +49 -10
  269. package/src/components/Chart/Chart.vue +46 -4
  270. package/src/components/Filterlist/Filterlist.vue +20 -0
  271. package/src/components/Filterlist/README.md +17 -0
  272. package/src/components/Pagination/Pagination.vue +30 -0
  273. package/src/components/Pagination/README.md +11 -0
  274. package/src/components/Table/README.md +29 -44
  275. package/src/components/Table/Table.spec.js +5 -37
  276. package/src/components/Table/Table.vue +16 -91
  277. package/src/foundations/YoutubeVideo/YoutubeVideo.vue +1 -1
  278. package/src/index.js +3 -2
  279. package/assets/css/components/buttons.css +0 -1
  280. package/assets/css/components/buttons.css.map +0 -1
  281. package/assets/css/components/cardDeck.css +0 -1
  282. package/assets/css/components/cardDeck.css.map +0 -1
  283. package/assets/css/components/links.css +0 -1
  284. package/assets/css/components/links.css.map +0 -1
  285. package/assets/css/components/modal.css +0 -1
  286. package/assets/css/components/modal.css.map +0 -1
  287. package/assets/css/components/panel.css +0 -1
  288. package/assets/css/components/panel.css.map +0 -1
  289. package/assets/css/components/tables.css +0 -1
  290. package/assets/css/components/tables.css.map +0 -1
  291. package/assets/js/modules/modal.js +0 -69
  292. package/assets/sass/components/buttons.scss +0 -252
  293. package/assets/sass/components/cardDeck.scss +0 -108
  294. package/assets/sass/components/links.scss +0 -99
  295. package/assets/sass/components/modal.scss +0 -136
  296. package/assets/sass/components/panel.scss +0 -161
  297. package/assets/sass/components/tables.scss +0 -291
  298. package/assets/ts/modules/modal.ts +0 -91
  299. package/src/components/CardDeck/CardDeck.spec.js +0 -99
  300. package/src/components/CardDeck/CardDeck.vue +0 -77
  301. package/src/components/CardDeck/README.md +0 -25
  302. package/src/components/Modal/Modal.spec.js +0 -22
  303. package/src/components/Modal/Modal.vue +0 -43
  304. 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) => {
66
+
67
+ if(table.closest('.table--fullwidth'))
68
+ return false;
72
69
 
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);
70
+ Array.from(table.querySelectorAll('tbody tr')).forEach((row, index) => {
71
+ let firstCol = row.querySelector(':scope > :is(td,th):first-child');
72
+ let colContent = firstCol.textContent;
73
+ firstCol.innerHTML =`<span class="td__content">${colContent}</span><button type="button" class="d-none">${colContent}</button>`;
74
+ });
75
+ }
76
76
 
77
- // Sort the table
78
- sortTable(target.textContent, sort);
77
+ export const addTableEventListeners = (table) => {
79
78
 
80
- Array.from(tableElement.querySelectorAll('tr[draggable]')).forEach((tableRow, index) => {
79
+ table.addEventListener('click', (event) => {
81
80
 
82
- tableRow.removeAttribute('draggable');
83
- });
84
- break;
85
- }
86
- }
87
- }, false);
81
+ if (event && event.target instanceof HTMLElement && event.target.closest('tr > :is(td,th):first-child button')){
88
82
 
89
- // On page load check if the table should be pre-sorted, if so trigger a click
90
- if(tableElement.getAttribute('data-sortBy')){
83
+ let firstCol = event.target.closest('tr > :is(td,th):first-child button');
84
+ let tableRow = firstCol.parentNode.closest('tr');
91
85
 
92
- let sort = tableElement.getAttribute('data-sort') == "ascending" ? "descending" : "ascending";
86
+ if(tableRow.getAttribute('data-view') == "full")
87
+ tableRow.setAttribute('data-view','default');
88
+ else
89
+ tableRow.setAttribute('data-view','full');
93
90
 
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
- }
91
+ firstCol.blur();
92
+ };
93
+ });
94
+ }
101
95
 
102
- // #endregion Sortable
96
+ // Filters
97
+ export const createSearchDataList = (table, form) => {
103
98
 
104
- // #region Filters
105
- const createFilterForm = function(count){
99
+ let searchInput = form.querySelector('[data-search]');
106
100
 
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');
101
+ if(!searchInput)
102
+ return false;
113
103
 
114
- // Create the filter options array
115
- const filterColumns = Array.from(tableElement.querySelectorAll('th[data-filterable]'));
104
+ const searchID = searchInput.getAttribute('id');
105
+ const searchableColumns = searchInput.getAttribute('data-search').split(',');
106
+ let inputWrapper = searchInput.parentNode;
116
107
 
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) => {
108
+ let searchableTerms = {};
109
+ searchableColumns.forEach((columnHeading, index) => {
121
110
 
122
- searchableTerms[label.textContent] = label.textContent;
123
- });
111
+ Array.from(table.querySelectorAll('td[data-label="'+columnHeading.trim()+'"]')).forEach((td, index) => {
112
+
113
+ if(td.querySelector('.td__content'))
114
+ searchableTerms[td.querySelector('.td__content').textContent] = td.querySelector('.td__content').textContent;
115
+ else
116
+ searchableTerms[td.textContent] = td.textContent;
124
117
  });
118
+ });
119
+
120
+ searchInput.setAttribute('list',`${searchID}_list`);
121
+ searchInput.setAttribute('autocomplete','off');
122
+
123
+ if(!inputWrapper.querySelector('datalist'))
124
+ inputWrapper.innerHTML += `<datalist id="${searchID}_list"></datalist>`;
125
+
126
+ inputWrapper.querySelector('datalist').innerHTML = `${Object.keys(searchableTerms).map(term => `<option value="${term}"></option>`).join("")}`;
127
+ }
128
+
129
+ export const addFilterEventListeners = (table, form, pagination, wrapper, savedTableBody) => {
125
130
 
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)
131
+ var timer;
132
+
133
+ // Check what conditions are set on the table to see what the form actions are
134
+ let formSubmit = function(){
135
+
136
+ if(form.hasAttribute('data-ajax'))
137
+ loadAjaxTable(table, form, pagination,wrapper);
138
+ else if(form.hasAttribute('data-submit'))
139
+ form.submit();
140
+ else {
141
+ filterTable(table, form, wrapper);
142
+ createPaginationButttons(wrapper,pagination);
143
+ }
145
144
  }
146
145
 
147
- const filterTable = function(searchTerm){
146
+ form.addEventListener('keyup', (event) => {
148
147
 
149
- // Create an array of rows that match the search term
150
- let tableArr = [];
151
- Array.from(storedData.querySelectorAll('tr')).forEach((tableRow, index) => {
148
+ clearTimeout(timer);
152
149
 
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
- });
150
+ if (event && event.target instanceof HTMLElement && event.target.closest('[data-search]')){
158
151
 
159
- // Check if the table row search string contains the search term
160
- if(rowSearchString.indexOf(searchTerm) >= 0){
152
+ timer = setTimeout(function(){
153
+ formSubmit();
154
+ }, 500);
155
+ };
156
+ });
161
157
 
162
- const dataRow = { row: tableRow }
163
- tableArr.push(dataRow);
164
- }
165
- });
158
+ form.addEventListener('change', (event) => {
166
159
 
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;
160
+ clearTimeout(timer);
161
+
162
+ if (event && event.target instanceof HTMLElement && event.target.closest('[data-sort]')){
163
+
164
+ if(!form.hasAttribute('data-submit'))
165
+ sortTable(table, form, savedTableBody);
173
166
 
174
- // Dispatch the filter event.
175
- tableElement.dispatchEvent(filteredEvent);
176
- }
167
+ formSubmit();
168
+ }
177
169
 
178
- const createFilterList = function(){
170
+ if (event && event.target instanceof HTMLElement && event.target.closest('[data-search]')){
171
+
172
+ formSubmit();
173
+ }
179
174
 
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
- });
175
+ if (event && event.target instanceof HTMLElement && event.target.closest('[data-filter]') && !event.target.closest('form dialog')){
176
+
177
+ formSubmit();
178
+ }
185
179
 
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
- });
180
+ if (event && event.target instanceof HTMLElement && event.target.closest('[data-show]')){
181
+
182
+ formSubmit();
183
+ }
184
+ });
193
185
 
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
186
 
199
- // On page load check if filters are needed
200
- if(Array.from(tableElement.querySelectorAll('[data-filterable]')).length){
187
+ form.addEventListener('click', (event) => {
201
188
 
202
- // Create the filter options
203
- createFilterForm(tableElement,Array.from(tableElement.querySelectorAll('[data-filterable]')).length);
189
+ clearTimeout(timer);
190
+
191
+ if (event && event.target instanceof HTMLElement && event.target.closest('dialog button:not([type="button"])')){
192
+
193
+ let button = event.target.closest('dialog button:not([type="button"])');
194
+ let modal = button.closest('dialog');
204
195
 
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"]')) {
196
+ modal.close();
197
+ }
209
198
 
210
- const searchTerm = target.value;
211
- filterTable(searchTerm)
212
- }
213
- }
214
- });
199
+ // Prevent the form from submitting
200
+ if (event && event.target instanceof HTMLElement && event.target.closest('.dialog__close')){
201
+
202
+ event.preventDefault();
203
+ event.stopPropagation();
204
+ }
215
205
 
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"]')) {
206
+ if (event && event.target instanceof HTMLElement && event.target.closest('[data-clear]')){
207
+
208
+ form.reset();
219
209
 
220
- const searchTerm = target.value;
221
- filterTable(searchTerm)
222
- }
223
- }
224
- });
210
+ if(!form.hasAttribute('data-submit'))
211
+ sortTable(table, form, savedTableBody);
225
212
 
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"]')) {
213
+ formSubmit();
214
+ }
215
+ });
229
216
 
230
- const searchTerm = tableElement.querySelector('input[type="search"]').value;
231
- filterTable(searchTerm)
232
- createFilterList()
233
- }
234
- }
235
- });
217
+ form.addEventListener('submit', (event) => {
218
+
219
+ clearTimeout(timer);
220
+
221
+ if(!form.hasAttribute('data-submit'))
222
+ event.preventDefault();
223
+
224
+ formSubmit();
225
+ });
226
+ }
227
+
228
+ export const sortTable = (table, form, savedTableBody) => {
229
+
230
+ if(form.getAttribute('data-ajax')){
231
+ return false;
232
+ }
233
+
234
+ let tbody = table.querySelector('tbody');
235
+ let select = form.querySelector('[data-sort]');
236
+ let selectedOption = select.querySelector(`option:nth-child(${select.selectedIndex + 1})`);
237
+
238
+ let sortBy = selectedOption.getAttribute('data-sort');
239
+ let order = selectedOption.getAttribute('data-order');
240
+ let format = selectedOption.getAttribute('data-format');
241
+
242
+ if(!sortBy){
243
+
244
+ tbody.innerHTML = savedTableBody.innerHTML;
245
+ addDataAttributes(table);
246
+ return false;
236
247
  }
237
- // #endregion Filters
238
248
 
239
- // #region Pagination
240
- const paginateRows = function(show, page){
249
+ let orderArray = [];
250
+ if(!['asc','desc','descending'].includes(order)){
251
+ orderArray = order.split(',');
252
+ }
253
+
254
+ // Create an array from the table rows, the index created is then used to sort the array
255
+ let tableArr = [];
256
+ Array.from(tbody.querySelectorAll('tr')).forEach((tableRow, index) => {
241
257
 
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');
258
+ let rowIndex = tableRow.querySelector('td[data-label="'+sortBy+'"], th[data-label="'+sortBy+'"]').textContent.trim();
244
259
 
245
- if(style == null){
246
- style = document.createElement("style");
247
- style.setAttribute('id',randID+'_style')
260
+ // If a predefined order set replace the search term with an ordered numeric value so it can be sorted
261
+ if(orderArray.length && orderArray.includes(rowIndex)){
262
+ rowIndex = orderArray.indexOf(rowIndex);
248
263
  }
249
264
 
250
- const startShowing = (show*(page-1))+1;
251
- const stopShowing = show*(page);
265
+ if(isNumeric(rowIndex))
266
+ rowIndex = zeroPad(rowIndex,10)
252
267
 
253
- style.innerHTML = `
254
- #${randID} tbody tr {
255
- display: none;
268
+ // If the sort format is date then lets transform the index to a sortable date (this is never displayed)
269
+ if(format && format == "date")
270
+ rowIndex = new Date(rowIndex);
271
+
272
+ const dataRow = {
273
+ index: rowIndex,
274
+ row: tableRow
256
275
  }
257
- #${randID} tbody tr:nth-child(${startShowing}),
258
- #${randID} tbody tr:nth-child(${startShowing}) ~ tr{
259
- display: block;
276
+ tableArr.push(dataRow);
277
+ });
278
+
279
+ // Sort array alphabetically
280
+ tableArr.sort((a, b) => (a.index > b.index) ? 1 : -1)
281
+
282
+ // Reverse if descending
283
+ if(order == "descending" || order == "desc")
284
+ tableArr = tableArr.reverse();
285
+
286
+ // Create a string to return and populate the tbody
287
+ let strTbody = '';
288
+ tableArr.forEach((tableRow, index) => {
289
+ strTbody += tableRow.row.outerHTML;
290
+ });
291
+ tbody.innerHTML = strTbody;
292
+ }
293
+
294
+ export const filterTable = (table, form, wrapper) => {
295
+
296
+ table.classList.remove('table--filtered');
297
+
298
+ let filters = [];
299
+ let searches = [];
300
+ let matched = 0;
301
+ let page = form.querySelector('[data-pagination]') ? parseInt(form.querySelector('[data-pagination]').value) : 1;
302
+ let showRows = form.querySelector('[data-show]') ? parseInt(form.querySelector('[data-show]').value) : 15;
303
+
304
+ // Filter
305
+ let filterInputs = Array.from(form.querySelectorAll('[data-filter]'));
306
+
307
+ filterInputs.forEach((filterInput, index) => {
308
+
309
+ // Ignore uncked radio inputs
310
+ if(filterInput.type == 'radio' && !filterInput.checked){
311
+ return;
312
+ }
313
+
314
+ if(filterInput.type == 'checkbox' && !filterInput.checked){
315
+ return;
260
316
  }
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;
317
+
318
+
319
+ if(filterInput.getAttribute('data-filter') == "multi"){
320
+
321
+ for (const [key, value] of Object.entries(JSON.parse(filterInput.value))) {
322
+ filters[filterInput.getAttribute('data-filter')].push(value);
265
323
  }
266
324
  }
267
- #${randID} tbody tr:nth-child(${stopShowing}) ~ tr{
268
- display: none;
325
+ else if (filterInput.value) {
326
+
327
+ if(!filters[filterInput.getAttribute('data-filter')])
328
+ filters[filterInput.getAttribute('data-filter')] = new Array();
329
+
330
+ filters[filterInput.getAttribute('data-filter')].push(filterInput.value);
269
331
  }
270
- `;
271
332
 
272
- tableElement.append(style);
333
+ });
334
+
335
+
336
+ // Add search columns too
337
+ if(form.querySelector('[data-search]')){
338
+ let searchInput = form.querySelector('[data-search]');
339
+ let searchColumns = form.querySelector('[data-search]').getAttribute('data-search').split(',');
340
+
341
+ searchColumns.forEach((column, index) => {
342
+
343
+ searches.push({'column':`${column.trim()}`,'value':`${searchInput.value}`});
344
+ });
273
345
  }
274
346
 
275
- // On page load check if the table should be paginated
276
- if(tableElement.getAttribute('data-show')){
347
+ //Display the filter count
348
+ Array.from(form.querySelectorAll('[data-filter-count]')).forEach((element, index) => {
349
+ element.innerHTML = '';
350
+ });
351
+ if(filters.length) {
352
+
353
+ Array.from(form.querySelectorAll('[data-filter-count]')).forEach((element, index) => {
354
+ element.innerHTML += `(${filters.length})`;
355
+ });
356
+ }
277
357
 
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;
358
+ // Stop function if no filters identified
359
+ if(!Object.keys(searches).length && !Object.keys(filters).length)
360
+ return false;
361
+
362
+ table.classList.add('table--filtered');
281
363
 
282
- if(show < totalRows){
283
- paginateRows(show,page);
284
- createPaginationForm(randID,tableElement,show,page,totalRows);
285
- createPaginationButttons(randID,tableElement,show,page,totalRows);
364
+ // Reset
365
+ Array.from(table.querySelectorAll('tbody tr')).forEach((row, index) => {
366
+ row.classList.remove('filtered');
367
+ row.classList.remove('filtered--matched');
368
+ row.classList.remove('filtered--show');
369
+
370
+ row.removeAttribute('data-filtered-by');
371
+ });
286
372
 
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"]')) {
373
+ // Filter the table
290
374
 
291
- paginateRows(target.value,page);
292
- createPaginationButttons(randID,tableElement,target.value,page,totalRows);
293
- tableElement.setAttribute('data-show',target.value)
294
- }
375
+ for (const [key, filterValue] of Object.entries(filters)) {
376
+
377
+ Array.from(table.querySelectorAll('tbody tr:not(.filtered)')).forEach((row, index) => {
378
+
379
+ let isMatched = false;
380
+ filterValue.forEach((filter, index) => {
381
+
382
+ let filterTd = row.querySelector(`[data-label="${key}"]`)
383
+
384
+ // Dynamic values
385
+ if(filter && filter == "$today")
386
+ filter = formatCell('date', new Date());
387
+ else if(filter && filter == "$yesterday"){
388
+
389
+ let yesterday = new Date();
390
+ yesterday.setDate(yesterday.getDate() - 1);
391
+ filter = formatCell('date', yesterday);
295
392
  }
296
- });
393
+ else if(filter && (filter == "$thisWeek" || filter == "$lastWeek")){
394
+
395
+ let today = new Date();
396
+ let mondayThisWeek = new Date(today.setDate(today.getDate() - (today.getDay()-1)));
397
+ let sundayThisWeek = new Date(today.setDate(today.getDate() - today.getDay() + 7));
398
+ let checkDate = new Date(filterTd.textContent.toLowerCase());
297
399
 
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')) {
400
+ today.setHours(0, 0, 0, 0);
401
+ mondayThisWeek.setHours(0, 0, 0, 0);
402
+ sundayThisWeek.setHours(0, 0, 0, 0);
403
+ checkDate.setHours(0, 0, 0, 0);
301
404
 
302
- paginateRows(tableElement.getAttribute('data-show'),target.getAttribute('data-page'));
303
- createPaginationButttons(randID,tableElement,tableElement.getAttribute('data-show'),target.getAttribute('data-page'),totalRows);
405
+ if(filter == "$thisWeek"){
406
+ isMatched = (checkDate >= mondayThisWeek && checkDate <= sundayThisWeek);
304
407
  }
305
- }
306
- }, false);
408
+ else {
409
+ let mondayLastWeek = new Date(mondayThisWeek.setDate(mondayThisWeek.getDate() - 7));
410
+ let sundayLastWeek = new Date(sundayThisWeek.setDate(sundayThisWeek.getDate() - 7));
307
411
 
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')) {
412
+ mondayLastWeek.setHours(0, 0, 0, 0);
413
+ sundayLastWeek.setHours(0, 0, 0, 0);
311
414
 
312
- paginateRows(tableElement.getAttribute('data-show'),target.value);
313
- createPaginationButttons(randID,tableElement,tableElement.getAttribute('data-show'),target.value,totalRows);
415
+ isMatched = (checkDate >= mondayLastWeek && checkDate <= sundayLastWeek);
314
416
  }
315
417
  }
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
- }
418
+ else if(filter && filter == "$thisMonth"){
328
419
 
329
- // Create the order column and event handler for rows
330
- const setReorderRows = function(){
420
+ let today = new Date(), year = today.getFullYear(), month = today.getMonth();
331
421
 
332
- Array.from(tbody.querySelectorAll('tr')).forEach((tableRow, index) => {
422
+ var firstDayMonth = new Date(year, month, 1);
423
+ var lastDayMonth = new Date(year, month + 1, 0);
424
+ let checkDate = new Date(filterTd.textContent.toLowerCase());
333
425
 
334
- // Create column if not already created
335
- if(tableRow.querySelector('[data-label="Order"]') == null){
426
+ firstDayMonth.setHours(0, 0, 0, 0);
427
+ lastDayMonth.setHours(0, 0, 0, 0);
428
+ checkDate.setHours(0, 0, 0, 0);
336
429
 
337
- const orderColumn = document.createElement('th');
338
- orderColumn.innerHTML = index + 1;
339
- orderColumn.setAttribute('data-label','Order');
340
- tableRow.prepend(orderColumn);
341
- }
430
+ isMatched = (checkDate >= firstDayMonth && checkDate <= lastDayMonth);
431
+ }
432
+ else if(filter && filter == "$lastMonth"){
342
433
 
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
- }
434
+ let today = new Date(), year = today.getFullYear(), month = today.getMonth();
350
435
 
351
- if(tableElement.getAttribute('data-reorder') && tableElement.getAttribute('data-reorder') != "false"){
436
+ var firstDayLastMonth = new Date(year, month - 1, 1);
437
+ var lastDayLastMonth = new Date(year, month, 0);
438
+ let checkDate = new Date(filterTd.textContent.toLowerCase());
352
439
 
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);
440
+ firstDayLastMonth.setHours(0, 0, 0, 0);
441
+ lastDayLastMonth.setHours(0, 0, 0, 0);
442
+ checkDate.setHours(0, 0, 0, 0);
359
443
 
360
- setReorderRows();
444
+ isMatched = (checkDate >= firstDayLastMonth && checkDate <= lastDayLastMonth);
445
+ }
446
+
447
+ if(filterTd && filterTd.textContent.toLowerCase().includes(filter.toLowerCase())){
448
+ isMatched = true;
449
+ }
361
450
 
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')) {
451
+ });
366
452
 
367
- // unset sort attributes
368
- Array.from(tableElement.querySelectorAll('[data-sortable]')).forEach((col, index) => {
369
- col.setAttribute('aria-sort','none');
370
- });
453
+ if(!isMatched){
371
454
 
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');
455
+ row.classList.add('filtered');
456
+ row.setAttribute('data-filtered-by',key)
457
+ }
458
+
459
+ });
460
+ }
461
+ // Search whats left of the table after filtering
462
+ Array.from(table.querySelectorAll('tbody tr:not(.filtered)')).forEach((row, index) => {
463
+
464
+ let isSearched = searches.length > 0 && searches[0].value.length >= 3 ? false : true;
465
+
466
+ searches.forEach((search, index) => {
375
467
 
376
- // Sort the table
377
- sortTable('Order', 'ascending');
468
+ let searchTd = row.querySelector(`[data-label="${search.column}"]`);
378
469
 
379
- Array.from(tableElement.querySelectorAll('tbody tr')).forEach((tableRow, index) => {
470
+ if(searchTd && search.value.length >= 3 && searchTd.textContent.toLowerCase().includes(search.value.toLowerCase())){
471
+ isSearched = true;
472
+ }
380
473
 
381
- tableRow.setAttribute('draggable','true');
382
- });
474
+ });
383
475
 
384
- break;
385
- }
386
- }
387
- }, false);
476
+ if(!isSearched)
477
+ row.classList.add('filtered');
478
+ });
388
479
 
480
+ // Work out what to display after pagination
481
+ Array.from(table.querySelectorAll('tbody tr:not(.filtered')).forEach((row, index) => {
389
482
 
390
- document.addEventListener("dragover", function( e ) {
391
- // prevent default to allow drop
392
- e.preventDefault();
393
- }, false);
483
+ matched++;
394
484
 
395
- document.addEventListener("dragenter", function( e ) {
396
- // prevent default to allow drop
397
- e.preventDefault();
398
- e.dataTransfer.dropEffect = "move";
485
+ row.classList.add('filtered--matched');
486
+ // pagination bit
487
+ if(Math.ceil(matched/showRows) == parseInt(page))
488
+ row.classList.add('filtered--show');
489
+ });
399
490
 
400
- for (var target = e.target; target && target != this; target = target.parentNode) {
401
- if (target.matches('[data-reorder] tbody tr')) {
491
+ if(wrapper){
492
+ wrapper.setAttribute('data-page',page);
493
+ wrapper.setAttribute('data-pages',Math.ceil(matched/showRows));
494
+ wrapper.setAttribute('data-total',matched);
495
+ wrapper.setAttribute('data-show',showRows);
496
+ }
402
497
 
403
- target.classList.add('tr--dropable')
404
- }
405
- }
406
- }, false);
498
+ populateDataQueries(table,form);
499
+ }
407
500
 
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')) {
501
+ export const populateDataQueries = (table,form) => {
413
502
 
414
- target.classList.remove('tr--dropable')
415
- }
416
- }
417
- }, false);
503
+ const dataQueries = Array.from(form.querySelectorAll('[data-query]'));
504
+
505
+ dataQueries.forEach((queryElement, index) => {
506
+
507
+ let query = queryElement.getAttribute('data-query');
508
+ let numberOfMatchedRows: 0;
509
+
510
+ if(query == 'total'){
511
+ numberOfMatchedRows = table.classList.contains('table--filtered') ? table.querySelectorAll('tbody tr:not(.filtered)').length : table.querySelectorAll('tbody tr').length;
512
+ }
513
+ else if(!query.includes(' == ') && query.includes(' & ')){
418
514
 
419
- document.addEventListener("drop", function(e) {
515
+ let queries = query.split(' & ');
516
+ let selector = '';
420
517
 
421
- e.preventDefault();
518
+ queries.forEach(element => {
519
+ selector += `:not([data-filtered-by="${element}"])`;
520
+ });
521
+
522
+ numberOfMatchedRows = Array.from(table.querySelectorAll(`tbody tr${selector}`)).length;
523
+ }
524
+ else if(!query.includes(' == ')){
422
525
 
423
- for (var target = e.target; target && target != this; target = target.parentNode) {
424
- if (target.matches('[data-reorder] tbody tr')) {
526
+ numberOfMatchedRows = Array.from(table.querySelectorAll(`tbody tr:not([data-filtered-by="${query}"])`)).length;
527
+ }
528
+ else if(query.includes(' && ')){
425
529
 
426
- if(target.parentNode != null && draggedRow.parentNode != null && target != draggedRow){
530
+ let queries = query.split(' && ');
427
531
 
428
- draggedRow.parentNode.removeChild( draggedRow );
532
+ numberOfMatchedRows = Array.from(table.querySelectorAll(`tbody tr:not(.filtered)`)).filter(function(row){
429
533
 
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);
534
+ let matched = true;
434
535
 
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
- });
536
+ for (const [index, value] of Object.entries(queries)) {
537
+
538
+ let queryParts = value.split(' == ');
442
539
 
443
- tableElement.dispatchEvent(reorderedEvent);
444
- }
445
- break;
540
+ if(!row.querySelector(`td[data-label="${queryParts[0]}"]`) || row.querySelector(`td[data-label="${queryParts[0]}"]`).textContent != `${queryParts[1]}`)
541
+ matched = false;
446
542
  }
447
- }
448
- }, false);
449
543
 
450
- }
451
- // #endregion Reorderable
544
+ return matched;
452
545
 
453
- // Watch for the filterable event and re-sort the tbody
454
- tableElement.addEventListener('filtered', function (e) {
546
+ }).length;
547
+ }
548
+ else {
455
549
 
456
- if(tableElement.getAttribute('data-sortBy') && tableElement.getAttribute('data-sort'))
457
- sortTable(tableElement.getAttribute('data-sortBy'), tableElement.getAttribute('data-sort'));
550
+ let queryParts = query.split(' == ');
551
+ numberOfMatchedRows = Array.from(table.querySelectorAll(`tbody tr:not([class*="filtered"]) 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
+ }
458
555
 
459
- if(tableElement.getAttribute('data-show')){
556
+ if(queryElement.hasAttribute('data-total'))
557
+ queryElement.setAttribute('data-total', numberOfMatchedRows);
558
+ else
559
+ queryElement.innerHTML = numberOfMatchedRows;
560
+ });
561
+ }
460
562
 
461
- const show = parseInt(tableElement.getAttribute('data-show'));
462
- const totalRows = tableElement.querySelectorAll('tbody tr').length;
463
- const tablePagination = tableElement.querySelector('.table__pagination');
563
+ // Pagination
564
+ export const addPaginationEventListeners = function(table, form, pagination, wrapper){
464
565
 
465
- if(tablePagination != null)
466
- tablePagination.remove();
566
+ pagination.addEventListener('click', (event) => {
467
567
 
468
- if(show < totalRows){
568
+ if (event && event.target instanceof HTMLElement && event.target.closest('[data-page]')){
569
+
570
+ event.preventDefault();
469
571
 
470
- paginateRows(show,1);
471
- createPaginationForm(randID,tableElement,show,1,totalRows);
472
- createPaginationButttons(randID,tableElement,show,1,totalRows);
473
- }
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"));
577
+
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){
489
607
 
490
- tableElement.addEventListener('populated', function (e) {
608
+ var csvData = [];
609
+ // Get each row data
610
+ var rows = table.getElementsByTagName('tr');
611
+ for (var i = 0; i < rows.length; i++) {
491
612
 
492
- var tableFilter = tableElement.querySelector('.table__filters')
493
- tableFilter.remove();
613
+ // Get each column data
614
+ var cols = rows[i].querySelectorAll('td,th');
494
615
 
495
- var tablePagination = tableElement.querySelector('.table__pagination')
496
- tablePagination.remove();
616
+ // Stores each csv row data
617
+ var csvRow = [];
618
+ for (var j = 0; j < cols.length; j++) {
497
619
 
498
- var newTable = tableElement.cloneNode(true);
499
- tableElement.parentNode.replaceChild(newTable, tableElement);
620
+ // Get the text data of each cell of a row and push it to csvrow
621
+ csvRow.push(`"${cols[j].textContent}"`);
622
+ }
623
+
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
+ }