FreeMarker in NetSuite SCA: Syntax and Use Cases

FreeMarker is a Java-based template engine designed to generate text output (e.g., HTML, XML, emails) by combining templates with dynamic data. It is widely used in web applications, enterprise systems like NetSuite, and other platforms requiring dynamic content generation. FreeMarker’s strength lies in its flexibility, allowing developers to embed logic, perform calculations, and format data within templates.

FreeMarker Syntax Overview

FreeMarker templates are text files (e.g., .ftl or embedded in HTML) containing static text, placeholders for dynamic data, and directives for logic. The syntax is intuitive, using delimiters like ${…} for expressions and <#…> for directives.

1. Basic Interpolation

The ${expression} syntax outputs the value of an expression. For example:

<p>Order Number: ${transaction.tranid}</p>
  • Use Case: In a NetSuite email template, ${transaction.tranid} displays the transaction ID (e.g., SO12345).
  • Note: The expression must evaluate to a non-null value, or FreeMarker throws an error unless handled (e.g., with ! for defaults).

2. Directives

Directives control template logic, enclosed in <#…> (start) and </#…> (end) for block directives, or single <#…> for standalone ones. Common directives include:

  • If/Else:
<#if processingFee != 0>
  <p>Processing Fee: ${processingFee?string.currency}</p>
<#else>
  <p>No processing fee applied.</p>
</#if>

  • List (Loop):
<#list transaction.item as item>
  <p>Item: ${item.item}, Amount: ${item.amount}</p>
</#list>
  • Iterates over a collection (e.g., transaction.item in NetSuite) to display line items.
  • Assign:
<#assign total = 100.50>
<p>Total: ${total}</p>

3. Comments

Comments are ignored during rendering, enclosed in <#– … –>:

<#-- Calculate adjusted subtotal -->
<#assign adjustedSubtotal = subtotal - processingFee>

4. Built-ins

Built-ins are functions applied to variables using ? (e.g., variable?built_in). Examples include:

  • String Manipulation:
${transaction.tranid?replace("Sales Order ", "")} <#-- Removes "Sales Order " prefix -->
${item.custcol1?lower_case} <#-- Converts to lowercase -->
${item.custcol1?trim} <#-- Removes leading/trailing whitespace -->
  • Number Formatting:
${adjustedSubtotal?string.currency} <#-- Formats as $974.75 -->
${adjustedSubtotal?string["#,##0.00"]} <#-- Formats as 974.75 -->
  • Null Handling:
${transaction.subtotal!0} <#-- Defaults to 0 if null -->
${transaction.otherrefnum?has_content?then("PO: " + transaction.otherrefnum, "No PO")} <#-- Conditional output -->

5. Null Handling

