Build a complete modern JavaScript application using all ES6+ features learned throughout the course.
Welcome to the capstone project of our ES6 Modern JavaScript course! This lesson combines all the ES6+ features you've mastered into a real-world application.
You'll build a modern, feature-rich Task Management Application that demonstrates:
This project showcases destructuring, spread/rest operators, let/const, enhanced object literals, classes, modules, promises, async/await, and advanced data structures working together in a cohesive application.
Modern JavaScript applications require thoughtful architecture. Our task management app will use:
Module-Based Architecture:
1 // Core application structure
2 class TaskManager {
3 constructor() {
4 this.tasks = new Map();
5 this.categories = new Set(['personal', 'work', 'urgent']);
6 this.observers = new WeakMap();
7 this.#initializeApp();
8 }
9
10 // Private method using class fields
11 #initializeApp() {
12 this.loadFromStorage()
13 .then(() => this.renderTasks())
14 .catch(error => console.error('Init failed:', error));
15 }
16 }Modern Import/Export Structure:
1 // utils/taskUtils.js
2 export const TaskPriority = Object.freeze({
3 LOW: Symbol('low'),
4 MEDIUM: Symbol('medium'),
5 HIGH: Symbol('high'),
6 URGENT: Symbol('urgent')
7 });
8
9 export const createTask = ({ title, category, priority = TaskPriority.MEDIUM, dueDate }) => ({
10 id: generateId(),
11 title,
12 category,
13 priority,
14 dueDate: new Date(dueDate),
15 createdAt: new Date(),
16 completed: false
17 });Enhanced Object Literals & Destructuring:
1 const TaskService = {
2 // Shorthand method
3 async createTask(taskData) {
4 const { title, category, ...rest } = taskData;
5 return this.save({ title, category, ...rest });
6 },
7
8 // Computed property names
9 [TaskPriority.HIGH]: 'high-priority-tasks',
10 [TaskPriority.URGENT]: 'urgent-tasks'
11 };Our application handles data asynchronously using modern JavaScript patterns:
Promise-Based Storage Service:
1 class StorageService {
2 static instance = null;
3
4 static getInstance() {
5 if (!this.instance) {
6 this.instance = new StorageService();
7 }
8 return this.instance;
9 }
10
11 async saveTask(task) {
12 try {
13 const existingTasks = await this.getAllTasks();
14 const updatedTasks = [...existingTasks, task];
15
16 return new Promise((resolve, reject) => {
17 setTimeout(() => {
18 try {
19 localStorage.setItem('tasks', JSON.stringify(updatedTasks));
20 resolve(task);
21 } catch (error) {
22 reject(new Error(`Failed to save task: ${error.message}`));
23 }
24 }, 100); // Simulate network delay
25 });
26 } catch (error) {
27 throw new Error(`Storage operation failed: ${error.message}`);
28 }
29 }
30
31 async getAllTasks() {
32 return new Promise((resolve) => {
33 const tasks = JSON.parse(localStorage.getItem('tasks') || '[]');
34 resolve(tasks.map(task => ({
35 ...task,
36 createdAt: new Date(task.createdAt),
37 dueDate: new Date(task.dueDate)
38 })));
39 });
40 }
41 }Async/Await with Error Handling:
1 class TaskController {
2 constructor() {
3 this.storage = StorageService.getInstance();
4 this.tasks = new Map();
5 }
6
7 async addTask(taskData) {
8 try {
9 const task = createTask(taskData);
10 const savedTask = await this.storage.saveTask(task);
11
12 this.tasks.set(savedTask.id, savedTask);
13 await this.notifyObservers('taskAdded', savedTask);
14
15 return savedTask;
16 } catch (error) {
17 console.error('Failed to add task:', error);
18 throw error;
19 }
20 }
21
22 async loadTasks() {
23 try {
24 const tasks = await this.storage.getAllTasks();
25
26 // Use destructuring and spread for efficient data processing
27 tasks.forEach(task => {
28 const { id, ...taskData } = task;
29 this.tasks.set(id, { id, ...taskData });
30 });
31
32 return [...this.tasks.values()];
33 } catch (error) {
34 console.error('Failed to load tasks:', error);
35 return [];
36 }
37 }
38 }Leveraging ES6 data structures for efficient state management:
Using Maps, Sets, and Symbols for Application State:
1 class AppState {
2 constructor() {
3 // Map for O(1) task lookups
4 this.tasks = new Map();
5
6 // Set for unique categories
7 this.categories = new Set(['personal', 'work', 'shopping', 'health']);
8
9 // Symbols for private state keys
10 this.CURRENT_FILTER = Symbol('currentFilter');
11 this.SORT_ORDER = Symbol('sortOrder');
12
13 // WeakMap for component observers (garbage collected automatically)
14 this.observers = new WeakMap();
15
16 this.state = new Map([
17 [this.CURRENT_FILTER, 'all'],
18 [this.SORT_ORDER, 'dueDate']
19 ]);
20 }
21
22 // Advanced filtering with destructuring
23 getFilteredTasks() {
24 const currentFilter = this.state.get(this.CURRENT_FILTER);
25 const sortOrder = this.state.get(this.SORT_ORDER);
26
27 let filteredTasks = [...this.tasks.values()];
28
29 // Apply filters using modern array methods
30 if (currentFilter !== 'all') {
31 filteredTasks = filteredTasks.filter(({ category, completed }) => {
32 switch (currentFilter) {
33 case 'completed': return completed;
34 case 'pending': return !completed;
35 default: return category === currentFilter;
36 }
37 });
38 }
39
40 // Sort with enhanced object method
41 return filteredTasks.sort((a, b) => {
42 const { [sortOrder]: aValue } = a;
43 const { [sortOrder]: bValue } = b;
44 return new Date(aValue) - new Date(bValue);
45 });
46 }
47
48 // Observer pattern with WeakMap
49 subscribe(component, callback) {
50 this.observers.set(component, callback);
51 }
52
53 // Notify all observers using spread syntax
54 notifyObservers(eventType, data) {
55 const notifications = [];
56
57 for (const [component, callback] of this.observers) {
58 notifications.push(
59 Promise.resolve(callback({ eventType, data, ...this.getState() }))
60 );
61 }
62
63 return Promise.allSettled(notifications);
64 }
65
66 getState() {
67 return {
68 tasks: this.getFilteredTasks(),
69 categories: [...this.categories],
70 currentFilter: this.state.get(this.CURRENT_FILTER),
71 totalTasks: this.tasks.size
72 };
73 }
74 }Advanced Task Processing with Modern JavaScript:
1 class TaskProcessor {
2 static async processTaskBatch(tasks) {
3 // Parallel processing with async/await
4 const processingPromises = tasks.map(async (task) => {
5 const { priority, dueDate, ...taskData } = task;
6
7 // Advanced date processing
8 const timeUntilDue = new Date(dueDate) - new Date();
9 const isOverdue = timeUntilDue < 0;
10 const urgencyScore = this.calculateUrgency(priority, timeUntilDue);
11
12 return {
13 ...taskData,
14 priority,
15 dueDate,
16 isOverdue,
17 urgencyScore,
18 formattedDueDate: this.formatDate(dueDate)
19 };
20 });
21
22 const processedTasks = await Promise.all(processingPromises);
23 return processedTasks.sort((a, b) => b.urgencyScore - a.urgencyScore);
24 }
25
26 static calculateUrgency(priority, timeUntilDue) {
27 const priorityWeights = new Map([
28 [TaskPriority.LOW, 1],
29 [TaskPriority.MEDIUM, 2],
30 [TaskPriority.HIGH, 3],
31 [TaskPriority.URGENT, 4]
32 ]);
33
34 const baseScore = priorityWeights.get(priority) || 1;
35 const timeScore = Math.max(0, 7 - Math.abs(timeUntilDue) / (24 * 60 * 60 * 1000));
36
37 return baseScore * timeScore;
38 }
39
40 static formatDate(date) {
41 const options = {
42 year: 'numeric',
43 month: 'short',
44 day: 'numeric',
45 hour: '2-digit',
46 minute: '2-digit'
47 };
48 return new Intl.DateTimeFormat('en-US', options).format(new Date(date));
49 }
50 }Building reusable UI components using ES6 classes and inheritance:
Base Component Class:
1 class Component {
2 constructor(element) {
3 this.element = element;
4 this.state = new Map();
5 this.eventListeners = new Set();
6 this.bindMethods();
7 }
8
9 bindMethods() {
10 // Bind all methods to maintain 'this' context
11 Object.getOwnPropertyNames(Object.getPrototypeOf(this))
12 .filter(name => typeof this[name] === 'function' && name !== 'constructor')
13 .forEach(method => {
14 this[method] = this[method].bind(this);
15 });
16 }
17
18 setState(newState) {
19 const { ...currentState } = Object.fromEntries(this.state);
20 const { ...updatedState } = { ...currentState, ...newState };
21
22 this.state.clear();
23 Object.entries(updatedState).forEach(([key, value]) => {
24 this.state.set(key, value);
25 });
26
27 this.render();
28 this.onStateChange(updatedState);
29 }
30
31 addEventListener(event, handler) {
32 this.element.addEventListener(event, handler);
33 this.eventListeners.add({ event, handler });
34 }
35
36 cleanup() {
37 // Remove all event listeners
38 this.eventListeners.forEach(({ event, handler }) => {
39 this.element.removeEventListener(event, handler);
40 });
41 this.eventListeners.clear();
42 }
43
44 // Abstract methods to be implemented by subclasses
45 render() {
46 throw new Error('render() must be implemented by subclass');
47 }
48
49 onStateChange(newState) {
50 // Optional hook for subclasses
51 }
52 }Task Component with Inheritance:
1 class TaskComponent extends Component {
2 constructor(element, taskData) {
3 super(element);
4
5 const { id, title, category, priority, dueDate, completed } = taskData;
6
7 this.setState({
8 id,
9 title,
10 category,
11 priority,
12 dueDate,
13 completed,
14 isEditing: false
15 });
16
17 this.setupEventListeners();
18 }
19
20 setupEventListeners() {
21 // Modern event delegation with destructuring
22 this.addEventListener('click', ({ target }) => {
23 const { dataset: { action, taskId } } = target;
24
25 switch (action) {
26 case 'complete':
27 this.toggleComplete();
28 break;
29 case 'edit':
30 this.startEditing();
31 break;
32 case 'delete':
33 this.deleteTask();
34 break;
35 }
36 });
37 }
38
39 async toggleComplete() {
40 const currentCompleted = this.state.get('completed');
41
42 try {
43 await this.updateTask({ completed: !currentCompleted });
44 this.setState({ completed: !currentCompleted });
45 } catch (error) {
46 console.error('Failed to toggle task completion:', error);
47 }
48 }
49
50 render() {
51 const {
52 id, title, category, priority, dueDate, completed, isEditing
53 } = Object.fromEntries(this.state);
54
55 const priorityIcon = this.getPriorityIcon(priority);
56 const dueDateFormatted = TaskProcessor.formatDate(dueDate);
57
58 this.element.innerHTML = `
59 <div class="task-item ${completed ? 'completed' : ''}" data-task-id="${id}">
60 <div class="task-content">
61 ${isEditing ? this.renderEditForm() : this.renderTaskView()}
62 </div>
63 <div class="task-meta">
64 <span class="priority">${priorityIcon}</span>
65 <span class="category">${category}</span>
66 <span class="due-date">${dueDateFormatted}</span>
67 </div>
68 <div class="task-actions">
69 <button data-action="complete">${completed ? '↻' : '✓'}</button>
70 <button data-action="edit">✎</button>
71 <button data-action="delete">🗑</button>
72 </div>
73 </div>
74 `;
75 }
76
77 renderTaskView() {
78 const title = this.state.get('title');
79 return `<h3 class="task-title">${title}</h3>`;
80 }
81
82 renderEditForm() {
83 const title = this.state.get('title');
84 return `
85 <form class="edit-form">
86 <input type="text" value="${title}" class="task-title-input">
87 <button type="submit">Save</button>
88 <button type="button" data-action="cancel">Cancel</button>
89 </form>
90 `;
91 }
92
93 getPriorityIcon(priority) {
94 const icons = new Map([
95 [TaskPriority.LOW, '🟢'],
96 [TaskPriority.MEDIUM, '🟡'],
97 [TaskPriority.HIGH, '🟠'],
98 [TaskPriority.URGENT, '🔴']
99 ]);
100 return icons.get(priority) || '⚪';
101 }
102
103 async updateTask(updates) {
104 const currentState = Object.fromEntries(this.state);
105 const updatedTask = { ...currentState, ...updates };
106
107 // Simulate API call
108 return new Promise((resolve) => {
109 setTimeout(() => resolve(updatedTask), 300);
110 });
111 }
112 }Bringing everything together with modern JavaScript patterns and best practices:
Main Application Class:
1 class TaskManagerApp {
2 constructor() {
3 this.appState = new AppState();
4 this.taskController = new TaskController();
5 this.components = new Map();
6
7 // Use symbols for private methods
8 this[Symbol.for('initialized')] = false;
9
10 this.init();
11 }
12
13 async init() {
14 try {
15 // Load initial data
16 const tasks = await this.taskController.loadTasks();
17 tasks.forEach(task => this.appState.tasks.set(task.id, task));
18
19 // Initialize UI components
20 await this.initializeComponents();
21
22 // Setup global event handling
23 this.setupGlobalEvents();
24
25 this[Symbol.for('initialized')] = true;
26
27 console.log('Task Manager App initialized successfully');
28 } catch (error) {
29 console.error('Failed to initialize app:', error);
30 this.showError('Failed to load application');
31 }
32 }
33
34 async initializeComponents() {
35 // Modern component initialization with async/await
36 const componentPromises = [
37 this.createTaskForm(),
38 this.createTaskList(),
39 this.createFilterControls(),
40 this.createStatistics()
41 ];
42
43 const [taskForm, taskList, filterControls, statistics] = await Promise.all(componentPromises);
44
45 // Store components for later reference
46 this.components.set('taskForm', taskForm);
47 this.components.set('taskList', taskList);
48 this.components.set('filterControls', filterControls);
49 this.components.set('statistics', statistics);
50 }
51
52 async createTaskForm() {
53 const formElement = document.getElementById('task-form');
54
55 return new Promise((resolve) => {
56 const form = new (class extends Component {
57 constructor(element) {
58 super(element);
59 this.setupForm();
60 }
61
62 setupForm() {
63 this.addEventListener('submit', async (e) => {
64 e.preventDefault();
65
66 const formData = new FormData(e.target);
67 const taskData = {
68 title: formData.get('title'),
69 category: formData.get('category'),
70 priority: TaskPriority[formData.get('priority').toUpperCase()],
71 dueDate: formData.get('dueDate')
72 };
73
74 try {
75 await this.addTask(taskData);
76 e.target.reset();
77 } catch (error) {
78 this.showError('Failed to add task');
79 }
80 });
81 }
82
83 async addTask({ title, category, priority, dueDate }) {
84 const app = TaskManagerApp.getInstance();
85 await app.addTask({ title, category, priority, dueDate });
86 }
87
88 render() {
89 // Form rendering logic
90 }
91 })(formElement);
92
93 resolve(form);
94 });
95 }
96
97 // Singleton pattern for global app access
98 static getInstance() {
99 if (!TaskManagerApp.instance) {
100 TaskManagerApp.instance = new TaskManagerApp();
101 }
102 return TaskManagerApp.instance;
103 }
104
105 async addTask(taskData) {
106 try {
107 const newTask = await this.taskController.addTask(taskData);
108 this.appState.tasks.set(newTask.id, newTask);
109
110 await this.updateAllComponents();
111 return newTask;
112 } catch (error) {
113 console.error('Error adding task:', error);
114 throw error;
115 }
116 }
117
118 async updateAllComponents() {
119 const updatePromises = [];
120
121 for (const [name, component] of this.components) {
122 updatePromises.push(
123 component.setState?.(this.appState.getState()) || Promise.resolve()
124 );
125 }
126
127 await Promise.allSettled(updatePromises);
128 }
129
130 setupGlobalEvents() {
131 // Modern event handling with destructuring
132 document.addEventListener('keydown', ({ key, ctrlKey, metaKey }) => {
133 // Keyboard shortcuts
134 if ((ctrlKey || metaKey) && key === 'n') {
135 this.focusNewTaskForm();
136 }
137 });
138
139 // Handle app lifecycle
140 window.addEventListener('beforeunload', () => {
141 this.cleanup();
142 });
143 }
144
145 cleanup() {
146 // Clean up all components
147 for (const component of this.components.values()) {
148 component.cleanup?.();
149 }
150 this.components.clear();
151 }
152 }Application Bootstrap:
1 // Modern application initialization
2 const initApp = async () => {
3 try {
4 // Check for modern JavaScript support
5 if (!window.Map || !window.Set || !window.Symbol) {
6 throw new Error('Modern JavaScript features not supported');
7 }
8
9 // Initialize the application
10 const app = new TaskManagerApp();
11
12 // Global app reference for debugging
13 window.taskApp = app;
14
15 console.log('✅ Task Manager ready!');
16 } catch (error) {
17 console.error('❌ Failed to start Task Manager:', error);
18 document.body.innerHTML = `
19 <div class="error-screen">
20 <h1>Application Error</h1>
21 <p>${error.message}</p>
22 <button onclick="location.reload()">Reload</button>
23 </div>
24 `;
25 }
26 };
27
28 // Start the app when DOM is ready
29 if (document.readyState === 'loading') {
30 document.addEventListener('DOMContentLoaded', initApp);
31 } else {
32 initApp();
33 }This comprehensive project demonstrates all ES6+ features working together in a real-world application with modern architecture, proper error handling, and professional code organization.
Complete Task Management Application
1// Task Manager - Modern JavaScript Project
2// This application demonstrates all ES6+ features in a real-world context
3
4// === Core Application Classes ===
5
6class TaskManagerApp {
7 constructor() {
8 this.appState = new AppState();
9 this.taskController = new TaskController();
10 this.components = new Map();
11 this.init();
12 }
13
14 async init() {
15 try {
16 const tasks = await this.taskController.loadTasks();
17 tasks.forEach(task => this.appState.tasks.set(task.id, task));
18
19 await this.initializeComponents();
20 this.setupGlobalEvents();
21
22 console.log('Task Manager initialized successfully');
23 } catch (error) {
24 console.error('Failed to initialize app:', error);
25 }
26 }
27
28 async addTask({ title, category, priority, dueDate }) {
29 try {
30 const taskData = { title, category, priority, dueDate };
31 const newTask = await this.taskController.addTask(taskData);
32
33 this.appState.tasks.set(newTask.id, newTask);
34 await this.updateAllComponents();
35
36 return newTask;
37 } catch (error) {
38 console.error('Failed to add task:', error);
39 throw error;
40 }
41 }
42
43 async updateAllComponents() {
44 const updates = [];
45 const currentState = this.appState.getState();
46
47 for (const [name, component] of this.components) {
48 if (component.setState) {
49 updates.push(component.setState(currentState));
50 }
51 }
52
53 await Promise.allSettled(updates);
54 }
55
56 setupGlobalEvents() {
57 document.addEventListener('keydown', ({ key, ctrlKey }) => {
58 if (ctrlKey && key === 'n') {
59 document.getElementById('task-title')?.focus();
60 }
61 });
62 }
63}
64
65// === State Management ===
66
67class AppState {
68 constructor() {
69 this.tasks = new Map();
70 this.categories = new Set(['personal', 'work', 'shopping', 'health']);
71 this.observers = new WeakMap();
72
73 // Private symbols for state
74 this.CURRENT_FILTER = Symbol('currentFilter');
75 this.SORT_ORDER = Symbol('sortOrder');
76
77 this.state = new Map([
78 [this.CURRENT_FILTER, 'all'],
79 [this.SORT_ORDER, 'dueDate']
80 ]);
81 }
82
83 getFilteredTasks() {
84 const currentFilter = this.state.get(this.CURRENT_FILTER);
85 let filteredTasks = [...this.tasks.values()];
86
87 if (currentFilter !== 'all') {
88 filteredTasks = filteredTasks.filter(({ category, completed }) => {
89 switch (currentFilter) {
90 case 'completed': return completed;
91 case 'pending': return !completed;
92 default: return category === currentFilter;
93 }
94 });
95 }
96
97 return filteredTasks.sort((a, b) => new Date(a.dueDate) - new Date(b.dueDate));
98 }
99
100 getState() {
101 return {
102 tasks: this.getFilteredTasks(),
103 categories: [...this.categories],
104 currentFilter: this.state.get(this.CURRENT_FILTER),
105 totalTasks: this.tasks.size
106 };
107 }
108}
109
110// === Task Management ===
111
112const TaskPriority = Object.freeze({
113 LOW: Symbol('low'),
114 MEDIUM: Symbol('medium'),
115 HIGH: Symbol('high'),
116 URGENT: Symbol('urgent')
117});
118
119const createTask = ({ title, category, priority = TaskPriority.MEDIUM, dueDate }) => ({
120 id: Date.now().toString(36) + Math.random().toString(36).substr(2),
121 title,
122 category,
123 priority,
124 dueDate: new Date(dueDate),
125 createdAt: new Date(),
126 completed: false
127});
128
129class TaskController {
130 constructor() {
131 this.storage = new StorageService();
132 this.tasks = new Map();
133 }
134
135 async addTask(taskData) {
136 try {
137 const task = createTask(taskData);
138 await this.storage.saveTask(task);
139
140 this.tasks.set(task.id, task);
141 return task;
142 } catch (error) {
143 console.error('Failed to add task:', error);
144 throw error;
145 }
146 }
147
148 async loadTasks() {
149 try {
150 const tasks = await this.storage.getAllTasks();
151 tasks.forEach(task => {
152 const { id, ...taskData } = task;
153 this.tasks.set(id, { id, ...taskData });
154 });
155 return [...this.tasks.values()];
156 } catch (error) {
157 console.error('Failed to load tasks:', error);
158 return [];
159 }
160 }
161
162 async updateTask(id, updates) {
163 if (!this.tasks.has(id)) {
164 throw new Error('Task not found');
165 }
166
167 const currentTask = this.tasks.get(id);
168 const updatedTask = { ...currentTask, ...updates };
169
170 await this.storage.updateTask(updatedTask);
171 this.tasks.set(id, updatedTask);
172
173 return updatedTask;
174 }
175}
176
177// === Storage Service ===
178
179class StorageService {
180 async saveTask(task) {
181 return new Promise((resolve, reject) => {
182 setTimeout(() => {
183 try {
184 const tasks = this.getStoredTasks();
185 tasks.push(task);
186 localStorage.setItem('tasks', JSON.stringify(tasks));
187 resolve(task);
188 } catch (error) {
189 reject(new Error(`Failed to save task: ${error.message}`));
190 }
191 }, 100);
192 });
193 }
194
195 async getAllTasks() {
196 return new Promise((resolve) => {
197 const tasks = this.getStoredTasks();
198 resolve(tasks.map(task => ({
199 ...task,
200 createdAt: new Date(task.createdAt),
201 dueDate: new Date(task.dueDate)
202 })));
203 });
204 }
205
206 async updateTask(updatedTask) {
207 return new Promise((resolve) => {
208 const tasks = this.getStoredTasks();
209 const index = tasks.findIndex(t => t.id === updatedTask.id);
210
211 if (index !== -1) {
212 tasks[index] = updatedTask;
213 localStorage.setItem('tasks', JSON.stringify(tasks));
214 }
215
216 resolve(updatedTask);
217 });
218 }
219
220 getStoredTasks() {
221 return JSON.parse(localStorage.getItem('tasks') || '[]');
222 }
223}
224
225// === UI Components ===
226
227class Component {
228 constructor(element) {
229 this.element = element;
230 this.state = new Map();
231 this.eventListeners = new Set();
232 }
233
234 setState(newState) {
235 const currentState = Object.fromEntries(this.state);
236 const updatedState = { ...currentState, ...newState };
237
238 this.state.clear();
239 Object.entries(updatedState).forEach(([key, value]) => {
240 this.state.set(key, value);
241 });
242
243 this.render();
244 }
245
246 addEventListener(event, handler) {
247 this.element.addEventListener(event, handler);
248 this.eventListeners.add({ event, handler });
249 }
250
251 cleanup() {
252 this.eventListeners.forEach(({ event, handler }) => {
253 this.element.removeEventListener(event, handler);
254 });
255 this.eventListeners.clear();
256 }
257
258 render() {
259 // To be implemented by subclasses
260 }
261}
262
263class TaskListComponent extends Component {
264 constructor(element) {
265 super(element);
266 this.setState({ tasks: [] });
267 }
268
269 render() {
270 const tasks = this.state.get('tasks') || [];
271
272 this.element.innerHTML = `
273 <div class="task-list">
274 ${tasks.map(task => this.renderTask(task)).join('')}
275 </div>
276 `;
277
278 this.attachTaskEvents();
279 }
280
281 renderTask(task) {
282 const { id, title, category, priority, dueDate, completed } = task;
283 const priorityIcon = this.getPriorityIcon(priority);
284 const formattedDate = dueDate.toLocaleDateString();
285
286 return `
287 <div class="task-item ${completed ? 'completed' : ''}" data-task-id="${id}">
288 <div class="task-content">
289 <h3>${title}</h3>
290 <div class="task-meta">
291 <span class="priority">${priorityIcon}</span>
292 <span class="category">${category}</span>
293 <span class="due-date">${formattedDate}</span>
294 </div>
295 </div>
296 <div class="task-actions">
297 <button class="complete-btn" data-action="complete">${completed ? '↻' : '✓'}</button>
298 <button class="delete-btn" data-action="delete">🗑</button>
299 </div>
300 </div>
301 `;
302 }
303
304 getPriorityIcon(priority) {
305 const icons = new Map([
306 [TaskPriority.LOW, '🟢'],
307 [TaskPriority.MEDIUM, '🟡'],
308 [TaskPriority.HIGH, '🟠'],
309 [TaskPriority.URGENT, '🔴']
310 ]);
311 return icons.get(priority) || '⚪';
312 }
313
314 attachTaskEvents() {
315 this.element.addEventListener('click', async ({ target }) => {
316 const { action } = target.dataset;
317 const taskItem = target.closest('[data-task-id]');
318 const taskId = taskItem?.dataset.taskId;
319
320 if (!taskId) return;
321
322 switch (action) {
323 case 'complete':
324 await this.toggleTask(taskId);
325 break;
326 case 'delete':
327 await this.deleteTask(taskId);
328 break;
329 }
330 });
331 }
332
333 async toggleTask(taskId) {
334 // Simulate task toggle
335 console.log(`Toggling task: ${taskId}`);
336 }
337
338 async deleteTask(taskId) {
339 // Simulate task deletion
340 console.log(`Deleting task: ${taskId}`);
341 }
342}
343
344// === Application Initialization ===
345
346const initTaskManager = async () => {
347 try {
348 // Check for modern JavaScript support
349 if (!window.Map || !window.Set || !window.Symbol) {
350 throw new Error('Modern JavaScript features not supported');
351 }
352
353 const app = new TaskManagerApp();
354
355 // Initialize UI components
356 const taskListElement = document.getElementById('task-list');
357 if (taskListElement) {
358 const taskList = new TaskListComponent(taskListElement);
359 app.components.set('taskList', taskList);
360 }
361
362 // Set up form handling
363 const form = document.getElementById('task-form');
364 if (form) {
365 form.addEventListener('submit', async (e) => {
366 e.preventDefault();
367
368 const formData = new FormData(e.target);
369 const taskData = {
370 title: formData.get('title'),
371 category: formData.get('category') || 'personal',
372 priority: TaskPriority[formData.get('priority')?.toUpperCase()] || TaskPriority.MEDIUM,
373 dueDate: formData.get('dueDate') || new Date()
374 };
375
376 try {
377 await app.addTask(taskData);
378 e.target.reset();
379 } catch (error) {
380 alert(`Failed to add task: ${error.message}`);
381 }
382 });
383 }
384
385 // Global app reference
386 window.taskApp = app;
387 console.log('✅ Task Manager ready!');
388
389 } catch (error) {
390 console.error('❌ Failed to start Task Manager:', error);
391 }
392};
393
394// Initialize when DOM is ready
395if (document.readyState === 'loading') {
396 document.addEventListener('DOMContentLoaded', initTaskManager);
397} else {
398 initTaskManager();
399}Your progress is saved in this browser, on this device only.