From 4f10a4fcf8db0c1b0e580e98c18df04c91526995 Mon Sep 17 00:00:00 2001 From: barbie-bot Date: Sat, 4 Apr 2026 12:45:04 +0000 Subject: [PATCH] Replace fetch() calls with Turbo form submission via requestSubmit() MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit setDefaultUnit and drop previously made raw fetch() requests and called Turbo.renderStreamMessage() manually. Now both create a temporary
, 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 --- CLAUDE.md | 22 +++++++++++++ app/javascript/application.js | 61 ++++++++++++++++------------------- 2 files changed, 49 insertions(+), 34 deletions(-) diff --git a/CLAUDE.md b/CLAUDE.md index bf935f0..c6042cd 100644 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -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: diff --git a/app/javascript/application.js b/app/javascript/application.js index 81be3a4..68bb153 100644 --- a/app/javascript/application.js +++ b/app/javascript/application.js @@ -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