In most cases, Phoenix will have the exact same syntax as Java, with the addition of the
@
character being pre-pended to the statement. The @
character must be
added directly adjetant to the statement, without any whitespaces.
Valid statements
@import java.util.List;
@args(int a, String b)
@if(a == 0) {
}
@if (a == 0) {
}
@for(int count = 0; count<4; count++) {
}
@for (int count = 0; count<4; count++) {
}
Invalid statements
@ import java.util.List;
@ args(int a, String b)
@ if(a == 0)
{
}
@ if (a == 0)
{
}
@ for(int count = 0; count<4; count++)
{
}
@ for (int count = 0; count<4; count++)
{
}
Furthermore, elements that can have nested elements inside curly braces are (like if
,
for
, template calls
, content block declaration
,
with
), must start on new lines. One exception is the if
statement which
allows inline, as long as the entire statement is inline.
Valid statements
<p>My text</p>
@if(a == 0) {
<p>A is Zero</p>
}
<tr class="@if (i.index() % 2 == 0) {odd} else {even}">
@if (item.getChange() < 0.0) {
<td class="minus">@item.getChange()</td>
<td class="minus">@item.getRatio()</td>
} else {
<td>@item.getChange()</td>
<td>@item.getRatio()</td>
}
</tr>
Invalid statements
<p>My text</p>@if(a == 0) {
<p>A is Zero</p>
}
@if(a == 0)
{
<p>A is Zero</p>
}
<tr class="@if (i.index() % 2 == 0) {odd}
else {even}">@if (item.getChange() < 0.0) {
<td class="minus">@item.getChange()</td>
<td class="minus">@item.getRatio()</td>
} else {
<td>@item.getChange()</td>
<td>@item.getRatio()</td>
}
</tr>
Phoenix currently goes for an "optimistic" approach where it will assume that the template is valid and will tryu to generate the undelying Java class. If this is not the case, the underlying Java class will fail to compile. At the moment Phoenix does not provide clear indication as to where the template is invalid, but further work will be done to provide this as well
Below you can find more clear explanations for each element supported by Phoenix, with more examples.
@args()
Each template must have a arguments declaration. This is done using the @args()
element. It is
recommended that the arguments be declared right below the imports
Additionally, you can provide input parameters for your template and you can use these input
parameters in the code. All input parameters must be declared on the same line. YOu can think of
this as the constructor of the template.
Input arguments are final and can't have their values changed inside the template.
Example: @args(String title, int numDivs)
Example:
@args(int a, int b)
<head>
<title>Test page</title>
</head>
<body>
</body>
@import
You can import any Java class from your project, including standard Java classes, right into the
template. All declared input arguments must have their underlying class imported. Imports must
be declared at the start of the template, before the args
declaration. Phoenix also
supports static imports. Primitives and java.lang.*
don't need to be imported.
@import com.example.demo.data.Stock;
@import java.util.List;
@import static java.lang.System.currentTimeMillis
@args (List<Stock> items)
@
You may need to use the @
character as an actual character (for example, for
emails). That is why you will need to escape it. This is done using, you guessed it, the @
character.
<a href="mailto:test@@domain.com">Email me</a>
Phoenix allows you send variables from the controller to the template in an easy way. In the
tempalte you can refference any variable by appending the @
character. This will
print the value of that variable in the HTML code of the page when it is being rendered.
Furthermore, you can call method from that object, just like you would in any Java class.
<p>The value of variable `test` is: @test<p>
<p>You can also call methods: @myList.get(0)</p>
@?
If a variable is null you will encounter a NullPointerException when the template is being rendered. Phoenix supports two null-safety operators. The null-safe printing element will try to evalueate the variable or method call. In case null is encoutnered anywhere in the stack trace, nothing will be printed. Phoenix achieves this by catching the NullPointerException in the code as well as valdiating that the result is not null.
In the below example, nothing will be printed if the variable test
is null.
<p>The value of variable `test` is: @?test<p>
The null-safe printing element also supports method calls. In the below example, nothing will be
printed if myList
is null OR if the element returned is null
<p>You can also call methods: @myList.get(0)</p>
@var?:"default"
The Null-safe ternary operator is the second null-safe element provided by Phoenix. This element is great if you want to provide a default value in case the variable you are trying to print is null. This element does not protect agains NullPointerException in the stacktrace, however, it checks if the variable or the method call is null. If it is, the default value is printed instead.
<p class='@a?:"defaultClass"'>Null-safe ternary</p>
By default, all variables printed are escaped. This way the rendered pages are protected from code injection via persisting data that eventually get's transmitted to the template.
However, there are scenarios where you would actually want to send HTML code to the template and
have it rendered as HTML. This is where the @raw
element comes in. The variable or
result of the method call that is present inside the @raw
element will not be
escaped.
<div>
@raw(myHtmlStringVariable)
</div>
@()
Phoenix allows you to evaluate expressions in the template. This is done using the
@()
. This element also comes in handy when you want to use a variable as part of
text which would otherwise be considered method calls, for example when building the URL to a
page where the fileName (but not the extension) is provided by your variable.
<p>
Sum of @a and @b = @(a + b)
</p>
<a href="@(myPageName).html">It holds another URL</a>
@for()
Phoenix supports for
loops in a similar way Java does. You can define a loop using
the @for
syntax and use a counter to itterate between elements as well as a
for-in
loops. For
statements must start on a new line, with the opening
curly bracket ({
) being on the same line. Furthermore, the ending curly bracket
}
should be on a seperate, empty line.
for
statemnt inside JavaScript code that also uses curly brackets
may break parsing.
@for(int count = 0; count<myList.size(); count++) {
<span>@myList.get(count)</span>
}
@for(String item: myList) {
<span>@item</span>
}
Furthermore, Phoenix supports a for-with-iterator
where in addition to the item
being returned from the collection, an itterator is also being returned which can provide the
index.
@for((itt, MyClass item):myList) {
<span>@itt.index() : @item</span>
}
@if
, @if-else
,
@if-else if-else
Phoenix supports standard Java if
conditionals along with if-else
and
if-else if-else
statements. The left curly bracket {
must be on the
same line with the statment. Phoenix allows the if
statment to be inline completly
or have the if
statement on a new line.
if
statemnt inside JavaScript code that also uses curly brackets may
break parsing.
<tr class="@if (i.index() % 2 == 0) {odd} else {even}">
</tr>
<h5>Complex if</h5>
@if (a < myList.size()) {
<p>Smaller</p>
} else if (a == myList.size()) {
<p>Equal</p>
} else {
<p>Bigger</p>
}
@with
In certain scenarios you would like to declare a new variable that holds the refference to an
element received from a method call, like an element from within a list, and to reuse it without
the need to do multiple calls to the getter. This is why Phoenix supports declaring a variable
using the @with
block. The @with
block declares a new variable and inside the block's body you can
use it with ease.
with
block supports declaring only one variable. Future versions
will support multiple.
@with (s = values.get(0)) {
<div>Value is @s</div>
}
@fragmentName.template()
Phoenix was conceived from the start to allow building of complex and modular web applications. An important component in any template engine, Phoenix including, is the possiblity to have fragments or templates that can be reused and combined to create more complex web pages. Any Phoenix template can be combined with others by calling the template and providing the right parameters.
A template is invoked by calling the .template()
method on the template. As with
invoking views, the template name is relative to the views
directory, with
.
being used as a path separator for sub-folders.
@for(int count = 0; count<2; count++) {
<hr />
@fragments.testFragment.template(count)
<span>Count is @count</span>
<hr />
}
If the last argument is of type PhoenixContent
(more on that later), the content can
be provided as blocks of code inside curly brackets {...}
@args(int a, PhoenixContent content)
<h1>Fragment @a</h1>
@content
<h1>Fragment is below</h1>
@fragments.withContent.template(a) {
<p>This is my text</p>
<a href="@(a).html">It holds an URL</a>
}
@myContent => { }
Phoenix allows declaration of content blocks which can be reused multiple times or sent as
inputs to other templates. Content blocks are reusable parts of content that are declared inside
the same template. Once declared, content blocks can be refferenced and inserted just like any
other variables.
Content blocks have the type PhoenixContent
and can be input parameters for other
templates.
@myContent => {
<p>This is my var</p>
<a href="@a">It holds a URL</a>
}
<hr />
@myContent
<hr />
@route
One advantage that Phoenix has over other templates engines is the presence of route elements
and reverse routing calculated at compile time. Any endpoint declared in your controllers will
create a route that includes the URL, path variables and request attributes. All controllers
that are defined in the configured package will be included and Pheonix currently supports
methods annotated with @GetMapping
, @PostMapping
and
@PutMapping
.
The route can have multiple arguments . First, all PathVariable
arguments are added
in the defined order, followed by RequestParam
attributes, also in the defined
order. A route can be easilly invoked using the @route
element, followed by the
controller name and method name.
@Controller
public class TestController {
@GetMapping("/test.html")
public Result renderTest(@RequestParam(name = "a", defaultValue = "0") int a,
@RequestParam(name = "b", defaultValue = "0") int b) {
return ok(View.of("test", a, b));
}
}
<a href="@routes.TestController.renderTest(0, 3)">
Go to this page
</a>
@csrf.meta
and @csrf.input
Phoenix provides an easy way of adding CSRF tokens to your web page, either via a hidden input
or using meta tags. This is done using the @csrf.input
or @csrf.meta
respectivly.
The @csrf.input
will generate a hidden input field with the name _csrf
and populated with the CSRF token provided by Spring.
The @csrf.meta
will generate two meta tags. The first will have the name
_csrf
and the content the CSRF token provided by Spring. The second one will have
the name _csrf_header
and the content the CSRF header name, provided by Spring.
Additionally, you
can provide an optional parameter to the csrf.meta(false)
, which will skip
generation for the _csrf_header
meta element.
@csrf.input()
<input type="hidden" name="_csrf"
value="1KkX7eTEipXipsECVmNW1ayt5nw_7Evxo7sj5FLGgZd1_A74sZ8hjNChv_DPk_EwN05i4JzOy0Ve23jclo5BhTanuK9Ezm-a">
@csrf.meta()
<meta name="_csrf" content="1KkX7eTEipXipsECVmNW1ayt5nw_7Evxo7sj5FLGgZd1_A74sZ8hjNChv_DPk_EwN05i4JzOy0Ve23jclo5BhTanuK9Ezm-a">
<meta name="_csrf_header" content="X-CSRF-TOKEN">
Phoenix has a feature where you can define sections in your template. Sections are rendered just as everything else, but has the advantage that code can be inserted inside from within fragments.
Let's take an example, since it is easier to understand. You have your HTML page and inside you have the styles, the HTML part and the JavaScript part.
You can define two sections, for the styles and the JavaScript parts, and fragments you later insert anywhere in the page can inject code inside those sections.
This way, your fragments have everything needed to make them work, all in one place and with a single fragment call, the needed dependencies are added where they need to be.
A section is defined using @section("name")
. Names are case-sensitive. The default section is named "html".
Also, similar to fragment calls, sections can have pre-populated data:
@section("css") {
.body {
background-color: red;
}
}
Phoenix allows you to insert content at a specific position in the template.
This can be achieved using either the @insertAt("sectionName")
or @insertOnce("sectionName")
When defining a fragment, you can include additional code in the two tags mentioned and when the fragment is included inside another template, the specified code will be inserted in the right section. If the section does not exist, the code will be ignored.
@insertAt("css") {
.btn {
background-color: red;
}
}
The difference between @insertAt
and @insertOnce
is that
@insertOnce
will only insert the code for the first call to the fragment in the template,
while @insertAt
will insert the code every time it is called.
This is important since, if you use variables in the @insertOnce
, only the first call will be executed.
Both tags use the same input variables as defined in the @args
tag.
Since the default section is named "html", you can have a fragment where everything is inside an
@insertAt
tags.
IMPORTANT: The two tags must be at root-level and not nested inside other tags or elements
Adding comments to your template:
@* *@
As with any modern programming language, having comments in your code can be very usefull. Obviously, you could add traditional HTML comments, however these will be present in the final rendered HTML. If you want to have comments that will simply be ignored by the Phonix parser, you can do this using
@* *@
. The parser will simply ignore anything that is between the comment identifiers. Comments can be on a single line or on multiple lines. See examples below.