Skip to content

Commit

Permalink
Paginate results
Browse files Browse the repository at this point in the history
  • Loading branch information
nothingface0 committed Jan 30, 2025
1 parent f68d049 commit 24f1c2d
Show file tree
Hide file tree
Showing 2 changed files with 289 additions and 31 deletions.
266 changes: 266 additions & 0 deletions static/pagination_helper.js
Original file line number Diff line number Diff line change
@@ -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)
}
}
};
54 changes: 23 additions & 31 deletions templates/release_comparisons.html
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{% extends "base.html" %} {% block head %}
<script src="{{ url_for('static', filename='index_utils.js') }}"></script>
<script src="{{ url_for('static', filename='pagination_helper.js') }}"></script>
<title>DQM CMSSW Release comparison results browser</title>
{% endblock %} {% block content %}
<div class="row mt-5">
Expand Down Expand Up @@ -27,10 +27,7 @@ <h2>Click an item from the list on the left.</h2>
<script>
const TIMEZONE = "{{ TIMEZONE }}";
const PREFIX = "{{ PREFIX }}";

function initialize_results_list() {
document.getElementById("results-list").innerHTML = "";
}
const MAX_RESULTS_PER_PAGE = 10;

// Remove the bootstrap 'active' class from all items
// in the available reports list.
Expand All @@ -57,28 +54,6 @@ <h2>Click an item from the list on the left.</h2>
set_comparison_frame_contents(id);
}

// Create a single li element for a single comparison report.
function create_result_list_element(data) {
let li = document.createElement("li");
li.classList.add("list-group-item", "list-group-item-action");
li.onclick = onclick_specific_comparison;
li.innerHTML = `${data.base_cmssw_version} (${data.base_prs}) vs ${data.comp_cmssw_version} (${data.comp_prs})`;
li.setAttribute("id", data.id);
li.setAttribute("title", `Comparison ran at ${data.comparison_ran_at}`);
return li;
}

// Given the list of available comparison reports, populate
// the results list.
function update_available_results_list(data) {
let results_list = document.getElementById("results-list");
data.forEach((element) => {
console.log(element);
let li = create_result_list_element(element);
results_list.append(li);
});
}

// Fetch the list of the available comparison reports from the API.
// Return the json response as a Promise.
function get_available_comparisons() {
Expand All @@ -89,10 +64,27 @@ <h2>Click an item from the list on the left.</h2>

// Attach to the load event of the window.
addEventListener("load", (event) => {
initialize_results_list();
get_available_comparisons().then((data) =>
update_available_results_list(data)
);
let pagination = pagination_helper();
get_available_comparisons().then((data) => {
pagination.paginate(
data /* data paginate */,
"results-list" /* DOM element to attach to */,
/* On result creation callback. Gets the DOM element created (li)
and the data corresponding as arg, must return it. */
(li, data) => {
li.setAttribute("id", data.id);
li.setAttribute(
"title",
`Comparison ran at ${data.comparison_ran_at}`
);
li.innerHTML = `${data.base_cmssw_version} (${data.base_prs}) vs ${data.comp_cmssw_version} (${data.comp_prs})`;
li.classList.add("list-group-item", "list-group-item-action");
li.onclick = onclick_specific_comparison;
return li;
},
MAX_RESULTS_PER_PAGE /* Items per page to show */
);
});
});
</script>
{% endblock %}

0 comments on commit 24f1c2d

Please sign in to comment.