jQuery / Elemente ohne ein bestimmtes Kind auswählen

Für Eltern ohne passenden Nachfahren entfernen Sie die .has(".badge")-Menge mit .not() aus den Kandidaten. Für direkte Kinder prüfen Sie pro Elternteil $(this).children(".badge").length === 0. Das Fehlen eines bestimmten Kindes bedeutet nicht, dass der Container vollständig leer ist.

Ziel und Tiefe festlegen

.has(".badge") behält Kandidaten mit passendem Nachfahren beliebiger Tiefe. Entfernen mit .not() lässt Kandidaten ohne badge im Inneren übrig.

.children(".badge") prüft nur eine Ebene. Ein verschachteltes badge besteht „kein direktes“, aber nicht „kein Nachfahre“. Versteckte Elemente sind im DOM, Document Object Model, weiterhin vorhanden.

Elternteil meint hier den Kandidatencontainer, nicht .parent() zum Aufwärtsgehen. Begrenzen Sie Kandidaten gegen unbeteiligte Vorfahren und innere Objekte.

Vollständiges Tiefen- und Inhaltsbeispiel

Speichern Sie alles als UTF-8 missing-child-demo.html und öffnen Sie es im Browser. Internet lädt die offizielle jQuery-Datei 4.0.0. Server oder Installation sind unnötig. Anfang, Ergänzung, Verschieben und Reset wurden in Windows Edge mit 3.7.1 und 4.0.0 ausgeführt.

a hat ein direktes badge, b ein verschachteltes, c ein anderes Kind und d nur Text. e ist leer, f nur Kommentar, g ein Leerzeichen und h ein verstecktes badge. Namen wie a: sind CSS-generiert, kein DOM-Inhalt. Andere innere Leerzeichen können :empty verändern.

<!doctype html>
<html lang="en">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Select containers without a badge</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; }
    button { font: inherit; margin: 4px; }
    .card { border: 2px solid #aaa; margin: 10px 0; padding: 10px; min-height: 28px; }
    .card::before { content: attr(id) ": "; font-weight: bold; }
    .card.chosen { border-color: teal; background: #e4f6f0; }
    .badge { border: 1px solid #777; padding: 2px; }
    .badge[hidden] { display: none; }
    #result { white-space: pre-wrap; overflow-wrap: anywhere; }
  </style>
</head>
<body>
  <p>Green cards have no .badge descendant. Names are CSS-generated labels.</p>
  <button type="button" id="add">Add badge to c</button>
  <button type="button" id="move">Move b badge to direct child</button>
  <button type="button" id="reset">Reset</button>
  <div id="cards">
    <div class="card" id="a">Direct <span class="badge">A</span></div>
    <div class="card" id="b"><div class="wrapper">Nested <span class="badge">B</span></div></div>
    <div class="card" id="c"><span class="note">Other element</span></div>
    <div class="card" id="d">Text only</div>
    <div class="card" id="e"></div>
    <div class="card" id="f"><!-- comment only --></div>
    <div class="card" id="g"> </div>
    <div class="card" id="h"><span class="badge" hidden>Hidden</span></div>
  </div>
  <pre id="result" aria-live="polite"></pre>
  <script>
    $(function () {
      const initial = $("#cards").html();
      function ids($set) { return $set.map(function () { return this.id; }).get().join(",") || "(none)"; }
      function report() {
        const $cards = $("#cards > .card");
        const $withoutDescendant = $cards.not($cards.has(".badge"));
        const $withoutDirect = $cards.filter(function () { return $(this).children(".badge").length === 0; });
        const $empty = $cards.filter(":empty");
        const $withoutElements = $cards.filter(function () { return $(this).children().length === 0; });
        $cards.removeClass("chosen"); $withoutDescendant.addClass("chosen");
        $("#result").text([
          "no descendant badge=" + ids($withoutDescendant),
          "no direct badge=" + ids($withoutDirect),
          "empty=" + ids($empty),
          "no element children=" + ids($withoutElements)
        ].join("\n"));
      }
      $("#add").on("click", function () {
        $("<span>").addClass("badge").text("New").appendTo("#c");
        $(this).prop("disabled", true); report();
      });
      $("#move").on("click", function () {
        $("#b .badge").appendTo("#b"); $(this).prop("disabled", true); report();
      });
      $("#reset").on("click", function () {
        $("#cards").html(initial); $("#add, #move").prop("disabled", false); report();
      });
      report();
    });
  </script>
</body>
</html>

Anfangsausgabe. Grün markiert nur die erste Menge ohne badge-Nachfahren. Weitere Zeilen vergleichen andere Bedingungen.

no descendant badge=c,d,e,f,g
no direct badge=b,c,d,e,f,g
empty=e,f
no element children=d,e,f,g
  1. c,d,e,f,g haben nirgends badge. c gehört trotz note-Element dazu.
  2. Die zweite Zeile enthält b, dessen badge im Wrapper statt direkt liegt.
  3. Add badge to c ergibt zuerst d,e,f,g, dann b,d,e,f,g.
  4. Move b badge to direct child ergibt auch zweitens d,e,f,g. Derselbe Knoten wird verschoben; nur die direkte Beziehung ändert sich.
  5. Reset stellt acht Container und Struktur wieder her. Verstecktes h bleibt vorhanden.

Kandidatenausschluss und leer unterscheiden

Nach Reset in Entwicklertools ausführen. Die erste Prüfung betrifft Kandidaten selbst, die zweite deren Inhalt:

const $cards = $("#cards > .card");
console.log($cards.not(".badge").map(function () { return this.id; }).get().join(","));
console.log($cards.filter(function () {
  return $(this).find(".badge").length === 0;
}).map(function () { return this.id; }).get().join(","));

Keiner der acht cards ist selbst .badge, daher zunächst alle. Die zweite Prüfung liefert das gesuchte c,d,e,f,g.

a,b,c,d,e,f,g,h
c,d,e,f,g

:empty bedeutet nicht das Fehlen einer bestimmten Art, sondern keine Element- oder Textkinder. e und Kommentar-f passen. Kommentare verhindern es nicht, Leerraum in g dagegen schon; auch Text in d verhindert es.

.children().length === 0 meint keine direkten Elementkinder: d,e,f,g. Text und Leerraum zählen nicht. CSS-::before-Namen sind keine DOM-Kinder und ändern e/f nicht. Sichtbarkeit und Knotenstruktur sind getrennte Fragen.

Änderungen und praktische Anwendung

Gespeicherte Ergebnisse aktualisieren sich nicht nach einem neuen badge. Hier ruft Ergänzen und Verschieben report() mit aktuellen Kandidaten auf. Verbinden Sie Neubewertung auch mit Änderungen aus anderem Code.

.not() und .filter() erstellen Mengen und löschen andere cards nicht. Klassen oder Leerzustandsmeldungen sind eigene Aktionen. Hier verschwinden alte Markierungen vor dem Setzen neuer.

Klären Sie Tag oder Klasse, direkt oder alle Nachfahren sowie versteckte und deaktivierte Ziele. Prüfen Sie Anfang und Änderung statt ähnlich aussehende Selektoren blind auszutauschen. Begrenzen Sie Kandidaten bei tiefen wiederholten Suchen.