Proje Detayları

Proje Tanımı

Bu projede, görevleri ekleyip, tamamlayıp, silebileceğiniz bir to-do listesi uygulaması geliştireceğiz. Uygulama, LocalStorage kullanarak verileri tarayıcıda saklayacak, böylece sayfa yenilense bile görevleriniz kaybolmayacak. Bu proje, DOM manipülasyonu, olay işleme ve tarayıcı depolama konularını pekiştirmenize yardımcı olacak.

Adım Adım Geliştirme

1 HTML Yapısını Oluşturma

İlk olarak, to-do listesi uygulamasının HTML yapısını oluşturalım. Bir form, görev listesi ve filtreler içeren bir yapı oluşturacağız.

index.html
<!DOCTYPE html>
<html lang="tr">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>To-Do Listesi</title>
    <link rel="stylesheet" href="style.css">
</head>
<body>
    <div class="todo-app">
        <div class="todo-header">
            <h2>To-Do Listesi</h2>
        </div>
        <div class="todo-form">
            <input type="text" class="todo-input" id="todoInput" placeholder="Yeni görev ekle...">
            <button class="todo-submit" id="todoSubmit">Ekle</button>
        </div>
        <ul class="todo-list" id="todoList">
            <!-- Görevler JavaScript ile eklenecek -->
        </ul>
        <div class="todo-filters">
            <div>
                <button class="filter-btn active" data-filter="all">Tümü</button>
                <button class="filter-btn" data-filter="active">Aktif</button>
                <button class="filter-btn" data-filter="completed">Tamamlanan</button>
            </div>
            <button class="clear-completed" id="clearCompleted">Tamamlananları Temizle</button>
        </div>
        <div class="todo-count" id="todoCount">
            0 görev kaldı
        </div>
    </div>
    
    <script src="script.js"></script>
</body>
</html>

Not: HTML yapısında, görev listesi için bir <ul> elementi kullanıyoruz. Görevler, JavaScript ile bu listeye eklenecek. Ayrıca, filtreleme butonları için data-filter özniteliği kullanarak hangi filtreyi temsil ettiklerini belirtiyoruz.

2 CSS ile Stil Verme

Şimdi, to-do listesi uygulamasına stil verelim. Modern ve kullanıcı dostu bir arayüz oluşturacağız.

style.css
* {
    box-sizing: border-box;
    margin: 0;
    padding: 0;
}

body {
    font-family: Arial, sans-serif;
    display: flex;
    justify-content: center;
    align-items: center;
    min-height: 100vh;
    background-color: #f5f5f5;
}

.todo-app {
    width: 100%;
    max-width: 500px;
    background-color: #fff;
    border-radius: 10px;
    box-shadow: 0 5px 15px rgba(0,0,0,0.1);
    overflow: hidden;
}

.todo-header {
    background-color: #4caf50;
    color: white;
    padding: 20px;
    text-align: center;
}

.todo-header h2 {
    margin: 0;
    font-size: 1.8rem;
}

.todo-form {
    display: flex;
    padding: 20px;
    border-bottom: 1px solid #eee;
}

.todo-input {
    flex: 1;
    padding: 10px;
    border: 1px solid #ddd;
    border-radius: 4px 0 0 4px;
    font-size: 1rem;
}

.todo-submit {
    background-color: #4caf50;
    color: white;
    border: none;
    padding: 10px 15px;
    border-radius: 0 4px 4px 0;
    cursor: pointer;
    font-size: 1rem;
}

.todo-submit:hover {
    background-color: #43a047;
}

.todo-list {
    list-style-type: none;
    padding: 0;
    margin: 0;
}

.todo-item {
    display: flex;
    align-items: center;
    padding: 15px 20px;
    border-bottom: 1px solid #eee;
    transition: background-color 0.3s;
}

.todo-item:hover {
    background-color: #f9f9f9;
}

.todo-item.completed {
    background-color: #f5f5f5;
}

