Loyiha: Vazifalar ro'yxati

Endi HTML, CSS va JavaScript bo'yicha o'rganganlarimizni bitta haqiqiy ilovaga yig'amiz — vazifalar ro'yxati (inglizcha to-do list). Vazifa qo'shish, bajarilganini belgilash, o'chirish va eng muhimi — sahifa yopilsa ham hammasini eslab qolish. Ilovani qadamma-qadam quramiz, har bir qadam natijasini darhol ko'rib boramiz.

Bu darsda

  • Ilovani rejalashtirish va fayllar tuzilishi
  • HTML tuzilish va CSS bezak
  • Vazifa qo'shish, bajarilgan deb belgilash va o'chirish
  • Hisoblagich: «3 ta vazifa, 1 tasi bajarildi»
  • localStorage va JSON bilan saqlash
  • «Holat → chizish» g'oyasi
  • Kodni VS Code'da uch faylga ajratib, Live Server'da ishga tushirish

Reja: ilova nima qiladi?

Dasturchilar kod yozishdan oldin ilova nima qilishi kerakligini oddiy so'zlar bilan yozib oladi. Bizning ilovamiz:

  1. Maydonga vazifa yozib, «Qo'shish» (yoki Enter) bosilsa — vazifa ro'yxatga qo'shiladi. Bo'sh vazifa qo'shilmaydi.
  2. Vazifa ustiga bosilsa — u bajarilgan deb belgilanadi (ustidan chiziladi). Yana bosilsa — qaytadi.
  3. Har bir vazifa yonidagi × tugmasi uni o'chiradi.
  4. Pastda hisob ko'rinadi: «3 ta vazifa, 1 tasi bajarildi».
  5. Sahifa yopilib, qayta ochilsa ham vazifalar saqlanib qoladi.

Kompyuteringizda ilova uchun alohida papka bo'ladi. Unda uchta fayl — saytning uch qatlami (1-dars):

vazifalar/
├── index.html    ← tuzilish (HTML)
├── style.css     ← ko'rinish (CSS)
└── script.js     ← harakat (JavaScript)

Dars davomida kodni shu sahifadagi muharrirlarda yozamiz: u yerda HTML, CSS va JavaScript bitta oynada turadi. Oxirida uni uch faylga qanday ajratishni ko'rsatamiz.

1-qadam. HTML tuzilish

Avval ilovaning «skeleti»: sarlavha, vazifa yoziladigan forma, ro'yxat va hisob uchun joy. Ro'yxatga hozircha ikkita namunaviy vazifani qo'lda yozib qo'yamiz — bezash paytida qanday ko'rinishini bilish uchun. Keyinroq ularni JavaScript yaratadi.

<div class="ilova">
  <h1>📝 Vazifalar</h1>

  <form id="forma">
    <input id="matn" placeholder="Yangi vazifa..." autocomplete="off">
    <button>Qo'shish</button>
  </form>

  <ul id="royxat">
    <li>
      <span>Kitob o'qish</span> <button class="ochir">×</button>
    </li>
    <li class="bajarildi">
      <span>Non olish</span> <button class="ochir">×</button>
    </li>
  </ul>

  <p id="hisob">2 ta vazifa, 1 tasi bajarildi</p>
</div>

Har bir qismga nom berdik, chunki JavaScript ularni shu nomlar bo'yicha topadi:

2-qadam. CSS bilan bezash

Endi ko'rinish. Bu yerda yangi narsa deyarli yo'q — CSS darslaridagi selektorlar, ranglar, padding va flex:

<style>
  * { box-sizing: border-box; }
  body { font-family: sans-serif; background: #f2f5fb;
         margin: 0; padding: 16px; }
  .ilova {
    max-width: 480px; margin: 0 auto; padding: 20px;
    background: white; border-radius: 14px;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
  }
  h1 { margin: 0 0 14px; font-size: 24px; }
  #forma { display: flex; gap: 8px; }
  #matn {
    flex: 1; padding: 10px; font-size: 16px;
    border: 2px solid #d5dbe8; border-radius: 8px;
  }
  #matn:focus { outline: none; border-color: #2563eb; }
  #forma button {
    padding: 10px 16px; font-size: 16px; cursor: pointer;
    border: none; border-radius: 8px; background: #2563eb; color: white;
  }
  #royxat { list-style: none; padding: 0; margin: 16px 0 8px; }
  #royxat li {
    display: flex; align-items: center; gap: 10px;
    padding: 10px 12px; border-bottom: 1px solid #eceff5; cursor: pointer;
  }
  #royxat li:hover { background: #f7f9fd; }
  /* chap tomondagi doiracha */
  #royxat li::before {
    content: ""; width: 18px; height: 18px; flex-shrink: 0;
    border: 2px solid #c5cddd; border-radius: 50%;
  }
  #royxat li span { flex: 1; }
  .bajarildi span { text-decoration: line-through; color: #9aa3b5; }
  #royxat li.bajarildi::before {
    background: #22a06b; border-color: #22a06b;
  }
  .ochir {
    border: none; background: none; cursor: pointer;
    color: #c0392b; font-size: 20px; line-height: 1;
  }
  #hisob { margin: 0; color: #6b7385; font-size: 14px; }
