Welcome Back TemplatePro
⭐ UPDATE (sept 2024): Updated official documentation about TemplatePRO is available here
Back in 2016, I embarked on a small experiment: I started developing a simple template language. At the time, I was going through a “funny phase,” and my intention wasn’t to build a full-fledged template language. It was more of a personal challenge, an exploration with no clear end goal in sight. Fast forward to today, the landscape of web development has significantly evolved. The surge in popularity of HTMX has challenged the long-standing dominance of single-page applications (SPAs) as the only viable approach to building serious web applications. In the world of DMVCFramework, we now have three template language integrations:
- mustache (Integrated with dmustache by synopse)
- embedded Lua (Integrated the Lua scripting language)
- Sempare (leveraging the very good Sempare Template Engine by Conrad Vermeulen)
While these integrations offer valuable options, I’ve never been completely satisfied with any of them for all use cases within DMVCFramework. None of these solutions fully met the specific needs I envisioned for the template language distributed by default with DMVCFramework, especially when building high-performance, cross-platform web applications.
👉🏼 Just to be clear, current solutions are great (especially the Sempare template language is very powerful) but I want something different for the default DMVCFramework template language, as explained in the next paragraph.
The Case with Existing Template Engines
Each of the existing template engines had strengths, but they also had limitations that kept me searching for something better. Here are some of the requirements that I felt were essential for the ideal DMVCFramework template engine:
- Permissive Open Source License: A license that allows use in commercial projects without restrictions (e.g., not GPL-only).
- Cross-Platform Compatibility: It must support Delphi across platforms like Win32, Win64, Linux, and Android.
- Simple Distribution: The engine should require just a single unit to be added to the project, avoiding dependency bloat.
- Performance: It should be optimized for performance with native Delphi strings or PChar.
- Zero External Dependencies: No external dynamic libraries (.dll or .so files) should be required.
- Delphi Object Support: Direct access to Delphi objects, not just JSON or string data.
- JSON Objects Compatibility: Built-in support for TJSONObject, not just raw JSON strings.
- Object Lists as Data Sources: Seamless support for lists of objects, useful when working with MVCActiveRecord.
- Template Reusability: The ability to reuse templates via includes, partials, etc.
- Active Development: The template engine must be actively maintained and supported.
Some of these requirements were non-negotiable, while others were more flexible. But the bottom line was clear: I wanted a template engine tailored for DMVCFramework’s needs. After not finding a perfect solution, I realized I had to create it.
Introducing TemplatePro
I’m excited to introduce TemplatePro, a completely rewritten template language designed specifically for DMVCFramework. Starting with dmvcframework-3.4.2-magnesium-rc3, TemplatePro will be included as the default template engine in DMVCFramework. However, just like LoggerPro, TemplatePro is not confined to DMVCFramework projects; it can be used in any Delphi application that requires a robust and efficient templating system.
TemplatePro is built to be:
- Fast: It features a highly efficient compiler and runtime engine optimized for Delphi applications.
- Extensible: It allows custom filters and functionality to be added easily.
- Cross-Platform: It supports all major Delphi targets, including Windows, Linux, and Android.
- Lightweight: With minimal dependencies, TemplatePro can be integrated into your project without unnecessary bloat.
You can find TemplatePro’s source code and documentation in its own repository here.
Sample Code Using TemplatePro
Below is a practical example demonstrating how to use TemplatePro within a console application. This example shows how to loop through a dataset, use conditional statements, apply filters, and work with JSON objects:
program tproconsole;
{$APPTYPE CONSOLE}
{$R *.res}
uses
System.SysUtils, TemplatePro, Data.DB, FireDAC.Stan.Intf, FireDAC.Stan.Option,
FireDAC.Stan.Param, FireDAC.Stan.Error, FireDAC.DatS, FireDAC.Phys.Intf,
FireDAC.DApt.Intf, FireDAC.Comp.DataSet, FireDAC.Comp.Client, System.Rtti, JsonDataObjects;
function GetPeopleDataset: TDataSet;
var
lMT: TFDMemTable;
begin
lMT := TFDMemTable.Create(nil);
try
lMT.FieldDefs.Clear;
lMT.FieldDefs.Add('id', ftInteger);
lMT.FieldDefs.Add('first_name', ftString, 20);
lMT.FieldDefs.Add('last_name', ftString, 20);
lMT.Active := True;
lMT.AppendRecord([1, 'Daniele', 'Teti']);
lMT.AppendRecord([2, 'Peter', 'Parker']);
lMT.AppendRecord([3, 'Bruce', 'Banner']);
lMT.AppendRecord([4, 'Scott', 'Summers']);
lMT.AppendRecord([5, 'Sue', 'Storm']);
lMT.First;
Result := lMT;
except
lMT.Free;
raise;
end;
end;
function GetJSON: TJSONObject;
var
ChildObj: TJsonObject;
begin
Result := TJsonObject.Create;
Result.I['foo'] := 123;
Result.S['bar'] := 'hello';
ChildObj := Result.A['myarray'].AddObject;
ChildObj.S['first_name'] := 'Daniele';
ChildObj.S['last_name'] := 'Teti';
ChildObj := ChildObj.O['company'];
ChildObj.S['name'] := 'bit Time Professionals';
ChildObj.S['country'] := 'ITALY';
ChildObj := Result.A['myarray'].AddObject;
ChildObj.S['first_name'] := 'Bruce';
ChildObj.S['last_name'] := 'Banner';
ChildObj := ChildObj.O['company'];
ChildObj.S['name'] := 'Green Power';
ChildObj.S['country'] := 'CA';
end;
function AddSquareBrackets(const aValue: TValue; const aParameters: TArray<string>): string;
begin
Result := '[' + aValue.AsString + ']';
end;
procedure Main;
begin
var lCompiler := TTProCompiler.Create();
try
var lTemplate :=
'Simple variable: ' + sLineBreak +
'{{:variable1}} ' + sLineBreak +
' ' + sLineBreak +
'Using loops: ' + sLineBreak +
'{{for person in people}} ' + sLineBreak +
' - {{:person.id}}) {{:person.first_name}}, {{:person.last_name}} ' + sLineBreak +
'{{endfor}} ' + sLineBreak +
' ' + sLineBreak +
'Using if statement: ' + sLineBreak +
'{{if people }}People dataset contains records{{endif}} ' + sLineBreak +
'{{if !people }}People dataset doesn''t contain records{{endif}} ' + sLineBreak +
' ' + sLineBreak +
' ' + sLineBreak +
'Using filters: ' + sLineBreak +
'uppercase: {{:variable1|uppercase}} ' + sLineBreak +
'lowercase: {{:variable1|lowercase}} ' + sLineBreak +
'lpad : {{:variable1|lpad,20,"*"}} ' + sLineBreak +
'rpad : {{:variable1|rpad,20,"*"}} ' + sLineBreak +
' ' + sLineBreak +
'Using custom filters: ' + sLineBreak +
'brackets : {{:variable1|brackets}} ' + sLineBreak +
' ' + sLineBreak +
'Using json objects: ' + sLineBreak +
'{{:jobj.foo}} ' + sLineBreak +
'{{:jobj.bar}} ' + sLineBreak +
'{{""|lpad,40,"_"}} ' + sLineBreak +
'{{for item in jobj.myarray}} ' + sLineBreak +
' - {{:item.first_name}} {{:item.last_name}} ' + sLineBreak +
' {{:item.company.name}} - {{:item.company.country}} ' + sLineBreak +
'{{endfor}} ' + sLineBreak;
var lCompiledTemplate := lCompiler.Compile(lTemplate);
var lPeopleDataset := GetPeopleDataset;
try
var lJObj := GetJSON;
try
lCompiledTemplate.AddFilter('brackets', AddSquareBrackets);
lCompiledTemplate.SetData('variable1', 'Daniele Teti');
lCompiledTemplate.SetData('people', lPeopleDataset);
lCompiledTemplate.SetData('jobj', lJObj);
WriteLn(lCompiledTemplate.Render);
finally
lJObj.Free;
end;
finally
lPeopleDataset.Free;
end;
finally
lCompiler.Free;
end;
end;
begin
try
Main;
except
on E: Exception do
begin
Writeln(E.ClassName, ': ', E.Message);
end;
end;
Writeln;
Writeln('Press ENTER to continue...');
ReadLn;
end.
The previous code generated the following output:
Simple variable:
Daniele Teti
Using loops:
- 1) Daniele, Teti
- 2) Peter, Parker
- 3) Bruce, Banner
- 4) Scott, Summers
- 5) Sue, Storm
Using if statement:
People dataset contains records
Using filters:
uppercase: DANIELE TETI
lowercase: daniele teti
lpad : ********Daniele Teti
rpad : Daniele Teti********
Using custom filters:
brackets : [Daniele Teti]
Using json objects:
123
hello
________________________________________
- Daniele Teti
bit Time Professionals - ITALY
- Bruce Banner
Green Power - CA
Work in Progress
While TemplatePro is now available and ready for use, today it’s still a work in progress. We’re constantly improving and optimizing the language. Your feedback will be invaluable in shaping the future of TemplatePro, so please don’t hesitate to share your thoughts.
Update (2024-08-31) TemplatePro is stable and can be used in production systems. Check the official documentations.
Current Integrations
Several DMVCFramework samples have already been updated to use TemplatePro:
More examples and detailed documentation will follow soon. As usual, I’ll be writing a comprehensive guide for DMVCFramework PATREON supporters. If you’re interested in learning more about TemplatePro and its integration with DMVCFramework, consider becoming a supporter.
Comments
comments powered by Disqus