Become a member!

TemplatePro Official Documentation

⭐ Updated 2025-01-28 to TemplatePro Language version 0.7.3.

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 comprehensive guide, we’ll explore all features of TemplatePro, from basic syntax to advanced features like custom filters, JSON manipulation, and nullable types support.

✌️ Since version 0.7.0 TemplatePro is case insensitive in variable names and in statements casing as-well.

Quick Start Example

Here’s a typical 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<TFilterParameter>): TValue;
begin
  Result := '[' + aValue.AsString + ']';
end;

procedure Main;
var
  lCompiledTemplate: ITProCompiledTemplate;
  lCompiler: TTProCompiler;
begin
  lCompiler := TTProCompiler.Create();
  try
    var lTemplate := 'Hello {{:name|brackets}}! Today is {{:today|datetostr}}.';

    // 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 that produced the template itself.
    // lCompiledTemplate.SaveToFile('template.tpc');
    // lCompiledTemplate := TTProCompiledTemplate.CreateFromFile('template.tpc');
  finally
    lCompiler.Free;
  end;

  // Then, on the compiled template, you can define variables,
  // datasets, list of objects and json objects to use while rendering
  // the compiled template.

  // In this case we use simple variables
  lCompiledTemplate.SetData('name', 'Daniele Teti');
  lCompiledTemplate.SetData('today', Date);

  // Compiled template can also use "filters" defined
  // as simple functions with a specific prototype.
  // In this case we are using a simple filter which 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);
  // Output: Hello [Daniele Teti]! Today is 2025-01-28.
end;

begin
  try
    Main;
  except
    on E: Exception do
      Writeln(E.ClassName, ': ', E.Message);
  end;
end.

By the end of this guide, you’ll have a comprehensive understanding of TemplatePro’s capabilities and how to use it to create dynamic, maintainable templates for various applications.

1. Variable Interpolation and Basic Syntax

At the core of TemplatePro is variable interpolation, allowing you to inject dynamic content directly into your templates using the {{:variable}} syntax.

Basic Variable Usage

Hello, {{:name}}!
Your age is {{:age}} years old.

Output (if name is “Daniele” and age is 45):

Hello, Daniele!
Your age is 45 years old.

Object Property Access

TemplatePro supports nested object property access using dot notation:

{{:user.profile.first_name}} {{:user.profile.last_name}}
Email: {{:user.contact.email}}

Accessing Array Elements

You can access array elements using index notation:

First item: {{:items[0]}}
Second item: {{:items[1]}}

Null and Undefined Variables

TemplatePro gracefully handles null or undefined variables by rendering them as empty strings:

This will be empty if undefined: {{:undefined_var}}
This handles nulls gracefully: {{:nullable_field}}

2. Conditional Logic with if/else

TemplatePro provides powerful conditional logic to control template rendering based on data conditions.

Basic Conditional Blocks

{{if user_logged_in}}
  Welcome back, {{:username}}!
{{else}}
  Please log in to continue.
{{endif}}

Truthiness Evaluation

TemplatePro evaluates different data types for truthiness:

  • Strings: Empty strings are falsy, non-empty strings are truthy
  • Numbers: Zero is falsy, non-zero is truthy
  • Objects: Null objects are falsy, non-null objects are truthy
  • Collections: Empty collections/datasets are falsy, non-empty are truthy
  • Boolean: True/false values work as expected
{{if customers}}
  We have {{:customer_count}} customers.
{{else}}
  No customers found.
{{endif}}

{{if score}}
  Your score: {{:score}}
{{else}}
  No score recorded.
{{endif}}

Complex Conditional Expressions

You can use filters in conditional expressions for more complex logic:

{{if age|ge,18}}
  You are an adult.
{{else}}
  You are a minor.
{{endif}}

{{if username|contains,"admin"}}
  Admin user detected.
{{endif}}

