Szablony w HTML – Nunjucks ninja

28.02.2023 | Autor: Przemysław

HTML

Hipertekstowy język znaczników, używany do tworzenia dokumentów/stron internetowych.
Dzięki wspomnianym znacznikom mamy możliwość budowania zaawansowanych strukturalnie dokumentów internetowych najczęściej wykorzystywanych jako zwykłe strony, wizytówki firmowe, aplikacje SPA, sklepy internetowe, blogi itd. W HTML’u mamy wiele typów znaczników odpowiadających głównie za rodzaj prezentowanej treści. Najczęściej wykorzystywane i popularne są między innymi nagłówki (H1..H5), paragrafy (P), listy numerowane i nienumerowane (OL, UL), tabele (TABLE), formularze z wieloma typami pól do wypełnienia (FORM, INPUT, TEXTAREA, SELECT) obrazki (IMG) oraz wiele innych. Dodatkowo są jeszcze znaczniki stylizujące jak np. pogrubienia (STRONG, B), podkreślenia (U), czy kursywa (I). Ponadto jest też kilka tagów, które odpowiadają głównie za strukturę czy układ dokumentu, nie wpływając znacząco na funkcje czy wygląd danego elementu, są to między innymi sekcje (SECTION), artykuły (ARTICLE), elementy nawigacyjne (NAV), frotery i headery (FOOTER, HEADER), sidebary (ASIDE), oraz wszechobecne i uniwersalne (DIV).

Poza strukturalnym elementami, za 90% wyglądu odpowiada jeszcze arkusz styli, dzięki któremu możemy wpływać na konkretny element w dowolny sposób, nadając mu unikalny wygląd rozmiar położenie względem innych itd. itp. W tym artykule zajmiemy się jednak samą strukturą dokumentu HTML.

Nunjucks

W skrócie jest to bogaty język szablonów dla JavaScriptu. Szybki, lekki (zminimalizowany 8kb), rozszerzalny przez różne filtry i wtyczki, dostępny w NODE, we wszystkich nowoczesnych przeglądarkach, z możliwością prekompilacji.

Spośród wielu możliwości, jakie oferuje nam nunjucks, w dzisiejszym artykule zajmiemy się szablonowaniem dokumentu HTML, dzięki któremu, przy dużych layoutach możemy sprawnie budować, generować powielające się fragmenty kodu i wielokrotnie je wykorzystywać w różnych widokach, bez konieczności pisania ich czy kopiowania z poprzednich. Działa to na podobnej zasadzie jak sytem szablonów w php (Twig) i tym podobne. Możemy cały layout dzielić na fragmenty, a potem składać je z klocków i dowolnie miksować. Teoretycznie niewiele, ale kiedy trzeba coś zmienić w konkretnym elemencie, który występuje na każdej podstronie lub w kilku miejscach w wielu różnych podstronach, daje nam to możliwość zmiany tylko w jednym miejscu i zmiana zajdzie w każdym innym, w którym skorzystaliśmy z mocy nunjucks.

Nunjucks diagram

Nunjucks diagram

Prosty przykład dowolnej strony internetowej z wieloma podstronami, gdzie header i footer jest identyczny na każdej z nich. W takim wypadku tworzymy podstawową strukturę dokumentu i rozszerzamy go o konkretne fragmenty.

<!DOCTYPE html>
<html lang="pl" dir="ltr">
    <head>
        <meta charset="utf-8">
        <title>lorem1</title>
    </head>
    <body>
    {% block header %}
        {% import "header.njk" as h %}{{ h.header(active=0) }}
    {% endblock %}

    {% block content %}
        <main>
            <section>
                <h1>main content</h1>
                <p>Lorem ipsum dolor sit amet, consectetur adipisicing elit. Rem sed alias quam explicabo sequi corporis asperiores blanditiis, maiores! Sit quasi a nobis fugiat, vero optio facere voluptates, repellat illum repudiandae.</p>
            </section>
        </main>
    {% endblock %}

    {% block footer %}
        {% include "footer.njk" %}
    {% endblock %}
    </body>
