Extend: Filter modules list by search strings could be stuck

Created on 23 July 2025, 21 days ago

Problem/Motivation

When the number of modules in composer is high, the filtering functionnality by a search string could be really slow and stuck for something like 20 seconds !

Steps to reproduce

Proposed resolution

Response generated by ChatGPT(sorry...) but working great, and make the search instant !

To optimize this Drupal code for filtering a large list of modules and avoid JavaScript freezes (especially when the module list is long), we can improve performance in a few key areas:
Key Optimizations

  1. Avoid redundant DOM queries inside loops.
  2. Use textContent concatenation smarter.
  3. Minimize DOM manipulation (toggle, show, etc.)—batch operations or defer them.
  4. Avoid jQuery .show() / .hide() per row; instead, use display: none classes with minimal DOM writes.
function hidePackageDetails(index, element) {
  const $packDetails = $(element);
  const visibleRows = $packDetails[0].querySelectorAll('tbody tr:not([data-hidden])');
  $packDetails.toggle(visibleRows.length > 0);
}

function filterModuleList(e) {
  const query = e.target.value.trim();
  const re = query.length >= 2 ? new RegExp(`\\b${escapeRegExp(query)}`, 'i') : null;

  let visibleCount = 0;

  if (re) {
    searching = true;

    $rows.each((index, row) => {
      const sources = row.querySelectorAll('.table-filter-text-source, .module-name, .module-description');
      let combined = '';
      for (let i = 0; i < sources.length; i++) {
        combined += sources[i].textContent + ' ';
      }

      const match = re.test(combined);
      if (match) {
        row.removeAttribute('data-hidden');
        row.style.display = '';
        visibleCount++;
      } else {
        row.setAttribute('data-hidden', 'true');
        row.style.display = 'none';
      }
    });

    $details.each((_, detail) => {
      detail.setAttribute('open', true);
      if (!detail.hasAttribute('open')) {
        detail.setAttribute('data-drupal-system-state', 'forced-open');
      }
      hidePackageDetails(null, detail);
    });

    Drupal.announce(
      Drupal.formatPlural(
        visibleCount,
        '1 module is available in the modified list.',
        '@count modules are available in the modified list.',
      ),
    );

  } else if (searching) {
    searching = false;

    $rows.each((_, row) => {
      row.removeAttribute('data-hidden');
      row.style.display = '';
    });

    $details.each((_, detail) => {
      if (detail.getAttribute('data-drupal-system-state') === 'forced-open') {
        detail.removeAttribute('data-drupal-system-state');
        detail.removeAttribute('open');
      }
    });
  }
}

function escapeRegExp(string) {
  // Prevent RegExp injection
  return string.replace(/[.*+?^${}()|[\]\\]/g, '\\$&');
}

function preventEnterKey(event) {
  if (event.which === 13) {
    event.preventDefault();
    event.stopPropagation();
  }
}

// Setup
if ($table.length) {
  $rowsAndDetails = $table.find('tr, details');
  $rows = $table.find('tbody tr');
  $details = $rowsAndDetails.filter('.package-listing');

  $(input).on({
    input: debounce(filterModuleList, 200),
    keydown: preventEnterKey,
  });
}

What's Improved

  • Avoided jQuery toggle() calls in loops; used style.display for fast updates.
  • Avoided querying :visible repeatedly—uses data-hidden flags instead.
  • Precompiled RegExp only once.
  • Debounced input listener already present, good for long lists.
🐛 Bug report
Status

Active

Version

11.0 🔥

Component

system.module

Created by

🇮🇱Israel heyyo Jerusalem

Live updates comments and jobs are added and updated live.
Sign in to follow issues

Merge Requests

Comments & Activities

  • Issue created by @heyyo
  • Pipeline finished with Failed
    21 days ago
    Total: 235s
    #554834
  • Pipeline finished with Success
    21 days ago
    Total: 587s
    #554883
  • 🇮🇱Israel heyyo Jerusalem
  • 🇮🇱Israel heyyo Jerusalem
  • 🇮🇱Israel heyyo Jerusalem

    In this video I'm showing before vs after patch (from 0:50s)
    its' look like I do nothing in the video after typing gro but I typed on on my keyboard, Drupal is just stuck from more or less 10 to 18 seconds

  • 🇺🇸United States nicxvan

    I think this is a duplicate and the other solution is more generic so I think this should be closed.

    Would you be able to do the test before and after you posted in contribute on the other issue?

    That performance test is valuable.

  • 🇮🇱Israel heyyo Jerusalem

    Sure on it

  • 🇮🇱Israel heyyo Jerusalem

    I confirm similar speed with the patch of the issue you mentioned and my patch. Both are filtering instantly.
    I tested in Claro and in Gin too, where this slowness is really more visible ( 2s for Claro, 18s for Gin when adding/removing character)
    So no doubt my issue could be closed.

Production build 0.71.5 2024