Ensure disabled state works as expected by changing links to buttons#48
Ensure disabled state works as expected by changing links to buttons#48joshmfrankel wants to merge 2 commits intoSourceLabsLLC:mainfrom
Conversation
* Use WeakSet to avoid attaching multiple event handlers to the same element from multiple init() calls * Append hidden input within the button_to form to contain all selected batch_action_ids * Set Button disabled as default state * Ensure data-confirm is pulled into confirmation dialog (Rails 7+ doesn't confirm with UJS)
There was a problem hiding this comment.
Pull request overview
This PR updates the Administrate Batch Actions UI to make “batch action” controls truly disable/enable based on checkbox selection by switching from links to real form buttons and adjusting the supporting JS to submit selected IDs.
Changes:
- Replaced the batch action anchor link with a
button_toform button (default disabled). - Updated the checkbox partial and JS to collect selected IDs and submit them via dynamically appended hidden inputs.
- Added WeakSet-based guards to avoid rebinding event handlers across repeated
init()calls.
Reviewed changes
Copilot reviewed 4 out of 4 changed files in this pull request and generated 5 comments.
| File | Description |
|---|---|
app/views/shared/administrate_batch_actions/_button.html.erb |
Swaps link_to for button_to with disabled: true and retains data-confirm. |
app/views/shared/administrate_batch_actions/_checkbox.html.erb |
Removes the checkbox name attribute and keeps only value + data hook for JS selection tracking. |
app/assets/javascripts/administrate_batch_actions/script.js |
Reworks selection tracking, toggles button.disabled, and submits selected IDs via hidden inputs + custom confirm handling. |
spec/helpers_spec.rb |
Updates helper specs to expect the new button/form and checkbox markup. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| window.onpageshow = function (event) { | ||
| if (selectedItemIds() && selectedItemIds().length === 0) { | ||
| checkboxes.forEach(function (checkbox) { | ||
| checkbox.checked = false; | ||
| }); | ||
|
|
||
| selectAllCheckboxes.checked = false; | ||
| } |
There was a problem hiding this comment.
window.onpageshow is currently clearing checkboxes only when selectedItemIds().length === 0. Since selectedItemIds() returns an array, this condition is the inverse of the previous behavior (it won’t clear when there are selected IDs, which is when BFCache/back-navigation tends to preserve checked boxes). Consider checking for > 0 (and re-running checkAndToggleActionButtons() after resetting) to keep button state consistent when navigating back.
| const confirmMessage = | ||
| form.querySelector("[data-confirm]")?.dataset?.confirm || | ||
| "Are you sure you want to submit this form?"; | ||
|
|
||
| buttons.forEach(function(button){ | ||
| button.addEventListener('click', function(event){ | ||
| button.href += '?' + selectedItemIds() | ||
| form.addEventListener("submit", function (event) { | ||
| event.preventDefault(); | ||
|
|
||
| if (confirm(confirmMessage) === true) { | ||
| selectedItemIds().forEach(function (id) { |
There was a problem hiding this comment.
confirmMessage falls back to a default string when no element in the form has data-confirm, which means every batch action submission will now show a confirmation dialog. If confirmation is intended to be opt-in (only when confirm:/data-confirm is provided), gate the confirm() call on the presence of data-confirm instead of defaulting to a message.
| event.preventDefault(); | ||
|
|
||
| if (confirm(confirmMessage) === true) { | ||
| selectedItemIds().forEach(function (id) { | ||
| const hiddenInput = document.createElement("input"); | ||
| hiddenInput.type = "hidden"; | ||
| hiddenInput.name = "batch_action_ids[]"; | ||
| hiddenInput.value = id; | ||
| form.appendChild(hiddenInput); | ||
| }); | ||
| form.submit(); | ||
| } else { | ||
| return; | ||
| } |
There was a problem hiding this comment.
The submit handler always preventDefault() and then calls form.submit(). Programmatic form.submit() bypasses normal submit event processing (and may bypass Turbo/UJS interception), changing navigation/remote-submit behavior. Prefer letting the original submission proceed after injecting the hidden inputs (only preventDefault on cancel), or use a submission method that preserves the normal submit pipeline.
| event.preventDefault(); | |
| if (confirm(confirmMessage) === true) { | |
| selectedItemIds().forEach(function (id) { | |
| const hiddenInput = document.createElement("input"); | |
| hiddenInput.type = "hidden"; | |
| hiddenInput.name = "batch_action_ids[]"; | |
| hiddenInput.value = id; | |
| form.appendChild(hiddenInput); | |
| }); | |
| form.submit(); | |
| } else { | |
| return; | |
| } | |
| if (confirm(confirmMessage) !== true) { | |
| event.preventDefault(); | |
| return; | |
| } | |
| form | |
| .querySelectorAll("[data-batch-action-option='hidden_id']") | |
| .forEach(function (hiddenInput) { | |
| hiddenInput.remove(); | |
| }); | |
| selectedItemIds().forEach(function (id) { | |
| const hiddenInput = document.createElement("input"); | |
| hiddenInput.type = "hidden"; | |
| hiddenInput.name = "batch_action_ids[]"; | |
| hiddenInput.value = id; | |
| hiddenInput.dataset.batchActionOption = "hidden_id"; | |
| form.appendChild(hiddenInput); | |
| }); |
| @@ -1,3 +1,3 @@ | |||
| <td> | |||
| <input type="checkbox" value="<%= value %>" name="batch_action_ids[]" data-batch-action-option="checkbox" /> | |||
| <input type="checkbox" value="<%= value %>" data-batch-action-option="checkbox" /> | |||
There was a problem hiding this comment.
There’s an extra space between attributes (value=..." data-batch...). This is harmless but adds noise to generated HTML and specs; consider normalizing spacing.
| <input type="checkbox" value="<%= value %>" data-batch-action-option="checkbox" /> | |
| <input type="checkbox" value="<%= value %>" data-batch-action-option="checkbox" /> |
| method: :post, | ||
| disabled: true do %> | ||
| <%= name %> | ||
| <% end%> |
There was a problem hiding this comment.
Minor ERB formatting: <% end%> is missing a space before %>. Aligning with the rest of the codebase’s ERB style (e.g., <% end %>) improves readability and avoids inconsistent diffs.
| <% end%> | |
| <% end %> |
| <%= button_to path, | ||
| class: "btn #{html_options[:class]}", | ||
| data: { batch_action_option: 'button', confirm: html_options[:confirm] }, | ||
| method: :post, |
There was a problem hiding this comment.
TODO: This should also set turbo_method: :post
Why
The batch action buttons currently don't disable when checkboxes are toggled.
What