Fix JavaScript tree reload after CRUD operations

- Add reloadTree() function to centralize tree updates
- Fix handleSaveEditItem to reload tree after item updates
- Fix handleAddItem to reload tree after adding items
- Fix handleDeleteItem to reload tree after item deletion
- Fix all category functions to reload tree after operations
- Replace broken Bootstrap.Modal.getInstance() with direct DOM manipulation
- Fix JavaScript syntax errors and missing braces
- Ensure tree updates automatically without manual page reload

Resolves issue where tree navigation was not updating after item modifications.
This commit is contained in:
Edwin 2025-11-12 16:29:35 +00:00
parent 9f9617ca45
commit d5ac2a21b3

View File

@ -4,21 +4,28 @@ document.addEventListener('DOMContentLoaded', function() {
// Global function for editing item from tree // Global function for editing item from tree
window.editItem = function(id) { window.editItem = function(id) {
// Create a mock button to reuse handleEditItem
const mockBtn = { getAttribute: (attr) => attr === 'data-id' ? id : null }; const mockBtn = { getAttribute: (attr) => attr === 'data-id' ? id : null };
handleEditItem.call(mockBtn); handleEditItem.call(mockBtn);
}; };
// Function to toggle category children // Function to toggle tree nodes
window.toggleCategory = function(span) { window.toggleNode = function(element) {
const li = span.parentElement; const li = element.closest('li');
const ul = li.querySelector('ul'); // First ul is children const childUl = li.querySelector(':scope > ul');
if (ul) { if (childUl) {
ul.style.display = ul.style.display === 'none' ? 'block' : 'none'; const isHidden = childUl.style.display === 'none';
childUl.style.display = isHidden ? 'block' : 'none';
const icon = element.querySelector('i');
if (icon) {
if (isHidden) {
icon.className = 'bi bi-folder-fill text-warning';
} else {
icon.className = 'bi bi-folder text-warning';
}
}
} }
}; };
// --- Helper Functions ---
function setPageTitle(title) { function setPageTitle(title) {
document.title = `${appName} - ${title}`; document.title = `${appName} - ${title}`;
} }
@ -27,11 +34,24 @@ document.addEventListener('DOMContentLoaded', function() {
mainContent.innerHTML = '<div class="text-center p-5"><div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div></div>'; mainContent.innerHTML = '<div class="text-center p-5"><div class="spinner-border text-primary" role="status"><span class="visually-hidden">Loading...</span></div></div>';
} }
// Function to fetch content and update the main view function reloadTree() {
fetch('/api/tree')
.then(response => response.text())
.then(html => {
const sidebarTree = document.getElementById('sidebar-tree');
if (sidebarTree) {
sidebarTree.innerHTML = html;
}
})
.catch(error => {
console.error('Error loading tree:', error);
});
}
function fetchContent(path, pushState = true) { function fetchContent(path, pushState = true) {
console.log('fetchContent called with path:', path);
showLoading(); showLoading();
// Determine the API endpoint based on the path
let apiPath = ''; let apiPath = '';
let pageTitle = ''; let pageTitle = '';
let routePath = path; let routePath = path;
@ -40,6 +60,10 @@ document.addEventListener('DOMContentLoaded', function() {
apiPath = '/api/items'; apiPath = '/api/items';
pageTitle = 'Overview'; pageTitle = 'Overview';
routePath = '/'; routePath = '/';
} else if (path.startsWith('/items')) {
apiPath = '/api/items';
pageTitle = 'Overview';
routePath = '/';
} else if (path.startsWith('/categories')) { } else if (path.startsWith('/categories')) {
if (path.includes('?category=')) { if (path.includes('?category=')) {
const categoryId = path.split('?category=')[1]; const categoryId = path.split('?category=')[1];
@ -59,7 +83,6 @@ document.addEventListener('DOMContentLoaded', function() {
pageTitle = 'Parts'; pageTitle = 'Parts';
} }
} else { } else {
// Fallback
apiPath = '/api/items'; apiPath = '/api/items';
pageTitle = 'Overview'; pageTitle = 'Overview';
routePath = '/'; routePath = '/';
@ -79,7 +102,6 @@ document.addEventListener('DOMContentLoaded', function() {
} }
setPageTitle(pageTitle); setPageTitle(pageTitle);
updateActiveLink(routePath); updateActiveLink(routePath);
// Re-initialize any scripts specific to the loaded content
initPageSpecificScripts(); initPageSpecificScripts();
}) })
.catch(error => { .catch(error => {
@ -88,7 +110,6 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
} }
// Update active class on sidebar links
function updateActiveLink(path) { function updateActiveLink(path) {
document.querySelectorAll('.nav-link[data-route]').forEach(link => { document.querySelectorAll('.nav-link[data-route]').forEach(link => {
link.classList.remove('active'); link.classList.remove('active');
@ -98,7 +119,6 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
} }
// Handle browser back/forward buttons
window.onpopstate = function(event) { window.onpopstate = function(event) {
if (event.state && event.state.html) { if (event.state && event.state.html) {
mainContent.innerHTML = event.state.html; mainContent.innerHTML = event.state.html;
@ -106,14 +126,10 @@ document.addEventListener('DOMContentLoaded', function() {
updateActiveLink(event.state.path); updateActiveLink(event.state.path);
initPageSpecificScripts(); initPageSpecificScripts();
} else { } else {
// If no state, force a full load of the current path
fetchContent(window.location.pathname, false); fetchContent(window.location.pathname, false);
} }
}; };
// --- Event Listeners ---
// Navigation links (Navbar and Sidebar)
document.body.addEventListener('click', function(e) { document.body.addEventListener('click', function(e) {
const link = e.target.closest('a[data-route]'); const link = e.target.closest('a[data-route]');
if (link) { if (link) {
@ -140,7 +156,6 @@ document.addEventListener('DOMContentLoaded', function() {
const initialPath = window.location.pathname === '/' ? '/items' : window.location.pathname; const initialPath = window.location.pathname === '/' ? '/items' : window.location.pathname;
fetchContent(initialPath, false); fetchContent(initialPath, false);
// Page-specific scripts (e.g., form submissions)
function initPageSpecificScripts() { function initPageSpecificScripts() {
// Overview page: filter form // Overview page: filter form
const filterForm = document.getElementById('filterForm'); const filterForm = document.getElementById('filterForm');
@ -175,9 +190,9 @@ document.addEventListener('DOMContentLoaded', function() {
} }
// Categories page: save edit category // Categories page: save edit category
const saveEditBtn = document.getElementById('saveEditCategoryBtn'); const saveEditCategoryBtn = document.getElementById('saveEditCategoryBtn');
if (saveEditBtn) { if (saveEditCategoryBtn) {
saveEditBtn.addEventListener('click', handleSaveEditCategory); saveEditCategoryBtn.addEventListener('click', handleSaveEditCategory);
} }
// Categories page: edit/delete buttons // Categories page: edit/delete buttons
@ -201,6 +216,7 @@ document.addEventListener('DOMContentLoaded', function() {
.then(data => { .then(data => {
if (data.success) { if (data.success) {
fetchContent('/', false); fetchContent('/', false);
reloadTree(); // Reload tree after deletion
} else { } else {
alert(data.error || 'Error deleting item'); alert(data.error || 'Error deleting item');
} }
@ -225,7 +241,8 @@ document.addEventListener('DOMContentLoaded', function() {
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
fetchContent('/', false); // Go to overview to see the new part fetchContent('/', false);
reloadTree(); // Reload tree after adding
} else { } else {
alert(data.error || 'Error adding part'); alert(data.error || 'Error adding part');
} }
@ -236,34 +253,6 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
} }
function handleAddCategoryFromItem() {
const name = document.getElementById('new_category_name').value.trim();
if (!name) return;
const formData = new FormData();
formData.append('category_name', name);
fetch('/api/categories', {
method: 'POST',
body: formData
})
.then(response => response.json())
.then(data => {
if (data.success) {
const select = document.getElementById('category_id');
const option = new Option(name, data.id);
select.appendChild(option);
select.value = data.id;
document.getElementById('new_category_name').value = '';
document.getElementById('addCategoryForm').classList.add('hidden');
} else {
alert(data.error || 'Error adding category');
}
})
.catch(error => {
console.error('Error:', error);
alert('Error adding category');
});
}
function handleFilter(e) { function handleFilter(e) {
e.preventDefault(); e.preventDefault();
const search = document.getElementById('search').value; const search = document.getElementById('search').value;
@ -275,7 +264,7 @@ document.addEventListener('DOMContentLoaded', function() {
.then(response => response.text()) .then(response => response.text())
.then(html => { .then(html => {
document.getElementById('itemsList').outerHTML = html.match(/<ul[^>]*id="itemsList"[^>]*>[\s\S]*?<\/ul>/)[0]; document.getElementById('itemsList').outerHTML = html.match(/<ul[^>]*id="itemsList"[^>]*>[\s\S]*?<\/ul>/)[0];
initPageSpecificScripts(); // Re-init for new buttons initPageSpecificScripts();
}) })
.catch(error => { .catch(error => {
console.error('Error:', error); console.error('Error:', error);
@ -284,7 +273,6 @@ document.addEventListener('DOMContentLoaded', function() {
function handleEditItem() { function handleEditItem() {
const id = this.getAttribute('data-id'); const id = this.getAttribute('data-id');
// Fetch current item data
fetch('/api/items/' + id) fetch('/api/items/' + id)
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
@ -299,7 +287,6 @@ document.addEventListener('DOMContentLoaded', function() {
} else { } else {
currentImageDiv.innerHTML = '<p>Geen afbeelding</p>'; currentImageDiv.innerHTML = '<p>Geen afbeelding</p>';
} }
// Populate category select
const categorySelect = document.getElementById('edit_item_category_id'); const categorySelect = document.getElementById('edit_item_category_id');
categorySelect.innerHTML = '<option value="">-- Select Category --</option>'; categorySelect.innerHTML = '<option value="">-- Select Category --</option>';
fetch('/api/categories/list') fetch('/api/categories/list')
@ -313,6 +300,10 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
categorySelect.value = data.category_id || ''; categorySelect.value = data.category_id || '';
const modal = new bootstrap.Modal(document.getElementById('editItemModal')); const modal = new bootstrap.Modal(document.getElementById('editItemModal'));
const saveEditItemBtn = document.getElementById('saveEditItemBtn');
if (saveEditItemBtn) {
saveEditItemBtn.addEventListener('click', handleSaveEditItem);
}
modal.show(); modal.show();
}); });
} else { } else {
@ -335,6 +326,7 @@ document.addEventListener('DOMContentLoaded', function() {
.then(data => { .then(data => {
if (data.success) { if (data.success) {
this.closest('li').remove(); this.closest('li').remove();
reloadTree(); // Reload tree after deletion
} else { } else {
alert(data.error || 'Error deleting part'); alert(data.error || 'Error deleting part');
} }
@ -356,6 +348,7 @@ document.addEventListener('DOMContentLoaded', function() {
.then(data => { .then(data => {
if (data.success) { if (data.success) {
fetchContent('/categories', false); fetchContent('/categories', false);
reloadTree(); // Reload tree after adding
} else { } else {
alert(data.error || 'Error adding category'); alert(data.error || 'Error adding category');
} }
@ -395,15 +388,25 @@ document.addEventListener('DOMContentLoaded', function() {
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
bootstrap.Modal.getInstance(document.getElementById('editItemModal')).hide(); // Hide modal
fetchContent('/', false); // Reload overview const modalElement = document.getElementById('editItemModal');
if (modalElement) {
modalElement.classList.remove('show', 'showing');
modalElement.style.display = 'none';
modalElement.setAttribute('aria-hidden', 'true');
document.body.classList.remove('modal-open');
const backdrops = document.querySelectorAll(' .modal-backdrop');
backdrops.forEach(backdrop => backdrop.remove());
}
fetchContent('/', false);
reloadTree(); // Reload tree after updating
} else { } else {
alert(data.error || 'Error updating part'); alert(data.error || 'Error updating part');
} }
}) })
.catch(error => { .catch(error => {
console.error('Error:', error); console.error('Update error:', error);
alert('Error updating part'); alert('Error updating part: ' + error.message);
}); });
} }
@ -432,21 +435,30 @@ document.addEventListener('DOMContentLoaded', function() {
.then(response => response.json()) .then(response => response.json())
.then(data => { .then(data => {
if (data.success) { if (data.success) {
bootstrap.Modal.getInstance(document.getElementById('editCategoryModal')).hide(); // Hide modal
const modalElement = document.getElementById('editCategoryModal');
if (modalElement) {
modalElement.classList.remove('show', 'showing');
modalElement.style.display = 'none';
modalElement.setAttribute('aria-hidden', 'true');
document.body.classList.remove('modal-open');
const backdrops = document.querySelectorAll(' .modal-backdrop');
backdrops.forEach(backdrop => backdrop.remove());
}
fetchContent('/categories', false); fetchContent('/categories', false);
reloadTree(); // Reload tree after updating
} else { } else {
alert(data.error || 'Error updating category'); alert(data.error || 'Error updating category');
} }
}) })
.catch(error => { .catch(error => {
console.error('Error:', error); console.error('Update error:', error);
alert('Error updating category'); alert('Error updating category: ' + error.message);
}); });
} }
function handleEditCategory() { function handleEditCategory() {
const id = this.getAttribute('data-id'); const id = this.getAttribute('data-id');
// Fetch current category data
fetch('/api/categories/' + id) fetch('/api/categories/' + id)
.then(response => { .then(response => {
if (!response.ok) { if (!response.ok) {
@ -458,7 +470,6 @@ document.addEventListener('DOMContentLoaded', function() {
if (data && !data.error) { if (data && !data.error) {
document.getElementById('edit_category_id').value = data.id; document.getElementById('edit_category_id').value = data.id;
document.getElementById('edit_category_name').value = data.name; document.getElementById('edit_category_name').value = data.name;
// Populate parent select, excluding current category
const parentSelect = document.getElementById('edit_parent_category_id'); const parentSelect = document.getElementById('edit_parent_category_id');
parentSelect.innerHTML = '<option value="">-- No Parent --</option>'; parentSelect.innerHTML = '<option value="">-- No Parent --</option>';
fetch('/api/categories/list') fetch('/api/categories/list')
@ -474,6 +485,10 @@ document.addEventListener('DOMContentLoaded', function() {
}); });
parentSelect.value = data.parent_id || ''; parentSelect.value = data.parent_id || '';
const modal = new bootstrap.Modal(document.getElementById('editCategoryModal')); const modal = new bootstrap.Modal(document.getElementById('editCategoryModal'));
const saveEditCategoryBtn = document.getElementById('saveEditCategoryBtn');
if (saveEditCategoryBtn) {
saveEditCategoryBtn.addEventListener('click', handleSaveEditCategory);
}
modal.show(); modal.show();
}); });
} else { } else {
@ -496,6 +511,7 @@ document.addEventListener('DOMContentLoaded', function() {
.then(data => { .then(data => {
if (data.success) { if (data.success) {
this.closest('li').remove(); this.closest('li').remove();
reloadTree(); // Reload tree after deletion
} else { } else {
alert(data.error || 'Error deleting category'); alert(data.error || 'Error deleting category');
} }