var timeouts = {}; tableFilters = {}; tableSorts = {}; $(document).on("selectedServerChange", async function() { // Clear filters and sorts when changing server tableFilters = {}; tableSorts = {}; }) function setTableFilter(tableId, key, value) { if (!tableFilters[tableId]) tableFilters[tableId] = {} tableFilters[tableId][key] = value; } function setTableSorts(tableId, value) { tableSorts[tableId] = value; } function ensureTablePagination(tableId) { if (!tableFilters[tableId]) throw new Error(`${tableId} isn't a valid table ID`); if (!tableFilters[tableId]["page"]) setTableFilter(tableId, "page", "1"); if (!tableFilters[tableId]["page_size"]) setTableFilter(tableId, "page_size", $(`#${tableId}`).closest(".tab-pane").find(".table-page-sizer").val()); } async function initTable(containingSelector, tableId, loadDataFunc, newRowFunc, deleteSelectedFunc, options=null) { let pageSizeId = tableId + "PageSize"; searchId = tableId + "SearchBar"; sortDropdownId = tableId + "SortDropdown"; filterDropdownId = tableId + "FilterDropdown"; createSearchRow(containingSelector, searchId, sortDropdownId, filterDropdownId, options, newRowFunc, deleteSelectedFunc); createTable(containingSelector, tableId); createTableControls(containingSelector, pageSizeId); await bindSearchBar(tableId, searchId, loadDataFunc); await bindSortDropdown(tableId, sortDropdownId, loadDataFunc); await bindFilterDropdown(tableId, filterDropdownId, loadDataFunc); await bindTablePagination(tableId, `${containingSelector} .table-pagination`, loadDataFunc); await bindTablePaginationResizer(tableId, `${containingSelector} .table-page-sizer`, loadDataFunc); } function createSearchRow(containingSelector, searchId, sortDropdownId, filterDropdownId, options, newRowFunc, deleteSelectedFunc) { $(containingSelector).append(`
`); // If there is a function that allows the creation of new rows, create a button for it if (newRowFunc) { $(`${containingSelector} .table-search-row .table-search-buttons`).prepend(`
`) $(`${containingSelector} .table-search-row .table-search-buttons .table-new-btn`).on("click", async () => {await newRowFunc(-1)}); } // Show the sort dropdown if (options.sort && options.actions.GET) { $(`${containingSelector} .table-search-row .table-search-buttons`).append(`
`); populateSortDropdown(sortDropdownId, options.actions.GET, options.sort); } // Show the filters dropdown if (options.filter && options.actions.GET) { $(`${containingSelector} .table-search-row .table-search-buttons`).append(`
`); populateFilterDropdown(filterDropdownId, options.actions.GET, options.filter); } // If there is a function for deleting selected rows, create a button for it if (deleteSelectedFunc) { $(`${containingSelector} .table-search-row .table-search-buttons`).append(`
`) $(`${containingSelector} .table-search-row .table-search-buttons .table-del-btn`).on("click", async () => {await deleteSelectedFunc()}); } } function populateSortDropdown(sortDropdownId, options, sortKeys) { for (key in options) { if (!sortKeys.includes(key)) continue; let label = options[key].label; $elem = null; $elem = $(`
  • `); bindSortBoolean($elem); $(`#${sortDropdownId} .dropdown-menu`).append($elem); updateSortCheckboxState($elem); } } function populateFilterDropdown(filterDropdownId, options, filterKeys) { for (key in options) { if (!filterKeys.includes(key)) continue; let label = options[key].label; id = key + "FilterOption"; $elem = null; switch (options[key].type) { case ("boolean"): $elem = $(`
  • `); bindFilterBoolean($elem); $elem.find('input[type="checkbox"]').prop("checked", options[key].default ? true : false).trigger("change"); updateFilterCheckboxState($elem); break default: continue; } $(`#${filterDropdownId} .dropdown-menu`).append($elem); } // Reset the controls $(document).on("selectedServerChange", async function() { $(`#${filterDropdownId} .dropdown-menu input[type="checkbox"]`).each(function() { $(this).data("state", null); updateFilterCheckboxState($(this).parent()); }); }); } function bindSortBoolean($elem) { $elem.find("button").on("click", function() { // Reset sibling buttons $(this).parent().siblings("li").each(function() { $(this).find("button").data("state", null); updateSortCheckboxState($(this)); }); let state = $(this).data("state"); state = determineState(state); $(this).data("state", state); updateSortCheckboxState($elem) }); } function bindFilterBoolean($elem) { $elem.find('label').on("click", function() { let $input = $elem.find('input[type="checkbox"]'); let state = $input.data("state"); state = determineState(state); $input.data("state", state); updateFilterCheckboxState($elem); }); } function determineState(state) { switch (state) { case true: return false; case false: return null; case null: default: return true; } } function updateSortCheckboxState($elem) { let state = $elem.find("button").data("state"); let $icon = $elem.find(".bi"); $icon.removeClass(); switch (state) { case true: $icon.addClass("bi bi-sort-alpha-up"); break; case false: $icon.addClass("bi bi-sort-alpha-down-alt"); break; case null: default: $icon.addClass("bi bi-slash invisible"); } } function updateFilterCheckboxState($elem) { let state = $elem.find('input[type="checkbox"]').data("state"); let $icon = $elem.find(".bi"); switch (state) { case true: $elem.find('input[type="checkbox"]').prop("checked", true); $icon.removeClass().addClass("bi bi-check"); break; case false: $elem.find('input[type="checkbox"]').prop("checked", false); $icon.removeClass().addClass("bi bi-x"); break; case null: default: $elem.find('input[type="checkbox"]').prop("checked", false); $icon.removeClass().addClass("bi bi-dash invisible"); break; } } async function bindSearchBar(tableId, searchBarSelector, loadDataFunc) { searchBar = $("#" + searchBarSelector) searchBar.on("input", async function() { clearTimeout(timeouts[searchBarSelector]); var searchString = $(this).val(); setTableFilter(tableId, "search", searchString); timeouts[searchBarSelector] = setTimeout(async function() { await loadDataFunc(getCurrentlyActiveServer().guild_id); }, 300); }); } async function bindFilterDropdown(tableId, filterDropdownId, loadDataFunc) { $(`#${filterDropdownId} .dropdown-menu`).on("change", "input", async function() { setTableFilter(tableId, $(this).attr("data-key"), $(this).data("state")); setTableFilter(tableId, "page", "1"); await loadDataFunc(getCurrentlyActiveServer().guild_id); }); } async function bindSortDropdown(tableId, sortDropdownId, loadDataFunc) { $(`#${sortDropdownId} .dropdown-menu`).on("click", "button", async function() { let state = $(this).data("state"); sortKey = ""; switch(state) { case true: sortKey = $(this).attr("data-sortkey") break; case false: sortKey = "-" + $(this).attr("data-sortkey") break; case null: default: sortKey = "" } setTableSorts(tableId, sortKey); await loadDataFunc(getCurrentlyActiveServer().guild_id); }); } function createTable(containingSelector, tableId) { $(containingSelector).append(`
    `); } function createTableControls(containingSelector, pageSizeId) { $(containingSelector).append(`
    `); $("#" + pageSizeId).select2({ theme: "bootstrap", minimumResultsForSearch: 10, }); } function updateTableContainer(containerId, page, pageSize, itemsCount, totalItemsCount, nextExists, prevExists) { if (!page || !pageSize) throw new Error(`Missing page data '${containerId}': page=${page} pageSize=${pageSize}`); updateTablePagination( `#${containerId} .table-pagination`, page, pageSize, totalItemsCount, nextExists, prevExists ); updateTablePaginationInfo( `#${containerId} .table-page-info`, itemsCount, totalItemsCount ); } // Updates the pagination text for a given pageInfoId function updateTablePaginationInfo(pageInfoId, showing, total) { $(`${pageInfoId} .pageinfo-showing`).text(showing); $(`${pageInfoId} .pageinfo-total`).text(`${total} Result${total > 1 ? "s" : ""}`); } // 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; currentPage = parseInt(currentPage); pageSize = parseInt(pageSize); 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 = $("
  • ").addClass("page-item"); let pageLink = $("