Blog

Ghost CMS. Krótkie wprowadzenie do Handlebars

Krótkie omówienie Handlebars, ponieważ ten język znaczników wykorzystywany jest w przypadku Ghost CMS.

Ghost CMS. Krótkie wprowadzenie do Handlebars

Przystępując do tworzenia własnego motywu dla Ghost należy mieć na uwadze, że do tego celu korzystać będziemy z dobrodziejstwa trzech języków czy tam technologii. Oczywiście dwoma podstawowymi będą HTML i CSS, ponieważ to nieodzowny element procesu projektowania jakiekolwiek strony internetowej. Kolejnym budulcem będzie rzecz jasna JavaScript, choć w tym wypadku można sobie bez trudu wyobrazić sensownie wyglądającą witrynę utworzoną bez nawet grama kodu w tym języku, ale to kiedyś było normą dzisiaj jest raczej wyjątkiem, czyli złożyć należy, że wiedza z tego zakresu okaże się nieodzowna. 

Tym niemniej uspokajam w tym miejscu, że w przypadku naszego projektu korzystać będziemy z gotowego szablonu strony, który następnie będziemy wtłaczać w schemat charakterystyczny dla bohatera tego cyklu wpisów. Dlatego w teorii znajomość wymienionych wyżej języków nie jest wymagana, choć bez przynajmniej podstawowej wiedzy z zakresu HTML obawiam się nie obejdzie. Jednakże nie sposób grzebać przy tematach czy tam motywach dla Ghost bez opanowania podstaw czwartego ze wspomnianych języków, czyli Handlebars (dalej HB). Stąd właśnie pomysł na dzisiejszy wpis, choć zaznaczę od razu, że nie jest to wyczerpujący wpis wszystkich możliwości, jakie ten ostatnie daje. Chodzi o przedstawienie absolutnego minimum, które pozwoli swobodnie poruszać się w strukturze motywów Ghost, choć nie wykluczam, że w miarę potrzeb niniejszy wpis będzie odpowiednio rozbudowywany.

Ghost – jak każdy sensowny CMS – został zaprojektowany z myślą o wyraźnym oddzieleniu prezentacji od treści. W sumie separacja tych dwóch obszarów to zasada projektowania każdej strony internetowej, zasada która nie jest nowa i bazuje na prostej idei, że jeśli treść i prezentacja są rozdzielone na niezależne byty, wówczas możliwa jest zmiana jednego bez równoczesnej konieczności modyfikacji drugiego. Innymi słowy: obecnie wykorzystywany motyw można bez trudu podmienić na inny bez potrzeby modyfikacji samej treści przez użytkownika. Gdy motywy zawierają funkcje, które nie są czysto prezentacyjne, autor nie może już przy nich bezkarnie majstrować nie martwiąc się o ewentualne zmiany po stronie treści, która je wypełnia.

To kluczowe założenie, o którym należy pamiętać podczas tworzenia motywów dla Ghosta, ponieważ platforma ta zachowuje bardzo wyraźne rozgraniczenie pomiędzy prezentacją/wyglądem (za które odpowiadają właśnie tematy) a funkcjonalnościami będącymi domeną platformy Ghost. W przeciwieństwie do pokrewnych rozwiązań pliki szablonów Ghost’s nie obsługują żadnej manipulacji danymi, niestandardowych zapytań do bazy danych ani żadnych innych procesów sterowanych jakoś logiką. W motywie Ghost nie można pisać funkcji, tworzyć zmiennych ani oceniać warunków poza sprawdzaniem, czy jest dostępna jakakolwiek zawartość do wyświetlenia. I w tym momencie na białym koniu wjeżdża HB, którego natura pomaga zapewnić, że motywy Ghost nie wychodzą poza rolę, do której są przeznaczone. czyli kontrolowanie prezentacji bloga.

HANDLEBARS

HB (kierownica roweru) zawdzięcza swoją nazwę charakterystycznemu zastosowaniu podwójnych lub potrójnych nawiasów klamrowych do oznaczania tagów szablonów, czyli na przykład: {{navigation}}. Wszystkie tagi szablonów Handlebars są wstępnie zdefiniowane przez Ghost lub zainstalowane do niego aplikacje dodatkowe, więc gdziekolwiek tag szablonu (taki jak podany przed momentem), zostanie umieszczony w twoim motywie, odpowiednia z góry określona zawartość będzie umieszczona w tej lokalizacji. Sądzę, że jaśniejsze stanie się to w momencie praktycznego wykorzystania tych mechanizmów, natomiast dzisiaj jednak skupimy się nie tyle na sposobie organizowania dostępu do zawartości/treści w Ghost, ale na mechanice stojącej za samym HB.

