Szablony w JavaScript z wykorzystaniem biblioteki mustache.js
Mustache.js to biblioteka, której zadaniem jest oddzielenie logiki aplikacji JavaScript od kodu HTML, poprzez obsługę szablonów. W tym krótkim kursie postaram się pokazać, w jaki sposób pisać szablony dla naszego kodu.
Mustache.js jest określany jako „logic-less template system”, ponieważ bazuje wyłącznie na specjalnych znacznikach i nie używa żadnych warunków, pętli, czy innych wyrażeń logicznych wykorzystywanych podczas programowania. Korzystanie z tej biblioteki nie jest skomplikowane. Wystarczy nauczyć się kilku podstawowych funkcji i tagów, aby z powodzeniem móc pisać swoje własne szablony w JS.
Podstawy mustache.js
W pierwszej kolejności przedstawmy podstawowe elementy omawianej biblioteki, z których najczęściej będziemy korzystać podczas pisania naszych skryptów.
Znaczniki:
- {{zmienna}} – znacznik wyświetlający zmienną
- {{{zmienna}}} lub {{&zmienna}} – to samo, co powyżej, tylko że znaczniki HTML nie są escape-owane
- {{#zmienna}} … {{/zmienna}} – sekcja, zawartość pomiędzy tymi znacznikami wykona się 0, 1 lub n razy w zależności od wartości zmiennej – jeżeli wartość zmiennej będzie false, 0, null, itp. to sekcja nie wykona się ani razu
- {{^zmienna}} … {{/zmienna}} – odwrotność powyższego, tzn. w przypadku gdy zmienna przyjmie wartość false, o, null itp. zawartość sekcji zostanie wyświetlona
- {{.}} – znacznik służący do wypisania kolejnych elementów w tablicy (zazwyczaj wykorzystywany w sekcji)
- {{! komentarz}} – zawartość tego znacznika nie zostanie wyświetlona
- {{> kod_html}} – wydzielony kod HTML jako część szablonu
Funkcje:
- render – zwraca wynikowy kod HTML, przetworzonego z użyciem szablonu i zmiennych – przyjmuje dwa podstawowe argumenty: szablon i obiekt JS
- parse –analizuje szablon ale go nie używa, jedynie zapisuje do cache – przyjmuje jeden argument: szablon, użycie tej funkcji w pewnych sytuacjach pozwala na przyśpieszenie działania aplikacji (np. na samym początku, w momencie ładowania się naszej aplikacji, można załadować szablony, tak aby później ich renderowanie wykonywało się szybciej)
Poniższy kod pokazuje bardzo proste, przykładowe użycie systemu szablonów mustache.js:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 |
<main id="books"></main> <script src="mustache.js"></script> <script> var book = {title: 'Pan Tadeusz', author: 'Adam Mickiewicz'}; var template = '<h1>{{title}}</h1> <p>{{author}}</p>'; var content = Mustache.render(template, book); document.getElementById('books').innerHTML = content; </script> |
Mamy znacznik <main> kodu HTML, w którym chcemy wyświetlić wynik działania naszego skryptu JS. Do kodu strony dołączamy bibliotekę mustache.js, a następnie zaczynamy pisać kod. W pierwszej kolejności tworzymy obiekt książki book, zawierający tytułu oraz imię i nazwisko autora. Dalej do zmiennej template przypisujemy szablon HTML, złożony z nagłówka i akapitu. Korzystając z odpowiednich znaczników mustache.js, wskazujemy miejsca umieszczenia danych. Następnie korzystając z funkcji render, łączymy szablon z danymi, i na końcu wstawiamy wynik na stronie.
Wykorzystanie – lista książek
Zajmijmy się praktycznym wykorzystaniem biblioteki w przykładowej aplikacji JavaScript. Załóżmy, że mamy do napisania skrypt wyświetlający listę książek w księgarni wraz z ich autorami, ceną, rokiem pierwszego wydania, itd. Nasz kod wygląda następująco:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 |
<!DOCTYPE html> <html> <head> <meta charset="UTF-8"> <meta name="viewport" content="width=device-width"> <title>Mustache.js - Lista książek w bibliotece</title> </head> <body> <main> <h1>Lista Książek</h1> <div id="books-list"></div> </main> <!-- INCLUDES --> <script src="jquery-1.11.2.min.js"></script> <script src="mustache.js"></script> <!-- TEMPLATES --> <script id="books-template" type="x-tmpl-mustache"> <ul> {{#books}} <li> <h2>{{title}}</h2> <i>{{author}}</i> <br /> <span>cena: {{price}} PLN</span> <br /> <span>rok wyd. {{year}}</span> </li> {{/books}} {{^books}} <li>Nie znaleziono żadnych książek...</li> {{/books}} </ul> </script> <!-- LOGIC --> <script> var library = {books: [ {title: 'Pan Tadeusz', author: 'Adam Mickiewicz', price: 40, year: 1834}, {title: 'Potop', author: 'Henryk Sienkiewicz', price: 35, year: 1886}, {title: 'Wesele', author: 'Stanisław Wyspiański', price: 25, year: 1901}, {title: 'Dziady', author: 'Adam Mickiewicz', price: 66, year: 1822}, {title: 'Lalka', author: 'Bolesław Prus', price: 39, year: 1890}, {title: 'Quo Vadis', author: 'Henryk Sienkiewicz', price: 40, year: 1895} ]}; var booksTemplate = jQuery('#books-template').html(); var content = Mustache.render(booksTemplate, library); jQuery('#books-list').html(content); </script> </body> </html> |
Wszystkie skrypty JS umieściłem na dole strony, natomiast jak widać użyty kod HTML jest bardzo prosty, aby nie zaciemniać przykładu. Lista książek będzie wyświetlać się w bloku <div>, poniżej nagłówka <h1>. Podczas pisania naszego skryptu, dla wygody, użyjemy frameworka jQuery. No i oczywiście poniżej załączamy system szablonów mustache.js.
Szablon, który będziemy wykorzystywać do pokazania listy, umieśćmy w osobnym znaczniku <script>. Wszystkie książki wypisywane będą jako elementy listy nieuporządkowanej, dlatego użyliśmy znacznika sekcji ({{#books}} … {{/books}}), który będzie przechodził po wszystkich książkach w obiekcie i umieszcza poszczególne informacje w kolejnych pozycjach na liście. W przypadku gdy w bibliotece zabraknie książek, poniżej wywołany zostanie odpowiedni napis, umieszczony w osobnych znacznikach sekcji.
Sam kod JavaScript wygląda następująco. Obiekt library zawiera wszelkie dane dotyczące książek. Oczywiście równie dobrze informacje te mogą być asynchronicznie ładowane z serwera lub pobierane z pliku, jednak w opisywanym przykładzie jest to nie istotne. W następnym kroku, do zmiennej booksTemplate, wykorzystując jQuery, przypisujemy wcześniej przygotowany szablon. No i w końcu wykorzystując funkcję render biblioteki mustache.js, tworzymy listę książek i umieszczamy ją w wybrane miejsce na stronie.
Na razie wszystko działa fajnie i jak widać nie jest to specjalnie skomplikowane. Spróbujmy rozszerzyć naszą listę o kilka dodatkowych elementów. Po pierwsze, poniżej każdej z pozycji chciałbym wyświetlić listę występujących w książce postaci. Ponadto, podane ceny książek nie uwzględniają podatku VAT, dlatego byłoby dobrze, aby obok każdej z cen pokazać ile wynosi cena brutto (o ile się nie mylę, to obecnie w Polsce podatek ten wynosi 7%). Chciałbym również podać informacje jak dawno temu poszczególna książka była wydane (tzn. ile minęło lat od pierwszego wydania do dziś). Wszystkie te zmiany uwzględnia poniższy listing.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 |
<!-- TEMPLATES --> <script id="books-template" type="x-tmpl-mustache"> <ul> {{#books}} <li> <h2>{{title}}</h2> <i>{{author}}</i> <br /> <span>cena: <b>{{price}} PLN</b> (z wliczonym podatkiem VAT: {{priceWithVat}})</span> <br /> <span>rok wyd. <b>{{year}}</b> (wydana {{yearAgo}} lat temu)</span> <br /> {{#arePeople}}<span>postacie: {{#people}} <i>{{.}}</i> {{/people}}</span>{{/arePeople}} </li> {{/books}} {{^books}} <li>Nie znaleziono żadnych książek...</li> {{/books}} </ul> </script> <!-- LOGIC --> <script> var vat = 0.07; var currentYear = new Date().getFullYear(); var library = {books: [ {title: 'Pan Tadeusz', author: 'Adam Mickiewicz', price: 40, year: 1834, people: ['Jacek Soplica', 'Tadeusz', 'Zosia', 'Telimena', 'Hrabia']}, {title: 'Potop', author: 'Henryk Sienkiewicz', price: 35, year: 1886, people: ['Andrzej Kmicic', 'Bilewiczówna', 'Wołodyjowski', 'Zagłoba']}, {title: 'Wesele', author: 'Stanisław Wyspiański', price: 25, year: 1901, people: ['Chochoł', 'Widmo', 'Stańczyk', 'Hetman', 'Gospodarz']}, {title: 'Dziady', author: 'Adam Mickiewicz', price: 66, year: 1822, people: []}, {title: 'Lalka', author: 'Bolesław Prus', price: 39, year: 1890, people: ['Wokulski', 'Łęcka', 'Rzecki', 'Ochocki']}, {title: 'Quo Vadis', author: 'Henryk Sienkiewicz', price: 40, year: 1895, people: ['Winicjusz', 'Petroniusz', 'Ligia', 'Neron', 'Piotr Apostoł']} ], arePeople: function() { if (typeof this.people !== 'undefined' && this.people.length > 0) { return true; } return false; }, priceWithVat: function() { vatPrice = this.price + this.price * vat; return vatPrice.toFixed(2); }, yearAgo: function() { return currentYear - this.year; } }; var booksTemplate = jQuery('#books-template').html(); var content = Mustache.render(booksTemplate, library); jQuery('#books-list').html(content); </script> |
Nasz kod JS został nieco zmodyfikowany. Na początku dodaliśmy dwie zmienne: do pierwszej z nich przypisaliśmy podatek VAT, do drugiej aktualny rok. Obiekt biblioteki wzbogacił się o kilka dodatkowych elementów. Przy niektórych pozycjach książkowych pojawiła się prosta lista postaci. Dalej dodaliśmy 3 funkcje:
- arePeople – sprawdza czy dana pozycja posiada jakiekolwiek postacie w tablicy (zwraca wartość logiczną),
- priceWithVat – oblicza i zwraca cenę książki brutto, z dokładnością do dwóch miejsc po przecinku,
- yearAgo – oblicza różnicę pomiędzy rokiem obecnym a rokiem wydania.
Każda z tych funkcji zostanie wywołana na rzecz poszczególnych książek w obiekcie biblioteki, podczas kolejnych podczas kolejnych iteracji w szablonie.
Szablon również uległ małej zmianie. Obok ceny netto wyświetlana jest cena brutto. Poniżej, obok roku wydania, wstawiana jest różnica lat. W dalszej kolejności sprawdzamy czy dana książka zawiera listę postaci i jeżeli tak, lista ta jest wyświetlana. W tym celu użyty został znacznika {{.}}, którego zadaniem jest proste wypisanie wszystkich elementów w tablicy.
W rezultacie otrzymaliśmy prostą listę książek. Wystarczy poprawić wygląd, dodając styl CSS i gotowe.
Podsumowanie
Mustache.js to bardzo przydatne narzędzie, umożliwiające w łatwy sposób oddzielenie naszej aplikacji JavaScript od kodu HTML. Z powodzeniem można go używać zarówno w małych jak i dużych projektach. Polecam i zachęcam do korzystania. Jakby ktoś chciał podzielić się swoimi opiniami lub wrażeniami, to bardzo proszę o komentarz.
Podczas pisania artykułu korzystałem z dokumentacji na stronie: https://github.com/janl/mustache.js