Source: pagination.js

"use strict";
// SPDX-License-Identifier: GPL-3.0-or-later
// myMPD (c) 2018-2024 Juergen Mang <mail@jcgames.de>
// https://github.com/jcorporation/mympd

/** @module pagination_js */

/**
 * Go's to the previous or next page
 * @param {string} direction on of next, prev
 * @param {number} limit maximum entries to display
 * @returns {void}
 */
function gotoPageDir(direction, limit) {
    let offset = app.current.offset;
    switch(direction) {
        case 'next':
            offset = offset + app.current.limit;
            break;
        case 'prev':
            offset = offset - app.current.limit;
            if (offset < 0) {
                offset = 0;
            }
            break;
        default:
            logError('Invalid goto page direction: ' + direction);
    }
    gotoPage(offset, limit);
}

/**
 * Go's to the page defined by offset
 * @param {number} offset page offset
 * @param {number} limit maximum entries to display
 * @returns {void}
 */
function gotoPage(offset, limit) {
    app.current.offset = offset;
    if (limit !== undefined) {
        if (limit === 0) {
            limit = maxElementsPerPage;
        }
        app.current.limit = limit;
        if (app.current.offset % app.current.limit > 0) {
            app.current.offset = Math.floor(app.current.offset / app.current.limit);
        }
    }
    appGoto(app.current.card, app.current.tab, app.current.view,
        app.current.offset, app.current.limit, app.current.filter, app.current.sort, app.current.tag, app.current.search, 0);
}

/**
 * Pagination function
 * @param {number} total number of total entries
 * @param {number} returned number of returned entries
 * @returns {void}
 */
function setPagination(total, returned) {
    const curPaginationTop = elGetById(app.id + 'PaginationTop');
    if (curPaginationTop === null) {
        return;
    }

    if (app.current.limit === 0) {
        app.current.limit = maxElementsPerPage;
    }

    let totalPages = total < app.current.limit
        ? total === -1
            ? -1
            : 1
        : Math.ceil(total / app.current.limit);
    const curPage = Math.ceil(app.current.offset / app.current.limit) + 1;
    if (app.current.limit > returned) {
        totalPages = curPage;
    }

    //toolbar
    const paginationTop = createPaginationEls(totalPages, curPage);
    paginationTop.classList.add('me-2');
    paginationTop.setAttribute('id', curPaginationTop.id);
    curPaginationTop.replaceWith(paginationTop);

    //bottom
    const bottomBar = elGetById(app.id + 'ButtonsBottom');
    elClear(bottomBar);
    if (domCache.body.classList.contains('not-mobile') ||
        returned < 25)
    {
        elHide(bottomBar);
        return;
    }
    const toTop = elCreateText('button', {"class": ["btn", "btn-secondary", "mi"],
        "data-title-phrase": "To top"}, 'keyboard_arrow_up');
    toTop.addEventListener('click', function(event) {
        event.preventDefault();
        scrollToPosY(null, 0);
    }, false);
    bottomBar.appendChild(toTop);
    const paginationBottom = createPaginationEls(totalPages, curPage);
    paginationBottom.childNodes[1].classList.add('dropup');
    bottomBar.appendChild(paginationBottom);
    elShow(bottomBar);
}

/**
 * Creates the pagination elements with the dropdown
 * @param {number} totalPages number of total pages
 * @param {number} curPage current page
 * @returns {HTMLElement} button group with pagination
 */
