JavaScript📅 2026-09-29⏱ 10 دقائق قراءة📜 مقال 10 من 10

مشروع قائمة المهام بـ JavaScript — دليل عملي شامل 2026

مشروع عملي لبناء تطبيق قائمة مهام كامل بـ JavaScript — إضافة، حذف، تعديل، حفظ تلقائي، مع الكود الكامل والشرح

في المقال السابق، بنيت آلة حاسبة كاملة. الآن سنرتقي بمستوى المشروع — سنبني تطبيق قائمة مهام (Todo App) احترافي، مع حفظ تلقائي للبيانات.

هذا المشروع سيعلمك:

  • التعامل مع النماذج (Forms).
  • إنشاء عناصر ديناميكياً.
  • حفظ البيانات في localStorage.
  • تعديل العناصر وحذفها.
  • تصفية المهام (الكل، النشطة، المكتملة).

ما سنبنيه

تطبيق قائمة مهام بوظائف:

  • إضافة مهمة جديدة.
  • تعديل مهمة موجودة.
  • حذف مهمة.
  • تعليم مهمة كمكتملة.
  • تصفية المهام (الكل، النشطة، المكتملة).
  • حذف المهام المكتملة.
  • حفظ تلقائي في المتصفح.

هيكل المشروع

سننشئ 3 ملفات:

todo-app/
├── index.html
├── style.css
└── script.js

الخطوة 1: HTML — هيكل التطبيق

index.html:

<!DOCTYPE html>
<html lang="ar" dir="rtl">
<head>
  <meta charset="UTF-8">
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  <title>قائمة المهام | JavaScript</title>
  <link rel="stylesheet" href="style.css">
</head>
<body>
  <div class="app">
    <header class="app-header">
      <h1>📝 قائمة المهام</h1>
      <p class="date" id="date"></p>
    </header>

    <form class="todo-form" id="todoForm">
      <input 
        type="text" 
        id="todoInput" 
        placeholder="أضف مهمة جديدة..."
        autocomplete="off"
      >
      <button type="submit" class="btn-add">إضافة</button>
    </form>

    <div class="filters">
      <button class="filter active" data-filter="all">الكل</button>
      <button class="filter" data-filter="active">النشطة</button>
      <button class="filter" data-filter="completed">المكتملة</button>
    </div>

    <ul class="todo-list" id="todoList"></ul>

    <footer class="app-footer">
      <span id="itemsLeft">0 مهام متبقية</span>
      <button id="clearCompleted" class="btn-clear">حذف المكتملة</button>
    </footer>
  </div>

  <script src="script.js"></script>
</body>
</html>

شرح:

  • header: العنوان والتاريخ.
  • form: حقل الإدخال وزر الإضافة.
  • filters: أزرار التصفية.
  • todoList: قائمة المهام (فارغة، تُملأ بـ JavaScript).
  • footer: عدد المهام المتبقية وزر حذف المكتملة.

الخطوة 2: CSS — تصميم التطبيق

style.css:

* {
  margin: 0;
  padding: 0;
  box-sizing: border-box;
  font-family: 'Segoe UI', Tahoma, sans-serif;
}

body {
  background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
  min-height: 100vh;
  display: flex;
  justify-content: center;
  align-items: flex-start;
  padding: 40px 20px;
}

.app {
  background: #ffffff;
  border-radius: 20px;
  padding: 30px;
  width: 100%;
  max-width: 500px;
  box-shadow: 0 20px 60px rgba(0, 0, 0, 0.2);
}

.app-header {
  text-align: center;
  margin-bottom: 25px;
}

.app-header h1 {
  color: #1e293b;
  font-size: 1.8rem;
  margin-bottom: 8px;
}

.date {
  color: #64748b;
  font-size: 0.9rem;
}

/* ========== النموذج ========== */
.todo-form {
  display: flex;
  gap: 10px;
  margin-bottom: 20px;
}

#todoInput {
  flex: 1;
  padding: 14px 18px;
  border: 2px solid #e2e8f0;
  border-radius: 12px;
  font-size: 1rem;
  transition: border-color 0.2s;
}

#todoInput:focus {
  outline: none;
  border-color: #667eea;
}

