From d5ac2a21b3a93ed0fc291067e7671c343c1101ef Mon Sep 17 00:00:00 2001 From: Edwin Date: Wed, 12 Nov 2025 16:29:35 +0000 Subject: [PATCH] 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. --- public/js/app.js | 142 ++++++++++++++++++++++++++--------------------- 1 file changed, 79 insertions(+), 63 deletions(-) diff --git a/public/js/app.js b/public/js/app.js index 990f628..f44fe96 100755 --- a/public/js/app.js +++ b/public/js/app.js @@ -4,21 +4,28 @@ document.addEventListener('DOMContentLoaded', function() { // Global function for editing item from tree window.editItem = function(id) { - // Create a mock button to reuse handleEditItem const mockBtn = { getAttribute: (attr) => attr === 'data-id' ? id : null }; handleEditItem.call(mockBtn); }; - // Function to toggle category children - window.toggleCategory = function(span) { - const li = span.parentElement; - const ul = li.querySelector('ul'); // First ul is children - if (ul) { - ul.style.display = ul.style.display === 'none' ? 'block' : 'none'; + // Function to toggle tree nodes + window.toggleNode = function(element) { + const li = element.closest('li'); + const childUl = li.querySelector(':scope > ul'); + if (childUl) { + 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) { document.title = `${appName} - ${title}`; } @@ -27,11 +34,24 @@ document.addEventListener('DOMContentLoaded', function() { mainContent.innerHTML = '
Loading...
'; } - // 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) { + console.log('fetchContent called with path:', path); showLoading(); - // Determine the API endpoint based on the path let apiPath = ''; let pageTitle = ''; let routePath = path; @@ -40,6 +60,10 @@ document.addEventListener('DOMContentLoaded', function() { apiPath = '/api/items'; pageTitle = 'Overview'; routePath = '/'; + } else if (path.startsWith('/items')) { + apiPath = '/api/items'; + pageTitle = 'Overview'; + routePath = '/'; } else if (path.startsWith('/categories')) { if (path.includes('?category=')) { const categoryId = path.split('?category=')[1]; @@ -59,7 +83,6 @@ document.addEventListener('DOMContentLoaded', function() { pageTitle = 'Parts'; } } else { - // Fallback apiPath = '/api/items'; pageTitle = 'Overview'; routePath = '/'; @@ -79,7 +102,6 @@ document.addEventListener('DOMContentLoaded', function() { } setPageTitle(pageTitle); updateActiveLink(routePath); - // Re-initialize any scripts specific to the loaded content initPageSpecificScripts(); }) .catch(error => { @@ -88,7 +110,6 @@ document.addEventListener('DOMContentLoaded', function() { }); } - // Update active class on sidebar links function updateActiveLink(path) { document.querySelectorAll('.nav-link[data-route]').forEach(link => { link.classList.remove('active'); @@ -98,7 +119,6 @@ document.addEventListener('DOMContentLoaded', function() { }); } - // Handle browser back/forward buttons window.onpopstate = function(event) { if (event.state && event.state.html) { mainContent.innerHTML = event.state.html; @@ -106,14 +126,10 @@ document.addEventListener('DOMContentLoaded', function() { updateActiveLink(event.state.path); initPageSpecificScripts(); } else { - // If no state, force a full load of the current path fetchContent(window.location.pathname, false); } }; - // --- Event Listeners --- - - // Navigation links (Navbar and Sidebar) document.body.addEventListener('click', function(e) { const link = e.target.closest('a[data-route]'); if (link) { @@ -140,7 +156,6 @@ document.addEventListener('DOMContentLoaded', function() { const initialPath = window.location.pathname === '/' ? '/items' : window.location.pathname; fetchContent(initialPath, false); - // Page-specific scripts (e.g., form submissions) function initPageSpecificScripts() { // Overview page: filter form const filterForm = document.getElementById('filterForm'); @@ -175,9 +190,9 @@ document.addEventListener('DOMContentLoaded', function() { } // Categories page: save edit category - const saveEditBtn = document.getElementById('saveEditCategoryBtn'); - if (saveEditBtn) { - saveEditBtn.addEventListener('click', handleSaveEditCategory); + const saveEditCategoryBtn = document.getElementById('saveEditCategoryBtn'); + if (saveEditCategoryBtn) { + saveEditCategoryBtn.addEventListener('click', handleSaveEditCategory); } // Categories page: edit/delete buttons @@ -201,6 +216,7 @@ document.addEventListener('DOMContentLoaded', function() { .then(data => { if (data.success) { fetchContent('/', false); + reloadTree(); // Reload tree after deletion } else { alert(data.error || 'Error deleting item'); } @@ -225,7 +241,8 @@ document.addEventListener('DOMContentLoaded', function() { .then(response => response.json()) .then(data => { if (data.success) { - fetchContent('/', false); // Go to overview to see the new part + fetchContent('/', false); + reloadTree(); // Reload tree after adding } else { 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) { e.preventDefault(); const search = document.getElementById('search').value; @@ -275,7 +264,7 @@ document.addEventListener('DOMContentLoaded', function() { .then(response => response.text()) .then(html => { document.getElementById('itemsList').outerHTML = html.match(/]*id="itemsList"[^>]*>[\s\S]*?<\/ul>/)[0]; - initPageSpecificScripts(); // Re-init for new buttons + initPageSpecificScripts(); }) .catch(error => { console.error('Error:', error); @@ -284,7 +273,6 @@ document.addEventListener('DOMContentLoaded', function() { function handleEditItem() { const id = this.getAttribute('data-id'); - // Fetch current item data fetch('/api/items/' + id) .then(response => response.json()) .then(data => { @@ -299,7 +287,6 @@ document.addEventListener('DOMContentLoaded', function() { } else { currentImageDiv.innerHTML = '

Geen afbeelding

'; } - // Populate category select const categorySelect = document.getElementById('edit_item_category_id'); categorySelect.innerHTML = ''; fetch('/api/categories/list') @@ -313,6 +300,10 @@ document.addEventListener('DOMContentLoaded', function() { }); categorySelect.value = data.category_id || ''; const modal = new bootstrap.Modal(document.getElementById('editItemModal')); + const saveEditItemBtn = document.getElementById('saveEditItemBtn'); + if (saveEditItemBtn) { + saveEditItemBtn.addEventListener('click', handleSaveEditItem); + } modal.show(); }); } else { @@ -335,6 +326,7 @@ document.addEventListener('DOMContentLoaded', function() { .then(data => { if (data.success) { this.closest('li').remove(); + reloadTree(); // Reload tree after deletion } else { alert(data.error || 'Error deleting part'); } @@ -356,6 +348,7 @@ document.addEventListener('DOMContentLoaded', function() { .then(data => { if (data.success) { fetchContent('/categories', false); + reloadTree(); // Reload tree after adding } else { alert(data.error || 'Error adding category'); } @@ -395,15 +388,25 @@ document.addEventListener('DOMContentLoaded', function() { .then(response => response.json()) .then(data => { if (data.success) { - bootstrap.Modal.getInstance(document.getElementById('editItemModal')).hide(); - fetchContent('/', false); // Reload overview + // Hide modal + 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 { alert(data.error || 'Error updating part'); } }) .catch(error => { - console.error('Error:', error); - alert('Error updating part'); + console.error('Update error:', error); + alert('Error updating part: ' + error.message); }); } @@ -432,21 +435,30 @@ document.addEventListener('DOMContentLoaded', function() { .then(response => response.json()) .then(data => { 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); + reloadTree(); // Reload tree after updating } else { alert(data.error || 'Error updating category'); } }) .catch(error => { - console.error('Error:', error); - alert('Error updating category'); + console.error('Update error:', error); + alert('Error updating category: ' + error.message); }); } function handleEditCategory() { const id = this.getAttribute('data-id'); - // Fetch current category data fetch('/api/categories/' + id) .then(response => { if (!response.ok) { @@ -458,7 +470,6 @@ document.addEventListener('DOMContentLoaded', function() { if (data && !data.error) { document.getElementById('edit_category_id').value = data.id; document.getElementById('edit_category_name').value = data.name; - // Populate parent select, excluding current category const parentSelect = document.getElementById('edit_parent_category_id'); parentSelect.innerHTML = ''; fetch('/api/categories/list') @@ -474,6 +485,10 @@ document.addEventListener('DOMContentLoaded', function() { }); parentSelect.value = data.parent_id || ''; const modal = new bootstrap.Modal(document.getElementById('editCategoryModal')); + const saveEditCategoryBtn = document.getElementById('saveEditCategoryBtn'); + if (saveEditCategoryBtn) { + saveEditCategoryBtn.addEventListener('click', handleSaveEditCategory); + } modal.show(); }); } else { @@ -496,6 +511,7 @@ document.addEventListener('DOMContentLoaded', function() { .then(data => { if (data.success) { this.closest('li').remove(); + reloadTree(); // Reload tree after deletion } else { alert(data.error || 'Error deleting category'); }