{{if password|eq,confirm_password}}
  Passwords match.
{{else}}
  Password confirmation failed.
{{endif}}

3. Looping with for .. in

The for .. in loop is a powerful construct that lets you iterate over collections such as JSON arrays, datasets, or lists of objects.

Basic Loop Structure

{{for person in people}}
  - {{:person.first_name}} {{:person.last_name}}
{{endfor}}

Output (for a list of people):

- Daniele Teti
- Peter Parker
- Bruce Banner

Loop Control with continue

Skip iterations using the {{continue}} statement:

{{for product in products}}
  {{if product.discontinued|eq,true}}
    {{continue}}
  {{endif}}
  Product: {{:product.name}} - Price: ${{:product.price}}
{{endfor}}

Nested Loops

You can 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}}

  {{if person.addresses}}
    Addresses:
    {{for address in person.addresses}}
      - {{:address.street}}, {{:address.city}} {{:address.zip}}
    {{endfor}}
  {{endif}}
{{endfor}}

Pseudo-Variables in Loops

Special pseudo-variables provide additional context during iteration:

  • @@index: Current iteration index (1-based)
  • @@odd: True for odd-indexed items
  • @@even: True for even-indexed items
{{for customer in customers}}
{{:customer.@@index}}. {{:customer.Name}}
{{if customer.@@odd}}
  (Odd position - highlighted)
{{endif}}
{{if customer.@@even}}
  (Even position - standard)
{{endif}}
{{endfor}}

Output:

1. Ford Motors
(Odd position - highlighted)
2. Ferrari SpA
(Even position - standard)
3. Lotus Cars
(Odd position - highlighted)

Empty Collection Handling

Combine loops with conditionals to handle empty collections:

{{if orders}}
  {{for order in orders}}
    Order #{{:order.id}}: {{:order.total|formatfloat,"$#,##0.00"}}
  {{endfor}}
{{else}}
  No orders found.
{{endif}}

4. Working with Datasets

TemplatePro has excellent support for Delphi datasets, making it perfect for database-driven applications:

{{if customers}}
  <table>
    <tr><th>Code</th><th>Name</th><th>City</th></tr>
    {{for customer in customers}}
      <tr>
        <td>{{:customer.Code}}</td>
        <td>{{:customer.Name}}</td>
        <td>{{:customer.City}}</td>
      </tr>
    {{endfor}}
  </table>
{{else}}
  <p>No customers in database.</p>
{{endif}}

5. Built-in Filters

TemplatePro provides a comprehensive set of built-in filters for common data transformations.

Text Transformation Filters

Original: {{:name}}
Uppercase: {{:name|uppercase}}
Lowercase: {{:name|lowercase}}
Capitalize: {{:name|capitalize}}

Output (if name is “daniele teti”):

Original: daniele teti
Uppercase: DANIELE TETI
Lowercase: daniele teti
Capitalize: Daniele Teti

Padding Filters

Right padded: "{{:code|rpad,10}}"
Right padded with dashes: "{{:code|rpad,10,"-"}}"
Left padded: "{{:code|lpad,8}}"
Left padded with zeros: "{{:id|lpad,6,"0"}}"

Date and Time Filters

System format: {{:birth_date|datetostr}}
Custom format: {{:birth_date|datetostr,"dd/mm/yyyy"}}
DateTime: {{:created_at|datetimetostr,"yyyy-mm-dd hh:nn:ss"}}
ISO format: {{:timestamp|datetoiso8601}}

Numeric Filters

Formatted: {{:price|formatfloat,"$#,##0.00"}}
Percentage: {{:rate|formatfloat,"0.00%"}}
Scientific: {{:large_number|formatfloat,"0.00E+00"}}

Text Processing Filters

Truncated: {{:description|trunc,50}}
HTML encoded: {{:user_input|htmlencode}}
Version info: {{:""|version}}

Comparison Filters (for use in if statements)