.btn-add {
  padding: 14px 24px;
  background: #667eea;
  color: white;
  border: none;
  border-radius: 12px;
  font-size: 1rem;
  font-weight: bold;
  cursor: pointer;
  transition: all 0.2s;
}

.btn-add:hover {
  background: #5568d3;
  transform: translateY(-2px);
}

/* ========== الفلاتر ========== */
.filters {
  display: flex;
  gap: 8px;
  margin-bottom: 20px;
  padding-bottom: 15px;
  border-bottom: 2px solid #f1f5f9;
}

.filter {
  flex: 1;
  padding: 10px;
  background: transparent;
  border: 2px solid #e2e8f0;
  border-radius: 10px;
  color: #64748b;
  font-size: 0.9rem;
  cursor: pointer;
  transition: all 0.2s;
}

.filter:hover {
  border-color: #667eea;
  color: #667eea;
}

.filter.active {
  background: #667eea;
  color: white;
  border-color: #667eea;
}

/* ========== القائمة ========== */
.todo-list {
  list-style: none;
  margin-bottom: 20px;
  max-height: 400px;
  overflow-y: auto;
}

.todo-item {
  display: flex;
  align-items: center;
  gap: 12px;
  padding: 14px;
  background: #f8fafc;
  border-radius: 12px;
  margin-bottom: 10px;
  transition: all 0.2s;
  animation: slideIn 0.3s ease;
}

@keyframes slideIn {
  from {
    opacity: 0;
    transform: translateY(-10px);
  }
  to {
    opacity: 1;
    transform: translateY(0);
  }
}

.todo-item:hover {
  background: #f1f5f9;
}

.todo-checkbox {
  width: 22px;
  height: 22px;
  cursor: pointer;
  accent-color: #667eea;
  flex-shrink: 0;
}

.todo-text {
  flex: 1;
  color: #1e293b;
  font-size: 1rem;
  word-break: break-word;
}

.todo-item.completed .todo-text {
  text-decoration: line-through;
  color: #94a3b8;
}

.todo-actions {
  display: flex;
  gap: 6px;
  flex-shrink: 0;
}

.btn-icon {
  width: 34px;
  height: 34px;
  border: none;
  border-radius: 8px;
  cursor: pointer;
  font-size: 1rem;
  transition: all 0.2s;
  display: flex;
  align-items: center;
  justify-content: center;
}

.btn-edit {
  background: #dbeafe;
  color: #1e40af;
}

.btn-edit:hover {
  background: #bfdbfe;
}

.btn-delete {
  background: #fee2e2;
  color: #991b1b;
}

.btn-delete:hover {
  background: #fecaca;
}

/* ========== التذييل ========== */
.app-footer {
  display: flex;
  justify-content: space-between;
  align-items: center;
  padding-top: 15px;
  border-top: 2px solid #f1f5f9;
  font-size: 0.9rem;
  color: #64748b;
}

.btn-clear {
  background: transparent;
  border: none;
  color: #ef4444;
  font-size: 0.9rem;
  cursor: pointer;
  padding: 8px 12px;
  border-radius: 8px;
  transition: all 0.2s;
}

.btn-clear:hover {
  background: #fee2e2;
}

/* ========== الحالة الفارغة ========== */
.empty-state {
  text-align: center;
  padding: 40px 20px;
  color: #94a3b8;
}

.empty-state-icon {
  font-size: 3rem;
  margin-bottom: 12px;
}

/* ========== الوضع الداكن ========== */
@media (prefers-color-scheme: dark) {
  .app {
    background: #1e293b;
  }
  .app-header h1,
  .todo-text {
    color: #f1f5f9;
  }
  .todo-item {
    background: #334155;
  }
  .todo-item:hover {
    background: #475569;
  }
  #todoInput {
    background: #334155;
    border-color: #475569;
    color: #f1f5f9;
  }
}

الخطوة 3: JavaScript — منطق التطبيق

script.js:

// ==================== المتغيرات ====================
let todos = [];
let currentFilter = "all";

