Magnolia 5.6 reached end of life on June 25, 2020. This branch is no longer supported, see End-of-life policy.
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.
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.
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
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();
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 definitionCurrent
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
– contextContext 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 stateThe 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 contentThe 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");
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.
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
.
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>
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
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).formProcessorScriptPath
property contains the path to the JavaScript of the processor.JavaScript-based form processor:
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.
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");
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");
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();
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.
The module provides two ways to limit the "power" of a JavaScript model.
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.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 };
#test
and #test2
methods (see lines 2, 7 in the Nashorn code above).#test3
method, although on line 14 Nashorn assigns an object of the type javax.jcr.Session.
2 Comments
marcel koch
Since it doesn't seem to work on my side. Do I have to provide module descriptor (module.yml) to activate the support for modelClass option?
Thx for your help.
Christoph Meier
Hi Marcel
There is no reason to have a module descriptor concerning the usage of the JS-models.
Can you provide some more informations?
Error-log message (which you please paste to a gist or pastebin and add a link here) would be nice.
What magnolia version are you using?