{{if age|ge,18}}
  Adult user
{{endif}}

{{if username|contains,"admin"}}
  Administrator privileges
{{endif}}

{{if status|eq,"active"}}
  User is active
{{endif}}

{{if price|lt,100}}
  Budget-friendly option
{{endif}}

Complete Built-in Filters Reference:

Filter Name Description Example
uppercase Convert to uppercase {{:name|uppercase}}
lowercase Convert to lowercase {{:name|lowercase}}
capitalize Capitalize each word {{:title|capitalize}}
rpad,length[,char] Right-pad to length {{:code|rpad,10,"-"}}
lpad,length[,char] Left-pad to length {{:id|lpad,6,"0"}}
datetostr[,format] Format date {{:date|datetostr,"dd/mm/yyyy"}}
datetimetostr[,format] Format datetime {{:datetime|datetimetostr}}
datetoiso8601 ISO 8601 format {{:date|datetoiso8601}}
formatfloat,format Format numbers {{:price|formatfloat,"$0.00"}}
trunc,length Truncate text {{:text|trunc,50}}
htmlencode HTML encode {{:input|htmlencode}}
version TemplatePro version {{""|version}}
totrue Always true {{:var|totrue}}
tofalse Always false {{:var|tofalse}}
Comparison Filters
eq,value Equal to {{if age|eq,25}}
ne,value Not equal to {{if status|ne,"inactive"}}
gt,value Greater than {{if score|gt,80}}
ge,value Greater or equal {{if age|ge,18}}
lt,value Less than {{if price|lt,100}}
le,value Less or equal {{if items|le,10}}
contains,text Contains text {{if name|contains,"John"}}
icontains,text Case-insensitive contains {{if email|icontains,"gmail"}}

6. Custom Filters

TemplatePro allows you to define custom filters to extend functionality beyond the built-in set.

Filter Function Signatures

// Regular function
TTProTemplateFunction =
  function(const aValue: TValue; const aParameters: TArray<TFilterParameter>): TValue;

// Anonymous method
TTProTemplateAnonFunction =
  reference to function(const aValue: TValue; const aParameters: TArray<TFilterParameter>): TValue;

Simple Custom Filter Example

function AddSquareBrackets(const aValue: TValue;
  const aParameters: TArray<TFilterParameter>): TValue;
begin
  Result := '[' + aValue.AsString + ']';
end;

// Register the filter
lCompiledTemplate.AddFilter('brackets', AddSquareBrackets);

Usage:

Original: {{:username}}
Bracketed: {{:username|brackets}}

Filter with Parameters

function RepeatText(const aValue: TValue;
  const aParameters: TArray<TFilterParameter>): TValue;
var
  lText: string;
  lCount, i: Integer;
begin
  lText := aValue.AsString;
  lCount := aParameters[0].ParIntValue;
  Result := '';
  for i := 1 to lCount do
    Result := Result + lText;
end;

// Register the filter
lCompiledTemplate.AddFilter('repeat', RepeatText);

Usage:

Repeated: {{:char|repeat,5}}

Advanced Filter with Multiple Parameters

function SubstringFilter(const aValue: TValue;
  const aParameters: TArray<TFilterParameter>): TValue;
var
  lText: string;
  lStart, lLength: Integer;
begin
  lText := aValue.AsString;
  lStart := aParameters[0].ParIntValue;
  if Length(aParameters) > 1 then
    lLength := aParameters[1].ParIntValue
  else
    lLength := Length(lText) - lStart + 1;

  Result := Copy(lText, lStart, lLength);
end;

// Register the filter
lCompiledTemplate.AddFilter('substr', SubstringFilter);

Usage:

Substring: {{:text|substr,1,10}}
From position: {{:text|substr,5}}

Using Variables as Filter Parameters

All filters support variables as parameters:

{{:message|truncate,max_length}}
{{:price|formatfloat,number_format}}
{{if score|gt,passing_grade}}
  Congratulations!
{{endif}}

