Refaktoryzacja i rozbudowa Kalendarza w HTML, CSS i jQuery

W poprzednim wpisie na blogu Kalendarz w HTML, CSS i jQuery pokazaliśmy, jak stworzyć prosty kalendarz miesięczny za pomocą HTML, CSS i jQuery. Kontynuując nasz projekt, skupimy się teraz na trzech kluczowych aspektach:

  1. Refaktoryzacja kodu: Użyjemy fragmentów dokumentu, aby zoptymalizować nasz kod i poprawić jego wydajność. Pozwoli to na bardziej efektywne zarządzanie operacjami DOM i zmniejszenie liczby operacji na żywym drzewie dokumentu, co jest szczególnie korzystne przy dużych strukturach HTML.
  2. Zmiana na siatkę CSS: Przeprojektujemy nasz kalendarz, korzystając z nowoczesnych technik CSS Grid, aby poprawić jego strukturę i wygląd. CSS Grid pozwoli na lepsze rozmieszczenie elementów kalendarza, co uczyni go bardziej responsywnym i estetycznym.
  3. Nawigacja między miesiącami: Dodamy funkcjonalność pozwalającą na zmianę miesięcy poprzez interaktywne przyciski. Dzięki temu użytkownicy będą mogli łatwo przechodzić między różnymi miesiącami, co zwiększy użyteczność kalendarza.
Kalendarz w HTML, CSS i jQuery (JavaScript)

Refaktoryzacja kodu za pomocą fragmentów dokumentu

W poprzedniej wersji kodu (zobacz na GitHubie) każdy element był dodawany bezpośrednio do dokumentu. W nowej wersji używamy fragmentów dokumentu (DocumentFragment), aby zmniejszyć liczbę operacji DOM, co zwiększa wydajność. Fragment dokumentu to lekkie, „tymczasowe” naczynie na elementy DOM, które można zbudować w pamięci, a następnie dodać do DOM za pomocą jednej operacji.

$(document).ready(function() {
    // Dane startowe
    var date = new Date();
    var month = date.getMonth() + 1; // Pobranie aktualnego miesiąca i korekta indeksu (getMonth zwraca miesiące od 0 do 11)
    var year = date.getFullYear(); // Pobranie aktualnego roku w formacie czterocyfrowym
    var day = date.getDate(); // Pobranie aktualnego dnia miesiąca

    // Obliczenie pierwszego dnia miesiąca
    var startDay = new Date(year, month - 1, 1).getDay(); // .getDay() zwraca indeksy dni tygodnia 0-6, gdzie 0 to niedziela
    if (startDay === 0) { startDay = 7; } // Zamiana numeru dla niedzieli na format 1-7

    // Pobranie maksymalnego dnia w miesiącu
    var maxDay = new Date(year, month, 0).getDate();

    // Tablica z nazwami miesięcy
    var monthName = ["Styczeń", "Luty", "Marzec", "Kwiecień", "Maj", "Czerwiec", "Lipiec", "Sierpień", "Wrzesień", "Październik", "Listopad", "Grudzień"];

    // Tworzenie fragmentu dokumentu
    var fragment = $(document.createDocumentFragment());

    // Wyświetlanie nazwy miesiąca i roku oraz elementów nawigacyjnych
    fragment.append($('<div>').text("<"));
    fragment.append($('<div>').text(monthName[month-1] + " " + year).addClass('month-year'));
    fragment.append($('<div>').text(">"));

    // Tablica z nazwami dni tygodnia
    var dayName = ["Pon", "Wto", "Śro", "Czw", "Pią", "Sob", "Nie"];

    // Wyświetlenie nazw dni tygodnia z odpowiednią klasą dla weekendu
    for (let i = 0; i <= 6; i++) {
        divDay = $('<div>').text(dayName[i]).addClass('day-name');
        if (i === 0) { divDay.addClass('clear'); }
        if (i === 5) { divDay.addClass('saturday'); }
        if (i === 6) { divDay.addClass('sunday'); }
        fragment.append(divDay);
    }

    // Wyświetlenie pustych dni na początku miesiąca
    for (let i = 1; i < startDay; i++) {
        divDay = $('<div>').addClass('none');
        if (i === 1) { divDay.addClass('clear'); }
        fragment.append(divDay);
    }

    // Wyświetlenie dni miesiąca
    for (let i = 1; i <= maxDay; i++) {
        var ii = i + startDay - 1;
        divDay = $('<div>').text(i);
        if (ii % 7 === 1) { divDay.addClass('clear'); }
        if (i === day) {
            divDay.addClass('today');
        } else {
            if (ii % 7 === 0) { divDay.addClass('sunday'); }
            if (ii % 7 === 6) { divDay.addClass('saturday'); }
        }
        fragment.append(divDay);
    }

    // Dodanie całego fragmentu do elementu #calendar
    $("#calendar").append(fragment);
});

