45
A modern Java templating language Thymeleaf Andre Azzolini Broadleaf Commerce FW JUG - July 2nd, 2014

Broadleaf Presents Thymeleaf

Embed Size (px)

DESCRIPTION

Broadleaf's Andre Azzolini presents Thymeleaf at July 2014 Fort Worth Java Users Group (FW JUG) meeting.

Citation preview

Page 1: Broadleaf Presents Thymeleaf

A modern Java templating language

Thymeleaf Andre Azzolini!Broadleaf Commerce!FW JUG - July 2nd, 2014

Page 2: Broadleaf Presents Thymeleaf

IntroductionWho am I?!

‣ Graduated from The University of Texas at Austin (Computer Science)!‣ Consultant at Credera!‣ Senior Engineer at Broadleaf Commerce!

!What is Broadleaf Commerce?!

‣ Open source Java enterprise eCommerce framework!‣ Focus on extensibility and scalability!‣ Based on Spring, Hibernate, Thymeleaf

Page 3: Broadleaf Presents Thymeleaf

‣ Why use Thymeleaf?!

‣ Thymeleaf Basics!

‣ Intermediate Thymeleaf!

‣ Existing Ecosystem!

‣ Broadleaf Use Cases

Agenda

Page 4: Broadleaf Presents Thymeleaf

‣ Not compiled —> short feedback loop!

‣ Natural templating —> closer to designers’ HTML!

‣ Modular architecture —> hooks for customization

Why use Thymeleaf?

Page 5: Broadleaf Presents Thymeleaf

Thymeleaf Basics

Page 6: Broadleaf Presents Thymeleaf

Installation in a Spring application<bean id="templateResolver" class="org.thymeleaf...ServletContextTemplateResolver"> <property name="prefix" value="/WEB-INF/templates/" /> <property name="suffix" value=".html" /> <property name="templateMode" value="HTML5" /> </bean> <bean id="templateEngine" class="org.thymeleaf.spring3.SpringTemplateEngine"> <property name="templateResolver" ref="templateResolver" /> </bean> !<bean class="org.thymeleaf.spring3.view.ThymeleafViewResolver"> <property name="templateEngine" ref="templateEngine" /> <property name="order" value="1" /> <property name="viewNames" value="*.html" /> </bean>

Page 7: Broadleaf Presents Thymeleaf

Outputting Values———— HomeController.java ———— !@RequestMapping("/") public String viewHomepage(Model model) { model.addAttribute("header", "My Header Message"); return "index"; } !———— /WEB-INF/templates/index.html ———— !<span th:text="${header}">A Default Header</span> !———— Browser rendering ———— !<span>A Default Header</span> !———— Thymeleaf rendering ———— !<span>My Header Message</span>

Page 8: Broadleaf Presents Thymeleaf

Outputting Values (i18n)———— /WEB-INF/templates/index.html ———— !<span th:text="#{homepage.header}">A Default Header</span> !———— messages_en.properties ———— !homepage.header=My Header Message !———— messages_es.properties ———— !homepage.header=Mi Mensaje de Cabecera !———— Thymeleaf rendering (en) ———— !<span>My Header Message</span> !———— Thymeleaf rendering (es) ———— !<span>Mi Mensaje de Cabecera</span>

Page 9: Broadleaf Presents Thymeleaf

Outputting Values (i18n cont.)———— HomeController.java ———— !@RequestMapping("/") public String viewHomepage(Model model) { model.addAttribute("header", "homepage.header"); return "index"; } !———— /WEB-INF/templates/index.html ———— !<span th:text="#{${header}}" />

Page 10: Broadleaf Presents Thymeleaf

Outputting Raw Values———— HomeController.java ———— !@RequestMapping("/") public String viewHomepage(Model model) { model.addAttribute("header", "<b>My Bolded Header</b>"); return "index"; } !———— Rendered with th:text ———— !<span>&lt;b&gt;My Bolded Header&lt;/b&gt;</span> !———— Rendered with th:utext ———— !<span><b>My Bolded Header</b></span>

Page 11: Broadleaf Presents Thymeleaf

Outputting Values Inline———— index.html ———— !<span th:inline="text"> [[${address.user?.firstName}]] [[${address.user?.lastName}]]<br /> [[${address.line1}]] <br /> [[${address.line2}]] <br /> [[${address.city}]], [[${address.state}]] [[${address.zipcode}]] </span>

Page 12: Broadleaf Presents Thymeleaf

Scoping available variables———— index.html ———— !<span th:inline="text" th:object="${address}"> [[*{user?.firstName}]] [[${user?.lastName}]]<br /> [[*{line1}]] <br /> [[*{line2}]] <br /> [[*{city}]], [[*{state}]] [[*{zipcode}]] </span>

Page 13: Broadleaf Presents Thymeleaf

Attribute Manipulation———— index.html ———— !<span th:class="${isEven ? 'even' : 'odd'}" th:classappend="${isError ? 'error'}" th:attr="data-row-id=${row.id}" th:text="${row.description}" /> ———— Rendered index.html ———— !<span class="odd error" data-row-id="5">Row 5 Description</span>

Page 14: Broadleaf Presents Thymeleaf

Conditionals———— index.html ———— !<span th:if="${user.loggedIn}" th:text="${'Hi ' + user.name}" /> <span th:unless="${user.loggedIn}">Welcome guest</span> !———— messages_en.properties ———— !header.greeting.user=Hi {0} header.greeting.anonymous=Welcome guest !———— index.html (i18n) ———— !<span th:if="…" th:text="#{header.greeting.user(${user.name})}" /> <span th:unless="…" th:text="#{header.greeting.anonymous}" />

Page 15: Broadleaf Presents Thymeleaf

Loops and Links———— HomeController.java ———— !@RequestMapping("/products") public String viewProductListing(Model model) { List<Product> products = productService.findAll(); model.addAttribute("products", products); return "productListing"; } !———— productListing.html ———— !<ul> <li th:each="product : ${products}" th:object="${product}"> <img th:src="@{*{mainImage}}" /> <br /> <a th:href="@{*{link}}" th:text="*{name}" /> </li> </ul>

Page 16: Broadleaf Presents Thymeleaf

Includes———— productListing.html ———— !<ul> <li th:each="product : ${products}" th:object="${product}" th:include="components/productBlock" /> </ul> !———— components/productBlock.html ————!<img th:src="@{*{mainImage}}" /> <br /> <a th:href="@{*{link}}" th:text="*{name}" />

Page 17: Broadleaf Presents Thymeleaf

Variable Expressions———— HomeController.java ———— !@RequestMapping("/products") public String viewProductListing(Model model) { List<Product> products = productService.findAll(); model.addAttribute("products", products); return "productListing"; } !———— productListing.html ———— !<span th:text="${'Products Found: ' + #lists.size(products)}" />

Page 18: Broadleaf Presents Thymeleaf

‣ format(date, 'dd/MMM/yyyy HH:mm')!

‣ day(date), month(date), dayOfWeek(date), etc!

‣ create(year, month, day)!

‣ createNow(), createToday()

#dates Variable Expression

Page 19: Broadleaf Presents Thymeleaf

‣ formatInteger(num, 3, 'POINT')!

‣ sequence(from, to)!

‣ sequence(from, to, step)

#numbers Variable Expression

Page 20: Broadleaf Presents Thymeleaf

‣ isEmpty!

‣ contains!

‣ indexOf, substring, replace!

‣ prepend, append!

‣ toLowerCase, toUpperCase, capitalize, capitalizeWords!

‣ escapeXml, unescapeJava

#strings Variable Expression

Page 21: Broadleaf Presents Thymeleaf

‣ isEmpty, size!

‣ contains, containsAll!

‣ sort

#arrays, #lists, #sets, #maps Variable Expressions

Page 22: Broadleaf Presents Thymeleaf

#aggregates Variable Expression———— Order.java ———— !public class Order { protected List<OrderLine> lines; } !class OrderLine { protected BigDecimal price; protected int qty; } !———— order.html ———— !<span th:text="${#aggregates.sum(order.lines.{price * qty})}" />

Page 23: Broadleaf Presents Thymeleaf

Intermediate Thymeleaf

Page 24: Broadleaf Presents Thymeleaf

Custom Variable Expressions———— SystemPropertyVariableExpression.java ———— !@Resource protected SystemPropertyService service; !public String getName() { return "sp"; } !public boolean getAsBoolean(String prop) { return service.resolveAsBoolean(prop); } !———— index.html ———— !<span th:if="${#sp.getAsBoolean('pagination.enabled')}" th:include="components/paginator" />

Page 25: Broadleaf Presents Thymeleaf

Spring Bean Direct Access———— MyService.java ———— !@Service public class MyService { ! public boolean isPalindrome(String str) { return str.equals(StringUtils.reverse(str)); } !} !———— index.html ———— !<span th:if="${@myService.isPalindrome('tacocat')}" />

Page 26: Broadleaf Presents Thymeleaf

Custom Processors———— PriceProcessor.java ———— !public class PriceProcessor extends AbstractTextChildModifierAttrProcessor { public PriceTextDisplayProcessor() { super("price"); } ! protected String getText(Arguments arguments, Element element, String attributeName) { Expression expression = ... Object result = expression.execute(...); if (result instanceof Money) { return ((Money) result).getFormattedValue(); } else { return result.toString(); } } }

Page 27: Broadleaf Presents Thymeleaf

Custom Processors (cont.)———— MyCustomDialect.java ———— public class MyCustomDialect extends AbstractDialect { private Set<IProcessor> processors = new HashSet<IProcessor>(); @Override public String getPrefix() { return "mcd"; } } !———— applicationContext.xml ———— !<bean id="myCustomDialect" class="com.myco.MyCustomDialect"> <property name="processors"> <set> <bean id="priceProcessor" class="com.myco.PriceProcessor"> </set> </property> </bean>

Page 28: Broadleaf Presents Thymeleaf

Custom Processors (cont.)———— applicationContext.xml (cont.) ———— !<bean id="templateEngine" class="..."> <property name="additionalDialects"> <set> <bean class="com.myco.MyCustomDialect"/> </set> </property> </bean> !———— components/productBlock.html ————!<img th:src="@{*{mainImage}}" /> <br /> <a th:href="@{*{link}}" th:text="*{name}" /> <span mcd:price="*{price}" />

Page 29: Broadleaf Presents Thymeleaf

Custom Processors (cont.)public class FormProcessor extends AbstractElementProcessor { ! protected ProcessorResult processElement(Arguments a, Element el) { String method = el.getAttributeValue("method"); if (!"GET".equals(method)) { String csrfToken = protectionService.getCsrfToken(); Element csrfNode = new Element("input"); csrfNode.setAttribute("type", "hidden"); csrfNode.setAttribute("name", "csrf-token"); csrfNode.setAttribute("value", csrfToken); el.addChild(csrfNode); } ! Element newForm = el.cloneElementNode(...); el.getParent().insertAfter(el, newForm); el.getParent().removeChild(el); return ProcessorResult.OK; } }

Page 30: Broadleaf Presents Thymeleaf

Custom Processors (cont.)———— login.html ———— !<mcd:form> <input type="text" name="username" /> <input type="password" name="pass" /> </mcd:form> !———— Rendered login page ———— !<form> <input type="text" name="username" /> <input type="password" name="pass" /> <input type="hidden" name="csrf-token" value="L9ThxnotKPzthJ" /> </form>

Page 31: Broadleaf Presents Thymeleaf

Spring Form Binding———— UserRegistrationForm.java ———— !public class UserRegistrationForm { ! protected String username; protected String password; protected String confirmPassword; protected String email; ! ... getters / setters ... !}

Page 32: Broadleaf Presents Thymeleaf

Spring Form Binding (cont.)———— UserRegistrationController.java ———— !public class UserRegistrationController { ! @RequsetMapping("/register", method = RequestMethod.GET) public String showRegisterForm(Model model, @ModelAttribute UserRegistrationForm registerForm) { return "components/userRegistrationForm"; } ! @RequsetMapping("/register", method = RequestMethod.POST) public String showRegisterForm(Model model, @ModelAttribute UserRegistrationForm registerForm) { // register the user return "redirect:/"; } !}

Page 33: Broadleaf Presents Thymeleaf

Spring Form Binding (cont.)———— components/userRegistrationForm.html ———— !<form th:action="@{/register}" th:object="${registerForm}" method="POST"> <input type="text" th:field="*{username}" /> <input type="password" th:field="*{password}" /> <input type="password" th:field="*{confirmPassword}" /> <input type="text" th:field="*{email}" /> </form>

Page 34: Broadleaf Presents Thymeleaf

Existing Ecosystem

Page 35: Broadleaf Presents Thymeleaf

‣ Code completion for out of box processors!

‣ Content assist inside expressions!

‣ Ability to provide completion for custom processors

Eclipse IDE Plugin

Page 36: Broadleaf Presents Thymeleaf

‣ Use Thymeleaf templates in Tiles definitions!

‣ Mix JSP and Thymeleaf templates!

‣ Optional Spring MVC 3 and Spring Web Flow 2.3 integrations

Thymeleaf + Apache Tiles 2

Page 37: Broadleaf Presents Thymeleaf

‣ Lightweight dialect approach instead of Tiles!

‣ Uses decorators and fragments within the templates, so there is no need for a Tiles definition file!

‣ Created by a core Thymeleaf contributor!

‣ I think it's more intuitive than the Tiles plugin

Layout Dialect (unofficial)

Page 38: Broadleaf Presents Thymeleaf

‣ sec:authorize processor (accepts normal Spring Security expressions like hasRole('ROLE_ADMIN'))!

‣ Grab the current authentication object in expressions: ${#authentication.name}!

Spring Security 3

Page 39: Broadleaf Presents Thymeleaf

‣ Allows you to specify a cache attribute on a DOM element!

‣ Caches the resulting HTML with the provided name!

‣ Doesn't require Thymeleaf to process the DOM for that node when rendering

Cache Dialect (unofficial)

<ul cache:name="productsList" cache:ttl="60"> <li th:each="product : ${products}" th:include="components/productBlock" /> </ul>

Page 40: Broadleaf Presents Thymeleaf

‣ JavaScript library for natural templating!

‣ Can process th:include without executing in an application!

‣ Provides features for evaluating conditionals while statically prototyping

Thymol (unofficial)

Page 41: Broadleaf Presents Thymeleaf

Broadleaf Use Cases

Page 42: Broadleaf Presents Thymeleaf

‣ Serve individual files in development, but a bundle in production!

‣ Handle expiration of bundles!

‣ Dynamically modify contents of certain resources

JS / CSS Bundling

<blc:bundle name="admin.js" files="BLC.js, BLC-system-property.js, blc-dates.js" />

Page 43: Broadleaf Presents Thymeleaf

‣ Intelligent auto-generation of cache keys!

‣ Provide ability to alter a portion of the cached, generated HTML!

‣ Invalidate cache elements when things change in the admin

Advanced Caching Strategy

Page 44: Broadleaf Presents Thymeleaf

‣ Allow users to modify templates through the admin tool!

‣ Serve templates directly from the database!

‣ No need for compiling the templates like we would have to with JSP

Database Template Resolution

Page 45: Broadleaf Presents Thymeleaf

Thanks!