Advance JavaScript শিখুন বাংলায় (পর্ব ১২) — localStorage-সহ পূর্ণাঙ্গ To-do List

Mini Project - localStorage সহ পূর্ণাঙ্গ To-do List

Advanced JavaScript সিরিজের এগারো পর্বে আমরা শিখেছি Closure, Scope/Hoisting, this, Destructuring, Spread/Rest, Promise, OOP (Class, Inheritance), localStorage persistence, Error Handling, আর Module। এই শেষ পর্বে সবকিছু একসাথে মিলিয়ে বানাব একটা সম্পূর্ণ কাজ করা To-do List — Class-ভিত্তিক, কাস্টম এরর হ্যান্ডলিং সহ, আর রিফ্রেশের পরও ডেটা ধরে রাখবে।

প্রজেক্টে যা যা প্রয়োগ হচ্ছে

  • Class (পর্ব ৭)Todo আর TodoList ক্লাস
  • Closure (পর্ব ১) — প্রাইভেট স্টেট রক্ষণাবেক্ষণ
  • Destructuring ও Spread (পর্ব ৪-৫) — ইমিউটেবল আপডেট
  • localStorage persist (পর্ব ৯)fromJSON প্যাটার্ন
  • Error Handling (পর্ব ১০) — কাস্টম ValidationError
  • this (পর্ব ৩) — ইভেন্ট হ্যান্ডলারে সঠিক কনটেক্সট

ধাপ ১ — Todo ক্লাস

একটা একক টাস্কের জন্য ক্লাস, সাথে persist করার জন্য fromJSON static মেথড (পর্ব ৯-এর প্যাটার্ন):

class ValidationError extends Error {
  constructor(message) {
    super(message);
    this.name = "ValidationError";
  }
}

class Todo {
  constructor(text, done = false, id = Date.now()) {
    if (!text || text.trim() === "") {
      throw new ValidationError("টাস্কের লেখা খালি রাখা যাবে না");
    }
    this.id = id;
    this.text = text.trim();
    this.done = done;
  }

  static fromJSON(data) {
    return new Todo(data.text, data.done, data.id);
  }
}

ধাপ ২ — TodoList ক্লাস (প্রাইভেট স্টেট + persist)