W nowej wersji kodu:

  • Tworzymy fragment dokumentu (var fragment = $(document.createDocumentFragment());).
  • Dodajemy wszystkie elementy do fragmentu dokumentu, zamiast bezpośrednio do DOM.
  • Po dodaniu wszystkich elementów, jednorazowo dołączamy cały fragment do elementu #calendar ($("#calendar").append(fragment);).

Więcej informacji na temat DocumentFragment znajdziesz w dokumentacji MDN.

Zobacz commit na GitHub.

Modernizacja kalendarza za pomocą CSS Grid

Teraz przejdziemy do zmiany układu kalendarza, wykorzystując nowoczesne techniki CSS Grid, aby poprawić jego strukturę i wygląd. CSS Grid to potężne narzędzie do tworzenia zaawansowanych układów siatki, które pozwala na elastyczne i responsywne rozmieszczanie elementów. Dzięki CSS Grid możemy łatwo tworzyć złożone układy, zachowując przy tym prostotę i czytelność kodu. W naszym przypadku CSS Grid eliminuje potrzebę stosowania hacków takich jak float i clear, upraszczając kod i poprawiając jego czytelność oraz elastyczność.

Nowy kod js:

$(document).ready(function() {
    // Dane startowe
    var date = new Date();
    var month = date.getMonth() + 1; // Pobranie aktualnego miesiąca i korekta indeksu (getMonth zwraca miesiące od 0 do 11)
    var year = date.getFullYear(); // Pobranie aktualnego roku w formacie czterocyfrowym
    var day = date.getDate(); // Pobranie aktualnego dnia miesiąca

    // Obliczenie pierwszego dnia miesiąca
    var startDay = new Date(year, month - 1, 1).getDay(); // .getDay() zwraca indeksy dni tygodnia 0-6, gdzie 0 to niedziela
    if (startDay === 0) { startDay = 7; } // Zamiana numeru dla niedzieli na format 1-7

    // Pobranie maksymalnego dnia w miesiącu
    var maxDay = new Date(year, month, 0).getDate();

    // Tablica z nazwami miesięcy
    var monthName = ["Styczeń", "Luty", "Marzec", "Kwiecień", "Maj", "Czerwiec", "Lipiec", "Sierpień", "Wrzesień", "Październik", "Listopad", "Grudzień"];

    // Tworzenie fragmentu dokumentu
    var fragment = $(document.createDocumentFragment());

    // Wyświetlanie nazwy miesiąca i roku oraz elementów nawigacyjnych
    fragment.append($('<div>').text("<"));
    fragment.append($('<div>').text(monthName[month-1] + " " + year).addClass('month-year'));
    fragment.append($('<div>').text(">"));

    // Tablica z nazwami dni tygodnia
    var dayName = ["Pon", "Wto", "Śro", "Czw", "Pią", "Sob", "Nie"];

    // Wyświetlenie nazw dni tygodnia z odpowiednią klasą dla weekendu
    for (let i = 0; i <= 6; i++) {
        divDay = $('<div>').text(dayName[i]).addClass('day-name');
        if (i === 5) { divDay.addClass('saturday'); }
        if (i === 6) { divDay.addClass('sunday'); }
        fragment.append(divDay);
    }

    // Wyświetlenie pustych dni na początku miesiąca
    for (let i = 1; i < startDay; i++) {
        divDay = $('<div>').addClass('none');
        fragment.append(divDay);
    }

    // Wyświetlenie dni miesiąca
    for (let i = 1; i <= maxDay; i++) {
        var ii = i + startDay - 1;
        divDay = $('<div>').text(i);
        if (i === day) {
            divDay.addClass('today');
        } else {
            if (ii % 7 === 0) { divDay.addClass('sunday'); }
            if (ii % 7 === 6) { divDay.addClass('saturday'); }
        }
        fragment.append(divDay);
    }

    // Dodanie całego fragmentu do elementu #calendar
    $("#calendar").append(fragment);
});

