Wraz z nastaniem listopada, przygotowania do świąt ruszyły pełną parą. Graficy właśnie kończą projektować świąteczne kartki oraz ulotki reklamujące świąteczne promocje, drukarnie sprawdzają zapasy czerwonego i zielonego tuszu, a programiści wklejają do kodu renifery stworzone za pomocą ASCII-Art. Poddając się temu nastrojowi, chciałem pokazać wam, jak w prosty sposób przyozdobić stronę internetową z okazji wszelkiego rodzaju świąt. Napiszemy do tego celu prosty plugin do jQuery.

Nauczymy się jak sprawić, aby z głośników odwiedzających naszą stronę gości pobrzmiewała kolęda „Cicha noc” w języku stosownym do lokalizacji użytkownika oraz by cały misternie projektowany interfejs strony zasypał padający z każdego narożnika ekranu śnieg! Pachnie kiczem? Macie rację… Nigdy czegoś takiego nie róbcie. Jaki jest jednak w ogóle sens dekorowania strony? Głównym celem tego zabiegu jest sprawienie wrażenia aktualności strony. Jest to przydatne szczególnie na stronach firmowych, na których nie ma aktualności i zwykle niewiele się dzieje.

Piszemy plugin do jQuery

Napiszemy prosty plugin do jQuery, który automatycznie sprawdzi czy aktualnie nie ma jakiegoś święta, a jeśli jest, przyozdobi logo umieszczone na stronie we wcześniej przygotowaną grafikę. Zaczniemy od gotowego szkieletu wtyczki, którego nie będę tutaj szerzej opisywał. Dla zainteresowanych tematem, polecam rozdział „jQuery Plugin Design Patterns” książki pod tytułem „Learning JavaScript Design Patterns” autorstwa Addy’ego Osmani, która jest w całości dostępna online za darmo. Szkielet ten zapisałem do pliku logoDecorator.js.

;(function ($, window, document, undefined) {
    var pluginName = 'logoDecorator',
        defaults = {};

    function Plugin(element, options) {
        this.element = element;

        this.options = $.extend({}, defaults, options) ;

        this._defaults = defaults;
        this._name = pluginName;

        this.init();
    }

    Plugin.prototype.init = function () {

    };

    $.fn[pluginName] = function (options) {
        return this.each(function () {
            if (!$.data(this, 'plugin_' + pluginName)) {
                $.data( this, 'plugin_' + pluginName,
                new Plugin(this, options));
            }
        });
    };
})(jQuery, window, document);

Rozpoznajemy święta

Pora nauczyć nasz skrypt rozpoznawać święta. Wykorzystując obiekt Date napiszemy jedną funkcję na jedno święto, każda będzie zwracała prawdę,  jeśli to święto występuje lub fałsz w przeciwnym wypadku. Na radzie skupimy się na obsłudze świąt Bożego Narodzenia, Sylwestra oraz Nowego Roku.

;(function ($, window, document, undefined) {
    var pluginName = 'logoDecorator',
        defaults = {
            date: null
        },
        today;

    function Plugin(element, options) {
        this.element = element;

        this.options = $.extend({}, defaults, options) ;

        this._defaults = defaults;
        this._name = pluginName;

        this.init();
    }

    Plugin.prototype.init = function () {
        // Inicjujemy obiekt z datą dzisiejszą lub tą podaną w opcjach.
        today = this.options.date !== null ? new Date(this.options.date) : new Date();
    };

    // Sprawdzamy, czy mamy okres świąteczny.
    function isChristmas() {
        if (today.getMonth() !== 11) {
            return false;
        }

        if (today.getDate() < 20) {
            return false;
        }

        if (today.getDate() > 30) {
            return false;
        }

        return true;
    }

    // Sprawdzamy, czy mamy Sylwester.
    function isNewYearsEve() {
        if (today.getMonth() !== 11 || today.getDate() !== 31) {
            return false;
        }

        return true;
    }

    // Sprawdzamy, czy mamy Nowy Rok.
    function isNewYear() {
        if (today.getMonth() !== 0 || today.getDate() !== 1) {
            return false;
        }

        return true;
    }

    $.fn[pluginName] = function (options) {
        return this.each(function () {
            if (!$.data(this, 'plugin_' + pluginName)) {
                $.data( this, 'plugin_' + pluginName,
                new Plugin(this, options));
            }
        });
    };
})(jQuery, window, document);