Tym samym wracając do bohatera dzisiejszego wpisu: HB jest określany jako język szablonów pozbawiony logiki (logicless), ponieważ nie może być używany do pisania funkcji, przechowywania zmiennych ani w inny sposób przygotowywania treści do wyświetlania. Oprócz renderowania treści w miejscu umieszczania tagów, jedynymi jego działaniami mogą być:

  1. sprawdzenie, czy dana treść istnieje, np. logo
  2. iteracja po zestawie danych, jeśli wstrzykiwana treść zawiera wiele rekordów, np. lista najnowszych postów na blogów.

Z tego co udało mi się wyczytać tu i ówdzie HB dziedziczy po silniku Mustache, więc w rezultacie w większej części jest zgodny z jego składnią, jednakże przy tym udostępnia więcej funkcji, tym samym poszerzające możliwości swojego znanego poprzednika. W obu wypadkach znakiem rozpoznawalnym są – wspomniane wcześniej – nawiasy klamrowe występujące parami lub trójkami. Skąd ta różnica się bierze, postaram się wyjaśnić w dalszej części niniejszego wpisu,  na początek jednak chciałbym się przyjrzeć innej kwestii, która przewijała się kilkukrotnie w moich wpisach, jednak teraz jest okazja by omówić ją nieco bardziej szczegółowo.

Widoki, układy oraz bloki, czyli szablony w pełnej krasie

Szablony po stronie serwera umożliwiają renderowanie kodu HTML przed wysłaniem go do klienta. W efekcie nasi użytkownicy nigdy nie zobaczą szablonu po stronie serwera ani obiektów kontekstu wykorzystywanych do generowania ostatecznego kodu HTML. Jest to jedna z zalet takiego rozwiązania, choć nie jedyna, ale tą kwestią zajmować się w tym miejscu nie zamierzam. Wszystkie wymienione w śródtytule elementy reprezentują w gruncie rzeczy to samo, czyli mamy do czynienia z szablonami, choć o różnym zakresie zastosowania. Zwykle mówiąc o widoku ma się na myśli plik (fragment kodu), który reprezentuje pojedynczą stronę witryny.

Jednakże kiedy budujemy witryny internetowe, praktycznie zawsze okazuje się, że pewne elementy powtarzają się na wszystkich stronach. Z rzeczy najbardziej oczywistych wszystkie z nich muszą zawierać elementy <html> i <title> oraz przeważnie wszystkie wczytują te same pliki CSS.  Zamiast powielać te powtarzające się składowe na każdej ze stron, zdecydowanie lepiej przygotować szablon bazowy zawierający te powielające się fragment kodu, by następnie zastosować na nich dziedziczenie po tym ostatnim. Dzięki takiemu podejściu możemy skupić się na unikatowych aspektach poszczególnych stron, a ponadto w przyszłości bardzo to ułatwia zmianę ogólnego widoku i sposobu działania projektu. 

Ten specjalny rodzaj widoki nazywa się układem czasem też matrycą. Można powiedzieć, że jest to szablon wszystkich szablonów. W pewnym sensie to kluczowy element całej architektury w tym sensie, że zazwyczaj od niego zaczyna się budowa całości. Dlatego w tym kontekście mówi się również o szablonie nadrzędnym wobec “zwykłych” szablonów, które dziedziczą po nim i dlatego nazywa się te ostatnie potomnymi. Rzecz jasna w przypadku naprawdę dużych projektów takich matryc może być więcej, ponieważ pewne obszary witryny mogą się różnić od pozostałych.

Podczas tworzenia szablonów zwykle chcesz uniknąć niepotrzebnego powtarzania tego samego kodu w wielu szablonach. Skorzystanie do tego celu z układu nie zawsze jest rozwiązaniem poręcznym albo zwyczajnie się do tego nie nadaje. Jednym z możliwych rozwiązań jest tworzenie wówczas tak zwanych bloków (komponentów). Jest to rodzaj szablonów używanych jako budulec innych widoków. Kolejnym powodem, by korzystać z tego rozwiązania jest podział skomplikowanego szablonu na prostsze elementy przez co łatwiej zarządzać całością. W naszym projekcie będziemy również korzystać z tego rozwiązania.

Zmienne

W celu wyświetlenia wartości w szablonie HB nazwę przywoływanej wartości należy umieścić w podwójnym nawiasie. Jeżeli chcesz wywołać element name jakiegoś kontekstu, powinieneś użyć znacznika {{name}}. Konstrukcja ta odwołuje się do zmiennej, która informuje mechanizm szablonu, że wartość, jaka ma się znaleźć w tym miejscu, powinna zostać uzyskana z danych dostarczonych w momencie renderowania szablonu. Na przykład jeśli obiekt kontekstu jest następujący:

{ name: 'Borciugner' }

a szablon wygląda tak:

<p>Witaj, {{name}}!</p>

wówczas element {{name}} zostanie zastąpiony słowem Borciugner. 

A co jeśli chcemy przekazać do szablonu kod HTML? Gdyby na przykład kontekst wyglądał następująco:

