Liquid templates

Hello,

One of the main challenges I encounter on Grist is exporting data into well-presented PDFs.

Some contributions suggest creating templates and calculating the result using formulas, which are essentially small Python programmes. This approach is problematic for two reasons:

  • The renderings are computed on the server side each time the data changes, which can increase the server’s computational load and slow down Grist’s performance, especially with large documents.
  • The template engine’s features must remain basic in order to keep the formula simple, otherwise there is a risk of increasing the computational load once again.

The approach proposed here is to perform the calculation on the client side, in JS, in a widget, using a powerful and proven template engine: LiquidJS (https://liquidjs.com/). The calculations are then only performed when a rendering is displayed.

I have created two widgets:

Here is a demo: Liquid - Grist

Enjoy !

5 Likes

I’ve used JS2Pdf with success as well. I find though that it’s hard to format the pdf exactly how I want programmatically.

So I personally have settled on the widget having a word doc template on the backend using docx-templates (npm), which gives me exact control over what the end product looks like.

Then I give the user the option to download the filled out word document directly (in our use case they might want to add additional text that doesn’t fit nicely in any of the grist columns) or click export to pdf, which runs a powershell script that uses an invisible word doc to convert to pdf. It takes like 4 seconds to execute but this was the easiest method I’ve found for perfect formatting of the resulting document.

# PowerShell script to convert DOCX to PDF using Microsoft Word
param(
   [Parameter(Mandatory=$true)]
   [string]$InputPath,
   
   [Parameter(Mandatory=$true)]
   [string]$OutputPath
)

try {
   # Create Word application
   $word = New-Object -ComObject Word.Application
   $word.Visible = $false
   $word.DisplayAlerts = 0  # wdAlertsNone
   
   # Open the document
   $doc = $word.Documents.Open($InputPath)
   
   # Save as PDF (wdFormatPDF = 17)
   $doc.SaveAs($OutputPath, 17)
   
   # Close document
   $doc.Close($false)
   
   # Quit Word
   $word.Quit()
   
   # Release COM objects
   [System.Runtime.Interopservices.Marshal]::ReleaseComObject($doc) | Out-Null
   [System.Runtime.Interopservices.Marshal]::ReleaseComObject($word) | Out-Null
   [System.GC]::Collect()
   [System.GC]::WaitForPendingFinalizers()
   
   Write-Output "SUCCESS"
   exit 0
}
catch {
   Write-Error $_.Exception.Message
   if ($word) {
       $word.Quit()
       [System.Runtime.Interopservices.Marshal]::ReleaseComObject($word) | Out-Null
   }
   exit 1
}