Magnolia 5.7 reached extended end of life on May 31, 2022. Support for this branch is limited, see End-of-life policy. Please note that to cover the extra maintenance effort, this EEoL period is a paid extension in the life of the branch. Customers who opt for the extended maintenance will need a new license key to run future versions of Magnolia 5.7. If you have any questions or to subscribe to the extended maintenance, please get in touch with your local contact at Magnolia.
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.
templateScript: /your-light-module/templates/pages/rhino.ftl renderType: freemarker modelClass: info.magnolia.module.jsmodels.rendering.JavascriptRenderingModel
<template-name>.js
. Create a JavaScript "class" with properties and methods in the file and create an instance at the end of the file.var Dumbo = function () { this.name = "John"; this.getRandomNumber = function () { return Math.ceil(100 * Math.random()); } }; new Dumbo();
<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.
Example:
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: (for the
state
object), (for the
content
object), (for the
i18n
object) etc.
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();
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
.
Within a FreeMarker template script, you reference this property as follows:
[#assign parentTemplateDefintion = model.parent.definition /]
#getPageInfo
) and built-in model properties:${model.parent.getPageInfo(model.parent.content)}
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 a 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.
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"];
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
RenderableDefinition
. Use def
to access the properties of the template definition such as title, or custom parameters. It is a JavaBean, which means you can access its properties with the "dot" operator or a getter method.
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 . It is a
when the script is executed from a Web page and
for instance when the script generates a mail from within a workflow or scheduled job.
var userName = ctx.user.name; var locale = ctx.locale; var contextPath = ctx.contextPath; var servletContext = ctx.servletContext;
state
– aggregation state
The current . Only set if ctx is of type WebContext. (See above.) It is a shortcut for
ctx.aggregationState
.
var currentURI = state.currentURI; var queryString = state.queryString; var channelName = state.channel.name;
i18n
– simple translator for localized content
The i18n
is an object of the type. It provides access to all the message bundles which 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:
javascript-model-samples.frontend.footer.arbitraryWisdom = You make your own luck javascript-model-samples.frontend.footer.happinessLevel = Random happiness level: {0}%
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
.
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
this.renderBreadcrumbs = function (pageNode) { var res = i18n.translate('javascript-model-samples.frontend.breadcrumbs.start.label'); cmsfn.ancestors(pageNode).forEach(function (anchestorPage) { res += '<span><a href="' + cmsfn.link(anchestorPage) + '">' + evalBreadcrumbItitle(anchestorPage) + '</a></span><span>>></span>'; }); res += '<span>' + evalBreadcrumbItitle(pageNode) + '</span>'; return res; }; function evalBreadcrumbItitle(pageNode) { return (pageNode.navigationTitle) ? pageNode.navigationTitle : (pageNode.title) ? pageNode.title : pageNode["@name"]; }//-
#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:
i18nBasename: info.magnolia.module.form.messages dialog: form:form description: paragraph.form.description modelClass: info.magnolia.module.form.templates.components.FormModel renderType: freemarker title: Form with Javascript Form Processor templateScript: /form/components/form.ftl class: info.magnolia.module.form.templates.components.FormParagraph formProcessors: - name: saveContact enabled: true class: info.magnolia.module.jsmodels.form.JavascriptFormProcessor formProcessorScriptPath: /javascript-model-samples/templates/js/formProcessors/saveContact.js parameters: formEnctype: multipart/form-data areas: fieldsets: description: areas.components.form.fieldsets.description type: list title: areas.components.form.fieldsets.title enabled: true templateScript: /form/generic/listArea.ftl availableComponents: formGroupFields: id: form:components/formGroupFields
- Line 12: The
class
property must have the valueinfo.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:
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; } }
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.
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
.
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.