7. Template Functions

Functions are filters applied to empty values, useful for calculations and utility operations.

Built-in Function Usage

Current version: {{:|version}}
Random calculation: {{:|sum,10,20,30}}
String concatenation: {{:|concat,"Hello"," ","World"}}

Custom Function Example

function GenerateUUID(const aValue: TValue;
  const aParameters: TArray<TFilterParameter>): TValue;
begin
  if not aValue.IsEmpty then
    raise ETProRenderException.Create('"GenerateUUID" is a function, not a filter');

  Result := TGuid.NewGuid.ToString;
end;

// Register the function
lCompiledTemplate.AddFilter('uuid', GenerateUUID);

Usage:

New ID: {{:|uuid}}

8. Working with JSON Data

TemplatePro excels at handling JSON data, making it ideal for APIs and structured data.

Basic JSON Iteration

{{for person in people}}
  {{:person.@@index}}) {{:person.first_name|uppercase}} {{:person.last_name|uppercase}}
  Age: {{:person.age}}

  {{if person.devices}}
    Devices:
    {{for device in person.devices}}
      {{:device.@@index}}° {{:device.name}} ({{:device.type}})
    {{endfor}}
  {{endif}}

  {{if person.car}}
    Car: {{:person.car.brand}} {{:person.car.model|uppercase}}
  {{endif}}

  {{if person.@@odd}}
    (This person is at an odd position)
  {{endif}}
{{endfor}}

Complex JSON Structures

{{for order in orders}}
  Order #{{:order.id}} - {{:order.date|datetostr}}
  Customer: {{:order.customer.name}}

  Items:
  {{for item in order.items}}
    - {{:item.product.name}}: {{:item.quantity}} x {{:item.price|formatfloat,"$0.00"}}
  {{endfor}}

  Total: {{:order.total|formatfloat,"$#,##0.00"}}
  Status: {{:order.status|uppercase}}

  {{if order.shipping_address}}
    Shipping to:
    {{:order.shipping_address.street}}
    {{:order.shipping_address.city}}, {{:order.shipping_address.state}} {{:order.shipping_address.zip}}
  {{endif}}
{{endfor}}

JSON Arrays and Objects

{{for category in categories}}
  <h2>{{:category.name}}</h2>

  {{if category.products}}
    <ul>
    {{for product in category.products}}
      <li>
        {{:product.name}} - {{:product.price|formatfloat,"$0.00"}}
        {{if product.on_sale}}
          <span class="sale">ON SALE!</span>
        {{endif}}
      </li>
    {{endfor}}
    </ul>
  {{else}}
    <p>No products in this category.</p>
  {{endif}}
{{endfor}}

9. Template Inheritance

Template inheritance allows you to create reusable layouts and component hierarchies.

Base Template (base.tpro)

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>{{block "title"}}Default Title{{endblock}}</title>
    <link rel="stylesheet" href="/css/style.css">
    {{block "extra_css"}}{{endblock}}
    <meta name="description" content="{{block "meta_description"}}Default description{{endblock}}">
</head>
<body>
    <header>
        <nav>
            {{block "navigation"}}
                <ul>
                    <li><a href="/">Home</a></li>
                    <li><a href="/about">About</a></li>
                    <li><a href="/contact">Contact</a></li>
                </ul>
            {{endblock}}
        </nav>
    </header>

    <main class="container">
        {{block "breadcrumbs"}}{{endblock}}

        <div class="content">
            {{block "content"}}
                <h1>Welcome</h1>
                <p>This is the default content.</p>
            {{endblock}}
        </div>

        <aside class="sidebar">
            {{block "sidebar"}}{{endblock}}
        </aside>
    </main>

    <footer>
        {{block "footer"}}
            <p>&copy; 2025 My Website. All rights reserved.</p>
        {{endblock}}
    </footer>

    {{block "extra_js"}}{{endblock}}
