Replace fetch() calls with Turbo form submission via requestSubmit()

setDefaultUnit and drop previously made raw fetch() requests and called
Turbo.renderStreamMessage() manually. Now both create a temporary <form>,
append hidden inputs, and call form.requestSubmit() — Turbo intercepts
the submission natively, handling CSRF, stream responses, and lifecycle.

Also document this convention in CLAUDE.md.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
This commit is contained in:
2026-04-04 12:45:04 +00:00
parent 652f9c0f34
commit 4f10a4fcf8
2 changed files with 49 additions and 34 deletions

View File

@@ -77,6 +77,28 @@ default/ namespace for default units import/export and admin panel
root → /units (authenticated), /sign_in (unauthenticated)
```
## JavaScript Conventions
### No manual fetch() — use Turbo
Never make AJAX requests with `fetch()` in JavaScript. Use Turbo's built-in mechanisms instead:
- **Links/buttons that trigger server actions**: use `data: {turbo_stream: true}` on the element (link or button_to form).
- **Dynamic form submissions from JS** (where HTML alone isn't enough): create a form element, append hidden inputs, and call `form.requestSubmit()`. Turbo intercepts it automatically — no manual CSRF handling, no `Turbo.renderStreamMessage()`.
```javascript
var form = document.createElement('form');
form.action = url; form.method = 'post'; form.dataset.turboStream = 'true';
// append hidden inputs...
form.addEventListener('turbo:submit-end', function() { form.remove(); });
document.body.appendChild(form);
form.requestSubmit();
```
- **Server-rendered HTML**: use ERB partials and Turbo Stream views (`*.turbo_stream.erb`), never build HTML in JavaScript.
### No HTML generation in JavaScript
Never use JavaScript to build and insert HTML (no `innerHTML =`, no `createElement` trees for content). Render HTML server-side in ERB partials; update the DOM via Turbo Stream actions (`replace`, `update`, `append`, etc.).
## Database Requirements
The database must support:

View File

@@ -147,27 +147,25 @@ window.readoutUnitChanged = readoutUnitChanged
function setDefaultUnit(button) {
var select = button.closest('tr').querySelector('select[data-default-unit-id]');
var params = new URLSearchParams();
params.append('quantity[default_unit_id]', select.value);
fetch(button.dataset.path, {
body: params,
headers: {
'Accept': 'text/vnd.turbo-stream.html',
'X-CSRF-Token': document.head.querySelector('meta[name=csrf-token]').content,
'X-Requested-With': 'XMLHttpRequest'
},
method: 'PATCH'
})
.then(response => {
if (response.ok) {
var form = document.createElement('form');
form.action = button.dataset.path;
form.method = 'post';
form.dataset.turboStream = 'true';
var methodInput = document.createElement('input');
methodInput.type = 'hidden'; methodInput.name = '_method'; methodInput.value = 'patch';
var unitInput = document.createElement('input');
unitInput.type = 'hidden'; unitInput.name = 'quantity[default_unit_id]'; unitInput.value = select.value;
form.appendChild(methodInput);
form.appendChild(unitInput);
form.addEventListener('turbo:submit-end', function(event) {
if (event.detail.success) {
select.dataset.defaultUnitId = select.value;
readoutUnitChanged(select);
}
return response.text();
})
.then(html => Turbo.renderStreamMessage(html))
.catch(err => console.error('setDefaultUnit failed:', err));
form.remove();
});
document.body.appendChild(form);
form.requestSubmit();
}
window.setDefaultUnit = setDefaultUnit
@@ -340,22 +338,17 @@ window.dragEnd = dragEnd
function drop(event) {
event.preventDefault()
var params = new URLSearchParams()
var id_param = event.currentTarget.getAttribute("data-drop-id-param")
var idParam = event.currentTarget.getAttribute("data-drop-id-param")
var id = event.currentTarget.getAttribute("data-drop-id").split("_").pop()
params.append(id_param, id)
fetch(event.dataTransfer.getData("text/plain"), {
body: params,
headers: {
"Accept": "text/vnd.turbo-stream.html",
"X-CSRF-Token": document.head.querySelector("meta[name=csrf-token]").content,
"X-Requested-With": "XMLHttpRequest"
},
method: "POST"
})
.then(response => response.text())
.then(html => Turbo.renderStreamMessage(html))
var form = document.createElement('form');
form.action = event.dataTransfer.getData("text/plain");
form.method = 'post';
form.dataset.turboStream = 'true';
var input = document.createElement('input');
input.type = 'hidden'; input.name = idParam; input.value = id;
form.appendChild(input);
form.addEventListener('turbo:submit-end', function() { form.remove(); });
document.body.appendChild(form);
form.requestSubmit();
}
window.drop = drop