jQuery / Choisir les parents selon le nombre d’enfants

Filtrez les parents candidats avec .filter(function () { ... }) et comparez $(this).children(".item").length dans chaque rappel. === 2 signifie exactement deux, >= 2 au moins deux. Utilisez find() à la place de children() uniquement pour tous les descendants imbriqués.

Définir ce que chaque parent compte

.filter() conserve les membres de la sélection qui passent le test. this est le parent DOM examiné ; true le conserve. Le résultat contient des parents, pas des enfants, et ne supprime pas les autres du document.

.children(".item") compte les éléments correspondants un niveau en dessous. Sans sélecteur, titres et autres enfants éléments comptent aussi. Texte et commentaires sont exclus dans les deux cas.

.find(".item") comprend les descendants plus profonds. Beta possède un enfant direct qui en contient deux autres, donc trois descendants. Les éléments masqués existent toujours dans le DOM et comptent. Une règle supplémentaire est nécessaire pour les seuls éléments visibles.

Exemple complet : exact ou minimal

Enregistrez tout en UTF-8 sous child-count-demo.html et ouvrez-le dans le navigateur. Internet charge le fichier officiel jQuery 4.0.0. Aucun serveur ou paquet n’est nécessaire. Conditions, réinitialisation et ajouts ont été exécutés dans Edge sous Windows avec 3.7.1 et 4.0.0.

Comptes directs : Alpha 2, Beta 1, Gamma 0, Delta 3. D3 masqué compte ; titres et texte ne sont pas .item. Les candidats sont uniquement les .group directs de #board, sans éléments intérieurs.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Filter parents by child count</title>
  <script src="https://code.jquery.com/jquery-4.0.0.min.js"></script>
  <style>
    body { font: 16px/1.6 sans-serif; margin: 20px; max-width: 760px; }
    label { display: block; margin: 8px 0; }
    select, input, button { font: inherit; max-width: 100%; }
    .group { border: 2px solid #aaa; padding: 12px; margin: 12px 0; }
    .group.chosen { border-color: teal; background: #e5f5f1; }
    .item { display: inline-block; padding: 4px; border: 1px solid #777; margin: 3px; }
    .item[hidden] { display: none; }
    .group h2 { font-size: 20px; margin: 0; }
    #result { white-space: pre-wrap; overflow-wrap: anywhere; }
  </style>
</head>
<body>
  <label>Count scope <select id="scope"><option value="direct">Direct children</option><option value="descendant">All descendants</option></select></label>
  <label>Rule <select id="rule"><option value="exact">Exactly</option><option value="minimum">At least</option></select></label>
  <label>Required count <input id="count" type="number" min="0" step="1" value="2"></label>
  <button type="button" id="add">Add direct item to Alpha</button>
  <button type="button" id="reset">Reset</button>
  <div id="board">
    <section class="group" id="alpha"><h2>Alpha</h2><span class="item">A1</span><span class="item">A2</span> plain text <!-- comment --></section>
    <section class="group" id="beta"><h2>Beta</h2><div class="item">B1 <span class="item">B2</span><span class="item">B3</span></div></section>
    <section class="group" id="gamma"><h2>Gamma</h2>Text without an item</section>
    <section class="group" id="delta"><h2>Delta</h2><span class="item">D1</span><span class="item">D2</span><span class="item" hidden>D3 hidden</span></section>
  </div>
  <pre id="result" aria-live="polite"></pre>
  <script>
    $(function () {
      const initial = $("#board").html();
      let added = 0;
      function apply() {
        const $groups = $("#board > .group");
        const scope = $("#scope").val(), rule = $("#rule").val();
        const field = document.getElementById("count"), target = Number(field.value);
        $groups.removeClass("chosen");
        if (field.value === "" || !field.checkValidity() || !Number.isInteger(target) || target < 0) {
          $("#result").text("Enter a nonnegative integer."); return;
        }
        function count(group) {
          return scope === "direct" ? $(group).children(".item").length : $(group).find(".item").length;
        }
        const $selected = $groups.filter(function () {
          const n = count(this);
          return rule === "exact" ? n === target : n >= target;
        });
        $selected.addClass("chosen");
        const ids = $selected.map(function () { return this.id; }).get();
        const counts = $groups.map(function () { return this.id + "=" + count(this); }).get();
        $("#result").text(`scope=${scope}; rule=${rule}; count=${target}\nselected=${ids.join(",") || "(none)"}\n${counts.join("; ")}`);
      }
      $("#scope, #rule, #count").on("change input", apply);
      $("#add").on("click", function () {
        added++; $("<span>").addClass("item").text("Added " + added).appendTo("#alpha"); apply();
      });
      $("#reset").on("click", function () {
        $("#board").html(initial); added = 0;
        $("#scope").val("direct"); $("#rule").val("exact"); $("#count").val(2); apply();
      });
      apply();
    });
  </script>
</body>
</html>

Au départ, seule Alpha avec exactement deux enfants est mise en évidence.

scope=direct; rule=exact; count=2
selected=alpha
alpha=2; beta=1; gamma=0; delta=3
  1. At least sélectionne Alpha et Delta. Exactement deux diffère d’au moins deux.
  2. All descendants ajoute Beta : son compte passe de un à trois.
  3. Exactly 3 sélectionne Beta et Delta pour les descendants, mais seulement Delta avec Direct children.
  4. Exactly 0 choisit Gamma ; At least 0 autorise zéro et choisit les quatre.
  5. Après Reset, Add direct item to Alpha porte Alpha à trois ; exactement deux donne (none). Aucun parent n’a été supprimé.
  6. Vide, négatif ou fractionnaire affiche une erreur et retire les marques. Reset restaure structure et conditions.

Total groupé et test par parent

Appuyez sur Reset puis exécutez dans les outils de développement pour comparer collection commune et filtre individuel :

const $groups = $("#board > .group");
console.log($groups.children(".item").length);
console.log($groups.filter(function () {
  return $(this).children(".item").length === 0;
}).map(function () { return this.id; }).get().join(","));

Six représente 2+1+0+3, sans indiquer quel parent en possède deux. La seconde ligne teste chaque parent et renvoie Gamma sans .item direct.

6
gamma

Une fonction classique reçoit this comme parent courant. La remplacer par une flèche en gardant this invalide cette hypothèse. Avec une flèche, recevez explicitement le second argument élément du rappel.

Une collection filtrée enregistrée ne réévalue pas continuellement sa condition. Après ajout, l’ancien résultat peut conserver Alpha. L’exemple rappelle apply() avec les candidats et comptes actuels. .filter() ne surveille pas les changements du DOM.

Périmètre et erreurs courantes

Précisez tous les éléments ou des cartes, li ou contrôles particuliers. Ici seuls les .item comptent : Gamma vaut zéro malgré son h2 et son texte. Cela ne veut pas dire conteneur complètement vide. Ajoutez explicitement visibilité, disponibilité ou état des données si nécessaire.

Pour la seule présence d’un descendant correspondant, .has() peut être plus simple. Quantités exactes et minimales sont claires avec des comparaisons numériques. Ne confondez pas nth-child, position parmi les frères, avec le nombre total du parent.

Avec des milliers de parents et des recherches profondes répétées, réduisez les candidats et calculs inutiles. Mais ne réutilisez pas de vieux résultats après changement structurel. Vérifiez l’état initial puis modifié.