Nowy kod css:

#calendar {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 5px;
    max-width: 400px;
}

#calendar div {
    width: 100%;
    height: 50px;
    text-align: center;
    line-height: 50px;
    background-color: lightgrey;
}

#calendar .day-name {
    color: white;
    background-color: darkgrey;
}

#calendar .sunday {
    color: white;
    background-color: blue;
}

#calendar .saturday {
    color: white;
    background-color: lightblue;
}

#calendar .none {
    background-color: white;
}

#calendar .month-year {
    grid-column: span 5;
    text-align: center;
}

#calendar .today {
    color: white;
    background-color: red;
}

Zmiany w JavaScript:

W kodzie JavaScript usunęliśmy warunki dotyczące klasy clear. Wcześniej służyły one do wymuszania nowej linii po każdym tygodniu, co teraz jest zbędne dzięki CSS Grid.

Zmiany w CSS:

Definicja siatki:

#calendar {
    display: grid;
    grid-template-columns: repeat(7, 1fr);
    gap: 5px;
    max-width: 400px;
}

Ustawienie display: grid; – definiuje kontener jako siatkę.
grid-template-columns: repeat(7, 1fr); – ustawia siedem kolumn o równych szerokościach.
gap: 5px; – ustala odstęp między komórkami siatki.
max-width: 400px; – ogranicza maksymalną szerokość kalendarza.

Dostosowanie stylów elementów:

#calendar div {
    width: 100%;
    height: 50px;
    text-align: center;
    line-height: 50px;
    background-color: lightgrey;
}

width: 100%; – zapewnia, że każda komórka siatki wypełnia całą szerokość kolumny.

Klasa month-year:

#calendar .month-year {
    grid-column: span 5;
    text-align: center;
}

grid-column: span 5; – rozciąga element na pięć kolumn siatki, aby umieścić miesiąc i rok na środku.

Więcej informacji na temat CSS Grid można znaleźć w dokumentacji MDN.

Zobacz commit na GitHubie.

Nawigacja między miesiącami w kalendarzu

Wprowadzimy możliwość nawigacji między miesiącami za pomocą przycisków. Dodatkowo, dokonamy refaktoryzacji naszego kodu poprzez zamknięcie renderowania kalendarza w funkcję. Dzięki temu kod stanie się bardziej modularny i łatwiejszy do utrzymania.

Nowy kod JavaScript:

$(document).ready(function() {
    // Dane startowe
    var date = new Date();
    var month = date.getMonth() + 1; // Pobranie aktualnego miesiąca i korekta indeksu (getMonth zwraca miesiące od 0 do 11)
    var year = date.getFullYear(); // Pobranie aktualnego roku w formacie czterocyfrowym
    generateCalendar(month, year);

    function generateCalendar(month, year) {
        var day = date.getDate(); // Pobranie aktualnego dnia miesiąca

        // Obliczenie pierwszego dnia miesiąca
        var startDay = new Date(year, month - 1, 1).getDay(); // .getDay() zwraca indeksy dni tygodnia 0-6, gdzie 0 to niedziela
        if (startDay === 0) { startDay = 7; } // Zamiana numeru dla niedzieli na format 1-7

        // Pobranie maksymalnego dnia w miesiącu
        var maxDay = new Date(year, month, 0).getDate();

        // Tablica z nazwami miesięcy
        var monthName = ["Styczeń", "Luty", "Marzec", "Kwiecień", "Maj", "Czerwiec", "Lipiec", "Sierpień", "Wrzesień", "Październik", "Listopad", "Grudzień"];

        // Tworzenie fragmentu dokumentu
        var fragment = $(document.createDocumentFragment());

        // Wyświetlanie nazwy miesiąca i roku oraz elementów nawigacyjnych
        fragment.append($('<div>').text("<").addClass('nav prev'));
        fragment.append($('<div>').text(monthName[month - 1] + " " + year).addClass('month-year'));
        fragment.append($('<div>').text(">").addClass('nav next'));

        // Tablica z nazwami dni tygodnia
        var dayName = ["Pon", "Wto", "Śro", "Czw", "Pią", "Sob", "Nie"];

        // Wyświetlenie nazw dni tygodnia z odpowiednią klasą dla weekendu
        for (let i = 0; i <= 6; i++) {
            divDay = $('<div>').text(dayName[i]).addClass('day-name');
            if (i === 5) { divDay.addClass('saturday'); }
            if (i === 6) { divDay.addClass('sunday'); }
            fragment.append(divDay);
        }

        // Wyświetlenie pustych dni na początku miesiąca
        for (let i = 1; i < startDay; i++) {
            divDay = $('<div>').addClass('none');
            fragment.append(divDay);
        }

        // Wyświetlenie dni miesiąca
        for (let i = 1; i <= maxDay; i++) {
            var ii = i + startDay - 1;
            divDay = $('<div>').text(i);
            if (i === day) {
                divDay.addClass('today');
            } else {
                if (ii % 7 === 0) { divDay.addClass('sunday'); }
                if (ii % 7 === 6) { divDay.addClass('saturday'); }
            }
            fragment.append(divDay);
        }

        // Dodanie całego fragmentu do elementu #calendar
        $("#calendar").empty().append(fragment);
    }

    // Onclick dla przycisków nawigujących kalendarzem
    $(document).on('click', '.nav', function() {
        if ($(this).hasClass('prev')) {
            month--;
            if (month < 1) {
                month = 12;
                year--;
            }
        } else if ($(this).hasClass('next')) {
            month++;
            if (month > 12) {
                month = 1;
                year++;
            }
        }
        generateCalendar(month, year);
    });
});

Co zostało zmienione i dlaczego?

Zamknięcie renderowania kalendarza w funkcję generateCalendar(month, year):

Stworzenie funkcji generateCalendar pozwala na ponowne użycie kodu odpowiedzialnego za renderowanie kalendarza. Dzięki temu możemy dynamicznie aktualizować kalendarz przy zmianie miesiąca bez duplikowania kodu. Funkcja przyjmuje dwa argumenty, month i year, co umożliwia jej elastyczne użycie do generowania kalendarza dla dowolnego miesiąca i roku.

Czyszczenie zawartości kalendarza przed dodaniem nowego fragmentu:

$("#calendar").empty().append(fragment);

Metoda .empty() usuwa całą bieżącą zawartość elementu #calendar. Jest to ważne, aby uniknąć nakładania się starych i nowych elementów kalendarza, co mogłoby prowadzić do błędów i nieczytelności.

Dodanie przycisków nawigacyjnych:

fragment.append($('<div>').text("<").addClass('nav prev'));
fragment.append($('<div>').text(">") .addClass('nav next'));

Elementy <div> z tekstem „<” i „>” oraz klasami nav prev i nav next służą jako przyciski do nawigacji między miesiącami.

Obsługa kliknięć na przyciskach nawigacyjnych:

