Compare commits

..

1 Commits

Author SHA1 Message Date
169e84fe8a Replace render_turbo_stream cancel handlers with form#cancel Stimulus action
Add cancel() method to form_controller that handles all close cases:
- tabular forms (<tr>): removes inner form, re-enables link, shows hidden row
- measurements create form: removes self, enables trigger link, shows no-items

Data attributes already present on controller elements (data-form, data-link,
data-hidden-row) drive the behaviour. Two extra attributes on the create form
(data-cancel-enable, data-cancel-show) cover the non-tabular case.

Delete now-unused _form_close and _edit_form_close partial templates.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-04 14:14:44 +00:00
15 changed files with 52 additions and 39 deletions

View File

@@ -2,7 +2,6 @@
// https://github.com/rails/importmap-rails // https://github.com/rails/importmap-rails
import "@hotwired/turbo-rails" import "@hotwired/turbo-rails"
import "controllers" import "controllers"
import { disableElement, enableElement } from "element_helpers"
/* Hide page before loaded for testing purposes */ /* Hide page before loaded for testing purposes */
@@ -13,13 +12,23 @@ document.addEventListener('turbo:load', showPage)
/* Turbo stream actions */ /* Turbo stream actions */
Turbo.StreamElement.prototype.disableElement = disableElement Turbo.StreamElement.prototype.disableElement = function(element) {
Turbo.StreamElement.prototype.enableElement = enableElement element.setAttribute("disabled", "disabled")
element.setAttribute("aria-disabled", "true")
element.setAttribute("tabindex", "-1")
}
Turbo.StreamActions.disable = function() { Turbo.StreamActions.disable = function() {
this.targetElements.forEach(disableElement) this.targetElements.forEach((e) => { this.disableElement(e) })
}
Turbo.StreamElement.prototype.enableElement = function(element) {
element.removeAttribute("disabled")
element.removeAttribute("aria-disabled")
// Assume 'tabindex' is not used explicitly, so removing it is safe
element.removeAttribute("tabindex")
} }
Turbo.StreamActions.enable = function() { Turbo.StreamActions.enable = function() {
this.targetElements.forEach(enableElement) this.targetElements.forEach((e) => { this.enableElement(e) })
} }
/* TODO: change to visibility = collapse to avoid width change? */ /* TODO: change to visibility = collapse to avoid width change? */

View File

@@ -1,5 +1,4 @@
import { Controller } from "@hotwired/stimulus" import { Controller } from "@hotwired/stimulus"
import { disableElement, enableElement } from "element_helpers"
export default class extends Controller { export default class extends Controller {
static targets = ["countLabel", "submitButton", "list"] static targets = ["countLabel", "submitButton", "list"]
@@ -19,10 +18,10 @@ export default class extends Controller {
const count = this.element.querySelectorAll('input:checked:not([disabled])').length const count = this.element.querySelectorAll('input:checked:not([disabled])').length
if (count > 0) { if (count > 0) {
this.countLabelTarget.textContent = count + ' selected' this.countLabelTarget.textContent = count + ' selected'
enableElement(this.submitButtonTarget) Turbo.StreamElement.prototype.enableElement(this.submitButtonTarget)
} else { } else {
this.countLabelTarget.textContent = this.countLabelTarget.dataset.prompt this.countLabelTarget.textContent = this.countLabelTarget.dataset.prompt
disableElement(this.submitButtonTarget) Turbo.StreamElement.prototype.disableElement(this.submitButtonTarget)
} }
} }

View File

@@ -22,4 +22,30 @@ export default class extends Controller {
event.preventDefault() event.preventDefault()
} }
} }
cancel(event) {
event.preventDefault()
const el = this.element
// Move focus to next open form if this one had focus
const focused = document.activeElement
if (!focused || focused === document.body || el.contains(focused)) {
const next = el.parentElement?.querySelector(`#${el.id} ~ tr:has([autofocus])`)
?? el.parentElement?.querySelector("tr:has([autofocus])")
next?.querySelector("[autofocus]")?.focus()
}
// Remove associated inner form element (tabular: <tr> wraps a separate <form>)
document.getElementById(el.dataset.form)?.remove()
// Re-enable trigger link
const enableId = el.dataset.link ?? el.dataset.cancelEnable
if (enableId) Turbo.StreamElement.prototype.enableElement(document.getElementById(enableId))
// Show hidden row (tabular forms) or no-items placeholder (create form)
document.getElementById(el.dataset.hiddenRow)?.removeAttribute("style")
document.getElementById(el.dataset.cancelShow)?.style.removeProperty("display")
el.remove()
}
} }

View File

