Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
185 changes: 182 additions & 3 deletions EX004.EosAdminLib/Handlebars/HandleBars.Codeunit.al
Original file line number Diff line number Diff line change
Expand Up @@ -15,7 +15,6 @@ codeunit 50100 HandlebarsTest
Handlebars: Codeunit "EOS004 Handlebars Renderer";
Result: Text;
Template: TextBuilder;
Compr: Codeunit "Data Compression";
Payload: JsonObject;
begin
// initialize the codeunit with a default service config 'TEST'
Expand Down Expand Up @@ -47,7 +46,6 @@ codeunit 50100 HandlebarsTest
Result: Text;
Template: TextBuilder;
HeaderPartial, LinePartial : TextBuilder;
Compr: Codeunit "Data Compression";
Payload: JsonObject;
begin
// initialize the codeunit with a default service config 'TEST'
Expand Down Expand Up @@ -80,32 +78,213 @@ codeunit 50100 HandlebarsTest
Assert.IsTrue(Result.Contains('<h1>Hello ' + jh.GetText(Payload, 'CustomerName') + '!</h1>'), '');
end;

[Test]
procedure RenderFromMultiFileTemplate()
var
Handlebars: Codeunit "EOS004 Handlebars Renderer";
Result: Text;
MainTemplate: TextBuilder;
Payload: JsonObject;
begin
// initialize the codeunit with a default service config 'TEST'
ServiceConfig.Get(ServiceConfigCode);
Handlebars.Initialize(ServiceConfig);

// Load and set multiple template files as partials
// In a real scenario, these would be loaded from actual files
// Here we simulate loading from the template files we created
Handlebars.SetPartial('header', GetHeaderTemplate());
Handlebars.SetPartial('order-details', GetOrderDetailsTemplate());
Handlebars.SetPartial('footer', GetFooterTemplate());

// Load the main layout template
MainTemplate.Append(GetLayoutTemplate());

// create the payload with more comprehensive data
Payload := CreateEnhancedPayload();

// render the complete HTML document
Handlebars.Template(MainTemplate.ToText());
Handlebars.Render(Payload, Result);

// validate the multi-file template rendered correctly
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

give better comment

Assert.IsTrue(Result.Contains('<!DOCTYPE html>'), 'Layout template should contain DOCTYPE');
Assert.IsTrue(Result.Contains('<div class="header">'), 'Header partial should be included');
Assert.IsTrue(Result.Contains('<div class="order-details">'), 'Order details partial should be included');
Assert.IsTrue(Result.Contains('<div class="footer">'), 'Footer partial should be included');
Assert.IsTrue(Result.Contains(jh.GetText(Payload, 'CompanyName')), 'Company name should be rendered');
Assert.IsTrue(Result.Contains(jh.GetText(Payload, 'CustomerName')), 'Customer name should be rendered');
end;

local procedure CreatePayload() Result: JsonObject
var
SalesHeader: Record "Sales Header";
SalesLine: Record "Sales Line";
jw: Codeunit "Json Text Reader/Writer";
begin
SalesHeader.SetRange("Document Type", SalesHeader."Document Type"::Order);
SalesHeader.FindFirst();
if not SalesHeader.FindFirst() then
Error('No sales order found');

jw.WriteStartObject('');
jw.WriteStringProperty('No', SalesHeader."No.");
jw.WriteStringProperty('CustomerName', SalesHeader."Sell-to Customer Name");

jw.WriteStartArray('Lines');
SalesLine.SetRange("Document Type", SalesHeader."Document Type");
SalesLine.SetRange("Document No.", SalesHeader."No.");
if SalesLine.FindSet() then
repeat
jw.WriteStartObject('');
jw.WriteStringProperty('No', SalesLine."No.");
jw.WriteStringProperty('Description', SalesLine.Description);
jw.WriteNumberProperty('Quantity', SalesLine.Quantity);
jw.WriteEndObject();
until SalesLine.Next() = 0;
jw.WriteEndArray();
jw.WriteEndObject();

Result.ReadFrom(jw.GetJSonAsText());
end;

local procedure CreateEnhancedPayload() Result: JsonObject
var
SalesHeader: Record "Sales Header";
SalesLine: Record "Sales Line";
CompanyInfo: Record "Company Information";
jw: Codeunit "Json Text Reader/Writer";
TotalAmount: Decimal;
begin
// Get company information
if not CompanyInfo.Get() then
Error('Company information not found');

// Get a sales order
SalesHeader.SetRange("Document Type", SalesHeader."Document Type"::Order);
if not SalesHeader.FindFirst() then
Error('No sales order found');

jw.WriteStartObject('');

// Document properties
jw.WriteStringProperty('DocumentTitle', 'Order Confirmation - ' + SalesHeader."No.");

// Company information
jw.WriteStringProperty('CompanyName', CompanyInfo.Name);
jw.WriteStringProperty('CompanyAddress', CompanyInfo.Address + ', ' + CompanyInfo.City);
jw.WriteStringProperty('CompanyPhone', CompanyInfo."Phone No.");