</html>

W powyższym przykładzie mamy podstawową strukturę dokumentu html, są w niej wszystkie wymagane znaczniki określające strukturę w dokumencie, jest główny wewnątrz którego mamy i , nowością są zdefiniowane w fragmenty kodu {%. blok nazwa %}{%.endblock %}. Pomiędzy tymi nowymi znacznikami możemy umieszczać standardowe fragmenty HTML albo inkludować z innych plików, inne fragmenty hard-kodu (footer.njk) lub wykorzystać makro, coś w rodzaju funkcji, ({% import "header.njk" as h %}{{ h.header(active=0) }}) która generuje nam kod na podstawie pętli, warunków, danych wejściowych itp.

Przykładowy plik odpowiedzialny za footer może mieć postać statycznego fragmentu HTML z podstawowymi danymi kontaktowymi i copyright jak poniżej.

<div class="row jcc aic">
    <div class="copy col-12 col-lg-auto ta-c">
        <p>© Okinet. All rights reserved 2023</p>
    </div>
    <div class="info col-12 col-lg-auto ta-c">
        <p>Contact us at <a href="mailto:[email protected]">[email protected]</a></p>
    </div>
</div>

Deeper dive in nunjucs

System szablonów nunjucs to nie tylko fragmentacja i wielokrotne wykorzystanie kodu w wielu miejscach to także potężne narzędzie do generowania kodu na podstawie danych wejściowych.
Po pierwsze, potrzebujemy do tego samych danych, na podstawie których będziemy pracować. Możemy je przechowywać w miejscu wykorzystania danego fragmentu kodu bądź w oddzielnych plikach. Struktura danych może mieć postać jednej ze standardowych form w JavaScripcie, mianowicie obiekt JSON. Deklarujemy go jako zmienną o konkretnej nazwie i wartości którą, przyjmie.
Dla przykładu posłużymy się uproszczoną strukturą dla listy typowych newsów, którą przechowamy w oddzielnym pliku. Każdy teki element powinien posiadać między innymi: autora, datę publikacji, tytuł, krótki opis oraz ewentualnie zdjęcie wyróżniające.

Przykładowy obiekt z newsami może wyglądać następująco:

{%
    set news01 = [
        {
            "img": "./img/newsImg01.jpg",
            "alt": "The State of Ransomware in Manufacturing and Production 2022",
            "title": "The State of Ransomware in Manufacturing and Production 2022",
            "daesc": "Ut enim ad minim veniam, quis nostrud exercitation ullamco laboris nisi ut aliquip ex ea commodo consequat. Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur.",
            "date": "2023-02-03",
            "author": "John Doe"
        },
        {
            "img": "./img/newsImg02.jpg",
            "alt": "Sophos MDR enables London South Bank University to deliver strategic IT priorities",
            "title": "Sophos MDR enables London South Bank University to deliver strategic IT priorities",
            "daesc": "Duis aute irure dolor in reprehenderit in voluptate velit esse cillum dolore eu fugiat nulla pariatur. Excepteur sint occaecat cupidatat non proident, sunt in culpa qui officia deserunt mollit anim id est laborum.",
            "date": "2023-02-03",
            "author": "John Doe"
        },
        {
            "img": "./img/newsImg03.jpg",
            "alt": "Sophos MDR: Results from the first MITRE Engenuity ATT&CK Evaluation for Security Service",
            "title": "Sophos MDR: Results from the first MITRE Engenuity ATT&CK Evaluation for Security Service",
            "daesc": "Lorem ipsum dolor sit amet, consectetur adipisicing elit, sed do eiusmod tempor incididunt ut labore et dolore magna aliqua.",
            "date": "2023-02-03",
            "author": "John Doe"
        }
    ]
%}

