Lab-2 Day-3
Problem Solving & Doubt Clearing : Electives III - Modern JavaScript From
The Beginning 2.0 (2024)
Demo Code for Task Manager App using HTML,CSS and Modern JavaScrip concepts
Time:30 mins
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Modern Task Manager</title>
<style>
:root {
--primary-color: #3498db;
--secondary-color: #2ecc71;
--danger-color: #e74c3c;
--background-color: #f5f6fa;
}
*{
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', sans-serif;
background-color: var(--background-color);
line-height: 1.6;
}
.container {
max-width: 800px;
margin: 2rem auto;
padding: 0 1rem;
}
.header {
text-align: center;
margin-bottom: 2rem;
}
.task-form {
background: white;
padding: 1.5rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
margin-bottom: 2rem;
}
.form-group {
margin-bottom: 1rem;
}
.form-group label {
display: block;
margin-bottom: 0.5rem;
font-weight: 600;
}
.form-group input,
.form-group textarea,
.form-group select {
width: 100%;
padding: 0.5rem;
border: 1px solid #ddd;
border-radius: 4px;
font-size: 1rem;
}
.btn {
padding: 0.5rem 1rem;
border: none;
border-radius: 4px;
cursor: pointer;
font-size: 1rem;
transition: opacity 0.2s;
}
.btn:hover {
opacity: 0.9;
}
.btn-primary {
background-color: var(--primary-color);
color: white;
}
.btn-danger {
background-color: var(--danger-color);
color: white;
}
.task-list {
display: grid;
gap: 1rem;
}
.task-item {
background: white;
padding: 1rem;
border-radius: 8px;
box-shadow: 0 2px 4px rgba(0,0,0,0.1);
display: grid;
gap: 0.5rem;
opacity: 1;
transition: opacity 0.3s;
}
.task-item.completed {
opacity: 0.7;
}
.task-item h3 {
margin: 0;
color: var(--primary-color);
}
.task-actions {
display: flex;
gap: 0.5rem;
}
.loading {
text-align: center;
padding: 2rem;
}
.error {
color: var(--danger-color);
padding: 1rem;
background: rgba(231, 76, 60, 0.1);
border-radius: 4px;
margin-bottom: 1rem;
}
</style>
</head>
<body>
<div class="container">
<header class="header">
<h1>Task Manager</h1>
</header>
<form id="taskForm" class="task-form">
<div class="form-group">
<label for="taskTitle">Title</label>
<input type="text" id="taskTitle" required>
</div>
<div class="form-group">
<label for="taskDescription">Description</label>
<textarea id="taskDescription" rows="3"></textarea>
</div>
<div class="form-group">
<label for="taskPriority">Priority</label>
<select id="taskPriority">
<option value="low">Low</option>
<option value="medium">Medium</option>
<option value="high">High</option>
</select>
</div>
<button type="submit" class="btn btn-primary">Add Task</button>
</form>
<div id="errorContainer"></div>
<div id="taskList" class="task-list"></div>
</div>
<script type="module">
// ES Modules
import { TaskManager } from './modules/TaskManager.js';
import { StorageService } from './modules/StorageService.js';
import { UIManager } from './modules/UIManager.js';
import { ValidationService } from './modules/ValidationService.js';
// Main application class
class App {
constructor() {
this.storageService = new StorageService();
this.taskManager = new TaskManager(this.storageService);
this.uiManager = new UIManager();
this.validationService = new ValidationService();
this.init();
}
async init() {
try {
// Initialize observers
this.setupIntersectionObserver();
// Load initial tasks
await this.loadTasks();
// Setup event listeners
this.setupEventListeners();
} catch (error) {
this.handleError(error);
}
}
setupIntersectionObserver() {
const options = {
root: null,
threshold: 0.1
};
const observer = new IntersectionObserver((entries) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
entry.target.classList.add('visible');
}
});
}, options);
// Observe task items
document.querySelectorAll('.task-item').forEach(item => {
observer.observe(item);
});
}
async loadTasks() {
const tasks = await this.taskManager.getAllTasks();
this.uiManager.renderTasks(tasks);
}
setupEventListeners() {
// Event delegation for task list
document.getElementById('taskList').addEventListener('click', async (e) => {
const taskId = e.target.closest('.task-item')?.dataset.id;
if (!taskId) return;
if (e.target.matches('.btn-complete')) {
await this.toggleTaskComplete(taskId);
} else if (e.target.matches('.btn-delete')) {
await this.deleteTask(taskId);
}
});
// Form submission
document.getElementById('taskForm').addEventListener('submit', async (e) => {
e.preventDefault();
await this.handleTaskSubmission(e);
});
}
async handleTaskSubmission(e) {
try {
const formData = new FormData(e.target);
const taskData = Object.fromEntries(formData.entries());
// Validate form data
if (!this.validationService.validateTask(taskData)) {
throw new Error('Invalid task data');
}
// Create new task
const task = await this.taskManager.createTask(taskData);
this.uiManager.addTaskToList(task);
e.target.reset();
} catch (error) {
this.handleError(error);
}
}
async toggleTaskComplete(taskId) {
try {
const updatedTask = await this.taskManager.toggleTaskComplete(taskId);
this.uiManager.updateTaskUI(updatedTask);
} catch (error) {
this.handleError(error);
}
}
async deleteTask(taskId) {
try {
await this.taskManager.deleteTask(taskId);
this.uiManager.removeTaskFromUI(taskId);
} catch (error) {
this.handleError(error);
}
}
handleError(error) {
console.error('Application Error:', error);
this.uiManager.showError(error.message);
}
}
// Initialize app
new App();
</script>
<!-- Module: TaskManager.js -->
<script type="module">
export class TaskManager {
constructor(storageService) {
this.storageService = storageService;
}
async getAllTasks() {
try {
return await this.storageService.getTasks();
} catch (error) {
throw new Error('Failed to fetch tasks');
}
}
async createTask({ title, description, priority }) {
const task = {
id: Date.now().toString(),
title,
description,
priority,
completed: false,
createdAt: new Date().toISOString()
};
await this.storageService.saveTask(task);
return task;
}
async toggleTaskComplete(taskId) {
const task = await this.storageService.getTask(taskId);
task.completed = !task.completed;
await this.storageService.updateTask(task);
return task;
}
async deleteTask(taskId) {
await this.storageService.deleteTask(taskId);
}
}
</script>
<!-- Module: StorageService.js -->
<script type="module">
export class StorageService {
constructor() {
this.STORAGE_KEY = 'taskManager_tasks';
}
async getTasks() {
const tasks = localStorage.getItem(this.STORAGE_KEY);
return tasks ? JSON.parse(tasks) : [];
}
async getTask(taskId) {
const tasks = await this.getTasks();
return tasks.find(task => task.id === taskId);
}
async saveTask(task) {
const tasks = await this.getTasks();
tasks.push(task);
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(tasks));
}
async updateTask(updatedTask) {
const tasks = await this.getTasks();
const index = tasks.findIndex(task => task.id === updatedTask.id);
if (index !== -1) {
tasks[index] = updatedTask;
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(tasks));
}
}
async deleteTask(taskId) {
const tasks = await this.getTasks();
const filteredTasks = tasks.filter(task => task.id !== taskId);
localStorage.setItem(this.STORAGE_KEY, JSON.stringify(filteredTasks));
}
}
</script>
<!-- Module: UIManager.js -->
<script type="module">
export class UIManager {
constructor() {
this.taskList = document.getElementById('taskList');
this.errorContainer = document.getElementById('errorContainer');
}
renderTasks(tasks) {
this.taskList.innerHTML = tasks.map(task => this.createTaskHTML(task)).join('');
}
createTaskHTML(task) {
return `
<div class="task-item ${task.completed ? 'completed' : ''}" data-id="${task.id}">
<h3>${task.title}</h3>
<p>${task.description}</p>
<div class="task-meta">
<span class="priority ${task.priority}">${task.priority}</span>
<span class="date">${new Date(task.createdAt).toLocaleDateString()}</span>
</div>
<div class="task-actions">
<button class="btn btn-primary btn-complete">
${task.completed ? 'Undo' : 'Complete'}
</button>
<button class="btn btn-danger btn-delete">Delete</button>
</div>
</div>
`;
}
addTaskToList(task) {
this.taskList.insertAdjacentHTML('afterbegin', this.createTaskHTML(task));
}
updateTaskUI(task) {
const taskElement = this.taskList.querySelector(`[data-id="${task.id}"]`);
if (taskElement) {
taskElement.outerHTML = this.createTaskHTML(task);
}
}
removeTaskFromUI(taskId) {
const taskElement = this.taskList.querySelector(`[data-id="${taskId}"]`);
if (taskElement) {
taskElement.remove();
}
}
showError(message) {
this.errorContainer.innerHTML = `
<div class="error">
${message}
</div>
`;
setTimeout(() => {
this.errorContainer.innerHTML = '';
}, 3000);
}
}
</script>
<!-- Module: ValidationService.js -->
<script type="module">
export class ValidationService {
validateTask(taskData) {
const { title, description, priority } = taskData;
if (!title || title.trim().length < 3) {
throw new Error('Title must be at least 3 characters long');
}
if (!description || description.trim().length < 5) {
throw new Error('Description must be at least 5 characters long');
}
if (!['low', 'medium', 'high'].includes(priority)) {
throw new Error('Invalid priority level');
}
return true;
}
}
</script>
</body>
</html>