The Phoenix Syntax and Template Elements

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.

Template arguments: @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>
                            
                        

Imports: @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)
                            
                        

Escaping the @

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>
                            
                        

Printing variables or calling methods

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>
                            
                        

Null-safe printing: @?

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>
                        
                    
Null-safe ternary operator: @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>
                        
                    
Automatic escaping or printing HTML code

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>
                        
                    
Evaluating expressions: @()

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>
                        
                    
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.

                        
@* This is a simple comment on one line *@

@*
    This is a complex comment
    on multiple lines
*@
                        
                    
For loops: @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(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>
}
                        
                    
Conditionals: @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.

                        
<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>
}
                        
                    
Variable declaration: @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 (s = values.get(0)) {
     <div>Value is @s</div>
}
                        
                    
Calling fragments: @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 {...}

All other parameters are provided normally, as arguments to the function call.
fragments/withContent.java.html
                                
@args(int a, PhoenixContent content)

<h1>Fragment @a</h1>

@content
                                
                            
main.java.html
                                
<h1>Fragment is below</h1>
@fragments.withContent.template(a) {
    <p>This is my text</p>
    <a href="@(a).html">It holds an URL</a>
}
                                
                            
Content blocks: @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 />
                        
                    
Reverse routing: @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.

TestController.java
                                
@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));
    }
}
                                
                            
main.java.html
                                
<a href="@routes.TestController.renderTest(0, 3)">
    Go to this page
</a>
                                
                            
CSRF protection: @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
Phoenix element
                                    
@csrf.input
                                    
                                
Example generated HTML code
                                    
<input type="hidden" name="_csrf" 
    value="1KkX7eTEipXipsECVmNW1ayt5nw_7Evxo7sj5FLGgZd1_A74sZ8hjNChv_DPk_EwN05i4JzOy0Ve23jclo5BhTanuK9Ezm-a">
                                    
                                
CSRF meta
Phoenix element
                                        
@csrf.meta
                                        
                                    
Example generated HTML code
                                        
<meta name="_csrf" content="1KkX7eTEipXipsECVmNW1ayt5nw_7Evxo7sj5FLGgZd1_A74sZ8hjNChv_DPk_EwN05i4JzOy0Ve23jclo5BhTanuK9Ezm-a">
<meta name="_csrf_header" content="X-CSRF-TOKEN">