W funkcji sprawdzającej święta Bożego narodzenia założyłem, że okres świąteczny trwa od 20. do 30. grudnia i w te dni moje logo ma zostać udekorowane. Zwróćcie uwagę, że liczba 11 oznacza w tym wypadku grudzień, gdyż miesiące w JavaScript liczone są od zera. Kolejne dwie funkcje są niemal identyczne, zmieniają się w nich jedynie liczby oznaczające dni świąteczne. Oprócz napisania funkcji, zainicjowałem obiekt Date. Wywołanie go z pustym parametrem oznacza ustawienie daty na moment uruchomienia kodu, dodałem również możliwość podania konkretnej daty w opcjach, aby umożliwić testowanie poprawności funkcji.

Teraz skupimy się na określeniu czy aktualnie obchodzone jest jakieś święto. Do tego celu posłuży nam funkcja, która wywoła po kolei wszystkie te, które przed chwilą napisaliśmy i zwróci nazwę klasy CSS w której zapiszemy adres do obrazka ze świąteczną dekoracją. Jeśli żadna z funkcji sprawdzających nie zwróci prawdy, nasza nowa funkcja zwróci fałsz. Będzie ona wyglądała następująco:

function isHoliday() {
    switch(true) {
        case isChristmas(): return 'christmasDecoration';
        case isNewYearsEve(): return 'newYearsEveDecoration';
        case isNewYear(): return 'newYearDecoration';
        default: return false;
    }
}

Umieszczamy dekorator na logo

Gdy już nauczyliśmy nasz skrypt wykrywać święta, musimy go teraz nauczyć odnajdywać nasze logo i przykrywać je odpowiednią dekoracją. Nasza wtyczka będzie wywoływana na elemencie nad którym dekoracja ma się pokazać (mam na myśli $('#logo img').logoDecorator();, jeśli nie używacie obrazka, prawdopodobnie trzeba będzie przypisać rozmiar ręcznie). Musimy więc określić położenie tego elementu, jego wymiary, a następnie nanieść dekorację. Kolejna funkcja utworzy nam element z dekoracją i umieści go w odpowiednim miejscu nad logiem.

function placeDecorator(element, className, options) {
    // Inicjujemy zmienne.
    var $element = $(element),
        $decorator = $('#logoDecoratorElement'),
        offset = $element.offset(),
        dimensions = {};

    // Jeśli element dekoratora nie został wcześniej stworzony, tworzyny go teraz.
    if ($decorator.length === 0) {
        // Jeśli dekorator ma być klikalny, tworzymy go jako element A, DIV w przeciwnym wypadku.
        if (options.anchor !== null && $(options.anchor).length > 0 && $(options.anchor).is('a')) {
            $decorator = $('<a id="logoDecoratorElement" />').attr('href', $(options.anchor).attr('href')).css('display', 'block');
        } else {
            $decorator = $('<div id="logoDecoratorElement" />');
        }
    }

    // Jeśli w konfiguracji są podane wymiary, używamy ich. Jeśli nie, staramy się wyliczyć.
    dimensions.width = options.width > 0 ? options.width : $element.width() + options.offsetX * 2;
    dimensions.height = options.height > 0 ? options.height : $element.height() + options.offsetY * 2;

    // Przypisujemy pozycje, wymiary, dodajemy odpowiednią klasę i przypisujemy na koniec body.
    $decorator.css({
        width: dimensions.width + 'px',
        height: dimensions.height + 'px',
        position: 'absolute',
        zIndex: 10000,
        left: (offset.left - options.offsetX) + 'px',
        top: (offset.top - options.offsetY) + 'px'
    }).addClass(className).appendTo(document.body);
}

Po zainicjowaniu zmiennych, tworzymy element dekoratora, jeśli wcześniej nie został stworzony. Na podstawie przekazanej konfiguracji (o ich przekazywaniu za chwilę) sprawdzamy czy dekorator ma być klikalny, czy też nie. Następnie obliczamy wysokość i szerokość elementu, o ile sztywne wartości nie zostały przekazane w konfiguracji. Czasami może zajść taka potrzeba, gdyż z różnych powodów, pomiar elementu może dawać błędne rezultaty, szczególnie, jeśli używamy @font-face, gdzie elementy mogą zmieniać wymiary po załadowaniu się fontu.

grafmag-Dekorujemy-strone-internetowa-na-swieta

Pozostało nam już tylko uruchomić poszczególne funkcje. Kod uruchamiający umieszczamy w funkcji init, która najpierw sprawdzi, czy mamy jakieś święto – jeśli tak jest, umieści dekorator oraz przypisze zdarzenie, które przemieści nam element z dekoratorem w odpowiednie miejsce, gdy zmienią się wymiary okna. Cały kod w pliku logoDecorator.js powinien wyglądać teraz następująco:

;(function ($, window, document, undefined) {
    var pluginName = 'logoDecorator',
        defaults = {
            date: null,
            offsetX: 0,
            offsetY: 0,
            width: 0,
            height: 0,
            anchor: null
        },
        today;

    function Plugin(element, options) {
        this.element = element;

        this.options = $.extend({}, defaults, options) ;

        this._defaults = defaults;
        this._name = pluginName;

        this.init();
    }

    Plugin.prototype.init = function () {
        var holiday = false,
            element = this.element,
            options = this.options;

        // Inicjujemy obiekt z datą dzisiejszą lub tą podaną w opcjach.
        today = this.options.date !== null ? new Date(this.options.date) : new Date();

        // Sprawdzamy, czy mamy dziś jakieś święto.
        holiday = isHoliday();
        if (!holiday) {
            return false;
        }

        // Umieszczamy dekorator na odpowiednim miejscu
        placeDecorator(element, holiday, options);

        // Przypisujemy event do zmiany rozmiaru okna, aby dekorator podążał za logiem.
        $(window).on('resize', function() {
            placeDecorator(element, holiday, options);
        });

        return true;
    };

    function placeDecorator(element, className, options) {
        // Inicjujemy zmienne.
        var $element = $(element),
            $decorator = $('#logoDecoratorElement'),
            offset = $element.offset(),
            dimensions = {};

        // Jeśli element dekoratora nie został wcześniej stworzony, tworzyny go teraz.
        if ($decorator.length === 0) {
            // Jeśli dekorator ma być klikalny, tworzymy go jako element A, DIV w przeciwnym wypadku.
            if (options.anchor !== null && $(options.anchor).length > 0 && $(options.anchor).is('a')) {
                $decorator = $('<a id="logoDecoratorElement" />').attr('href', $(options.anchor).attr('href')).css('display', 'block');
            } else {
                $decorator = $('<div id="logoDecoratorElement" />');
            }
        }

        // Jeśli w konfiguracji są podane wymiary, używamy ich. Jeśli nie, staramy się wyliczyć.
        dimensions.width = options.width > 0 ? options.width : $element.width() + options.offsetX * 2;
        dimensions.height = options.height > 0 ? options.height : $element.height() + options.offsetY * 2;

        // Przypisujemy pozycje, wymiary, dodajemy odpowiednią klasę i przypisujemy na koniec body.
        $decorator.css({
            width: dimensions.width + 'px',
            height: dimensions.height + 'px',
            position: 'absolute',
            zIndex: 10000,
            left: (offset.left - options.offsetX) + 'px',
            top: (offset.top - options.offsetY) + 'px'
        }).addClass(className).appendTo(document.body);
    }

    // Sprawdzamy, czy mamy jakieś święto.
    function isHoliday() {
        switch(true) {
            case isChristmas(): return 'christmasDecoration';
            case isNewYearsEve(): return 'newYearsEveDecoration';
            case isNewYear(): return 'newYearDecoration';
            default: return false;
        }
    }

    // Sprawdzamy, czy mamy okres świąteczny.
    function isChristmas() {
        if (today.getMonth() !== 11) {
            return false;
        }

        if (today.getDate() < 20) {
            return false;
        }

        if (today.getDate() > 30) {
            return false;
        }

        return true;
    }

    // Sprawdzamy, czy mamy Sylwester.
    function isNewYearsEve() {
        if (today.getMonth() !== 11 || today.getDate() !== 31) {
            return false;
        }

        return true;
    }

    // Sprawdzamy, czy mamy Nowy Rok.
    function isNewYear() {
        if (today.getMonth() !== 0 || today.getDate() !== 1) {
            return false;
        }

        return true;
    }

    $.fn[pluginName] = function (options) {
        return this.each(function () {
            if (!$.data(this, 'plugin_' + pluginName)) {
                $.data( this, 'plugin_' + pluginName,
                new Plugin(this, options));
            }
        });
    };
})(jQuery, window, document);

