Articles Report Designer – Scripting Guide
Table of Contents
- Introduction to Scripting
- The Script Editor
- PascalScript Basics
- Common Scripting Tasks
- Report Events
- Accessing Report Objects
- Working with Data
- Complete Examples
Introduction to Scripting
Articles uses PascalScript for report scripting. This is a Pascal-like scripting language that runs inside the report engine. Scripts allow you to:
- Control report behavior dynamically
- Validate user input in dialogs
- Calculate values on the fly
- Conditionally format report elements
- Manipulate datasets and queries
- Control report flow and execution
Key Points:
- Scripts are embedded in the report file (.fr3)
- Scripts run at report execution time, not design time
- Each report object can have its own event handlers
- Scripts have access to all report objects, datasets, and variables
The Script Editor
Accessing the Script Editor
There are several ways to open the script editor:
Method 1: Main Menu
- In the Articles Designer, click Report โ Code
- The script window opens showing all report code
Method 2: Events Tab
- Select any report object (band, memo, dialog control, etc.)
- Click the Events tab in the Object Inspector
- Double-click any event (e.g., OnBeforePrint, OnClick)
- The script editor opens with the event handler created
Method 3: Toolbar
- Click the Code button on the designer toolbar
The Script Window Layout
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
โ File Edit Search Run โ Menu bar
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ [โถ Run] [โ Stop] [Objects โผ] โ Toolbar
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ โ
โ Script Editor (code area) โ Main editing area
โ โ
โ procedure MemoHeaderOnBeforePrint... โ
โ begin โ
โ ... โ
โ โ
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโค
โ Messages / Errors โ Output panel
โโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโโ
Key Features:
- Syntax highlighting: Keywords are colored for readability
- Code completion: Press Ctrl+Space to see available properties/methods
- Object dropdown: Quick navigation to different event handlers
- Run button: Test your script before running the full report
- Error messages: Shows compilation errors with line numbers
Script Organization
All scripts are contained in one code unit per report:
begin
// Global initialization code (runs once at report start)
end.
// Event handlers appear below
procedure Page1OnBeforePrint(Sender: TfrxComponent);
begin
// Page-level code
end;
procedure MemoHeaderOnBeforePrint(Sender: TfrxComponent);
begin
// Memo-level code
end;
procedure DialogPage1OnShow(Sender: TfrxComponent);
begin
// Dialog code
end;
PascalScript Basics
Variable Declaration
Variables must be declared at the top of each procedure:
procedure Page1OnBeforePrint(Sender: TfrxComponent);
var
CustomerName: String;
TotalAmount: Double;
RecordCount: Integer;
IsValid: Boolean;
begin
CustomerName := 'Acme Corp';
TotalAmount := 1500.50;
RecordCount := 10;
IsValid := True;
end;
Data Types
Common types:
var
S: String; // Text
I: Integer; // Whole numbers
D: Double; // Decimal numbers
B: Boolean; // True/False
DT: TDateTime; // Dates and times
V: Variant; // Any type (flexible but slower)
Operators
Arithmetic:
Result := 10 + 5; // Addition: 15
Result := 10 - 5; // Subtraction: 5
Result := 10 * 5; // Multiplication: 50
Result := 10 / 5; // Division: 2.0
Result := 10 div 5; // Integer division: 2
Result := 10 mod 3; // Modulus (remainder): 1
Comparison:
if Amount > 100 then ... // Greater than
if Amount < 100 then ... // Less than
if Amount >= 100 then ... // Greater or equal
if Amount <= 100 then ... // Less or equal
if Amount = 100 then ... // Equal
if Amount <> 100 then ... // Not equal
Logical:
if (Age > 18) and (Age < 65) then ... // AND
if (Status = 'A') or (Status = 'P') then ... // OR
if not IsProcessed then ... // NOT
Control Structures
If-Then-Else:
if Amount > 1000 then
Memo1.Text := 'High Value'
else if Amount > 500 then
Memo1.Text := 'Medium Value'
else
Memo1.Text := 'Low Value';
Case Statement:
case Status of
'A': Memo1.Text := 'Active';
'I': Memo1.Text := 'Inactive';
'P': Memo1.Text := 'Pending';
else
Memo1.Text := 'Unknown';
end;
For Loop:
var
I: Integer;
Total: Double;
begin
Total := 0;
for I := 1 to 10 do
Total := Total + I;
end;
While Loop:
var
Counter: Integer;
begin
Counter := 0;
while Counter < 10 do
begin
ShowMessage(IntToStr(Counter));
Counter := Counter + 1;
end;
end;
String Functions
var
S: String;
begin
S := 'Hello World';
// Length
ShowMessage(IntToStr(Length(S))); // "11"
// Substring
S := Copy('Hello World', 1, 5); // "Hello"
// Position
I := Pos('World', S); // 7
// Uppercase/Lowercase
S := UpperCase('hello'); // "HELLO"
S := LowerCase('HELLO'); // "hello"
// Trim spaces
S := Trim(' hello '); // "hello"
// Concatenation
S := 'Hello' + ' ' + 'World'; // "Hello World"
end;
Type Conversion Functions
var
S: String;
I: Integer;
D: Double;
begin
// String to Integer
I := StrToInt('123');
// String to Float
D := StrToFloat('123.45');
// Integer to String
S := IntToStr(123);
// Float to String
S := FloatToStr(123.45);
// Variant to String
S := VarToStr(SomeVariant);
// Format numbers
S := FormatFloat('#,##0.00', 1234.5); // "1,234.50"
end;
Date/Time Functions
var
D: TDateTime;
S: String;
begin
// Current date/time
D := Now; // Current date and time
D := Date; // Current date only
D := Time; // Current time only
// Date arithmetic
D := Date + 7; // 7 days from now
D := Date - 30; // 30 days ago
// Extract components
I := DayOf(D); // Day of month
I := MonthOf(D); // Month number
I := YearOf(D); // Year
// Format dates
S := FormatDateTime('mm/dd/yyyy', Date); // "02/17/2026"
S := FormatDateTime('dd-mmm-yyyy', Date); // "17-Feb-2026"
end;
Common Scripting Tasks
Setting Memo Text Dynamically
procedure MemoTitleOnBeforePrint(Sender: TfrxComponent);
begin
// Set text from a variable
TfrxMemoView(Sender).Text := 'Report for ' + Report.Variables['CustomerName'];
// Or access memo directly
Memo1.Text := 'Total: ' + FloatToStr(TotalAmount);
end;
Conditional Formatting
procedure MemoAmountOnBeforePrint(Sender: TfrxComponent);
var
Amount: Double;
begin
Amount := <Orders."Amount">;
// Change color based on value
if Amount > 1000 then
begin
TfrxMemoView(Sender).Font.Color := clRed;
TfrxMemoView(Sender).Font.Style := [fsBold];
end
else
begin
TfrxMemoView(Sender).Font.Color := clBlack;
TfrxMemoView(Sender).Font.Style := [];
end;
end;
Hiding/Showing Bands
procedure MasterData1OnBeforePrint(Sender: TfrxComponent);
begin
// Hide band if condition is met
if <Orders."Status"> = 'Cancelled' then
TfrxMasterData(Sender).Visible := False;
end;
Running Totals
var
PageTotal: Double;
procedure ReportTitleOnBeforePrint(Sender: TfrxComponent);
begin
// Initialize at start of report
PageTotal := 0;
end;
procedure MasterData1OnBeforePrint(Sender: TfrxComponent);
begin
// Accumulate as records print
PageTotal := PageTotal + <Orders."Amount">;
end;
procedure PageFooterOnBeforePrint(Sender: TfrxComponent);
begin
// Display total in footer
MemoPageTotal.Text := 'Page Total: ' + FormatFloat('#,##0.00', PageTotal);
PageTotal := 0; // Reset for next page
end;
Alternating Row Colors
var
RowNumber: Integer;
procedure ReportTitleOnBeforePrint(Sender: TfrxComponent);
begin
RowNumber := 0;
end;
procedure MasterData1OnBeforePrint(Sender: TfrxComponent);
begin
RowNumber := RowNumber + 1;
if (RowNumber mod 2) = 0 then
TfrxMasterData(Sender).Color := clSilver
else
TfrxMasterData(Sender).Color := clWhite;
end;
Report Events
Main Report Events
OnStartReport
Fires once when the report starts building.
procedure Page1OnStartReport(Sender: TfrxComponent);
begin
// Initialize variables
GrandTotal := 0;
RecordCounter := 0;
// Set report title
Report.ReportOptions.Name := 'Sales Report - ' + FormatDateTime('mm/dd/yyyy', Date);
end;
OnBeforePrint
Fires before each object prints. Most commonly used event.
procedure MemoCustomerOnBeforePrint(Sender: TfrxComponent);
begin
// Format or modify content just before printing
if <Customers."Country"> = 'USA' then
TfrxMemoView(Sender).Text := TfrxMemoView(Sender).Text + ' (Domestic)';
end;
OnAfterPrint
Fires after each object prints.
procedure MasterData1OnAfterPrint(Sender: TfrxComponent);
begin
// Track what was printed
RecordCounter := RecordCounter + 1;
end;
OnStopReport
Fires once when report finishes building.
procedure Page1OnStopReport(Sender: TfrxComponent);
begin
// Cleanup or final calculations
ShowMessage('Report complete. ' + IntToStr(RecordCounter) + ' records printed.');
end;
Band Events
All bands (Header, MasterData, GroupHeader, etc.) support:
- OnBeforePrint: Before the band prints
- OnAfterPrint: After the band prints
- OnAfterData: After data is fetched but before printing
procedure GroupHeader1OnBeforePrint(Sender: TfrxComponent);
begin
// Start new page for each group
if Engine.CurY > 0 then
Engine.NewPage;
end;
Accessing Report Objects
Finding Objects by Name
procedure DialogPage1OnShow(Sender: TfrxComponent);
var
MyMemo: TfrxMemoView;
MyQuery: TfrxFDQuery;
begin
// Find a memo
MyMemo := TfrxMemoView(Report.FindObject('Memo1'));
if Assigned(MyMemo) then
MyMemo.Text := 'Hello';
// Find a query
MyQuery := TfrxFDQuery(Report.FindObject('qryCustomers'));
if Assigned(MyQuery) then
MyQuery.Open;
end;
Accessing Dataset Fields
procedure MasterData1OnBeforePrint(Sender: TfrxComponent);
var
CustomerName: String;
OrderAmount: Double;
begin
// Direct field access
CustomerName := <Customers."CompanyName">;
OrderAmount := <Orders."Amount">;
// Or using dataset
CustomerName := VarToStr(Report.GetDataset('Customers').Value['CompanyName']);
end;
Modifying Object Properties
procedure Memo1OnBeforePrint(Sender: TfrxComponent);
var
M: TfrxMemoView;
begin
M := TfrxMemoView(Sender);
// Font properties
M.Font.Name := 'Arial';
M.Font.Size := 12;
M.Font.Color := clBlue;
M.Font.Style := [fsBold, fsItalic];
// Alignment
M.HAlign := haCenter;
M.VAlign := vaCenter;
// Background
M.Color := clYellow;
// Frame
M.Frame.Typ := [ftLeft, ftRight, ftTop, ftBottom];
M.Frame.Width := 2;
end;
Working with Data
Opening and Closing Queries
procedure DialogPage1OnCloseQuery(Sender: TfrxComponent; var CanClose: Boolean);
var
Query: TfrxFDQuery;
begin
Query := TfrxFDQuery(Report.FindObject('qryOrders'));
if Assigned(Query) then
begin
Query.Close;
Query.ParamByName('CustomerID').Value := DBLookupComboBox1.KeyValue;
Query.Open;
end;
end;
Looping Through Dataset
procedure Page1OnStartReport(Sender: TfrxComponent);
var
DS: TfrxDataSet;
Total: Double;
begin
Total := 0;
DS := Report.GetDataset('Orders');
if Assigned(DS) then
begin
DS.First;
while not DS.Eof do
begin
Total := Total + DS.Value['Amount'];
DS.Next;
end;
end;
Report.Variables['GrandTotal'] := Total;
end;
Checking for Null Values
procedure Memo1OnBeforePrint(Sender: TfrxComponent);
var
V: Variant;
begin
V := <Customers."Phone">;
if VarIsNull(V) or VarIsEmpty(V) then
TfrxMemoView(Sender).Text := 'N/A'
else
TfrxMemoView(Sender).Text := VarToStr(V);
end;
Complete Examples
Example 1: Invoice Report with Calculations
var
InvoiceTotal: Double;
TaxRate: Double;
procedure ReportTitleOnBeforePrint(Sender: TfrxComponent);
begin
// Initialize
InvoiceTotal := 0;
TaxRate := 0.08; // 8% tax
// Set invoice date
MemoInvoiceDate.Text := FormatDateTime('mm/dd/yyyy', Date);
end;
procedure MasterData1OnBeforePrint(Sender: TfrxComponent);
var
LineTotal: Double;
begin
// Calculate line total
LineTotal := <InvoiceItems."Quantity"> * <InvoiceItems."UnitPrice">;
// Display formatted
MemoLineTotal.Text := FormatFloat('$#,##0.00', LineTotal);
// Add to invoice total
InvoiceTotal := InvoiceTotal + LineTotal;
end;
procedure ReportSummaryOnBeforePrint(Sender: TfrxComponent);
var
Tax: Double;
GrandTotal: Double;
begin
// Calculate tax and grand total
Tax := InvoiceTotal * TaxRate;
GrandTotal := InvoiceTotal + Tax;
// Display
MemoSubtotal.Text := FormatFloat('$#,##0.00', InvoiceTotal);
MemoTax.Text := FormatFloat('$#,##0.00', Tax);
MemoGrandTotal.Text := FormatFloat('$#,##0.00', GrandTotal);
end;
Example 2: Conditional Section Display
procedure GroupHeader1OnBeforePrint(Sender: TfrxComponent);
var
Category: String;
begin
Category := <Products."Category">;
// Show different header image based on category
if Category = 'Electronics' then
PictureHeader.Picture.LoadFromFile('C:\Images\electronics.jpg')
else if Category = 'Clothing' then
PictureHeader.Picture.LoadFromFile('C:\Images\clothing.jpg')
else
PictureHeader.Visible := False;
end;
procedure MasterData1OnBeforePrint(Sender: TfrxComponent);
var
StockLevel: Integer;
begin
StockLevel := <Products."InStock">;
// Highlight low stock items
if StockLevel < 10 then
begin
MemoProductName.Font.Color := clRed;
MemoProductName.Font.Style := [fsBold];
MemoStock.Text := 'LOW STOCK: ' + IntToStr(StockLevel);
end
else
begin
MemoProductName.Font.Color := clBlack;
MemoProductName.Font.Style := [];
MemoStock.Text := IntToStr(StockLevel);
end;
end;
Example 3: Complex Dialog Validation
procedure DialogPage1OnShow(Sender: TfrxComponent);
var
ds: TfrxDataSet;
begin
// Set default date range
DateEditStart.Date := Date - 30;
DateEditEnd.Date := Date;
// Load customer list
qryCustomers.Open;
// Select first customer
ds := DBLookupComboBoxCustomer.ListSource.DataSet;
if Assigned(ds) and (ds.RecordCount > 0) then
begin
ds.First;
DBLookupComboBoxCustomer.KeyValue := ds.Value['CustomerID'];
end;
// Set default status
if ComboBoxStatus.Items.Count > 0 then
ComboBoxStatus.ItemIndex := 0;
end;
procedure DialogPage1OnCloseQuery(Sender: TfrxComponent; var CanClose: Boolean);
var
MinAmount: Double;
begin
CanClose := False; // Assume failure until all validation passes
// Validate customer selection
if VarIsNull(DBLookupComboBoxCustomer.KeyValue) then
begin
ShowMessage('Please select a customer');
Exit;
end;
// Validate date range
if DateEditStart.Date > DateEditEnd.Date then
begin
ShowMessage('Start date must be before end date');
Exit;
end;
// Validate minimum amount
try
MinAmount := StrToFloat(EditMinAmount.Text);
if MinAmount < 0 then
begin
ShowMessage('Minimum amount cannot be negative');
Exit;
end;
except
ShowMessage('Please enter a valid minimum amount');
Exit;
end;
// All validation passed - set query parameters
qryOrders.Close;
qryOrders.ParamByName('CustomerID').Value := DBLookupComboBoxCustomer.KeyValue;
qryOrders.ParamByName('StartDate').Value := DateEditStart.Date;
qryOrders.ParamByName('EndDate').Value := DateEditEnd.Date;
qryOrders.ParamByName('Status').Value := ComboBoxStatus.Text;
qryOrders.ParamByName('MinAmount').Value := MinAmount;
qryOrders.Open;
CanClose := True;
end;
Example 4: Page Numbering with Conditions
var
SectionPage: Integer;
procedure GroupHeader1OnBeforePrint(Sender: TfrxComponent);
begin
// Reset page counter for each section
SectionPage := 1;
// Start new page for each group
if Engine.CurY > 0 then
Engine.NewPage;
end;
procedure PageFooterOnBeforePrint(Sender: TfrxComponent);
var
Category: String;
begin
Category := <Products."Category">;
// Custom page numbering
MemoPageNumber.Text := Category + ' - Page ' + IntToStr(SectionPage) +
' (Overall: ' + IntToStr(<Page>) + ' of ' + IntToStr(<TotalPages>) + ')';
SectionPage := SectionPage + 1;
end;