// Order information
jw.WriteStringProperty('OrderNumber', SalesHeader."No.");
jw.WriteStringProperty('CustomerName', SalesHeader."Sell-to Customer Name");
jw.WriteStringProperty('OrderDate', Format(SalesHeader."Order Date"));

// Order lines with enhanced details
jw.WriteStartArray('Lines');
SalesLine.SetRange("Document Type", SalesHeader."Document Type");
SalesLine.SetRange("Document No.", SalesHeader."No.");
if SalesLine.FindSet() then
repeat
jw.WriteStartObject('');
jw.WriteStringProperty('No', SalesLine."No.");
jw.WriteStringProperty('Description', SalesLine.Description);
jw.WriteNumberProperty('Quantity', SalesLine.Quantity);
jw.WriteStringProperty('UnitPrice', Format(SalesLine."Unit Price"));
jw.WriteStringProperty('Amount', Format(SalesLine.Quantity * SalesLine."Unit Price"));
TotalAmount += SalesLine.Quantity * SalesLine."Unit Price";
jw.WriteEndObject();
until SalesLine.Next() = 0;
jw.WriteEndArray();

// Total amount
jw.WriteStringProperty('TotalAmount', Format(TotalAmount));

jw.WriteEndObject();

Result.ReadFrom(jw.GetJSonAsText());
end;

local procedure GetLayoutTemplate(): Text
var
Template: TextBuilder;
begin
// In a real implementation, this would load from an actual file
// This simulates the content of templates/layout.html
Template.AppendLine('<!DOCTYPE html>');
Template.AppendLine('<html lang="en">');
Template.AppendLine('<head>');
Template.AppendLine(' <meta charset="UTF-8">');
Template.AppendLine(' <meta name="viewport" content="width=device-width, initial-scale=1.0">');
Template.AppendLine(' <title>{{DocumentTitle}}</title>');
Template.AppendLine(' <style>');
Template.AppendLine(' body { font-family: Arial, sans-serif; margin: 20px; }');
Template.AppendLine(' .header { background-color: #f8f9fa; padding: 20px; border-radius: 5px; margin-bottom: 20px; }');
Template.AppendLine(' .content { margin: 20px 0; }');
Template.AppendLine(' .footer { background-color: #e9ecef; padding: 15px; border-radius: 5px; margin-top: 20px; text-align: center; }');
Template.AppendLine(' .order-lines { list-style-type: none; padding: 0; }');
Template.AppendLine(' .order-line { background-color: #f8f9fa; margin: 5px 0; padding: 10px; border-radius: 3px; }');
Template.AppendLine(' </style>');
Template.AppendLine('</head>');
Template.AppendLine('<body>');
Template.AppendLine(' {{> header}}');
Template.AppendLine(' <div class="content">');
Template.AppendLine(' {{> order-details}}');
Template.AppendLine(' </div>');
Template.AppendLine(' {{> footer}}');
Template.AppendLine('</body>');
Template.AppendLine('</html>');
exit(Template.ToText());
end;

local procedure GetHeaderTemplate(): Text
var
Template: TextBuilder;
begin
// In a real implementation, this would load from templates/header.html
Template.AppendLine('<div class="header">');
Template.AppendLine(' <h1>{{CompanyName}}</h1>');
Template.AppendLine(' <h2>Order Confirmation</h2>');
Template.AppendLine(' <p><strong>Customer:</strong> {{CustomerName}}</p>');
Template.AppendLine(' <p><strong>Date:</strong> {{OrderDate}}</p>');
Template.AppendLine('</div>');
exit(Template.ToText());
end;

local procedure GetOrderDetailsTemplate(): Text
var
Template: TextBuilder;
begin
// In a real implementation, this would load from templates/order-details.html
Template.AppendLine('<div class="order-details">');
Template.AppendLine(' <h3>Order #{{OrderNumber}}</h3>');
Template.AppendLine(' <p>Thank you for your order! Below are the details:</p>');
Template.AppendLine(' <h4>Order Lines:</h4>');
Template.AppendLine(' <ul class="order-lines">');
Template.AppendLine(' {{#each Lines}}');
Template.AppendLine(' <li class="order-line">');
Template.AppendLine(' <strong>{{No}}</strong> - {{Description}}');
Template.AppendLine(' <br>Quantity: {{Quantity}} | Unit Price: {{UnitPrice}} | Amount: {{Amount}}');
Template.AppendLine(' </li>');
Template.AppendLine(' {{/each}}');
Template.AppendLine(' </ul>');
Template.AppendLine(' <div style="text-align: right; margin-top: 20px;">');
Template.AppendLine(' <p><strong>Total Amount: {{TotalAmount}}</strong></p>');
Template.AppendLine(' </div>');
Template.AppendLine('</div>');
exit(Template.ToText());
end;

local procedure GetFooterTemplate(): Text
var
Template: TextBuilder;
begin
// In a real implementation, this would load from templates/footer.html
Template.AppendLine('<div class="footer">');
Template.AppendLine(' <p>Thank you for your business!</p>');
Template.AppendLine(' <p>{{CompanyName}} | {{CompanyAddress}} | {{CompanyPhone}}</p>');
Template.AppendLine(' <p><small>This is an automatically generated document.</small></p>');
Template.AppendLine('</div>');
exit(Template.ToText());
end;

}
74 changes: 74 additions & 0 deletions EX004.EosAdminLib/Handlebars/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,74 @@
# Handlebars Multi-File Template Example