</style>

<div class="ilova">
  <h1>📝 Vazifalar</h1>

  <form id="forma">
    <input id="matn" placeholder="Yangi vazifa..." autocomplete="off">
    <button>Qo'shish</button>
  </form>

  <ul id="royxat">
    <li>
      <span>Kitob o'qish</span> <button class="ochir">×</button>
    </li>
    <li class="bajarildi">
      <span>Non olish</span> <button class="ochir">×</button>
    </li>
  </ul>

  <p id="hisob">2 ta vazifa, 1 tasi bajarildi</p>
</div>

E'tiborga loyiq joylar:

Ilova chiroyli, lekin hali «jim»: tugmalar ishlamaydi. Navbat JavaScript'ga.

3-qadam. Vazifa qo'shish

Forma yuborilganda (17-darsdagi submit hodisasi) quyidagilar bajarilishi kerak:

  1. sahifa qayta yuklanmasligi uchun — event.preventDefault();
  2. maydondagi matnni olish va chetlaridagi bo'sh joylarni olib tashlash — trim();
  3. matn bo'sh bo'lsa — hech narsa qilmaslik (return funksiyadan darhol chiqib ketadi);
  4. yangi <li> yaratib, ro'yxatga qo'shish;
  5. maydonni tozalash.

Endi HTML'dagi namunaviy vazifalarni olib tashlaymiz — ularni JavaScript yaratadi. CSS'ni esa bu qadamlarda joy tejash uchun qisqartirilgan holda beramiz:

<style>
  body { font-family: sans-serif; background: #f2f5fb; }
  .ilova { max-width: 440px; margin: 0 auto; padding: 16px;
           background: white; border-radius: 12px; }
  #forma { display: flex; gap: 8px; }
  #matn { flex: 1; padding: 8px; font-size: 16px; }
  #royxat { list-style: none; padding: 0; }
  #royxat li { display: flex; gap: 10px; padding: 8px;
               border-bottom: 1px solid #eee; cursor: pointer; }
  #royxat li span { flex: 1; }
  .bajarildi span { text-decoration: line-through; color: #999; }
  .ochir { border: none; background: none; color: crimson;
           font-size: 18px; cursor: pointer; }
</style>
<div class="ilova">
  <h2>📝 Vazifalar</h2>
  <form id="forma">
    <input id="matn" placeholder="Yangi vazifa..." autocomplete="off">
    <button>Qo'shish</button>
  </form>
  <ul id="royxat"></ul>
  <p id="hisob"></p>
</div>

<script>
  const forma = document.querySelector("#forma");
  const maydon = document.querySelector("#matn");
  const royxat = document.querySelector("#royxat");

  forma.addEventListener("submit", function (event) {
    event.preventDefault();             // sahifa qayta yuklanmasin
    const matn = maydon.value.trim();   // chetlaridagi bo'sh joylarsiz
    if (matn === "") {
      return;                           // bo'sh vazifa qo'shilmaydi
    }

    const li = document.createElement("li");
    const span = document.createElement("span");
    span.textContent = matn;            // textContent — xavfsiz!
    li.append(span);
    royxat.append(li);

    maydon.value = "";                  // maydonni tozalaymiz
  });
</script>

Bir nechta vazifa qo'shib ko'ring: tugma bilan ham, Enter bilan ham. Faqat probellar yozib qo'shishga urinib ko'ring — hech narsa qo'shilmaydi.

Vazifa matni foydalanuvchidan keladi, shuning uchun uni textContent bilan qo'yamiz, innerHTML bilan emas (16-dars). Maydonga <b>salom</b> deb yozib ko'ring — teglar oddiy matn bo'lib ko'rinadi.

4-qadam. Bajarilgan deb belgilash

Vazifa ustiga bosilganda bajarildi klassi qo'shilib-olinib tursin. Har bir yangi li yaratilganda unga click ishlovchisini ulaymiz — royxat.append(li); qatoridan oldin shuni qo'shamiz:

    li.addEventListener("click", function () {
      li.classList.toggle("bajarildi");
    });

Yuqoridagi muharrirga shu uch qatorni o'zingiz qo'shib ko'ring. Qo'shilmasa, xavotir olmang — keyingi qadamdagi muharrirda bu kod allaqachon bor.

5-qadam. O'chirish

Har bir vazifa yoniga × tugmasini qo'shamiz. U bosilganda li.remove() vazifani o'chiradi. Lekin bitta nozik joy bor — hodisaning ko'tarilishi.

Hodisa yuqoriga ko'tariladi

× tugmasi li ning ichida turibdi. Ichki element bosilganda hodisa avval o'sha elementda, keyin uning otasida, keyin bobosida… sodir bo'ladi. Bu hodisaning ko'tarilishi (inglizcha event bubbling — «pufakchaning suv yuziga ko'tarilishi») deyiladi. Ya'ni × ni bossak, li ham «meni bosishdi» deb o'ylaydi. Sinab ko'ring:

<style>
  body { font-family: sans-serif; }
  .karta { padding: 16px; background: #eef4ff;
           border-radius: 10px; cursor: pointer; }
</style>
<div class="karta" id="karta">
  Karta (meni bosing)
  <button id="tugma">Ichki tugma</button>
</div>

<script>
  document.querySelector("#karta").addEventListener("click", function () {
    console.log("Karta bosildi");
  });

  document.querySelector("#tugma").addEventListener("click", function (event) {
    console.log("Tugma bosildi");
    // event.stopPropagation();
  });
</script>

Ichki tugmani bossangiz, konsolga ikkala yozuv ham chiqadi. Endi oxirgi izoh qatoridagi // ni o'chiring va yana bosing — faqat «Tugma bosildi» chiqadi. event.stopPropagation() («tarqalishni to'xtat») hodisaning yuqoriga ko'tarilishini to'xtatadi.

Bizning ilovada ham × bosilganda vazifa «bajarildi» deb belgilanmasligi kerak — o'chirish va belgilash alohida ishlar. Shuning uchun × ishlovchisida stopPropagation() chaqiramiz:

<style>
  body { font-family: sans-serif; background: #f2f5fb; }
  .ilova { max-width: 440px; margin: 0 auto; padding: 16px;
           background: white; border-radius: 12px; }
  #forma { display: flex; gap: 8px; }
  #matn { flex: 1; padding: 8px; font-size: 16px; }
  #royxat { list-style: none; padding: 0; }
  #royxat li { display: flex; gap: 10px; padding: 8px;
               border-bottom: 1px solid #eee; cursor: pointer; }
  #royxat li span { flex: 1; }
  .bajarildi span { text-decoration: line-through; color: #999; }
  .ochir { border: none; background: none; color: crimson;
           font-size: 18px; cursor: pointer; }
</style>
<div class="ilova">
  <h2>📝 Vazifalar</h2>
  <form id="forma">
    <input id="matn" placeholder="Yangi vazifa..." autocomplete="off">
    <button>Qo'shish</button>
  </form>
  <ul id="royxat"></ul>
  <p id="hisob"></p>
</div>

<script>
  const forma = document.querySelector("#forma");
  const maydon = document.querySelector("#matn");
  const royxat = document.querySelector("#royxat");

  forma.addEventListener("submit", function (event) {
    event.preventDefault();
    const matn = maydon.value.trim();
    if (matn === "") {
      return;
    }

    const li = document.createElement("li");
    const span = document.createElement("span");
    span.textContent = matn;

    const ochir = document.createElement("button");   // × tugmasi
    ochir.className = "ochir";
    ochir.textContent = "×";

    li.addEventListener("click", function () {         // 4-qadam
      li.classList.toggle("bajarildi");
    });

    ochir.addEventListener("click", function (event) { // 5-qadam
      event.stopPropagation();   // li ga yetib bormasin
      li.remove();
    });

    li.append(span, ochir);
    royxat.append(li);
    maydon.value = "";
  });
</script>

Vazifalar qo'shing, ba'zilarini bosib belgilang, ba'zilarini × bilan o'chiring.

6-qadam. Hisoblagich

Pastda nechta vazifa borligi va nechtasi bajarilgani ko'rinsin. Buning uchun ro'yxatdagi li larni sanaydigan funksiya yozamiz va uni har bir o'zgarishdan keyin — qo'shganda, belgilaganda, o'chirganda — chaqiramiz:

  function hisobla() {
    const hammasi = royxat.querySelectorAll("li").length;
    const bajarilgan = royxat.querySelectorAll("li.bajarildi").length;
    hisob.textContent = hammasi + " ta vazifa, " +
      bajarilgan + " tasi bajarildi";
  }
<style>
  body { font-family: sans-serif; background: #f2f5fb; }
  .ilova { max-width: 440px; margin: 0 auto; padding: 16px;
           background: white; border-radius: 12px; }
  #forma { display: flex; gap: 8px; }
  #matn { flex: 1; padding: 8px; font-size: 16px; }
  #royxat { list-style: none; padding: 0; }
  #royxat li { display: flex; gap: 10px; padding: 8px;
               border-bottom: 1px solid #eee; cursor: pointer; }
  #royxat li span { flex: 1; }
  .bajarildi span { text-decoration: line-through; color: #999; }
  .ochir { border: none; background: none; color: crimson;
           font-size: 18px; cursor: pointer; }
  #hisob { color: #777; }
</style>
<div class="ilova">
  <h2>📝 Vazifalar</h2>
  <form id="forma">
    <input id="matn" placeholder="Yangi vazifa..." autocomplete="off">
    <button>Qo'shish</button>
  </form>
  <ul id="royxat"></ul>
  <p id="hisob"></p>
</div>

<script>
  const forma = document.querySelector("#forma");
  const maydon = document.querySelector("#matn");
  const royxat = document.querySelector("#royxat");
  const hisob = document.querySelector("#hisob");

  function hisobla() {
    const hammasi = royxat.querySelectorAll("li").length;
    const bajarilgan = royxat.querySelectorAll("li.bajarildi").length;
    hisob.textContent = hammasi + " ta vazifa, " +
      bajarilgan + " tasi bajarildi";
  }

  forma.addEventListener("submit", function (event) {
    event.preventDefault();
    const matn = maydon.value.trim();
    if (matn === "") {
      return;
    }

    const li = document.createElement("li");
    const span = document.createElement("span");
    span.textContent = matn;
    const ochir = document.createElement("button");
    ochir.className = "ochir";
    ochir.textContent = "×";

    li.addEventListener("click", function () {
      li.classList.toggle("bajarildi");
      hisobla();
    });
    ochir.addEventListener("click", function (event) {
      event.stopPropagation();
      li.remove();
      hisobla();
    });

    li.append(span, ochir);
    royxat.append(li);
    maydon.value = "";
    hisobla();
  });

  hisobla();   // boshida: "0 ta vazifa, 0 tasi bajarildi"
</script>

Ilova ishlayapti! Lekin bitta katta kamchilik bor: bir nechta vazifa qo'shing va muharrirdagi «Yangilash» tugmasini bosing (yoki sahifani yangilang). Hammasi yo'qoldi. Brauzer xotirasidagi DOM sahifa bilan birga o'chib ketadi.

7-qadam. Saqlash: localStorage

localStorage nima?

localStorage («mahalliy ombor») — brauzerning har bir sayt uchun ajratilgan kichik xotirasi. Unga yozilgan ma'lumot sahifa yopilsa ham, kompyuter o'chirib-yoqilsa ham saqlanib qoladi. U xuddi lug'at kabi ishlaydi: kalit va qiymat.

Muhim cheklov: localStorage faqat matn saqlaydi. Massiv yoki obyektni saqlash uchun uni 15-darsdagi JSON.stringify bilan matnga aylantiramiz, o'qiganda esa JSON.parse bilan qaytadan obyektga.

// Har ishga tushirishda hisoblagich bittaga oshadi
const soni = Number(localStorage.getItem("tashriflar")) + 1;
localStorage.setItem("tashriflar", soni);
console.log("Bu kodni " + soni + "-marta ishga tushirdingiz");

// Massivni saqlash: avval JSON matnga aylantiramiz
const royxat = [{ matn: "Kitob o'qish", bajarildi: false }];
localStorage.setItem("sinov", JSON.stringify(royxat));

const matn = localStorage.getItem("sinov");
console.log(matn);                   // bu — matn
console.log(JSON.parse(matn)[0].matn);  // bu — yana obyekt

console.log(localStorage.getItem("yoq"));  // saqlanmagan kalit

«Ishga tushirish» tugmasini bir necha marta bosing, keyin sahifani butunlay yangilang — hisob davom etadi. Brauzer eslab qoldi!

Bu saytdagi muharrirlarning localStorage qutisi saytning o'z sozlamalaridan alohida, shuning uchun bemalol tajriba qiling. Har bir brauzer va har bir sayt uchun bu xotira alohida: Chrome'da saqlangan narsa Firefox'da ko'rinmaydi.

Holat → chizish

Endi savol: vazifalarni qanday saqlaymiz? Hozir ular faqat DOM'da — li elementlari ko'rinishida. DOM'ni to'g'ridan-to'g'ri saqlab bo'lmaydi. Shuning uchun ilovani biroz boshqacha tuzamiz — haqiqiy ilovalar aynan shunday quriladi:

  1. Holat (inglizcha state) — barcha vazifalar oddiy massivda saqlanadi: [{ matn: "Non olish", bajarildi: false }, …]. Bu — «haqiqat manbai».
  2. Chizish (render) — bitta chizish() funksiyasi ro'yxatni tozalaydi va massivdan qaytadan quradi (16-darsdagi «massivdan sahifa yasash»).
  3. Har qanday harakatda — qo'shish, belgilash, o'chirish — biz DOM'ga tegmaymiz: faqat massivni o'zgartiramiz, uni saqlaymiz va qaytadan chizamiz.
Holat va chizish aylanasi: hodisa massivni o'zgartiradi, massiv saqlanadi, chizish funksiyasi ro'yxatni quradi, foydalanuvchi yangi ro'yxatni ko'radi 1. Hodisa qo'shish, bosish yoki × 2. Holat o'zgaradi vazifalar massivi 3. saqlash() massiv → JSON → localStorage 4. chizish() massivdan ro'yxat quriladi foydalanuvchi yangi ro'yxatni ko'radi
Har bir harakat bitta aylanadan o'tadi: holat → saqlash → chizish.

Maktab jurnalini eslang. Baho qo'yilganda o'qituvchi devordagi e'lonni o'chirib-yozib o'tirmaydi: u bahoni jurnalga yozadi (holat). E'lon taxtasidagi reyting esa har safar jurnaldan qaytadan ko'chiriladi (chizish). Jurnal — asosiy manba, e'lon — uning ko'rinishi xolos.

Ilovaning yangi «yuragi» — uchta funksiya:

  let vazifalar = [];   // holat

  function saqlash() {
    localStorage.setItem("vazifalar", JSON.stringify(vazifalar));
  }

  function yuklash() {
    const matn = localStorage.getItem("vazifalar");
    if (matn) {
      return JSON.parse(matn);   // saqlangan bo'lsa — o'shani
    }
    return [];                   // bo'lmasa — bo'sh massiv
  }

  function chizish() {
    royxat.innerHTML = "";       // eski ro'yxatni tozalaymiz
    vazifalar.forEach(function (vazifa, indeks) {
      // ... har bir vazifa uchun li yasaymiz (avvalgidek)
    });
    // ... hisobni yangilaymiz
  }

Hodisa ishlovchilari esa endi juda qisqa — hammasi bir xil uch qadamdan iborat:

  // Qo'shish
  vazifalar.push({ matn: matn, bajarildi: false });
  saqlash();
  chizish();

  // Belgilash: true bo'lsa false, false bo'lsa true
  vazifa.bajarildi = !vazifa.bajarildi;
  saqlash();
  chizish();

  // O'chirish: indeks joyidan 1 ta elementni kesib olamiz
  vazifalar.splice(indeks, 1);
  saqlash();
  chizish();

Ikki yangi narsa: ! — «emas» amali, qiymatni teskarisiga aylantiradi (!true → false). splice(indeks, 1) — massivning shu indeksidan boshlab 1 ta elementni olib tashlaydi. royxat.innerHTML = "" esa ro'yxatni bo'shatadi — bu yerda foydalanuvchi matni yo'q, shuning uchun xavfsiz.

Tayyor ilova

Hammasini birlashtiramiz. Bu — to'liq, ishlaydigan ilova. Vazifalar qo'shing, belgilang, o'chiring, keyin sahifani yangilang — hammasi joyida qoladi. Birinchi ochilganda ro'yxat bo'sh turmasligi uchun ikkita namunaviy vazifa qo'shib qo'ydik.

<style>
  * { box-sizing: border-box; }
  body { font-family: sans-serif; background: #f2f5fb;
         margin: 0; padding: 16px; }
  .ilova {
    max-width: 480px; margin: 0 auto; padding: 20px;
    background: white; border-radius: 14px;
    box-shadow: 0 4px 16px rgba(0, 0, 0, 0.08);
  }
  h1 { margin: 0 0 14px; font-size: 24px; }
  #forma { display: flex; gap: 8px; }
  #matn {
    flex: 1; padding: 10px; font-size: 16px;
    border: 2px solid #d5dbe8; border-radius: 8px;
  }
  #matn:focus { outline: none; border-color: #2563eb; }
  #forma button {
    padding: 10px 16px; font-size: 16px; cursor: pointer;
    border: none; border-radius: 8px; background: #2563eb; color: white;
  }
  #royxat { list-style: none; padding: 0; margin: 16px 0 8px; }
  #royxat li {
    display: flex; align-items: center; gap: 10px;
    padding: 10px 12px; border-bottom: 1px solid #eceff5; cursor: pointer;
  }
  #royxat li:hover { background: #f7f9fd; }
  #royxat li::before {
    content: ""; width: 18px; height: 18px; flex-shrink: 0;
    border: 2px solid #c5cddd; border-radius: 50%;
  }
  #royxat li span { flex: 1; }
  .bajarildi span { text-decoration: line-through; color: #9aa3b5; }
  #royxat li.bajarildi::before {
    background: #22a06b; border-color: #22a06b;
  }
  .ochir {
    border: none; background: none; cursor: pointer;
    color: #c0392b; font-size: 20px; line-height: 1;
  }
  #hisob { margin: 0; color: #6b7385; font-size: 14px; }
</style>

<div class="ilova">
  <h1>📝 Vazifalar</h1>
  <form id="forma">
    <input id="matn" placeholder="Yangi vazifa..." autocomplete="off">
    <button>Qo'shish</button>
  </form>
  <ul id="royxat"></ul>
  <p id="hisob"></p>
</div>

<script>
  const forma = document.querySelector("#forma");
  const maydon = document.querySelector("#matn");
  const royxat = document.querySelector("#royxat");
  const hisob = document.querySelector("#hisob");

  // ---- Holat: barcha vazifalar shu massivda ----
  let vazifalar = [];

  // ---- Saqlash va yuklash ----
  function saqlash() {
    localStorage.setItem("vazifalar", JSON.stringify(vazifalar));
  }

  function yuklash() {
    const matn = localStorage.getItem("vazifalar");
    if (matn) {
      return JSON.parse(matn);
    }
    // Birinchi marta ochilganda — namunaviy vazifalar
    return [
      { matn: "JavaScript darslarini takrorlash", bajarildi: true },
      { matn: "Vazifalar ilovasini yasash", bajarildi: false }
    ];
  }

  // ---- Chizish: ro'yxatni massivdan qaytadan quramiz ----
  function chizish() {
    royxat.innerHTML = "";

    vazifalar.forEach(function (vazifa, indeks) {
      const li = document.createElement("li");
      if (vazifa.bajarildi) {
        li.classList.add("bajarildi");
      }

      const span = document.createElement("span");
      span.textContent = vazifa.matn;

      const ochir = document.createElement("button");
      ochir.className = "ochir";
      ochir.textContent = "×";
      ochir.title = "O'chirish";

      // Vazifa bosilsa — bajarildi / bajarilmadi
      li.addEventListener("click", function () {
        vazifa.bajarildi = !vazifa.bajarildi;
        saqlash();
        chizish();
      });

      // × bosilsa — o'chirish
      ochir.addEventListener("click", function (event) {
        event.stopPropagation();
        vazifalar.splice(indeks, 1);
        saqlash();
        chizish();
      });

      li.append(span, ochir);
      royxat.append(li);
    });

    // Hisoblagich
    const bajarilgan = vazifalar.filter((v) => v.bajarildi).length;
    if (vazifalar.length === 0) {
      hisob.textContent = "Vazifalar yo'q. Birinchisini qo'shing!";
    } else {
      hisob.textContent = vazifalar.length + " ta vazifa, " +
        bajarilgan + " tasi bajarildi";
    }
  }

  // ---- Yangi vazifa qo'shish ----
  forma.addEventListener("submit", function (event) {
    event.preventDefault();
    const matn = maydon.value.trim();
    if (matn === "") {
      return;
    }
    vazifalar.push({ matn: matn, bajarildi: false });
    saqlash();
    chizish();
    maydon.value = "";
    maydon.focus();   // kursor yana maydonda turadi
  });

  // ---- Dastur ishga tushganda ----
  vazifalar = yuklash();
  chizish();
</script>

Kodning tuzilishiga yana bir bor qarang — u to'rt qismdan iborat va har biri o'z ishini qiladi:

  1. Holat — vazifalar massivi;
  2. Saqlash va yuklash — massiv bilan localStorage o'rtasidagi «ko'prik»;
  3. Chizish — massivdan ekranni quradi. Ekranni boshqa hech kim o'zgartirmaydi;
  4. Hodisalar — massivni o'zgartiradi va saqlash() + chizish() ni chaqiradi.

Ilova ishlamay qolsa, konsoldagi xabarni o'qing. Ko'p uchraydigan sabablar: id nomida xato ("#royxat" va HTML'dagi id="royxat" bir xil bo'lishi kerak), yopilmagan qavs } yoki ), saqlash() yoki chizish() chaqirilmay qolgani.