$(document).on('click', '.nav', function() {
    if ($(this).hasClass('prev')) {
        month--;
        if (month < 1) {
            month = 12;
            year--;
        }
    } else if ($(this).hasClass('next')) {
        month++;
        if (month > 12) {
            month = 1;
            year++;
        }
    }
    generateCalendar(month, year);
});

Dodanie zdarzenia click dla elementów z klasą nav. W zależności od tego, czy kliknięto przycisk „prev” czy „next”, zmieniamy wartość month i odpowiednio dostosowujemy year, jeśli zmiana miesiąca wykracza poza zakres (1-12).

Dodatkowy kod CSS. Stylowanie przycisków nawigacyjnych:

#calendar .nav {
    cursor: pointer;
    user-select: none;
}

Dodanie klasy nav z właściwościami cursor: pointer; i user-select: none; sprawia, że przyciski są bardziej intuicyjne w obsłudze (kursor zmienia się na „wskaźnik” przy najechaniu) oraz zapobiega zaznaczaniu tekstu przy kliknięciu.

Zobacz commit na GitHubie.

Aktualny dzień co miesiąc? Czas to naprawić!

Nasz kalendarz działa świetnie, ale jest jeden mały problem – aktualny dzień miesiąca świeci w każdym miesiącu! Coś jakbyśmy co miesiąc mieli urodziny. Aby naprawić tę anomalię, musimy upewnić się, że kolorowanie aktualnego dnia dotyczy tylko bieżącego miesiąca.

if (month === (new Date()).getMonth() + 1 && year === (new Date()).getFullYear() && i === day) {
    divDay.addClass('today');
} else {
    if (ii % 7 === 0) { divDay.addClass('sunday'); }
    if (ii % 7 === 6) { divDay.addClass('saturday'); }
}

Wcześniej, nasz kod kolorował aktualny dzień niezależnie od wyświetlanego miesiąca, co mogło prowadzić do zamieszania. Dzięki tej zmianie, upewniamy się, że kolorujemy tylko rzeczywisty aktualny dzień w bieżącym miesiącu i roku, co jest bardziej logiczne i użyteczne. Teraz już żaden użytkownik nie będzie miał urodzin co miesiąc w naszym kalendarzu!

Zobacz commit na GitHubie.

Zakończenie

I tak oto nasz kalendarz przeszedł metamorfozę! Od optymalizacji za pomocą fragmentów dokumentu, przez modernizację przy użyciu CSS Grid, aż po dodanie nawigacji między miesiącami. Dzięki tym zmianom, nasz kalendarz nie tylko działa sprawniej, ale też wygląda lepiej i jest bardziej użyteczny. Mam nadzieję, że te kroki pomogły wam zrozumieć, jak wprowadzać zmiany w istniejącym kodzie, aby go ulepszać.

W następnym wpisie zajmiemy się dodaniem wielojęzyczności do naszego kalendarza. Dane będziemy pobierali ajaxem z API, które z kolei będzie korzystało z bazy danych.

Bądźcie na bieżąco, bo wkrótce pojawią się kolejne ciekawostki z zakresu web developmentu! Sprawdź kod na GitHubie i śledź kolejne wpisy, aby być na bieżąco z najnowszymi technikami i narzędziami.

5 thoughts on “Refaktoryzacja i rozbudowa Kalendarza w HTML, CSS i jQuery”

  1. Pingback: Kalendarz w HTML, CSS i jQuery - brylka.net

  2. Dzięki za kolejny odcinek 🙂 Czekam z niecierpliwością na kolejny wpis.

    „…Aktualny dzień co miesiąc? Czas to naprawić!” …coś tutaj chyba nie poszło jak należy gdyż kolejne miesiące wyświetlają się z data zaznaczoną na czerwono.

  3. Pingback: Dodanie tłumaczeń do kalendarza za pomocą AJAX - brylka.net

Leave a Comment

Twój adres e-mail nie zostanie opublikowany. Wymagane pola są oznaczone *

Scroll to Top