</body>
</html>

Child Template

{{extends "base.tpro"}}

{{block "title"}}{{:page_title}} - My Website{{endblock}}

{{block "meta_description"}}{{:page_description}}{{endblock}}

{{block "extra_css"}}
    <link rel="stylesheet" href="/css/products.css">
{{endblock}}

{{block "breadcrumbs"}}
    <nav aria-label="breadcrumb">
        <ol class="breadcrumb">
            <li><a href="/">Home</a></li>
            <li><a href="/products">Products</a></li>
            <li class="active">{{:category_name}}</li>
        </ol>
    </nav>
{{endblock}}

{{block "content"}}
    <h1>{{:category_name|capitalize}}</h1>
    <p>{{:category_description}}</p>

    {{if products}}
        <div class="product-grid">
            {{for product in products}}
                <div class="product-card">
                    <img src="{{:product.image_url}}" alt="{{:product.name}}">
                    <h3>{{:product.name}}</h3>
                    <p class="price">{{:product.price|formatfloat,"$#,##0.00"}}</p>

                    {{if product.discount|gt,0}}
                        <span class="discount">{{:product.discount}}% OFF</span>
                    {{endif}}

                    <button class="add-to-cart" data-product-id="{{:product.id}}">
                        Add to Cart
                    </button>
                </div>
            {{endfor}}
        </div>
    {{else}}
        <p class="no-products">No products found in this category.</p>
    {{endif}}
{{endblock}}

{{block "sidebar"}}
    <div class="category-sidebar">
        <h3>Categories</h3>
        <ul>
            {{for cat in all_categories}}
                <li>
                    <a href="/products/{{:cat.slug}}"
                       {{if cat.id|eq,current_category_id}}class="active"{{endif}}>
                        {{:cat.name}} ({{:cat.product_count}})
                    </a>
                </li>
            {{endfor}}
        </ul>
    </div>
{{endblock}}

{{block "extra_js"}}
    <script src="/js/products.js"></script>
{{endblock}}

Template Inheritance Rules

  1. Single-level inheritance: Child templates can only extend one parent template (no multi-level inheritance)
  2. Block overriding: If a child defines a block that exists in the parent, it overrides the parent’s block
  3. Default blocks: If a child doesn’t define a block, the parent’s default content is used
  4. Ignored content: Any content outside of blocks in child templates is ignored
  5. Unknown blocks: Child blocks not defined in the parent are ignored

10. Template Inclusion

Use the {{include}} directive to modularize templates by splitting them into reusable components.

Basic Inclusion

{{include "header.tpro"}}

<main>
    <!-- Main content here -->
    <h1>{{:page_title}}</h1>
    <p>{{:content}}</p>
</main>

{{include "footer.tpro"}}

Including with Subdirectories

{{include "partials/navigation.tpro"}}
{{include "components/product-card.tpro"}}
{{include "layouts/sidebar.tpro"}}

Modular Component Example

components/product-card.tpro:

<div class="product-card">
    <img src="{{:product.image}}" alt="{{:product.name|htmlencode}}">
    <h3>{{:product.name|htmlencode}}</h3>
    <p class="description">{{:product.description|trunc,100|htmlencode}}</p>

    <div class="price">
        {{if product.sale_price}}
            <span class="original-price">{{:product.price|formatfloat,"$0.00"}}</span>
            <span class="sale-price">{{:product.sale_price|formatfloat,"$0.00"}}</span>
        {{else}}
            <span class="price">{{:product.price|formatfloat,"$0.00"}}</span>
        {{endif}}
    </div>

    {{if product.in_stock}}
        <button class="btn-primary">Add to Cart</button>
    {{else}}
        <button class="btn-disabled" disabled>Out of Stock</button>
    {{endif}}
</div>

Main template:

{{include "components/header.tpro"}}