Kompyuteringizda: uch faylga ajratish

Endi ilovani o'z kompyuteringizda, haqiqiy sayt sifatida yarataylik (2-darsdagi kabi):

  1. VS Code'da yangi vazifalar papkasini oching va unda uchta fayl yarating: index.html, style.css, script.js.
  2. index.html ga quyidagi kodni yozing. E'tibor bering: CSS <link> bilan, JavaScript esa defer bilan ulangan (16-dars).
<!doctype html>
<html lang="uz">
<head>
  <meta charset="utf-8">
  <meta name="viewport" content="width=device-width, initial-scale=1">
  <title>Vazifalar</title>
  <link rel="stylesheet" href="style.css">
  <script src="script.js" defer></script>
</head>
<body>
  <div class="ilova">
    <h1>📝 Vazifalar</h1>
    <form id="forma">
      <input id="matn" placeholder="Yangi vazifa..." autocomplete="off">
      <button>Qo'shish</button>
    </form>
    <ul id="royxat"></ul>
    <p id="hisob"></p>
  </div>
</body>
</html>
  1. style.css ga yuqoridagi muharrirdan <style> va </style> orasidagi hamma narsani ko'chiring (teglarning o'zisiz).
  2. script.js ga <script> va </script> orasidagi hamma narsani ko'chiring (teglarning o'zisiz).
  3. Barcha fayllarni saqlang (Ctrl + S) va index.html ni Live Server bilan oching («Go Live» tugmasi).
