Projekt TODO

Ostatnia aktualizacja: 28 stycznia 2020

Czas przećwiczyć to co poznaliśmy do tej pory. Naszym celem będzie stworzenie prostej aplikacji służącej do mini zarządzania zadaniami.

HTML i CSS

Zanim zaczniemy pisać nasze skrypty, powinniśmy stworzyć wygląd naszych elementów. Zaczynamy od html i css dla formularza dodawania zadań.

Pokaż HTML

<form class="form" id="todoForm">
    <div class="form-row">
        <label class="form-label" for="todoMessage">Podaj treść zadania</label>
        <textarea class="form-message" name="todoMessage" id="todoMessage"></textarea>
    </div>
    <div class="form-row">
        <button type="submit" class="button form-button">Dodaj</button>
    </div>
</form>
Pokaż CSS

* {
    box-sizing: border-box;
}
body {
    background-image: linear-gradient( 135deg, #FFF6B7 10%, #F6416C 100%);
    min-height: 100vh;
    margin: 0;
}

.cnt {
    max-width:40rem;
    margin:2rem auto;
}
.form {
    font-family:sans-serif;
    box-shadow: 0 1px 2px rgba(0,0,0,0.2);
    padding:2rem;
    background: #fff;
    border-radius: 5px;
}
.form-row {
    overflow:hidden;
}
.form-label {
    display: block;
    font-size:0.8rem;
    margin-bottom:0.2rem;
}
.form-message {
    padding:1rem;
    height:10rem;
    width:100%;
    border:1px solid #aaa;
    margin-bottom: 0.5rem;
    resize: vertical;
    font-family:sans-serif;
}
.form-button {
    padding:0.8rem 1.6rem;
    background: tomato;
    color:#fff;
    border:0;
    border-radius: 0.188rem;
    transition:0.4s all;
    cursor: pointer;
    float: right;
}
.form-button:hover {
    background: #ED2D2D;
}

Nasz formularz jest prostym tworem. Nie zakładamy możliwości jego działania bez JavaScript, dlatego dla elementu form pominąłem atrybuty method i action. W praktyce gdybyś celował w jak największą dostępność twojej aplikacji, wtedy takie atrybuty powinny się tutaj znaleźć, a sam formularz powinien być możliwy do użycia bez JavaScript.

Lista zadań

Kolejnym elementem będzie lista z zadaniami. Na razie stwórzmy ich wygląd na sztywno. W końcowej fazie zadania te będą generowane dynamicznie. Zanim jednak tą dynamikę dodamy, musimy stworzyć HTML by mieć na czym się opierać.

Lista zadań składa się z tytułu i mini formularza służącego do wyszukiwania zadań.


<section class="list-cnt">
    <header class="list-header">
        <h2 class="list-title">
            Lista zadań
        </h2>
        <form class="list-search-form">
            <input type="search" id="todoSearch" class="list-search">
        </form>
    </header>

    <div class="list" id="todoList">
        <!-- tutaj trafią zadania -->
    </div>
</section>

.list-cnt {
    background: #fff;
    font-family: sans-serif;
    border-radius: 5px;
    margin:1rem 0;
    box-shadow: 0 1px 2px rgba(0,0,0,0.2);
    padding: 2rem;
}
.list-header {
    display: flex;
    justify-content: space-between;
    align-items: center;
}
.list-search-form {
    width: 50%;
}
.list-search {
    border: 1px solid #aaa;
    padding: 0.7rem;
    width: 100%;
    border-radius: 3px;
}
.list-title {
    margin-top: 0;
    margin-bottom: 0;
    font-size: 1rem;
    text-transform: uppercase;
}

Pojedyncze zadanie

Ostatnim elementem będzie dodawane zadanie.


<div class="element">
    <div class="element-bar">
        <h3 class="element-date">20-10-2016 godz. 16:32</h3>
        <button class="element-delete" title="Usuń task">
            Usuń
        </button>
    </div>
    <div class="element-text">
        Lorem ipsum dolor sit amet, consectetur adipisicing elit. Distinctio laudantium quasi blanditiis enim molestias explicabo id totam veniam corporis maiores.
    </div>
</div>

Nasze zadanie składa się z belki z datą utworzenia i przyciskiem usunięcia. Przycisk usunięcia ma w sobie ikonkę svg, której kod pobrałem ze strony https://tablericons.com/.


<script defer src="https://use.fontawesome.com/releases/v5.0.2/js/all.js"></script>

Po dodaniu skryptu, wystarczy użyć odpowiedniej ikonki - w naszym przypadku to ikonka .fa-times-circle

Pozostaje ostylować elementy zadań:


.element {
    border-top: 1px solid #ddd;
    margin-top: 1rem;
    padding-top: 2rem;
    padding-bottom: 2rem;
}
.element-bar {
    overflow:hidden;
    display: flex;
    justify-content: space-between;
    align-items: center;
    margin-bottom: 0.75rem;
}
.element-date {
    margin:0;
}
.element-date {
    font-size: 0.9rem;
    font-weight: normal;
}
.element-delete {
    border:0;
    background: url(images/trash.svg);
    background-position: center;
    background-repeat: no-repeat;
    background-size: 20px 20px;
    cursor: pointer;
    width: 30px;
    height: 30px;
    text-indent: -999px;
    overflow: hidden;
}
.element-text {
    color:#777;
    font-size:0.8rem;
}

Dodawanie taska do listy

Skrypt rozpoczynamy od wyłapania elementów, na których będziemy pracować:


document.addEventListener("DOMContentLoaded", () => {
    const todoList = document.querySelector("#todoList");
    const todoForm = document.querySelector("#todoForm");
    const todoSearch = document.querySelector("#todoSearch");
    const todoTextarea = todoForm.querySelector('textarea');

    ...
});

Pierwszą rzeczą którą zrobimy to podpięcie się pod wysyłkę formularza. Gdy ktoś wyśle formularz, sprawdzimy czy treść nie jest pusta. Jeżeli tak jest, dodamy nowe zadanie do listy. Zadanie powinno zawierać tekst pobrany z formularza oraz datę dodania. Po dodaniu zadania do listy wyczyścimy treść w formularzu.


document.addEventListener("DOMContentLoaded", () => {
    const todoList = document.querySelector("#todoList");
    const todoForm = document.querySelector("#todoForm");
    const todoSearch = document.querySelector("#todoSearch");
    const todoTextarea = todoForm.querySelector('textarea');

    function addTask(text) {
        console.log("Dodaję zadanie do listy")
    }

    todoForm.addEventListener("submit", e => {
        e.preventDefault();

        if (todoTextarea.value !== "") {
            addTask(todoTextarea.value);
            todoTextarea.value = "";
        }
    });
});

Kolejnym krokiem jest stworzenie kodu funkcji addTask(). Musimy wygenerować kod elementu, który już stworzyliśmy powyżej. Moglibyśmy tutaj użyć innerHTML wraz z template string, ale możemy też spróbować użyć template. Do HTML dodajemy template z kodem pojedynczego elementu:


<template id="elementTemplate">
    <div class="element-bar">
        <h3 class="element-date"></h3>
        <button class="element-delete" title="Usuń task">
            Usuń
        </button>
    </div>
    <div class="element-text">
    </div>
</template>

Następnie tworzymy funkcję, która pobierze jego kod i na jego podstawie stworzy nowy element w liście:


function addTask(text) {
    const element = document.createElement("div");
    element.classList.add("element");

    //pobieram zawartość templatki
    const elementInner = document.querySelector("#elementTemplate").content.cloneNode(true);

    //wrzucam do elementu
    element.append(elementInner);

    //tworzę datę
    const date = new Date();
    const dateText = `${date.getDate()} - ${date.getMonth()+1} - ${date.getFullYear()} godz.: ${date.getHours()}:${date.getMinutes()}`;
    element.querySelector(".element-date").innerText = dateText;

    //wstawiam tekst
    element.querySelector(".element-text").innerText = text;

    //i wrzucam element do listy
    todoList.append(element);
}

Usuwanie zadania z listy

Po kliknięciu na przycisk Usuń w elemencie, zadanie to powinno być usunięte.

Zdarzenia nie możemy podpiąć tak samo jak dla formularza. Pamiętaj, że elementy z zadaniami jeszcze nie istnieją, więc podpięlibyśmy event pod przyciski usuń, których nie ma.

W tym przypadku zastosujmy propagację zdarzeń wraz ze sprawdzaniem elementu który został kliknięty.


document.addEventListener("DOMContentLoaded", () => {
    ...

    todoList.addEventListener("click", e => {
        if (e.target.classList.contains("element-delete")) {
            e.target.closest(".element").remove();
        }
    });
});

Wyszukiwanie zadań

Ostatnia funkcjonalność to wyszukiwanie zadań. Wykorzystamy do tego mini formularz leżący w nagłówku listy zadań.

W tym przypadku zadanie jest bardzo proste. Po wpisaniu jakiejś wartości musimy zrobić pętlę po liście zadań. Dla każdego elementu będziemy sprawdzać czy tekst w danym zadaniu zawiera szukaną frazę - wykorzystamy do tego includes. Jeżeli zawiera szukany tekst, to taki element pokażemy, w przeciwnym wypadku ukryjemy go poprzez ustawienie display:none:


todoSearch.addEventListener("input", () => {
    const val = todoSearch.value;
    const elems = todoList.querySelectorAll(".element");

    for (const el of elems) {
        const text = el.querySelector(".element-text").innerText;

        if (text.includes(val)) {
            el.style.setProperty("display", "");
        } else {
            el.style.setProperty("display", "none");
        }
    }
});

Demo

Nasz powyższy przykład jest bardzo prosty i w praktyce jeszcze trochę mu brakuje. Jego główną bolączką jest to, że każdorazowo po wejściu na stronę zadania są czyszczone.

demo

Aby móc zapisywać zadania trzeba wykorzystać do tego celu localStorage albo - lepiej - skorzystać z Ajax i zapisywać dane na serwerze.

Trening czyni mistrza

Jeżeli chcesz sobie potrenować zdobytą wiedzę z tego działu, to zadania znajdują się w repozytorium pod adresem: https://github.com/kartofelek007/zadania-dom

W repozytorium jest branch "solutions". Tam znajdziesz przykładowe rozwiązania.

Wszelkie prawa zastrzeżone. Jeżeli chcesz używać jakiejś części tego kursu, skontaktuj się z autorem. Aha - i ta strona korzysta z ciasteczek.

Menu