datatable.js 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. class TableHeader {
  2. constructor(columns) {
  3. this.columns = columns;
  4. }
  5. render() {
  6. const tr = document.createElement('tr');
  7. const colgroup = document.createElement('colgroup');
  8. this.columns.forEach((column) => {
  9. const th = document.createElement('th');
  10. const col = document.createElement('col');
  11. th.setAttribute('data-column', column.column);
  12. col.setAttribute('data-column', column.column);
  13. th.innerHTML = column.label;
  14. tr.appendChild(th);
  15. colgroup.appendChild(col);
  16. });
  17. const thead = document.createElement('thead');
  18. thead.appendChild(tr);
  19. return {colgroup : colgroup, thead : thead };
  20. }
  21. }
  22. class ActionSet {
  23. arrowDown = '<i class="fa fa-arrow-down" aria-hidden="true"></i>';
  24. constructor(actions, element = 'button', value = null) {
  25. this.actions = actions;
  26. this.element = element;
  27. this.value = value;
  28. }
  29. render() {
  30. if (this.element == 'button') {
  31. return this.renderButton();
  32. }
  33. if (this.element == 'span') {
  34. return this.renderSpan();
  35. }
  36. }
  37. renderButton() {
  38. this.actionButton = document.createElement('a');
  39. this.actionButton.classList.add('btn', 'btn-outline-secondary');
  40. this.actionButton.innerHTML = `<i class="fa fa-${this.actions[0].icon}" aria-hidden="true"></i> ${this.actions[0].label}`;
  41. this.actionButton.href = this.actions[0].route.replace('{{id}}', this.value);
  42. if (this.actions.length == 1) {
  43. return { 'button': this.actionButton, 'toggle': null, 'list': null };
  44. }
  45. this.actionToggle = document.createElement('button');
  46. this.actionToggle.classList.add('btn', 'btn-outline-secondary', 'dropdown-toggle-split');
  47. this.actionToggle.innerHTML = this.arrowDown;
  48. this.actionToggle.setAttribute('data-bs-toggle', 'dropdown');
  49. this.actionList = document.createElement('ul');
  50. this.actionList.classList.add('dropdown-menu');
  51. this.actions.forEach((action) => {
  52. const actionHolder = document.createElement('li');
  53. const actionLink = document.createElement('a');
  54. actionLink.classList.add('dropdown-item');
  55. actionLink.href = action.route.replace('{{id}}', this.value);
  56. actionLink.innerHTML = `<i class="fa fa-${action.icon}" aria-hidden="true"></i> ${action.label}`;
  57. actionHolder.appendChild(actionLink);
  58. this.actionList.appendChild(actionHolder);
  59. });
  60. return {
  61. 'button': this.actionButton,
  62. 'toggle': this.actionToggle,
  63. 'list': this.actionList
  64. };
  65. }
  66. renderSpan() {
  67. this.actionButton = document.createElement('div');
  68. this.actionToggle = document.createElement('span');
  69. this.actionToggle.setAttribute('data-bs-toggle', 'dropdown');
  70. this.actionToggle.innerHTML = `#${this.value} ${this.arrowDown}`;
  71. this.actionList = document.createElement('ul');
  72. this.actionList.classList.add('dropdown-menu');
  73. this.actions.forEach((action) => {
  74. const actionHolder = document.createElement('li');
  75. if(action.type == 'divider'){
  76. const actionDivider = document.createElement('hr');
  77. actionDivider.classList.add('dropdown-divider');
  78. this.actionList.appendChild(actionDivider);
  79. return;
  80. }
  81. const actionLink = document.createElement('a');
  82. actionLink.classList.add('dropdown-item');
  83. actionLink.href = action.route.replace('{{id}}', this.value);
  84. actionLink.innerHTML = `<i class="fa fa-${action.icon}" aria-hidden="true"></i> ${action.label}`;
  85. actionHolder.appendChild(actionLink);
  86. this.actionList.appendChild(actionHolder);
  87. });
  88. this.actionButton.appendChild(this.actionToggle);
  89. this.actionButton.appendChild(this.actionList);
  90. return this.actionButton;
  91. }
  92. renderLabel(label, icon){
  93. if(icon){
  94. return `<i class="fa fa-${icon}" aria-hidden="true"></i> ${label}`
  95. }
  96. return label
  97. }
  98. }
  99. class TableBody {
  100. constructor(rows, columns, rowActions, options) {
  101. this.rows = rows;
  102. this.columns = columns;
  103. this.rowActions = rowActions;
  104. this.options = options;
  105. }
  106. render() {
  107. if(this.options.selectable){
  108. return this.renderSelectable();
  109. }
  110. return this.renderDefaultTable();
  111. }
  112. renderDefaultTable(){
  113. const tbody = document.createElement('tbody');
  114. this.rows.forEach((row) => {
  115. const tr = document.createElement('tr');
  116. this.columns.forEach((column) => {
  117. const td = document.createElement('td');
  118. td.setAttribute('data-name', column.column);
  119. td.setAttribute('data-value', row[column.column]);
  120. if (column.column == 'id') {
  121. tr.setAttribute('data-value', row[column.column]);
  122. const idColumn = new ActionSet(this.rowActions, 'span', row[column.column]);
  123. td.appendChild(idColumn.render());
  124. } else {
  125. td.innerHTML = row[column.column];
  126. }
  127. tr.appendChild(td);
  128. });
  129. tbody.appendChild(tr);
  130. });
  131. return tbody;
  132. }
  133. renderSelectable(){
  134. const tbody = document.createElement('tbody');
  135. this.rows.forEach((row) => {
  136. const tr = document.createElement('tr');
  137. this.columns.forEach((column) => {
  138. const td = document.createElement('td');
  139. td.setAttribute('data-name', column.column);
  140. td.setAttribute('data-value', row[column.column]);
  141. if (column.column == 'id') {
  142. tr.setAttribute('data-value', row[column.column]);
  143. const idColumn = document.createElement('button');
  144. let payload = {'element': row, ...this.options.selectable.callbackParameters};
  145. idColumn.addEventListener('click', this.options.selectable.select.bind(null, payload));
  146. idColumn.classList.add('btn', 'btn-danger', 'btn-block');
  147. idColumn.style = "padding: 0.1rem 0.5em;";
  148. idColumn.innerHTML = 'Selecionar';
  149. td.appendChild(idColumn);
  150. } else {
  151. td.innerHTML = row[column.column];
  152. }
  153. tr.appendChild(td);
  154. });
  155. tbody.appendChild(tr);
  156. });
  157. return tbody;
  158. }
  159. }
  160. class Buttons {
  161. constructor(currentPage, totalPages, maxButtons, onPageChange, replicateChanges) {
  162. this.currentPage = currentPage;
  163. this.totalPages = totalPages;
  164. this.maxButtons = maxButtons;
  165. this.onPageChange = onPageChange;
  166. this.replicateChanges = replicateChanges;
  167. }
  168. render() {
  169. console.log('adssaddas');
  170. this.buttonHolder = document.createElement('div');
  171. this.buttonHolder.classList.add('col-12', 'col-md-9');
  172. this.paginationButtons = document.createElement('ul');
  173. this.paginationButtons.classList.add('pagination', 'pagination-sm');
  174. this.prevButton = document.createElement('li');
  175. this.prevButton.classList.add('page-link');
  176. this.prevButton.textContent = 'Previous';
  177. this.prevButton.disabled = true;
  178. this.prevButton.addEventListener('click', () => {
  179. if (this.currentPage > 1) {
  180. this.onPageChange(this.currentPage - 1);
  181. this.updateButtons(this.currentPage - 1);
  182. this.replicateChanges(this.currentPage);
  183. }
  184. });
  185. this.paginationButtons.appendChild(this.prevButton);
  186. this.pageButtons = Array();
  187. for (let i = 1; i <= this.totalPages; i++) {
  188. const pageButton = document.createElement('li');
  189. pageButton.classList.add('page-item');
  190. const pageButtonLink = document.createElement('a');
  191. pageButtonLink.classList.add('page-link');
  192. pageButtonLink.textContent = i;
  193. if (!(this.currentPage === i ||
  194. i > (this.currentPage + this.maxButtons / 2) ||
  195. i < (this.currentPage - this.maxButtons / 2))) {
  196. pageButton.classList.add('hidden');
  197. }
  198. if(this.currentPage === i){
  199. pageButton.classList.add('active');
  200. }
  201. pageButtonLink.addEventListener('click', () => {
  202. this.onPageChange(i);
  203. this.updateButtons(i);
  204. this.replicateChanges(this.currentPage);
  205. });
  206. pageButton.appendChild(pageButtonLink);
  207. this.pageButtons.push(pageButton);
  208. this.paginationButtons.appendChild(pageButton);
  209. }
  210. this.nextButton = document.createElement('li');
  211. this.nextButton.classList.add('page-link');
  212. this.nextButton.textContent = 'Next';
  213. this.nextButton.disabled = this.totalPages === 0 || this.currentPage === this.totalPages;
  214. this.nextButton.addEventListener('click', () => {
  215. if (this.currentPage < this.totalPages) {
  216. this.onPageChange(this.currentPage + 1);
  217. this.updateButtons(this.currentPage + 1);
  218. this.replicateChanges(this.currentPage);
  219. }
  220. });
  221. this.paginationButtons.appendChild(this.nextButton);
  222. this.buttonHolder.appendChild(this.paginationButtons);
  223. return this.buttonHolder;
  224. }
  225. updateButtons(page, perPage) {
  226. if (page) {
  227. this.currentPage = parseInt(page);
  228. }
  229. if (this.prevButton) {
  230. this.prevButton.disabled = this.currentPage === 1;
  231. }
  232. if (this.nextButton) {
  233. this.nextButton.disabled = this.totalPages === 0 || this.currentPage === this.totalPages;
  234. }
  235. if (this.pageButtons) {
  236. this.pageButtons.forEach((button, index) => {
  237. button.classList.remove('active');
  238. if (this.currentPage === index + 1 ||
  239. index > (this.currentPage + this.maxButtons / 2) ||
  240. index < (this.currentPage - this.maxButtons / 2)) {
  241. button.classList.add('hidden');
  242. }else {
  243. button.classList.remove('hidden');
  244. }
  245. if (index + 1 == this.currentPage) {
  246. button.classList.add('active');
  247. button.classList.remove('hidden');
  248. } else {
  249. button.removeAttribute('value');
  250. }
  251. });
  252. }
  253. }
  254. }
  255. class Label {
  256. constructor(actualPage, totalPages, recordsCount, totalRecords) {
  257. this.actualPage = actualPage;
  258. this.totalPages = totalPages;
  259. this.recordsCount = recordsCount;
  260. this.totalRecords = totalRecords;
  261. }
  262. render(){
  263. this.paginationLabel = document.createElement('div');
  264. this.paginationLabel.classList.add('col-12');
  265. this.pageLabel = document.createElement('span');
  266. this.pageLabel.innerText = `Page: ${this.actualPage} of ${this.totalPages} | `;
  267. this.recordLabel = document.createElement('span');
  268. this.recordLabel.innerText = `${this.recordsCount} of ${this.totalRecords} records shown`;
  269. this.paginationLabel.appendChild(this.pageLabel);
  270. this.paginationLabel.appendChild(this.recordLabel);
  271. return this.paginationLabel;
  272. }
  273. rerender() {
  274. this.pageLabel.innerText = `Page: ${this.actualPage} of ${this.totalPages} | `;
  275. this.recordLabel.innerText = `${this.recordsCount} of ${this.totalRecords} records shown`;
  276. }
  277. updateLabel(page, perPage, totalPages){
  278. this.actualPage = page;
  279. this.perPage = perPage;
  280. this.rerender();
  281. }
  282. }
  283. class Search {
  284. constructor(currentPage, totalPages, onPageChange, replicateChanges){
  285. this.currentPage = currentPage;
  286. this.totalPages = totalPages;
  287. this.onPageChange = onPageChange;
  288. this.replicateChanges = replicateChanges;
  289. }
  290. render(){
  291. const paginationHolder = document.createElement('div');
  292. paginationHolder.classList.add('col-12', 'col-md-3');
  293. this.paginationSearch = document.createElement('div');
  294. this.paginationSearch.classList.add('search', 'input-group','col-md-3', 'col-sm-12');
  295. this.textualNumber = document.createElement('input');
  296. this.textualNumber.classList.add('form-control', 'form-control-sm');
  297. this.textualNumber.setAttribute('type', 'number');
  298. this.textualNumber.setAttribute('min', 1);
  299. this.textualNumber.setAttribute('max', this.totalPages);
  300. this.textualNumber.value = this.currentPage;
  301. this.textualNumber.addEventListener('blur', (event) => {
  302. let page = event.target.value;
  303. if(page > this.totalPages){
  304. page = this.totalPages;
  305. this.textualNumber.value = this.totalPages;
  306. }
  307. if(page < 1){
  308. page = 1;
  309. this.textualNumber.value = page;
  310. }
  311. this.replicateChanges(page);
  312. this.onPageChange(page);
  313. });
  314. this.textualNumber.addEventListener('keydown', (event) => {
  315. if (event.key === 'Enter') {
  316. console.log('reberberbrte');
  317. }
  318. });
  319. //this.paginationSearch.appendChild(this.textualNumber);
  320. this.append(this.textualNumber);
  321. this.selectNumber = document.createElement('select');
  322. this.selectNumber.classList.add('form-select', 'form-select-sm');
  323. for(let i = 1; i < this.totalPages; i++){
  324. const selectOption = document.createElement('option');
  325. selectOption.innerText = i;
  326. selectOption.setAttribute('value', i);
  327. this.selectNumber.appendChild(selectOption);
  328. }
  329. this.selectNumber.addEventListener('change', (event) => {
  330. this.replicateChanges(event.target.value);
  331. this.onPageChange(event.target.value);
  332. });
  333. //this.paginationSearch.appendChild(this.selectNumber);
  334. this.append(this.selectNumber);
  335. const recordsPerPage = document.createElement('select');
  336. recordsPerPage.classList.add('form-select', 'form-select-sm');
  337. for(let i = 1; i < 10; i++){
  338. const selectOption = document.createElement('option');
  339. selectOption.innerText = i * 25;
  340. selectOption.setAttribute('value', i * 25);
  341. recordsPerPage.appendChild(selectOption);
  342. }
  343. recordsPerPage.addEventListener('change', (event) => {
  344. //console.log(event.target.value)
  345. //this.replicateChanges(event.target.value);
  346. });
  347. //this.paginationSearch.appendChild(recordsPerPage);
  348. this.append(recordsPerPage);
  349. paginationHolder.appendChild(this.paginationSearch);
  350. return paginationHolder;
  351. }
  352. updateSearch(pageNumber, perPage) {
  353. this.selectNumber.value = pageNumber;
  354. this.textualNumber.value = pageNumber;
  355. }
  356. searchChange(){
  357. }
  358. append(element){
  359. let container = document.createElement('div');
  360. container.classList.add('col-4');
  361. container.appendChild(element);
  362. this.paginationSearch.appendChild(container);
  363. }
  364. }
  365. class Pagination {
  366. constructor(onPageChange, rowCount, perPage) {
  367. this.currentPage = 1;
  368. this.maxButtons = 10;
  369. this.onPageChange = onPageChange;
  370. this.rowCount = rowCount;
  371. this.perPage = perPage;
  372. this.totalPages = Math.ceil(rowCount / perPage);
  373. }
  374. render() {
  375. this.paginationContainer = document.createElement('div');
  376. this.paginationContainer.classList.add('pagination', 'row');
  377. if(this.totalPages <= 1){
  378. return this.paginationContainer;
  379. }
  380. this.labels = new Label(this.currentPage, this.totalPages, 10, 1000);
  381. this.buttons = new Buttons(this.currentPage, this.totalPages, 10, this.onPageChange, (page) => {
  382. this.labels.updateLabel(page, this.perPage);
  383. this.search.updateSearch(page, this.perPage);
  384. });
  385. this.search = new Search(this.currentPage, this.totalPages, this.onPageChange, (page, size) => {
  386. this.labels.updateLabel(page, this.perPage);
  387. this.buttons.updateButtons(page, this.perPage);
  388. this.search.updateSearch(page, this.perPage);
  389. });
  390. this.paginationContainer.appendChild(this.labels.render());
  391. this.paginationContainer.appendChild(this.buttons.render());
  392. this.paginationContainer.appendChild(this.search.render());
  393. return this.paginationContainer;
  394. }
  395. changePageSize(onPageChange, rowCount, perPage) {
  396. this.currentPage = 1;
  397. this.maxButtons = 10;
  398. this.onPageChange = onPageChange;
  399. this.rowCount = rowCount;
  400. this.perPage = perPage;
  401. this.totalPages = Math.ceil(rowCount / perPage);
  402. return this.paginationContainer;
  403. }
  404. }
  405. class ListButton{
  406. constructor(icon, actions){
  407. this.icon = icon;
  408. this.actions = actions;
  409. }
  410. render() {
  411. this.actionButton = document.createElement('button');
  412. this.actionButton.classList.add('btn', 'btn-outline-secondary');
  413. this.actionButton.innerHTML = `<i class="fa fa-${this.icon}" aria-hidden="true"></i>`;
  414. return this.actionButton;
  415. }
  416. }
  417. class SearchBar {
  418. constructor(onSearch) {
  419. this.onSearch = onSearch;
  420. }
  421. render() {
  422. this.searchBarContainer = document.createElement('div');
  423. this.searchBarContainer.classList.add('search-bar');
  424. this.inputElement = document.createElement('input');
  425. this.inputElement.classList.add('form-control');
  426. this.inputElement.setAttribute('type', 'text');
  427. this.inputElement.addEventListener('keydown', (event) => {
  428. if (event.key === 'Enter') {
  429. this.searchText = this.inputElement.value;
  430. this.onSearch(this.searchText);
  431. }
  432. });
  433. this.searchBarContainer.appendChild(this.inputElement);
  434. return this.searchBarContainer;
  435. }
  436. getSearch(){
  437. return this.inputElement.value;
  438. }
  439. }
  440. class ToolBar {
  441. constructor(onSearch, actions, options) {
  442. this.onSearch = onSearch;
  443. this.actions = actions;
  444. this.options = options;
  445. }
  446. render() {
  447. this.toolbar = document.createElement('div');
  448. this.toolbar.classList.add('input-group', 'mb-3');
  449. if (typeof this.actions !== 'undefined' && typeof this.options.selectable === 'undefined' && this.actions.length > 0) {
  450. this.actionSet = new ActionSet(this.actions, 'button');
  451. let { button, toggle, list } = this.actionSet.render();
  452. if (button) {
  453. this.toolbar.appendChild(button);
  454. }
  455. if (toggle) {
  456. this.toolbar.appendChild(toggle);
  457. }
  458. if (list) {
  459. this.toolbar.append(list);
  460. }
  461. }
  462. this.searchBar = new SearchBar(this.onSearch);
  463. this.toolbar.appendChild(this.searchBar.render());
  464. this.toolbar.appendChild(new ListButton('th', '').render());
  465. return this.toolbar;
  466. }
  467. getSearch() {
  468. return this.searchBar.getSearch();
  469. }
  470. }
  471. class DataTable {
  472. constructor(tableElement, options = {}) {
  473. if (typeof tableElement === 'string') {
  474. this.tableElement = document.getElementById(tableElement);
  475. } else {
  476. this.tableElement = tableElement;
  477. }
  478. this.page = 1;
  479. this.options = options;
  480. this.perPage = 25;
  481. this.getData();
  482. }
  483. render(data) {
  484. const { actions, rowActions, columns, rows, total } = data;
  485. this.table = document.createElement('table');
  486. this.table.classList.add('table', 'table-responsive');
  487. this.searchBar = new ToolBar((searchText) => {
  488. this.getData(searchText);
  489. }, actions, this.options);
  490. this.pagination = new Pagination((page, perPage) => {
  491. this.page = page--;
  492. //this.perPage = perPage;
  493. this.getData();
  494. }, total, this.perPage);
  495. this.headerElement = new TableHeader(columns);
  496. const tableHeader = this.headerElement.render();
  497. this.table.appendChild(tableHeader.colgroup);
  498. this.table.appendChild(tableHeader.thead);
  499. this.bodyElement = new TableBody(rows, columns, rowActions, this.options);
  500. this.table.appendChild(this.bodyElement.render());
  501. // #ToDo Convert table to div
  502. if (this.tableElement.tagName === 'TABLE') {
  503. const divContainer = document.createElement('div');
  504. while (this.tableElement.firstChild) {
  505. divContainer.appendChild(this.tableElement.firstChild);
  506. }
  507. this.tableElement.parentNode.replaceChild(divContainer, this.tableElement);
  508. }
  509. this.tableElement.appendChild(this.searchBar.render());
  510. this.tableElement.appendChild(this.pagination.render());
  511. this.tableElement.appendChild(this.table);
  512. }
  513. rerender(data) {
  514. const { columns, rows, rowActions, total } = data;
  515. if (!this.table) {
  516. this.render(data);
  517. return;
  518. }
  519. const content = new TableBody(rows, columns, rowActions, this.options).render();
  520. this.table.querySelector('tbody').innerHTML = content.innerHTML;
  521. }
  522. async getData() {
  523. let requestBody = {
  524. 'page': this.page - 1,
  525. 'pageSize': this.perPage
  526. };
  527. if(this.searchBar){
  528. requestBody.search = this.searchBar.getSearch();
  529. }
  530. const params = new URLSearchParams(requestBody).toString();
  531. const response = await fetch(this.options.url + '?' + params);
  532. const data = await response.json();
  533. this.rerender(data);
  534. }
  535. }
  536. export { DataTable as default };