* { box-sizing: border-box; }
body { font-family: sans-serif; background: #f2f5fb;
       margin: 0; padding: 16px; }
/* ... qolgan qoidalar muharrirdagidek ... */
const forma = document.querySelector("#forma");
const maydon = document.querySelector("#matn");
// ... qolgan kod muharrirdagidek ...

Saqlangan ma'lumotni o'z ko'zingiz bilan ko'ring: ilova ochiq turganda F12 → Application bo'limi → chapda Local Storage → saytingiz manzili. U yerda vazifalar kaliti va uning JSON qiymati turibdi. Uni o'chirsangiz va sahifani yangilasangiz, ilova namunaviy vazifalar bilan qaytadan boshlanadi.

Mashqlar

1-mashq «Hammasini o'chirish» tugmasi

Quyida ilovaning soddalashtirilgan nusxasi berilgan (u vazifalar-mashq kaliti bilan saqlaydi). Ro'yxat ostiga id="tozala" bo'lgan «Hammasini o'chirish» tugmasini qo'shing. U bosilganda barcha vazifalar o'chsin va bu localStorage da ham saqlansin (sahifa yangilansa, vazifalar qaytib kelmasin).

<style>
  body { font-family: sans-serif; }
  #royxat li { cursor: pointer; }
  .bajarildi { text-decoration: line-through; color: #999; }
</style>
<form id="forma">
  <input id="matn" placeholder="Yangi vazifa...">
  <button>Qo'shish</button>
</form>
<ul id="royxat"></ul>
<!-- Shu yerga "Hammasini o'chirish" tugmasini qo'shing -->

<script>
  const forma = document.querySelector("#forma");
  const maydon = document.querySelector("#matn");
  const royxat = document.querySelector("#royxat");
  const KALIT = "vazifalar-mashq";

  // Saqlangan bo'lsa o'qiymiz, bo'lmasa — bo'sh massiv
  let vazifalar = JSON.parse(localStorage.getItem(KALIT)) || [];

  function saqlash() {
    localStorage.setItem(KALIT, JSON.stringify(vazifalar));
  }

  function chizish() {
    royxat.innerHTML = "";
    vazifalar.forEach(function (vazifa) {
      const li = document.createElement("li");
      li.textContent = vazifa.matn;
      if (vazifa.bajarildi) {
        li.classList.add("bajarildi");
      }
      li.addEventListener("click", function () {
        vazifa.bajarildi = !vazifa.bajarildi;
        saqlash();
        chizish();
      });
      royxat.append(li);
    });
  }

  forma.addEventListener("submit", function (event) {
    event.preventDefault();
    const matn = maydon.value.trim();
    if (matn === "") {
      return;
    }
    vazifalar.push({ matn: matn, bajarildi: false });
    saqlash();
    chizish();
    maydon.value = "";
  });

  chizish();
</script>
Ishora

«Holat → chizish» g'oyasini eslang: DOM'dagi li larni birma-bir o'chirish shart emas. Massivni bo'shating (vazifalar = [];), keyin saqlash() va chizish() ni chaqiring — qolganini ular o'zi qiladi.

Yechimni ko'rish
<style>
  body { font-family: sans-serif; }
  #royxat li { cursor: pointer; }
  .bajarildi { text-decoration: line-through; color: #999; }
</style>
<form id="forma">
  <input id="matn" placeholder="Yangi vazifa...">
  <button>Qo'shish</button>
</form>
<ul id="royxat"></ul>
<button id="tozala">Hammasini o'chirish</button>

<script>
  const forma = document.querySelector("#forma");
  const maydon = document.querySelector("#matn");
  const royxat = document.querySelector("#royxat");
  const KALIT = "vazifalar-mashq";

  // Saqlangan bo'lsa o'qiymiz, bo'lmasa — bo'sh massiv
  let vazifalar = JSON.parse(localStorage.getItem(KALIT)) || [];

  function saqlash() {
    localStorage.setItem(KALIT, JSON.stringify(vazifalar));
  }

  function chizish() {
    royxat.innerHTML = "";
    vazifalar.forEach(function (vazifa) {
      const li = document.createElement("li");
      li.textContent = vazifa.matn;
      if (vazifa.bajarildi) {
        li.classList.add("bajarildi");
      }
      li.addEventListener("click", function () {
        vazifa.bajarildi = !vazifa.bajarildi;
        saqlash();
        chizish();
      });
      royxat.append(li);
    });
  }

  forma.addEventListener("submit", function (event) {
    event.preventDefault();
    const matn = maydon.value.trim();
    if (matn === "") {
      return;
    }
    vazifalar.push({ matn: matn, bajarildi: false });
    saqlash();
    chizish();
    maydon.value = "";
  });

  document.querySelector("#tozala").addEventListener("click", function () {
    vazifalar = [];
    saqlash();
    chizish();
  });

  chizish();
</script>

Tekshiruv tugmani o'zi bosib ko'radi, shuning uchun mashq yechilgach, bu muharrirdagi ro'yxat har ochilganda bo'sh bo'ladi — bu normal.

2-mashq Ilovani yaxshilang

Quyidagilardan bir yoki bir nechtasini «Tayyor ilova» muharririda yoki kompyuteringizdagi loyihada qo'shib ko'ring. Ular tekshirilmaydi — bu o'zingiz uchun ijod.

  1. Bajarilganlarni tozalash. Faqat bajarilgan vazifalarni o'chiradigan tugma. Ishora: vazifalar = vazifalar.filter((v) => !v.bajarildi);
  2. Tahrirlash. Vazifa matni ustida ikki marta bosilganda (dblclick) uni o'zgartirish. Ishora: eng oddiy yo'l — prompt("Yangi matn:", vazifa.matn) bilan yangi matnni so'rash.
  3. Filtr. «Hammasi», «Faol», «Bajarilgan» tugmalari. Ishora: let filtr = "hammasi"; o'zgaruvchisini qo'shing va chizish() ichida qaysi vazifalarni ko'rsatishni shu bo'yicha tanlang. Holatning o'zi o'zgarmaydi — faqat ko'rinish.
  4. Muddat. Formaga <input type="date"> qo'shing va har bir vazifa obyektida muddat xususiyatini saqlang. Muddati o'tganlarni qizil rangda ko'rsating.
  5. Sudrab almashtirish (drag & drop). Vazifalar tartibini sichqoncha bilan sudrab o'zgartirish — murakkabroq vazifa. HTML'dagi draggable="true" atributi va dragstart, dragover, drop hodisalari haqida o'qib ko'ring.

Amaliy topshiriq O'z kompyuteringizda

  1. «Kompyuteringizda: uch faylga ajratish» bo'limidagi qadamlarni bajaring va ilovani Live Server'da oching.
  2. Bir nechta vazifa qo'shing, brauzerni butunlay yoping va qayta oching — vazifalar joyidami?
  3. F12 → Application → Local Storage bo'limida saqlangan JSON'ni toping.
  4. Ilovaga o'z nomingizni bering va ranglarini o'zgartiring — bu endi sizning ilovangiz!

O'zingizni sinang

Vazifa qo'shish ishlovchisida nega event.preventDefault() chaqiriladi?

  1. Vazifa ikki marta qo'shilmasligi uchun
  2. Maydon tozalanishi uchun
  3. Forma yuborilganda sahifa qayta yuklanib ketmasligi uchun
  4. Bo'sh vazifa qo'shilmasligi uchun

To'g'ri. Odatda forma yuborilganda brauzer sahifani qayta yuklaydi — buni bekor qilamiz. Bo'sh vazifani esa trim() va if to'xtatadi.

localStorage qanday qiymatlarni saqlay oladi?

  1. Faqat matnni — shuning uchun massivni JSON.stringify bilan matnga aylantiramiz
  2. Istalgan JavaScript qiymatini, hech qanday o'zgarishsiz
  3. Faqat sonlarni
  4. Faqat HTML elementlarini

To'g'ri. Saqlashda JSON.stringify, o'qishda JSON.parse.

× tugmasi ishlovchisidagi event.stopPropagation() nima qiladi?

  1. Vazifani o'chiradi
  2. Sahifani qayta yuklashni to'xtatadi
  3. Tugmani o'chirib qo'yadi
  4. Hodisa ota elementga (li ga) ko'tarilib, uni ham «bosilgan» qilishini to'xtatadi

To'g'ri. Shunda × bosilganda vazifa «bajarildi» deb belgilanmaydi.

«Holat → chizish» usulida yangi vazifa qo'shilganda nima qilinadi?

  1. Faqat yangi li yaratilib, ro'yxatga qo'shiladi
  2. Vazifa massivga qo'shiladi, massiv saqlanadi va ro'yxat massivdan qaytadan chiziladi
  3. Sahifa qayta yuklanadi
  4. localStorage dagi matn qo'lda tahrirlanadi

To'g'ri. Massiv — yagona haqiqat manbai, ekran esa doim undan quriladi.

Nega vazifa matni innerHTML bilan emas, textContent bilan qo'yiladi?

  1. textContent tezroq yoziladi
  2. Foydalanuvchi yozgan matndagi HTML va kod ishga tushib ketmasligi uchun
  3. innerHTML li ichida ishlamaydi
  4. Farqi yo'q, ikkalasi ham bir xil

To'g'ri. Foydalanuvchidan kelgan matn uchun doim textContent.

Xulosa

  • Ilova avval rejalashtiriladi: nima qilishi kerak, qanday qismlardan iborat.
  • Uch qatlam: HTML — tuzilish (index.html), CSS — ko'rinish (style.css), JavaScript — harakat (script.js, defer bilan).
  • Forma: submit + preventDefault() + trim() + bo'sh matn tekshiruvi.
  • Ichki tugma bosilganda hodisa ota elementlarga ko'tariladi; stopPropagation() buni to'xtatadi.
  • localStorage faqat matn saqlaydi: JSON.stringify bilan saqlab, JSON.parse bilan o'qiymiz.
  • «Holat → chizish»: ma'lumot massivda, har bir harakat massivni o'zgartiradi, saqlaydi va ekranni qaytadan chizadi.
  • Keyingi darsda bu ilovani internetga joylab, havolasini do'stlaringizga yuborishni o'rganamiz: 19-dars.