jQuery / Escolher elementos sem um filho específico

Para escolher pais sem descendente correspondente, exclua candidatos .has(".badge") com .not(). Para filhos diretos, teste $(this).children(".badge").length === 0 por pai. Não ter um filho específico não significa estar completamente vazio.

Definir alvo ausente e profundidade

.has(".badge") mantém candidatos com descendente correspondente em qualquer profundidade. Remover com .not() deixa aqueles sem badge dentro.

.children(".badge") verifica um nível. Um badge aninhado atende «sem direto», mas não «sem descendente». Oculto continua existindo no DOM, Document Object Model.

Pai significa contêiner candidato, não .parent() para subir. Limite candidatos para não incluir ancestrais alheios ou objetos internos.

Exemplo completo de profundidade e conteúdo

Salve tudo em UTF-8 como missing-child-demo.html e abra no navegador. A Internet carrega o arquivo oficial do jQuery 4.0.0. Não exige servidor nem pacotes. Início, adição, movimento e reinício foram executados no Edge para Windows com 3.7.1 e 4.0.0.

a tem badge direto, b aninhado, c outro filho e d apenas texto. e é vazio, f tem comentário, g um espaço e h badge oculto. Nomes a: são CSS, não filhos DOM. Alterar espaços internos pode alterar :empty.

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

Saída inicial. Verde destaca somente o primeiro conjunto sem descendente badge; as outras linhas comparam condições diferentes.

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 não têm badge. c atende apesar do elemento note.
  2. A segunda linha acrescenta b: seu badge está dentro de um wrapper.
  3. Add badge to c resulta em d,e,f,g e depois b,d,e,f,g.
  4. Move b badge to direct child resulta também em d,e,f,g na segunda. O mesmo nó muda relação direta, não presença descendente.
  5. Reset restaura oito contêineres e estrutura. O badge oculto de h continua presente.

Exclusão do candidato e vazio

Após Reset, execute nas ferramentas de desenvolvimento. A primeira expressão examina os candidatos, a segunda seu interior:

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

Nenhuma das oito cards é .badge, portanto a primeira retorna todas. A segunda fornece c,d,e,f,g, o resultado desejado.

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

:empty significa sem filhos elemento ou texto, não sem um tipo específico. e e f com comentário atendem. Comentário não impede; o espaço de g sim, assim como o texto de d.

.children().length === 0 significa sem filhos elemento diretos: d,e,f,g. Texto e espaços não contam. Nomes CSS ::before não são filhos DOM e não alteram e/f. Aparência e estrutura são diferentes.

Mudanças e aplicação prática

Um resultado salvo não se atualiza após adicionar badge. Adição e movimento chamam report() com candidatos atuais. Conecte também a reavaliação a mudanças feitas por outro código.

.not() e .filter() criam seleções sem remover outras cards. Classes ou mensagens de estado vazio são ações separadas. Destaques antigos saem antes dos atuais.

Defina tag ou classe, profundidade direta ou completa e tratamento de ocultos ou desabilitados. Compare estrutura inicial e alterada sem trocar seletores parecidos às cegas. Limite candidatos em buscas profundas repetidas.