W powyższym przykładzie widać sposób deklaracji zmiennych pomiędzy klamrami {% %}, większość pętli i warunków również umieszczamy pomiędzy tymi klamrami, nazwę zmiennej poprzedzamy słowem „set”, natomiast samą zmienną wykorzystujemy, stosując podwójne klamry {{ news01 }}

Dla powyższej struktury danych stwórzmy teraz plik z makrem, który nam wygeneruje listę newsów. Wiemy, że pojedynczy wpis będzie zawierał takie informacje jak zdjęcie, atrybut alt, tytuł, krótki opis, datę i autora. Struktura poniżej przedstawia przykładowy kod, który możemy wykorzystać.

news_list.njk
{% macro news_list(object, slider=false) %}
    <div class="{% if slider %}newsSlider{% else %}newsList{% endif %} col-12 px-lg-0">
        <div class="{% if slider %}newsSliderWrapper owl-carousel{% else %}row{% endif %}">
            {% for ob in object %}
                <div class="{% if slider %}newsSliderSingle{% else %}newsListSingle col-12 col-md-6 col-lg-4{% endif %}">
                    <div class=„row jcb">
                        <div class="img col-12"><a href="#"><img src="{{ ob.img }}" alt="{{ ob.alt }}"></a></div>
                        <div class="title col-12"><h3><a href="#">{{ ob.title }}</a></h3></div>
                        <div class="desc col-12"><p>{{ ob.desc }}</p></div>

                        <div class="author col-12”><p><a href="#">{{ ob.author }}</a> - {{ ob.date }}</p></div>

                        <a href="#" class="more col-12" title="{{ ob.title }}">Czytaj więcej</a>
                    </div>
                </div>
            {% endfor %}
        </div>
    </div>
{% endmacro %}

W powyższym fragmencie mamy wykorzystanych kilka podstawowych, ogólnie znanych struktur w programowaniu. Wykorzystujemy instrukcję warunkową
{% if slider %} … {% else %} … {% endif %}
Daje nam to możliwość zmiany wyglądu/struktury/itp. danego elementu na podstawie wyniku warunku.
Operator else jest opcjonalny i niekonieczny do wykorzystania.
Instrukcję warunkową, przedstawioną w przykładzie w standardowej strukturze, możemy wykorzystać również jako jedno linijkową wersje w formie {{ "true" if foo else "false" }}

Wykorzystujemy również pętle po wszystkich elementach w przekazanym obiekcie,

{% for ob in object %}

{% endfor %}

Dzięki czemu tworząc pojedynczą strukturę danego newsa, wygeneruje nam całą listę, w zależności od tego ile mamy zadeklarowanych w obiekcie elementów.
Pętle dają nam możliwość przechodzenia po obiektach, tablicach i innych grupujących strukturach. Mamy również dostęp specjalnych wartości (loop.index, loop.index0, loop.revindex, loop.revindex0, loop.first, loop.last, loop.length) dające nam informacje o indeksie, ilości elementów w pętli, mamy dostęp do pierwszych elementów ostatnich itd.
Nunjucks daje nam również możliwość stosowania filtrów w pętlach, dzięki którym możemy przykładowo posortować literalnie elementy.

Dodatkowo w pętli po wszystkich obiektach korzystamy ze zmiennych, które są pojedynczymi wartościami odpowiadającymi za poszczególne deklaracje w drzewie JSON.
{{ ob.title }}
{{ ob.desc }} itd.
Zmienne podobnie jak w pętlach, również mają możliwość zastosowania różnego rodzaju filtrów manipulujących prezentowanymi danymi. Filtry są pewnego rodzaju funkcjami, które mogą przyjmować argumenty. Filtry stosujemy, dodając znak „|”.

{{ ob.title | title }}
{{ ob.list | join(",") }}
{{ ob.desc | replace("foo", "bar") | capitalize }}

Po tych przygotowaniach możemy teraz wykorzystać je w dowolnym miejscu na stronie lub wielu podstronach. Robimy to, załączając plik z danymi, oraz plik z szablonem listy newsów. Następnie wykorzystujemy zadeklarowane w nim makro i przekazujemy mu niezbędne dane.

