Actions

MediaWiki

Common.js: Difference between revisions

From SUALEX

No edit summary
No edit summary
 
(One intermediate revision by the same user not shown)
Line 44: Line 44:
     $headerRow.before($filterRow);
     $headerRow.before($filterRow);


     // Pager UI (placed above the table)
     // Pager UI
    var $pager = $('<div class="table-pager" style="margin:0.5em 0; display:flex; align-items:center; gap:0.5em;">');
    function createPager() {
    var $prev = $('<button type="button" class="pager-prev">◀</button>');
      var $pager = $('<div class="table-pager">').css({
    var $next = $('<button type="button" class="pager-next">▶</button>');
        margin: '0.5em 0',
    var $pageInfo = $('<span class="pager-pageinfo">');
        display: 'flex',
    var $status = $('<span class="pager-status">');
        justifyContent: 'space-between',
    $pager.append($prev, $next, $pageInfo, $status);
        alignItems: 'center',
    $table.after($pager);
        gap: '0.5em'
      });


    // Page size fixed
      var $left = $('<div class="pager-left">').css({ display: 'flex', alignItems: 'center', gap: '0.5em' });
     var pageSize = 100;
      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;
     var currentPage = 1;


Line 61: Line 89:
       var $rows = $table.find('tbody tr');
       var $rows = $table.find('tbody tr');
       if ($rows.length === 0) {
       if ($rows.length === 0) {
        // fallback: all tr under table excluding thead rows
         $rows = $table.find('tr').not($thead.find('tr'));
         $rows = $table.find('tr').not($thead.find('tr'));
       }
       }
Line 76: Line 105:
       var end = Math.min(currentPage * pageSize, totalMatches);
       var end = Math.min(currentPage * pageSize, totalMatches);


       $pageInfo.text('Page ' + currentPage + ' of ' + totalPages);
       var pageText = 'Page ' + currentPage + ' of ' + totalPages;
       if (totalMatches === 0) {
       var statusText = (totalMatches === 0)
         $status.text('Showing 0 entries');
         ? 'Showing 0 entries'
      } else {
         : 'Showing ' + start + '–' + end + ' of ' + totalMatches + ' entries';
         $status.text('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);


       $prev.prop('disabled', currentPage <= 1);
       // enable/disable both prev/next buttons
       $next.prop('disabled', currentPage >= totalPages);
      topPager.$prev.prop('disabled', currentPage <= 1);
      bottomPager.$prev.prop('disabled', currentPage <= 1);
       topPager.$next.prop('disabled', currentPage >= totalPages);
      bottomPager.$next.prop('disabled', currentPage >= totalPages);
     }
     }


Line 109: Line 145:
     }
     }


     // Prev / Next handlers
     // Navigation button handlers
     $prev.on('click', function () {
     function goPrev() {
       if (currentPage > 1) {
       if (currentPage > 1) {
         currentPage--;
         currentPage--;
         renderPage();
         renderPage();
       }
       }
     });
     }
     $next.on('click', function () {
     function goNext() {
       var totalMatches = $allRows.filter(function () { return $(this).data('matched') === true; }).length;
       var totalMatches = $allRows.filter(function () { return $(this).data('matched') === true; }).length;
       var totalPages = Math.max(1, Math.ceil(totalMatches / pageSize));
       var totalPages = Math.max(1, Math.ceil(totalMatches / pageSize));
Line 123: Line 159:
         renderPage();
         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)
     // Filtering logic (applies across all rows)

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