Skip to content

Commit 3cfa9a8

Browse files
committed
Generate unique DOM ids for radio and checkbox groups
DOM#id now accepts optional suffixes so radio and checkbox components produce unique ids per value (e.g. gender_male, role_ids_1). This prevents duplicate ids in HTML and allows labels to target individual inputs with for=.
1 parent 338222f commit 3cfa9a8

5 files changed

Lines changed: 24 additions & 5 deletions

File tree

lib/superform/dom.rb

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,8 +16,8 @@ def value
1616
# Walks from the current node to the parent node, grabs the names, and seperates
1717
# them with a `_` for a DOM ID. One limitation of this approach is if multiple forms
1818
# exist on the same page, the ID may be duplicate.
19-
def id
20-
lineage.map(&:key).join("_")
19+
def id(*suffixes)
20+
(lineage.map(&:key) + suffixes).join("_")
2121
end
2222

2323
# The `name` attribute of a node, which is influenced by Rails (not sure where Rails got

lib/superform/rails/components/checkbox.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,7 @@ def field_attributes
2020
elsif collection?
2121
{ id: dom.id, name: dom.name, checked: true }
2222
else
23-
{ id: dom.id, name: dom.array_name, checked: Array(field.value).include?(@attributes[:value]) }
23+
{ id: dom.id(@attributes[:value]), name: dom.array_name, checked: Array(field.value).include?(@attributes[:value]) }
2424
end
2525
end
2626

lib/superform/rails/components/radio.rb

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@ def view_template(&)
1212
end
1313

1414
def field_attributes
15-
{ id: dom.id, name: dom.name, value: @value, checked: field.value == @value }
15+
{ id: dom.id(@value), name: dom.name, value: @value, checked: field.value == @value }
1616
end
1717
end
1818
end

spec/superform/rails/components/checkbox_spec.rb

Lines changed: 14 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,12 +75,26 @@
7575
expect(html).not_to match(/<input[^>]*checked[^>]*value="2"/)
7676
end
7777

78+
it 'renders unique ids per value' do
79+
all_roles = [[1, "Admin"], [2, "Editor"], [3, "Viewer"]]
80+
html = ""
81+
all_roles.each do |id, _name|
82+
html += render(described_class.new(field, value: id))
83+
end
84+
85+
expect(html).to include('id="role_ids_1"')
86+
expect(html).to include('id="role_ids_2"')
87+
expect(html).to include('id="role_ids_3"')
88+
end
89+
7890
it 'works through the field helper' do
7991
html = render(field.checkbox(value: 1))
92+
expect(html).to include('id="role_ids_1"')
8093
expect(html).to include('name="role_ids[]"')
8194
expect(html).to include('checked')
8295

8396
html = render(field.checkbox(value: 2))
97+
expect(html).to include('id="role_ids_2"')
8498
expect(html).to include('name="role_ids[]"')
8599
expect(html).not_to include('checked')
86100
end

spec/superform/rails/components/radio_spec.rb

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,13 +7,15 @@
77
it 'renders a checked radio when value matches' do
88
html = render(field.radio("male"))
99
expect(html).to include('type="radio"')
10+
expect(html).to include('id="gender_male"')
1011
expect(html).to include('name="gender"')
1112
expect(html).to include('value="male"')
1213
expect(html).to include('checked')
1314
end
1415

1516
it 'renders an unchecked radio when value does not match' do
1617
html = render(field.radio("female"))
18+
expect(html).to include('id="gender_female"')
1719
expect(html).to include('value="female"')
1820
expect(html).not_to include('checked')
1921
end
@@ -23,7 +25,7 @@
2325
expect(html).to include('class="radio-input"')
2426
end
2527

26-
it 'renders a group with correct checked state' do
28+
it 'renders a group with unique ids' do
2729
html = ""
2830
["male", "female", "other"].each do |value|
2931
html += render(field.radio(value))
@@ -32,6 +34,9 @@
3234
expect(html.scan(/type="radio"/).count).to eq(3)
3335
expect(html.scan(/name="gender"/).count).to eq(3)
3436
expect(html.scan(/checked/).count).to eq(1)
37+
expect(html).to include('id="gender_male"')
38+
expect(html).to include('id="gender_female"')
39+
expect(html).to include('id="gender_other"')
3540
expect(html).to match(/<input[^>]*value="male"[^>]*checked/)
3641
end
3742
end

0 commit comments

Comments
 (0)