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:
parent
9f9617ca45
commit
d5ac2a21b3
142
public/js/app.js
142
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 = '<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) {
|
||||
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(/<ul[^>]*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 = '<p>Geen afbeelding</p>';
|
||||
}
|
||||
// Populate category select
|
||||
const categorySelect = document.getElementById('edit_item_category_id');
|
||||
categorySelect.innerHTML = '<option value="">-- Select Category --</option>';
|
||||
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 = '<option value="">-- No Parent --</option>';
|
||||
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');
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user