প্রাইভেট ফিল্ড (#, পর্ব ৭) দিয়ে স্টেট সুরক্ষিত রেখে, Spread (পর্ব ৫) দিয়ে ইমিউটেবল আপডেট করা হচ্ছে:

class TodoList {
  #todos = [];
  #storageKey;

  constructor(storageKey) {
    this.#storageKey = storageKey;
    this.load();
  }

  add(text) {
    const todo = new Todo(text); // ভুল ইনপুট হলে ValidationError থ্রো হবে
    this.#todos = [...this.#todos, todo]; // ইমিউটেবল আপডেট
    this.save();
    return todo;
  }

  toggle(id) {
    this.#todos = this.#todos.map((todo) =>
      todo.id === id ? { ...todo, done: !todo.done } : todo
    );
    this.save();
  }

  remove(id) {
    this.#todos = this.#todos.filter((todo) => todo.id !== id);
    this.save();
  }

  get all() {
    return [...this.#todos]; // কপি রিটার্ন — বাইরে থেকে সরাসরি পাল্টানো যাবে না
  }

  get stats() {
    const total = this.#todos.length;
    const completed = this.#todos.filter((t) => t.done).length;
    return { total, completed, remaining: total - completed };
  }

  save() {
    localStorage.setItem(this.#storageKey, JSON.stringify(this.#todos));
  }

  load() {
    try {
      const raw = JSON.parse(localStorage.getItem(this.#storageKey)) || [];
      this.#todos = raw.map((data) => Todo.fromJSON(data));
    } catch (error) {
      console.error("টাস্ক লোড করতে সমস্যা হয়েছে:", error);
      this.#todos = [];
    }
  }
}

ধাপ ৩ — UI সংযুক্ত করা (this সচেতনতা)

পর্ব ৩-এ শেখা this সচেতনতা এখানে গুরুত্বপূর্ণ — arrow function ব্যবহার করে this ঠিক রাখা হচ্ছে:

const list = new TodoList("myTodos");
const form = document.getElementById("todoForm");
const input = document.getElementById("todoInput");
const listEl = document.getElementById("todoListEl");
const statsEl = document.getElementById("statsEl");

function render() {
  listEl.innerHTML = "";
  list.all.forEach((todo) => {
    const li = document.createElement("li");
    li.textContent = todo.text;
    li.style.textDecoration = todo.done ? "line-through" : "none";

    // arrow function — this এখানে দরকার নেই, কিন্তু লেক্সিক্যাল স্কোপ থেকে todo, list ঠিকই অ্যাক্সেস হচ্ছে
    li.addEventListener("click", () => {
      list.toggle(todo.id);
      render();
    });

    listEl.appendChild(li);
  });

  const { total, completed, remaining } = list.stats; // Destructuring, পর্ব ৪
  statsEl.textContent = `মোট: ${total}, সম্পন্ন: ${completed}, বাকি: ${remaining}`;
}

form.addEventListener("submit", (e) => {
  e.preventDefault();
  try {
    list.add(input.value);
    input.value = "";
    render();
  } catch (error) {
    if (error instanceof ValidationError) {
      alert(error.message); // কাস্টম এরর, পর্ব ১০
    }
  }
});

render();

লাইভ ডেমো — সম্পূর্ণ কাজ করা To-do List

নিচে সবকিছু একসাথে কাজ করছে — টাস্ক যোগ করো, ক্লিক করে সম্পন্ন করো, মুছে ফেলো, পেজ রিফ্রেশ করেও দেখো ডেটা থেকে যাচ্ছে।

    চেষ্টা করে দেখো: খালি বক্সে সাবমিট করলে ValidationError ধরা পড়ে alert দেখাবে। টাস্কের লেখায় ক্লিক করলে সম্পন্ন/অসম্পন্ন টগল হবে। পেজ রিফ্রেশ করলেও সব থেকে যাবে।

    কোড কীভাবে সিরিজের সব শেখা মেলাচ্ছে — সংক্ষেপে

    প্রজেক্টের অংশ কোন পর্বের কনসেপ্ট
    #todos প্রাইভেট ফিল্ড পর্ব ৭ (Class)
    ValidationError extends Error পর্ব ৮ (Inheritance), পর্ব ১০ (Error Handling)
    { ...todo, done: !todo.done } পর্ব ৫ (Spread)
    const { total, completed } = list.stats পর্ব ৪ (Destructuring)
    Todo.fromJSON(data) পর্ব ৯ (localStorage persist)
    Arrow function ইভেন্ট হ্যান্ডলার পর্ব ৩ (this)

    পরবর্তী উন্নতির আইডিয়া

    Exercise: প্রজেক্টটা আরও ভালো করার জন্য নিজে চেষ্টা করো —

    1. টাস্ক এডিট করার ফিচার যোগ করো (ডাবল-ক্লিক করলে ইনপুটে বদলে যাবে)।
    2. ফিল্টার বাটন যোগ করো — "সব", "সম্পন্ন", "বাকি" অনুযায়ী তালিকা দেখানো।
    3. একটা clearCompleted() মেথড TodoList-এ যোগ করো, যেটা সব সম্পন্ন টাস্ক একসাথে মুছে দেবে।
    4. প্রজেক্টটা পর্ব ১১-এর টেকনিক দিয়ে models/todo.js, models/todo-list.js, main.js-এ আলাদা ফাইলে ভাগ করো।

    সিরিজ শেষ!

    Advanced JavaScript সিরিজের বারো পর্বে আমরা Closure থেকে শুরু করে Scope, this, Destructuring, Spread/Rest, Promise, OOP, Error Handling, Module পর্যন্ত গিয়েছি, আর আজ সেগুলো একসাথে মিলিয়ে একটা সত্যিকারের কাজ করা প্রজেক্ট বানিয়েছি।

    একটি মন্তব্য পোস্ট করুন

    নবীনতর পূর্বতন

    যোগাযোগ ফর্ম