Nunjucks macro

{% block content %}
    {% import "objects.njk" as ob %}
    <main>
        <section class=”news”>
            <row class=”row”>
                {% import „news_list.njk" as n %}
                {{ n.news_list(ob. news01, „false”) }}
            </row>
        </section>
    </main>
{% endblock %}

Możemy jeszcze skorzystać z bloku call, który jest podobny do importu, z tą różnica, że możemy go wywołać razem z zawartością wew. bloku, np.:

{% macro dodaj(x, y) %}
    {{ caller() }}: {{ x + y }}
{% endmacro%}

{% call dodaj(1, 2) -%}
    Wynikiem dodawania to: 
{%- endcall %}

Wynik: „Wynikiem dodawania to: 3”.

Nunjucks, poza dodawaniem i wielokrotnym wykorzystaniem fragmentów kodu, daje nam też możliwość rozszerzania przygotowanych wcześniej szablonów/plików.

rodzic.html
{% block header %}
	Podstawowa treść headera
{% endblock %}

<section class="left">
	{% block left %}{% endblock %}
</section>

<section class="right">
	{% block right %}
		Treść prawej kolumny
	{% endblock %}
</section>

Następnie w „dziecku” możemy wykorzystać całą strukturę „rodzica” i uzupełnić ją o pożądane treści.

dziecko.html
{% extends "parent.html" %}

{% block left %}
    Treść prezentowana na podstronie w lewej kolumnie
{% endblock %}

{% block right %}
    Treść prezentowana na podstronie w prawej kolumnie
{% endblock %}

Mamy również możliwość wykorzystania wartości predefiniowanych w rodzicu i przekazania ich do dziecka plus dopisanie własnych treści, wywołując dyrektywę super.

dziecko2.html
{% block right %}
    {{ super() }}
    Treść prezentowana na podstronie w prawej kolumnie
{% endblock %}

Więcej możliwości

Dzięki nunjucks mamy nie tylko możliwość dziedziczenia szablonów, generowania treści, wykorzystywania zmiennych, ale również możemy nimi dowolnie manipulować. Umożliwiają nam to operatory arytmetyczne dodawania, odejmowania, dzielenia, mod, mnożenia, potęgowania. Możemy również korzystać z operatorów logicznych i porównawczych do badania różnych wartości, o czym już było wstępnie wspomniane wcześniej przy warunkach. 

Nunjucks umożliwia nam również wywoływanie własnych zadeklarowanych w skrypcie funkcji.

{{ sort(1, 4, 3, 5, 9, 2, 6) }}

Dostępne są również wyrażenia regularne, które tworzymy podobnie jak w czystym JavaScript, poprzedzając wyrażenie prefiksem „r/”, Więcej na temat samego Regex możemy przeczytać w dokumentacji MDN.

{% set regExp = r/^lorem.*/g %}
{% if regExp.test('Lorem, ipsum dolor sit amet consectetur adipisicing elit. In, magni.') %}
	Mamy to!
{% endif %}

Nunjucks oferuje jeszcze wiele więcej, wiele wbudowanych funkcji, oraz wspomnianych filtrów, o czym więcej możemy się dowiedzieć z dokumentacji.

Podsumowując

Zajęliśmy się w tym artykule głównie szablonowaniem widoków w html. Wspomnieliśmy również o wielu dodatkowych możliwościach, jakie niesie ze sobą nunjucks, a jeszcze więcej szczegółów można doczytać w dokumentacji. Niewątpliwie jest to potężne narzędzie usprawniające prace w dużych projektach z wieloma widokami, gdzie zarządzanie powtarzającą się treścią i elementami może być znacząco ułatwione i przyśpieszone, ale również sprawdzi się w prostych SPA, gdzie ułatwi nam tworzenie i zarządzanie różnego rodzaju listami, liderami, newsami i innymi powielającymi się elementami.

Porozmawiaj z nami
o swoim projekcie

+48 506 160 480
[email protected]

lub napisz