<div class="products-container">
    {{for product in products}}
        {{include "components/product-card.tpro"}}
    {{endfor}}
</div>

{{include "components/footer.tpro"}}

11. Working with Nullable Types

TemplatePro has excellent support for Delphi’s nullable types, handling them gracefully in templates.

Nullable Variables

{{if user.middle_name}}
    Full name: {{:user.first_name}} {{:user.middle_name}} {{:user.last_name}}
{{else}}
    Name: {{:user.first_name}} {{:user.last_name}}
{{endif}}

{{if order.shipped_date}}
    Shipped: {{:order.shipped_date|datetostr}}
{{else}}
    Status: Processing
{{endif}}

{{if product.weight}}
    Weight: {{:product.weight|formatfloat,"0.00"}} kg
{{endif}}

Nullable Numeric Values

{{if item.discount_percentage}}
    Discount: {{:item.discount_percentage|formatfloat,"0.0"}}%
    Final price: {{:item.final_price|formatfloat,"$0.00"}}
{{else}}
    Price: {{:item.price|formatfloat,"$0.00"}}
{{endif}}

12. Advanced Examples

E-commerce Product Listing

{{extends "shop-base.tpro"}}

{{block "title"}}{{:category.name}} Products - Online Store{{endblock}}

{{block "content"}}
    <div class="category-header">
        <h1>{{:category.name|capitalize}}</h1>
        <p>{{:category.description}}</p>

        {{if products}}
            <p class="product-count">
                Showing {{:products|count}} of {{:total_products}} products
            </p>
        {{endif}}
    </div>

    {{if products}}
        <div class="product-filters">
            <select name="sort" onchange="updateSort(this.value)">
                <option value="name">Sort by Name</option>
                <option value="price_low">Price: Low to High</option>
                <option value="price_high">Price: High to Low</option>
                <option value="rating">Highest Rated</option>
            </select>

            <div class="price-filter">
                <label>Max Price: {{:max_price|formatfloat,"$0"}}</label>
            </div>
        </div>

        <div class="products-grid">
            {{for product in products}}
                <article class="product-item" data-product-id="{{:product.id}}">
                    <div class="product-image">
                        <img src="{{:product.main_image.url}}"
                             alt="{{:product.name|htmlencode}}"
                             loading="lazy">

                        {{if product.badge}}
                            <span class="product-badge {{:product.badge.type}}">
                                {{:product.badge.text|uppercase}}
                            </span>
                        {{endif}}
                    </div>

                    <div class="product-info">
                        <h3>
                            <a href="/product/{{:product.slug}}">
                                {{:product.name|htmlencode}}
                            </a>
                        </h3>

                        <div class="product-rating">
                            {{for star in product.rating_stars}}
                                <span class="star {{if star.filled}}filled{{endif}}">★</span>
                            {{endfor}}
                            <span class="rating-count">({{:product.review_count}})</span>
                        </div>

                        <p class="product-summary">
                            {{:product.short_description|trunc,100|htmlencode}}
                        </p>

                        <div class="product-price">
                            {{if product.sale_price}}
                                <span class="original-price">
                                    {{:product.original_price|formatfloat,"$#,##0.00"}}
                                </span>
                                <span class="sale-price">
                                    {{:product.sale_price|formatfloat,"$#,##0.00"}}
                                </span>
                                <span class="savings">
                                    Save {{:product.savings|formatfloat,"$0.00"}}
                                </span>
                            {{else}}
                                <span class="price">
                                    {{:product.price|formatfloat,"$#,##0.00"}}
                                </span>
                            {{endif}}
                        </div>

                        <div class="product-actions">
                            {{if product.in_stock}}
                                <button class="btn-add-cart"
                                        data-product="{{:product.id}}"
                                        onclick="addToCart(this)">
                                    Add to Cart
                                </button>

                                {{if product.stock_level|le,5}}
                                    <small class="stock-warning">
                                        Only {{:product.stock_level}} left!
                                    </small>
                                {{endif}}
                            {{else}}
                                <button class="btn-out-of-stock" disabled>
                                    Out of Stock
                                </button>
                                <button class="btn-notify"
                                        onclick="notifyWhenAvailable('{{:product.id}}')">
                                    Notify When Available
                                </button>
                            {{endif}}
                        </div>

                        {{if product.features}}
                            <div class="product-features">
                                {{for feature in product.features}}
                                    <span class="feature-tag">{{:feature.name}}</span>
                                {{endfor}}
                            </div>
                        {{endif}}
                    </div>
                </article>
            {{endfor}}
        </div>

        {{if has_more_products}}
            <div class="pagination">
                {{if current_page|gt,1}}
                    <a href="?page={{:prev_page}}" class="page-link">Previous</a>
                {{endif}}

                {{for page in page_numbers}}
                    {{if page.current}}
                        <span class="page-current">{{:page.number}}</span>
                    {{else}}
                        <a href="?page={{:page.number}}" class="page-link">
                            {{:page.number}}
                        </a>
                    {{endif}}
                {{endfor}}

                {{if current_page|lt,total_pages}}
                    <a href="?page={{:next_page}}" class="page-link">Next</a>
                {{endif}}
            </div>
        {{endif}}
    {{else}}
        <div class="no-products">
            <h2>No products found</h2>
            <p>Try adjusting your search criteria or browse our other categories.</p>

            <div class="suggested-categories">
                <h3>You might be interested in:</h3>
                <ul>
                    {{for suggestion in suggested_categories}}
                        <li>
                            <a href="/category/{{:suggestion.slug}}">
                                {{:suggestion.name}} ({{:suggestion.product_count}} products)
                            </a>
                        </li>
                    {{endfor}}
                </ul>
            </div>
        </div>
    {{endif}}
{{endblock}}

