HTML で同じ名前のラジオボタンの別グループを作る
- カテゴリ:
- JavaScript
- コメント数:
- Comments: 0
◆ 基本は name 属性が同じものが同じグループ
◆ fieldset を分けても同じ name 属性だと同じグループ扱いになる
◆ form を分けると別グループにできる
◆ ラジオボタンのグループのために form をわけると form の機能で submit する使い方だと辛い時もある
◆ 同じ名前で fieldset ごとに別グループにしたいというときは 繰り返しになるということだと思うのでコンポーネント化して ShadowDOM を使えばいい
◆ ShadowDOM ごとに別グループになる
◆ fieldset を分けても同じ name 属性だと同じグループ扱いになる
◆ form を分けると別グループにできる
◆ ラジオボタンのグループのために form をわけると form の機能で submit する使い方だと辛い時もある
◆ 同じ名前で fieldset ごとに別グループにしたいというときは 繰り返しになるということだと思うのでコンポーネント化して ShadowDOM を使えばいい
◆ ShadowDOM ごとに別グループになる
HTML ではラジオボタンのグループをつくるときに name 属性を指定します
同じ名前のものが同じグループです
こういう感じなのですが この section が複数あって選択項目も同じなのでコピペしたいときがあります
そのときに name は変えないようにしたいです
フォームをグループ化するためには fieldset というタグがあるので 使ってみましたが
これの上のグループとしたのグループが別々になりません
上側をチェックしたら下側のチェックが外れます
同じグループ扱いになってます
MDN 探してみるとラジオボタンのグループらしいタグがあったのですが Firefox 拡張機能用みたいで HTML では動きません
内部で name 管理して内側のグループ化する CustomElement でも作って対処しようと考えていたのですが form タグを使えば別々にできました
これなら上下両方にチェックを付けられます
私の場合基本 JavaScript で処理してそのまま送信するので form タグは基本使いません
なので グループ化のためだけに form 使うのは特に抵抗ないのですが form タグをちゃんと使ってる場合はネストすることになります
そう考えると name 変えずにグループ化したいなら CustomElement 作っておくほうがいいかもしれません
使うときはこういう感じで name すらなくて radio-item にラベルのテキストを入れるだけです
MutationObserver が付いてるのでラベル名の切り替えや radio-item の追加削除に連動して ラジオボタンも変わります
DEMO
もともとは radio-item ごとに ShadowDOM をアタッチしてそれぞれに input タグを入れていました
すると同じ name なのに全部に同時にチェックをつけることができてしまいました
その対処で radio-item に ShadowDOM をアタッチしないようにする作りにしたのですが 単純に 上で書いた例で言う section 単位でコンポーネント化してしまえば特に何もしなくてよかった気がします
コピペで繰り返す form パーツのセットならコンポーネント化してしまうのはありですし そうすれば普通の input タグで radio を指定するだけで済みます
同じ名前のものが同じグループです
<section>
<div>
<label><input type="radio" name="xx"> a</label>
<label><input type="radio" name="xx"> b</label>
</div>
<div>
<label><input type="radio" name="yy"> c</label>
<label><input type="radio" name="yy"> d</label>
<label><input type="radio" name="yy"> e</label>
</div>
</section>
こういう感じなのですが この section が複数あって選択項目も同じなのでコピペしたいときがあります
そのときに name は変えないようにしたいです
フォームをグループ化するためには fieldset というタグがあるので 使ってみましたが
<fieldset>
<input type="radio" name="r">
<input type="radio" name="r">
<input type="radio" name="r">
</fieldset>
<fieldset>
<input type="radio" name="r">
<input type="radio" name="r">
<input type="radio" name="r">
</fieldset>
これの上のグループとしたのグループが別々になりません
上側をチェックしたら下側のチェックが外れます
同じグループ扱いになってます
MDN 探してみるとラジオボタンのグループらしいタグがあったのですが Firefox 拡張機能用みたいで HTML では動きません
内部で name 管理して内側のグループ化する CustomElement でも作って対処しようと考えていたのですが form タグを使えば別々にできました
<form>
<input type="radio" name="r">
<input type="radio" name="r">
<input type="radio" name="r">
</form>
<form>
<input type="radio" name="r">
<input type="radio" name="r">
<input type="radio" name="r">
</form>
これなら上下両方にチェックを付けられます
私の場合基本 JavaScript で処理してそのまま送信するので form タグは基本使いません
なので グループ化のためだけに form 使うのは特に抵抗ないのですが form タグをちゃんと使ってる場合はネストすることになります
そう考えると name 変えずにグループ化したいなら CustomElement 作っておくほうがいいかもしれません
radio-group
form でできると気づく前に一応作ってたのはこんなのですcustomElements.define(
"radio-item",
class extends HTMLElement {
constructor() {
super()
new MutationObserver(mutations => {
for (const mu of mutations) {
this.dispatchEvent(new Event("mutated", { bubbles: true }))
}
}).observe(this, {
subtree: true,
childList: true,
attributes: true,
attributeFilter: ["value"],
characterData: true,
})
}
get value() {
return this.getAttribute("value") || this.textContent
}
set value(value) {
this.setAttribute("value", value)
}
}
)
customElements.define(
"radio-group",
class extends HTMLElement {
constructor() {
super()
this.attachShadow({ mode: "open" }).innerHTML = `
<style></style>
<div id="radios"></div>
`
const radios = this.shadowRoot.querySelector("#radios")
const wmap = new WeakMap()
const prevRi = from => {
const prev = from.previousElementSibling
if (!prev) return null
if (prev.localName === "radio-item") return prev
return prevRi(prev)
}
const add = elem => {
const label = this._create(elem.textContent, elem.value)
const prev_ri = prevRi(elem)
if (prev_ri) {
const prev_label = wmap.get(prev_ri)
if (prev_label) {
prev_label.after(label)
} else {
radios.append(label)
}
} else {
radios.prepend(label)
}
wmap.set(elem, label)
}
const remove = elem => {
const label = wmap.get(elem)
if (label) {
label.remove()
wmap.delete(elem)
}
}
const change = elem => {
const label = wmap.get(elem)
if (label) {
const new_label = this._create(elem.textContent, elem.value, !!label.querySelector(":checked"))
label.replaceWith(new_label)
wmap.set(elem, new_label)
}
}
for (const elem of this.children) {
if (elem.localName === "radio-item") {
add(elem)
}
}
new MutationObserver(mutations => {
for (const mu of mutations) {
for (const elem of mu.addedNodes) {
if (elem.localName === "radio-item") {
add(elem)
}
}
for (const elem of mu.removedNodes) {
if (elem.localName === "radio-item") {
remove(elem)
}
}
}
}).observe(this, {
childList: true,
})
this.addEventListener("mutated", eve => {
if (eve.target.localName === "radio-item") {
change(eve.target)
}
})
}
_create(text, value, checked) {
const label = document.createElement("label")
label.innerHTML = `
<input type="radio" name="name" value="${value}" ${checked ? "checked" : ""}>
${text}
`
return label
}
get value() {
const elem = this.shadowRoot.querySelector(":checked")
return elem ? elem.value : null
}
set value(value) {
for (const checkbox of this.shadowRoot.querySelectorAll("input")) {
if (checkbox.value === value) {
checkbox.checked = true
return true
}
}
return false
}
}
)
使うときはこういう感じで name すらなくて radio-item にラベルのテキストを入れるだけです
<radio-group>
<radio-item>one</radio-item>
<radio-item>two</radio-item>
<radio-item>three</radio-item>
</radio-group>
<radio-group>
<radio-item>one</radio-item>
<radio-item>two</radio-item>
<radio-item>three</radio-item>
</radio-group>
MutationObserver が付いてるのでラベル名の切り替えや radio-item の追加削除に連動して ラジオボタンも変わります
DEMO
ShadowDOM でグループ化できた
作っていて気づいたのですが ShadowDOM で切り離されるとグループも別になりますもともとは radio-item ごとに ShadowDOM をアタッチしてそれぞれに input タグを入れていました
すると同じ name なのに全部に同時にチェックをつけることができてしまいました
その対処で radio-item に ShadowDOM をアタッチしないようにする作りにしたのですが 単純に 上で書いた例で言う section 単位でコンポーネント化してしまえば特に何もしなくてよかった気がします
コピペで繰り返す form パーツのセットならコンポーネント化してしまうのはありですし そうすれば普通の input タグで radio を指定するだけで済みます