FreeMarker is strict about null values. To avoid errors, use:

  • Default Values: ${variable!default} (e.g., ${transaction.subtotal!0}).
  • Has Content Check: variable?has_content (e.g., <#if transaction.subtotal?has_content>…</#if>).
  • Null Check: variable?? (e.g., <#if transaction.subtotal??>…</#if>).

Example:

<#assign subtotal = transaction.subtotal?has_content?then(transaction.subtotal?number, 0)>

This assigns 0 if transaction.subtotal is null, preventing arithmetic errors.

Arithmetic Operations in FreeMarker

FreeMarker supports standard arithmetic operations for numbers, which are crucial for calculations like subtotals or tax adjustments in templates. Arithmetic is performed within expressions, typically in <#assign> directives or ${…} interpolations.

1. Supported Operators

  • Addition: + (e.g., total + tax)
  • Subtraction: – (e.g., subtotal – processingFee)
  • Multiplication: * (e.g., quantity * rate)
  • Division: / (e.g., total / 2)
  • Modulus: % (e.g., index % 2)

Example:

<#assign subtotal = 1000.50>
<#assign processingFee = 25.75>
<#assign adjustedSubtotal = subtotal - processingFee>
<p>Subtotal: ${adjustedSubtotal?string.currency}</p> <#-- Outputs: $974.75 -->

2. Number Conversion

NetSuite fields like transaction.subtotal or item.amount may be strings or complex objects. Use ?number to convert to a number:

<#assign processingFee = item.amount?has_content?then(item.amount?number, 0)>
  • Issue: If item.amount is a currency string (e.g., “$25.75”), ?number may fail. Strip currency symbols first:
<#assign amount = item.amount?replace("[^d.]", "", "r")?number>

3. Type Safety

Arithmetic operations require numbers. If a variable is not a number, FreeMarker throws an error. Use checks:

<#assign subtotal = 0>
<#if transaction.subtotal?has_content && transaction.subtotal?string?matches("d*.?d+")>
  <#assign subtotal = transaction.subtotal?number>
</#if>

This ensures subtotal is a valid number or defaults to 0.

4. Rounding and Formatting

Use ?round, ?floor, or ?ceiling for rounding, and ?string for formatting:

<#assign price = 123.456>
${price?round} <#-- 123 -->
${price?string["0.00"]} <#-- 123.46 -->
${price?string.currency} <#-- $123.46 -->

5. Example: Arithmetic in NetSuite

<#assign processingFee = 0>
<#list transaction.item as item>
  <#if item.custcol1?has_content && item.custcol1?trim?lower_case == "processing fee" && item.amount?has_content>
    <#assign processingFee = item.amount?number>
    <#break>
  </#if>
</#list>
<#assign subtotal = transaction.subtotal?has_content?then(transaction.subtotal?number, 0)>
<#assign adjustedSubtotal = subtotal - processingFee>
<td>${adjustedSubtotal?string.currency}</td>
  • Issue: If transaction.subtotal is a complex object, ?number fails. The corrected version (as provided) uses regex to validate strings.

Practical Example: NetSuite Email Template

<html>
<body>
<p>Dear ${transaction.entityname!""},</p>
<p>Thank you for your order (${transaction.tranid?replace("Sales Order ", "")}).</p>

<table>
  <#list transaction.item as item>
    <#if item_index == 0>
      <thead>
        <tr>
          <th>SKU</th>
          <th>Item</th>
          <th>Qty</th>
          <th>Price</th>
          <th>Amount</th>
        </tr>
      </thead>
    </#if>
    <#if item.itemtype != "Discount" && item.custcol1?trim?lower_case != "processing fee">
      <tr>
        <td>${item.custcol1!""}</td>
        <td>${item.item!""}</td>
        <td>${item.quantity!0}</td>
        <td>${item.rate?string.currency}</td>
        <td>${item.amount?string.currency}</td>
      </tr>
    </#if>
  </#list>
</table>

<#-- Calculate adjusted subtotal -->
<#assign processingFee = 0>
<#list transaction.item as item>
  <#if item.custcol1?has_content && item.custcol1?trim?lower_case == "processing fee" && item.amount?has_content>
    <#assign processingFee = item.amount?number>
    <#break>
  </#if>
</#list>
<#assign subtotal = 0>
<#if transaction.subtotal?has_content>
  <#if transaction.subtotal?is_number>
    <#assign subtotal = transaction.subtotal>
  <#elseif transaction.subtotal?string?matches("d*.?d+")>
    <#assign subtotal = transaction.subtotal?number>
  </#if>
</#if>
<#assign adjustedSubtotal = subtotal - processingFee>

<table>
  <tr>
    <td>Subtotal</td>
    <td>${adjustedSubtotal?string.currency}</td>
  </tr>
  <#if processingFee != 0>
    <tr>
      <td>Processing Fee</td>
      <td>${processingFee?string.currency}</td>
    </tr>
  </#if>
  <tr>
    <td>Total</td>
    <td>${transaction.total?has_content?then(transaction.total?string.currency, "$0.00")}</td>
  </tr>
</table>
</body>
</html>

Leave a comment

Your email address will not be published. Required fields are marked *