jQuery / 特定の子要素を持たない要素を選ぶ
特定の子孫がない親は、候補から.has(".badge")の集合を.not()で除外して選びます。直接子だけなら各親の$(this).children(".badge").length === 0を調べます。特定の子がないことと完全に空であることは別条件です。
対象と深さを決める
.has(".badge")は深さを問わず一致子孫のある候補を残します。それを.not()で除くと内部にbadgeがない候補になります。
.children(".badge")は一段だけです。中に入ったbadgeなら直接なしを通過しても子孫なしは通過しません。非表示でもDOM(Document Object Model)に存在すれば対象ありです。
親は検査対象コンテナーの意味で、.parent()で上に移動する操作ではありません。無関係な祖先や内部要素を混ぜないよう候補を限定します。
深さと内容を比べる完全な例
全コードをUTF-8のmissing-child-demo.htmlとして保存し開きます。インターネットで公式jQueryファイル4.0.0を読みます。サーバーや導入は不要です。初期結果、追加、移動、復元をWindows Edge、3.7.1・4.0.0で実行しました。
aは直接badge、bは中のbadge、cは別の子、dは文字だけです。eは空、fはコメントだけ、gは空白一個、hは非表示badgeです。a:などの名前はCSS生成でDOM内容ではありません。e・f・g内部の空白変更は: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>
初期結果です。緑は一行目の子孫badgeなしだけを強調し、他の行は別条件の比較です。
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
- c,d,e,f,gは全階層でbadgeなしです。cにはnote要素があっても含みます。
- 二行目はbも含みます。badgeはwrapper内で直接ではありません。
- Add badge to cで一行目d,e,f,g、二行目b,d,e,f,gです。
- Move b badge to direct childで二行目もd,e,f,gです。同じノードを動かし、子孫存在は同じで直接関係だけ変わります。
- Resetは八つと元構造を復元します。非表示hも存在として扱います。
候補除外と空を混同しない
Reset後に開発者ツールで実行します。最初は候補自身、次は内部を調べます。
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(","));
八つのcard自身にbadgeクラスはないので最初は全部です。次が目的のc,d,e,f,gです。
a,b,c,d,e,f,g,h
c,d,e,f,g
:emptyは特定種類の欠如ではなく、要素・文字の子がない条件です。eとコメントだけのfが該当します。コメントは妨げませんがgの空白文字は妨げ、dの文字も同じです。
.children().length === 0は直接子要素なしでd,e,f,gです。文字・空白は数えません。CSS ::beforeの名前もDOM子ではなくe・fの:emptyを変えません。外観とノード構造を分けて確認します。
動的変更と適用確認
結果を保存してもbadge追加で自動更新しません。例は追加・移動後report()で現在候補を再取得します。他コードの変更完了後にも再評価をつなぎます。
.not()・.filter()は集合を作り、他cardを削除しません。必要なclassや空状態表示は別操作です。例は古い強調を消して現在だけ付けます。
対象タグ・クラス、直接か全子孫か、非表示・無効も存在とするか決めます。似た選択子へ無条件に変えず初期と変更後を比較します。深い反復検索は候補を限定して減らします。