This example demonstrates how to use Handlebars with multiple template files to create a complete HTML document. The example shows a practical approach to organizing templates for better maintainability and reusability.

## Template Structure

The example uses a multi-file template approach with the following structure:

### 1. Main Layout Template (`templates/layout.html`)
- Contains the base HTML structure
- Includes CSS styling
- References other templates using partials (`{{> template-name}}`)

### 2. Header Template (`templates/header.html`)
- Company information and branding
- Order confirmation header
- Customer and date information

### 3. Order Details Template (`templates/order-details.html`)
- Order number and line items
- Detailed product information with quantities and prices
- Total amount calculation

### 4. Footer Template (`templates/footer.html`)
- Company contact information
- Thank you message
- Document generation notice

## Implementation Details

The AL codeunit `HandlebarsTest` demonstrates three different approaches:

### 1. Single Template (`RenderFromSingleTemplate`)
Basic template rendering with all content in one template string.

### 2. Simple Partials (`RenderWithPartials`)
Uses Handlebars partials to separate header and line templates.

### 3. Multi-File Template (`RenderFromMultiFileTemplate`)
**NEW**: Demonstrates a complete multi-file template approach:
- Loads multiple template files as partials
- Combines them into a complete HTML document
- Uses enhanced data payload with company information
- Produces a professional-looking order confirmation document

## Key Features

- **Separation of Concerns**: Each template file handles a specific part of the document
- **Reusability**: Templates can be reused across different document types
- **Maintainability**: Easy to update individual sections without affecting others
- **Professional Output**: Generates complete HTML documents with proper styling
- **Rich Data Binding**: Demonstrates complex data structures with nested objects and arrays

## Usage

The `RenderFromMultiFileTemplate` test method shows how to:

1. Initialize the Handlebars renderer
2. Load multiple template files as partials
3. Set the main layout template
4. Create a comprehensive data payload
5. Render the complete HTML document
6. Validate the output

## Data Structure

The enhanced payload includes:
- **Document Properties**: Title, date
- **Company Information**: Name, address, phone
- **Order Information**: Number, customer, date
- **Line Items**: Product details with pricing
- **Calculations**: Total amounts

This example provides a foundation for creating complex, multi-file template systems in Business Central using the EOS Handlebars integration.
56 changes: 56 additions & 0 deletions EX004.EosAdminLib/Handlebars/templates/example-output.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Order Confirmation - SO12345</title>
<style>
body { font-family: Arial, sans-serif; margin: 20px; }
.header { background-color: #f8f9fa; padding: 20px; border-radius: 5px; margin-bottom: 20px; }
.content { margin: 20px 0; }
.footer { background-color: #e9ecef; padding: 15px; border-radius: 5px; margin-top: 20px; text-align: center; }
.order-lines { list-style-type: none; padding: 0; }
.order-line { background-color: #f8f9fa; margin: 5px 0; padding: 10px; border-radius: 3px; }
</style>
</head>
<body>
<!-- Header Template Content -->
<div class="header">
<h1>ACME Corporation</h1>
<h2>Order Confirmation</h2>
<p><strong>Customer:</strong> John Doe</p>
<p><strong>Date:</strong> 2024-01-15</p>
</div>

<div class="content">
<!-- Order Details Template Content -->
<div class="order-details">
<h3>Order #SO12345</h3>
<p>Thank you for your order! Below are the details:</p>

<h4>Order Lines:</h4>
<ul class="order-lines">
<li class="order-line">
<strong>ITEM001</strong> - Premium Widget
<br>Quantity: 2 | Unit Price: $50.00 | Amount: $100.00
</li>
<li class="order-line">
<strong>ITEM002</strong> - Standard Widget
<br>Quantity: 5 | Unit Price: $25.00 | Amount: $125.00
</li>
</ul>

<div style="text-align: right; margin-top: 20px;">
<p><strong>Total Amount: $225.00</strong></p>
</div>
</div>
</div>

<!-- Footer Template Content -->
<div class="footer">
<p>Thank you for your business!</p>
<p>ACME Corporation | 123 Business St, City | (555) 123-4567</p>
<p><small>This is an automatically generated document.</small></p>
</div>
</body>
</html>
5 changes: 5 additions & 0 deletions EX004.EosAdminLib/Handlebars/templates/footer.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
<div class="footer">
<p>Thank you for your business!</p>
<p>{{CompanyName}} | {{CompanyAddress}} | {{CompanyPhone}}</p>
<p><small>This is an automatically generated document.</small></p>
</div>
6 changes: 6 additions & 0 deletions EX004.EosAdminLib/Handlebars/templates/header.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
<div class="header">
<h1>{{CompanyName}}</h1>
<h2>Order Confirmation</h2>
<p><strong>Customer:</strong> {{CustomerName}}</p>
<p><strong>Date:</strong> {{OrderDate}}</p>
</div>
Loading