From 24f1c2d7b26b339ab8ed54798f1a2a8ff7256f3f Mon Sep 17 00:00:00 2001 From: Dimitris Papagiannis Date: Wed, 29 Jan 2025 18:43:03 +0100 Subject: [PATCH] Paginate results --- static/pagination_helper.js | 266 +++++++++++++++++++++++++++++ templates/release_comparisons.html | 54 +++--- 2 files changed, 289 insertions(+), 31 deletions(-) create mode 100644 static/pagination_helper.js diff --git a/static/pagination_helper.js b/static/pagination_helper.js new file mode 100644 index 0000000..20b8d02 --- /dev/null +++ b/static/pagination_helper.js @@ -0,0 +1,266 @@ + +function pagination_helper() { + let _current_page = 1; + let _data = {} + let _num_total_items = 1; + let _dom_element = ''; // The DOM element where the data will be put in + let _items_per_page = 1; + let _total_pages = 1; + let _item_creation_callback; + let _is_initialized = false; + const NUM_PAGES_SHOW_ELLIPSIS = 2 + + + function _calculate_num_pages() { + _total_pages = Math.ceil(_num_total_items / _items_per_page); + } + + /* Helper function for adding CSS classes to a paginato page button */ + function _add_bootstrap_classes_to_page_li(el) { + el.classList.add('page-item') + el.firstChild.classList.add('page-link') + return el + } + + /* Callback to run when clicking the next page button */ + function _goto_next_page() { + if (_current_page + 1 > _total_pages) { + _current_page = _total_pages; + } + else { + _current_page += 1; + } + _goto_specific_page(_current_page) + } + + /* Callback to run when clicking the previous page button */ + function _goto_prev_page() { + if (_current_page - 1 < 1) { + _current_page = 1; + } + else { + _current_page -= 1; + } + _goto_specific_page(_current_page) + } + + /* Callback to run when clicking on a page button */ + function _goto_specific_page(page_num) { + if (page_num <= _total_pages && page_num >= 1) { + _current_page = page_num; + _clear_pagination_el() + _create_pages() + _clear_data() + _fill_data() + } + } + + /* + Function that crates a paginator button for a single page. + + content: can be either a DOM Element (in which case it is appended directly + into the button), or a text/number, in which case it replaces the innerHTML. + + additional classes: a string of CSS classes to be added to the button (e.g., "class1 class2") + + onclick: the function to run when the button is clicked. It will be passed the + DOM Element as an argument. + + title: the string to display on button mouse hover. + */ + function _create_page_el(content, additional_classes, onclick, title) { + let li = document.createElement('li') + if (additional_classes) { + li.classList.add(additional_classes) + } + if (title) { + li.setAttribute('title', title) + } + let a = document.createElement('a') + a.setAttribute.href = '#' + if (content instanceof Element) { + a.appendChild(content) + } else if (typeof content === 'string' || typeof content === 'number') { + a.innerHTML = content + if (!title) { + li.setAttribute('title', `Go to page ${content}`) + } + } + if (typeof onclick === 'function') { + li.onclick = onclick + } + li.appendChild(a) + return _add_bootstrap_classes_to_page_li(li) + } + + function _create_first_page_el() { + let i = document.createElement('i') + i.classList.add('bi', 'bi-chevron-double-left') + + return _create_page_el(i, _current_page === 1 ? "disabled" : "", + () => { _goto_specific_page(1) }, "First page") + } + + function _create_previous_page_el() { + let i = document.createElement('i') + i.classList.add('bi', 'bi-chevron-left') + return _create_page_el(i, _current_page === 1 ? "disabled" : "", + _goto_prev_page, "Previous page") + } + + function _create_last_page_el() { + let i = document.createElement('i') + i.classList.add('bi', 'bi-chevron-double-right') + return _create_page_el(i, (_current_page === _total_pages || _total_pages === 0) ? + "disabled" : "", + () => { _goto_specific_page(_total_pages) }, + 'Last page') + } + + function _create_next_page_el() { + let i = document.createElement('i') + i.classList.add('bi', 'bi-chevron-right') + return _create_page_el(i, (_current_page === _total_pages || _total_pages === 0) ? + "disabled" : "", + _goto_next_page, + "Next page") + } + + /* Create a single page button for a specific page */ + function _create_page_number_el(page_num) { + return _create_page_el( + String(page_num), + _current_page === page_num ? "active" : "", + () => { _goto_specific_page(page_num) } + ) + } + + /* Get the DOM element id where page buttons will be added */ + function _get_pagination_el_id() { + return `${_dom_element}_pagination` + } + + /* Clear the pagination buttons completely */ + function _clear_pagination_el() { + let pagination_el = document.getElementById(_get_pagination_el_id()) + if (pagination_el) { + pagination_el.innerHTML = '' + } + } + + /* + Create the pagination element under the same parent as the + _dom_element passed at initialization. + */ + function _create_pagination_el() { + let base_el = document.getElementById(_dom_element).parentElement; + let pagination_el = document.createElement('nav'); + let ul_el = document.createElement('ul'); + ul_el.classList.add('pagination', 'mt-2') + ul_el.setAttribute('id', _get_pagination_el_id()) + pagination_el.appendChild(ul_el) + base_el.appendChild(pagination_el); + } + + /* + Create the page links inside the pagination DOM element. + */ + function _create_pages() { + let pagination_el = document.getElementById(_get_pagination_el_id()) + pagination_el.appendChild(_create_first_page_el()) + pagination_el.appendChild(_create_previous_page_el()) + if (_total_pages < 3) { + for (i = 1; i <= _total_pages; i++) { + pagination_el.appendChild(_create_page_number_el(i)) + } + } + else { + if (_current_page - 1 <= NUM_PAGES_SHOW_ELLIPSIS) { + for (let i = 1; i <= _current_page - 1; i++) { + pagination_el.appendChild(_create_page_number_el(i)) + } + } + else { + pagination_el.appendChild(_create_page_el('...', 'disabled')) + pagination_el.appendChild(_create_page_number_el(_current_page - 1)) + } + + pagination_el.appendChild(_create_page_number_el(_current_page)) + + if (_total_pages - _current_page <= NUM_PAGES_SHOW_ELLIPSIS) { + for (let i = _current_page + 1; i <= _total_pages; i++) { + pagination_el.appendChild(_create_page_number_el(i)) + } + } + else { + pagination_el.appendChild(_create_page_number_el(_current_page + 1)) + pagination_el.appendChild(_create_page_el('...', 'disabled')) + + } + } + pagination_el.appendChild(_create_next_page_el()) + pagination_el.appendChild(_create_last_page_el()) + } + + // Create a single li element for a single comparison report. + function _create_result_list_element(data) { + let li = document.createElement("li"); + + if (typeof _item_creation_callback === 'function') { + li = _item_creation_callback(li, data) + } + return li; + } + + function _clear_data() { + let data_el = document.getElementById(_dom_element) + data_el.innerHTML = '' + } + function _fill_data() { + let data_el = document.getElementById(_dom_element) + let index_start = (_current_page - 1) * _items_per_page + let page_data = _data.slice(index_start, index_start + _items_per_page) + page_data.forEach(element => { + data_el.appendChild(_create_result_list_element(element)) + }); + } + function _paginate(data, dom_element, item_creation_callback, items_per_page) { + if (typeof data !== 'object' || !data.hasOwnProperty('length')) { + throw Error(`data passed should be array-like, cannot initialize pagination`) + } + _data = data; + _num_total_items = data.length; + if (typeof dom_element !== 'string' || !document.getElementById(dom_element)) { + throw Error(`Could not find DOM element "${dom_element}" to attach pagination to`) + } + _dom_element = dom_element; + + _items_per_page = Number(items_per_page) + if (isNaN(_items_per_page)) { + throw Error(`items_per_page should be a number, received ${typeof items_per_page}: ${items_per_page}`) + } + + if (typeof item_creation_callback === 'function') { + _item_creation_callback = item_creation_callback + } + _calculate_num_pages() + _clear_pagination_el() + _create_pagination_el() + _create_pages() + _clear_data() + _fill_data() + } + /* "Public" methods are returned as an object with functions */ + return { + paginate: (data, dom_element, item_creation_callback, items_per_page = _items_per_page) => { + _paginate(data, dom_element, item_creation_callback, items_per_page) + _is_initialized = true + }, + data: (data) => { + if (!_is_initialized) { + throw Error(`Paginator has not been initialized!`) + } + _paginate(data, _dom_element, _item_creation_callback, _items_per_page) + } + } +}; \ No newline at end of file diff --git a/templates/release_comparisons.html b/templates/release_comparisons.html index 951d5f9..ae92eaa 100644 --- a/templates/release_comparisons.html +++ b/templates/release_comparisons.html @@ -1,5 +1,5 @@ {% extends "base.html" %} {% block head %} - + DQM CMSSW Release comparison results browser {% endblock %} {% block content %}
@@ -27,10 +27,7 @@

Click an item from the list on the left.

{% endblock %}