{ name: '<b>Borciugner</b>' }

to po zastosowaniu poprzedniego szablonu uzyskalibyśmy wynik 

<p>Witaj, &lt;b&gt;Borciugner&lt;b&gt;</p>.

Niestety efekt ten odbiega znacznie odbiega od naszych oczekiwań. Aby rozwiązać ten problem, wystarczy użyć trzech nawiasów klamrowych zamiast dwóch: {{{name}}}. Jest 

Komentarze

Komentarze w HB tworzy się następująco: {{! miejsce na komentarz }}. Warto wiedzieć, jak odróżnić komentarze HB od komentarzy HTML. Przeanalizujmy przykład:

{{! Komentarz niewidoczny }}

<!– Komentarz niespecjalnie tajny –>

Zakładając, że jest to szablon wykorzystywany po stronie serwera, pierwszy z komentarzy nie trafi do przeglądarki, natomiast drugi będzie widoczny dla użytkownika, jeśli ten zechce zajrzeć do kodu źródłowego strony. Dlatego jeśli chcemy skomentować jakiś element implementacji i nie chcemy przy tym zdradzać jego szczegółów, powinniśmy używać komentarzy Handlebars.

Struktury sterujące

HB umożliwia kilka mechanizmów pozwalających na sterowanie prezentacją danych. Dwa z nich omówimy pokrótce w niniejszym wpisie, ponieważ w zupełności one wystarczą, by poradzić sobie z większością wyzwań, na jakie możemy i zapewne się natkniemy przy okazji tworzenia własnego motywu. Tym bardziej, że twórcy Ghost udostępniają kilka własnych rozwiązań, ponieważ w przypadku HB istnieje możliwość tworzenia własnych funkcji pomocniczych.

W przypadku wszelakich CMS-ów kluczowym jest możliwość iteracji wartości zwracanych przez listę czy tablicę. Do tego celu można użyć pętli “each”, która ma banalną konstrukcję:

{{#each data}}

<p>{{this}}</p>

{{/each}}

W przypadku tablic słowo this można pominąć i w to miejsce wstawić nazwę klucza dla interesującą nas wartości.

{{#each data}}

<p>{{name}}</p>

{{/each}}

Gdybyśmy jednak chcieli otrzymać parę index-wartość (dla listy) albo klucz-wartość (dla tablic) wówczas należy użyć odpowiednio znaczników @index albo @key.

{{#each array}} {{@index}}: {{this}} {{/each}}

{{#each object}} {{@key}}: {{this}} {{/each}}

Czyli dla przykładu mamy listę CMS-ów const cms = [ 'wordpress', 'drupal', 'ghost'] i chcielibyśmy ją zobaczyć wraz z podanym indeksem. Wówczas wystarczy przygotować następujący kawałek kodu:

<div>

{{#each cms}}

<p>{{@index}}. {{this}}</p>

{{/each}}

</div>

by w efekcie po stronie klienta pojawił się następujący kod:

<div>

<p>0. wordpress</p>

<p>1. drupal</p>

<p>2. ghost</p>

</div>

Oczywiście czasem chcielibyśmy, żeby treść wyświetlała się tylko w przypadku spełnienia pewnych warunków. Do tego służy funkcja pomocnicza “if”. Oczywiście jak zazwyczaj w takich przypadkach w parze z “if” mamy “else”, gdy warunek nie jest spełniony. Konstrukacja jest na tyle prosta, że nie ma sensu poświęcać jej wiele miejsca i dlatego ograniczę się do podania przykładu.

Jeśli dla tablicy widocznej poniżej,

{

  author: false,

  firstName: „Rafał”,

  lastName: „Borawski”,

}

wywołamy następującą instrukcję,

{{#if author}}

<h1>{{firstName}} {{lastName}}</h1>

{{else}}

<h1>Unknown Author</h1>

{{/if}}

to efekcie otrzymamy: <h1>Unknown Author</h1>.

ZAMIAST PODSUMOWANIA

Jak widać idea stojąca za HB jest równie prosta jak jej praktyczna realizacja. Dlatego chociaż zaraz na początku niniejszego wpisu zaznaczyłem, że dzisiejsze wprowadzenie do tego języka ma charakter zajawkowy, a nie wyczerpujący, to wydaje mi się, że wszystko, co może się przydać w kolejnych odsłonach tego cyklu, zostało omówione. O to właśnie głównie chodziło, by przy okazji kolejnych wpisów, omawiając rozwiązania charakterystyczna dla Ghosta, skupić się właśnie na tych ostatnich, a nie dziwacznych klamerkach pojawiających się w kodzie. Tym niemniej gdyby okazało się, że jednak coś pominąłem lub w niedostatecznym stopniu omówiłem, to nie wykluczam, że w przyszłości zmodyfikuję dzisiejszy tekst.