// DOM Elements
const itemForm = document.getElementById('item-form');
const formTitle = document.getElementById('form-title');
const itemIdInput = document.getElementById('item-id');
const titleInput = document.getElementById('title');
const descriptionInput = document.getElementById('description');
const completedCheckbox = document.getElementById('completed');
const submitBtn = document.getElementById('submit-btn');
const cancelBtn = document.getElementById('cancel-btn');
const itemsTableBody = document.getElementById('items-table-body');
const noItemsMessage = document.getElementById('no-items-message');
const createFirstItemBtn = document.getElementById('create-first-item-btn');
const loadingSpinner = document.getElementById('loading-spinner');
const toastElement = document.getElementById('toast');
const toastTitle = document.getElementById('toast-title');
const toastMessage = document.getElementById('toast-message');
const toastIcon = document.getElementById('toast-icon');
// Create Bootstrap toast instance
const toast = new bootstrap.Toast(toastElement, {
delay: 3000
});
// API Endpoints
const API_URL = '/api/items';
// App State
let isEditing = false;
let items = [];
// Event Listeners
document.addEventListener('DOMContentLoaded', fetchItems);
itemForm.addEventListener('submit', handleFormSubmit);
cancelBtn.addEventListener('click', resetForm);
if (createFirstItemBtn) {
createFirstItemBtn.addEventListener('click', () => {
window.scrollTo({
top: itemForm.offsetTop - 100,
behavior: 'smooth'
});
titleInput.focus();
});
}
// Functions
async function fetchItems() {
try {
showLoading(true);
const response = await fetch(API_URL);
const data = await response.json();
// Keep a reference to all items
items = data;
renderItems(items);
showLoading(false);
} catch (error) {
console.error('Error fetching items:', error);
showToast('Error', 'Failed to load items. Please try again.', 'error');
showLoading(false);
}
}
function renderItems(items) {
itemsTableBody.innerHTML = '';
if (items.length === 0) {
noItemsMessage.style.display = 'block';
document.querySelector('.table-responsive').style.display = 'none';
return;
}
noItemsMessage.style.display = 'none';
document.querySelector('.table-responsive').style.display = 'block';
items.forEach(item => {
const row = document.createElement('tr');
row.id = `item-row-${item.id}`;
row.innerHTML = `
${item.id} |
${escapeHtml(item.title)} |
${escapeHtml(item.description || '-')} |
${item.completed ?
'Completed' :
'Pending'}
|
|
`;
itemsTableBody.appendChild(row);
// Add event listeners to buttons
const editBtn = row.querySelector('.edit-btn');
const deleteBtn = row.querySelector('.delete-btn');
editBtn.addEventListener('click', () => editItem(item));
deleteBtn.addEventListener('click', () => deleteItem(item.id));
});
}
async function handleFormSubmit(event) {
event.preventDefault();
const itemData = {
title: titleInput.value.trim(),
description: descriptionInput.value.trim(),
completed: completedCheckbox.checked
};
if (!itemData.title) {
showToast('Validation Error', 'Title is required', 'error');
titleInput.focus();
return;
}
try {
showLoading(true);
if (isEditing) {
// Update existing item
const itemId = parseInt(itemIdInput.value);
await updateItem(itemId, itemData);
showToast('Success', 'Item updated successfully!', 'success');
} else {
// Create new item
await createItem(itemData);
showToast('Success', 'New item created successfully!', 'success');
}
resetForm();
fetchItems();
} catch (error) {
console.error('Error saving item:', error);
showToast('Error', 'Failed to save item. Please try again.', 'error');
showLoading(false);
}
}
async function createItem(itemData) {
const response = await fetch(API_URL, {
method: 'POST',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(itemData)
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Failed to create item');
}
return await response.json();
}
async function updateItem(itemId, itemData) {
const response = await fetch(`${API_URL}/${itemId}`, {
method: 'PUT',
headers: {
'Content-Type': 'application/json'
},
body: JSON.stringify(itemData)
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Failed to update item');
}
return await response.json();
}
async function deleteItem(itemId) {
// Create custom confirm dialog
if (!confirm('Are you sure you want to delete this item? This action cannot be undone.')) {
return;
}
try {
showLoading(true);
const response = await fetch(`${API_URL}/${itemId}`, {
method: 'DELETE'
});
if (!response.ok) {
const error = await response.json();
throw new Error(error.detail || 'Failed to delete item');
}
showToast('Success', 'Item deleted successfully!', 'success');
// Remove item from local array
items = items.filter(item => item.id !== itemId);
// Re-render items
renderItems(items);
showLoading(false);
} catch (error) {
console.error('Error deleting item:', error);
showToast('Error', 'Failed to delete item. Please try again.', 'error');
showLoading(false);
}
}
function editItem(item) {
// Set form to edit mode
isEditing = true;
formTitle.innerHTML = 'Edit Item';
submitBtn.innerHTML = 'Update';
cancelBtn.style.display = 'block';
// Populate form with item data
itemIdInput.value = item.id;
titleInput.value = item.title;
descriptionInput.value = item.description || '';
completedCheckbox.checked = item.completed;
// Scroll to form
window.scrollTo({
top: itemForm.offsetTop - 100,
behavior: 'smooth'
});
// Add highlight class to row
const row = document.getElementById(`item-row-${item.id}`);
if (row) {
row.classList.add('highlight-row');
setTimeout(() => {
row.classList.remove('highlight-row');
}, 2000);
}
// Focus on title input
titleInput.focus();
}
function resetForm() {
// Reset form state
isEditing = false;
formTitle.innerHTML = 'Add New Item';
submitBtn.innerHTML = 'Save';
cancelBtn.style.display = 'none';
// Clear form inputs
itemForm.reset();
itemIdInput.value = '';
}
function showToast(title, message, type = 'info') {
toastTitle.textContent = title;
toastMessage.textContent = message;
// Remove any existing classes
toastElement.classList.remove('bg-success', 'bg-danger', 'bg-info', 'bg-warning', 'text-white');
toastIcon.classList.remove('bi-check-circle-fill', 'bi-exclamation-triangle-fill', 'bi-info-circle-fill');
// Add appropriate styling based on type
if (type === 'success') {
toastElement.classList.add('bg-success', 'text-white');
toastIcon.classList.add('bi-check-circle-fill');
} else if (type === 'error') {
toastElement.classList.add('bg-danger', 'text-white');
toastIcon.classList.add('bi-exclamation-triangle-fill');
} else {
toastElement.classList.add('bg-info', 'text-white');
toastIcon.classList.add('bi-info-circle-fill');
}
toast.show();
}
function showLoading(isLoading) {
if (isLoading) {
loadingSpinner.style.display = 'inline-block';
} else {
loadingSpinner.style.display = 'none';
}
}
// Utility function to prevent XSS
function escapeHtml(str) {
if (!str) return '';
return str
.replace(/&/g, '&')
.replace(//g, '>')
.replace(/"/g, '"')
.replace(/'/g, ''');
}