This is an internal documentation. There is a good chance you’re looking for something else. See Disclaimer.

Freemarker Code Style Guide

Overview

This code style guide establishes the basic rules on how to write freemarker code in general. Code must be as clean and easy to read as possible. A good code style greatly assists in that.

Naming

  • Use camel case for variables, functions and macros

  • Use snake case for snippets and field names

    • Use single word variable, function or macro names if appropriate (see next point)

  • Use concise naming and prefixing

    • concise: it should be clear from the name what is stored inside variables

    • prefixing: isMale, hasData, isActive, hasBillingAddress, etc

  • English names only

  • Avoid generic names such as img1, img2 or value1, value2, etc

// BAD - no camel casing and inconsistent naming for variables

// neither camel cased nor English
[#assign mein_geburtstdatum = evaluationData.relUser.birthdate/]

// not clear at one glance what "bDat" implies
[#assign bDat = evaluationData.relUser.birthdate/]

// no idea what the "its" prefix implies here
[#assign itsData = qualification.data /]


// GOOD - concise camel case naming
[#assign birthDate = evaluationData.relUser.birthdate/]
[#assign qualificationData = qualification.data /]

// GOOD - concise camel case and snake case naming
[#assign sampleField = {"field_key": field.value}/]

DRY - Do not repeat yourself

The DRY principal aims to reduce repetition of code. This will prevent superfluous code and improves maintainability.

  • Avoid repeating yourself

// BAD - the same check is done multiple times. If changes are needed each instance must be adjusted.
[#if status == "active"] // do this [#else] // do that [/#if]
[#if status == "active"] // do this [#else] // do that [/#if]
[#if status == "active"] // do this [#else] // do that [/#if]


// GOOD - declared once and reused
[#assign isActive = (status == "active")/]

[#if isActive] // do this[#else] // do that [/#if]
[#if isActive] // do this[#else] // do that [/#if]
  • Extract repeated code to its own Template Snippet

// BAD - same function is declared in every place it is needed
// File 1
[#function startsWithVowel hometown]
  [#return hometown?has_content && hometown?upper_case[0]?matches('[AEIOU]')]
[/#function]
[#if startsWithVowel(hometown)] // do this [#else] // do that [/#if]

// File 2
[#function startsWithVowel hometown]
  [#return hometown?has_content && hometown?upper_case[0]?matches('[AEIOU]')]]
[/#function]
[#if startsWithVowel(hometown)] // do this [#else] // do that [/#if]


// GOOD - the function is extracted into a separate Template Snippet and loaded if needed
// File 1
[@templateSnippet id="starts_with_vowel"/]
[#if startsWithVowel(hometown)] // do this [#else] // do that [/#if]

// File 2
[@templateSnippet id="starts_with_vowel"/]
[#if startsWithVowel(hometown)] // do this [#else] // do that [/#if]

Keep it short

Code should be kept as short and concise as possible.

// BAD - verbose function to check if a string starts with a vowel
[#function startsWithVowel hometown]
  [#if hometown[0] == "A"
  || hometown[0] == "E"
  || hometown[0] == "I"
  || hometown[0] == "O"
  || hometown[0] == "U"]
     [#return true]
  [/#if]
  [#return false]
[/#function]


// GOOD - the same result can be achieved with a regex check
[#function startsWithVowel hometown]
  [#return hometown?has_content && hometown?upper_case[0]?matches('[AEIOU]')]
[/#function]
// BAD - verbose if/else statement
[#if isMale]
  [#assign profession = evaluationData.relEvaluation.relEvaluation_node.label_male/]
[#else]
  [#assign profession = evaluationData.relEvaluation.relEvaluation_node.label_female/]
[/#if]


// GOOD - use built-in then
[#assign evaluation = evaluationData.relEvaluation.relEvaluation_node/]
[#assign profession = isMale?then(evaluation.label_male, evaluation.label_female)/]

Using list directives

  • New directive [@list] should be used for large sets of data and must not be nested within itself again.

[@list source=orderBy(baseData, "relUser.lastname") var="evaluationData"; loop]
  [#--Do stuff here--]
[/@list]
  • Standard directive [#list] should be used for small sets of data and can be nested within itself again.

[#list orderBy(baseData, "relUser.lastname") as evaluationData]
  [#list evaluationData as evaluation]
     [#--Do stuff here--]
  [/#list]
[/#list]

No Inline Styling

Inline Styling must be avoided as it bloats the code and makes it hard to maintain. CSS Classes must be used instead.

// BAD - ugly inline styles make the code hard to read
<td style="text-align: left; padding-left: 5mm; vertical-align:bottom;">
  ${moduleDays}
</td>


// GOOD - use CSS Classes that also can be reused
<td class="align-left padding-left-5 v-align-bottom">
  ${moduleDays}
</td>

<div class="padding-left-5 v-align-bottom">
  ${moduleDays}
</div>

Naming queries

Queries need to be prefixed with query. This reduces ambiguity of code.

// BAD - the query is not prefixed and further down in the code it's not clear that we are looping over query result
[@query name="pages"]
  find Page where relContent_category.unique_id == "navigation" and exists(relContent_published)
[/@query]

[#list pages as page]
  <li><a href="[@loadPath entity=page/]">${page.label}</a></li>
[/#list]


// GOOD - the query is prefixed and further down it's clear that we are looping over a query result
[@query name="queryPages"]
  find Page where relContent_category.unique_id == "navigation" and exists(relContent_published)
[/@query]

[#list queryPages as page]
  <li><a href="[@loadPath entity=page/]">${page.label}</a></li>
[/#list]

Report Resource Text/Image

Use report resource text and images as opposed to the [@loadTextResource/] directive. This allows for easy adjustments of report texts and images directly in the admin, as opposed to having to edit textresources which require a Prod-Deployment to take effect.

Default Value Operator

Use the default value operator if appropriate. This reduces the chance of errors being thrown during report generation.

// BAD - there is no default value specified, the report throws an error in case there is no value for company_c
${leadingInstitutionEntity.company_c?string}

// GOOD - in case company_c is empty,
${leadingInstitutionEntity.company_c?string!""}