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
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 (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">Prev</button>');
    var $next = $('<button type="button" class="pager-next">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 = [];
       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));
     });
     });
   });
   });
});
});

Revision as of 00:14, 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">Prev</button>');
    var $next = $('<button type="button" class="pager-next">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));
    });
  });
});