.todo-checkbox {
    margin-right: 15px;
    width: 20px;
    height: 20px;
}

.todo-text {
    flex: 1;
    font-size: 1rem;
}

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

.todo-delete {
    background-color: #f44336;
    color: white;
    border: none;
    border-radius: 4px;
    padding: 5px 10px;
    cursor: pointer;
    font-size: 0.9rem;
}

.todo-delete:hover {
    background-color: #e53935;
}

.todo-filters {
    display: flex;
    justify-content: space-between;
    padding: 15px 20px;
    background-color: #f5f5f5;
}

.filter-btn {
    background: none;
    border: none;
    padding: 5px 10px;
    cursor: pointer;
    font-size: 0.9rem;
    border-radius: 4px;
}

.filter-btn.active {
    background-color: #4caf50;
    color: white;
}

.clear-completed {
    background-color: #f44336;
    color: white;
    border: none;
    padding: 5px 10px;
    border-radius: 4px;
    cursor: pointer;
    font-size: 0.9rem;
}

.clear-completed:hover {
    background-color: #e53935;
}

.todo-count {
    font-size: 0.9rem;
    color: #666;
    padding: 10px 20px;
    text-align: center;
    border-top: 1px solid #eee;
}

İpucu: CSS'te, tamamlanan görevleri görsel olarak ayırt etmek için .todo-item.completed sınıfını kullanıyoruz. Bu sınıf, görev tamamlandığında JavaScript tarafından eklenecek ve görevin üzerini çizecek.

3 JavaScript ile İşlevsellik Ekleme

Şimdi, JavaScript ile to-do listesi uygulamasına işlevsellik ekleyelim. Görevleri ekleme, tamamlama, silme ve LocalStorage'da saklama işlemlerini kodlayacağız.

script.js
// DOM elementlerini seçme
const todoForm = document.querySelector('.todo-form');
const todoInput = document.getElementById('todoInput');
const todoSubmit = document.getElementById('todoSubmit');
const todoList = document.getElementById('todoList');
const todoCount = document.getElementById('todoCount');
const filterButtons = document.querySelectorAll('.filter-btn');
const clearCompleted = document.getElementById('clearCompleted');

// Görevleri saklayacak dizi
let todos = [];

// Sayfa yüklendiğinde çalışacak fonksiyon
document.addEventListener('DOMContentLoaded', () => {
    // LocalStorage'dan görevleri yükle
    loadTodos();
    
    // Form gönderildiğinde yeni görev ekle
    todoForm.addEventListener('submit', addTodo);
    
    // Tamamlananları temizle butonuna tıklandığında
    clearCompleted.addEventListener('click', clearCompletedTodos);
    
    // Filtre butonlarına tıklandığında
    filterButtons.forEach(button => {
        button.addEventListener('click', filterTodos);
    });
});

// LocalStorage'dan görevleri yükleme
function loadTodos() {
    // LocalStorage'dan todos dizisini al
    const storedTodos = localStorage.getItem('todos');
    
    // Eğer LocalStorage'da todos varsa, JSON.parse ile diziye çevir
    if (storedTodos) {
        todos = JSON.parse(storedTodos);
    }
    
    // Görevleri ekrana render et
    renderTodos();
}

// Görevleri LocalStorage'a kaydetme
function saveTodos() {
    // todos dizisini JSON string'e çevir ve LocalStorage'a kaydet
    localStorage.setItem('todos', JSON.stringify(todos));
}

// Yeni görev ekleme
function addTodo(e) {
    // Form gönderimini engelle
    e.preventDefault();
    
    // Input değerini al ve boşlukları temizle
    const todoText = todoInput.value.trim();
    
    // Eğer input boş değilse
    if (todoText) {
        // Yeni görev objesi oluştur
        const todo = {
            id: Date.now(), // Benzersiz ID için timestamp kullan
            text: todoText,
            completed: false
        };
        
        // Görevi todos dizisine ekle
        todos.push(todo);
        
        // Görevleri LocalStorage'a kaydet
        saveTodos();
        
        // Görevleri ekrana render et
        renderTodos();
        
        // Input'u temizle
        todoInput.value = '';
        
        // Input'a odaklan
        todoInput.focus();
    }
}

