thymeleaf - szablony, które bez przetworzenia zrozumie twoja przeglądarka
DESCRIPTION
WJUG presentation about Thymeleaf. Language: polishTRANSCRIPT
Thymeleaf - szablony, które bez przetworzenia zrozumie twoja
przeglądarka
Maciej Ziarko
14.10.2014
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?
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?
Jak działa Thymeleaf?
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
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>
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
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
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:
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:
Składnia wyrażeń - ${…}
<span th:text="${user.name}">
j_kowalski
</span>
${…} oblicza wartość na podstawie zmiennych znajdujących się w kontekście
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]
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:
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
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>
@{…} - 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>
@{…} - 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})}
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}!|"
Czas na pierwsze ćwiczenie
http://itutorial.thymeleaf.org/exercise/1
Ćwiczenie znajduje się pod adresem:
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>
Rozwiążmy lepiej ćwiczenie pierwsze
http://itutorial.thymeleaf.org/exercise/1
Ćwiczenie znajduje się pod adresem:
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')}
Czas na drugie ćwiczenie
http://itutorial.thymeleaf.org/exercise/2
Ćwiczenie znajduje się pod adresem:
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.
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}"
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}"
Dodatkowe wsparcie dla niektórych atrybutów
Wsparcie dla atrybutu class:
class="row"
th:classappend="${prodStat.odd} ? 'odd'"
class="row odd"
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>
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
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:
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:
Wsparcie dla prototypowania
<tbody th:remove="all-but-first">
<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>
...
</tbody>
Solucja to wykorzystanie th:remove:
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:
Czas na kolejne ćwiczenia
http://itutorial.thymeleaf.org/exercise/6
http://itutorial.thymeleaf.org/exercise/7
Ćwiczenia znajdują się pod adresami:
th:if oraz th:unless
<a href="comments.html" th:href="@{/product/comments(prodId=${prod.id})}"
th:if="${not #lists.isEmpty(prod.comments)}">
view </a>
Warunkowe wyświetlanie tagów:
<a href="comments.html" th:href="@{/product/comments(prodId=${prod.id})}"
th:unless="${#lists.isEmpty(prod.comments)}">
view </a>
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:
Czas na kolejne ćwiczenie
http://itutorial.thymeleaf.org/exercise/8
Ćwiczenie znajduje się pod adresem:
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
Fragmenty - definiowanie
<body> … <div th:fragment="copy"> © 2011 The Good Thymes Virtual Grocery </div>
… </body>
Thymeleaf posiada wsparcie dla wielokrotnego wykorzystania pewnych poddrzew (DRY):
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:
Fragmenty - czym różnią się th:include i th:replace?
<footer th:fragment="copy"> © 2011 The Good Thymes Virtual Grocery </footer>
<div th:include="layout :: copy"></div> <div th:replace="layout :: copy"></div>
<div>
© 2011 The Good Thymes Virtual Grocery
</div> <footer>
© 2011 The Good Thymes Virtual Grocery
</footer>
Fragmenty - parametryzowanie
<div th:fragment="frag (onevar,twovar)"> <p th:text="${onevar} + ' - ' + ${twovar}">...</p> </div>
<div th:include="layout::frag(${value1},${value2})">
...
</div>
Definicja:
Wykorzystanie:
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.)
Wsparcie dla formularzy
<li th:each="feat : ${allFeatures}"> <input type="checkbox" th:field="*{features}"
th:value="${feat}"/> <label th:for="${#ids.prev('features')}"
th:text="#{${'feature.' + feat}}">Heating</label> </li>
Checkbox:
Dropdown:
<select th:field="*{type}"> <option th:each="type : ${allTypes}" th:value="${type}"
th:text="#{${'type.' + type}}">Wireframe</option> </select>
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:
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:
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.
Kolejność przetwarzania atrybutów
th:include, th:replace
th:each
th:if, th:unless, th:switch, th:case
th:object, th:with
th:attr, th:attrprepend, th:attrappend
th:value, th:href, th:src itd.
th:text, th:utext
th:fragment
th:remove
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:
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>
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:
http://www.thymeleaf.org/documentation.html
http://www.thymeleaf.org/doc/springmail.html
https://github.com/thymeleaf/thymeleaf-testing
https://github.com/spring-projects/spring-boot/tree/master/spring-boot-samples/spring-boot-sample-web-ui
Gdzie dalej?
Dziękuję za uwagę!