MediaWiki:Common.js
From SUALEX
Note: After publishing, you may have to bypass your browser's cache to see the changes.
- Firefox / Safari: Hold Shift while clicking Reload, or press either Ctrl-F5 or Ctrl-R (⌘-R on a Mac)
- Google Chrome: Press Ctrl-Shift-R (⌘-Shift-R on a Mac)
- Edge: Hold Ctrl while clicking Refresh, or press Ctrl-F5.
/* Any JavaScript here will be loaded for all users on every page load. */
mw.loader.using(['jquery'], function () {
// Debounce helper to avoid too-frequent re-evaluations while typing
function debounce(fn, wait) {
var timeout;
return function () {
var context = this, args = arguments;
clearTimeout(timeout);
timeout = setTimeout(function () {
fn.apply(context, args);
}, wait);
};
}
function initColumnSearchAndPager($table) {
var $thead = $table.find('thead');
// Ensure a thead exists (clone first row into thead if necessary)
if ($thead.length === 0) {
var $firstRow = $table.find('tr').first();
$thead = $('<thead>').append($firstRow.clone());
$firstRow.remove();
$table.prepend($thead);
}
var $headerRow = $thead.find('tr').last();
var $filterRow = $('<tr class="searchable-row">');
var colCount = $headerRow.find('th').length;
for (var colIndex = 0; colIndex < colCount; colIndex++) {
var $input = $('<input>', {
type: 'search',
placeholder: 'Search…',
'data-col-index': colIndex
}).css({
width: '95%',
boxSizing: 'border-box'
});
$filterRow.append($('<th>').append($input));
}
$headerRow.before($filterRow);
// Pager UI
function createPager() {
var $pager = $('<div class="table-pager">').css({
margin: '0.5em 0',
display: 'flex',
justifyContent: 'space-between',
alignItems: 'center',
gap: '0.5em'
});
var $left = $('<div class="pager-left">').css({ display: 'flex', alignItems: 'center', gap: '0.5em' });
var $right = $('<div class="pager-right">').css({ display: 'flex', alignItems: 'center', gap: '0.5em' });
var $status = $('<span class="pager-status">');
var $pageInfo = $('<span class="pager-pageinfo">');
var $prev = $('<button type="button" class="pager-prev">◀</button>');
var $next = $('<button type="button" class="pager-next">▶</button>');
$left.append($status);
$right.append($pageInfo, $prev, $next);
$pager.append($left, $right);
return {
$pager: $pager,
$status: $status,
$pageInfo: $pageInfo,
$prev: $prev,
$next: $next
};
}
var topPager = createPager();
var bottomPager = createPager();
$table.before(topPager.$pager);
$table.after(bottomPager.$pager);
var pageSize = 100;
var currentPage = 1;
// Helper to fetch current candidate rows (recomputed when needed)
function getAllRows() {
var $rows = $table.find('tbody tr');
if ($rows.length === 0) {
// fallback: all tr under table excluding thead rows
$rows = $table.find('tr').not($thead.find('tr'));
}
return $rows;
}
// Initialize matched flag
var $allRows = getAllRows();
$allRows.each(function () { $(this).data('matched', true); });
function updatePagerUI(totalMatches) {
var totalPages = Math.max(1, Math.ceil(totalMatches / pageSize));
// compute shown range
var start = (totalMatches === 0) ? 0 : (currentPage - 1) * pageSize + 1;
var end = Math.min(currentPage * pageSize, totalMatches);
var pageText = 'Page ' + currentPage + ' of ' + totalPages;
var statusText = (totalMatches === 0)
? 'Showing 0 entries'
: 'Showing ' + start + '–' + end + ' of ' + totalMatches + ' entries';
// Update both pagers
topPager.$pageInfo.text(pageText);
bottomPager.$pageInfo.text(pageText);
topPager.$status.text(statusText);
bottomPager.$status.text(statusText);
// enable/disable both prev/next buttons
topPager.$prev.prop('disabled', currentPage <= 1);
bottomPager.$prev.prop('disabled', currentPage <= 1);
topPager.$next.prop('disabled', currentPage >= totalPages);
bottomPager.$next.prop('disabled', currentPage >= totalPages);
}
function renderPage() {
// Recompute rows set (in case table changed)
$allRows = getAllRows();
// Hide everything first
$allRows.hide();
// Filtered rows (preserve order)
var $matched = $allRows.filter(function () { return $(this).data('matched') === true; });
var totalMatches = $matched.length;
var totalPages = Math.max(1, Math.ceil(totalMatches / pageSize));
if (currentPage > totalPages) currentPage = totalPages;
var startIndex = (currentPage - 1) * pageSize;
var endIndex = startIndex + pageSize;
$matched.slice(startIndex, endIndex).show();
updatePagerUI(totalMatches);
}
// Navigation button handlers
function goPrev() {
if (currentPage > 1) {
currentPage--;
renderPage();
}
}
function goNext() {
var totalMatches = $allRows.filter(function () { return $(this).data('matched') === true; }).length;
var totalPages = Math.max(1, Math.ceil(totalMatches / pageSize));
if (currentPage < totalPages) {
currentPage++;
renderPage();
}
}
// Wire handlers for both pagers
topPager.$prev.on('click', goPrev);
bottomPager.$prev.on('click', goPrev);
topPager.$next.on('click', goNext);
bottomPager.$next.on('click', goNext);
// Filtering logic (applies across all rows)
function applyFilters() {
var filters = [];
$filterRow.find('input').each(function () {
filters.push($(this).val().toLowerCase());
});
// Recompute rows set
$allRows = getAllRows();
$allRows.each(function () {
var $cells = $(this).find('td');
var visible = true;
for (var i = 0; i < filters.length; i++) {
var text = filters[i];
if (!text) continue;
var cell = $cells.eq(i);
var cellText = cell.length ? cell.text().toLowerCase() : '';
if (cellText.indexOf(text) === -1) {
visible = false;
break;
}
}
$(this).data('matched', visible);
});
// Reset to first page when filters change
currentPage = 1;
renderPage();
}
// Debounce input handler for better UX on large tables
var debouncedApply = debounce(applyFilters, 150);
$filterRow.find('input').on('input', debouncedApply);
// Expose a reapply function for external code that mutates or sorts the table
$table.data('reapplySearchAndPaging', function () {
applyFilters();
});
// Initial render
renderPage();
}
// Initialize every matching table on DOM ready
$(function () {
$('table.searchable.sortable').each(function () {
initColumnSearchAndPager($(this));
});
});
});