Uruchamiamy plugin

Teraz stwórzmy plik index.html, w którym będziemy mogli podejrzeć efekty naszego dzieła. Ja w swoim przykładzie umieściłem po prostu logo na środku strony.

<!DOCTYPE html>
<html lang="pl">
    <head>
        <meta charset="utf-8">

        <title>Dekorator Logo</title>

        <style>
            html, body {
                margin: 0px;
            }

            #container {
                position: fixed;
                left: 50%;
                top: 50%;
                width: 223px;
                height: 37px;
                text-align: center;
                -webkit-transform: translate(-50%,-50%);
                -ms-transform: translate(-50%,-50%);
                transform: translate(-50%,-50%);
            }

            /* Ustawiamy klasy odpowiedzialne za wyświetlanie prawidłowego obrazka */

            #logoDecoratorElement.christmasDecoration {
                background: transparent url('images/boze-narodzenie.png') no-repeat center center;
            }

            #logoDecoratorElement.newYearsEveDecoration {
                background: transparent url('images/sylwester.png') no-repeat center center;
            }

            #logoDecoratorElement.newYearDecoration {
                background: transparent url('images/nowy-rok.png') no-repeat center center;
            }
        </style>
    </head>
    <body>
        <div id="container">
            <a href="http://grafmag.pl" id="logo"><img src="images/logo.png" alt="Grafmag"></a>
        </div>

        <script src="//ajax.googleapis.com/ajax/libs/jquery/1.10.2/jquery.min.js"></script>
        <script src="js/logoDecorator.js"></script>
        <script>
            // Inicjujemy plugin. 
            $(window).ready(function() {
                $('#logo img').logoDecorator({
                    offsetX: 122.5,
                    offsetY: 37,
                    anchor: '#logo'
                });
            });
        </script>
    </body>
</html>

Jak widać, napisałem klasy, których nazwy odpowiadają tym, które wcześniej zwracała funkcja isHoliday. Na samym dole plugin zostaje zainicjowany. Należy to zrobić, gdy okno (a nie dokument) będzie gotowe, aby mieć pewność, że obrazek z logo jest już załadowany i można go zmierzyć. Dostępnymi opcjami, które przekazujemy jako parametr są:

  • date – opcja przydatna, jeśli chcemy przetestować działanie pluginu; domyślnie testowany na obecność świąt jest dzień dzisiejszy, jeśli podamy datę za pomocą tej opcji (w formacie RRRR-MM-DD), możemy sprawdzić, czy święto na podany dzień jest dobrze wykrywane;
  • offsetX – określa o ile wysokość dekoratora ma być wyższa od wysokości loga (w pikselach);
  • offsetY – określa o ile wysokość dekoratora ma być szersza od wysokości loga (w pikselach);
  • width – jeśli szerokość jest została źle wyliczona automatycznie, możemy ją podać tutaj;
  • height – jeśli wysokość jest została źle wyliczona automatycznie, możemy ją podać tutaj;
  • anchor – tutaj podajemy selektor odnośnika, z które ma zostać pobrany adres URL, jeśli dekorator ma być klikalny.

Bonus: Jak ustalić, kiedy obchodzimy Wielkanoc?

Dodawanie nowych świąt do sprawdzenia nie powinno sprawić wam żadnego problemu. No może z wyjątkiem Wielkanocy. Jest to święto ruchome, a więc niepowiązane z żadną datą. Nie mamy innego wyjścia jak obliczyć, kiedy ona wypada. Posłużymy się w tym celu metodą Meeusa/Jonesa/Butchera, która pozwala wyliczyć dzień niedzieli wielkanocnej dla podanego roku. Okres wielkanocny zaczyna się już od Wielkiego Czwartku i trwa aż do Lanego Poniedziałku, także musimy uwzględnić w naszym sprawdzaniu również te dni. Gotowa funkcja wygląda następująco:

function isEaster() {
    var a, b, c, d, e, f, g, h, i, j, k, m, month, day, firstDay, temp;

    a = today.getFullYear() % 19;
    b = Math.floor(today.getFullYear() / 100);
    c = today.getFullYear() % 100;
    d = Math.floor(b / 4);
    e = b % 4;
    f = Math.floor((b + 8) / 25);
    g = Math.floor((b-f + 1) / 3);
    h = (19 * a + b - d - g + 15) % 30;
    i = Math.floor(c / 4);
    j = c % 4;
    k = (32 + 2 * e + 2 * i - h - j) % 7;
    m = Math.floor((a + 11 * h + 22*k) / 451);
    month = Math.floor((h + k - 7 * m + 114) / 31);
    day = ((h + k - 7 * m +114) % 31) + 1;

    firstDay = new Date(today.getFullYear(), (month - 1), day);

    // Sprawdzamy, czy mamy Niedzielę Wielkanocną
    if (today.getMonth() == firstDay.getMonth() && today.getDate() == firstDay.getDate()) {
        return true;
    }

    temp = new Date(today.getFullYear(), (month - 1), day);

    // Sprawdzamy, czy mamy Lany Poniedziałek
    temp.setDate(temp.getDate() + 1);
    if (today.getMonth() == temp.getMonth() && today.getDate() == temp.getDate()) {
        return true;
    }

    // Sprawdzamy, czy mamy Wielką Sobotę
    temp.setDate(temp.getDate() - 2);
    if (today.getMonth() == temp.getMonth() && today.getDate() == temp.getDate()) {
        return true;
    }

    // Sprawdzamy, czy mamy Wielki Piątek
    temp.setDate(temp.getDate() - 1);
    if (today.getMonth() == temp.getMonth() && today.getDate() == temp.getDate()) {
        return true;
    }

    // Sprawdzamy, czy mamy Wielki Czwartek
    temp.setDate(temp.getDate() - 1);
    if (today.getMonth() == temp.getMonth() && today.getDate() == temp.getDate()) {
        return true;
    }

    return false;
}

W naszym kalendarzu istnieją jeszcze dwa ruchome święta, które są ustawowo wolne od pracy, ale ich datę wylicza się na podstawie dnia Wielkanocy. Są to Uroczystość Wniebowstąpienia Pańskiego (znana szerzej, jako Boże Ciało – 40 dni po Wielkanocy) oraz Zesłanie Ducha Świętego (bardziej znane jako Zielone Świątki – 50 dni po Wielkanocy).

Na zakończenie

I to już wszystko! Mam nadzieję, że znajdziecie wykorzystanie dla przedstawionego tutaj kodu i stworzycie na jego podstawie własne implementacje. My na pewno wykorzystamy go do przyozdobienia naszego logo. W następnym artykule, który ukarze się w grudniu, pokażę Wam jak wykorzystać inne możliwości, jakie daje nam obiekt Date w JavaScript: stronę reagującą na porę roku i dnia oraz na dni wolne od pracy. A wszystkich, którzy byli zawiedzeni brakiem skryptu, do tworzenia śniegu na stronie, odsyłam do skryptu Scotta Schillera.

Pobierz Zobacz demo