// Görev tamamlama durumunu değiştirme
function toggleTodo(id) {
    // ID'ye göre görevi bul ve completed durumunu tersine çevir
    todos = todos.map(todo => {
        if (todo.id === id) {
            return { ...todo, completed: !todo.completed };
        }
        return todo;
    });
    
    // Görevleri LocalStorage'a kaydet
    saveTodos();
    
    // Görevleri ekrana render et
    renderTodos();
}

// Görev silme
function deleteTodo(id) {
    // ID'ye göre görevi diziden çıkar
    todos = todos.filter(todo => todo.id !== id);
    
    // Görevleri LocalStorage'a kaydet
    saveTodos();
    
    // Görevleri ekrana render et
    renderTodos();
}

// Tamamlanan görevleri temizleme
function clearCompletedTodos() {
    // Tamamlanmamış görevleri filtrele
    todos = todos.filter(todo => !todo.completed);
    
    // Görevleri LocalStorage'a kaydet
    saveTodos();
    
    // Görevleri ekrana render et
    renderTodos();
}

// Görevleri filtreleme
function filterTodos(e) {
    // Tıklanan filtre butonunu al
    const filterType = e.target.getAttribute('data-filter');
    
    // Tüm filtre butonlarından active sınıfını kaldır
    filterButtons.forEach(button => {
        button.classList.remove('active');
    });
    
    // Tıklanan filtre butonuna active sınıfını ekle
    e.target.classList.add('active');
    
    // Görevleri filtrele ve ekrana render et
    renderTodos(filterType);
}

// Görevleri ekrana render etme
function renderTodos(filterType = 'all') {
    // Görev listesini temizle
    todoList.innerHTML = '';
    
    // Filtrelenmiş görevleri al
    let filteredTodos = todos;
    
    if (filterType === 'active') {
        filteredTodos = todos.filter(todo => !todo.completed);
    } else if (filterType === 'completed') {
        filteredTodos = todos.filter(todo => todo.completed);
    }
    
    // Her görev için bir liste öğesi oluştur
    filteredTodos.forEach(todo => {
        // Yeni liste öğesi oluştur
        const todoItem = document.createElement('li');
        todoItem.className = `todo-item ${todo.completed ? 'completed' : ''}`;
        
        // Görev içeriğini oluştur
        todoItem.innerHTML = `
            
            ${todo.text}
            Sil
        `;
        
        // Checkbox'a tıklandığında görevi tamamla/tamamlama
        const checkbox = todoItem.querySelector('.todo-checkbox');
        checkbox.addEventListener('change', () => {
            toggleTodo(todo.id);
        });
        
        // Sil butonuna tıklandığında görevi sil
        const deleteButton = todoItem.querySelector('.todo-delete');
        deleteButton.addEventListener('click', () => {
            deleteTodo(todo.id);
        });
        
        // Görev öğesini listeye ekle
        todoList.appendChild(todoItem);
    });
    
    // Kalan görev sayısını güncelle
    const activeTodos = todos.filter(todo => !todo.completed);
    todoCount.textContent = `${activeTodos.length} görev kaldı`;
}

Uyarı: LocalStorage, sadece string değerleri saklayabilir. Bu nedenle, görevleri JSON formatına dönüştürerek saklıyoruz ve yüklerken tekrar JavaScript objelerine dönüştürüyoruz.

4 LocalStorage ile Veri Saklama

To-do listesi uygulamasında, görevleri tarayıcının LocalStorage'ında saklıyoruz. Bu, kullanıcının sayfayı yenilemesi veya tarayıcıyı kapatıp tekrar açması durumunda bile görevlerin kaybolmamasını sağlar.

