├── .github
└── workflows
│ └── npm-publish.yml
├── .gitignore
├── LICENSE
├── README.md
├── dist
├── jstable.css
├── jstable.es5.min.js
├── jstable.min.js
└── polyfill-fetch.min.js
├── gulpfile.js
├── package-lock.json
├── package.json
├── server
├── data.json
└── data.php
└── src
├── jstable.js
└── jstable.scss
/.github/workflows/npm-publish.yml:
--------------------------------------------------------------------------------
1 | name: npm-publish
2 |
3 | on:
4 | push:
5 | branches:
6 | - master
7 | jobs:
8 | npm-publish:
9 | runs-on: ubuntu-latest
10 | steps:
11 | - name: Checkout repository
12 | uses: actions/checkout@v2
13 | - name: Publish if version has been updated
14 | uses: JS-DevTools/npm-publish@v1
15 | with:
16 | token: ${{ secrets.NPM_TOKEN }}
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | node_modules
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2019
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # JSTable
2 |
3 | The JSTable is a lightweight, dependency-free JavaScript plugin which makes a HTML table interactive.
4 |
5 | The plugin is similar to the [jQuery datatables](https://datatables.net/) but without the jQuery dependencies.
6 |
7 | The implementation is inspired by [Vanilla-DataTables](https://github.com/Mobius1/Vanilla-DataTables). Unlike Vanilla-Datatables this implementation is using the in ES6 introduced [classes](https://www.w3schools.com/js/js_classes.asp).
8 | Additionally JSTable includes the possibility for server side rendering, which is inspired by [jQuery datatables](https://datatables.net/manual/server-side).
9 |
10 | You can get more information about the usage on [https://jstable.github.io/](https://jstable.github.io/).
11 |
12 | ## Install
13 |
14 | * Clone the [github repository](https://github.com/jstable/JSTable)
15 | * Include the stylesheet and JavaScript files from the `dist` folder:
16 |
17 | ```
18 |
19 | ```
20 | ```
21 |
22 | ```
23 | * If the target browser does not support not all ES2015+ features you need to include the `es5` version:
24 | ```
25 |
26 | ```
27 | * If the target browser does not support fetch you need to include the following polyfills:
28 | ```
29 |
30 | ```
31 |
32 | ## Initialize
33 |
34 | The HTML table needs a `thead` and `tbody` section.
35 |
36 | #### Example table
37 | ```
38 |
39 |
40 |
41 | Name
42 | Country
43 | Date
44 | Number
45 |
46 |
47 |
48 |
49 | Norman Small
50 | Tokelau
51 | 2020-02-01 07:22:40
52 | 8243
53 |
54 | ...
55 |
56 |
57 | ```
58 |
59 | #### JavaScript
60 |
61 | The JSTable can be initialized by passing a reference or a CSS3 selector as string:
62 | ```
63 | let myTable = new JSTable("#basic");
64 | ```
65 | or
66 | ```
67 | let table = document.getElementById('basic');
68 | let myTable = new JSTable(table);
69 | ```
70 | [Options](https://jstable.github.io/options.html) can be passed as second argument:
71 |
72 | ```
73 | let myTable = new JSTable("#basic", {
74 | sortable: true,
75 | searchable: false,
76 | ...
77 | });
78 | ```
79 |
--------------------------------------------------------------------------------
/dist/jstable.css:
--------------------------------------------------------------------------------
1 | /*!
2 | * JSTable v1.6.5
3 | */.dt-container{position:relative;display:block;width:100%;overflow-x:auto;-webkit-overflow-scrolling:touch;-ms-overflow-style:-ms-autohiding-scrollbar;overflow-y:hidden}.dt-container .dt-message{text-align:center}.dt-container .dt-loading{position:absolute;top:50%;left:50%;width:100%;margin-left:-50%;margin-top:-20px;height:40px;text-align:center;background-color:white;display:flex;justify-content:center;align-items:center;background:linear-gradient(to right, rgba(255,255,255,0) 0, rgba(255,255,255,0.9) 25%, rgba(255,255,255,0.9) 75%, rgba(255,255,255,0) 100%)}.dt-top,.dt-bottom{padding:8px 10px;display:flex;justify-content:space-between}.dt-top .dt-selector,.dt-bottom .dt-selector{padding:6px}.dt-top .dt-info,.dt-bottom .dt-info{margin:7px 0}.dt-input{padding:6px 12px}.dt-pagination ul{margin:0;padding-left:0}.dt-pagination ul li{list-style:none;float:left}.dt-pagination a,.dt-pagination span{border:1px solid transparent;float:left;margin-left:2px;padding:6px 12px;position:relative;text-decoration:none;color:inherit}.dt-pagination a:hover{background-color:#d9d9d9}.dt-pagination .active a,.dt-pagination .active a:focus,.dt-pagination .active a:hover{background-color:#d9d9d9;cursor:default}.dt-pagination .dt-ellipsis span{cursor:not-allowed}.dt-pagination .disabled a,.dt-pagination .disabled a:focus,.dt-pagination .disabled a:hover{cursor:not-allowed;opacity:0.4}.dt-pagination .pager a{font-weight:bold}.dt-table{max-width:100%;width:100%;border-spacing:0}.dt-table>tbody>tr>td,.dt-table>tbody>tr>th,.dt-table>tfoot>tr>td,.dt-table>tfoot>tr>th,.dt-table>thead>tr>td,.dt-table>thead>tr>th{vertical-align:top;padding:8px 10px;white-space:nowrap}.dt-table>thead>tr>th,.dt-table>thead>tr>td{vertical-align:bottom;text-align:left;border-bottom:1px solid #d9d9d9}.dt-table>tfoot>tr>th,.dt-table>tfoot>tr>td{vertical-align:bottom;text-align:left;border-top:1px solid #d9d9d9}.dt-table th{vertical-align:bottom;text-align:left}.dt-table th.dt-sorter{position:relative;cursor:pointer;padding-right:20px}.dt-table th.dt-sorter::before,.dt-table th.dt-sorter::after{content:"";height:0;width:0;position:absolute;right:7px;border-left:4px solid transparent;border-right:4px solid transparent;opacity:0.2}.dt-table th.dt-sorter::before{border-top:4px solid #000;top:18px}.dt-table th.dt-sorter::after{border-bottom:4px solid #000;border-top:4px solid transparent;bottom:22px}.dt-table th.dt-sorter.asc::after,.dt-table th.dt-sorter.desc::before{opacity:0.6}.hidden{display:none !important;opacity:0 !important}
4 |
--------------------------------------------------------------------------------
/dist/jstable.es5.min.js:
--------------------------------------------------------------------------------
1 | !function(){
2 | /*!
3 | * JSTable v1.6.5
4 | */
5 | const e={perPage:5,perPageSelect:[5,10,15,20,25],sortable:!0,searchable:!0,nextPrev:!0,firstLast:!1,prevText:"‹",nextText:"›",firstText:"«",lastText:"»",ellipsisText:"…",truncatePager:!0,pagerDelta:2,classes:{top:"dt-top",info:"dt-info",input:"dt-input",table:"dt-table",bottom:"dt-bottom",search:"dt-search",sorter:"dt-sorter",wrapper:"dt-wrapper",dropdown:"dt-dropdown",ellipsis:"dt-ellipsis",selector:"dt-selector",container:"dt-container",pagination:"dt-pagination",loading:"dt-loading",message:"dt-message"},labels:{placeholder:"Search...",perPage:"{select} entries per page",noRows:"No entries found",info:"Showing {start} to {end} of {rows} entries",loading:"Loading...",infoFiltered:"Showing {start} to {end} of {rows} entries (filtered from {rowsTotal} entries)"},layout:{top:"{select}{search}",bottom:"{info}{pager}"},serverSide:!1,deferLoading:null,ajax:null,ajaxParams:{},queryParams:{page:"page",search:"search",sortColumn:"sortColumn",sortDirection:"sortDirection",perPage:"perPage"},addQueryParams:!0,rowAttributesCreator:null,searchDelay:null,method:"GET"};class t{constructor(e){this.element=e,this.body=this.element.tBodies[0],this.head=this.element.tHead,this.rows=Array.from(this.element.rows).map((function(e,t){return new s(e,e.parentNode.nodeName,t)})),this.dataRows=this._getBodyRows(),this.header=this._getHeaderRow()}_getBodyRows(){return this.rows.filter((function(e){return!e.isHeader&&!e.isFooter}))}_getHeaderRow(){return this.rows.find((function(e){return e.isHeader}))}getColumnCount(){return this.header.getColumnCount()}getFooterRow(){return this.rows.find((function(e){return e.isFooter}))}}class s{constructor(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:"",s=arguments.length>2&&void 0!==arguments[2]?arguments[2]:null;this.cells=Array.from(e.cells).map((function(e){return new a(e)})),this.d=this.cells.length,this.isHeader="THEAD"===t,this.isFooter="TFOOT"===t,this.visible=!0,this.rowID=s;var r=this;this.attributes={},[...e.attributes].forEach((function(e){r.attributes[e.name]=e.value}))}getCells(){return Array.from(this.cells)}getColumnCount(){return this.cells.length}getCell(e){return this.cells[e]}getCellTextContent(e){return this.getCell(e).getTextContent()}static createFromData(e){let t=document.createElement("tr");if(e.hasOwnProperty("data")){if(e.hasOwnProperty("attributes"))for(const s in e.attributes)t.setAttribute(s,e.attributes[s]);e=e.data}return e.forEach((function(e){let s=document.createElement("td");if(s.innerHTML=e&&e.hasOwnProperty("data")?e.data:e,e&&e.hasOwnProperty("attributes"))for(const t in e.attributes)s.setAttribute(t,e.attributes[t]);t.appendChild(s)})),new s(t)}getFormatted(e){let t=arguments.length>1&&void 0!==arguments[1]?arguments[1]:null,s=document.createElement("tr");var a=this;for(let e in this.attributes)s.setAttribute(e,this.attributes[e]);let r=t?t.call(this,this.getCells()):{};for(const e in r)s.setAttribute(e,r[e]);return this.getCells().forEach((function(t,r){var i=document.createElement("td");i.innerHTML=t.getInnerHTML(),e.hasOwnProperty(r)&&(i.innerHTML=e[r].call(a,t.getElement(),r)),t.classes.length>0&&(i.className=t.classes.join(" "));for(let e in t.attributes)i.setAttribute(e,t.attributes[e]);s.appendChild(i)})),s}setCellClass(e,t){this.cells[e].addClass(t)}}class a{constructor(e){this.textContent=e.textContent,this.innerHTML=e.innerHTML,this.className="",this.element=e,this.hasSortable=e.hasAttribute("data-sortable"),this.isSortable=this.hasSortable?"true"===e.getAttribute("data-sortable"):null,this.hasSort=e.hasAttribute("data-sort"),this.sortDirection=e.getAttribute("data-sort"),this.classes=[];var t=this;this.attributes={},[...e.attributes].forEach((function(e){t.attributes[e.name]=e.value}))}getElement(){return this.element}getTextContent(){return this.textContent}getInnerHTML(){return this.innerHTML}setClass(e){this.className=e}setSortable(e){this.isSortable=e}addClass(e){this.classes.push(e)}removeClass(e){this.classes.indexOf(e)>=0&&this.classes.splice(this.classes.indexOf(e),1)}addAttribute(e,t){this.attributes[e]=t}}class r{constructor(e){this.instance=e}getPages(){let e=Math.ceil(this.instance.getDataCount()/this.instance.config.perPage);return 0===e?1:e}render(){var e=this.instance.config;let t=this.getPages(),s=document.createElement("ul");if(t>1){let a=1===this.instance.currentPage?1:this.instance.currentPage-1,r=this.instance.currentPage===t?t:this.instance.currentPage+1;e.firstLast&&s.appendChild(this.createItem("pager",1,e.firstText)),e.nextPrev&&s.appendChild(this.createItem("pager",a,e.prevText)),this.truncate().forEach((function(e){s.appendChild(e)})),e.nextPrev&&s.appendChild(this.createItem("pager",r,e.nextText)),e.firstLast&&s.appendChild(this.createItem("pager",t,e.lastText))}return s}createItem(e,t,s,a){let r=document.createElement("li");return r.className=e,r.innerHTML=a?""+s+" ":''+s+" ",r}isValidPage(e){return e>0&&e<=this.getPages()}truncate(){var e,t=this,s=t.instance.config,a=2*s.pagerDelta,r=t.instance.currentPage,i=r-s.pagerDelta,n=r+s.pagerDelta,o=this.getPages(),l=[],c=[];if(this.instance.config.truncatePager){r<4-s.pagerDelta+a?n=3+a:r>this.getPages()-(3-s.pagerDelta+a)&&(i=this.getPages()-(2+a));for(var h=1;h<=o;h++)(1===h||h===o||h>=i&&h<=n)&&l.push(h);l.forEach((function(a){e&&(a-e==2?c.push(t.createItem("",e+1,e+1)):a-e!=1&&c.push(t.createItem(s.classes.ellipsis,0,s.ellipsisText,!0))),c.push(t.createItem(a==r?"active":"",a,a)),e=a}))}else for(let e=1;e<=this.getPages();e++)c.push(this.createItem(e===r?"active":"",e,e));return c}}window.JSTable=class{constructor(s){let a=arguments.length>1&&void 0!==arguments[1]?arguments[1]:{},i=s;"string"==typeof s&&(i=document.querySelector(s)),null!==i&&(this.config=this._merge(e,a),this.table=new t(i),this.currentPage=1,this.columnRenderers=[],this.columnsNotSearchable=[],this.searchQuery=null,this.sortColumn=null,this.sortDirection="asc",this.isSearching=!1,this.dataCount=null,this.filteredDataCount=null,this.searchTimeout=null,this.pager=new r(this),this._build(),this._buildColumns(),this.update(null===this.config.deferLoading),this._bindEvents(),this._emit("init"),this._parseQueryParams())}_build(){let e=this.config;this.wrapper=document.createElement("div"),this.wrapper.className=e.classes.wrapper;var t=["",e.layout.top,"
","","
",e.labels.loading,"
","
","",e.layout.bottom,"
"].join("");if(t=t.replace("{info}","
"),e.perPageSelect){var s=["","",e.labels.perPage," ","
"].join(""),a=document.createElement("select");a.className=e.classes.selector,e.perPageSelect.forEach((function(t){var s=t===e.perPage,r=new Option(t,t,s,s);a.add(r)})),s=s.replace("{select}",a.outerHTML),t=t.replace(/\{select\}/g,s)}else t=t.replace(/\{select\}/g,"");if(e.searchable){var r=[""," ","
"].join("");t=t.replace(/\{search\}/g,r)}else t=t.replace(/\{search\}/g,"");this.table.element.classList.add(e.classes.table),t=t.replace("{pager}",""),this.wrapper.innerHTML=t,this.table.element.parentNode.replaceChild(this.wrapper,this.table.element),this.wrapper.querySelector("."+e.classes.container).appendChild(this.table.element),this._updatePagination(),this._updateInfo()}async update(){let e=!(arguments.length>0&&void 0!==arguments[0])||arguments[0];var t=this;this.currentPage>this.pager.getPages()&&(this.currentPage=this.pager.getPages());let s=t.wrapper.querySelector(" ."+t.config.classes.loading);if(s.classList.remove("hidden"),this.table.header.getCells().forEach((function(e,s){let a=t.table.head.rows[0].cells[s];a.innerHTML=e.getInnerHTML(),e.classes.length>0&&(a.className=e.classes.join(" "));for(let t in e.attributes)a.setAttribute(t,e.attributes[t]);a.setAttribute("data-sortable",e.isSortable)})),e)return this.getPageData(this.currentPage).then((function(e){t.table.element.classList.remove("hidden"),t.table.body.innerHTML="",e.forEach((function(e){t.table.body.appendChild(e.getFormatted(t.columnRenderers,t.config.rowAttributesCreator))})),s.classList.add("hidden")})).then((function(){t.getDataCount()<=0&&(t.wrapper.classList.remove("search-results"),t.setMessage(t.config.labels.noRows)),t._emit("update")})).then((function(){t._updatePagination(),t._updateInfo()}));t.table.element.classList.remove("hidden"),t.table.body.innerHTML="",this.getDataCount()<=0&&(t.wrapper.classList.remove("search-results"),t.setMessage(t.config.labels.noRows)),this._getData().forEach((function(e){t.table.body.appendChild(e.getFormatted(t.columnRenderers,t.config.rowAttributesCreator))})),s.classList.add("hidden")}_updatePagination(){let e=this.wrapper.querySelector(" ."+this.config.classes.pagination);e.innerHTML="",e.appendChild(this.pager.render(this.currentPage))}_updateInfo(){let e=this.wrapper.querySelector(" ."+this.config.classes.info),t=this.isSearching?this.config.labels.infoFiltered:this.config.labels.info;if(e&&t.length){var s=t.replace("{start}",this.getDataCount()>0?this._getPageStartIndex()+1:0).replace("{end}",this._getPageEndIndex()+1).replace("{page}",this.currentPage).replace("{pages}",this.pager.getPages()).replace("{rows}",this.getDataCount()).replace("{rowsTotal}",this.getDataCountTotal());e.innerHTML=s}}_getPageStartIndex(){return(this.currentPage-1)*this.config.perPage}_getPageEndIndex(){let e=this.currentPage*this.config.perPage-1;return e>this.getDataCount()-1?this.getDataCount()-1:e}_getData(){return this._emit("getData",this.table.dataRows),this.table.dataRows.filter((function(e){return e.visible}))}_fetchData(){var e=this;let t={searchQuery:this.searchQuery,sortColumn:this.sortColumn,sortDirection:this.sortDirection,start:this._getPageStartIndex(),length:this.config.perPage,datatable:1};t=Object.assign({},this.config.ajaxParams,t);let a=this.config.ajax+"?"+this._queryParams(t);return fetch(a,{method:this.config.method,credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"}}).then((function(e){return e.json()})).then((function(t){return e._emit("fetchData",t),e.dataCount=t.recordsTotal,e.filteredDataCount=t.recordsFiltered,t.data})).then((function(e){let t=[];return e.forEach((function(e){t.push(s.createFromData(e))})),t})).catch((function(e){console.error(e)}))}_queryParams(e){return Object.keys(e).map((t=>encodeURIComponent(t)+"="+encodeURIComponent(e[t]))).join("&")}getDataCount(){return this.isSearching?this.getDataCountFiltered():this.getDataCountTotal()}getDataCountFiltered(){return this.config.serverSide?this.filteredDataCount:this._getData().length}getDataCountTotal(){return this.config.serverSide?null!==this.config.deferLoading?this.config.deferLoading:this.dataCount:this.table.dataRows.length}getPageData(){if(this.config.serverSide)return this._fetchData();let e=this._getPageStartIndex();var t=this._getPageEndIndex();return Promise.resolve(this._getData()).then((function(s){return s.filter((function(s,a){return a>=e&&a<=t}))}))}async search(e){var t=this;if(this.searchQuery===e.toLowerCase())return!1;if(this.searchQuery=e.toLowerCase(),this.config.searchDelay){if(this.searchTimeout)return!1;this.searchTimeout=setTimeout((function(){t.searchTimeout=null}),this.config.searchDelay)}return this.currentPage=1,this.isSearching=!0,this.searchQuery.length?(this.config.serverSide||this.table.dataRows.forEach((function(e){e.visible=!1,t.searchQuery.split(" ").reduce((function(s,a){var r;let i=e.getCells();return i=i.filter((function(e,s){if(t.columnsNotSearchable.indexOf(s)<0)return!0})),r=i.some((function(e,t){if(e.getTextContent().toLowerCase().indexOf(a)>=0)return!0})),s&&r}),!0)&&(e.visible=!0)})),this.wrapper.classList.add("search-results"),this.update().then((function(){t._emit("search",e)}))):(this.table.dataRows.forEach((function(e){e.visible=!0})),this.isSearching=!1,t.wrapper.classList.remove("search-results"),t.update(),!1)}sort(e,t){let s=arguments.length>2&&void 0!==arguments[2]&&arguments[2];var a=this;if(this.sortColumn=e||0,this.sortDirection=t,this.sortColumn<0||this.sortColumn>this.table.getColumnCount()-1)return!1;var r=this.table.header.getCell(this.sortColumn),i=this.table.dataRows;this.table.header.getCells().forEach((function(e){e.removeClass("asc"),e.removeClass("desc")})),r.addClass(this.sortDirection),this.config.serverSide||(i=i.sort((function(e,t){var s=e.getCellTextContent(a.sortColumn).toLowerCase(),r=t.getCellTextContent(a.sortColumn).toLowerCase();return s=s.replace(/(\$|\,|\s|%)/g,""),r=r.replace(/(\$|\,|\s|%)/g,""),s=isNaN(s)||""===s?s:parseFloat(s),r=isNaN(r)||""===r?r:parseFloat(r),""===s&&""!==r||!isNaN(s)&&isNaN(r)?"asc"===a.sortDirection?1:-1:""!==s&&""===r||isNaN(s)&&!isNaN(r)?"asc"===a.sortDirection?-1:1:"asc"===a.sortDirection?s===r?0:s>r?1:-1:s===r?0:s'+e+"",this.table.body.innerHTML="",this.table.body.appendChild(s)}_buildColumns(){var e=this;let t=null,s=null;this.config.columns&&this.config.columns.forEach((function(a){isNaN(a.select)||(a.select=[a.select]),a.select.forEach((function(r){var i=e.table.header.getCell(r);if(void 0!==i){if(a.hasOwnProperty("render")&&"function"==typeof a.render&&(e.columnRenderers[r]=a.render),a.hasOwnProperty("sortable")){let r=!1;i.hasSortable?r=i.isSortable:(r=a.sortable,i.setSortable(r)),r&&(i.addClass(e.config.classes.sorter),a.hasOwnProperty("sort")&&1===a.select.length&&(t=a.select[0],s=a.sort))}a.hasOwnProperty("searchable")&&(i.addAttribute("data-searchable",a.searchable),!1===a.searchable&&e.columnsNotSearchable.push(r))}}))})),this.table.header.getCells().forEach((function(a,r){null===a.isSortable&&a.setSortable(e.config.sortable),a.isSortable&&(a.addClass(e.config.classes.sorter),a.hasSort&&(t=r,s=a.sortDirection))})),null!==t&&e.sort(t,s,!0)}_merge(e,t){var s=this;return Object.keys(e).forEach((function(a){!t.hasOwnProperty(a)||"object"!=typeof t[a]||t[a]instanceof Array||null===t[a]?t.hasOwnProperty(a)||(t[a]=e[a]):s._merge(e[a],t[a])})),t}async _parseQueryParams(){const e=new URLSearchParams(window.location.search);let t=e.get(this.config.queryParams.perPage);if(t){t=parseInt(t),this.config.perPage=t,this.wrapper.querySelectorAll("."+this.config.classes.selector).forEach((function(e){e.querySelectorAll("option").forEach((e=>e.removeAttribute("selected"))),e.value=t,e.querySelector(`option[value='${t}']`).setAttribute("selected","")})),this.update()}let s=e.get(this.config.queryParams.search);if(s){this.wrapper.querySelectorAll("."+this.config.classes.input).forEach((function(e){e.value=s})),await this.search(s)}let a=e.get(this.config.queryParams.page);a&&await this.paginate(parseInt(a));let r=e.get(this.config.queryParams.sortColumn);if(r){r=parseInt(r);let t=e.get(this.config.queryParams.sortDirection);t=null==t?"asc":t,this.sort(r,t)}}}}();
--------------------------------------------------------------------------------
/dist/jstable.min.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * JSTable v1.6.5
3 | */
4 | const JSTableDefaultConfig={perPage:5,perPageSelect:[5,10,15,20,25],sortable:!0,searchable:!0,nextPrev:!0,firstLast:!1,prevText:"‹",nextText:"›",firstText:"«",lastText:"»",ellipsisText:"…",truncatePager:!0,pagerDelta:2,classes:{top:"dt-top",info:"dt-info",input:"dt-input",table:"dt-table",bottom:"dt-bottom",search:"dt-search",sorter:"dt-sorter",wrapper:"dt-wrapper",dropdown:"dt-dropdown",ellipsis:"dt-ellipsis",selector:"dt-selector",container:"dt-container",pagination:"dt-pagination",loading:"dt-loading",message:"dt-message"},labels:{placeholder:"Search...",perPage:"{select} entries per page",noRows:"No entries found",info:"Showing {start} to {end} of {rows} entries",loading:"Loading...",infoFiltered:"Showing {start} to {end} of {rows} entries (filtered from {rowsTotal} entries)"},layout:{top:"{select}{search}",bottom:"{info}{pager}"},serverSide:!1,deferLoading:null,ajax:null,ajaxParams:{},queryParams:{page:"page",search:"search",sortColumn:"sortColumn",sortDirection:"sortDirection",perPage:"perPage"},addQueryParams:!0,rowAttributesCreator:null,searchDelay:null,method:"GET"};class JSTable{constructor(e,t={}){let s=e;"string"==typeof e&&(s=document.querySelector(e)),null!==s&&(this.config=this._merge(JSTableDefaultConfig,t),this.table=new JSTableElement(s),this.currentPage=1,this.columnRenderers=[],this.columnsNotSearchable=[],this.searchQuery=null,this.sortColumn=null,this.sortDirection="asc",this.isSearching=!1,this.dataCount=null,this.filteredDataCount=null,this.searchTimeout=null,this.pager=new JSTablePager(this),this._build(),this._buildColumns(),this.update(null===this.config.deferLoading),this._bindEvents(),this._emit("init"),this._parseQueryParams())}_build(){let e=this.config;this.wrapper=document.createElement("div"),this.wrapper.className=e.classes.wrapper;var t=["",e.layout.top,"
","","
",e.labels.loading,"
","
","",e.layout.bottom,"
"].join("");if(t=t.replace("{info}","
"),e.perPageSelect){var s=["","",e.labels.perPage," ","
"].join(""),a=document.createElement("select");a.className=e.classes.selector,e.perPageSelect.forEach((function(t){var s=t===e.perPage,r=new Option(t,t,s,s);a.add(r)})),s=s.replace("{select}",a.outerHTML),t=t.replace(/\{select\}/g,s)}else t=t.replace(/\{select\}/g,"");if(e.searchable){var r=[""," ","
"].join("");t=t.replace(/\{search\}/g,r)}else t=t.replace(/\{search\}/g,"");this.table.element.classList.add(e.classes.table),t=t.replace("{pager}",""),this.wrapper.innerHTML=t,this.table.element.parentNode.replaceChild(this.wrapper,this.table.element),this.wrapper.querySelector("."+e.classes.container).appendChild(this.table.element),this._updatePagination(),this._updateInfo()}async update(e=!0){var t=this;this.currentPage>this.pager.getPages()&&(this.currentPage=this.pager.getPages());let s=t.wrapper.querySelector(" ."+t.config.classes.loading);if(s.classList.remove("hidden"),this.table.header.getCells().forEach((function(e,s){let a=t.table.head.rows[0].cells[s];a.innerHTML=e.getInnerHTML(),e.classes.length>0&&(a.className=e.classes.join(" "));for(let t in e.attributes)a.setAttribute(t,e.attributes[t]);a.setAttribute("data-sortable",e.isSortable)})),e)return this.getPageData(this.currentPage).then((function(e){t.table.element.classList.remove("hidden"),t.table.body.innerHTML="",e.forEach((function(e){t.table.body.appendChild(e.getFormatted(t.columnRenderers,t.config.rowAttributesCreator))})),s.classList.add("hidden")})).then((function(){t.getDataCount()<=0&&(t.wrapper.classList.remove("search-results"),t.setMessage(t.config.labels.noRows)),t._emit("update")})).then((function(){t._updatePagination(),t._updateInfo()}));t.table.element.classList.remove("hidden"),t.table.body.innerHTML="",this.getDataCount()<=0&&(t.wrapper.classList.remove("search-results"),t.setMessage(t.config.labels.noRows)),this._getData().forEach((function(e){t.table.body.appendChild(e.getFormatted(t.columnRenderers,t.config.rowAttributesCreator))})),s.classList.add("hidden")}_updatePagination(){let e=this.wrapper.querySelector(" ."+this.config.classes.pagination);e.innerHTML="",e.appendChild(this.pager.render(this.currentPage))}_updateInfo(){let e=this.wrapper.querySelector(" ."+this.config.classes.info),t=this.isSearching?this.config.labels.infoFiltered:this.config.labels.info;if(e&&t.length){var s=t.replace("{start}",this.getDataCount()>0?this._getPageStartIndex()+1:0).replace("{end}",this._getPageEndIndex()+1).replace("{page}",this.currentPage).replace("{pages}",this.pager.getPages()).replace("{rows}",this.getDataCount()).replace("{rowsTotal}",this.getDataCountTotal());e.innerHTML=s}}_getPageStartIndex(){return(this.currentPage-1)*this.config.perPage}_getPageEndIndex(){let e=this.currentPage*this.config.perPage-1;return e>this.getDataCount()-1?this.getDataCount()-1:e}_getData(){return this._emit("getData",this.table.dataRows),this.table.dataRows.filter((function(e){return e.visible}))}_fetchData(){var e=this;let t={searchQuery:this.searchQuery,sortColumn:this.sortColumn,sortDirection:this.sortDirection,start:this._getPageStartIndex(),length:this.config.perPage,datatable:1};t=Object.assign({},this.config.ajaxParams,t);let s=this.config.ajax+"?"+this._queryParams(t);return fetch(s,{method:this.config.method,credentials:"same-origin",headers:{Accept:"application/json","Content-Type":"application/json"}}).then((function(e){return e.json()})).then((function(t){return e._emit("fetchData",t),e.dataCount=t.recordsTotal,e.filteredDataCount=t.recordsFiltered,t.data})).then((function(e){let t=[];return e.forEach((function(e){t.push(JSTableRow.createFromData(e))})),t})).catch((function(e){console.error(e)}))}_queryParams(e){return Object.keys(e).map((t=>encodeURIComponent(t)+"="+encodeURIComponent(e[t]))).join("&")}getDataCount(){return this.isSearching?this.getDataCountFiltered():this.getDataCountTotal()}getDataCountFiltered(){return this.config.serverSide?this.filteredDataCount:this._getData().length}getDataCountTotal(){return this.config.serverSide?null!==this.config.deferLoading?this.config.deferLoading:this.dataCount:this.table.dataRows.length}getPageData(){if(this.config.serverSide)return this._fetchData();let e=this._getPageStartIndex();var t=this._getPageEndIndex();return Promise.resolve(this._getData()).then((function(s){return s.filter((function(s,a){return a>=e&&a<=t}))}))}async search(e){var t=this;if(this.searchQuery===e.toLowerCase())return!1;if(this.searchQuery=e.toLowerCase(),this.config.searchDelay){if(this.searchTimeout)return!1;this.searchTimeout=setTimeout((function(){t.searchTimeout=null}),this.config.searchDelay)}return this.currentPage=1,this.isSearching=!0,this.searchQuery.length?(this.config.serverSide||this.table.dataRows.forEach((function(e){e.visible=!1,t.searchQuery.split(" ").reduce((function(s,a){var r;let i=e.getCells();return i=i.filter((function(e,s){if(t.columnsNotSearchable.indexOf(s)<0)return!0})),r=i.some((function(e,t){if(e.getTextContent().toLowerCase().indexOf(a)>=0)return!0})),s&&r}),!0)&&(e.visible=!0)})),this.wrapper.classList.add("search-results"),this.update().then((function(){t._emit("search",e)}))):(this.table.dataRows.forEach((function(e){e.visible=!0})),this.isSearching=!1,t.wrapper.classList.remove("search-results"),t.update(),!1)}sort(e,t,s=!1){var a=this;if(this.sortColumn=e||0,this.sortDirection=t,this.sortColumn<0||this.sortColumn>this.table.getColumnCount()-1)return!1;var r=this.table.header.getCell(this.sortColumn),i=this.table.dataRows;this.table.header.getCells().forEach((function(e){e.removeClass("asc"),e.removeClass("desc")})),r.addClass(this.sortDirection),this.config.serverSide||(i=i.sort((function(e,t){var s=e.getCellTextContent(a.sortColumn).toLowerCase(),r=t.getCellTextContent(a.sortColumn).toLowerCase();return s=s.replace(/(\$|\,|\s|%)/g,""),r=r.replace(/(\$|\,|\s|%)/g,""),s=isNaN(s)||""===s?s:parseFloat(s),r=isNaN(r)||""===r?r:parseFloat(r),""===s&&""!==r||!isNaN(s)&&isNaN(r)?"asc"===a.sortDirection?1:-1:""!==s&&""===r||isNaN(s)&&!isNaN(r)?"asc"===a.sortDirection?-1:1:"asc"===a.sortDirection?s===r?0:s>r?1:-1:s===r?0:s'+e+"",this.table.body.innerHTML="",this.table.body.appendChild(s)}_buildColumns(){var e=this;let t=null,s=null;this.config.columns&&this.config.columns.forEach((function(a){isNaN(a.select)||(a.select=[a.select]),a.select.forEach((function(r){var i=e.table.header.getCell(r);if(void 0!==i){if(a.hasOwnProperty("render")&&"function"==typeof a.render&&(e.columnRenderers[r]=a.render),a.hasOwnProperty("sortable")){let r=!1;i.hasSortable?r=i.isSortable:(r=a.sortable,i.setSortable(r)),r&&(i.addClass(e.config.classes.sorter),a.hasOwnProperty("sort")&&1===a.select.length&&(t=a.select[0],s=a.sort))}a.hasOwnProperty("searchable")&&(i.addAttribute("data-searchable",a.searchable),!1===a.searchable&&e.columnsNotSearchable.push(r))}}))})),this.table.header.getCells().forEach((function(a,r){null===a.isSortable&&a.setSortable(e.config.sortable),a.isSortable&&(a.addClass(e.config.classes.sorter),a.hasSort&&(t=r,s=a.sortDirection))})),null!==t&&e.sort(t,s,!0)}_merge(e,t){var s=this;return Object.keys(e).forEach((function(a){!t.hasOwnProperty(a)||"object"!=typeof t[a]||t[a]instanceof Array||null===t[a]?t.hasOwnProperty(a)||(t[a]=e[a]):s._merge(e[a],t[a])})),t}async _parseQueryParams(){const e=new URLSearchParams(window.location.search);let t=e.get(this.config.queryParams.perPage);if(t){t=parseInt(t),this.config.perPage=t,this.wrapper.querySelectorAll("."+this.config.classes.selector).forEach((function(e){e.querySelectorAll("option").forEach((e=>e.removeAttribute("selected"))),e.value=t,e.querySelector(`option[value='${t}']`).setAttribute("selected","")})),this.update()}let s=e.get(this.config.queryParams.search);if(s){this.wrapper.querySelectorAll("."+this.config.classes.input).forEach((function(e){e.value=s})),await this.search(s)}let a=e.get(this.config.queryParams.page);a&&await this.paginate(parseInt(a));let r=e.get(this.config.queryParams.sortColumn);if(r){r=parseInt(r);let t=e.get(this.config.queryParams.sortDirection);t=null==t?"asc":t,this.sort(r,t)}}}class JSTableElement{constructor(e){this.element=e,this.body=this.element.tBodies[0],this.head=this.element.tHead,this.rows=Array.from(this.element.rows).map((function(e,t){return new JSTableRow(e,e.parentNode.nodeName,t)})),this.dataRows=this._getBodyRows(),this.header=this._getHeaderRow()}_getBodyRows(){return this.rows.filter((function(e){return!e.isHeader&&!e.isFooter}))}_getHeaderRow(){return this.rows.find((function(e){return e.isHeader}))}getColumnCount(){return this.header.getColumnCount()}getFooterRow(){return this.rows.find((function(e){return e.isFooter}))}}class JSTableRow{constructor(e,t="",s=null){this.cells=Array.from(e.cells).map((function(e){return new JSTableCell(e)})),this.d=this.cells.length,this.isHeader="THEAD"===t,this.isFooter="TFOOT"===t,this.visible=!0,this.rowID=s;var a=this;this.attributes={},[...e.attributes].forEach((function(e){a.attributes[e.name]=e.value}))}getCells(){return Array.from(this.cells)}getColumnCount(){return this.cells.length}getCell(e){return this.cells[e]}getCellTextContent(e){return this.getCell(e).getTextContent()}static createFromData(e){let t=document.createElement("tr");if(e.hasOwnProperty("data")){if(e.hasOwnProperty("attributes"))for(const s in e.attributes)t.setAttribute(s,e.attributes[s]);e=e.data}return e.forEach((function(e){let s=document.createElement("td");if(s.innerHTML=e&&e.hasOwnProperty("data")?e.data:e,e&&e.hasOwnProperty("attributes"))for(const t in e.attributes)s.setAttribute(t,e.attributes[t]);t.appendChild(s)})),new JSTableRow(t)}getFormatted(e,t=null){let s=document.createElement("tr");var a=this;for(let e in this.attributes)s.setAttribute(e,this.attributes[e]);let r=t?t.call(this,this.getCells()):{};for(const e in r)s.setAttribute(e,r[e]);return this.getCells().forEach((function(t,r){var i=document.createElement("td");i.innerHTML=t.getInnerHTML(),e.hasOwnProperty(r)&&(i.innerHTML=e[r].call(a,t.getElement(),r)),t.classes.length>0&&(i.className=t.classes.join(" "));for(let e in t.attributes)i.setAttribute(e,t.attributes[e]);s.appendChild(i)})),s}setCellClass(e,t){this.cells[e].addClass(t)}}class JSTableCell{constructor(e){this.textContent=e.textContent,this.innerHTML=e.innerHTML,this.className="",this.element=e,this.hasSortable=e.hasAttribute("data-sortable"),this.isSortable=this.hasSortable?"true"===e.getAttribute("data-sortable"):null,this.hasSort=e.hasAttribute("data-sort"),this.sortDirection=e.getAttribute("data-sort"),this.classes=[];var t=this;this.attributes={},[...e.attributes].forEach((function(e){t.attributes[e.name]=e.value}))}getElement(){return this.element}getTextContent(){return this.textContent}getInnerHTML(){return this.innerHTML}setClass(e){this.className=e}setSortable(e){this.isSortable=e}addClass(e){this.classes.push(e)}removeClass(e){this.classes.indexOf(e)>=0&&this.classes.splice(this.classes.indexOf(e),1)}addAttribute(e,t){this.attributes[e]=t}}class JSTablePager{constructor(e){this.instance=e}getPages(){let e=Math.ceil(this.instance.getDataCount()/this.instance.config.perPage);return 0===e?1:e}render(){var e=this.instance.config;let t=this.getPages(),s=document.createElement("ul");if(t>1){let a=1===this.instance.currentPage?1:this.instance.currentPage-1,r=this.instance.currentPage===t?t:this.instance.currentPage+1;e.firstLast&&s.appendChild(this.createItem("pager",1,e.firstText)),e.nextPrev&&s.appendChild(this.createItem("pager",a,e.prevText)),this.truncate().forEach((function(e){s.appendChild(e)})),e.nextPrev&&s.appendChild(this.createItem("pager",r,e.nextText)),e.firstLast&&s.appendChild(this.createItem("pager",t,e.lastText))}return s}createItem(e,t,s,a){let r=document.createElement("li");return r.className=e,r.innerHTML=a?""+s+" ":''+s+" ",r}isValidPage(e){return e>0&&e<=this.getPages()}truncate(){var e,t=this,s=t.instance.config,a=2*s.pagerDelta,r=t.instance.currentPage,i=r-s.pagerDelta,n=r+s.pagerDelta,o=this.getPages(),l=[],c=[];if(this.instance.config.truncatePager){r<4-s.pagerDelta+a?n=3+a:r>this.getPages()-(3-s.pagerDelta+a)&&(i=this.getPages()-(2+a));for(var h=1;h<=o;h++)(1===h||h===o||h>=i&&h<=n)&&l.push(h);l.forEach((function(a){e&&(a-e==2?c.push(t.createItem("",e+1,e+1)):a-e!=1&&c.push(t.createItem(s.classes.ellipsis,0,s.ellipsisText,!0))),c.push(t.createItem(a==r?"active":"",a,a)),e=a}))}else for(let e=1;e<=this.getPages();e++)c.push(this.createItem(e===r?"active":"",e,e));return c}}window.JSTable=JSTable;
--------------------------------------------------------------------------------
/dist/polyfill-fetch.min.js:
--------------------------------------------------------------------------------
1 | !function(t,e){"object"==typeof exports&&"undefined"!=typeof module?e(exports):"function"==typeof define&&define.amd?define(["exports"],e):e(t.WHATWGFetch={})}(this,(function(t){"use strict";var e="undefined"!=typeof globalThis&&globalThis||"undefined"!=typeof self&&self||void 0!==e&&e,r="URLSearchParams"in e,o="Symbol"in e&&"iterator"in Symbol,n="FileReader"in e&&"Blob"in e&&function(){try{return new Blob,!0}catch(t){return!1}}(),i="FormData"in e,s="ArrayBuffer"in e;if(s)var a=["[object Int8Array]","[object Uint8Array]","[object Uint8ClampedArray]","[object Int16Array]","[object Uint16Array]","[object Int32Array]","[object Uint32Array]","[object Float32Array]","[object Float64Array]"],h=ArrayBuffer.isView||function(t){return t&&a.indexOf(Object.prototype.toString.call(t))>-1};function u(t){if("string"!=typeof t&&(t=String(t)),/[^a-z0-9\-#$%&'*+.^_`|~!]/i.test(t)||""===t)throw new TypeError('Invalid character in header field name: "'+t+'"');return t.toLowerCase()}function f(t){return"string"!=typeof t&&(t=String(t)),t}function c(t){var e={next:function(){var e=t.shift();return{done:void 0===e,value:e}}};return o&&(e[Symbol.iterator]=function(){return e}),e}function d(t){this.map={},t instanceof d?t.forEach((function(t,e){this.append(e,t)}),this):Array.isArray(t)?t.forEach((function(t){this.append(t[0],t[1])}),this):t&&Object.getOwnPropertyNames(t).forEach((function(e){this.append(e,t[e])}),this)}function y(t){if(t.bodyUsed)return Promise.reject(new TypeError("Already read"));t.bodyUsed=!0}function p(t){return new Promise((function(e,r){t.onload=function(){e(t.result)},t.onerror=function(){r(t.error)}}))}function l(t){var e=new FileReader,r=p(e);return e.readAsArrayBuffer(t),r}function b(t){if(t.slice)return t.slice(0);var e=new Uint8Array(t.byteLength);return e.set(new Uint8Array(t)),e.buffer}function m(){return this.bodyUsed=!1,this._initBody=function(t){var e;this.bodyUsed=this.bodyUsed,this._bodyInit=t,t?"string"==typeof t?this._bodyText=t:n&&Blob.prototype.isPrototypeOf(t)?this._bodyBlob=t:i&&FormData.prototype.isPrototypeOf(t)?this._bodyFormData=t:r&&URLSearchParams.prototype.isPrototypeOf(t)?this._bodyText=t.toString():s&&n&&((e=t)&&DataView.prototype.isPrototypeOf(e))?(this._bodyArrayBuffer=b(t.buffer),this._bodyInit=new Blob([this._bodyArrayBuffer])):s&&(ArrayBuffer.prototype.isPrototypeOf(t)||h(t))?this._bodyArrayBuffer=b(t):this._bodyText=t=Object.prototype.toString.call(t):this._bodyText="",this.headers.get("content-type")||("string"==typeof t?this.headers.set("content-type","text/plain;charset=UTF-8"):this._bodyBlob&&this._bodyBlob.type?this.headers.set("content-type",this._bodyBlob.type):r&&URLSearchParams.prototype.isPrototypeOf(t)&&this.headers.set("content-type","application/x-www-form-urlencoded;charset=UTF-8"))},n&&(this.blob=function(){var t=y(this);if(t)return t;if(this._bodyBlob)return Promise.resolve(this._bodyBlob);if(this._bodyArrayBuffer)return Promise.resolve(new Blob([this._bodyArrayBuffer]));if(this._bodyFormData)throw new Error("could not read FormData body as blob");return Promise.resolve(new Blob([this._bodyText]))},this.arrayBuffer=function(){if(this._bodyArrayBuffer){var t=y(this);return t||(ArrayBuffer.isView(this._bodyArrayBuffer)?Promise.resolve(this._bodyArrayBuffer.buffer.slice(this._bodyArrayBuffer.byteOffset,this._bodyArrayBuffer.byteOffset+this._bodyArrayBuffer.byteLength)):Promise.resolve(this._bodyArrayBuffer))}return this.blob().then(l)}),this.text=function(){var t,e,r,o=y(this);if(o)return o;if(this._bodyBlob)return t=this._bodyBlob,e=new FileReader,r=p(e),e.readAsText(t),r;if(this._bodyArrayBuffer)return Promise.resolve(function(t){for(var e=new Uint8Array(t),r=new Array(e.length),o=0;o-1?o:r),this.mode=e.mode||this.mode||null,this.signal=e.signal||this.signal,this.referrer=null,("GET"===this.method||"HEAD"===this.method)&&n)throw new TypeError("Body not allowed for GET or HEAD requests");if(this._initBody(n),!("GET"!==this.method&&"HEAD"!==this.method||"no-store"!==e.cache&&"no-cache"!==e.cache)){var i=/([?&])_=[^&]*/;if(i.test(this.url))this.url=this.url.replace(i,"$1_="+(new Date).getTime());else{this.url+=(/\?/.test(this.url)?"&":"?")+"_="+(new Date).getTime()}}}function E(t){var e=new FormData;return t.trim().split("&").forEach((function(t){if(t){var r=t.split("="),o=r.shift().replace(/\+/g," "),n=r.join("=").replace(/\+/g," ");e.append(decodeURIComponent(o),decodeURIComponent(n))}})),e}function T(t,e){if(!(this instanceof T))throw new TypeError('Please use the "new" operator, this DOM object constructor cannot be called as a function.');e||(e={}),this.type="default",this.status=void 0===e.status?200:e.status,this.ok=this.status>=200&&this.status<300,this.statusText=void 0===e.statusText?"":""+e.statusText,this.headers=new d(e.headers),this.url=e.url||"",this._initBody(t)}v.prototype.clone=function(){return new v(this,{body:this._bodyInit})},m.call(v.prototype),m.call(T.prototype),T.prototype.clone=function(){return new T(this._bodyInit,{status:this.status,statusText:this.statusText,headers:new d(this.headers),url:this.url})},T.error=function(){var t=new T(null,{status:0,statusText:""});return t.type="error",t};var A=[301,302,303,307,308];T.redirect=function(t,e){if(-1===A.indexOf(e))throw new RangeError("Invalid status code");return new T(null,{status:e,headers:{location:t}})},t.DOMException=e.DOMException;try{new t.DOMException}catch(e){t.DOMException=function(t,e){this.message=t,this.name=e;var r=Error(t);this.stack=r.stack},t.DOMException.prototype=Object.create(Error.prototype),t.DOMException.prototype.constructor=t.DOMException}function _(r,o){return new Promise((function(i,a){var h=new v(r,o);if(h.signal&&h.signal.aborted)return a(new t.DOMException("Aborted","AbortError"));var u=new XMLHttpRequest;function c(){u.abort()}u.onload=function(){var t,e,r={status:u.status,statusText:u.statusText,headers:(t=u.getAllResponseHeaders()||"",e=new d,t.replace(/\r?\n[\t ]+/g," ").split("\r").map((function(t){return 0===t.indexOf("\n")?t.substr(1,t.length):t})).forEach((function(t){var r=t.split(":"),o=r.shift().trim();if(o){var n=r.join(":").trim();e.append(o,n)}})),e)};r.url="responseURL"in u?u.responseURL:r.headers.get("X-Request-URL");var o="response"in u?u.response:u.responseText;setTimeout((function(){i(new T(o,r))}),0)},u.onerror=function(){setTimeout((function(){a(new TypeError("Network request failed"))}),0)},u.ontimeout=function(){setTimeout((function(){a(new TypeError("Network request failed"))}),0)},u.onabort=function(){setTimeout((function(){a(new t.DOMException("Aborted","AbortError"))}),0)},u.open(h.method,function(t){try{return""===t&&e.location.href?e.location.href:t}catch(e){return t}}(h.url),!0),"include"===h.credentials?u.withCredentials=!0:"omit"===h.credentials&&(u.withCredentials=!1),"responseType"in u&&(n?u.responseType="blob":s&&h.headers.get("Content-Type")&&-1!==h.headers.get("Content-Type").indexOf("application/octet-stream")&&(u.responseType="arraybuffer")),!o||"object"!=typeof o.headers||o.headers instanceof d?h.headers.forEach((function(t,e){u.setRequestHeader(e,t)})):Object.getOwnPropertyNames(o.headers).forEach((function(t){u.setRequestHeader(t,f(o.headers[t]))})),h.signal&&(h.signal.addEventListener("abort",c),u.onreadystatechange=function(){4===u.readyState&&h.signal.removeEventListener("abort",c)}),u.send(void 0===h._bodyInit?null:h._bodyInit)}))}_.polyfill=!0,e.fetch||(e.fetch=_,e.Headers=d,e.Request=v,e.Response=T),t.Headers=d,t.Request=v,t.Response=T,t.fetch=_,Object.defineProperty(t,"__esModule",{value:!0})}));
--------------------------------------------------------------------------------
/gulpfile.js:
--------------------------------------------------------------------------------
1 | const gulp = require('gulp');
2 | const sass = require( 'gulp-sass' )(require('node-sass'));
3 | const rename = require('gulp-rename');
4 | const terser = require('gulp-terser');
5 | const autoprefixer = require('gulp-autoprefixer');
6 | const webpackStream = require('webpack-stream');
7 | const TerserPlugin = require("terser-webpack-plugin");
8 |
9 | function sassTask(cb) {
10 | return gulp.src('src/jstable.scss')
11 | .pipe(sass({ outputStyle: 'compressed' })
12 | .on('error', printError))
13 | .pipe(autoprefixer())
14 | .pipe(gulp.dest('dist'));
15 | }
16 |
17 | function uglifyTask(cb) {
18 | return gulp.src(['src/jstable.js'])
19 | .pipe(terser())
20 | .on('error', printError)
21 | .pipe(rename({
22 | suffix: '.min'
23 | }))
24 | .pipe(gulp.dest('dist'))
25 | }
26 |
27 | function uglifyCompatibilityTask(cb) {
28 | return gulp.src(['src/jstable.js'])
29 | .pipe(webpackStream({
30 | output: {
31 | filename: 'jstable.js',
32 | },
33 | mode: 'production',
34 | optimization: {
35 | minimize: true,
36 | minimizer: [
37 | new TerserPlugin({
38 | extractComments: false,
39 | }),
40 | ],
41 | },
42 | module: {
43 | rules: [
44 | {
45 | test: /\.js$/,
46 | exclude: /node_modules/,
47 | use: {
48 | loader: "babel-loader",
49 | options: {
50 | presets: [
51 | ['@babel/preset-env', {
52 | "useBuiltIns": "usage",
53 | "corejs": 3
54 | }]
55 | ]
56 | }
57 | }
58 | }
59 | ]
60 | }
61 | }))
62 | .pipe(rename({
63 | suffix: '.es5.min'
64 | }))
65 | .pipe(gulp.dest('dist'))
66 | }
67 |
68 |
69 | function copyPolyfillFetch(cb) {
70 | return gulp.src('node_modules/whatwg-fetch/dist/fetch.umd.js')
71 | .pipe(terser())
72 | .pipe(rename('polyfill-fetch.min.js'))
73 | .pipe(gulp.dest('dist'))
74 | }
75 |
76 | function watchTask(cb) {
77 | gulp.watch(['src/*.scss'], sassTask)
78 | gulp.watch('src/*.js', gulp.series(uglifyTask, uglifyCompatibilityTask));
79 | }
80 |
81 | function printError(error) {
82 | console.log('---- Error ----');
83 | console.log("message", error.cause.message);
84 | console.log("file", error.cause.filename);
85 | console.log("line", error.cause.line);
86 | console.log("col", error.cause.col);
87 | console.log("pos", error.cause.pos);
88 | console.log("");
89 |
90 | // this will ensure that gulp will stop processing the pipeline without a crash
91 | this.emit('end');
92 | }
93 |
94 | exports.sass = sassTask;
95 | exports.uglify = gulp.series(uglifyTask, uglifyCompatibilityTask, copyPolyfillFetch);
96 |
97 | exports.default = watchTask;
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "@jstable/jstable",
3 | "version": "1.6.5",
4 | "description": "a lightweight, dependency-free JavaScript plugin which makes a HTML table interactive",
5 | "repository": {
6 | "type": "git",
7 | "url": "git+https://github.com/jstable/JSTable.git"
8 | },
9 | "license": "MIT",
10 | "bugs": {
11 | "url": "https://github.com/jstable/JSTable/issues"
12 | },
13 | "homepage": "https://jstable.github.io/",
14 | "devDependencies": {
15 | "@babel/core": "^7.19.3",
16 | "@babel/preset-env": "^7.19.3",
17 | "babel-loader": "^8.2.5",
18 | "core-js": "^3.25.4",
19 | "gulp": "^4.0.2",
20 | "gulp-autoprefixer": "^8.0.0",
21 | "gulp-concat": "^2.6.1",
22 | "gulp-rename": "^2.0.0",
23 | "gulp-sass": "^5.1.0",
24 | "gulp-terser": "^2.1.0",
25 | "node-sass": "^7.0.3",
26 | "terser-webpack-plugin": "^5.3.6",
27 | "vinyl-named": "^1.1.0",
28 | "webpack": "^5.74.0",
29 | "webpack-stream": "^7.0.0",
30 | "whatwg-fetch": "^3.6.2"
31 | },
32 | "browserslist": [
33 | "> 1%",
34 | "last 2 versions",
35 | "not dead",
36 | "not ie <= 8"
37 | ]
38 | }
39 |
--------------------------------------------------------------------------------
/server/data.php:
--------------------------------------------------------------------------------
1 | 0) {
33 | return true;
34 | }
35 | }
36 | });
37 | }
38 |
39 | /*
40 | * Sort
41 | */
42 | if (!is_null($sortColumnIndex) && $sortColumnIndex !== FALSE && $sortColumnIndex !== "null" && is_numeric($sortColumnIndex)) {
43 | array_multisort(array_column($filtered, $sortColumnIndex), ($sortDirection === "asc" ? SORT_ASC : SORT_DESC), $filtered);
44 | }
45 |
46 |
47 | /**
48 | * Slice
49 | */
50 | $response = array_slice($filtered, $start, $length);
51 |
52 | echo json_encode([
53 | "recordsTotal" => count($data),
54 | "recordsFiltered" => count($filtered),
55 | "data" => $response
56 | ]);
57 |
--------------------------------------------------------------------------------
/src/jstable.js:
--------------------------------------------------------------------------------
1 | /*!
2 | * JSTable v1.6.5
3 | */
4 |
5 | const JSTableDefaultConfig = {
6 | perPage: 5,
7 | perPageSelect: [5, 10, 15, 20, 25],
8 |
9 | sortable: true,
10 | searchable: true,
11 |
12 | // Pagination
13 | nextPrev: true,
14 | firstLast: false,
15 | prevText: "‹",
16 | nextText: "›",
17 | firstText: "«",
18 | lastText: "»",
19 | ellipsisText: "…",
20 | truncatePager: true,
21 | pagerDelta: 2,
22 |
23 | classes: {
24 | top: "dt-top",
25 | info: "dt-info",
26 | input: "dt-input",
27 | table: "dt-table",
28 | bottom: "dt-bottom",
29 | search: "dt-search",
30 | sorter: "dt-sorter",
31 | wrapper: "dt-wrapper",
32 | dropdown: "dt-dropdown",
33 | ellipsis: "dt-ellipsis",
34 | selector: "dt-selector",
35 | container: "dt-container",
36 | pagination: "dt-pagination",
37 | loading: "dt-loading",
38 | message: "dt-message"
39 | },
40 |
41 | // Customise the display text
42 | labels: {
43 | placeholder: "Search...",
44 | perPage: "{select} entries per page",
45 | noRows: "No entries found",
46 | info: "Showing {start} to {end} of {rows} entries",
47 | loading: "Loading...",
48 | infoFiltered: "Showing {start} to {end} of {rows} entries (filtered from {rowsTotal} entries)"
49 | },
50 |
51 | // Customise the layout
52 | layout: {
53 | top: "{select}{search}",
54 | bottom: "{info}{pager}"
55 | },
56 |
57 | // server side
58 | serverSide: false,
59 | // total count of elements
60 | deferLoading: null,
61 | // url for queries
62 | ajax: null,
63 | // additional params
64 | ajaxParams: {},
65 | // query params names
66 | queryParams: {
67 | page: 'page',
68 | search: 'search',
69 | sortColumn: 'sortColumn',
70 | sortDirection: 'sortDirection',
71 | perPage: 'perPage'
72 | },
73 | // append query params on events
74 | addQueryParams: true,
75 |
76 | rowAttributesCreator: null,
77 |
78 | searchDelay: null,
79 |
80 | // http method request, default is: GET
81 | method: 'GET'
82 | };
83 |
84 | class JSTable {
85 |
86 | constructor(element, config = {}) {
87 |
88 | let DOMElement = element;
89 | if (typeof element === "string") {
90 | DOMElement = document.querySelector(element);
91 | }
92 | if (DOMElement === null) {
93 | return;
94 | }
95 |
96 | this.config = this._merge(JSTableDefaultConfig, config);
97 | this.table = new JSTableElement(DOMElement);
98 |
99 | // reset values
100 | this.currentPage = 1;
101 | this.columnRenderers = [];
102 | this.columnsNotSearchable = [];
103 | this.searchQuery = null;
104 | this.sortColumn = null;
105 | this.sortDirection = "asc";
106 | this.isSearching = false;
107 | this.dataCount = null;
108 | this.filteredDataCount = null;
109 | this.searchTimeout = null;
110 |
111 |
112 | // init pager
113 | this.pager = new JSTablePager(this);
114 |
115 | // build wrapper and layout
116 | this._build();
117 | this._buildColumns();
118 |
119 | // update table content
120 | this.update(this.config.deferLoading === null);
121 |
122 | // bind events
123 | this._bindEvents();
124 |
125 | this._emit("init");
126 |
127 | this._parseQueryParams();
128 |
129 | }
130 |
131 | _build() {
132 | var that = this;
133 | let options = this.config;
134 |
135 | this.wrapper = document.createElement("div");
136 | this.wrapper.className = options.classes.wrapper;
137 |
138 |
139 | var inner = [
140 | "", options.layout.top, "
",
141 | "",
142 | "
", options.labels.loading, "
",
143 | "
",
144 | "", options.layout.bottom, "
"
145 | ].join("");
146 |
147 | // Info placement
148 | inner = inner.replace(
149 | "{info}",
150 | "
"
151 | );
152 |
153 | // Per Page Select
154 | if (options.perPageSelect) {
155 | var wrap = [
156 | "",
157 | "", options.labels.perPage, " ",
158 | "
"
159 | ].join("");
160 |
161 | // Create the select
162 | var select = document.createElement("select");
163 | select.className = options.classes.selector;
164 |
165 | // Create the options
166 | options.perPageSelect.forEach(function (val) {
167 | var selected = val === options.perPage;
168 | var option = new Option(val, val, selected, selected);
169 | select.add(option);
170 | });
171 |
172 | // Custom label
173 | wrap = wrap.replace("{select}", select.outerHTML);
174 |
175 | // Selector placement
176 | inner = inner.replace(/\{select\}/g, wrap);
177 | } else {
178 | inner = inner.replace(/\{select\}/g, "");
179 | }
180 |
181 | // Searchable
182 | if (options.searchable) {
183 | var form = [
184 | "",
185 | " ",
186 | "
"
187 | ].join("");
188 |
189 | // Search input placement
190 | inner = inner.replace(/\{search\}/g, form);
191 | } else {
192 | inner = inner.replace(/\{search\}/g, "");
193 | }
194 |
195 | // Add table class
196 |
197 | this.table.element.classList.add(options.classes.table);
198 |
199 | // Pager
200 |
201 | inner = inner.replace(
202 | "{pager}",
203 | ""
204 | );
205 |
206 | this.wrapper.innerHTML = inner;
207 |
208 | this.table.element.parentNode.replaceChild(this.wrapper, this.table.element);
209 |
210 | let container = this.wrapper.querySelector("." + options.classes.container);
211 | container.appendChild(this.table.element);
212 |
213 | this._updatePagination();
214 | this._updateInfo();
215 |
216 | }
217 |
218 | async update(reloadData = true) {
219 | var that = this;
220 |
221 | // no overlap please
222 | if (this.currentPage > this.pager.getPages()) {
223 | this.currentPage = this.pager.getPages();
224 | }
225 |
226 | let loading = that.wrapper.querySelector(" ." + that.config.classes.loading);
227 | loading.classList.remove("hidden");
228 |
229 | // Create Header
230 | this.table.header.getCells().forEach(function (tableHeaderCell, columnIndex) {
231 |
232 | let th = that.table.head.rows[0].cells[columnIndex];
233 | th.innerHTML = tableHeaderCell.getInnerHTML();
234 | if (tableHeaderCell.classes.length > 0) {
235 | th.className = tableHeaderCell.classes.join(" ");
236 | }
237 | for (let attr in tableHeaderCell.attributes) {
238 | th.setAttribute(attr, tableHeaderCell.attributes[attr]);
239 | }
240 | th.setAttribute("data-sortable", tableHeaderCell.isSortable);
241 | });
242 |
243 | if (reloadData) {
244 |
245 | // Change Table Body
246 | return this.getPageData(this.currentPage).then(function (data) {
247 |
248 | that.table.element.classList.remove("hidden");
249 | that.table.body.innerHTML = "";
250 |
251 | data.forEach(function (row) {
252 | that.table.body.appendChild(row.getFormatted(that.columnRenderers, that.config.rowAttributesCreator));
253 | });
254 |
255 | loading.classList.add("hidden");
256 |
257 | }).then(function () {
258 |
259 | // No Data
260 | if (that.getDataCount() <= 0) {
261 | that.wrapper.classList.remove("search-results");
262 | that.setMessage(that.config.labels.noRows);
263 | }
264 |
265 | that._emit("update");
266 |
267 | }).then(function () {
268 | that._updatePagination();
269 | that._updateInfo();
270 | });
271 | }
272 | // when there is a defer loading (server side) the initial data needs to be formatted
273 | else {
274 | that.table.element.classList.remove("hidden");
275 | that.table.body.innerHTML = "";
276 |
277 | // No Data
278 | if (this.getDataCount() <= 0) {
279 | that.wrapper.classList.remove("search-results");
280 | that.setMessage(that.config.labels.noRows);
281 | }
282 |
283 | this._getData().forEach(function (row) {
284 | that.table.body.appendChild(row.getFormatted(that.columnRenderers, that.config.rowAttributesCreator));
285 | });
286 | loading.classList.add("hidden");
287 |
288 | }
289 | }
290 |
291 | _updatePagination() {
292 | // change Pagination
293 | let pagination = this.wrapper.querySelector(" ." + this.config.classes.pagination);
294 | pagination.innerHTML = "";
295 | pagination.appendChild(this.pager.render(this.currentPage));
296 |
297 |
298 | }
299 |
300 | _updateInfo() {
301 | // change info
302 | let info = this.wrapper.querySelector(" ." + this.config.classes.info);
303 |
304 | let infoString = this.isSearching ? this.config.labels.infoFiltered : this.config.labels.info;
305 | if (info && infoString.length) {
306 | var string = infoString
307 | .replace("{start}", this.getDataCount() > 0 ? this._getPageStartIndex() + 1 : 0)
308 | .replace("{end}", this._getPageEndIndex() + 1)
309 | .replace("{page}", this.currentPage)
310 | .replace("{pages}", this.pager.getPages())
311 | .replace("{rows}", this.getDataCount())
312 | .replace("{rowsTotal}", this.getDataCountTotal());
313 |
314 | info.innerHTML = string;
315 | }
316 | }
317 |
318 | _getPageStartIndex() {
319 | return (this.currentPage - 1) * this.config.perPage;
320 | }
321 | _getPageEndIndex() {
322 | let end = this.currentPage * this.config.perPage - 1;
323 | return end > this.getDataCount() - 1 ? this.getDataCount() - 1 : end;
324 | }
325 |
326 | _getData() {
327 | this._emit("getData", this.table.dataRows);
328 | return this.table.dataRows.filter(function (row) {
329 | return row.visible;
330 | });
331 | }
332 |
333 | _fetchData() {
334 | var that = this;
335 |
336 | let params = {
337 | "searchQuery": this.searchQuery,
338 | "sortColumn": this.sortColumn,
339 | "sortDirection": this.sortDirection,
340 | "start": this._getPageStartIndex(),
341 | "length": this.config.perPage,
342 | "datatable": 1
343 | };
344 |
345 | params = Object.assign({}, this.config.ajaxParams, params);
346 |
347 | let query = this.config.ajax + '?' + this._queryParams(params);
348 |
349 | return fetch(query, {
350 | method: this.config.method,
351 | credentials: "same-origin",
352 | headers: {
353 | 'Accept': 'application/json',
354 | 'Content-Type': 'application/json'
355 | }
356 | }).then(function (response) {
357 | return response.json();
358 | }).then(function (json) {
359 | that._emit("fetchData", json);
360 | that.dataCount = json.recordsTotal;
361 | that.filteredDataCount = json.recordsFiltered;
362 | return json.data;
363 | }).then(function (data) {
364 | let rows = [];
365 | // Create Table
366 | data.forEach(function (dataRow) {
367 | rows.push(JSTableRow.createFromData(dataRow));
368 | });
369 | return rows;
370 | }).catch(function (error) {
371 | console.error(error);
372 | });
373 | }
374 |
375 | _queryParams(params) {
376 | return Object.keys(params)
377 | .map(k => encodeURIComponent(k) + '=' + encodeURIComponent(params[k]))
378 | .join('&');
379 | }
380 |
381 | getDataCount() {
382 | if (this.isSearching) {
383 | return this.getDataCountFiltered();
384 | }
385 | return this.getDataCountTotal();
386 | }
387 |
388 | getDataCountFiltered() {
389 | if (this.config.serverSide) {
390 | return this.filteredDataCount;
391 | }
392 | return this._getData().length;
393 | }
394 |
395 | getDataCountTotal() {
396 | if (this.config.serverSide) {
397 | return this.config.deferLoading !== null ? this.config.deferLoading : this.dataCount;
398 | }
399 | return this.table.dataRows.length;
400 | }
401 |
402 | getPageData() {
403 | // return the ajax data with a promise
404 | if (this.config.serverSide) {
405 | return this._fetchData();
406 | }
407 |
408 |
409 | // filter the table data and return a promise
410 | let start_idx = this._getPageStartIndex();
411 | var end_idx = this._getPageEndIndex();
412 | return Promise.resolve(this._getData()).then(function (data) {
413 | return data.filter(function (row, idx) {
414 | return idx >= start_idx && idx <= end_idx;
415 | });
416 | });
417 | }
418 |
419 | async search(query) {
420 | var that = this;
421 |
422 | // do nothing if the query has not changed since last search
423 | if (this.searchQuery === query.toLowerCase()) {
424 | return false;
425 | }
426 |
427 | this.searchQuery = query.toLowerCase();
428 |
429 | // do not perform another search before enough time has passed since the last
430 | if (this.config.searchDelay) {
431 | if (this.searchTimeout) {
432 | return false;
433 | } else {
434 | // trigger search again after the given delay
435 | this.searchTimeout = setTimeout(
436 | function () {
437 | that.searchTimeout = null;
438 | },
439 | this.config.searchDelay
440 | );
441 | }
442 | }
443 |
444 | // reset parameters
445 | this.currentPage = 1;
446 | this.isSearching = true;
447 |
448 | // reset search
449 | if (!this.searchQuery.length) {
450 | // reset data to all table data
451 | this.table.dataRows.forEach(function (row) {
452 | row.visible = true;
453 | });
454 | this.isSearching = false;
455 | that.wrapper.classList.remove("search-results");
456 | that.update();
457 | return false;
458 | }
459 |
460 | // search in all the data
461 | if (!this.config.serverSide) {
462 | this.table.dataRows.forEach(function (row) {
463 | row.visible = false;
464 |
465 | var match = that.searchQuery.split(" ").reduce(function (bool, word) {
466 | var hasMatch = false;
467 |
468 | let cells = row.getCells();
469 |
470 | // only use searchable cells
471 | cells = cells.filter(function (cell, idx) {
472 | if (that.columnsNotSearchable.indexOf(idx) < 0) {
473 | return true;
474 | }
475 | });
476 |
477 | hasMatch = cells.some(function (cell, idx) {
478 | if (cell.getTextContent().toLowerCase().indexOf(word) >= 0) {
479 | return true;
480 | }
481 | });
482 |
483 | return bool && hasMatch;
484 | }, true);
485 |
486 | if (match) {
487 | row.visible = true;
488 | }
489 | });
490 | }
491 | this.wrapper.classList.add("search-results");
492 |
493 | return this.update().then(function () {
494 | that._emit("search", query);
495 | });
496 |
497 | }
498 |
499 | sort(column, direction, initial = false) {
500 | var that = this;
501 | this.sortColumn = column || 0;
502 | this.sortDirection = direction;
503 |
504 | if (this.sortColumn < 0 || this.sortColumn > this.table.getColumnCount() - 1) {
505 | return false;
506 | }
507 |
508 | var node = this.table.header.getCell(this.sortColumn);
509 | var rows = this.table.dataRows;
510 |
511 |
512 | //Remove class from previus columns
513 | let tableHeaderCells = this.table.header.getCells();
514 | tableHeaderCells.forEach(function (tableHeaderCell) {
515 | tableHeaderCell.removeClass("asc");
516 | tableHeaderCell.removeClass("desc");
517 | });
518 |
519 | node.addClass(this.sortDirection);
520 |
521 | if (!this.config.serverSide) {
522 | rows = rows.sort(function (a, b) {
523 | var ca = a.getCellTextContent(that.sortColumn).toLowerCase();
524 | var cb = b.getCellTextContent(that.sortColumn).toLowerCase();
525 |
526 | // replace $, coma, whitespace and %
527 | ca = ca.replace(/(\$|\,|\s|%)/g, "");
528 | cb = cb.replace(/(\$|\,|\s|%)/g, "");
529 |
530 | ca = !isNaN(ca) && ca !== '' ? parseFloat(ca) : ca;
531 | cb = !isNaN(cb) && cb !== '' ? parseFloat(cb) : cb;
532 |
533 | // Sort empty cells or cells with different content types (numeric/not numeric) to top
534 | if ((ca === '' && cb !== '') || (!isNaN(ca) && isNaN(cb))) {
535 | return that.sortDirection === "asc" ? 1 : -1;
536 | }
537 | if ((ca !== '' && cb === '') || (isNaN(ca) && !isNaN(cb))) {
538 | return that.sortDirection === "asc" ? -1 : 1;
539 | }
540 |
541 | // Otherwise
542 | if (that.sortDirection === "asc") {
543 | return ca === cb ? 0 : ca > cb ? 1 : -1;
544 | }
545 | return ca === cb ? 0 : ca < cb ? 1 : -1;
546 |
547 | });
548 |
549 | // replace dataRows with sorted rows
550 | this.table.dataRows = rows;
551 |
552 | }
553 |
554 | // initial sorting with serverSide on is not needed
555 | if (!this.config.serverSide || !initial) {
556 | this.update();
557 | }
558 |
559 | this._emit("sort", this.sortColumn, this.sortDirection);
560 |
561 | }
562 |
563 | async paginate(new_page) {
564 | var that = this;
565 |
566 | this.currentPage = new_page;
567 | return this.update().then(function () {
568 | that._emit("paginate", that.currentPage, new_page);
569 | });
570 | }
571 |
572 | _setQueryParam(key, value) {
573 | if (!this.config.addQueryParams) return;
574 |
575 | const url = new URL(window.location.href);
576 | url.searchParams.set(this.config.queryParams[key], value);
577 | window.history.replaceState(null, null, url);
578 | }
579 |
580 | _bindEvents() {
581 | var that = this;
582 |
583 |
584 | this.wrapper.addEventListener("click", function (event) {
585 | var node = event.target;
586 |
587 | if (node.hasAttribute("data-page")) {
588 | event.preventDefault();
589 | let new_page = parseInt(node.getAttribute("data-page"), 10);
590 | that.paginate(new_page);
591 |
592 | that._setQueryParam('page', new_page)
593 | }
594 |
595 | if (node.nodeName === "TH" && node.hasAttribute("data-sortable")) {
596 |
597 | if (node.getAttribute("data-sortable") === "false")
598 | return false;
599 |
600 | event.preventDefault();
601 | let sortDirection = node.classList.contains("asc") ? "desc" : "asc";
602 | that.sort(node.cellIndex, sortDirection);
603 |
604 | that._setQueryParam('sortColumn', node.cellIndex)
605 | that._setQueryParam('sortDirection', sortDirection)
606 | }
607 | });
608 |
609 | if (this.config.perPageSelect) {
610 | this.wrapper.addEventListener("change", function (e) {
611 | var node = e.target;
612 | if (node.nodeName === "SELECT" && node.classList.contains(that.config.classes.selector)) {
613 | e.preventDefault();
614 | let value = parseInt(node.value, 10);
615 | that._emit("perPageChange", that.config.perPage, value);
616 | that.config.perPage = value;
617 | that.update();
618 |
619 | that._setQueryParam('perPage', value)
620 | }
621 | });
622 | }
623 |
624 | if (this.config.searchable) {
625 | this.wrapper.addEventListener("keyup", function (e) {
626 | if (e.target.nodeName === "INPUT" && e.target.classList.contains(that.config.classes.input)) {
627 | e.preventDefault();
628 | that.search(e.target.value);
629 |
630 | that._setQueryParam('search', e.target.value)
631 | }
632 | });
633 | }
634 | }
635 |
636 | on(event, callback) {
637 | this.events = this.events || {};
638 | this.events[event] = this.events[event] || [];
639 | this.events[event].push(callback);
640 | }
641 |
642 | off(event, callback) {
643 | this.events = this.events || {};
644 | if (event in this.events === false)
645 | return;
646 | this.events[event].splice(this.events[event].indexOf(callback), 1);
647 | }
648 |
649 | _emit(event) {
650 | this.events = this.events || {};
651 | if (event in this.events === false)
652 | return;
653 | for (var i = 0; i < this.events[event].length; i++) {
654 | this.events[event][i].apply(this, Array.prototype.slice.call(arguments, 1));
655 | }
656 | }
657 |
658 | setMessage(message) {
659 | var colspan = this.table.getColumnCount();
660 |
661 | var node = document.createElement("tr");
662 | node.innerHTML = '' +
665 | message +
666 | " ";
667 |
668 | this.table.body.innerHTML = "";
669 |
670 | this.table.body.appendChild(node);
671 | }
672 |
673 | _buildColumns() {
674 | var that = this;
675 |
676 | /**
677 | * change sortable attribute of columns:
678 | * - a global definition of the sortable attribute of the table can be overwritten
679 | * by a "data-sortable" attribute on the table header or
680 | * by a custom column definitions attribute "sortable"
681 | * - a custom column definitions attribute "sortable" can be overwritten
682 | * by a "data-sortable" attribute on the table header
683 | * - a "data-sortable" attribute on the table header cannot be overwritten
684 | *
685 | * the initial sort column/direction can be defined on the table header (data-sort) or
686 | * on the custom column definitions (attribute "sort") with possible values "asc"/"desc"
687 | * - a custom column definitions attribute is overwritten by a "data-sort" attribute
688 | * - since sorting is only supported for 1 column at the same time the last defined column is used
689 | */
690 |
691 | let initialSortColumn = null;
692 | let initialSortDirection = null;
693 |
694 | /**
695 | * Process custom column definitions
696 | */
697 | if (this.config.columns) {
698 |
699 | this.config.columns.forEach(function (columnsDefinition) {
700 |
701 | // convert single column selection to array
702 | if (!isNaN(columnsDefinition.select)) {
703 | columnsDefinition.select = [columnsDefinition.select];
704 | }
705 |
706 | // Add the data attributes to the th elements
707 | columnsDefinition.select.forEach(function (column) {
708 | var tableHeaderCell = that.table.header.getCell(column);
709 |
710 | // Skip missing
711 | if (tableHeaderCell === undefined) {
712 | return;
713 | }
714 |
715 | /**
716 | * Rendering
717 | */
718 | if (columnsDefinition.hasOwnProperty("render") && typeof columnsDefinition.render === "function") {
719 | that.columnRenderers[column] = columnsDefinition.render;
720 | }
721 |
722 | /**
723 | * Sortable
724 | */
725 | if (columnsDefinition.hasOwnProperty("sortable")) {
726 |
727 | let sortable = false;
728 | if (tableHeaderCell.hasSortable) {
729 | sortable = tableHeaderCell.isSortable;
730 | } else {
731 | sortable = columnsDefinition.sortable;
732 | tableHeaderCell.setSortable(sortable);
733 | }
734 |
735 |
736 | if (sortable) {
737 |
738 | tableHeaderCell.addClass(that.config.classes.sorter);
739 |
740 | // save sortable column/direction
741 | // when there is one selected column in columns definition
742 | // and this column should be sortable
743 | if (columnsDefinition.hasOwnProperty("sort") && columnsDefinition.select.length === 1) {
744 | initialSortColumn = columnsDefinition.select[0];
745 | initialSortDirection = columnsDefinition.sort;
746 | }
747 | }
748 | }
749 |
750 | /**
751 | * Searchable (not serverside)
752 | */
753 | if (columnsDefinition.hasOwnProperty("searchable")) {
754 | tableHeaderCell.addAttribute("data-searchable", columnsDefinition.searchable);
755 |
756 | if (columnsDefinition.searchable === false) {
757 | that.columnsNotSearchable.push(column);
758 | }
759 | }
760 |
761 | });
762 | });
763 | }
764 |
765 | /**
766 | * Process data-attributes
767 | */
768 | this.table.header.getCells().forEach(function (tableHeaderCell, columnIndex) {
769 |
770 | if (tableHeaderCell.isSortable === null) {
771 | tableHeaderCell.setSortable(that.config.sortable);
772 | }
773 |
774 | if (tableHeaderCell.isSortable) {
775 | tableHeaderCell.addClass(that.config.classes.sorter);
776 |
777 | if (tableHeaderCell.hasSort) {
778 | initialSortColumn = columnIndex;
779 | initialSortDirection = tableHeaderCell.sortDirection;
780 | }
781 | }
782 | });
783 |
784 |
785 | // sort the table by the last column which is marked to be sorted
786 | if (initialSortColumn !== null) {
787 | that.sort(initialSortColumn, initialSortDirection, true);
788 | }
789 |
790 | }
791 |
792 | // deep merge two objects
793 | _merge(current, update) {
794 | var that = this;
795 | Object.keys(current).forEach(function (key) {
796 | if (update.hasOwnProperty(key) && typeof update[key] === "object" && !(update[key] instanceof Array) && update[key] !== null) {
797 | that._merge(current[key], update[key]);
798 | } else if (!update.hasOwnProperty(key)) {
799 | update[key] = current[key];
800 | }
801 | });
802 | return update;
803 | }
804 |
805 | async _parseQueryParams() {
806 | const urlParams = new URLSearchParams(window.location.search);
807 |
808 | let perPage = urlParams.get(this.config.queryParams.perPage);
809 | if (perPage) {
810 | perPage = parseInt(perPage)
811 | this.config.perPage = perPage;
812 | let selectors = this.wrapper.querySelectorAll('.' + this.config.classes.selector);
813 | selectors.forEach(function (selector) {
814 | selector.querySelectorAll('option').forEach(opt => opt.removeAttribute('selected'))
815 | selector.value = perPage;
816 | selector.querySelector(`option[value='${perPage}']`).setAttribute('selected', '')
817 | });
818 | this.update()
819 | }
820 |
821 | // parse search param and populate search
822 | let search = urlParams.get(this.config.queryParams.search);
823 | if (search) {
824 | let searchfields = this.wrapper.querySelectorAll('.' + this.config.classes.input);
825 | searchfields.forEach(function (searchfield) {
826 | searchfield.value = search;
827 | });
828 | await this.search(search);
829 | }
830 |
831 | // parse page param and navigate to page
832 | let page = urlParams.get(this.config.queryParams.page);
833 | if (page) {
834 | await this.paginate(parseInt(page));
835 | }
836 |
837 | let sortColumn = urlParams.get(this.config.queryParams.sortColumn);
838 | if (sortColumn) {
839 | sortColumn = parseInt(sortColumn)
840 | let sortDirection = urlParams.get(this.config.queryParams.sortDirection);
841 | sortDirection = (sortDirection == undefined) ? "asc" : sortDirection;
842 | this.sort(sortColumn, sortDirection);
843 | }
844 | }
845 | }
846 |
847 | class JSTableElement {
848 |
849 | constructor(element) {
850 | this.element = element;
851 |
852 | this.body = this.element.tBodies[0];
853 | this.head = this.element.tHead;
854 |
855 | // we are modifying the data of the underlying element so first
856 | // make a copy for row extraction
857 | //let table = this.element.cloneNode(true);
858 |
859 | // Process table rows
860 | this.rows = Array.from(this.element.rows).map(function (row, rowID) {
861 | return new JSTableRow(row, row.parentNode.nodeName, rowID);
862 | });
863 |
864 | this.dataRows = this._getBodyRows();
865 | this.header = this._getHeaderRow();
866 |
867 | }
868 |
869 | _getBodyRows() {
870 | return this.rows.filter(function (row) {
871 | return !row.isHeader && !row.isFooter;
872 | });
873 | }
874 |
875 | _getHeaderRow() {
876 | return this.rows.find(function (row) {
877 | return row.isHeader;
878 | });
879 | }
880 |
881 | getColumnCount() {
882 | return this.header.getColumnCount();
883 | }
884 |
885 | getFooterRow() {
886 | return this.rows.find(function (row) {
887 | return row.isFooter;
888 | });
889 | }
890 |
891 | }
892 |
893 | class JSTableRow {
894 |
895 | constructor(element, parentName = "", rowID = null) {
896 | // Process row cells
897 | this.cells = Array.from(element.cells).map(function (cell) {
898 | return new JSTableCell(cell);
899 | });
900 |
901 | this.d = this.cells.length;
902 |
903 | //this.element = element;
904 | this.isHeader = parentName === "THEAD";
905 | this.isFooter = parentName === "TFOOT";
906 | this.visible = true;
907 | this.rowID = rowID;
908 |
909 | var that = this;
910 | // parse attributes
911 | this.attributes = {};
912 | [...element.attributes].forEach(function (attr) {
913 | that.attributes[attr.name] = attr.value;
914 | });
915 | }
916 |
917 | getCells() {
918 | return Array.from(this.cells);
919 | }
920 |
921 | getColumnCount() {
922 | return this.cells.length;
923 | }
924 |
925 | getCell(cell) {
926 | return this.cells[cell];
927 | }
928 |
929 | // for sorting
930 | getCellTextContent(cell) {
931 | return this.getCell(cell).getTextContent();
932 | }
933 |
934 | static createFromData(data) {
935 | let tr = document.createElement("tr");
936 |
937 | if(data.hasOwnProperty("data")){
938 | if(data.hasOwnProperty("attributes")){
939 | for (const attrName in data["attributes"]) {
940 | tr.setAttribute(attrName, data["attributes"][attrName])
941 | }
942 | }
943 | data = data["data"];
944 | }
945 |
946 | data.forEach(function (cellData) {
947 | let td = document.createElement("td");
948 | td.innerHTML = !!cellData && cellData.hasOwnProperty("data") ? cellData["data"]: cellData;
949 |
950 | if(!!cellData && cellData.hasOwnProperty("attributes")){
951 | for (const attrName in cellData["attributes"]) {
952 | td.setAttribute(attrName, cellData["attributes"][attrName])
953 | }
954 | }
955 | tr.appendChild(td);
956 | });
957 | return new JSTableRow(tr);
958 | }
959 |
960 | getFormatted(columnRenderers, rowAttributesCreator = null) {
961 | let tr = document.createElement("tr");
962 | var that = this;
963 |
964 | for (let attr in this.attributes) {
965 | tr.setAttribute(attr, this.attributes[attr]);
966 | }
967 |
968 | let rowAttributes = !!rowAttributesCreator ? rowAttributesCreator.call(this, this.getCells()): {};
969 | for (const attrName in rowAttributes) {
970 | tr.setAttribute(attrName, rowAttributes[attrName])
971 | }
972 |
973 | this.getCells().forEach(function (cell, idx) {
974 | var td = document.createElement('td');
975 | td.innerHTML = cell.getInnerHTML();
976 | if (columnRenderers.hasOwnProperty(idx)) {
977 | td.innerHTML = columnRenderers[idx].call(that, cell.getElement(), idx);
978 | }
979 | if (cell.classes.length > 0) {
980 | td.className = cell.classes.join(" ");
981 | }
982 | for (let attr in cell.attributes) {
983 | td.setAttribute(attr, cell.attributes[attr]);
984 | }
985 | tr.appendChild(td);
986 | });
987 | return tr;
988 |
989 | }
990 |
991 | setCellClass(cell, className) {
992 | this.cells[cell].addClass(className);
993 | }
994 | }
995 |
996 | class JSTableCell {
997 |
998 | constructor(element) {
999 | this.textContent = element.textContent;
1000 | this.innerHTML = element.innerHTML;
1001 | this.className = "";
1002 | this.element = element;
1003 |
1004 | this.hasSortable = element.hasAttribute("data-sortable");
1005 | this.isSortable = this.hasSortable ? element.getAttribute("data-sortable") === "true" : null;
1006 |
1007 | this.hasSort = element.hasAttribute("data-sort");
1008 | this.sortDirection = element.getAttribute("data-sort");
1009 |
1010 | this.classes = [];
1011 |
1012 | var that = this;
1013 | // parse attributes
1014 | this.attributes = {};
1015 | [...element.attributes].forEach(function (attr) {
1016 | that.attributes[attr.name] = attr.value;
1017 | });
1018 | }
1019 |
1020 | getElement() {
1021 | return this.element;
1022 | }
1023 |
1024 | // for sorting
1025 | getTextContent() {
1026 | return this.textContent;
1027 | }
1028 |
1029 | // for rendering
1030 | getInnerHTML() {
1031 | return this.innerHTML;
1032 | }
1033 |
1034 | setClass(className) {
1035 | this.className = className;
1036 | }
1037 |
1038 | setSortable(value) {
1039 | this.isSortable = value;
1040 | //tableHeaderCell.setAttribute("data-sortable", sortable);
1041 | }
1042 |
1043 | addClass(value) {
1044 | this.classes.push(value);
1045 | }
1046 |
1047 | removeClass(value) {
1048 | if (this.classes.indexOf(value) >= 0) {
1049 | this.classes.splice(this.classes.indexOf(value), 1);
1050 | }
1051 | }
1052 |
1053 | addAttribute(key, value) {
1054 | this.attributes[key] = value;
1055 | }
1056 | }
1057 |
1058 | class JSTablePager {
1059 |
1060 | constructor(instance) {
1061 | this.instance = instance;
1062 | }
1063 |
1064 | getPages() {
1065 | let pages = Math.ceil(this.instance.getDataCount() / this.instance.config.perPage);
1066 | return pages === 0 ? 1 : pages;
1067 | }
1068 |
1069 | render() {
1070 | var options = this.instance.config;
1071 | let pages = this.getPages();
1072 |
1073 | let ul = document.createElement("ul");
1074 | if (pages > 1) {
1075 |
1076 | let prev = this.instance.currentPage === 1 ? 1 : this.instance.currentPage - 1,
1077 | next = this.instance.currentPage === pages ? pages : this.instance.currentPage + 1;
1078 |
1079 | // first button
1080 | if (options.firstLast) {
1081 | ul.appendChild(this.createItem("pager", 1, options.firstText));
1082 | }
1083 |
1084 | // prev button
1085 | if (options.nextPrev) {
1086 | ul.appendChild(this.createItem("pager", prev, options.prevText));
1087 | }
1088 |
1089 | var pager = this.truncate();
1090 | // append the links
1091 | pager.forEach(function (btn) {
1092 | ul.appendChild(btn);
1093 | });
1094 |
1095 | // next button
1096 | if (options.nextPrev) {
1097 | ul.appendChild(this.createItem("pager", next, options.nextText));
1098 | }
1099 |
1100 | // first button
1101 | if (options.firstLast) {
1102 | ul.appendChild(this.createItem("pager", pages, options.lastText));
1103 | }
1104 |
1105 |
1106 | }
1107 | return ul;
1108 |
1109 | }
1110 |
1111 | createItem(className, pageNum, content, ellipsis) {
1112 | let item = document.createElement("li");
1113 | item.className = className;
1114 | item.innerHTML = !ellipsis ? '' + content + " " : '' + content + " ";
1115 | return item;
1116 | }
1117 |
1118 | isValidPage(page) {
1119 | return page > 0 && page <= this.getPages();
1120 | }
1121 |
1122 | truncate() {
1123 | var that = this,
1124 | options = that.instance.config,
1125 | delta = options.pagerDelta * 2,
1126 | currentPage = that.instance.currentPage,
1127 | left = currentPage - options.pagerDelta,
1128 | right = currentPage + options.pagerDelta,
1129 | totalPages = this.getPages(),
1130 | range = [],
1131 | pager = [],
1132 | lastIndex;
1133 |
1134 | if (!this.instance.config.truncatePager) {
1135 | for (let i = 1; i <= this.getPages(); i++) {
1136 | pager.push(this.createItem(i === currentPage ? "active" : "", i, i));
1137 | }
1138 |
1139 | } else {
1140 | if (currentPage < 4 - options.pagerDelta + delta) {
1141 | right = 3 + delta;
1142 | } else if (currentPage > this.getPages() - (3 - options.pagerDelta + delta)) {
1143 | left = this.getPages() - (2 + delta);
1144 | }
1145 |
1146 | // Get the links that will be visible
1147 | for (var i = 1; i <= totalPages; i++) {
1148 | if (i === 1 || i === totalPages || (i >= left && i <= right)) {
1149 | range.push(i);
1150 | }
1151 | }
1152 |
1153 | range.forEach(function (index) {
1154 | if (lastIndex) {
1155 | if (index - lastIndex == 2) {
1156 | pager.push(that.createItem("", lastIndex + 1, lastIndex + 1));
1157 | } else if (index - lastIndex != 1) {
1158 | // Create ellipsis node
1159 | pager.push(that.createItem(options.classes.ellipsis, 0, options.ellipsisText, true));
1160 | }
1161 | }
1162 |
1163 | pager.push(that.createItem(index == currentPage ? "active" : "", index, index));
1164 | lastIndex = index;
1165 | });
1166 | }
1167 |
1168 | return pager;
1169 | }
1170 |
1171 | }
1172 |
1173 | window.JSTable = JSTable;
1174 |
--------------------------------------------------------------------------------
/src/jstable.scss:
--------------------------------------------------------------------------------
1 | /*!
2 | * JSTable v1.6.5
3 | */
4 |
5 | .dt-container{
6 | position:relative;
7 | display: block;
8 | width: 100%;
9 | overflow-x: auto;
10 | -webkit-overflow-scrolling: touch;
11 | -ms-overflow-style: -ms-autohiding-scrollbar;
12 | overflow-y: hidden;
13 |
14 | .dt-message {
15 | text-align: center;
16 | }
17 |
18 | .dt-loading{
19 | position: absolute;
20 | top: 50%;
21 | left: 50%;
22 | width: 100%;
23 | margin-left: -50%;
24 | margin-top: -20px;
25 | height: 40px;
26 | text-align: center;
27 | background-color: white;
28 | display: flex;
29 | justify-content: center;
30 | align-items: center;
31 | background: linear-gradient(to right,rgba(255,255,255,0) 0,rgba(255,255,255,0.9) 25%,rgba(255,255,255,0.9) 75%,rgba(255,255,255,0) 100%);
32 | }
33 | }
34 |
35 | .dt-top,
36 | .dt-bottom {
37 | padding: 8px 10px;
38 | display:flex;
39 | justify-content:space-between;
40 |
41 | .dt-selector {
42 | padding: 6px;
43 | }
44 | .dt-info {
45 | margin: 7px 0;
46 | }
47 |
48 | }
49 |
50 | .dt-input {
51 | padding: 6px 12px;
52 | }
53 |
54 |
55 | /* PAGER */
56 | .dt-pagination {
57 |
58 | ul {
59 | margin: 0;
60 | padding-left: 0;
61 | li {
62 | list-style: none;
63 | float: left;
64 | }
65 | }
66 | a, span{
67 | border: 1px solid transparent;
68 | float: left;
69 | margin-left: 2px;
70 | padding: 6px 12px;
71 | position: relative;
72 | text-decoration: none;
73 | color: inherit;
74 | }
75 |
76 | a:hover {
77 | background-color: #d9d9d9;
78 | }
79 | .active a{
80 | &, &:focus, &:hover{
81 | background-color: #d9d9d9;
82 | cursor: default;
83 | }
84 | }
85 |
86 | .dt-ellipsis span{
87 | cursor: not-allowed;
88 | }
89 | .disabled a{
90 | &, &:focus, &:hover{
91 | cursor: not-allowed;
92 | opacity: 0.4;
93 | }
94 | }
95 |
96 | .pager a {
97 | font-weight: bold;
98 | }
99 | }
100 |
101 |
102 | .dt-table {
103 | max-width: 100%;
104 | width: 100%;
105 | border-spacing: 0;
106 |
107 | & > tbody, > tfoot, > thead{
108 | & > tr{
109 | & > td, & > th{
110 | vertical-align: top;
111 | padding: 8px 10px;
112 | white-space: nowrap;
113 | }
114 | }
115 | }
116 |
117 | & > thead > tr{
118 | & > th, & > td{
119 | vertical-align: bottom;
120 | text-align: left;
121 | border-bottom: 1px solid #d9d9d9;
122 | }
123 | }
124 |
125 | & > tfoot > tr{
126 | & > th, & > td{
127 | vertical-align: bottom;
128 | text-align: left;
129 | border-top: 1px solid #d9d9d9;
130 | }
131 | }
132 |
133 | th {
134 | vertical-align: bottom;
135 | text-align: left;
136 |
137 | &.dt-sorter {
138 | position: relative;
139 | cursor: pointer;
140 | padding-right:20px;
141 |
142 | &::before,
143 | &::after {
144 | content: "";
145 | height: 0;
146 | width: 0;
147 | position: absolute;
148 | right: 7px;
149 | border-left: 4px solid transparent;
150 | border-right: 4px solid transparent;
151 | opacity: 0.2;
152 | }
153 |
154 | &::before {
155 | border-top: 4px solid #000;
156 | top: 18px;
157 | }
158 |
159 | &::after {
160 | border-bottom: 4px solid #000;
161 | border-top: 4px solid transparent;
162 | bottom: 22px;
163 | }
164 |
165 | &.asc::after,
166 | &.desc::before {
167 | opacity: 0.6;
168 | }
169 |
170 | }
171 | }
172 |
173 | }
174 |
175 | .hidden{
176 | display:none!important;
177 | opacity:0!important;
178 | }
179 |
--------------------------------------------------------------------------------