npm.io
4.0.0 • Published 4 weeks ago

@item-enonic-types/lib-freemarker

Licence
MIT
Version
4.0.0
Deps
1
Size
16 kB
Vulns
0
Weekly
0

FreeMarker for Enonic XP

This library lets you use Apache FreeMarker-templates with Enonic XP.

Build badge

Installation

To install this library you need to add a new dependency to your app's build.gradle file.

Gradle

repositories {
  maven { url "https://repo.itemtest.no/releases" }
  maven { url "https://repo.itemtest.no/snapshots" }
}

dependencies {
  include "no.item:lib-xp-freemarker:3.0.0-SNAPSHOT"
}
TypeScript

To update the version of enonic-types in package.json using npm, run the following command:

npm i -D @item-enonic-types/lib-freemarker

You can add the following changes to your tsconfig.json to get TypeScript-support.

{
  "compilerOptions": {
+   "baseUrl": "./",
+   "paths": {
+     "/lib/xp/*": ["./node_modules/@enonic-types/lib-*"],
+     "/lib/*": [ "./node_modules/@item-enonic-types/lib-*" ,"./src/main/resources/lib/*"],
+   }
  }
}
Add types for IntelliJ

If you are using IDEs from IntelliJ, you can get the correct type for the portal object by adding a file src/main/resources/freemarker_implicit.ftl with this content:

[#ftl]
[#-- @implicitly included --]
[#-- @ftlvariable name="portal" type="no.item.freemarker.FreemarkerPortalObject" --]
Configuration file

You can configure the FreeMarker settings by adding an XP_HOME/config/freemarker.properties file.

To preserve backwards compatibility in an existing project you can set the incompatible_improvements version like this:

incompatible_improvements=2.3.25

Developers should use the HTML Debug Exception Handler in their local development environments.

template_exception_handler=html_debug

Do not use the html_debug exception handler in production! It should only be used for development as it shows technical information about your system.

Usage

Render from template files

You can use resolve to get the ResourceKey of a template file in your application. Then you can pass in a data model to render that template.

import { render } from "/lib/freemarker";
import { getContent } from "/lib/xp/portal";
import type { Response } from "@enonic-types/core";

type FreeMarkerParams = {
  title: string;
  text: string | undefined;
}

const view = resolve("article.ftl");

export function get(): Response {
  const content = getContent()!;

  const model: FreeMarkerParams = {
    title: content.data.title,
    text: content.data.text,
  };

  return {
    body: render<FreeMarkerParams>(view, model),
  };
};
Render from inline strings

Alternatively you can use a string as the template.

import { render } from "/lib/freemarker";
import { getContent } from "/lib/xp/portal";
import type { Response } from "@enonic-types/core";

type FreeMarkerParams = {
  title: string;
  text: string | undefined;
  year: string;
}

// The FreeMarker template as a string. Notice how the $ needs to be escaped.
const view = `
    [#import "/site/utils/footer.ftl" as Footer]

    <h1>\${title}</h1>

    [#if text?has_content]
      <p>\${text}</p>
    [/#if]

    [@Footer.render year=year /]
  `;

export function get(): Response {
  const content = getContent()!;
  const virtualTemplateName = "my-inline-template.ftl";

  const model: FreeMarkerParams = {
    title: content.data.title,
    text: content.data.text,
    year: "2025"
  };

  return {
    body: render<FreeMarkerParams>(view, model, virtualTemplateName),
  };
};
The portal object

The following utility functions are made available in the portal object:

  • portal.localize()
  • portal.processHtml()

The portal-functions can be used within interpolations (${}). Example:

[#-- @ftlvariable name="nextPageUrl" type="String" --]

<a href="${nextPageUrl}">
  ${portal.localize("article.nextPage")}
</a>

Always use the ?no_esc built-in with portal.processHtml(). It prevents auto-escaping the markup if an output format is set.

The portal.component directive

When rendering components from regions in pages and layouts you can use the portal.component directive.

[#-- @ftlvariable name="mainRegion" type="com.enonic.xp.region.Region" --]

<div class="my-layout">
  [#list mainRegion.components as component]
    [@portal.component path=component.path /]
  [/#list]
</div>

If you are creating a view to preview fragments, you can use the portal.component directive like this:

[@portal.component path="fragment" /]

Localization

This library will use the first locale it finds, checking in the following order:

Order Source of locale Use case
1 The language field of the current content Normal content
2 The language field of the current site E.g. the error page
3 The end users "Accept-Language" that matches a supported language in the i18n directory E.g. an admin tool
4 Default to "en-US" E.g. the main.js file

So you can use the special variables ${.locale} and ${.lang} to access the "language tag" resolved based on the list above.

You also don't need to pass in a locale for portal.localize() unless you want a different locale then automatically selected locale.

<!doctype html>
<html lang="${.lang}">
  <body>
    <h1>The language tag is: ${.locale}</h1>

    <p>${portal.localize("article.text")}</p>
  </body>
</html>

If you need another locale to be used (han that of the current content), you can set it with the #setting directive.

[#-- Changes the `locale` to Norwegian for the remainder of the template.  --]
[#-- This will affect the `portal.localize()` and date formatting. --]
[#setting locale="no"]

[#-- Setting the `time_zone` to get the correct time when formatting dates  --]
[#setting time_zone="Europe/Oslo"]

Deploying

Building

To build the project, run the following command

enonic project build

You will find the jar-file at ./build/libs/lib-xp-freemarker-[version].jar

Deploying locally

Deploy locally for testing purposes:

./gradlew publishToMavenLocal

Releasing

Releases are driven by Changesets and run in GitHub Actions.

  1. Describe your change in a changeset and commit it with the change itself:

    npx changeset
  2. When that lands on main, the Publish workflow opens (or updates) a Version Packages pull request. It bumps the version in package.json, package-lock.json and gradle.properties, and folds the changesets into CHANGELOG.md.

  3. Merging that pull request makes the same workflow do the release:

Release credentials

npm is published with trusted publishing: no token is stored anywhere, the npm CLI trades the workflow's OIDC token for a short-lived one. It is configured on npmjs.com under the package's Settings > Trusted publisher, pointing at organization ItemConsulting, repository lib-xp-freemarker and workflow publish.yml. All three are case-sensitive, and renaming the workflow file breaks publishing until it is updated there.

The Maven repository still uses credentials, so the repository needs two secrets: REPOSILITE_USERNAME and REPOSILITE_PASSWORD.

Releasing by hand

Only needed if the automated release fails. The Maven publish reads its credentials from itemtestRepositoryUsername / itemtestRepositoryPassword in ~/.gradle/gradle.properties.

./gradlew publishAllPublicationsToItemtestRepositoryRepository
npm publish
# npm publish --tag beta

Acknowledgments

This library was inspired by the lib-freemarker library by TINE IKT.