LocalStorage Nedir?

LocalStorage, tarayıcının sağladığı bir web depolama API'sidir. Anahtar-değer çiftleri şeklinde veri saklamanıza olanak tanır. Veriler, kullanıcının tarayıcısında kalıcı olarak saklanır ve oturum sona erse bile silinmez.

LocalStorage Kullanımı

LocalStorage Örneği
// Veri saklama
localStorage.setItem('key', 'value');

// Veri okuma
const value = localStorage.getItem('key');

// Veri silme
localStorage.removeItem('key');

// Tüm verileri silme
localStorage.clear();

LocalStorage, sadece string değerleri saklayabilir. Bu nedenle, nesneleri veya dizileri saklamak istediğinizde, bunları JSON formatına dönüştürmeniz gerekir:

JSON ile LocalStorage Kullanımı
// Nesne veya dizi saklama
const data = { name: 'John', age: 30 };
localStorage.setItem('data', JSON.stringify(data));

// Nesne veya dizi okuma
const storedData = localStorage.getItem('data');
const parsedData = JSON.parse(storedData);

Not: LocalStorage'ın bazı sınırlamaları vardır. Tarayıcılar genellikle domain başına 5-10 MB arası depolama alanı sağlar. Ayrıca, LocalStorage verileri şifrelenmez, bu nedenle hassas bilgileri saklamak için uygun değildir.

5 Filtreleme İşlevselliği Ekleme

To-do listesi uygulamasına filtreleme işlevselliği ekleyerek, kullanıcının tüm görevleri, aktif görevleri veya tamamlanan görevleri görüntüleyebilmesini sağlıyoruz.

Filtreleme işlevselliği, renderTodos fonksiyonuna bir filterType parametresi ekleyerek gerçekleştirilir. Bu parametre, hangi görevlerin görüntüleneceğini belirler:

  • all: Tüm görevleri gösterir
  • active: Sadece tamamlanmamış görevleri gösterir
  • completed: Sadece tamamlanmış görevleri gösterir
Filtreleme Fonksiyonu
// Görevleri filtreleme
function filterTodos(e) {
    // Tıklanan filtre butonunu al
    const filterType = e.target.getAttribute('data-filter');
    
    // Tüm filtre butonlarından active sınıfını kaldır
    filterButtons.forEach(button => {
        button.classList.remove('active');
    });
    
    // Tıklanan filtre butonuna active sınıfını ekle
    e.target.classList.add('active');
    
    // Görevleri filtrele ve ekrana render et
    renderTodos(filterType);
}

Filtreleme butonlarına tıklandığında, filterTodos fonksiyonu çağrılır ve ilgili filtre tipi renderTodos fonksiyonuna aktarılır. renderTodos fonksiyonu, filtre tipine göre görevleri filtreler ve ekrana render eder.

6 Projeyi Test Etme ve Hata Ayıklama

To-do listesi uygulamasını oluşturduktan sonra, farklı senaryoları test ederek doğru çalıştığından emin olun:

  • Yeni görev ekleme: Boş olmayan bir görev eklenebilmeli
  • Görev tamamlama: Checkbox'a tıklandığında görev tamamlanmalı/tamamlanmamalı
  • Görev silme: Sil butonuna tıklandığında görev silinmeli
  • Filtreleme: Filtre butonlarına tıklandığında ilgili görevler görüntülenmeli
  • Tamamlananları temizleme: Tamamlananları Temizle butonuna tıklandığında tamamlanan görevler silinmeli
  • LocalStorage: Sayfa yenilendiğinde görevler kaybolmamalı

Herhangi bir hata veya beklenmeyen davranış tespit ederseniz, JavaScript kodunuzu gözden geçirin ve düzeltin.

7 Bonus Özellikleri Ekleme

Temel to-do listesi işlevselliğini tamamladıktan sonra, aşağıdaki bonus özellikleri ekleyebilirsiniz:

Görevleri Düzenleme

Görev Düzenleme Ekleme
// Görev metni oluşturma kısmını değiştirin
todoItem.innerHTML = `
    
    ${todo.text}
    Düzenle
    Sil
`;

// Düzenle butonuna tıklandığında
const editButton = todoItem.querySelector('.todo-edit');
editButton.addEventListener('click', () => {
    const todoText = todoItem.querySelector('.todo-text');
    const input = document.createElement('input');
    input.type = 'text';
    input.className = 'todo-edit-input';
    input.value = todoText.textContent;
    
    // Input'u görev metninin yerine koy
    todoText.replaceWith(input);
    input.focus();
    
    // Düzenle butonunu Kaydet butonuyla değiştir
    editButton.textContent = 'Kaydet';
    editButton.removeEventListener('click', arguments.callee);
    
    // Kaydet butonuna tıklandığında
    editButton.addEventListener('click', () => {
        const newText = input.value.trim();
        if (newText) {
            // Görevi güncelle
            todos = todos.map(t => {
                if (t.id === todo.id) {
                    return { ...t, text: newText };
                }
                return t;
            });
            
            // Görevleri kaydet ve render et
            saveTodos();
            renderTodos();
        }
    });
    
    // Input'tan çıkıldığında da kaydet
    input.addEventListener('blur', () => {
        const newText = input.value.trim();
        if (newText) {
            // Görevi güncelle
            todos = todos.map(t => {
                if (t.id === todo.id) {
                    return { ...t, text: newText };
                }
                return t;
            });
            
            // Görevleri kaydet ve render et
            saveTodos();
            renderTodos();
        }
    });
});

Görevlere Öncelik Ekleme

Öncelik Ekleme
// Görev objesine öncelik ekleyin
const todo = {
    id: Date.now(),
    text: todoText,
    completed: false,
    priority: 'normal' // 'low', 'normal', 'high'
};

// Form'a öncelik seçimi ekleyin
<select id="todoPriority">
    <option value="low">Düşük</option>
    <option value="normal" selected>Normal</option>
    <option value="high">Yüksek</option>
</select>

// Görev eklerken önceliği alın
const todoPriority = document.getElementById('todoPriority').value;
const todo = {
    id: Date.now(),
    text: todoText,
    completed: false,
    priority: todoPriority
};

// Görev öğesine öncelik göstergesi ekleyin
todoItem.innerHTML = `
    
    
    ${todo.text}
    Sil
`;

// CSS'e öncelik stilleri ekleyin
.todo-priority {
    width: 10px;
    height: 10px;
    border-radius: 50%;
    margin-right: 10px;
}

.todo-priority.low {
    background-color: #2196f3;
}

.todo-priority.normal {
    background-color: #ff9800;
}

.todo-priority.high {
    background-color: #f44336;
}

Görevlere Son Tarih Ekleme

Son Tarih Ekleme
// Görev objesine son tarih ekleyin
const todo = {
    id: Date.now(),
    text: todoText,
    completed: false,
    dueDate: null // Tarih string'i veya null
};

// Form'a tarih seçimi ekleyin
<input type="date" id="todoDueDate">

// Görev eklerken tarihi alın
const todoDueDate = document.getElementById('todoDueDate').value;
const todo = {
    id: Date.now(),
    text: todoText,
    completed: false,
    dueDate: todoDueDate || null
};

// Görev öğesine son tarih göstergesi ekleyin
todoItem.innerHTML = `
    
    ${todo.text}
    ${todo.dueDate ? `Son Tarih: ${todo.dueDate}` : ''}
    Sil
`;

// CSS'e son tarih stili ekleyin
.todo-due-date {
    font-size: 0.8rem;
    color: #888;
    margin-left: 10px;
}

