UNPKG

tabulator-tables

Version:

Interactive table generation JavaScript library

891 lines (686 loc) 24.5 kB
import Module from '../../core/Module.js'; import defaultPageCounters from './defaults/pageCounters.js'; class Page extends Module{ constructor(table){ super(table); this.mode = "local"; this.progressiveLoad = false; this.element = null; this.pageCounterElement = null; this.pageCounter = null; this.size = 0; this.page = 1; this.count = 5; this.max = 1; this.remoteRowCountEstimate = null; this.displayIndex = 0; //index in display pipeline this.initialLoad = true; this.dataChanging = false; //flag to check if data is being changed by this module this.pageSizes = []; this.registerTableOption("pagination", false); //set pagination type this.registerTableOption("paginationMode", "local"); //local or remote pagination this.registerTableOption("paginationSize", false); //set number of rows to a page this.registerTableOption("paginationInitialPage", 1); //initial page to show on load this.registerTableOption("paginationCounter", false); // set pagination counter this.registerTableOption("paginationCounterElement", false); // set pagination counter this.registerTableOption("paginationButtonCount", 5); // set count of page button this.registerTableOption("paginationSizeSelector", false); //add pagination size selector element this.registerTableOption("paginationElement", false); //element to hold pagination numbers // this.registerTableOption("paginationDataSent", {}); //pagination data sent to the server // this.registerTableOption("paginationDataReceived", {}); //pagination data received from the server this.registerTableOption("paginationAddRow", "page"); //add rows on table or page this.registerTableOption("progressiveLoad", false); //progressive loading this.registerTableOption("progressiveLoadDelay", 0); //delay between requests this.registerTableOption("progressiveLoadScrollMargin", 0); //margin before scroll begins this.registerTableFunction("setMaxPage", this.setMaxPage.bind(this)); this.registerTableFunction("setPage", this.setPage.bind(this)); this.registerTableFunction("setPageToRow", this.userSetPageToRow.bind(this)); this.registerTableFunction("setPageSize", this.userSetPageSize.bind(this)); this.registerTableFunction("getPageSize", this.getPageSize.bind(this)); this.registerTableFunction("previousPage", this.previousPage.bind(this)); this.registerTableFunction("nextPage", this.nextPage.bind(this)); this.registerTableFunction("getPage", this.getPage.bind(this)); this.registerTableFunction("getPageMax", this.getPageMax.bind(this)); //register component functions this.registerComponentFunction("row", "pageTo", this.setPageToRow.bind(this)); } initialize(){ if(this.table.options.pagination){ this.subscribe("row-deleted", this.rowsUpdated.bind(this)); this.subscribe("row-added", this.rowsUpdated.bind(this)); this.subscribe("data-processed", this.initialLoadComplete.bind(this)); this.subscribe("table-built", this.calculatePageSizes.bind(this)); this.subscribe("footer-redraw", this.footerRedraw.bind(this)); if(this.table.options.paginationAddRow == "page"){ this.subscribe("row-adding-position", this.rowAddingPosition.bind(this)); } if(this.table.options.paginationMode === "remote"){ this.subscribe("data-params", this.remotePageParams.bind(this)); this.subscribe("data-loaded", this._parseRemoteData.bind(this)); } if(this.table.options.progressiveLoad){ console.error("Progressive Load Error - Pagination and progressive load cannot be used at the same time"); } this.registerDisplayHandler(this.restOnRenderBefore.bind(this), 40); this.registerDisplayHandler(this.getRows.bind(this), 50); this.createElements(); this.initializePageCounter(); this.initializePaginator(); }else if(this.table.options.progressiveLoad){ this.subscribe("data-params", this.remotePageParams.bind(this)); this.subscribe("data-loaded", this._parseRemoteData.bind(this)); this.subscribe("table-built", this.calculatePageSizes.bind(this)); this.subscribe("data-processed", this.initialLoadComplete.bind(this)); this.initializeProgressive(this.table.options.progressiveLoad); if(this.table.options.progressiveLoad === "scroll"){ this.subscribe("scroll-vertical", this.scrollVertical.bind(this)); } } } rowAddingPosition(row, top){ var rowManager = this.table.rowManager, displayRows = rowManager.getDisplayRows(), index; if(top){ if(displayRows.length){ index = displayRows[0]; }else{ if(rowManager.activeRows.length){ index = rowManager.activeRows[rowManager.activeRows.length-1]; top = false; } } }else{ if(displayRows.length){ index = displayRows[displayRows.length - 1]; top = displayRows.length < this.size ? false : true; } } return {index, top}; } calculatePageSizes(){ var testElRow, testElCell; if(this.table.options.paginationSize){ this.size = this.table.options.paginationSize; }else{ testElRow = document.createElement("div"); testElRow.classList.add("tabulator-row"); testElRow.style.visibility = "hidden"; testElCell = document.createElement("div"); testElCell.classList.add("tabulator-cell"); testElCell.innerHTML = "Page Row Test"; testElRow.appendChild(testElCell); this.table.rowManager.getTableElement().appendChild(testElRow); this.size = Math.floor(this.table.rowManager.getElement().clientHeight / testElRow.offsetHeight); this.table.rowManager.getTableElement().removeChild(testElRow); } this.dispatchExternal("pageSizeChanged", this.size); this.generatePageSizeSelectList(); } initialLoadComplete(){ this.initialLoad = false; } remotePageParams(data, config, silent, params){ if(!this.initialLoad){ if((this.progressiveLoad && !silent) || (!this.progressiveLoad && !this.dataChanging)){ this.reset(true); } } //configure request params params.page = this.page; //set page size if defined if(this.size){ params.size = this.size; } return params; } /////////////////////////////////// ///////// Table Functions ///////// /////////////////////////////////// userSetPageToRow(row){ if(this.table.options.pagination){ row = this.rowManager.findRow(row); if(row){ return this.setPageToRow(row); } } return Promise.reject(); } userSetPageSize(size){ if(this.table.options.pagination){ this.setPageSize(size); return this.setPage(1); }else{ return false; } } /////////////////////////////////// ///////// Internal Logic ////////// /////////////////////////////////// scrollVertical(top, dir){ var element, diff, margin; if(!dir && !this.table.dataLoader.loading){ element = this.table.rowManager.getElement(); diff = element.scrollHeight - element.clientHeight - top; margin = this.table.options.progressiveLoadScrollMargin || (element.clientHeight * 2); if(diff < margin){ this.nextPage() .catch(() => {}); //consume the exception thrown when on the last page } } } restOnRenderBefore(rows, renderInPosition){ if(!renderInPosition){ if(this.mode === "local"){ this.reset(); } } return rows; } rowsUpdated(){ this.refreshData(true, "all"); } createElements(){ var button; this.element = document.createElement("span"); this.element.classList.add("tabulator-paginator"); this.pagesElement = document.createElement("span"); this.pagesElement.classList.add("tabulator-pages"); button = document.createElement("button"); button.classList.add("tabulator-page"); button.setAttribute("type", "button"); button.setAttribute("role", "button"); button.setAttribute("aria-label", ""); button.setAttribute("title", ""); this.firstBut = button.cloneNode(true); this.firstBut.setAttribute("data-page", "first"); this.prevBut = button.cloneNode(true); this.prevBut.setAttribute("data-page", "prev"); this.nextBut = button.cloneNode(true); this.nextBut.setAttribute("data-page", "next"); this.lastBut = button.cloneNode(true); this.lastBut.setAttribute("data-page", "last"); if(this.table.options.paginationSizeSelector){ this.pageSizeSelect = document.createElement("select"); this.pageSizeSelect.classList.add("tabulator-page-size"); } } generatePageSizeSelectList(){ var pageSizes = []; if(this.pageSizeSelect){ if(Array.isArray(this.table.options.paginationSizeSelector)){ pageSizes = this.table.options.paginationSizeSelector; this.pageSizes = pageSizes; if(this.pageSizes.indexOf(this.size) == -1){ pageSizes.unshift(this.size); } }else{ if(this.pageSizes.indexOf(this.size) == -1){ pageSizes = []; for (let i = 1; i < 5; i++){ pageSizes.push(this.size * i); } this.pageSizes = pageSizes; }else{ pageSizes = this.pageSizes; } } while(this.pageSizeSelect.firstChild) this.pageSizeSelect.removeChild(this.pageSizeSelect.firstChild); pageSizes.forEach((item) => { var itemEl = document.createElement("option"); itemEl.value = item; if(item === true){ this.langBind("pagination|all", function(value){ itemEl.innerHTML = value; }); }else{ itemEl.innerHTML = item; } this.pageSizeSelect.appendChild(itemEl); }); this.pageSizeSelect.value = this.size; } } initializePageCounter(){ var counter = this.table.options.paginationCounter, pageCounter = null; if(counter){ if(typeof counter === "function"){ pageCounter = counter; }else{ pageCounter = Page.pageCounters[counter]; } if(pageCounter){ this.pageCounter = pageCounter; this.pageCounterElement = document.createElement("span"); this.pageCounterElement.classList.add("tabulator-page-counter"); }else{ console.warn("Pagination Error - No such page counter found: ", counter); } } } //setup pagination initializePaginator(hidden){ var pageSelectLabel, paginationCounterHolder; if(!hidden){ //build pagination element //bind localizations this.langBind("pagination|first", (value) => { this.firstBut.innerHTML = value; }); this.langBind("pagination|first_title", (value) => { this.firstBut.setAttribute("aria-label", value); this.firstBut.setAttribute("title", value); }); this.langBind("pagination|prev", (value) => { this.prevBut.innerHTML = value; }); this.langBind("pagination|prev_title", (value) => { this.prevBut.setAttribute("aria-label", value); this.prevBut.setAttribute("title", value); }); this.langBind("pagination|next", (value) => { this.nextBut.innerHTML = value; }); this.langBind("pagination|next_title", (value) => { this.nextBut.setAttribute("aria-label", value); this.nextBut.setAttribute("title", value); }); this.langBind("pagination|last", (value) => { this.lastBut.innerHTML = value; }); this.langBind("pagination|last_title", (value) => { this.lastBut.setAttribute("aria-label", value); this.lastBut.setAttribute("title", value); }); //click bindings this.firstBut.addEventListener("click", () => { this.setPage(1); }); this.prevBut.addEventListener("click", () => { this.previousPage(); }); this.nextBut.addEventListener("click", () => { this.nextPage(); }); this.lastBut.addEventListener("click", () => { this.setPage(this.max); }); if(this.table.options.paginationElement){ this.element = this.table.options.paginationElement; } if(this.pageSizeSelect){ pageSelectLabel = document.createElement("label"); this.langBind("pagination|page_size", (value) => { this.pageSizeSelect.setAttribute("aria-label", value); this.pageSizeSelect.setAttribute("title", value); pageSelectLabel.innerHTML = value; }); this.element.appendChild(pageSelectLabel); this.element.appendChild(this.pageSizeSelect); this.pageSizeSelect.addEventListener("change", (e) => { this.setPageSize(this.pageSizeSelect.value == "true" ? true : this.pageSizeSelect.value); this.setPage(1); }); } //append to DOM this.element.appendChild(this.firstBut); this.element.appendChild(this.prevBut); this.element.appendChild(this.pagesElement); this.element.appendChild(this.nextBut); this.element.appendChild(this.lastBut); if(!this.table.options.paginationElement){ if(this.table.options.paginationCounter){ paginationCounterHolder; if(this.table.options.paginationCounterElement){ if(this.table.options.paginationCounterElement instanceof HTMLElement){ this.table.options.paginationCounterElement.appendChild(this.pageCounterElement); }else if(typeof this.table.options.paginationCounterElement === "string"){ paginationCounterHolder = document.querySelector(this.table.options.paginationCounterElement); if(paginationCounterHolder){ paginationCounterHolder.appendChild(this.pageCounterElement); }else{ console.warn("Pagination Error - Unable to find element matching paginationCounterElement selector:", this.table.options.paginationCounterElement); } } }else{ this.footerAppend(this.pageCounterElement); } } this.footerAppend(this.element); } this.page = this.table.options.paginationInitialPage; this.count = this.table.options.paginationButtonCount; } //set default values this.mode = this.table.options.paginationMode; } initializeProgressive(mode){ this.initializePaginator(true); this.mode = "progressive_" + mode; this.progressiveLoad = true; } trackChanges(){ this.dispatch("page-changed"); } setDisplayIndex(index){ this.displayIndex = index; } getDisplayIndex(){ return this.displayIndex; } //calculate maximum page from number of rows setMaxRows(rowCount){ if(!rowCount){ this.max = 1; }else{ this.max = this.size === true ? 1 : Math.ceil(rowCount/this.size); } if(this.page > this.max){ this.page = this.max; } } //reset to first page without triggering action reset(force){ if(!this.initialLoad){ if(this.mode == "local" || force){ this.page = 1; } } } //set the maximum page setMaxPage(max){ max = parseInt(max); this.max = max || 1; if(this.page > this.max){ this.page = this.max; this.trigger(); } } //set current page number setPage(page){ switch(page){ case "first": return this.setPage(1); case "prev": return this.previousPage(); case "next": return this.nextPage(); case "last": return this.setPage(this.max); } page = parseInt(page); if((page > 0 && page <= this.max) || this.mode !== "local"){ this.page = page; this.trackChanges(); return this.trigger(); }else{ console.warn("Pagination Error - Requested page is out of range of 1 - " + this.max + ":", page); return Promise.reject(); } } setPageToRow(row){ var rows = this.table.rowManager.getDisplayRows(this.displayIndex - 1); var index = rows.indexOf(row); if(index > -1){ var page = this.size === true ? 1 : Math.ceil((index + 1) / this.size); return this.setPage(page); }else{ console.warn("Pagination Error - Requested row is not visible"); return Promise.reject(); } } setPageSize(size){ if(size !== true){ size = parseInt(size); } if(size > 0){ this.size = size; this.dispatchExternal("pageSizeChanged", size); } if(this.pageSizeSelect){ // this.pageSizeSelect.value = size; this.generatePageSizeSelectList(); } this.trackChanges(); } _setPageCounter(totalRows, size, currentRow){ var content; if(this.pageCounter){ if(this.mode === "remote"){ size = this.size; currentRow = ((this.page - 1) * this.size) + 1; totalRows = this.remoteRowCountEstimate; } content = this.pageCounter.call(this, size, currentRow, this.page, totalRows, this.max); switch(typeof content){ case "object": if(content instanceof Node){ //clear previous cell contents while(this.pageCounterElement.firstChild) this.pageCounterElement.removeChild(this.pageCounterElement.firstChild); this.pageCounterElement.appendChild(content); }else{ this.pageCounterElement.innerHTML = ""; if(content != null){ console.warn("Page Counter Error - Page Counter has returned a type of object, the only valid page counter object return is an instance of Node, the page counter returned:", content); } } break; case "undefined": this.pageCounterElement.innerHTML = ""; break; default: this.pageCounterElement.innerHTML = content; } } } //setup the pagination buttons _setPageButtons(){ let leftSize = Math.floor((this.count-1) / 2); let rightSize = Math.ceil((this.count-1) / 2); let min = this.max - this.page + leftSize + 1 < this.count ? this.max-this.count+1: Math.max(this.page-leftSize,1); let max = this.page <= rightSize? Math.min(this.count, this.max) :Math.min(this.page+rightSize, this.max); while(this.pagesElement.firstChild) this.pagesElement.removeChild(this.pagesElement.firstChild); if(this.page == 1){ this.firstBut.disabled = true; this.prevBut.disabled = true; }else{ this.firstBut.disabled = false; this.prevBut.disabled = false; } if(this.page == this.max){ this.lastBut.disabled = true; this.nextBut.disabled = true; }else{ this.lastBut.disabled = false; this.nextBut.disabled = false; } for(let i = min; i <= max; i++){ if(i>0 && i <= this.max){ this.pagesElement.appendChild(this._generatePageButton(i)); } } this.footerRedraw(); } _generatePageButton(page){ var button = document.createElement("button"); button.classList.add("tabulator-page"); if(page == this.page){ button.classList.add("active"); } button.setAttribute("type", "button"); button.setAttribute("role", "button"); this.langBind("pagination|page_title", (value) => { button.setAttribute("aria-label", value + " " + page); button.setAttribute("title", value + " " + page); }); button.setAttribute("data-page", page); button.textContent = page; button.addEventListener("click", (e) => { this.setPage(page); }); return button; } //previous page previousPage(){ if(this.page > 1){ this.page--; this.trackChanges(); return this.trigger(); }else{ console.warn("Pagination Error - Previous page would be less than page 1:", 0); return Promise.reject(); } } //next page nextPage(){ if(this.page < this.max){ this.page++; this.trackChanges(); return this.trigger(); }else{ if(!this.progressiveLoad){ console.warn("Pagination Error - Next page would be greater than maximum page of " + this.max + ":", this.max + 1); } return Promise.reject(); } } //return current page number getPage(){ return this.page; } //return max page number getPageMax(){ return this.max; } getPageSize(size){ return this.size; } getMode(){ return this.mode; } //return appropriate rows for current page getRows(data){ var actualRowPageSize = 0, output, start, end, actualStartRow; var actualRows = data.filter((row) => { return row.type === "row"; }); if(this.mode == "local"){ output = []; this.setMaxRows(data.length); if(this.size === true){ start = 0; end = data.length; }else{ start = this.size * (this.page - 1); end = start + parseInt(this.size); } this._setPageButtons(); for(let i = start; i < end; i++){ let row = data[i]; if(row){ output.push(row); if(row.type === "row"){ if(!actualStartRow){ actualStartRow = row; } actualRowPageSize++; } } } this._setPageCounter(actualRows.length, actualRowPageSize, actualStartRow ? (actualRows.indexOf(actualStartRow) + 1) : 0); return output; }else{ this._setPageButtons(); this._setPageCounter(actualRows.length); return data.slice(0); } } trigger(){ var left; switch(this.mode){ case "local": left = this.table.rowManager.scrollLeft; this.refreshData(); this.table.rowManager.scrollHorizontal(left); this.dispatchExternal("pageLoaded", this.getPage()); return Promise.resolve(); case "remote": this.dataChanging = true; return this.reloadData(null) .finally(() => { this.dataChanging = false; }); case "progressive_load": case "progressive_scroll": return this.reloadData(null, true); default: console.warn("Pagination Error - no such pagination mode:", this.mode); return Promise.reject(); } } _parseRemoteData(data){ var margin; if(typeof data.last_page === "undefined"){ console.warn("Remote Pagination Error - Server response missing '" + (this.options("dataReceiveParams").last_page || "last_page") + "' property"); } if(data.data){ this.max = parseInt(data.last_page) || 1; this.remoteRowCountEstimate = typeof data.last_row !== "undefined" ? data.last_row : (data.last_page * this.size - (this.page == data.last_page ? (this.size - data.data.length) : 0)); if(this.progressiveLoad){ switch(this.mode){ case "progressive_load": if(this.page == 1){ this.table.rowManager.setData(data.data, false, this.page == 1); }else{ this.table.rowManager.addRows(data.data); } if(this.page < this.max){ setTimeout(() => { this.nextPage(); }, this.table.options.progressiveLoadDelay); } break; case "progressive_scroll": data = this.page === 1 ? data.data : this.table.rowManager.getData().concat(data.data); this.table.rowManager.setData(data, this.page !== 1, this.page == 1); margin = this.table.options.progressiveLoadScrollMargin || (this.table.rowManager.element.clientHeight * 2); if(this.table.rowManager.element.scrollHeight <= (this.table.rowManager.element.clientHeight + margin)){ if(this.page < this.max){ setTimeout(() => { this.nextPage(); }); } } break; } return false; }else{ // left = this.table.rowManager.scrollLeft; this.dispatchExternal("pageLoaded", this.getPage()); // this.table.rowManager.scrollHorizontal(left); // this.table.columnManager.scrollHorizontal(left); } }else{ console.warn("Remote Pagination Error - Server response missing '" + (this.options("dataReceiveParams").data || "data") + "' property"); } return data.data; } //handle the footer element being redrawn footerRedraw(){ var footer = this.table.footerManager.containerElement; if((Math.ceil(footer.clientWidth) - footer.scrollWidth) < 0){ this.pagesElement.style.display = 'none'; }else{ this.pagesElement.style.display = ''; if((Math.ceil(footer.clientWidth) - footer.scrollWidth) < 0){ this.pagesElement.style.display = 'none'; } } } } Page.moduleName = "page"; //load defaults Page.pageCounters = defaultPageCounters; export default Page;