Email Template Example

{{extends "email-base.tpro"}}

{{block "subject"}}Order Confirmation #{{:order.number}}{{endblock}}

{{block "preheader"}}Your order has been confirmed and will be shipped soon.{{endblock}}

{{block "content"}}
    <div class="email-header">
        <h1>Thank you for your order!</h1>
        <p>Hi {{:customer.first_name}}, your order has been confirmed.</p>
    </div>

    <div class="order-details">
        <h2>Order Summary</h2>
        <table class="order-table">
            <thead>
                <tr>
                    <th>Item</th>
                    <th>Quantity</th>
                    <th>Price</th>
                    <th>Total</th>
                </tr>
            </thead>
            <tbody>
                {{for item in order.items}}
                    <tr>
                        <td>
                            <strong>{{:item.product.name}}</strong>
                            {{if item.variant}}
                                <br><small>{{:item.variant.name}}</small>
                            {{endif}}
                        </td>
                        <td>{{:item.quantity}}</td>
                        <td>{{:item.unit_price|formatfloat,"$0.00"}}</td>
                        <td>{{:item.total_price|formatfloat,"$0.00"}}</td>
                    </tr>
                {{endfor}}
            </tbody>
            <tfoot>
                <tr>
                    <td colspan="3"><strong>Subtotal:</strong></td>
                    <td><strong>{{:order.subtotal|formatfloat,"$0.00"}}</strong></td>
                </tr>
                {{if order.discount_amount|gt,0}}
                    <tr>
                        <td colspan="3">Discount ({{:order.discount_code}}):</td>
                        <td>-{{:order.discount_amount|formatfloat,"$0.00"}}</td>
                    </tr>
                {{endif}}
                <tr>
                    <td colspan="3">Shipping:</td>
                    <td>{{:order.shipping_cost|formatfloat,"$0.00"}}</td>
                </tr>
                <tr>
                    <td colspan="3">Tax:</td>
                    <td>{{:order.tax_amount|formatfloat,"$0.00"}}</td>
                </tr>
                <tr class="total-row">
                    <td colspan="3"><strong>Total:</strong></td>
                    <td><strong>{{:order.total|formatfloat,"$0.00"}}</strong></td>
                </tr>
            </tfoot>
        </table>
    </div>

    <div class="shipping-info">
        <h3>Shipping Information</h3>
        <p>
            <strong>{{:shipping.address.name}}</strong><br>
            {{:shipping.address.street1}}<br>
            {{if shipping.address.street2}}
                {{:shipping.address.street2}}<br>
            {{endif}}
            {{:shipping.address.city}}, {{:shipping.address.state}} {{:shipping.address.zip}}<br>
            {{:shipping.address.country}}
        </p>

        <p>
            <strong>Shipping Method:</strong> {{:shipping.method.name}}<br>
            <strong>Estimated Delivery:</strong> {{:shipping.estimated_delivery|datetostr,"MMM dd, yyyy"}}
        </p>
    </div>

    <div class="next-steps">
        <h3>What happens next?</h3>
        <ol>
            <li>We'll prepare your order for shipping</li>
            <li>You'll receive a tracking number when your order ships</li>
            <li>Your order will be delivered by {{:shipping.estimated_delivery|datetostr,"MMM dd"}}</li>
        </ol>
    </div>

    <div class="customer-service">
        <p>
            Questions about your order? Contact our customer service team at
            <a href="mailto:{{:company.support_email}}">{{:company.support_email}}</a>
            or call {{:company.phone}}.
        </p>
    </div>
{{endblock}}