// ==================== العناصر ====================
const form = document.querySelector("#todoForm");
const input = document.querySelector("#todoInput");
const todoList = document.querySelector("#todoList");
const itemsLeft = document.querySelector("#itemsLeft");
const clearCompletedBtn = document.querySelector("#clearCompleted");
const filterBtns = document.querySelectorAll(".filter");
const dateEl = document.querySelector("#date");

// ==================== التاريخ ====================
function displayDate() {
  const options = { weekday: "long", year: "numeric", month: "long", day: "numeric" };
  dateEl.textContent = new Date().toLocaleDateString("ar-EG", options);
}

// ==================== localStorage ====================
function saveTodos() {
  localStorage.setItem("todos", JSON.stringify(todos));
}

function loadTodos() {
  const stored = localStorage.getItem("todos");
  if (stored) {
    todos = JSON.parse(stored);
  }
}

// ==================== إضافة مهمة ====================
function addTodo(text) {
  const todo = {
    id: Date.now(),
    text: text.trim(),
    completed: false,
    createdAt: new Date().toISOString(),
  };
  todos.push(todo);
  saveTodos();
  render();
}

// ==================== حذف مهمة ====================
function deleteTodo(id) {
  todos = todos.filter((t) => t.id !== id);
  saveTodos();
  render();
}

// ==================== تعديل مهمة ====================
function editTodo(id) {
  const todo = todos.find((t) => t.id === id);
  if (!todo) return;

  const newText = prompt("عدّل المهمة:", todo.text);
  if (newText !== null && newText.trim() !== "") {
    todo.text = newText.trim();
    saveTodos();
    render();
  }
}

// ==================== تعليم كمكتملة ====================
function toggleTodo(id) {
  const todo = todos.find((t) => t.id === id);
  if (todo) {
    todo.completed = !todo.completed;
    saveTodos();
    render();
  }
}

// ==================== حذف المكتملة ====================
function clearCompleted() {
  const completedCount = todos.filter((t) => t.completed).length;
  if (completedCount === 0) return;

  if (confirm(`هل تريد حذف ${completedCount} مهمة مكتملة؟`)) {
    todos = todos.filter((t) => !t.completed);
    saveTodos();
    render();
  }
}

// ==================== الفلترة ====================
function getFilteredTodos() {
  switch (currentFilter) {
    case "active":
      return todos.filter((t) => !t.completed);
    case "completed":
      return todos.filter((t) => t.completed);
    default:
      return todos;
  }
}

// ==================== العرض ====================
function render() {
  const filtered = getFilteredTodos();

  // القائمة الفارغة
  if (filtered.length === 0) {
    todoList.innerHTML = `
      <div class="empty-state">
        <div class="empty-state-icon">📭</div>
        <p>لا توجد مهام هنا</p>
      </div>
    `;
  } else {
    todoList.innerHTML = filtered
      .map(
        (todo) => `
      <li class="todo-item ${todo.completed ? "completed" : ""}" data-id="${todo.id}">
        <input 
          type="checkbox" 
          class="todo-checkbox" 
          ${todo.completed ? "checked" : ""}
          data-action="toggle"
        >
        <span class="todo-text">${escapeHtml(todo.text)}</span>
        <div class="todo-actions">
          <button class="btn-icon btn-edit" data-action="edit" title="تعديل">✏️</button>
          <button class="btn-icon btn-delete" data-action="delete" title="حذف">🗑️</button>
        </div>
      </li>
    `
      )
      .join("");
  }

  // عدد المهام
  const activeCount = todos.filter((t) => !t.completed).length;
  itemsLeft.textContent = `${activeCount} ${
    activeCount === 1 ? "مهمة متبقية" : "مهام متبقية"
  }`;
}

// ==================== حماية من XSS ====================
function escapeHtml(text) {
  const div = document.createElement("div");
  div.textContent = text;
  return div.innerHTML;
}

// ==================== الأحداث ====================

// إرسال النموذج
form.addEventListener("submit", (e) => {
  e.preventDefault();
  const text = input.value.trim();
  if (text === "") return;
  addTodo(text);
  input.value = "";
  input.focus();
});

