From 4b561ef4e3331ef1cb1cfbd1fbb3b4d9dd5825b4 Mon Sep 17 00:00:00 2001 From: Rick Barrette Date: Thu, 19 Mar 2026 12:44:27 -0400 Subject: [PATCH] added progressive row creation --- assets/javascripts/nested_form_controller.js | 101 ++++++++++++++++++- 1 file changed, 96 insertions(+), 5 deletions(-) diff --git a/assets/javascripts/nested_form_controller.js b/assets/javascripts/nested_form_controller.js index 221473f..c3709f3 100644 --- a/assets/javascripts/nested_form_controller.js +++ b/assets/javascripts/nested_form_controller.js @@ -1,3 +1,9 @@ +let lastKeyWasTab = false; + +document.addEventListener("keydown", function (e) { + lastKeyWasTab = (e.key === "Tab"); +}); + (function () { function initNestedForms() { document.querySelectorAll("[data-nested-form]").forEach(function (wrapper) { @@ -22,11 +28,28 @@ Date.now().toString() ); - //container.insertAdjacentHTML("beforeend", content); container.insertAdjacentHTML("beforeend", content); - // initialize autocomplete on the new row - initLineItemAutocomplete(container.lastElementChild); + const newRow = container.lastElementChild; + + // Ensure clean state + newRow.dataset.autoAdded = "false"; + + // Reset defaults + const qty = newRow.querySelector(".qty-field"); + if (qty && !qty.value) qty.value = 1; + + const price = newRow.querySelector(".price-field"); + if (price) price.value = ""; + + // initialize autocomplete + initLineItemAutocomplete(newRow); + + // Only focus if NOT tabbing + if (!lastKeyWasTab) { + const desc = newRow.querySelector(".line-item-description"); + if (desc) desc.focus(); + } } // REMOVE @@ -56,9 +79,77 @@ document.addEventListener("turbo:load", initNestedForms); })(); -$(document).on("input", ".line-item-description", function(){ +// Keep your existing behavior +$(document).on("input", ".line-item-description", function () { let row = $(this).closest(".line-item"); row.find(".item-id-field").val(""); +}); -}); \ No newline at end of file + +// ------------------------------- +// AUTO-ADD NEW ROW LOGIC +// ------------------------------- + +// Reset autoAdded flag if cleared +document.addEventListener("input", function (e) { + if (!e.target.classList.contains("line-item-description")) return; + + const row = e.target.closest(".line-item"); + if (!row) return; + + if (e.target.value.trim() === "") { + row.dataset.autoAdded = "false"; + } +}); + + +// Add row when leaving last description (without breaking TAB flow) +document.addEventListener("blur", function (e) { + if (!e.target.classList.contains("line-item-description")) return; + + const input = e.target; + const row = input.closest(".line-item"); + if (!row) return; + + const wrapper = input.closest("[data-nested-form]"); + if (!wrapper) return; + + const container = wrapper.querySelector("[data-nested-form-container]"); + if (!container) return; + + // Active (visible + not destroyed) rows only + const rows = Array.from( + container.querySelectorAll(wrapper.dataset.wrapperSelector) + ).filter(r => { + const destroy = r.querySelector("input[name*='[_destroy]']"); + const hidden = window.getComputedStyle(r).display === "none"; + return !(destroy && destroy.value === "1") && !hidden; + }); + + const lastRow = rows[rows.length - 1]; + + // Only last row + if (row !== lastRow) return; + + // Must have content + if (input.value.trim() === "") return; + + // Prevent duplicate firing + if (row.dataset.autoAdded === "true") return; + + // If TAB, ensure user is leaving the row entirely + if (lastKeyWasTab) { + const next = document.activeElement; + + if (row.contains(next)) { + return; // still inside row → allow normal tabbing + } + } + + row.dataset.autoAdded = "true"; + + const addButton = wrapper.querySelector("[data-nested-form-add]"); + if (addButton) addButton.click(); + +}, true); // capture phase required for blur \ No newline at end of file