13. Performance Tips and Best Practices

Template Compilation

  • Always compile templates once and reuse the compiled version
  • Save compiled templates to files for better startup performance
  • Use appropriate caching strategies for production environments

Filter Usage

  • Use built-in filters when possible (they’re optimized)
  • Minimize complex custom filters in loops
  • Consider pre-processing data instead of complex template logic

JSON Data Handling

  • Structure your JSON data to minimize nested loops
  • Use appropriate data types to leverage TemplatePro’s type system
  • Consider paginating large datasets

Template Organization

  • Use template inheritance for consistent layouts
  • Break large templates into smaller, reusable components
  • Organize templates in logical directory structures

Error Handling

try
  lCompiledTemplate := lCompiler.Compile(lTemplate);
  lResult := lCompiledTemplate.Render;
except
  on E: ETProCompilerException do
    // Handle compilation errors
    ShowMessage('Template compilation error: ' + E.Message);
  on E: ETProRenderException do
    // Handle rendering errors
    ShowMessage('Template rendering error: ' + E.Message);
  on E: Exception do
    // Handle other errors
    ShowMessage('Unexpected error: ' + E.Message);
end;

Conclusion

TemplatePro is a comprehensive and powerful templating engine that provides developers with the flexibility and control needed for modern application development. Its rich feature set includes:

  • Intuitive Syntax: Clean, readable template syntax inspired by popular engines
  • Powerful Logic: Comprehensive conditional statements and looping constructs
  • Template Inheritance: Reusable layouts and component hierarchies
  • Extensive Filters: Rich set of built-in filters plus custom filter support
  • JSON Excellence: First-class support for complex JSON data structures
  • Type Safety: Excellent handling of Delphi’s type system including nullables
  • Modular Design: Template inclusion and component-based development
  • Production Ready: Performance-optimized compilation and rendering

Whether you’re building simple HTML emails, complex web applications, or dynamic report generation systems, TemplatePro provides the tools you need to create maintainable, efficient, and powerful templates.

The engine’s ability to handle everything from basic variable interpolation to complex data transformations, combined with its robust error handling and performance optimizations, makes it an excellent choice for Delphi developers seeking a modern templating solution.

📚 Check unit tests for comprehensive examples and edge cases.

Current Version: 0.7.3 - Updated January 2025

Comments

comments powered by Disqus