// أحداث القائمة (Event Delegation)
todoList.addEventListener("click", (e) => {
  const target = e.target;
  const todoItem = target.closest(".todo-item");
  if (!todoItem) return;

  const id = Number(todoItem.dataset.id);
  const action = target.dataset.action;

  if (action === "toggle") {
    toggleTodo(id);
  } else if (action === "edit") {
    editTodo(id);
  } else if (action === "delete") {
    deleteTodo(id);
  }
});

// الفلاتر
filterBtns.forEach((btn) => {
  btn.addEventListener("click", () => {
    filterBtns.forEach((b) => b.classList.remove("active"));
    btn.classList.add("active");
    currentFilter = btn.dataset.filter;
    render();
  });
});

// حذف المكتملة
clearCompletedBtn.addEventListener("click", clearCompleted);

// ==================== التهيئة ====================
displayDate();
loadTodos();
render();

شرح الكود خطوة بخطوة

1. هيكل البيانات

const todo = {
  id: Date.now(),         // معرف فريد
  text: "نص المهمة",       // نص المهمة
  completed: false,       // هل مكتملة؟
  createdAt: "...",       // تاريخ الإنشاء
};

2. حفظ البيانات في localStorage

function saveTodos() {
  localStorage.setItem("todos", JSON.stringify(todos));
}

function loadTodos() {
  const stored = localStorage.getItem("todos");
  if (stored) todos = JSON.parse(stored);
}

الفائدة: البيانات تبقى حتى بعد إغلاق المتصفح.

3. إضافة مهمة

function addTodo(text) {
  todos.push({
    id: Date.now(),
    text: text.trim(),
    completed: false,
  });
  saveTodos();
  render();
}

4. الفلترة

function getFilteredTodos() {
  switch (currentFilter) {
    case "active":    return todos.filter((t) => !t.completed);
    case "completed": return todos.filter((t) => t.completed);
    default:          return todos;
  }
}

5. Event Delegation

todoList.addEventListener("click", (e) => {
  const target = e.target;
  const todoItem = target.closest(".todo-item");
  if (!todoItem) return;
  
  const id = Number(todoItem.dataset.id);
  const action = target.dataset.action;
  
  if (action === "toggle") toggleTodo(id);
  else if (action === "edit") editTodo(id);
  else if (action === "delete") deleteTodo(id);
});

الفائدة: مستمع واحد لكل العناصر (حتى المضافة ديناميكياً).

6. حماية من XSS

function escapeHtml(text) {
  const div = document.createElement("div");
  div.textContent = text;
  return div.innerHTML;
}

الفائدة: منع تنفيذ أكواد ضارة من المستخدم.

تمارين إضافية

تمرين 1: إضافة أولوية للمهمة

أضف خيار "أولوية" (عالية، متوسطة، منخفضة).

الحل:

// في addTodo:
todos.push({
  id: Date.now(),
  text: text.trim(),
  priority: "متوسطة",  // الافتراضي
  completed: false,
});

// في HTML للعرض:
// <span class="priority-${todo.priority}">${todo.priority}</span>

تمرين 2: إضافة تاريخ استحقاق

أضف تاريخ استحقاق لكل مهمة.

الحل:

todos.push({
  id: Date.now(),
  text: text.trim(),
  dueDate: new Date().toISOString(),
  completed: false,
});

// عرض التاريخ في القائمة

تمرين 3: البحث في المهام

أضف حقل بحث لتصفية المهام.

الحل:

<input type="text" id="searchInput" placeholder="ابحث...">
const searchInput = document.querySelector("#searchInput");
let searchQuery = "";

searchInput.addEventListener("input", (e) => {
  searchQuery = e.target.value.toLowerCase();
  render();
});

// في getFilteredTodos:
return todos.filter((t) => 
  t.text.toLowerCase().includes(searchQuery)
);

تمرين 4: التعديل المباشر (Inline Edit)

بدلاً من prompt، اجعل النص قابلاً للتعديل مباشرة.

الحل:

function editTodo(id) {
  const todoItem = document.querySelector(`.todo-item[data-id="${id}"]`);
  const textSpan = todoItem.querySelector(".todo-text");
  const originalText = textSpan.textContent;
  
  textSpan.contentEditable = true;
  textSpan.focus();
  
  textSpan.addEventListener("blur", () => {
    const newText = textSpan.textContent.trim();
    if (newText && newText !== originalText) {
      const todo = todos.find((t) => t.id === id);
      todo.text = newText;
      saveTodos();
    }
    textSpan.contentEditable = false;
    render();
  });
}