function createPaginationEls(totalPages, curPage) {
    const prev = elCreateText('button', {"data-title-phrase": "Previous page",
        "type": "button", "class": ["btn", "btn-secondary", "mi"]}, 'navigate_before');
    if (curPage === 1) {
        elDisable(prev);
    }
    else {
        prev.addEventListener('click', function(event) {
            event.preventDefault();
            gotoPageDir('prev', undefined);
        }, false);
    }

    const pageDropdownBtn = elCreateText('button', {"type": "button", "data-bs-toggle": "dropdown",
        "class": ["rounded-end-0", "btn", "btn-secondary", "dropdown-toggle", "px-2"]}, curPage.toString());
    const pageDropdownMenu = elCreateEmpty('div', {"class": ["dropdown-menu", "px-2", "dropdownWide"]});

    const row = elCreateNodes('div', {"class": ["row"]}, [
        elCreateTextTn('label', {"class": ["col-8", "col-form-label"]}, 'Elements per page'),
        elCreateEmpty('div', {"class": ["col-4"]})
    ]);

    const elPerPage = elCreateEmpty('select', {"class": ["form-control", "form-select", "border-secondary"]});
    for (const i in settingsWebuiFields.maxElementsPerPage.validValues) {
        elPerPage.appendChild(
            elCreateText('option', {"value": i}, i)
        );
        if (Number(i) === app.current.limit) {
            elPerPage.lastChild.setAttribute('selected', 'selected');
        }
    }
    elPerPage.addEventListener('click', function(event) {
        event.stopPropagation();
    }, false);
    elPerPage.addEventListener('change', function(event) {
        const newLimit = Number(getSelectValue(event.target));
        if (app.current.limit !== newLimit) {
            BSN.Dropdown.getInstance(event.target.parentNode.parentNode.parentNode.previousElementSibling).hide();
            gotoPage(app.current.offset, newLimit);
        }
    }, false);
    row.lastChild.appendChild(elPerPage);

    const pageGrp = elCreateEmpty('div', {"class": ["btn-group"]});

    let start = curPage - 3;
    if (start < 1) {
        start = 1;
    }
    let end = start + 5;
    if (end >= totalPages) {
        end = totalPages - 1;
        start = end - 6 > 1
            ? end - 6
            : 1;
    }

    const first = elCreateEmpty('button', {"data-title-phrase": "First page", "type": "button", "class": ["btn", "btn-secondary"]});
    if (start === 1) {
        first.textContent = '1';
    }
    else {
        first.textContent = 'first_page';
        first.classList.add('mi');
    }
    if (curPage === 1) {
        elDisable(first);
        first.classList.add('active');
    }
    else {
        first.addEventListener('click', function(event) {
            event.preventDefault();
            gotoPage(0, undefined);
        }, false);
    }
    pageGrp.appendChild(first);

    if (end > start) {
        for (let i = start; i < end; i++) {
            pageGrp.appendChild(
                elCreateText('button', {"class": ["btn", "btn-secondary"]}, (i + 1).toString())
            );
            if (i + 1 === curPage) {
                pageGrp.lastChild.classList.add('active');
            }
            if (totalPages === -1) {
                elDisable(pageGrp.lastChild);
            }
            else {
                pageGrp.lastChild.addEventListener('click', function(event) {
                    event.preventDefault();
                    gotoPage(i * app.current.limit, undefined);
                }, false);
            }
        }

        const last = elCreateEmpty('button', {"data-title-phrase": "Last page", "type": "button", "class": ["btn", "btn-secondary"]});
        if (totalPages === end + 1) {
            last.textContent = (end + 1).toString();
        }
        else {
            last.textContent = 'last_page';
            last.classList.add('mi');
        }
        if (totalPages === -1) {
            elDisable(last);
        }
        else if (totalPages === curPage) {
            if (curPage !== 1) {
                last.classList.add('active');
            }
            elDisable(last);
        }
        else {
            last.addEventListener('click', function(event) {
                event.preventDefault();
                gotoPage(totalPages * app.current.limit - app.current.limit, undefined);
            }, false);
        }
        pageGrp.appendChild(last);
    }

    pageDropdownMenu.appendChild(
        elCreateNode('div', {"class": ["row", "mb-3"]}, pageGrp)
    );
    pageDropdownMenu.appendChild(row);

    const next = elCreateText('button', {"data-title-phrase": "Next page", "type": "button", "class": ["btn", "btn-secondary", "mi"]}, 'navigate_next');
    if (totalPages !== -1 && totalPages === curPage) {
        elDisable(next);
    }
    else {
        next.addEventListener('click', function(event) {
            event.preventDefault();
            gotoPageDir('next', undefined);
        }, false);
    }

    const outer = elCreateNodes('div', {"class": ["btn-group", "pagination"]}, [
        prev,
        elCreateNodes('div', {"class": ["btn-group"]}, [
            pageDropdownBtn,
            pageDropdownMenu
        ]),
        next
    ]);
    new BSN.Dropdown(pageDropdownBtn);
    return outer;
}