The dynamically rendered inputs are not being pulled through to the post request?
Code is used to render the input fields based on the different expense types to the edit form
// Clear previous fields
dynamicFieldsContainer.innerHTML = '';
// Inject fields dynamically
for (const [key, value] of Object.entries(data.fields)) {
// Skip fields that are null or undefined
if (value === null || value === undefined) continue;
const field = `
<div class="mb-3">
<label for="${key}" class="form-label">${key.replace('_', ' ').toUpperCase()}</label>
<input type="text" class="form-control" id="${key}" name="${key}" value="${value}" required>
</div>`;
dynamicFieldsContainer.insertAdjacentHTML('beforeend', field);
}
These are the hidden fields that are already present on the form and are populated by the JS script these, are the only fields present in the POST request
// Set hidden ID field
document.getElementById('expense-id').value = data.id;
// Set hidden expense-type field
document.getElementById('expense-type').value = data.type;
The current html structure which shows the form and the post action as well as the hidden and dynamically render html tags
<form id="editExpenseForm" name="editExpenseForm" method="post" action="{% url 'edit_expense' %}">
{% csrf_token %}
<div class="modal-header">
<h5 class="modal-title" id="editExpenseModalLabel">Edit Expense</h5>
<button type="button" class="btn-close" data-bs-dismiss="modal" aria-label="Close"></button>
</div>
<div class="modal-body">
<input type="hidden" name="expense-id" id="expense-id">
<input type="hidden" name="expense-type" id="expense-type">
<div id="dynamic-fields">
<!-- Fields will be injected dynamically -->
</div>
</div>
<div class="modal-footer">
<button type="button" class="btn btn-secondary" data-bs-dismiss="modal">Cancel</button>
<button type="submit" class="btn btn-primary">Save Changes</button>
</div>
</form>
Your javascript
code just needed a little tweaking. It should look something like this:
// your previous code
const formData = new FormData(form);
const searchParams = new URLSearchParams();
for (let [key, value] of formData.entries()) {
searchParams.append(key.toString(), value.toString());
}
fetch(form.action, {
method: 'POST',
body: searchParams.toString(),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
})
// your other code
I just checked with myself, it should work, request.POST
should now contain the form data. Check it out and let me know if it works for you?
p.s. I did not use your update_expense
controller code.
UPDATED
I'm adding what I tested for just in case:
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta http-equiv="X-UA-Compatible" content="ie=edge">
<title>Document</title>
</head>
<body>
<form action="{% url 'foo_bar' %}" method="POST" id="my-form">
{% csrf_token %}
<div class="modal-body">
<input type="hidden" name="expense-id" id="expense-id">
<input type="hidden" name="expense-type" id="expense-type">
<div id="dynamic-fields"></div>
<button type="submit">CLICK ME</button>
</div>
</form>
<script>
const element = document.querySelector('#dynamic-fields');
for (const [key, value] of Object.entries({ field1: 1 })) {
// Skip fields that are null or undefined
if (value === null || value === undefined) continue;
const field = `
<div class="mb-3">
<label for="${key}" class="form-label">${key.replace('_', ' ').toUpperCase()}</label>
<input type="text" class="form-control" id="${key}" name="${key}" value="${value}" required>
</div>`;
element.insertAdjacentHTML('beforeend', field);
}
const form = document.querySelector('#my-form');
form.addEventListener('submit', function (event) {
event.preventDefault();
const formData = new FormData(form);
const searchParams = new URLSearchParams();
for (let [key, value] of formData.entries()) {
searchParams.append(key, value.toString());
}
fetch(form.action, {
method: 'POST',
body: searchParams.toString(),
headers: {
'Content-Type': 'application/x-www-form-urlencoded',
},
})
})
</script>
</body>
</html>
# views
def spam(request):
return render(request=request, template_name='test.html')
// Solution to the answer.
document.addEventListener('DOMContentLoaded', function () {
const editModal = document.getElementById('editExpenseModal');
const dynamicFieldsContainer = document.getElementById('dynamic-fields');
const form = document.getElementById('editExpenseForm');
const confirmationModal = new bootstrap.Modal(document.getElementById('confirmationModal'));
const cancelButton = document.getElementById('cancelEdit'); // Ensure this ID exists in your cancel button
document.querySelectorAll('.edit-expense-btn').forEach(button => {
button.addEventListener('click', () => {
const url = button.getAttribute('data-url');
fetch(url)
.then(response => {
if (!response.ok) {
throw new Error(`HTTP error! Status: ${response.status}`);
}
return response.json();
})
.then(data => {
if (data.error) {
alert('Error fetching expense details: ' + data.error);
return;
}
// Clear previous fields inside the form
dynamicFieldsContainer.innerHTML = '';
// Inject fields dynamically inside the form
for (const [key, value] of Object.entries(data.fields)) {
if (value === null || value === undefined) continue;
const field = document.createElement('div');
field.classList.add('mb-3');
field.innerHTML = `
<label for="${key}" class="form-label">${key.replace('_', ' ').toUpperCase()}</label>
<input type="text" class="form-control dynamic-input" id="${key}" name="${key}" value="${value}" required>
`;
dynamicFieldsContainer.appendChild(field);
}
// Ensure hidden input fields retain their values
document.getElementById('expense-id').value = data.id;
document.getElementById('expense-type').value = data.type;
// Open modal properly
editModal.setAttribute('aria-hidden', 'false');
const modalInstance = new bootstrap.Modal(editModal);
modalInstance.show();
})
.catch(error => console.error('Fetch Error:', error));
});
});
// Handle form submission
form.addEventListener('submit', function (event) {
event.preventDefault();
// Ensure dynamically generated inputs are captured
document.querySelectorAll('.dynamic-input').forEach(input => {
if (!form.contains(input)) {
form.appendChild(input);
}
});
const formData = new FormData(form);
// Get the CSRF token from the form
const csrfToken = document.querySelector('[name=csrfmiddlewaretoken]').value;
fetch(form.action, {
method: 'POST',
body: formData,
headers: {
'X-CSRFToken': csrfToken,
}
})
.then(response => {
const contentType = response.headers.get("content-type");
if (contentType && contentType.includes("application/json")) {
return response.json();
} else {
return response.text();
}
})
.then(data => {
console.log(data)
if (typeof data === "object" && data.success) {
// Hide edit modal properly
const editModalInstance = bootstrap.Modal.getInstance(editModal);
if (editModalInstance) {
editModalInstance.hide();
}
// Ensure modal cleanup
cleanupModals();
// Show confirmation modal
confirmationModal.show();
// Auto-hide confirmation modal after 1.5s
setTimeout(() => {
confirmationModal.hide();
window.location.reload();
cleanupModals();
}, 1500);
} else {
console.log("Non-JSON response received:", data);
}
})
.catch(error => console.warn('⚠️ Fetch Warning:', error));
});
// Handle Cancel button click
cancelButton.addEventListener('click', function () {
const editModalInstance = bootstrap.Modal.getInstance(editModal);
if (editModalInstance) {
editModalInstance.hide();
}
cleanupModals();
});
// Ensure modal cleanup when closed
editModal.addEventListener('hidden.bs.modal', cleanupModals);
// Function to remove lingering backdrops and reset page state
function cleanupModals() {
document.body.classList.remove('modal-open');
document.querySelectorAll('.modal-backdrop').forEach(backdrop => backdrop.remove());
editModal.setAttribute('aria-hidden', 'true');
}
});