تمرين 5: الإشعارات

أضف إشعار عند إكمال مهمة.

الحل:

function notify(message) {
  if ("Notification" in window && Notification.permission === "granted") {
    new Notification("قائمة المهام", { body: message });
  }
}

// في toggleTodo:
if (todo.completed) {
  notify(`أكملت: ${todo.text}`);
}

حل المشاكل الشائعة

🔴 المشكلة 1: البيانات تُفقد عند التحديث

السبب: لم يتم حفظ البيانات.

الحل: استخدم localStorage (saveTodos بعد كل تعديل).

🔴 المشكلة 2: XSS (حقن أكواد)

السبب: استخدام innerHTML مع بيانات المستخدم مباشرة.

الحل:

// خطأ:
todoList.innerHTML = `<span>${todo.text}</span>`;

// صحيح:
todoList.innerHTML = `<span>${escapeHtml(todo.text)}</span>`;

🔴 المشكلة 3: Date.now() ينتج نفس المعرف

السبب: إضافة مهام في نفس المللي ثانية.

الحل: استخدم crypto.randomUUID():

id: crypto.randomUUID(),

🔴 المشكلة 4: المهام لا تظهر بعد الإضافة

السبب: نسيت استدعاء render().

الحل: تأكد من استدعاء render() بعد كل تعديل.

قائمة تحقق نهائية

المهمةالحالة
إنشاء ملف index.html⬜
إنشاء ملف style.css⬜
إنشاء ملف script.js⬜
تشغيل التطبيق في المتصفح⬜
اختبار إضافة مهمة⬜
اختبار تعديل وحذف مهمة⬜
اختبار الفلاتر⬜
التأكد من حفظ البيانات بعد التحديث⬜
حل تمرين واحد على الأقل إضافي⬜

ماذا بعد هذا المشروع؟

🎉 مبروك! لقد أكملت مسار JavaScript بالكامل!

الآن أنت جاهز لـ:

  1. تطوير مشاريع أكثر تعقيداً: مدونات، متاجر إلكترونية، تطبيقات.
  2. تعلم أطر العمل: React، Vue، Angular.
  3. تعلم Backend: Node.js، Express.
  4. تعلم قواعد البيانات: MongoDB، PostgreSQL.
  5. بناء معرض أعمالك: ارفع مشاريعك على GitHub.

الخلاصة

في هذا المشروع، تعلمت:

  • ✅ إدارة الحالة — تخزين المهام في مصفوفة.
  • ✅ localStorage — حفظ البيانات في المتصفح.
  • ✅ DOM المتقدم — إنشاء وتعديل العناصر.
  • ✅ Event Delegation — مستمع واحد للعناصر المتعددة.
  • ✅ الفلترة — عرض المهام حسب الحالة.
  • ✅ حماية XSS — تنظيف النصوص.
  • ✅ تصميم تفاعلي — CSS مع animations.

هذا المشروع هو أساس كل تطبيقات المهام الاحترافية (مثل Todoist و Microsoft To Do). احتفظ بالكود، وطور فيه بنفسك!

🎉 هل أكملت مسار JavaScript بالكامل؟ شاركنا في التعليقات!

📚 مقالات ذات صلة

JavaScript

مشروع آلة حاسبة بـ JavaScript — دليل عملي شامل 2026

مشروع عملي لبناء آلة حاسبة كاملة بـ JavaScript و HTML و CSS — خطوة بخطوة مع الكود الكامل والشرح

JavaScript

التعامل مع DOM في JavaScript — دليل شامل 2026

دليل عملي مفصل للتعامل مع DOM في JavaScript — التحديد، التعديل، الأحداث، الإنشاء، مع تمارين وحلول

JavaScript

المصفوفات والكائنات في JavaScript — دليل شامل 2026

دليل عملي مفصل للمصفوفات والكائنات في JavaScript — الإنشاء، التعديل، الطرق الشائعة، مع تمارين وحلول