311 lines
12 KiB
JavaScript

var timeouts = {};
async function initTable(containingSelector, tableId, loadDataFunc, options=null) {
let pageSizeId = tableId + "PageSize";
searchId = tableId + "SearchBar";
sortDropdownId = tableId + "SortDropdown";
filterDropdownId = tableId + "FilterDropdown";
createSearchRow(containingSelector, searchId, sortDropdownId, filterDropdownId, options);
createTable(containingSelector, tableId);
createTableControls(containingSelector, pageSizeId);
await bindSearchBar(searchId, loadDataFunc);
await bindTablePagination(`${containingSelector} .table-pagination`, loadDataFunc);
await bindTablePaginationResizer(`${containingSelector} .table-page-sizer`, loadDataFunc);
}
function createSearchRow(containingSelector, searchId, sortDropdownId, filterDropdownId, options) {
$(containingSelector).append(`
<div class="row my-3 px-3 table-search-row">
<div class="col-lg-4">
<div class="input-group mb-lg-0 mb-3 rounded-1">
<span class="input-group-text">
<i class="bi bi-search"></i>
</span>
<input type="search" id="${searchId}" name="${searchId}" class="form-control table-searchbar" placeholder="Search">
</div>
</div>
</div>
`);
if (!options || !options.sort)
return;
$(`${containingSelector} .table-search-row`).append(`
<div class="col-lg-8 text-end">
<div class="d-inline-block me-3">
<div id=${sortDropdownId} class="dropdown table-sort-dropdown">
<button type="button" class="btn btn-secondary rounded-1" data-bs-toggle="dropdown">
<i class="bi bi-sort-alpha-up"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li><h6 class="dropdown-header">Sort By</h6></li>
</ul>
</div>
</div>
<div class="d-inline-block">
<div id=${filterDropdownId} class="dropdown table-filter-dropdown">
<button type="button" class="btn btn-secondary rounded-1" data-bs-toggle="dropdown">
<i class="bi bi-funnel"></i>
</button>
<ul class="dropdown-menu dropdown-menu-end">
<li><h6 class="dropdown-header">Filter By</h6></li>
</ul>
</div>
</div>
</div>
`);
options.sort.forEach(sortKey => {
let label = sortKey.replace(/_/g, " ")
$(`#${sortDropdownId} .dropdown-menu`).append(`
<li>
<button type="button" class="dropdown-item text-capitalize d-flex justify-content-between" data-sortkey="${sortKey}">
<span class="me-3">${label}</span><i class="bi bi-chevron-up"></i>
</button>
</li>
<li>
<button type="button" class="dropdown-item text-capitalize d-flex justify-content-between" data-sortKey="-${sortKey}">
<span class="me-3">${label}</span><i class="bi bi-chevron-down"></i>
</button>
</li>
`);
});
// <div class="d-block text-end">
// <div id=${sortDropdownId} class="dropdown d-inline">
// <button type="button" class="btn btn-secondary rounded-1 me-3" data-bs-toggle="dropdown">
// <i class="bi bi-sort-alpha-up"></i>
// </button>
// <div class="dropdown-menu dropdown-menu-end"></div>
// </div>
// <div id=${filterDropdownId} class="dropdown d-inline">
// <button type="button" class="btn btn-secondary rounded-1" data-bs-toggle="dropdown">
// <i class="bi bi-funnel"></i>
// </button>
// <div class="dropdown-menu dropdown-menu-end"></div>
// </div>
// </div>
}
async function bindSearchBar(searchBarSelector, loadDataFunc) {
searchBar = $("#" + searchBarSelector)
searchBar.on("input", async function() {
clearTimeout(timeouts[searchBarSelector]);
searchString = $(this).val();
timeouts[searchBarSelector] = setTimeout(async function() {
await loadDataFunc(getCurrentlyActiveServer().guild_id, 1, null, searchString);
}, 300);
});
}
function createTable(containingSelector, tableId) {
$(containingSelector).append(`
<div class="table-responsive my-3 px-3">
<table id="${tableId}" class="table table-hover align-middle"></table>
</div>
`);
}
function createTableControls(containingSelector, pageSizeId) {
$(containingSelector).append(`
<div class="table-controls row mb-3 px-3">
<div class="col-lg-2">
<div class="table-page-info d-flex justify-content-start align-items-center mx-auto">
<span class="pageinfo-total"></span>&nbsp;Results
</div>
</div>
<div class="col-lg-8">
<nav class="table-pagination d-flex justify-content-center">
<ul class="pagination mb-0">
<li class="page-item">
<button type="button" class="page-link page-prev rounded-start-1">
<i class="bi bi-chevron-left"></i>
</button>
</li>
<li class="page-item">
<button type="button" class="page-link page-next rounded-end-1">
<i class="bi bi-chevron-right"></i>
</button>
</li>
</ul>
</nav>
</div>
<div class="col-lg-2">
<div class="d-flex justify-content-end">
<label for="${pageSizeId}" class="form-label align-self-center mb-0 me-2">Per Page</label>
<select name="${pageSizeId}" id="${pageSizeId}" class="select-2 table-page-sizer">
<option value="10" selected>10&emsp;</option>
<option value="15">15&emsp;</option>
<option value="20">20&emsp;</option>
<option value="25">25&emsp;</option>
</select>
</div>
</div>
</div>
`);
$("#" + pageSizeId).select2({
theme: "bootstrap",
minimumResultsForSearch: 10,
});
}
// Updates the pagination text for a given pageInfoId
function updateTablePaginationInfo(pageInfoId, showing, total) {
$(`${pageInfoId} .pageinfo-showing`).text(showing);
$(`${pageInfoId} .pageinfo-total`).text(total);
}
// Updates the pagination buttons for a given pageControlsId
function updateTablePagination(pageControlsId, currentPage, pageSize, totalItems, nextExists, prevExists) {
$(pageControlsId).attr("data-page", currentPage);
// Remove existing page specific buttons
$(`${pageControlsId} .page-pick`).remove();
// Determine states of 'previous page' 'next page' buttons
$(`${pageControlsId} .page-prev`).toggleClass("disabled", !prevExists).attr("tabindex", prevExists ? "" : "-1");
$(`${pageControlsId} .page-next`).toggleClass("disabled", !nextExists).attr("tabindex", nextExists ? "" : "-1");
// Calculate amount of pages to account for
const pages = Math.max(Math.ceil(totalItems / pageSize), 1);
const maxVisiblePages = 10;
let startPage, endPage;
if (pages <= maxVisiblePages) {
// If total pages are less than or equal to max visible pages, show all
startPage = 1;
endPage = pages;
} else {
// Determine the start and end pages
const halfVisible = Math.floor(maxVisiblePages / 2);
if (currentPage <= halfVisible) {
startPage = 1;
endPage = maxVisiblePages;
} else if (currentPage + halfVisible >= pages) {
startPage = pages - maxVisiblePages + 1;
endPage = pages;
} else {
startPage = currentPage - halfVisible;
endPage = currentPage + halfVisible;
}
}
if (startPage > 1) {
addPageButton(pageControlsId, 1);
if (startPage > 2) {
addEllipsis(pageControlsId);
}
}
for (let i = startPage; i <= endPage; i++) {
addPageButton(pageControlsId, i, currentPage);
}
if (endPage < pages) {
if (endPage < pages - 1) {
addEllipsis(pageControlsId);
}
addPageButton(pageControlsId, pages);
}
}
function addPageButton(pageControlsId, pageNumber, currentPage) {
let pageItem = $("<li>").addClass("page-item");
let pageLink = $("<button>")
.attr("type", "button")
.attr("data-page", pageNumber)
.addClass("page-link page-pick")
.text(pageNumber);
if (pageNumber === currentPage) {
pageLink.addClass("disabled").attr("tabindex", -1);
}
pageItem.append(pageLink);
$(`${pageControlsId} .pagination .page-next`).parent().before(pageItem);
}
function addEllipsis(pageControlsId) {
let ellipsisItem = $("<li>").addClass("page-item disabled");
let ellipsisLink = $("<span>").addClass("page-link page-pick").text("...");
ellipsisItem.append(ellipsisLink);
$(`${pageControlsId} .pagination .page-next`).parent().before(ellipsisItem);
}
// Bind the table pagination buttons to control the table pagination
async function bindTablePagination(pageControlsId, dataLoadFunc) {
$(`${pageControlsId} .pagination`).on("click", ".page-link", async function() {
let wantedPage;
let currentPage = parseInt($(pageControlsId).attr("data-page"));
if ($(this).hasClass("page-prev"))
wantedPage = currentPage - 1;
else if ($(this).hasClass("page-next"))
wantedPage = currentPage + 1;
else
wantedPage = $(this).attr("data-page");
await dataLoadFunc(getCurrentlyActiveServer().guild_id, wantedPage)
});
}
// Bind the table pagination page resizer control
async function bindTablePaginationResizer(resizerControlId, dataLoadFunc) {
$(resizerControlId).on("change", async function() {
const page = 1;
const pageSize = $(this).val();
await dataLoadFunc(getCurrentlyActiveServer().guild_id, page, pageSize);
});
}
function bindTableCheckboxes(tableSelector, tableObject, deleteButtonSelector) {
// Select a row via checkbox
$(tableSelector).on("change", "tbody tr .table-select-row", function() {
var selected = $(this).prop("checked");
rowIndex = $(this).closest("tr").index();
row = tableObject.row(rowIndex);
if (selected) row.select();
else row.deselect();
determineSelectAllState(tableSelector, tableObject, deleteButtonSelector);
});
// Select all rows checkbox
$(tableSelector).on("change", "thead .table-select-all", function() {
var selected = $(this).prop("checked");
$(`${tableSelector} tbody tr`).each(function(rowIndex) {
var row = tableObject.row(rowIndex);
if (selected) row.select();
else row.deselect();
$(this).find('.table-select-row').prop("checked", selected);
});
determineSelectAllState(tableSelector, tableObject, deleteButtonSelector);
});
}
function determineSelectAllState(tableSelector, tableObject, deleteButtonSelector) {
var selectedRowsCount = tableObject.rows(".selected").data().toArray().length;
allRowsCount = tableObject.rows().data().toArray().length;
selectAllCheckbox = $(`${tableSelector} thead .table-select-all`);
checked = selectedRowsCount === allRowsCount;
indeterminate = !checked && selectedRowsCount > 0;
selectAllCheckbox.prop("checked", checked);
selectAllCheckbox.prop("indeterminate", indeterminate);
$(deleteButtonSelector).prop("disabled", !(checked || indeterminate) || !(allRowsCount > 0));
}