Skip to content

Commit 302cfca

Browse files
rubysclaude
andcommitted
Add radio button deselection and move Answers button to summary page
- Add click-to-deselect for radio buttons in questions - Clicking selected radio button unselects it, returning to null state - Uses event delegation with data-wasChecked tracking - Works for both initial and dynamically loaded questions - Move Answers button from root page to summary page - Button now appears after Options section on summary page - Button text updated to "View Question Answers" - Updated tests to check summary page instead of root page - Update documentation to reflect UI changes 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
1 parent 14db3ac commit 302cfca

File tree

5 files changed

+51
-11
lines changed

5 files changed

+51
-11
lines changed

app/javascript/controllers/person_questions_controller.js

Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -11,6 +11,12 @@ export default class extends Controller {
1111
connect() {
1212
// Don't update on initial load - let the server-rendered content show
1313
// Only update when package or options change
14+
15+
// Set up radio button deselection behavior using event delegation
16+
this.element.addEventListener('click', this.handleRadioClick.bind(this))
17+
18+
// Track initial state of radio buttons
19+
this.markCheckedRadios()
1420
}
1521

1622
// Called when package dropdown changes
@@ -23,6 +29,35 @@ export default class extends Controller {
2329
this.updateQuestions()
2430
}
2531

32+
// Handle radio button clicks to allow deselection
33+
handleRadioClick(event) {
34+
const radio = event.target.closest('input[type="radio"]')
35+
if (!radio) return
36+
37+
// If this radio is already checked, uncheck it
38+
if (radio.checked && radio.dataset.wasChecked === 'true') {
39+
radio.checked = false
40+
delete radio.dataset.wasChecked
41+
} else {
42+
// Clear wasChecked from all radios in this group
43+
const name = radio.name
44+
this.element.querySelectorAll(`input[type="radio"][name="${name}"]`).forEach(r => {
45+
delete r.dataset.wasChecked
46+
})
47+
// Mark this one as checked
48+
if (radio.checked) {
49+
radio.dataset.wasChecked = 'true'
50+
}
51+
}
52+
}
53+
54+
// Mark currently checked radio buttons
55+
markCheckedRadios() {
56+
this.element.querySelectorAll('input[type="radio"]:checked').forEach(radio => {
57+
radio.dataset.wasChecked = 'true'
58+
})
59+
}
60+
2661
updateQuestions() {
2762
// Get selected package
2863
const packageId = this.hasPackageSelectTarget ?
@@ -59,6 +94,8 @@ export default class extends Controller {
5994
.then(response => response.text())
6095
.then(html => {
6196
this.questionsContainerTarget.innerHTML = html
97+
// Mark checked state for dynamically loaded radio buttons
98+
this.markCheckedRadios()
6299
})
63100
.catch(error => {
64101
console.error('Error fetching questions:', error)

app/views/docs/tasks/Questions.md

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ Two types of questions are supported:
2020
* **Radio Buttons** - For multiple choice questions (e.g., meal choice: beef, chicken, fish, vegetarian)
2121
* Enter each choice on a separate line
2222
* Participants can select one option
23+
* Click a selected radio button again to deselect it (return to "no answer")
2324

2425
* **Text Area** - For free-form text responses (e.g., dietary restrictions, special requests)
2526
* Participants can enter any text
@@ -52,7 +53,7 @@ Once participants have answered questions, you can view and export the results:
5253

5354
### Web Summary
5455

55-
* From the **main index page**, click the **Answers** button (only visible when questions exist)
56+
* From the **Summary** page, click the **View Question Answers** button (only visible when questions exist)
5657
* View all answers grouped by option and question
5758
* See which participants have answered and which haven't
5859
* Click on participant names to view their full profile

app/views/event/root.html.erb

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -88,10 +88,6 @@
8888
<% else %>
8989
<%= link_to "Settings", settings_event_index_path , class: "btn-grey" %>
9090
<% end %>
91-
92-
<% if Question.exists? %>
93-
<%= link_to "Answers", answers_path, class: "btn-pale-blue" %>
94-
<% end %>
9591
</div>
9692

9793
<p class="mt-4 text-right">

app/views/event/summary.html.erb

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,12 @@
4444
<% end %>
4545
</ul>
4646
<% end %>
47+
48+
<% if Question.exists? %>
49+
<div class="mt-6">
50+
<%= link_to "View Question Answers", answers_path, class: "btn-pale-blue" %>
51+
</div>
52+
<% end %>
4753
</div>
4854

4955
<div>

test/system/questions_test.rb

Lines changed: 6 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -170,20 +170,20 @@ class QuestionsTest < ApplicationSystemTestCase
170170
end
171171
end
172172

173-
test "should show Answers button on main index when questions exist" do
174-
visit root_url
173+
test "should show Answers button on summary page when questions exist" do
174+
visit summary_event_index_url
175175

176176
# Should show Answers button since we have questions in fixtures
177-
assert_link "Answers", href: answers_path
177+
assert_link "View Question Answers", href: answers_path
178178
end
179179

180-
test "should not show Answers button when no questions exist" do
180+
test "should not show Answers button on summary page when no questions exist" do
181181
Question.destroy_all
182182

183-
visit root_url
183+
visit summary_event_index_url
184184

185185
# Should not show Answers button
186-
assert_no_link "Answers", href: answers_path
186+
assert_no_link "View Question Answers", href: answers_path
187187
end
188188

189189
test "should display answer summary" do

0 commit comments

Comments
 (0)