How to work with JavaScript models

This page explains in detail how to use JavaScript models for templates. JavaScript models can be done via Light development, thus enabling fast development and deployment without the need for Java, Maven or WAR deployment. JavaScript models represent a type of Magnolia Resources.

If you want to use JavaScript models, make sure that your bundle contains the magnolia-module-javascript-models module. For further information see JavaScript Models module - Installing.

Defining, referencing and using a JavaScript model

Base recipe: Using a convention for naming and location of the model

  1. Set the model class property in the template definition.

rhino.yaml
templateScript: /your-light-module/templates/pages/rhino.ftl
renderType: freemarker
modelClass: info.magnolia.module.jsmodels.rendering.JavascriptRenderingModel
  1. Create the model file in the same location as the template definition and give it the name <template-name>.js. Create a JavaScript class with properties and methods in the file and create an instance at the end of the file.

rhino.js
var Dumbo = function () {
    this.name = "John";
    this.getRandomNumber = function () {
        return Math.ceil(100 * Math.random());
    }
};
new Dumbo();
  1. Use the model in the template script.

rhino.ftl
<div>Hey ${model.name}, your happiness level is at ${model.getRandomNumber()}%.</div>

Reference the model in the script and use the properties or methods defined within the model.

Files involved
your-light-module/
└── templates
    └── pages
        ├── rhino.ftl
        ├── rhino.js
        └── rhino.yaml

This recipe works for both page and component templates.

Using the modelPath and class property

With this approach you do not have to follow the naming convention. This can be handy to use the same model among multiple templates.

Instead of the property modelClass, you must set the properties class and modelPath. The value for the former must be info.magnolia.module.jsmodels.rendering.JavascriptTemplateDefinition and for the latter the path to the JavaScript model.

rhino.yaml
templateScript: /your-light-module/templates/pages/rhino.ftl
renderType: freemarker
class: info.magnolia.module.jsmodels.rendering.JavascriptTemplateDefinition
modelPath: /your-light-module/templates/common/baseModel.js

Nashorn, JavaScript and the Java API

When you write a JavaScript model, you obviously write a JavaScript code. However, you will encounter objects which come from the Java world. For instance this is true when you work with Magnolia’s built-in rendering context objects or when using templating functions.

When using objects originating in Java within a JavaScript model, it is helpful to know their public methods, which you can also use within the JavaScript code. To get familiar with these objects, it helps to have a look at the Java API pages which list the public methods and the corresponding return types. On this page you will find many links to Java docs. Follow the links to get an understanding of these Java-origin objects.

Examples: info.magnolia.cms.core.AggregationState (for the state object), info.magnolia.jcr.util.ContentMap (for the content object) and info.magnolia.i18nsystem.SimpleTranslator (for the i18n object).

Most context objects are JavaBeans, which means you can access their properties with the dot operator or a getter method in a template script or in a JavaScript model. Both expressions are valid and return the same value.

state.channel.name;
state.getChannel().getName();

Both the lines above have the same meaning.

Using built-in rendering context objects

In this section we take a look at the Magnolia built-in objects model, content, def, ctx, state and i18n. These objects can be used directly within a template script. Below you will see how to access their properties within a JavaScript model.

The code snippets starting with var are in JavaScript and are intended to be used within JavaScript models.

model

The model itself provides the following built-in properties:

  • parent: Model of the parent component or template.

  • root: Top root model of the rendering process.

  • content: Content node bound to the model in the rendering context provided as a ContentMap.

  • node: Content node bound to the model in the rendering context provided as a Node.

  • definition: The renderable definition (template, area or component) bound to the model. Same as def.

These properties can referenced like this:

model.root
model.node

The probably most handy property is parent. Parent points to the parent model, its meaning depends on the case. If you have for instance a component template with a model and a page template with a model - the parent model of the component template is the model of the page template. Via parent you can access all built in properties of the parent model like this:

model.parent.definition
model.parent.content

Example: Asking a model for a URL and a title, then building a link.

[#assign linkURL = model.url!]
[#assign linkText = model.title!]

<a href="${linkURL}">${linkText}</a>

Within a FreeMarker template script, you reference this property as follows:

[#assign parentTemplateDefintion = model.parent.definition /]

Combining a method of the parent model (#getPageInfo) and built-in model properties:

 ${model.parent.getPageInfo(model.parent.content)}

In a JavaScript model class, you must use this instead of model :

var parentTemplateDefintion = this.parent.definition;

Note the usage of the parent property, which is a handy pointer to the parent model. With parent you have access to all properties and methods defined on the parent model as well as to the built-in properties.

content

Current content node in the rendering context provided as info.magnolia.jcr.util.ContentMap.

In a page template, current node is the page node (mgnl:page). In a component template, current node is the component node (mgnl:component). It is the contextual root content node. The current node is exposed as a ContentMap object, which means it carries the properties of the underlying Node.

Example: Rendering a page title. Here the current node is a page node.

<h1>${content.title!""}</h1>

example

var pageTitle = content.title;
var nodePrimaryType = content["jcr:primaryType"];
var nodeType = content["@nodeType"]; // same as above)
var name = content["@name"];
var id = content["@id"];
var path = content["@path"];
var depth = content["@depth"];

The content object is a map. You can access its properties via the dot notation'' (line 1) or the ``brackets notation (line 2). The latter is required if the property’s name contains a colon or a dot.

The content object also maps some always-existing properties with the spacial delimiter @. These properties are @name, @id (same as jcr:uuid), @path, @depth, and @nodeType (same as jcr:primaryType).

def – template definition

Current info.magnolia.rendering.template.RenderableDefinition. Use def to access the properties of the template definition such as title or use custom parameters. It is a JavaBean, which means you can access its properties with the dot operator or a getter method.

Example: Getting a CSS class name from custom parameters and assigning it to a variable.

[#assign cssClass=def.parameters.cssClass]

example

var templateTitle = def.title;
var myColor = (def.parameters && def.parameters.color && ""!=def.parameters.color) ? def.parameters.color : "red";

ctx – context

Context represents the environment in which the current process runs. The type is info.magnolia.context.Context. It is info.magnolia.context.WebContext when the script is executed from a Web page and info.magnolia.context.SimpleContext, for instance, when the script generates a mail from within a workflow or scheduled job.

The info.magnolia.context.Context interface provides access to:

  • user

  • locale (java.util.Locale)

    Please note that ${ctx.locale} is different from ${cmsfn.language()}, the former referring to the locale currently used by the user, the latter to the locale currently used by the site. See also AdminCentral and public locales.

In addition, info.magnolia.context.WebContext provides access to:

Any ctx attributes (request, session or application scoped) listed under config:server/rendering/engine/protectedAttributes are not exposed.

'protectedAttributes':
  'servletContext': 'servletContext'

WebContext properties are null if the execution is not a Web page request.

Example: Getting a search query from a request parameter.

[#assign queryStr = ctx.getParameter('queryStr')!?html]

example

var userName = ctx.user.name;
var locale = ctx.locale;
var contextPath = ctx.contextPath;
var servletContext = ctx.servletContext;

state – aggregation state

The current info.magnolia.cms.core.AggregationState. Only set if ctx is of type WebContext. (See above.) It is a shortcut for ctx.aggregationState.

Provides access to many properties such as:

  • channel

  • originalURI

  • currentURI

  • queryString

  • mainContentNode (javax.jcr.Node)

    This property returns the node (of type Node) that contains the main content. For example, if a child node inherits content from another component and state.mainContentNode was applied to the child node, it is the child node’s main content that would be returned.

    A use case for this is shown below. Since the type returned by state.mainContentNode is Node, the function cmsfn.asContentMap converts that node to a ContentMap, providing access to its properties. Finally, the cmsfn.page function returns the page to which the content belongs.

    [#local currentPage = (cmsfn.page(cmsfn.asContentMap(state.mainContentNode)))!{}]
  • templateName

  • locale (same as ctx.locale)

Check out info.magnolia.cms.core.AggregationState for all properties.

Please note that the values of all the properties are HTML-escaped by default. Should you need it, the raw (unescaped) data can still be accessed in the following manner:

${state.unwrap().originalURI}

However, be warned that this may expose your webapp to XSS attacks.

example

var currentURI = state.currentURI;
var queryString = state.queryString;
var channelName = state.channel.name;

i18n – simple translator for localized content

i18n is an object of the info.magnolia.i18nsystem.SimpleTranslator type. It provides access to all the message bundles that have been loaded into the system. Use the #translate method to get a localized value identified by a key. You can provide more String arguments to the method in order to replace the placeholders ({}) in the translation.

Example:

message-bundle snippet

javascript-model-samples.frontend.footer.arbitraryWisdom = You make your own luck
javascript-model-samples.frontend.footer.happinessLevel = Random happiness level: {0}%

JavaScript model snippet

var localizedText = i18n.translate("javascript-model-samples.frontend.footer.arbitraryWisdom");
var localizedText2 = i18n.translate("javascript-model-samples.frontend.footer.happinessLevel", "95");

Enabling and using templating functions for JS models

Templating functions were built mainly to be used within template scripts directly, but they can also be very handy within JavaScript models. In the default configuration of magnolia-module-javascript-models, templating functions are not enabled.

To give more power to your JavaScript models, enable the templating functions to access and search JCR content, such as cmsfn, damfn, searchfn and similar.

Enabling templating functions

Edit the configuration of the module with the Configuration app. For each templating function you want to use, add an entry at /modules/javascript-models/config/engineConfiguration/exposedComponents.

Templating function in the Configuration app The stock configuration of the module shows just the version of the module. You have to create the subtree starting with the folder /modules/javascript-models/config.

Using templating functions in a JavaScript model

Once a templating function is enabled, use it in the JavaScript code of the model.

Example: Breadcrumb navigation using cmsfn#ancestors

JS snippet from the model class

The method #renderBreadcrumbs expects a page node as the argument.

Here is the method call within a page template script:

<div class="breadcrumbs">
  ${model.renderBreadcrumbs(content)}
</div>

Creating a custom form processor with JavaScript

JavaScript can also be used for processing forms created by Magnolia’s Form Module (see also Form module creating a custom form processor). Below is the form definition and the JavaScript based form processor for a form in order to create a new contact in the contacts workspace.

The complete example is available on bitbucket.

YAML definition of the form component:

/javascript-model-samples/raw/templates/components/form.yaml

Notes:

  • Line 12: The class property must have the value info.magnolia.module.jsmodels.form.JavascriptFormProcessor, this definition class enables the usage of a JavaScript based form processor (whose path is defined on the next line).

  • Line 13: The formProcessorScriptPath property contains the path to the JavaScript of the processor.

JavaScript-based form processor:

/javascript-model-samples/raw/templates/js/formProcessors/saveContact.js

Loading scripts to the model

You can load other JavaScript files in the Javascript model file. This is handy when reusing JavaScript classes in several models. There are two possibilites to load a script. In both cases, load the script at the top of model file.

Loading a script as a Magnolia resource with loadScript

We recommend using loadScript if the JavaScript is a Magnolia resource. It can then load it independently of its origin. When referencing the script, the path is absolute and has to start with the module name.

Example:

loadScript("/javascript-model-samples/templates/js/utils.js");

Loading a script from the filesystem with load

A Nashorn built-in feature, load can be used to load a script with http. When using it for files of Magnolia modules, both the JavaScript model file and the file to be included must be available on the filesystem. Combine it with the built-in PATH variable, which is the absolute path to the parent directory of the current model file.

Examples:

load(__PATH__ + "/../js/utils.js");
load("https://cdnjs.cloudflare.com/ajax/libs/validate.js/0.11.1/validate.min.js");

A complete example

Below is a complete example showing how to use the included script.

Example:

The file to be loaded:

var Utils = function () {

    this.formatDate = function (calendarObject) {
        var date = new Date(calendarObject.getTimeInMillis());
        var datestring = date.getFullYear() + "-" +
            ("0" + (date.getMonth() + 1)).slice(-2) + "-" +
            ("0" + date.getDate()).slice(-2) + "-" +
            ("0" + date.getHours()).slice(-2) + ":" +
            ("0" + date.getMinutes()).slice(-2);

        return datestring;
    }
}

The model which is loading and using the above script:

loadScript("/javascript-model-samples/templates/js/utils.js");

var MyModel = function () {
    var utils = new Utils();

    this.getPageInfo = function (pageNode) {
        var pageCreator = pageNode["mgnl:createdBy"];
        var modificationDate = utils.formatDate(pageNode["mgnl:lastModified"]);
        return i18n.translate("javascript-model-samples.frontend.footer.pageInfo.label", pageCreator, modificationDate);
    };

};
new MyModel();
  • Line 1: Load the external script with a JavaScript class.

  • Line 4: Create an instance of the external script’s class.

  • Line 8: Use the instance method of the external class.

Exposing other components

You can expose any other component in the same way as enabling the templating functions, see JavaScript Models module - Exposing components. A component is a Java class (or an interface for which a type mapping exists within the framework) having a default constructor that can be resolved and instantiated by the IoC framework.

This is a very powerful mechanism and should be used carefully. Fortunately, its power can be limited by restricting access to the Java API.

Restricting access to the Java API

The module provides two ways to limit the power of a JavaScript model.

Disabling Nashorn extensions and Java syntax extensions

There are two Nashorn engine options to limit the power:

  • --no-java turns off Java specific syntax extensions like Java, Packages, etc.

  • --no-syntax-extensions makes sure that only standard ECMAScript syntax is supported, disabling the Nashorn extensions.

Disabling Nashorn extensions and Java syntax extensions in the Configuration app

Using class filter to exclude instantiation of some Java classes

To interpret JavaScript, Nashorn creates a compiled version of a JS model. When the script is executed, Nashorn instantiates Java classes. With a class filter you can exclude Java classes which you do not want to be instantiated. The class filter must implement dk.nashorn.api.scripting.ClassFilter.

The filter is applied when you explicitly use the types in the Nashorn code, for instance, with the expression Java.type("myType"). The filter is not applied when you assign an object to a Nashorn variable without explicitly mentioning the type.

If the filter prevents the usage of a class – technically speaking, if the filter returns false – Nashorn then handles this case as a java.lang.ClassNotFoundException which leads to a java.lang.RuntimeException.

Using class filter to exclude instantiation of some Java classes

Example:

Java class filter code:

Nashorn code:

this.test = function () {
    var sytemTime = Java.type("java.lang.System").currentTimeMillis();
    return sytemTime;
};

this.test2 = function (myObject) {
    if (myObject instanceof Java.type("java.util.GregorianCalendar")) {
        return utils.formatDate(myObject)
    }else{
        return myObject;
    }
};
this.test3 = function (workspaceName) {
    var jcrSession = ctx.getJCRSession(workspaceName);
    return
};
  • The filter is applied when calling the #test and #test2 methods (see lines 2, 7 in the Nashorn code above).

  • The filter is not applied when calling the #test3 method, although on line 14 Nashorn assigns an object of the type javax.jcr.Session.

Feedback

DX Core

×

Location

This widget lets you know where you are on the docs site.

You are currently perusing through the DX Core docs.

Main doc sections

DX Core Headless PaaS Legacy Cloud Incubator modules