TemplatePro Official Documentation
⭐ Updated to TemplatePro Language version 0.7.0.
Here’s an example of a dynamic web application powered by DMVCFramework, TemplatePro and HTMX.
TemplatePro is a modern and versatile templating engine designed to simplify the dynamic generation of HTML, email content, and text files. With a syntax inspired by popular templating systems such as Jinja and Smarty, TemplatePro offers powerful features, including conditional blocks, loops, template inheritance and support for JSON data. In this article, we’ll explore the core features of TemplatePro, from basic syntax to advanced features like custom filters and JSON manipulation.
✌️ Since version 0.7.0 TemplatePro is case insensitive in variable names and in statements casing as-well.
Here’s a tipical utilization of a template:
program tproconsole;
{$APPTYPE CONSOLE}
uses
TemplatePro { TemplatePro compiler and executor },
System.Rtti { required by TValue },
System.SysUtils { required by try..except block }
;
//this is a custom filter
function AddSquareBrackets(const aValue: TValue; const aParameters: TArray<string>): TValue;
begin
Result := '[' + aValue.AsString + ']';
end;
procedure Main;
begin
var lCompiledTemplate: ITProCompiledTemplate := nil;
var lCompiler := TTProCompiler.Create();
try
var lTemplate := 'Simple variable: {{:variable1}}';
// template must be compiled before utilization
lCompiledTemplate := lCompiler.Compile(lTemplate);
// compiled template can be saved and retrieved from a file.
// A compiled template can be executed only
// by the same version of that produced the template itself.
finally
lCompiler.Free;
end;
//then, on the compiled template, you can define variable,
//datasets, list of objects and json objects to use while rendering
//the compiled template.
//in this case we use only a simple variable
lCompiledTemplate.SetData('variable1', 'Daniele Teti');
//compiled template can also use "filters" defined
//as simple functions with a specific prototype.
//In this case we are going to use a simple filter wich adds
//square brackets to the input value.
lCompiledTemplate.AddFilter('brackets', AddSquareBrackets);
//the render method returns the result of
//the template execution as string
WriteLn(lCompiledTemplate.Render);
end;
begin
try
Main;
except
on E: Exception do
Writeln(E.ClassName, ': ', E.Message);
end;
end.
By the end of this article, you’ll have a comprehensive understanding of TemplatePro’s capabilities and how to use it to create dynamic, maintainable templates for various applications.
1. Basic Template Structure
TemplatePro uses double curly braces ({{ }}
) to define logic and data expressions in the template. Here’s a simple example demonstrating the use of conditional blocks and loops:
Example: Iterating over a dataset with conditions
DataSets
{{if customers}}
{{for cust in customers}}
Code: {{:cust.Code}} - Name: {{:cust.Name}}
SINGLE {{:customer.Code}} {{:customer.Name}}
{{endfor}}
{{endif}}
Analysis:
-
Conditional Block (
if
): The{{if customers}}
block checks if the variablecustomers
exists and is not empty (in case of “list-like” variables and datasets, the “if” condition checks also if element/record count is greater than 0). If the condition is true, the content inside the block is executed. -
Loop (
for
): The{{for cust in customers}}
loop iterates over each customer in thecustomers
list, rendering theCode
andName
fields for each customer. -
Variable Insertion: Using the
{{:cust.Code}}
syntax, TemplatePro allows you to insert dynamic data into the output by accessing object properties.
This basic structure shows how TemplatePro manages logic and dynamic data insertion within templates, offering a clean separation between data and presentation.
for..in
loop support also {{continue}}
statement to skip the current iteration and continue with the next value.
At the core of TemplatePro is variable interpolation, allowing you to inject dynamic content directly into your templates. You can use the {{:variable}}
syntax to display the value of a variable.
Example:
Hello, {{:name}}!
Output (if name
is “Daniele”):
Hello, Daniele!
2. Looping with for .. in
The for .. in
loop is a powerful construct in TemplatePro that lets you iterate over collections such as json arrays, datasets or lists of objects. Inside the loop, you can access each item in the collection using a loop variable.
Basic Example:
{{for person in people}}
- {{:person.first_name}} {{:person.last_name}}
{{endfor}}
Output (for a list of people):
- Daniele Teti
- Peter Parker
- Bruce Banner
Nested Loop Example:
You can also nest loops to iterate over collections within collections.
{{for person in people}}
{{:person.first_name}} {{:person.last_name}}
{{for phone in person.phones}}
- {{:phone.type}}: {{:phone.number}}
{{endfor}}
{{endfor}}
Output:
Daniele Teti
- Mobile: 123-456-7890
- Work: 098-765-4321
Peter Parker
- Home: 555-555-5555
- Work: 444-444-4444
Using pseudo-variables in for .. in
loop
Special pseudo-variables like @@index
, @@odd
, and @@even
help manage iteration and conditions during the loop. @@index
provides the current index (1- based), while @@odd
and @@even
help apply logic based on the position: @@odd
is true for “odd” index values, while @@even
is true for “even” index value.
Using the following template…
{{for cust in customers}}
{{:cust.@@index}}. {{:cust.Name}} - This index is {{if cust.@@odd }}odd{{endif}}{{if cust.@@even }}even{{endif}}
{{endfor}}
…you get the following output.
1. Ford - This index is odd
2. Ferrari - This index is even
3. Lotus - This index is odd
4. FCA - This index is even
5. Hyundai - This index is odd
6. De Tomaso - This index is even
7. Dodge - This index is odd
8. Tesla - This index is even
9. Kia - This index is odd
10. Tata - This index is even
11. Volkswagen - This index is odd
12. Audi - This index is even
13. Skoda - This index is odd
2. More Advanced Conditional Logic Using {{else}}
TemplatePro also supports more advanced conditional logic with else
blocks, enabling you to provide alternative content when conditions aren’t met.
Example: Conditional logic with else
{{if customers}}
{{for cust in customers}}
Code: {{:cust.Code}} - Name: {{:cust.Name}}
{{endfor}}
{{else}}
<p>No customers found.</p>
{{endif}}
In this case, if the customers
dataset is empty, the else
block displays the message “No customers found.” This simple feature makes TemplatePro flexible and robust for dynamic content generation.
3. Template Inheritance and Reusability
TemplatePro allows template inheritance, which enables the creation of reusable components and layouts, helping to organize large projects effectively.
Example: Extending a base template
Parent template: base_template.tpro
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<title>{{block "title"}}Default Page Title{{endblock}}</title>
<link rel="stylesheet" href="style.css">
{{block "extraheader"}}{{endblock}}
</head>
<body>
<div class="content">
{{block "body"}}{{endblock}}
</body>
</body>
</html>
{{extends "base_template.tpro"}}
{{block "content"}}
<h1>{{:title}}</h1>
<p>{{:description}}</p>
{{endblock}}
Here, the {{extends}}
directive imports a base template, and the {{block}}
keyword defines the content that will populate a specific block within the base template. The “title” and “extraheader” blocks defined in parent template are not overwritten by the child, so the default block content defined by the parent is used. This approach allows for reusable, maintainable layouts where common components (like headers and footers) are centrally managed. You can use as many blocks you need in your base template. If child template contains a block with the same name of the parent, the parent block is overwritten by the child one, otherwise the parent one is used.
To mantain things simple and effective, a parent page cannot use another parent page. In other words, the hierarchy cannot be composed by more than 2 templates.
Note:
- If the child template contains a block not declared in the parent, that child block is ignored.
- Any content outside blocks in child template is ignored.
4. Template Inclusion
In addition to inheritance, TemplatePro supports file inclusion, allowing you to modularize templates by splitting them into smaller, reusable parts.
Example: Including external templates
{{include "included1.tpro"}}
{{include "partials/included2.tpro"}}
The {{include}}
directive lets you import other templates into the current one. In this example, we include included1.tpro
and partials/included2.tpro
, allowing for organized, modular template files.
5. Filters
Filters in TemplatePro allow you to modify the output of variables before they are rendered. Filter parameters (if any) are passed using “,” notation (see next paragraph).
Built-in Filter Example:
Original: {{:name}}
Uppercase: {{:name|uppercase}}
Lowercase: {{:name|lowercase}}
Output (if name
is “Daniele Teti”):
Original: Daniele Teti
Uppercase: DANIELE TETI
Lowercase: daniele teti
Filters parameters (if any) can be passed comma separated (e.g. {{:value1|filter,"par1",2,"par3"}}
).
TemplatePro offers a set of built-in filters:
Filter Name | Description |
---|---|
uppercase | returns the value in uppercase |
lowercase | returns the value in lowercase |
capitalize | returns the value capitalized a.k.a. first letter in each word in uppercase |
rpad,xx | returns the value padded to right with spaces. Total length is xx. |
rpad,xx,"-" | returns the value padded to right with “-” char. Total length is xx. |
lpad,xx | returns the value padded to left with spaces. Total length is xx. |
lpad,xx,"-" | returns the value padded to left with “-” char. Total length is xx. |
datetostr | returns the date value as string formatted using the system locale |
datetostr,“yyyy mm dd” | returns the date value as string formatted using the specified format |
datetimetostr | returns the datetime value as string formatted using the system locale |
datetimetostr,“yyyy mm dd hh:nn:ss” | returns the datetime value as string formatted using the specified format |
formatdatetime | alias to datetimetostr |
trunc,numericpar | truncates text contents to <numericpar> character and adds “…” to the end. e.g. "Hello World"|trunc,5 returns Hello... |
totrue | always returns a thruty value. It is useful to rapidly test the if conditions. |
tofalse | always returns a falsy value. It is useful to rapidly test the if conditions. |
version | always returns the current TemplatePro version. e.g. ""|version returns 0.7.0 |
if
statement supports simple expressions, so some built-in filter are designed to be used with “if” statements like the following example.
{{if my_value|eq,0}}
Printed only if variable my_value is zero
{{endif}}
Here’s the built-in comparand filters:
Comparand Filter Name | Meaning |
---|---|
gt |
returns true is the value is greater than the parameter |
ge |
returns true is the value is greater than or equal to the parameter |
lt |
returns true is the value is less than the parameter |
le |
returns true is the value is less than or equal to the parameter |
contains |
returns true if the value contains the parameter |
contains_ignore_case |
case-insensitive version of contains |
eq |
returns true if the value is equals to the parameter |
ne |
returns true if the value is not equals to the parameter |
6. Custom Filters in TemplatePro
TemplatePro offers the ability to define custom filters that manipulate data before it’s rendered. These filters are defined in Delphi using the AddFilter
method and can be applied within the template using the pipe (|
) syntax.
Example: Applying filters
{{:value2|sayhello}}
{{:value2}} => {{:value2|lowercase}}
In this example, the sayhello
and lowercase
filters are applied to the value2
variable. Filters are useful for data transformations like converting text to uppercase or formatting dates.
Defining a Filter
TemplatePro supports filters in two ways: as a regular function or as an anonymous method. Here are the signatures for defining custom filters:
TTProTemplateFunction =
function(const aValue: TValue; const aParameters: TArray<string>): TValue;
Or as an anonymous method:
TTProTemplateAnonFunction =
reference to function(const aValue: TValue; const aParameters: TArray<string>): TValue;
Example: Uppercase filter
function UpperCaseFilter(const aValue: TValue; const aParameters: TArray<string>): TValue;
begin
Result := UpperCase(aValue.AsString);
end;
Compiler.AddFilter('uppercase', UpperCaseFilter);
This filter checks if the input value is a string, converts it to uppercase, and returns the result.
Example: Filter with parameters
Here’s a more advanced filter that truncates strings based on a specified length:
function TruncateFilter(const aValue: TValue; const aParameters: TArray<string>): TValue;
var
MaxLength: Integer;
begin
if aValue.IsType<string> and (Length(aParameters) > 0)
and TryStrToInt(aParameters[0], MaxLength) then
begin
if Length(aValue.AsString) > MaxLength then
Result := aValue.AsString.SubString(0, MaxLength)
else
Result := aValue;
end
else
begin
Result := aValue;
end;
end;
Compiler.AddFilter('truncate', TruncateFilter);
This filter takes an additional parameter (MaxLength
) and truncates the input string accordingly.
To use this filter ue th following syntax in template.
Template
Original value: {{:value}}
Truncated value: {{:value|truncate,14}}
Output
Original value: This is a very long string
Truncated value: This is a very
7. Using Functions
TemplatePro supports the use of functions to manipulate data within your templates. Functions can be invoked with parameters and are a great way to add logic to your templates. Currently functions are just filters applied to an empty string, so be warned that the first function parameter is always an empty string.
Basic Function Example:
Sum: {{""|sum,2,3}}
Greeting: {{""|concat,"Hello","World"}}
Output:
Sum: 5
Greeting: Hello, Daniele
8. Working with JSON Data
TemplatePro excels at handling JSON data, making it ideal for rendering content from APIs or other data sources that return JSON. The engine allows you to iterate over arrays, access object properties, and use special pseudo-variables such as @@index
for advanced control.
Example: Iterating over JSON data
{{for person in jsonobj.people}}
{{:person.@@index}}) {{:person.first_name|uppercase}}
{{:person.last_name|uppercase}} (age {{:person.age}})
{{for device in person.devices}}
{{:device.@@index}}° {{:device}}
{{endfor}}
{{:person.car.brand}} (Model {{:person.car.model|uppercase}})
{{if person.@@odd}}this line is odd{{endif}}
{{if person.@@even}}this line is even{{endif}}
{{endfor}}
Analysis:
-
Iterating through JSON objects: The
{{for person in jsonobj.people}}
loop iterates over thepeople
array inside thejsonobj
. Properties likefirst_name
,last_name
, andage
are accessed directly. -
Using pseudo-variables: Special pseudo-variables like
@@index
,@@odd
, and@@even
help manage iteration and conditions during the loop.@@index
provides the current index, while@@odd
and@@even
help apply logic based on the position. -
Nested JSON arrays: Nested arrays, such as
devices
, can be iterated over using a second{{for}}
loop. This feature is crucial when dealing with complex JSON structures. -
Accessing object properties: The properties of nested objects, like
person.car.brand
, can be accessed directly, and filters can be applied to transform values (e.g.,uppercase
).
Note:
- There’s no need to reset an iterator if you need to iterate it two or more times. Any
for..in
loop restart the iterator from the first element.
Conclusion
TemplatePro is a powerful templating system that offers developers flexibility and control over dynamic content generation. Its intuitive syntax, combined with support for conditional logic, loops, template inheritance, and JSON handling, makes it a robust tool for modern web development. Moreover, the ability to define custom filters using Delphi’s AddFilter
method extends its functionality even further, allowing for complex data transformations directly within templates.
Whether you’re building a simple HTML email or a complex web application, TemplatePro provides the tools you need to create modular, maintainable, and reusable templates. Its ability to handle JSON data, combined with features like template inclusion and filters, makes it a strong choice for developers seeking an efficient and flexible templating solution.
Links
Check unit tests for more info about syntax and border cases.
Comments
comments powered by Disqus