@@ -1,14 +1,13 @@
import { Controller } from "@hotwired/stimulus" import { Controller } from "@hotwired/stimulus"
import { disableElement, enableElement } from "element_helpers"
export default class extends Controller { export default class extends Controller {
static targets = ["select", "button"] static targets = ["select", "button"]
unitChanged() { unitChanged() {
if (this.selectTarget.value && this.selectTarget.value !== this.selectTarget.dataset.defaultUnitId) { if (this.selectTarget.value && this.selectTarget.value !== this.selectTarget.dataset.defaultUnitId) {
enableElement(this.buttonTarget) Turbo.StreamElement.prototype.enableElement(this.buttonTarget)
} else { } else {
disableElement(this.buttonTarget) Turbo.StreamElement.prototype.disableElement(this.buttonTarget)
} }
} }

View File

@@ -1,11 +0,0 @@
export function disableElement(element) {
element.setAttribute("disabled", "disabled")
element.setAttribute("aria-disabled", "true")
element.setAttribute("tabindex", "-1")
}
export function enableElement(element) {
element.removeAttribute("disabled")
element.removeAttribute("aria-disabled")
element.removeAttribute("tabindex")
}

View File

@@ -19,7 +19,7 @@
<%= form.button %> <%= form.button %>
<%= image_link_to t(:cancel), "close-outline", measurements_path, <%= image_link_to t(:cancel), "close-outline", measurements_path,
class: 'dangerous', name: :cancel, class: 'dangerous', name: :cancel,
onclick: render_turbo_stream('edit_form_close', {row: row}) %> data: {action: 'click->form#cancel'} %>
</td> </td>
<% end %> <% end %>
<% end %> <% end %>

View File

@@ -1,2 +0,0 @@
<%= turbo_stream.close_form row %>
<%= turbo_stream.update :flashes %>

View File

@@ -26,7 +26,7 @@
<%= form.button %> <%= form.button %>
<%= image_link_to t(:cancel), "close-outline", measurements_path, <%= image_link_to t(:cancel), "close-outline", measurements_path,
class: 'dangerous', name: :cancel, class: 'dangerous', name: :cancel,
onclick: render_turbo_stream('edit_form_close', {row: row}) %> data: {action: 'click->form#cancel'} %>
</td> </td>
<% end %> <% end %>
</tbody> </tbody>

View File

@@ -1,6 +1,8 @@
<%= tabular_form_with model: Measurement.new, id: :measurement_form, <%= tabular_form_with model: Measurement.new, id: :measurement_form,
class: 'topside-area flex vertical center', class: 'topside-area flex vertical center',
html: {data: {controller: 'form', action: 'keydown->form#processKey'}} do |form| %> html: {data: {controller: 'form', action: 'keydown->form#processKey',
cancel_enable: 'new_measurement_link',
cancel_show: 'no_items'}} do |form| %>
<table class="items-table center"> <table class="items-table center">
<tbody id="readouts"> <tbody id="readouts">
@@ -34,6 +36,6 @@
<div class="flex reverse"> <div class="flex reverse">
<%= form.button id: :create_measurement_button, disabled: true -%> <%= form.button id: :create_measurement_button, disabled: true -%>
<%= image_link_to t(:cancel), "close-outline", measurements_path, name: :cancel, <%= image_link_to t(:cancel), "close-outline", measurements_path, name: :cancel,
class: 'dangerous', onclick: render_turbo_stream('form_close') %> class: 'dangerous', data: {action: 'click->form#cancel'} %>
</div> </div>
<% end %> <% end %>

View File

@@ -1,4 +0,0 @@
<%= turbo_stream.update :flashes %>
<%= turbo_stream.remove :measurement_form %>
<%= turbo_stream.show :no_items -%>
<%= turbo_stream.enable :new_measurement_link -%>

View File

@@ -18,7 +18,7 @@
<td class="flex"> <td class="flex">
<%= form.button %> <%= form.button %>
<%= image_link_to t(:cancel), "close-outline", quantities_path, class: 'dangerous', <%= image_link_to t(:cancel), "close-outline", quantities_path, class: 'dangerous',
name: :cancel, onclick: render_turbo_stream('form_close', {row: row}) %> name: :cancel, data: {action: 'click->form#cancel'} %>
</td> </td>
<td></td> <td></td>
<% end %> <% end %>

View File

@@ -1,2 +0,0 @@
<%= turbo_stream.close_form row %>
<%= turbo_stream.update :flashes %>

View File

@@ -16,7 +16,7 @@
<td class="flex"> <td class="flex">
<%= form.button %> <%= form.button %>
<%= image_link_to t(:cancel), "close-outline", units_path, class: 'dangerous', <%= image_link_to t(:cancel), "close-outline", units_path, class: 'dangerous',
name: :cancel, onclick: render_turbo_stream('form_close', {row: row}) %> name: :cancel, data: {action: 'click->form#cancel'} %>
</td> </td>
<td></td> <td></td>
<% end %> <% end %>

View File

@@ -1,2 +0,0 @@
<%= turbo_stream.close_form row %>
<%= turbo_stream.update :flashes %>

View File

@@ -1,7 +1,6 @@
# Pin npm packages by running ./bin/importmap # Pin npm packages by running ./bin/importmap
pin "application", preload: true pin "application", preload: true
pin "element_helpers"
pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true pin "@hotwired/turbo-rails", to: "turbo.min.js", preload: true
pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true pin "@hotwired/stimulus", to: "stimulus.min.js", preload: true
pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true pin "@hotwired/stimulus-loading", to: "stimulus-loading.js", preload: true