jQuery / Elegir padres por el número de hijos

Filtre padres candidatos con .filter(function () { ... }) y compare $(this).children(".item").length en cada callback. === 2 significa exactamente dos y >= 2 al menos dos. Use find() en lugar de children() solo para todos los descendientes anidados.

Definir qué cuenta cada padre

.filter() conserva miembros de la selección que superan la prueba. this es el padre DOM examinado; true lo conserva. El resultado contiene padres, no hijos, y no elimina otros del documento.

.children(".item") cuenta coincidencias un nivel por debajo. Sin selector, títulos y otros hijos elemento también cuentan. Texto y comentarios quedan fuera en ambos casos.

.find(".item") incluye descendientes más profundos. Beta tiene un hijo directo con dos dentro: tres descendientes. Los ocultos siguen en el DOM y cuentan. Solo visibles requiere una regla explícita adicional.

Ejemplo completo: exacto o mínimo

Guarde todo en UTF-8 como child-count-demo.html y ábralo en el navegador. Internet carga el archivo oficial de jQuery 4.0.0. No requiere servidor ni paquetes. Condiciones, reinicio y añadidos se ejecutaron en Edge para Windows con 3.7.1 y 4.0.0.

Recuentos directos: Alpha 2, Beta 1, Gamma 0 y Delta 3. D3 oculto cuenta; títulos y texto no son .item. Los candidatos son solo .group directos de #board, sin objetos interiores.

<!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>

Inicialmente solo se resalta Alpha, con exactamente dos hijos.

scope=direct; rule=exact; count=2
selected=alpha
alpha=2; beta=1; gamma=0; delta=3
  1. At least selecciona Alpha y Delta. Exactamente dos difiere de al menos dos.
  2. All descendants añade Beta: pasa de uno a tres.
  3. Exactly 3 selecciona Beta y Delta por descendientes, pero solo Delta por Direct children.
  4. Exactly 0 elige Gamma; At least 0 permite cero y elige los cuatro.
  5. Tras Reset, Add direct item to Alpha la lleva a tres; exactamente dos da (none). No se elimina ningún padre.
  6. Vacío, negativo o fraccionario muestra error y quita resaltados. Reset restaura estructura y condiciones.

Total conjunto y prueba por padre

Pulse Reset y ejecute en herramientas de desarrollo para comparar la colección conjunta con el filtro individual:

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(","));

Seis es 2+1+0+3 combinado, sin decir qué padre tiene dos. La segunda línea examina cada padre y devuelve Gamma sin .item directo.

6
gamma

Una función normal recibe this como padre actual. Cambiar a flecha manteniendo this rompe esa premisa. Con flecha, reciba explícitamente el segundo argumento elemento del callback.

Una colección filtrada guardada no reevalúa continuamente la condición. Tras añadir, el resultado antiguo puede conservar Alpha. El ejemplo llama apply() con candidatos y números actuales. .filter() no observa cambios del DOM.

Alcance y errores habituales

Defina todos los elementos o tarjetas, li o controles específicos. Aquí solo cuenta .item: Gamma es cero pese a h2 y texto. No significa contenedor totalmente vacío. Añada explícitamente visibilidad, disponibilidad o estado de datos cuando haga falta.

Si solo importa que exista un descendiente, .has() puede ser más sencillo. Cantidades exactas y mínimas se expresan claramente con números. No confunda nth-child, posición entre hermanos, con cantidad total del padre.

Con miles de padres y búsquedas profundas repetidas, limite candidatos y cálculos innecesarios. Pero no reutilice resultados antiguos tras cambios estructurales. Compruebe estado inicial y actualizado.