Actions

MediaWiki

Common.js: Difference between revisions

From SUALEX

No edit summary
No edit summary
Line 46: Line 46:
     // Pager UI (placed above the table)
     // Pager UI (placed above the table)
     var $pager = $('<div class="table-pager" style="margin:0.5em 0; display:flex; align-items:center; gap:0.5em;">');
     var $pager = $('<div class="table-pager" style="margin:0.5em 0; display:flex; align-items:center; gap:0.5em;">');
     var $prev = $('<button type="button" class="pager-prev">Prev</button>');
     var $prev = $('<button type="button" class="pager-prev"></button>');
     var $next = $('<button type="button" class="pager-next">Next</button>');
     var $next = $('<button type="button" class="pager-next"></button>');
     var $pageInfo = $('<span class="pager-pageinfo">');
     var $pageInfo = $('<span class="pager-pageinfo">');
     var $status = $('<span class="pager-status">');
     var $status = $('<span class="pager-status">');

Revision as of 00:17, 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 (placed above the table)
    var $pager = $('<div class="table-pager" style="margin:0.5em 0; display:flex; align-items:center; gap:0.5em;">');
    var $prev = $('<button type="button" class="pager-prev">◀</button>');
    var $next = $('<button type="button" class="pager-next">▶</button>');
    var $pageInfo = $('<span class="pager-pageinfo">');
    var $status = $('<span class="pager-status">');
    $pager.append($prev, $next, $pageInfo, $status);
    $table.before($pager);

    // Page size fixed per request
    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);

      $pageInfo.text('Page ' + currentPage + ' of ' + totalPages);
      if (totalMatches === 0) {
        $status.text('Showing 0 entries');
      } else {
        $status.text('Showing ' + start + '–' + end + ' of ' + totalMatches + ' entries');
      }

      $prev.prop('disabled', currentPage <= 1);
      $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);
    }

    // Prev / Next handlers
    $prev.on('click', function () {
      if (currentPage > 1) {
        currentPage--;
        renderPage();
      }
    });
    $next.on('click', function () {
      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();
      }
    });

    // 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));
    });
  });
});