// Geçmiş tarihleri vurgulayın
const today = new Date().toISOString().split('T')[0];
if (todo.dueDate && todo.dueDate < today) {
    todoItem.classList.add('overdue');
}

.todo-item.overdue .todo-due-date {
    color: #f44336;
    font-weight: bold;
}

Proje Zorlukları ve Çözümleri

Zorluk 1: Veri Kalıcılığı

To-do listesi uygulamalarında, kullanıcının görevlerinin sayfa yenilendiğinde veya tarayıcı kapatılıp açıldığında kaybolmaması önemlidir.

Çözüm: LocalStorage API'sini kullanarak görevleri tarayıcıda saklayabilirsiniz. Görevleri JSON formatına dönüştürerek LocalStorage'a kaydedebilir ve sayfa yüklendiğinde tekrar yükleyebilirsiniz.

Zorluk 2: Görev Durumu Yönetimi

Görevlerin tamamlanma durumunu yönetmek ve görsel olarak göstermek, kullanıcı deneyimi açısından önemlidir.

Çözüm: Her görev için bir completed özelliği kullanabilir ve bu özelliğe göre görevin görünümünü değiştirebilirsiniz. Tamamlanan görevlere .completed sınıfı ekleyerek üzerini çizebilirsiniz.

Zorluk 3: Filtreleme

Kullanıcının görevleri filtreleyebilmesi, özellikle çok sayıda görev olduğunda önemlidir.

Çözüm: Görevleri render ederken bir filtre parametresi kullanabilir ve bu parametreye göre görevleri filtreleyebilirsiniz. Filtre butonlarına data-filter özniteliği ekleyerek hangi filtreyi temsil ettiklerini belirtebilirsiniz.

Geliştirme İpuçları

  • Modüler Kod Yazın: Kodunuzu küçük, anlaşılır fonksiyonlara bölün. Her fonksiyon tek bir işi yapmalıdır.
  • Veri Modeli Oluşturun: Görevleri temsil eden bir veri modeli oluşturun ve bu modeli güncelleyin. Kullanıcı arayüzünü bu modele göre render edin.
  • Olay Delegasyonu Kullanın: Her görev öğesi için ayrı olay dinleyicileri eklemek yerine, üst öğeye bir olay dinleyicisi ekleyip olayı delege edebilirsiniz. Bu, performansı artırır ve kod tekrarını azaltır.
  • LocalStorage Sınırlamalarını Göz Önünde Bulundurun: LocalStorage'ın domain başına 5-10 MB arası depolama alanı sağladığını unutmayın. Çok büyük veri saklamanız gerekiyorsa, alternatif çözümler düşünün.
  • Kullanıcı Deneyimine Odaklanın: Görevleri ekleme, tamamlama ve silme işlemlerini kullanıcı için kolay ve sezgisel hale getirin. Görsel geri bildirimler ve animasyonlar ekleyin.

Proje Genişletme Fikirleri

  • Sürükle-Bırak ile Sıralama: Görevleri sürükle-bırak ile yeniden sıralama özelliği ekleyin. HTML5 Drag and Drop API veya bir kütüphane (örneğin, SortableJS) kullanabilirsiniz.
  • Kategoriler veya Etiketler: Görevleri kategorilere ayırma veya etiketleme özelliği ekleyin. Kullanıcılar, görevleri kategorilere göre filtreleyebilir.
  • Hatırlatıcılar: Görevler için hatırlatıcılar ekleme özelliği ekleyin. Notification API veya e-posta bildirimleri kullanabilirsiniz.
  • Çoklu Kullanıcı Desteği: Kullanıcıların kendi to-do listelerini oluşturabilmesi için oturum açma ve kullanıcı yönetimi ekleyin. Bu, bir backend servisi gerektirir.
  • Tema Değiştirme: Kullanıcının açık/koyu tema arasında geçiş yapabilmesini sağlayın.
  • İstatistikler: Tamamlanan görev sayısı, ortalama tamamlama süresi gibi istatistikler gösterin.