Thymeleaf - szablony, które bez przetworzenia zrozumie twoja przeglądarka
1. Thymeleaf - szablony, które bez przetworzenia zrozumie twoja przeglądarka
Maciej Ziarko
14.10.2014
2. Dowiecie się, czym jest i co potrafi Thymeleaf
Wykonacie kilka ćwiczeń w webowym tutorialu
Będziecie mogli jutro z marszu wykorzystać Thymeleaf we własnym projekcie
Dowiecie się, gdzie poszerzyć swoją wiedzę, gdy zajdzie taka potrzeba
Cel dzisiejszego spotkania?
3. Potężny silnik szablonów, potrafiący generować dokumenty XML/XHTML/HTML5
Szablony są poprawnymi dokumentami XML/XHTML/HTML5
Wiele dialektów (możliwość tworzenia własnych)
Twórcy biblioteki zapewniają integrację ze Spring MVC i Spring Security
Wysoka wydajność dzięki mechanizmowy pamięci podręcznej
Czym jest Thymeleaf?
5. Jak to wygląda w Spring MVC?
@RequestMapping("/somePage") String showSomePage(Model model) { model.addAttribute("key1", new Value1()) model.addAttribute("key2", new Value2()) return "templateName" }
Dzięki integracji ze Spring MVC i auto-konfiguracji w Spring Boot proces z poprzedniego slajdu może być zupełnie transparentny dla programisty
6. Przykład prostego szablonu
<!DOCTYPE html> <html xmlns="http://www.w3.org/1999/xhtml" xmlns:th="http://www.thymeleaf.org"> <body> <p th:text="#{wjug.welcome}"> Welcome to WJUG! </p> </body> </html>
7. Informacje potrzebne bibliotece przekazywane są przy pomocy atrybutów, a nie tagów
Czołowe przeglądarki ignorują nieznane przez siebie atrybuty
Natural Templating – szablony, które mogą być jednocześnie prototypem, który można uruchomić w przeglądarce bez przetwarzania
Zupełnie inaczej jest w przypadku większości popularnych dawniej technologii szablonowych: JSP, Freemarker, Velocity itd.
Analiza prostego szablonu
8. Thymeleaf jest bardzo elastyczny – rozszerzalny przy pomocy nowych dialektów
Silnik szablonów może korzystać jednocześnie z wielu dialektów
Głównym składnikiem dialektu są procesory, które wiedzą, jak interpretować poszczególne atrybuty
Można tworzyć nowe dialekty w oparciu o istniejące
Najpopularniejsze dialekty: Standard, Spring Standard, Spring Security, Layout
Dialekty
9. th:text
<p th:text="#{wjug.welcome}">
Welcome to WJUG!
</p>
Wylicza wartość wyrażenia i zastępuje nią wnętrze tagu
Wyliczona wartość może być bezpiecznie umieszczona w dokumencie HTML (escaping)
wjug.welcome=Witamy na WJUG!
W pliku messages_pl.properties:
10. th:utext
<p th:utext="#{wjug.welcome}"> Welcome to <strong>WJUG</strong>! </p>
Czasem może zdarzyć się, że z jakiegoś powodu escaping nie jest pożądany
wjug.welcome=Witamy na <strong>WJUG</strong>!
W pliku messages_pl.properties:
11. Składnia wyrażeń - ${…}
<span th:text="${user.name}"> j_kowalski </span>
${…} oblicza wartość na podstawie zmiennych znajdujących się w kontekście
12. Dostęp do elementów mapy, pól obiektów…
map['key'] map.key
Wyciąganie elementów mapy:
Wyciąganie pól z obiektów:
object['field'] object.field
Wołanie metod:
person.countDebt()
Elementy listy/tablicy:
people[2]
13. Składnia wyrażeń - #{…}
<p th:text="#{wjug.welcome(${user.name})}">
Welcome to WJUG, Johnny Kowalski!
</p>
#{…} jest wykorzystywane do internacjonalizacji – wartość jest ustalana na postawie Locale oraz plików properties
wjug.welcome=Witamy na WJUG, {0}!
W pliku messages_pl.properties:
14. Składnia wyrażeń - @{…}
@{…} jest wykorzystywane do tworzenia odnośników
Szczególnie przydatne dla th:href
W przypadku web aplikacji dodaje do odnośnika ścieżkę kontekstową
Umożliwia ustawianie parametrów
15. Dla aplikacji ze ścieżką kontekstową /app zostanie zmienione na:
@{…} – ścieżka kontekstowa
<a href="profile.html" th:href="@{/profile}"> profile </a>
<a href="/app/profile"> profile </a>
16. @{…} - parametry
<a href="details.html" th:href="@{/order/details(orderId=${o.id})}">
view
</a>
Dla o.id równego 100 zostanie zamienione na:
<a href="/app/order/details?orderId=100">
view
</a>
17. @{…} - parametry
@{/order/process(execId=${execId},execType='FAST')}
Można ustawić kilka parametrów na raz:
A także parametryzować zmienne wewnątrz ścieżki:
@{/order/{orderId}/details(orderId=${o.id})}
18. Konkatenacja łańcuchów znaków
th:text="'The name of the user is ' + ${user.name}"
Łańcuchy znaków można konkatenować korzystając z operatora +:
Można też skorzystać ze specjalnej składni literal substitutions:
th:text="|Welcome to our application, ${user.name}!|"
19. Czas na pierwsze ćwiczenie
http://itutorial.thymeleaf.org/exercise/1
Ćwiczenie znajduje się pod adresem:
20. th:object i *{…}
th:text="${product.description}"
th:text="${product.price}"
Atrybut th:object oraz *{…} pozwalają ulepszyć nasz kod:
Jako dobrzy inżynierowie nie lubimy duplikacji:
<dl th:object="${product}"> <dt>Product name</dt> <dd th:text="*{description}">...</dd> </dl>
21. Rozwiążmy lepiej ćwiczenie pierwsze
http://itutorial.thymeleaf.org/exercise/1
Ćwiczenie znajduje się pod adresem:
22. Utility Objects
#dates
#calendars
#numbers
Obiekty, posiadające pomocnicze metody:
Głównie przydają się do różnego rodzaju formatowania:
${#numbers.formatInteger(num,3)} ${#numbers.formatDecimal(num,3,2)} ${#dates.format(date, 'dd/MMM/yyyy HH:mm')}
23. Czas na drugie ćwiczenie
http://itutorial.thymeleaf.org/exercise/2
Ćwiczenie znajduje się pod adresem:
24. Ustawianie wartości atrybutów
Thymeleaf umożliwia wstawianie wartości dla wszystkich standardowych atrybutów:
th:abbr
th:accept
th:accept-charset
th:accesskey
th:action
th:align
th:alt
itd.
25. Ustawianie wartości dowolnych atrybutów
Thymeleaf umożliwia wstawianie wartości dowolnych np. customowych atrybutów:
th:attr="attr-name=${object.property}"
th:attr="src=@{/images/gtvglogo.png},
title=#{logo},alt=#{logo}"
th:attr="data-some-attr=${object.property}"
26. Dodatkowe wsparcie dla niektórych atrybutów
Ustawienie kilku atrybutów jednocześnie:
th:alt-title ustawia alt i title
Wsparcie dla atrybutów o stałej wartości:
th:checked
th:hidden
th:selected
th:disabled
...
th:checked="${user.active}"
27. Dodatkowe wsparcie dla niektórych atrybutów
Wsparcie dla atrybutu class:
class="row"
th:classappend="${prodStat.odd} ? 'odd'"
class="row odd"
28. Iteracja
Każda technologia szablonów musi posiadać wsparcie dla wyświetlania kolekcji, map i tablic:
<tr th:each="prod : ${prods}">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
</tr>
29. Iteracja – status
Możemy zadeklarować zmienną, która będzie przechowywała status iteracji:
<tr th:each="prod, stat : ${prods}">
<td th:text="${stat.count}">1</td>
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
</tr>
Dostępne dane:
index, count, size, odd, even, last, first
30. Wsparcie dla prototypowania
<tr th:each="prod : ${prods}">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
</tr>
Jeden wiersz mało atrakcyjny z punktu widzenia prototypu:
31. Wsparcie dla prototypowania
<tr th:each="prod : ${prods}">
<td th:text="${prod.name}">Onions</td>
<td th:text="${prod.price}">2.41</td>
</tr>
<tr>
<td>Apples</td>
<td>3.98</td>
</tr>
<tr>
<td>Oranges</td>
<td>4.52</td>
</tr>
Chcemy, żeby kolejne wiersze były widoczne tylko w prototypie:
33. Wsparcie dla prototypowania
all - usuwa tag i wszystkie dzieci
body - zostawia tag, ale usuwa dzieci
tag - usuwa tag, ale zostawia dzieci
all-but-first - usuwa wszystkie dzieci poza pierwszym
none - przydatne gdy dynamicznie decydujemy o usunięciu elementów i pod pewnym warunkiem chcemy zaniechać usuwania
Możliwe wartości th:remove:
34. Czas na kolejne ćwiczenia
http://itutorial.thymeleaf.org/exercise/6
http://itutorial.thymeleaf.org/exercise/7
Ćwiczenia znajdują się pod adresami:
36. th:switch oraz th:case
<div th:switch="${user.role.name()}">
<p th:case="'ADMIN'">User is an administrator</p>
<p th:case="#{roles.manager}">User is a manager</p>
<p th:case="*">User is some other thing</p>
</div>
Znana z większości języków instrukcja switch:
37. Czas na kolejne ćwiczenie
http://itutorial.thymeleaf.org/exercise/8
Ćwiczenie znajduje się pod adresem:
38. Zmienne lokalne
<div th:with="company=${user.company + ' Co.'}>
...
</div>
Czasem może zdarzyć się, że pewne wyrażenie wykorzystujemy wielokrotnie wewnątrz bloku:
Zalety:
jednokrotna ewaluacja (być może skomplikowanego) wyrażenia
nadanie nazwy
mniej kodu
40. Fragmenty - wykorzystanie
<div th:include="layout :: copy"></div>
lub
<div th:replace="layout :: copy"></div>
Zdefiniowany w jednym pliku fragment można wykorzystywać w innych plikach:
43. Wsparcie dla formularzy
<form action="#" th:action="@{/seedstartermng}" th:object="${seedStarter}" method="post">
…
<input type="text" th:field="*{datePlanted}"/>
…
</form>
Atrybut th:object służy do precyzowania form-backing beana:
th:field zachowuje się odpowiednio w zależności od inputu (inaczej dla text, jeszcze inaczej dla textarea itd.)
45. Inlining
<body th:inline="text">
...
<p>Hello, [[${session.user.name}]]!</p>
...
</body>
Mimo, iż możemy osiągnąć wszystko przy pomocy atrybutów, czasem może zdarzyć się, że chcemy wstawić wartość bezpośrednio wewnątrz tagu:
46. Inlining - JavaScript
<script th:inline="javascript">
var username = /*[[${session.user.name}]]*/ 'Sebastian';
</script>
Thymeleaf posiada wsparcie dla JavaScriptu:
Thymeleaf jest inteligentny:
var user = /*[[${session.user}]]*/ null;
var user = {'firstName':'John', 'lastName':'Apricot', 'name':'John Apricot', 'nationality':'Antarctica'};
Wygenerowano literał odpowiedniego typu:
47. Kolejność przetwarzania atrybutów
<li th:if="item.status == 'APPROVED'"
th:each="item : ${items}">
</li>
Atrybuty w dokumentach XML i HTML nie mają określonej kolejności.
Czasem może zdarzyć się, że kolejność jest dla nas istotna:
By sobie z tym poradzić, Thymeleaf definiuje kolejność, w jakiej przetwarza atrybuty.
49. Dostęp do specjalnych obiektów
#locale
#vars
param
session
application
#httpServletRequest
#httpSession
Czasem może zdarzyć się, że potrzebny jest nam bezpośredni dostęp do pewnych specjalnych obiektów.
Najważniejsze z obiektów dostępnych z poziomu szablonu:
50. Dostęp do Springowych beanów
Kiedy korzystamy z dialektu Spring Standard, dostajemy pełne możliwości Spring EL.
Na przykład możemy dostawać się do beanów:
<div th:text="${@authService.getUserName()}">...</div>
51. Dialekt Spring Security
<div sec:authorize="hasRole('ROLE_ADMIN')">
This will only be displayed if authenticated
user has role ROLE_ADMIN.
</div>
<a href="#" th:href="@{/admin}" sec:authorize-url="/admin">
This will only be displayed if authenticated
user can call the "/admin" URL.
</a>
<div sec:authentication="name">
The value of the "name" property of
the authentication object should appear here.
</div>
Możliwości: