diff --git a/docs/src/main/asciidoc/mailer.adoc b/docs/src/main/asciidoc/mailer.adoc index d744ca942d689d..10a134dbd1fc0a 100644 --- a/docs/src/main/asciidoc/mailer.adoc +++ b/docs/src/main/asciidoc/mailer.adoc @@ -230,6 +230,39 @@ When you want to reference your attachment, for instance in the `src` attribute, It's also possible to inject a mail template, where the message body is created automatically using link:qute[Qute templates]. +[source, java] +---- +@Path("") +public class MailingResource { + + @CheckedTemplate + class Templates { + public static native MailTemplateInstance hello(String name); <1> + } + + @GET + @Path("/mail") + public CompletionStage send() { + // the template looks like: Hello {name}! <2> + return Templates.hello("John") + .to("to@acme.org") <3> + .subject("Hello from Qute template") + .send() <4> + .subscribeAsCompletionStage() + .thenApply(x -> Response.accepted().build()); + } +} +---- +<1> By convention, the enclosing class name and method names are used to locate the template. In this particular case, +we will use the `MailingResource/hello.html` and `MailingResource/hello.txt` templates to create the message body. +<2> Set the data used in the template. +<3> Create a mail template instance and set the recipient. +<4> `MailTemplate.send()` triggers the rendering and, once finished, sends the e-mail via a `Mailer` instance. + +TIP: Injected mail templates are validated during build. If there is no matching template in `src/main/resources/templates` the build fails. + +You can also do this without type-safe templates: + [source, java] ---- @Inject diff --git a/docs/src/main/asciidoc/qute.adoc b/docs/src/main/asciidoc/qute.adoc index fa4fb810ffb34a..40bc4b3979108a 100644 --- a/docs/src/main/asciidoc/qute.adoc +++ b/docs/src/main/asciidoc/qute.adoc @@ -81,12 +81,115 @@ $ curl -w "\n" http://localhost:8080/hello?name=Martin Hello Martin! ---- -== Parameter Declarations and Template Extension Methods +== Type-safe templates + +There's an alternate way to declare your templates in your Java code, which relies on the following convention: + +- Organise your template files in the `/src/main/resources/templates` directory, by grouping them into one directory per resource class. So, if + your `ItemResource` class references two templates `hello` and `goodbye`, place them at `/src/main/resources/templates/ItemResource/hello.txt` + and `/src/main/resources/templates/ItemResource/goodbye.txt`. Grouping templates per resource class makes it easier to navigate to them. +- In each of your resource class, declare a `@CheckedTemplate static class Template {}` class within your resource class. +- Declare one `public static native TemplateInstance method();` per template file for your resource. +- Use those static methods to build your template instances. + +Here's the previous example, rewritten using this style: + +We'll start with a very simple template: + +.HelloResource/hello.txt +[source] +---- +Hello {name}! <1> +---- +<1> `{name}` is a value expression that is evaluated when the template is rendered. + +Now let's declare and use those templates in the resource class. + +.HelloResource.java +[source,java] +---- +package org.acme.quarkus.sample; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; + +import io.quarkus.qute.TemplateInstance; +import io.quarkus.qute.api.CheckedTemplate; + +@Path("hello") +public class HelloResource { + + @CheckedTemplate + class Templates { + public static native TemplateInstance hello(); <1> + } + + @GET + @Produces(MediaType.TEXT_PLAIN) + public TemplateInstance get(@QueryParam("name") String name) { + return Templates.hello().data("name", name); <2> <3> + } +} +---- +<1> This declares a template with path `templates/HelloResource/hello.txt`. +<2> `Templates.hello()` returns a new template instance that can be customized before the actual rendering is triggered. In this case, we put the name value under the key `name`. The data map is accessible during rendering. +<3> Note that we don't trigger the rendering - this is done automatically by a special `ContainerResponseFilter` implementation. + +NOTE: Once you have declared a `@CheckedTemplate` class, we will check that all its methods point to existing templates, so if you try to use a template +from your Java code and you forgot to add it, we will let you know at build time :) + +Keep in mind this style of declaration allows you to reference templates declared in other resources too: + +.HelloResource.java +[source,java] +---- +package org.acme.quarkus.sample; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.QueryParam; + +import io.quarkus.qute.TemplateInstance; + +@Path("goodbye") +public class GoodbyeResource { + + @GET + @Produces(MediaType.TEXT_PLAIN) + public TemplateInstance get(@QueryParam("name") String name) { + return HelloResource.Templates.hello().data("name", name); + } +} +---- + +=== Toplevel type-safe templates + +Naturally, if you want to declare templates at the toplevel, directly in `/src/main/resources/templates/hello.txt`, for example, +you can declare them in a toplevel (non-nested) `Templates` class: + +.HelloResource.java +[source,java] +---- +package org.acme.quarkus.sample; + +import io.quarkus.qute.TemplateInstance; +import io.quarkus.qute.Template; +import io.quarkus.qute.api.CheckedTemplate; + +@CheckedTemplate +public class Templates { + public static native TemplateInstance hello(); <1> +} +---- +<1> This declares a template with path `templates/hello.txt`. + + +== Template Parameter Declarations -Qute has many useful features. -In this example, we'll demonstrate two of them. If you declare a *parameter declaration* in a template then Qute attempts to validate all expressions that reference this parameter and if an incorrect expression is found the build fails. -*Template extension methods* are used to extend the set of accessible properties of data objects. Let's suppose we have a simple class like this: @@ -99,9 +202,69 @@ public class Item { } ---- -And we'd like to render a simple HTML page that contains the item name, price and also a discounted price. -The discounted price is sometimes called a "computed property". -We will implement a template extension method to render this property easily. +And we'd like to render a simple HTML page that contains the item name and price. + +Let's start again with the template: + +.ItemResource/item.html +[source,html] +---- + + + + +{item.name} <1> + + +

{item.name}

+
Price: {item.price}
<2> + + +---- +<1> This expression is validated. Try to change the expression to `{item.nonSense}` and the build should fail. +<2> This is also validated. + +Finally, let's create a resource class with type-safe templates: + +.ItemResource.java +[source,java] +---- +package org.acme.quarkus.sample; + +import javax.inject.Inject; +import javax.ws.rs.GET; +import javax.ws.rs.Path; +import javax.ws.rs.PathParam; +import javax.ws.rs.Produces; +import javax.ws.rs.core.MediaType; + +import io.quarkus.qute.TemplateInstance; +import io.quarkus.qute.Template; +import io.quarkus.qute.api.CheckedTemplate; + +@Path("item") +public class ItemResource { + + @CheckedTemplate + class Templates { + public static native TemplateInstance item(Item item); <1> + } + + @GET + @Path("{id}") + @Produces(MediaType.TEXT_HTML) + public TemplateInstance get(@PathParam("id") Integer id) { + return Templates.item("item", service.findItem(id)); <2> + } +} +---- +<1> Declare a method that gives us a `TemplateInstance` for `templates/ItemResource/item.html` and declare its `Item item` parameter so we can validate the template. +<2> Make the `Item` object accessible in the template. + +=== Template parameter declaration inside the template itself + +Alternately, you can declare your template parameters in the template file itself. + Let's start again with the template: .item.html @@ -117,16 +280,11 @@ Let's start again with the template:

{item.name}

Price: {item.price}
- {#if item.price > 100} <3> -
Discounted Price: {item.discountedPrice}
<4> - {/if} ---- <1> Optional parameter declaration. Qute attempts to validate all expressions that reference the parameter `item`. <2> This expression is validated. Try to change the expression to `{item.nonSense}` and the build should fail. -<3> `if` is a basic control flow section. -<4> This expression is also validated against the `Item` class and obviously there is no such property declared. However, there is a template extension method declared on the `ItemResource` class - see below. Finally, let's create a resource class. @@ -160,16 +318,66 @@ public class ItemResource { public TemplateInstance get(@PathParam("id") Integer id) { return item.data("item", service.findItem(id)); <2> } +} +---- +<1> Inject the template with path `templates/item.html`. +<2> Make the `Item` object accessible in the template. + +== Template Extension Methods + +*Template extension methods* are used to extend the set of accessible properties of data objects. + +Sometimes, you're not in control of the classes that you want to use in your template, and you cannot add methods +to them. Template extension methods allows you to declare new method for those classes that will be available +from your templates just as if they belonged to the target class. + +Let's keep extending on our simple HTML page that contains the item name, price and add a discounted price. +The discounted price is sometimes called a "computed property". +We will implement a template extension method to render this property easily. +Let's update our template: + +.HelloResource/item.html +[source,html] +---- + + + + +{item.name} + + +

{item.name}

+
Price: {item.price}
+ {#if item.price > 100} <1> +
Discounted Price: {item.discountedPrice}
<2> + {/if} + + +---- +<1> `if` is a basic control flow section. +<2> This expression is also validated against the `Item` class and obviously there is no such property declared. However, there is a template extension method declared on the `TemplateExtensions` class - see below. - @TemplateExtension <3> - static BigDecimal discountedPrice(Item item) { +Finally, let's create a class where we put all our extension methods: + +.TemplateExtensions.java +[source,java] +---- +package org.acme.quarkus.sample; + +import io.quarkus.qute.TemplateExtension; + +@TemplateExtension +public class TemplateExtensions { + + public static BigDecimal discountedPrice(Item item) { <1> return item.price.multiply(new BigDecimal("0.9")); } } ---- -<1> Inject the template with path `templates/item.html`. -<2> Make the `Item` object accessible in the template. -<3> A static template extension method can be used to add "computed properties" to a data class. The class of the first parameter is used to match the base object and the method name is used to match the property name. +<1> A static template extension method can be used to add "computed properties" to a data class. The class of the first parameter is used to match the base object and the method name is used to match the property name. + +NOTE: you can place template extension methods in every class if you annotate them with `@TemplateExtension` but we advise to keep them either +grouped by target type, or in a single `TemplateExtensions` class by convention. == Rendering Periodic Reports