Actions

MediaWiki

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 initColumnSearch($table) {
   function initColumnSearchAndPager($table) {
     var $thead = $table.find('thead');
     var $thead = $table.find('thead');
    var $rows  = $table.find('tbody tr');


    // 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(); // REAL header
     var $headerRow = $thead.find('tr').last();
     var $filterRow = $('<tr class="searchable-row">');
     var $filterRow = $('<tr class="searchable-row">');


     $headerRow.find('th').each(function (colIndex) {
     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);


     $filterRow.find('input').on('input', function () {
    // 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());
       });
       });


       $rows.each(function () {
      // 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.forEach(function (text, i) {
         for (var i = 0; i < filters.length; i++) {
           if (!text) return;
          var text = filters[i];
           if (!$cells.eq(i).text().toLowerCase().includes(text)) {
           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).toggle(visible);
         $(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 () {
       initColumnSearch($(this));
       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));
    });
  });
});