Chapter 7: Advanced Template Development
Introduction
Chapter 6 taught you the DataPublisher platform basics: how it works, deployment models, and simple template development.
Now we level up.
This chapter covers advanced techniques that transform basic templates into sophisticated, production-ready solutions that clients can't live without.
You'll learn: - Complex conditional logic patterns - Multi-level master-detail relationships - Dynamic layouts and formatting - Error handling and validation - Performance optimization - Testing and quality assurance
By the end, you'll build templates that rival custom software solutions.
Complex Conditional Logic
Beyond simple IF/THEN statements.
Nested Conditionals
Scenario: Show different content based on multiple conditions
Law firm complaint example:
{{IF JurisdictionType=Federal}}
{{IF DiversityJurisdiction=Yes}}
This Court has jurisdiction over this matter pursuant to 28 U.S.C. § 1332(a)
as there is complete diversity of citizenship between the parties and the
amount in controversy exceeds $75,000, exclusive of interest and costs.
{{ENDIF}}
{{IF FederalQuestion=Yes}}
This Court has jurisdiction pursuant to 28 U.S.C. § 1331 as this action
arises under the laws of the United States.
{{ENDIF}}
{{ENDIF}}
{{IF JurisdictionType=State}}
{{IF State=California}}
This Court has jurisdiction pursuant to California Code of Civil Procedure § 410.10.
{{ENDIF}}
{{IF State=NewYork}}
This Court has jurisdiction pursuant to New York CPLR § 301.
{{ENDIF}}
{{ENDIF}}
Pattern: Outer IF determines jurisdiction type, inner IFs select specific language.
Boolean Logic (AND/OR)
DataPublisher supports: - AND: Both conditions must be true - OR: Either condition must be true - NOT: Condition must be false
Insurance proposal example:
{{IF BusinessType=Restaurant AND AnnualRevenue>1000000}}
You qualify for our preferred commercial package with premium discounts.
{{ENDIF}}
{{IF PriorClaims>3 OR CreditScore<600}}
Additional underwriting review required. Policy may include higher premiums or restrictions.
{{ENDIF}}
{{IF NOT NewBusiness AND YearsInBusiness>=5}}
Mature business discount: 15% off standard rates.
{{ENDIF}}
Switch/Case Pattern
When you have many mutually-exclusive options:
Property management - lease term options:
{{IF LeaseTermMonths=6}}
Lease Type: Short-Term (6 Month)
Base Rent: <<BaseRent>>{{FormatCurrency}}
Utilities: Tenant pays all utilities
Notice to Terminate: 60 days written notice required
{{ENDIF}}
{{IF LeaseTermMonths=12}}
Lease Type: Standard Annual
Base Rent: <<BaseRent>>{{FormatCurrency}}
Utilities: As specified in Utility Addendum
Notice to Terminate: 30 days written notice required
{{ENDIF}}
{{IF LeaseTermMonths=24}}
Lease Type: Two-Year Fixed
Base Rent: <<Year1Rent>>{{FormatCurrency}} (Year 1), <<Year2Rent>>{{FormatCurrency}} (Year 2)
Special Terms: Rent locked for duration, 10% discount vs month-to-month
Early Termination: Subject to 2-month penalty
{{ENDIF}}
{{IF LeaseTermMonths=0}}
Lease Type: Month-to-Month
Base Rent: <<MonthToMonthRent>>{{FormatCurrency}}
Premium: 20% above standard annual rate
Notice to Terminate: 30 days written notice required by either party
{{ENDIF}}
Better than: Giant nested IF/ELSE structure
Fallback Pattern
Provide default content when data missing:
{{IF ClientLogo!=}}
[Logo: <<ClientLogo>>{{InsertImage:width=2in}}]
{{ENDIF}}
{{IF ClientLogo=}}
[Logo: <<DefaultLogo>>{{InsertImage:width=2in}}]
{{ENDIF}}
Or with coalesce function (if supported):
[Logo: <<ClientLogo | DefaultLogo>>{{InsertImage:width=2in}}]
Use case: Clients without logos get default branding, not blank space.
Multi-Level Master-Detail Relationships
Handling complex data hierarchies.
Three-Level Hierarchy
Homeschool co-op: Families → Students → Enrollments
FALL 2025 ENROLLMENT REPORT
{{ForEach:Families}}
Family: <<Families.FamilyName>>{{MakeBold}}
Address: <<Families.Address>>
Contact: <<Families.Email>>, <<Families.Phone>>{{FormatPhone}}
Students in this family:
{{ForEach:Families.Students}}
Student: <<Families.Students.FirstName>> <<Families.Students.LastName>>
Grade: <<Families.Students.GradeLevel>>
Date of Birth: <<Families.Students.DateOfBirth>>{{FormatDate:MM/dd/yyyy}}
Enrolled in these classes:
{{ForEach:Families.Students.Enrollments}}
- <<Families.Students.Enrollments.ClassName>> with <<Families.Students.Enrollments.TeacherName>>
<<Families.Students.Enrollments.DayOfWeek>> at <<Families.Students.Enrollments.StartTime>>
Fee: <<Families.Students.Enrollments.ClassFee>>{{FormatCurrency}}
{{EndForEach}}
Total fees for <<Families.Students.FirstName>>: <<Families.Students.TotalFees>>{{FormatCurrency}}
{{EndForEach}}
FAMILY TOTAL: <<Families.FamilyTotalFees>>{{FormatCurrency}}
────────────────────────────────────────
{{EndForEach}}
GRAND TOTAL ALL FAMILIES: <<GrandTotalFees>>{{FormatCurrency}}
Key techniques: - Nested loops (3 levels deep) - Dotted notation (Families.Students.Enrollments.ClassName) - Subtotals at each level - Visual separators between families
Aggregations in Loops
Calculations across related records:
Law firm matter report:
{{ForEach:Matters}}
Matter: <<Matters.MatterNumber>> - <<Matters.ClientName>> v. <<Matters.OpposingParty>>
Status: <<Matters.Status>>
Opened: <<Matters.OpenDate>>{{FormatDate:MM/dd/yyyy}}
Time Entries:
{{ForEach:Matters.TimeEntries}}
<<Matters.TimeEntries.Date>>{{FormatDate:MM/dd/yyyy}} | <<Matters.TimeEntries.Attorney>> | <<Matters.TimeEntries.Hours>> hrs | <<Matters.TimeEntries.Description>>
{{EndForEach}}
Hours Summary:
Partner Hours: <<Matters.PartnerHours>> @ <<PartnerRate>>{{FormatCurrency}}/hr = <<Matters.PartnerHours * PartnerRate>>{{FormatCurrency}}
Associate Hours: <<Matters.AssociateHours>> @ <<AssociateRate>>{{FormatCurrency}}/hr = <<Matters.AssociateHours * AssociateRate>>{{FormatCurrency}}
Paralegal Hours: <<Matters.ParalegalHours>> @ <<ParalegalRate>>{{FormatCurrency}}/hr = <<Matters.ParalegalHours * ParalegalRate>>{{FormatCurrency}}
Total Fees: <<Matters.TotalFees>>{{FormatCurrency}}
Amount Billed: <<Matters.AmountBilled>>{{FormatCurrency}}
Amount Collected: <<Matters.AmountCollected>>{{FormatCurrency}}
Outstanding: <<Matters.Outstanding>>{{FormatCurrency}}
{{EndForEach}}
Aggregations performed: - SUM(hours WHERE role = 'Partner') - SUM(hours * rate) per role - Total fees, billed, collected calculations
Implementation: Calculate aggregates in database query or in template logic.
Conditional Display of Details
Show details only when needed:
Event planning proposal - optional vendors:
YOUR WEDDING PROPOSAL
For: <<ClientNames>>
Date: <<EventDate>>{{FormatDate:dddd, MMMM d, yyyy}}
VENUE
<<VenueName>>
<<VenueAddress>>
Capacity: <<VenueCapacity>> guests
Rental Fee: <<VenueRentalFee>>{{FormatCurrency}}
{{IF CateringIncluded=Yes}}
CATERING
<<CatererName>>
Menu: <<MenuSelection>>
Price per person: <<CateringPricePerPerson>>{{FormatCurrency}}
Guest count: <<EstimatedGuestCount>>
Total: <<CateringTotal>>{{FormatCurrency}}
Menu Details:
{{ForEach:MenuCourses}}
<<MenuCourses.CourseName>>: <<MenuCourses.Items>>
{{EndForEach}}
{{ENDIF}}
{{IF PhotographyIncluded=Yes}}
PHOTOGRAPHY
<<PhotographerName>>
Package: <<PhotographyPackage>>
Coverage: <<CoverageHours>> hours
Price: <<PhotographyPrice>>{{FormatCurrency}}
Deliverables:
{{ForEach:PhotographyDeliverables}}
- <<PhotographyDeliverables.Item>>
{{EndForEach}}
{{ENDIF}}
{{IF FloralIncluded=Yes}}
FLORAL DESIGN
<<FloristName>>
Arrangements: <<FloralArrangements>>
Price: <<FloralPrice>>{{FormatCurrency}}
{{ENDIF}}
TOTAL INVESTMENT: <<ProposalTotal>>{{FormatCurrency}}
Benefit: Proposal only shows vendors client selected, not all possibilities.
Dynamic Layouts and Formatting
Making documents adapt to content.
Page Breaks
Insert page breaks conditionally:
{{ForEach:Classes}}
CLASS ROSTER
Class: <<Classes.ClassName>>
Teacher: <<Classes.TeacherName>>
{{ForEach:Classes.Students}}
[Student information...]
{{EndForEach}}
{{IF NOT @last}}
{{PageBreak}}
{{ENDIF}}
{{EndForEach}}
@last is special variable: TRUE for last iteration, FALSE otherwise
Result: Page break after each class roster except the last one.
Table Layouts
Dynamic table rows:
INVOICE
┌────────────────────────────────────────────────────────┐
│ Item │ Qty │ Unit Price │ Total │
├────────────────────────────────────────────────────────┤
{{ForEach:LineItems}}
│ <<LineItems.Description>> │ <<LineItems.Quantity>> │ <<LineItems.UnitPrice>>{{FormatCurrency}} │ <<LineItems.LineTotal>>{{FormatCurrency}} │
{{EndForEach}}
├────────────────────────────────────────────────────────┤
│ Subtotal: <<Subtotal>>{{FormatCurrency}} │
│ Tax: <<Tax>>{{FormatCurrency}} │
│ TOTAL: <<Total>>{{FormatCurrency}}{{MakeBold}} │
└────────────────────────────────────────────────────────┘
Table expands to accommodate any number of line items.
Conditional Sections with Distinct Formatting
Medical consent form - procedure-specific warnings:
{{IF ProcedureType=Surgery}}
SURGICAL RISKS AND WARNINGS{{MakeBold}}{{SetFontSize:14}}{{SetColor:red}}
The following risks are associated with surgical procedures:
- Infection
- Bleeding
- Adverse reaction to anesthesia
- Scarring
- [Procedure-specific risks...]
You must not eat or drink for 8 hours before surgery.
{{ENDIF}}
{{IF ProcedureType=ImagingWithContrast}}
CONTRAST MEDIA NOTICE{{MakeBold}}{{SetFontSize:14}}
This procedure uses contrast media (dye). Inform your physician if you:
- Have kidney disease
- Are allergic to iodine or shellfish
- Are pregnant or breastfeeding
- Take metformin (diabetes medication)
{{ENDIF}}
Different formatting (color, size, boldness) for different risk levels.
Responsive Text Sizing
Adjust font size based on content length:
{{IF ClientName.Length>50}}
<<ClientName>>{{SetFontSize:10}}
{{ENDIF}}
{{IF ClientName.Length<=50}}
<<ClientName>>{{SetFontSize:14}}
{{ENDIF}}
Long names get smaller font to fit space.
Error Handling and Validation
Preventing garbage documents.
Required Field Validation
Check for missing data before generating:
{{IF ClientName=}}
ERROR: Client name is required but missing. Document cannot be generated.
{{StopGeneration}}
{{ENDIF}}
{{IF InvoiceDate=}}
ERROR: Invoice date is required but missing.
{{StopGeneration}}
{{ENDIF}}
[Rest of template...]
{{StopGeneration}} prevents document from being created with missing critical data.
Data Type Validation
Ensure data is correct type:
{{IF NOT IsNumber(InvoiceTotal)}}
ERROR: Invoice total must be a number. Current value: <<InvoiceTotal>>
{{StopGeneration}}
{{ENDIF}}
{{IF NOT IsDate(EventDate)}}
ERROR: Event date is invalid. Current value: <<EventDate>>
{{StopGeneration}}
{{ENDIF}}
Range Validation
Ensure values are reasonable:
{{IF TaxRate<0 OR TaxRate>0.20}}
WARNING: Tax rate seems unusual: <<TaxRate * 100>>%
Please verify before sending to client.
{{ENDIF}}
{{IF EstimatedGuestCount<10 OR EstimatedGuestCount>500}}
WARNING: Guest count seems unusual: <<EstimatedGuestCount>>
Typical range is 10-500. Please verify.
{{ENDIF}}
Warnings don't stop generation, but alert user to check.
Relationship Validation
Ensure related data exists:
{{IF StudentCount=0}}
ERROR: No students enrolled in this class. Cannot generate roster.
{{StopGeneration}}
{{ENDIF}}
{{IF NOT TeacherAssigned}}
WARNING: No teacher assigned to <<ClassName>>. Roster will show "TBA" for teacher.
{{ENDIF}}
User-Friendly Error Messages
Bad error:
ERROR: NULL_REFERENCE_EXCEPTION at line 247
Good error:
ERROR: Student photo is missing for Emma Johnson (Student ID: 1234).
Please upload photo or use placeholder image.
To fix: Go to Student Management → Emma Johnson → Upload Photo
Include: - What's wrong (specific) - Why it matters (context) - How to fix (actionable steps)
Performance Optimization Techniques
Making large document generation fast.
Lazy Loading Images
Don't load images until needed:
{{ForEach:Students}}
Name: <<Students.FirstName>> <<Students.LastName>>
{{IF IncludePhotos=Yes}}
[Photo: <<Students.PhotoPath>>{{InsertImage:width=2in,height=2in,lazy=true}}]
{{ENDIF}}
{{EndForEach}}
lazy=true defers image loading, dramatically improving generation speed.
Pagination for Large Datasets
Generate in chunks:
{{-- Generate documents 1-100 --}}
{{ForEach:Records WHERE RecordNumber>=1 AND RecordNumber<=100}}
[Document content...]
{{EndForEach}}
Client generates: - Batch 1: Records 1-100 - Batch 2: Records 101-200 - Batch 3: Records 201-300 - Etc.
Prevents timeout on large batches.
Caching Lookups
Store frequently-accessed data:
{{-- Load states once, reuse multiple times --}}
{{LoadLookupTable:States}}
{{ForEach:Clients}}
Client: <<Clients.Name>>
State: <<States[Clients.StateCode].FullName>>
State Capital: <<States[Clients.StateCode].Capital>>
State Tax Rate: <<States[Clients.StateCode].TaxRate>>%
{{EndForEach}}
Without caching: Database query for each client's state (1,000 clients = 1,000 queries)
With caching: One query loads all states, then lookup in memory (1,000 clients = 1 query + fast lookups)
Minimizing Loops
Fetch only necessary data:
Inefficient:
{{ForEach:AllStudents}}
{{IF AllStudents.ClassID=CurrentClassID}}
[Show student...]
{{ENDIF}}
{{EndForEach}}
Loops through all 500 students, shows 20.
Efficient:
{{ForEach:Students WHERE ClassID=CurrentClassID}}
[Show student...]
{{EndForEach}}
Loops through only 20 students enrolled in this class.
Difference: 96% reduction in iterations.
Testing and Quality Assurance
Ensuring templates work correctly.
Test Data Strategy
Create comprehensive test datasets:
1. Happy Path Data - All fields populated correctly - Typical values - Expected scenarios - Should generate cleanly
2. Edge Case Data - Empty optional fields - Maximum lengths - Minimum values - Boundary conditions
3. Error Case Data - Missing required fields - Invalid data types - Out-of-range values - Should trigger errors
Example test family:
Family: Smith (typical)
- 2 students, 3 classes each, all data present
- Should generate perfectly
Family: TestEdgeCases (edge cases)
- 1 student with no photo (tests placeholder)
- Student with very long name (50 characters)
- Student with no allergies, no medical conditions
- Should handle gracefully
Family: TestErrors (error cases)
- Student with no name (should error)
- Student with future birth date (should warn)
- Student in 0 classes (should handle)
Manual Testing Checklist
For each template, verify:
□ Data accuracy
- All placeholders replaced correctly
- No "<
□ Conditional logic - Sections show/hide correctly - All IF paths tested - Edge cases handled
□ Loops - All items displayed - Master-detail relationships correct - No duplicates or missing items
□ Formatting - Professional appearance - Consistent fonts and sizing - Page breaks in right places - Tables aligned correctly
□ Images - Photos display correctly - Sizing consistent - Placeholders for missing images
□ Multi-page documents - Headers/footers correct on all pages - Page numbers accurate - Sections don't break awkwardly
Automated Testing (Advanced)
If generating hundreds of documents:
1. Regression Testing
// Generate documents with test data
// Compare output to known-good baseline
// Flag differences
testTemplate('ClassRoster.docx', 'testData.csv', 'baseline-output.docx');
// Returns: PASS or list of differences
2. Validation Scripts
// Check generated documents for common errors
validateDocument('output.docx');
// Checks:
// - No template syntax visible (<<...>>)
// - All sections present
// - No orphan headings
// - Calculations add up
3. Load Testing
// Generate 1,000 documents, measure time
loadTest('ClassRoster.docx', 1000);
// Returns: Average time, max time, errors
Client UAT (User Acceptance Testing)
Before going live:
- Demo with sample data
- Generate documents with client's real data structure
- Show variety of scenarios
-
Get feedback on formatting, layout, content
-
Pilot with subset of real data
- 10-20 records
- Client reviews thoroughly
-
Identify issues before full deployment
-
Parallel run
- Generate documents both manually and automated
- Compare outputs
-
Ensure automation matches or exceeds quality
-
Sign-off
- Client approves template
- Document any known limitations
- Establish change request process
Advanced Patterns Library
Reusable solutions to common problems.
Pattern 1: Alternating Row Colors
For readability in long tables:
{{ForEach:Students}}
{{IF @index % 2 = 0}}
{{SetBackgroundColor:lightgray}}
{{ENDIF}}
[Student information row...]
{{IF @index % 2 = 0}}
{{SetBackgroundColor:white}}
{{ENDIF}}
{{EndForEach}}
@index is loop counter (0, 1, 2, ...) % 2 is modulo (remainder after dividing by 2) Result: Even rows gray, odd rows white
Pattern 2: Running Totals
Show cumulative totals:
PAYMENT HISTORY
{{ForEach:Payments}}
Date: <<Payments.Date>>{{FormatDate:MM/dd/yyyy}}
Amount: <<Payments.Amount>>{{FormatCurrency}}
Running Total: <<Payments.RunningTotal>>{{FormatCurrency}}
{{EndForEach}}
Total Paid: <<TotalPaid>>{{FormatCurrency}}
Balance Due: <<BalanceDue>>{{FormatCurrency}}
Calculation done in database:
SELECT
PaymentDate,
Amount,
SUM(Amount) OVER (ORDER BY PaymentDate) AS RunningTotal
FROM Payments
Pattern 3: Section Numbering
Auto-number sections:
COMPLAINT
{{ForEach:Claims}}
CLAIM {{@index + 1}}: <<Claims.ClaimType>>{{MakeBold}}
{{ForEach:Claims.Elements}}
{{@parentIndex + 1}}.{{@index + 1}} <<Claims.Elements.ElementText>>
{{EndForEach}}
{{EndForEach}}
Result:
CLAIM 1: BREACH OF CONTRACT
1.1 Plaintiff and Defendant entered into contract...
1.2 Plaintiff performed all obligations...
1.3 Defendant failed to perform...
CLAIM 2: FRAUD
2.1 Defendant made false representations...
2.2 Defendant knew representations were false...
Pattern 4: Conditional Pluralization
Grammar-correct text:
This class has <<StudentCount>> student{{IF StudentCount!=1}}s{{ENDIF}}.
You have <<ClassCount>> class{{IF ClassCount=1}}{{ENDIF}}{{IF ClassCount!=1}}es{{ENDIF}} remaining.
<<DaysUntilEvent>> day{{IF DaysUntilEvent!=1}}s{{ENDIF}} until your event!
Result: - "1 student" (singular) - "5 students" (plural) - "1 class" (singular) - "3 classes" (plural)
Pattern 5: Smart Abbreviations
Show full or abbreviated based on space:
{{IF AvailableWidth>6in}}
<<States[StateCode].FullName>>
{{ENDIF}}
{{IF AvailableWidth<=6in}}
<<StateCode>>
{{ENDIF}}
Result: - Wide layout: "California" - Narrow layout: "CA"
Troubleshooting Common Issues
When things go wrong.
Issue 1: Placeholder Not Replaced
Symptom: "<
Causes: 1. Field name misspelled in template 2. Field doesn't exist in data source 3. Field is empty (NULL or blank)
Solutions:
- Check field name spelling
- Verify field exists in CSV/database
- Use fallback: <
Issue 2: Loop Not Executing
Symptom: Content inside {{ForEach}} doesn't appear
Causes: 1. No related records (empty child table) 2. Relationship not defined correctly 3. WHERE clause filters out all records
Solutions: - Check data: Do child records exist? - Verify foreign key relationships - Remove WHERE clause to test - Add diagnostic: {{Students.Count}} students found
Issue 3: Conditional Always Shows/Hides
Symptom: Section always appears or never appears
Causes: 1. Comparison syntax wrong (= vs ==) 2. Data type mismatch (comparing number to string) 3. Field is NULL (NULL != anything)
Solutions: - Use correct comparison operator (IF FieldName=Value) - Check data types (IF Age>18, not IF Age>"18") - Handle NULL: {{IF FieldName!=}} [field has value] {{ENDIF}}
Issue 4: Formatting Not Applied
Symptom: {{MakeBold}} or {{SetColor:red}} doesn't work
Causes: 1. Function applied to wrong element 2. Syntax error (typo in function name) 3. Function not supported in this context
Solutions:
- Apply to field: <
Issue 5: Images Not Displaying
Symptom: [Photo] or blank space where image should be
Causes: 1. File path incorrect or file doesn't exist 2. File format not supported 3. Image too large (memory issue) 4. Permissions issue (server can't read file)
Solutions: - Verify file exists at path specified - Use supported formats (JPG, PNG) - Resize large images before inserting - Check file permissions
Key Takeaways
Advanced templates require: - Complex conditional logic (nested IFs, Boolean AND/OR) - Multi-level master-detail (Families→Students→Enrollments) - Dynamic layouts (page breaks, tables, responsive sizing) - Error handling (validation, user-friendly messages) - Performance optimization (lazy loading, caching, smart queries) - Thorough testing (happy path, edge cases, errors)
Patterns to master: - Alternating row colors - Running totals - Section numbering - Conditional pluralization - Smart abbreviations
Troubleshooting skills: - Diagnostic techniques - Common pitfalls - Systematic debugging
Quality standards: - Professional formatting - Grammatically correct - Error-free - Performant - Maintainable
With these advanced techniques, you can build production-ready templates that solve real business problems and delight clients.
In the next chapter, we shift from building to selling: how to find your first clients and get them to say yes.
End of Chapter 7
Next: Chapter 8 - Finding Your First Clients