Common.js: Difference between revisions
From SUALEX
Created page with "→Any JavaScript here will be loaded for all users on every page load.: mw.loader.using(['jquery'], function () { function initColumnSearch($table) { var $thead = $table.find('thead'); var $rows = $table.find('tbody tr'); 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')...." |
No edit summary |
||
| (5 intermediate revisions by the same user not shown) | |||
| Line 1: | Line 1: | ||
/* Any JavaScript here will be loaded for all users on every page load. */ | /* Any JavaScript here will be loaded for all users on every page load. */ | ||
mw.loader.using(['jquery'], function () { | 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 | function initColumnSearchAndPager($table) { | ||
var $thead = $table.find('thead'); | var $thead = $table.find('thead'); | ||
// Ensure a thead exists (clone first row into thead if necessary) | |||
if ($thead.length === 0) { | if ($thead.length === 0) { | ||
var $firstRow = $table.find('tr').first(); | var $firstRow = $table.find('tr').first(); | ||
| Line 13: | Line 24: | ||
} | } | ||
var $headerRow = $thead.find('tr').last(); | var $headerRow = $thead.find('tr').last(); | ||
var $filterRow = $('<tr class="searchable-row">'); | var $filterRow = $('<tr class="searchable-row">'); | ||
$headerRow.find('th'). | var colCount = $headerRow.find('th').length; | ||
for (var colIndex = 0; colIndex < colCount; colIndex++) { | |||
var $input = $('<input>', { | var $input = $('<input>', { | ||
type: 'search', | type: 'search', | ||
placeholder: 'Search…' | placeholder: 'Search…', | ||
'data-col-index': colIndex | |||
}).css({ | }).css({ | ||
width: '95%', | width: '95%', | ||
| Line 26: | Line 40: | ||
$filterRow.append($('<th>').append($input)); | $filterRow.append($('<th>').append($input)); | ||
} | } | ||
$headerRow.before($filterRow); | $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 = 50; | |||
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 = []; | var filters = []; | ||
$filterRow.find('input').each(function () { | $filterRow.find('input').each(function () { | ||
filters.push($(this).val().toLowerCase()); | filters.push($(this).val().toLowerCase()); | ||
}); | }); | ||
$ | // Recompute rows set | ||
$allRows = getAllRows(); | |||
$allRows.each(function () { | |||
var $cells = $(this).find('td'); | var $cells = $(this).find('td'); | ||
var visible = true; | var visible = true; | ||
filters. | for (var i = 0; i < filters.length; i++) { | ||
if (!text) | 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; | visible = false; | ||
break; | |||
} | } | ||
} | } | ||
$(this). | $(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 () { | $(function () { | ||
$('table.searchable.sortable').each(function () { | $('table.searchable.sortable').each(function () { | ||
initColumnSearchAndPager($(this)); | |||
}); | }); | ||
}); | }); | ||
}); | }); | ||
Latest revision as of 01:39, 31 January 2026
/* 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 = 50;
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));
});
});
});