Rendering

  Written by The Jahia Team
   Estimated reading time:
7.0 7.1 7.2

Introduction

The rendering process is the chain of operations executed by Jahia to extract the desired nodes from the repository and apply treatments until the content (usually an html page) is delivered to the user who requested it. the rendering process is a key mechanism, designed to provide a maximum flexibility to developpers to interact with it and add their own operations during the process.

Components rendering and views

Every component can be rendered by using a view. A View is a script, usually a JSP file.

Using the module tag

The module tag allows to render other components. The path or the node need to be passed.

Templates

A node needs to be associated to a template to be displayed in a full HTML page. A template will define which element to display and where to display them, gathering different component views in one single HTML page.

Templates are created with the Studio. Page templates are specially created for pages, and Content templates are created for all other type of content. The content templates are associated to a node type.

It's possible to limit templates to some users based on permissions. For example, templates showing some private areas or allowing to edit the content can be hidden for normal users.

Selecting a template

A node can be associated to a template - this is the case for all page nodes. A template must be specified at creation time. The template is set in the property j:templateNode, defined by the mixin type jmix:hasTemplateNode

The user can also force the choice of a template by specifiying in the url the name of the template, before the .html extension. For example,

http://localhost:8080/cms/render/default/en/sites/ACME/home/news/maincontent/news_36.content-template.html

will use the template named content-template to display the node news_36.

Default template and priority

Default templates are used automatically, if no specific template has been requested by the user, and if no template is associated to a node. As multiple templates can be used for one single node, and only one need to be selected, a priority can be defined. The template with the highest priority will be selected.

If no template is specified in the URL, no template associated to the node and no default template can be found, the node cannot be displayed and the system will throw a 404 error.

Templates sequence

A template must either have an associated view, or must be contained inside another template. If a view is defined, it will be called to render the template. If not, the parent template will be called instead. This allows to cascade templates, from the most specific template to more global templates, defining standard headers and footers. The top-level template, defining all common areas, is usually called the base template.

cascade.png

The template sequencem that will be used for the rendering, is then defined as :

  • the template view
  • the base template
  • the home template
  • the home page

Calling content from a template

Building a page is a matter of aggregating the template and the requested node. This node is called the main resource, it's the one that we can find in the URL, and which contains the information to be displayed.

When a page need to be built, Jahia will look for the template node. The template node will defined the layout, and which information will be displayed from the main resource.

To use content from the main resource, some components are available in the studio.

Area

Areas can be defined in the JSP views, with template:area tags, or in the templates, with the area component. The goal of an area is to display content coming from a node next in the templates sequence. An area is said to be enabled under one node if the list named like the area is present under that node. For example, an area maincontent defined in the template view is enabled in the main resource if the list maincontent is created under the main resource.

Area are represented as blue blocks in the studio.

For example, let's use the same the same templates sequence as before - template view, base template, home template and home page.

areas.png

  • The template view defines 2 area tags, header and pagecontent.
  • Both of them are enabled in the base template as list, the header list containing one content head content, the pagecontent list containing 2 area nodes, areaA and pagecontent.
  • The home template enables the areaA, containing one content content1.
  • The home page enables the pagecontent area, container one content content2

Absolute area

Absolute area are similar to area, but do not use the templates sequence to resolve the list to display - they use an ancestor of the main resource instead. A level of ancestor can be specified - 0 is for the home page level, 1 for the first sub pages level, etc.. If no level is specified, the content list will be taken from the home page of the site.

Absolute areas are represented as red blocks in the studio.

Main resource display

This component will call a specific view on the main resource node directly. It can be used in content templates only. It's represented in the studio as a grey area. It is used to gather in one complex templates different views of the same node - for example, in the wiki wiki-content template, we can find multiple main resource display components. Here in the history tab, the history and the compare views are displayed one on top of the other :

wiki-content.png

 

Example of rendering sequence

area-display.png

  1. The user request a page
  2. The system first tries to resolve the template. It finds a template home associated to the page.
  3. The home template is contained inside the base template. The system will first render the base template
  4. base template is associated to a view - the file template.myset.jsp - the JSP is called to start building the page.
  5. An area header is found - the system will look sequentially in base, home, and finally in the page to find a list named header. This list is found in the base template, and contains a navigation menu. All nodes here are rendered using their own views.
  6. Another area pagecontent is found. This one does not exist in base, but is present in home. The system will start the rendering of home/pagecontent .
  7. First node found is a jnt:row node. The node is rendered using its own view, row.jsp.
  8. The JSP contains a first area, row-col1. The system finds the area in home - which contains itself an area node named areaA.
  9. The template areaA is found in the page. Content of page/areaA is rendered.
  10. The rendering of row-col1 is over, another area is definied in the row.jsp file : row-col2. row-col2 is found in home, which contains an area
  11. Again, the areaB is found in the page. Content of page/areaB is rendered.

The view row.jsp is fully rendered. As home/pagecontent does not contain any other node, the home/pagecontent area rendering is finished. The view template.myset.jsp finishes its execution. The page is fully rendered.

Filters

Rendering filters are classes that can transform the output of a module. They work like standard servlet filters, except that they are executed independently for every module inclusion. Filters can be executed on all modules, or on some specific module or nodes.

An overview of all currently registered filters can be viewed in a Jahia Tools area JSP when the Jahia is running: http://localhost:8080/tools/renderFilters.jsp.

Configuration

Jahia supports filter configuration using Spring bean definition files.

Default filters

Default filter chain is defined in the applicationcontext-renderer.xml

Every filter needs to define a priority, which defines the order in which the filters will be executed.

<bean class="org.jahia.services.render.filter.portlet.PlutoProcessActionFilter">
    <property name="priority" value="-3"/>
    <property name="applyOnConfigurations" value="page"/>
</bean>
<bean class="org.jahia.services.render.filter.ExternalizeHtmlFilter">
    <property name="priority" value="-1"/>
    <property name="applyOnModes" value="live,preview"/>
    <property name="applyOnConfigurations" value="page"/>
    <property name="htmlExternalizationService" ref="HtmlExternalizationService"/>
</bean>
<bean class="org.jahia.services.render.filter.StaticAssetsFilter">
    <property name="priority" value="0"/>
    <property name="applyOnConfigurations" value="page"/>
    <property name="applyOnTemplateTypes" value="html,edit,html-.*"/>
    <property name="scriptEngineUtils" ref="scriptEngineUtils"/>
    <property name="ajaxTemplate" value="/modules/assets/WEB-INF/scripts/ajaxResources.groovy"/>
    <property name="template" value="/modules/assets/WEB-INF/scripts/resources.groovy"/>
</bean>

Module filters

Any module can define its own filters to be executed. Filters are defined in a Spring bean definition file in an XML file (with any arbitrary name) in the templates/<module-name>/META-INF/spring/ folder. In order to define module filters, you simply need to provide a list of filter beans. The filters will be added to the render chain and executed for every request for all modules.

The priority defines where the filter will be inserted in the render chain. If the priority is > to 16, it will be executed the first time the resource is generated, and then the result will be cached. If the priority is < to 16, it will be reevaluated each time the resource is served. Be careful, this is very consuming in terms of performance.

An example of a filter definition for the Jahia Wiki module, located in the the templates/wiki/META-INF/spring/ folder, is:

<bean name="WikiFilter" class="org.jahia.modules.wiki.filter.WikiFilter">
    <property name="priority" value="90"/>
    <property name="syntaxFactory" ref="defaultSyntaxFactory"/>
    <property name="inputSyntax" value="xwiki/2.0"/>
    <property name="outputSyntax" value="xhtml/1.0"/>
    <property name="applyOnNodeTypes" value="jnt:wikiPage"/>
    <property name="applyOnTemplates" value="syntax"/>
</bean>

Conditional execution of filters

Any filter, extending org.jahia.services.render.filter.AbstractFilter, can accept several types of conditions that control filter execution. If all conditions are matched, the filter is executed. Otherwise it is bypassed and the execution is continued with the next filter in the chain. An example of a filter, which is only executed for nodes of type jnt:wikiContent, was already given above. More conditions can be provided using:

  • applyOnMainResource - the filter will be applied only on the main resource
  • applyOnModules - comma-separated list of module names this filter will be executed for (all others are skipped)
  • applyOnNodeTypes - comma-separated list of node type names this filter will be executed for (all others are skipped)
  • applyOnTemplates - comma-separated list of template names this filter will be executed for (all others are skipped)
  • applyOnTemplateTypes - comma-separated list of template type names this filter will be executed for (all others are skipped)
  • applyOnConfigurations – comma-separated list of configuration this filter will be executed for (all others are skipped)
  • skipOnModules - comma-separated list of module names this filter won't be executed for
  • skipOnNodeTypes - comma-separated list of node type names this filter won't be executed for
  • skipOnTemplates - comma-separated list of template names this filter won't be executed for
  • skipOnTemplateTypes - comma-separated list of template type names this filter won't be executed for
  • skipOnConfigurations - comma-separated list of configuration this filter won't be executed for

More advanced configuration (using regular expressions for matching, NOT conditions or any arbitrary custom condition can be achieved by using filter's conditions property and providing a list of condition beans (instances of classes that implement org.jahia.services.render.filter.AbstractFilter.ExecutionCondition). Please, consult the documentation of org.jahia.services.render.filter.AbstractFilter for more details.

Filter implementation

A filter is single class implementing the org.jahia.services.render.filter.RenderFilter interface.

The interface defines mainly one method to implement:

public String doFilter(RenderContext renderContext, Resource resource, RenderChain chain) throws RenderFilterException;

This method returns the final output after filtering. The chain attribute gives the possibility to get the result generated by the remaining chain. This result can be used as an input for the filter.

If the filter needs to transform the output of the chain, it needs to call:

String out = chain.doFilter(renderContext, resource);

So, a basic filter, replacing all occurrences of letter a by letter b, would look like:

public String doFilter(RenderContext renderContext, Resource resource, RenderChain chain)
        throws RenderFilterException {
    String out = chain.doFilter(renderContext, resource);
    return out.replace("a","b");
}

A filter can also break the chain, by returning its own content and not calling the rest of the chain. The following filter will just return some information on the node that was supposed to be displayed, but won't display the template itself :

public String doFilter(RenderContext renderContext, Resource resource, RenderChain chain)
        throws RenderFilterException {
    return resource.getNode().getPath() + " / " + resource.getResolvedTemplate();
}

An abstract class org.jahia.services.render.filter.AbstractFilter should be used as a base class for a new filter, which allows to specify execution conditions for a filter. In your filter extends the AbstractFilter, then your can implement the following methods:

String prepare(RenderContext renderContext, Resource resource, RenderChain chain) throws Exception;

When a resource is called by the end user, we enter in the prepare method. This method allows to put some information in the scope of the request before the generation of the HTML output.

String execute(String previousOut, RenderContext renderContext, Resource resource, RenderChain chain)
            throws Exception;

After the resource rendering (HTML output generation), we enter in the execute method. It allows to modify the generated HTML fragment before to return it to the end user.

void finalize(RenderContext renderContext, Resource resource, RenderChain renderChain);

Finally, when the fragment is finalized, we enter in the finalize method. It allows to reset some things in the context or to reinitialize some variables. It is usually used by the system. At this step it is no more possible to interact with the generated HTML fragment.

See Conditional execution of filters in this document for more details on how to configure such filters.

Provided filters

Jahia provides some simple filters that can be added to the default configuration.

Email obfuscation

The filter org.jahia.services.render.filter.EmailObfuscatorFilter replaces all mail addresses by entity-encoded values. This protects email addresses harvesting.

Regular expression replacement

The filter org.jahia.services.render.filter.RegexpFilter allows to make any replacement in the output, based on regular expressions. The list of replacements need to be defined in the regexp property. The following example replaces all text inside square brackets with <em> tag:

<bean class="org.jahia.services.render.filter.RegexpFilter" >
    <property name="regexp">
        <map>
            <entry key="\[([a-zA-Z]*)\]">
                <value><![CDATA[<em>$1</em>]]></value>
            </entry>
        </map>
    </property>
</bean>

Script execution info

You can add a scriptinfo request parameter to obtain information about files used in modules to render the content

To activate, add moduleinfo=true in the url parameters

It will surround with a border all modules within a page, displaying an info bullet to get more information (like script used to be displayed, and time to render)

Channels

The ChannelsService takes the list of available channels through multiple configurable providers. The provider are also responsible for the channel detection, based on user agent or other request headers, and can provide a list of capabilities for every channel

A provider should implement the interface ChannelProvider :

public int getPriority();
public Map<String,String> getChannelCapabilities(String identifier);
public String resolveChannel(HttpServletRequest request);
public List<String> getAllChannels();

To detect the channel, the resolveChannel() method will be called on each provider, in priority order, until one returns a valid channel.

The getAllChannels() method should return the list of channels that will be available in the studio and edit mode.

ChannelService will aggregate all capabilities from the different providers by calling the getChannelCapabilities() method.

UserAgentChannelProvider

This simple implementation use an XML spring configuration to list all the possible channels. For each channel, a regular expression on the user agent can be defined for channel detection. This provider is used in the channels module.

<bean class="org.jahia.services.channels.Channel">
    <constructor-arg index="0" value="apple_iphone_ver1"/>
    <property name="capabilities">
        <map>
            <entry key="display-name" value="iPhone"/>
            <entry key="template-type-mapping" value="iphone"/>
            <entry key="variants" value="portrait,landscape"/>
            <entry key="variants-displayNames" value="Portrait,Landscape"/>
            <entry key="usable-resolutions" value="320x356,480x208"/>
            <entry key="device-image" value="/modules/channels/images/devices/iphone-small.png"/>
            <entry key="decorator-images"
                   value="/modules/channels/images/devices/iphone-portrait.png,/modules/channels/images/devices/iphone-landscape.png"/>
            <entry key="decorator-image-sizes" value="388x738,734x383"/>
            <entry key="decorator-screen-positions" value="35x216,124x115"/>
            <entry key="resolution_width" value="320"/>
            <entry key="resolution_height" value="480"/>
            <entry key="userAgentPattern" value=".*iPhone.*"/>
        </map>
    </property>
</bean>

WURFLProvider

This provider, available at https://github.com/Jahia/WURFLProvider , uses the WURFL database to detect devices and get the capabilities. The WURFL provider is able to detect more than 7000 devices, including phones, tablets, browsers, or webTV devices. Note that the devices are not automatically available in edit mode : they have to be declared in an other provider first.

View resolution with channels

When a channel is detected, the system can be used to use another view for a specific module. If a component need to be rendered differently when used on some device, it is possible to create a specific jsp that will be used only for that channel.

The property "template-type-mapping" that is returned in the device capabilities will be appended to the template type for resolving the view. If the view is not found with this specific mapping, it will fallback to the standard view.

On an iPhone device, where template-type-mapping is iphone, the system will try to resolve the view first in html-iphone folder, then in html.

Module exclusion

It is possible to exclude some components for some devices, directly in the studio. In the layout tab, select "Exclude from channels" and select the channels in which the component should not appear.

Channel switching

The component jnt:toggleMobileDisplay can be used in a page to display a link to normal display. This will allows the user to switch from a device-specific view to the generic view.

Visibility framework

The goal of the visibility framework is to allow you to add runtime conditions to define if a node is visible or not to visitors.
By default the only way to define if someone can see or not a node is by assigning specific rights to it. The visibility framework will add rules/conditions to the normal ACLs system. those conditions can be set by authors in the editing engines in a dedicated tab.

Default condition(s)

Jahia provides a default condition that can define the visibility of a node based on the current date, hence doing time based publication. The node will be visible if the current date is after/between start date and after/(or before) end date. The conditions are checked every time we try to access the node, if a node is not visible nobody will see it in the live repository even in administration.

The conditions will not be checked against when in the cache, so you will need to flush the cache when reaching a change of condition that will make a node visible or not. This way the conditions will be reevaluated.

How to write your own conditions

Conditions can be defined in any module, they are initialized through Spring framework. This way they have access to all services. To write your own condition simply implement the interface org.jahia.services.visiblity.VisibilityConditionRule.


/**
* Defines a visibility condition for a piece of content in Jahia.
*/
public interface VisibilityConditionRule {

    /**
     * Returns <code>true</code> if the condition is satisfied and content will be rendered.
     *
     * @param node
     *            the node to test visibility condition for
     * @return <code>true</code> if the condition is satisfied and content will be rendered
     */
    boolean matches(JCRNodeWrapper node);

    /**
     * Return the node type associated with this condition.
     *
     * @return Return the node type associated with this condition.
     */
    String getAssociatedNodeType();

    /**
     * Return the associated display template that will be used by gwt.
     *
     * @return Return the associated display template that will be used by gwt.
     */
    String getGWTDisplayTemplate(Locale locale);
    /**
     * Returns a list of field names, required to display the info.
     *
     * @return a list of field names, required to display the info
     */
     List<String> getRequiredFieldNamesForTemplate();
}

Each condition should be associated with a node type that will provide a way for the end user to interact with it, by filling the associated auto generated form like we do for the workflow.

Example of a condition

Let us review the start/end date condition code.

/**
* This class handle the execution of a start/end date condition for checking a node visibility.
* If the current date is after the start date or if start date is not defined then the node is visible unless
* end date is defined and we are after end date.
*/
public class StartEndDateConditionRuleImpl extends BaseVisibilityConditionRule {
    private transient static Logger logger = LoggerFactory.getLogger(StartEndDateConditionRuleImpl.class);

    /**
     * Return the associated display template that will be used by gwt.
     *
     * @return Return the associated display template that will be used by gwt.
     */
     public String getGWTDisplayTemplate(Locale locale) {
        return JahiaResourceBundle.getString("JahiaVisibility", "label.startEndDateCondition.xtemplate",locale, "Jahia Visibility");
     }

     public boolean matches(JCRNodeWrapper nodeWrapper) {
        Calendar start = null;
        Calendar end = null;
        try {
            start = nodeWrapper.getProperty("start").getValue().getDate();
        } catch (PathNotFoundException e) {
            logger.debug("start is not defined for this rule");
        } catch (RepositoryException e) {
            logger.error(e.getMessage(), e);
        }
        try {
            end = nodeWrapper.getProperty("end").getValue().getDate();
        } catch (PathNotFoundException e) {
            logger.debug("end is not defined for this rule");
        } catch (RepositoryException e) {
            logger.error(e.getMessage(), e);
        }
        Calendar calendar = null;
        try {
            calendar = nodeWrapper.getSession().getPreviewDate();
        } catch (RepositoryException e) {
        }
        if (calendar == null) {
            calendar = Calendar.getInstance();
        }
        if (start != null) {
            if (!calendar.after(start)) {
                return false;
            }
        }
        if (end != null) {
            if (!calendar.before(end)) {
                return false;
            }
        }
        return true;
     }
}

This condition is associated through Spring to this definition.

[jnt:startEndDateCondition] > jnt:condition
- start (date, datetimepicker)
- end   (date, datetimepicker)

Association in the Spring file of the module.

<bean class="org.jahia.modules.visibility.conditions.StartEndDateConditionRuleImpl">
  <property name="associatedNodeType" value="jnt:startEndDateCondition"/>
  <property name="requiredFieldNamesForTemplate">
    <list>
      <value>start</value>
      <value>end</value>
    </list>
  </property>
</bean>

How do you handle multiple conditions on a node ?

Multiple conditions are handle in a very simple way either you have to only match one condition to be visible or you have to match them all. To change the rule simply check/uncheck the conditional visibility checkbox in the engine.

How to flush cache when conditions changed ?

In this example we will use rules to launch Quartz jobs at the specified date (start and/or end) to flush the cache of the node having the condition and its parent also. The jobs are implemented using org.jahia.services.content.rules.BackgroundAction.

rule "Flush caches for start date visibility condition"
    salience 25
    when
        A property start has been set on a node
             - the node has the type jnt:startEndDateCondition
    then
        Execute the action "startDateVisibilityAction" at start on the node
end

The code of the action itself

public class FlushCacheOnNodeBackgroundAction extends BaseBackgroundAction {

    private static Logger logger = LoggerFactory.getLogger(FlushCacheOnNodeBackgroundAction.class);

    private FileCacheManager fileCacheManager;
    private ModuleCacheProvider cacheProvider;

    private int startLevel;

    private int levelsUp;

    public FlushCacheOnNodeBackgroundAction() {
        fileCacheManager = FileCacheManager.getInstance();
    }

    public void setCacheProvider(ModuleCacheProvider cacheProvider) {
        this.cacheProvider = cacheProvider;
    }

    public void executeBackgroundAction(JCRNodeWrapper node) {
        String workspace = Constants.LIVE_WORKSPACE;
        try {
            JCRNodeWrapper currentNode = node;
            workspace = node.getSession().getWorkspace().getName();
            for (int level = 0; level <= (startLevel + levelsUp); level++) {
                if (level >= startLevel) {
                    cacheProvider.invalidate(currentNode.getPath());
                    if (currentNode.isFile()) {
                        fileCacheManager.invalidate(workspace, currentNode.getPath());
                    }
                }
                currentNode = currentNode.getParent();
            }
        } catch (RepositoryException e) {
            //Flush by path directly as node might not be visible anymore
            String currentNode = node.getPath();
            for (int level = 0; level <= (startLevel + levelsUp); level++) {
                if (level >= startLevel) {
                    cacheProvider.invalidate(currentNode);
                    fileCacheManager.invalidate(workspace, currentNode);
                }
                currentNode = StringUtils.substringBeforeLast(currentNode,"/");
            }
        }
    }

    public void setStartLevel(int startLevel) {
        this.startLevel = startLevel;
    }

    public void setLevelsUp(int endLevel) {
        this.levelsUp = endLevel;
    }
}

Then the real actions are instantiated by Spring on module startup

<bean id="abstractFlushCacheOnVisibilityNodeBackgroundAction" parent="abstractFlushCacheOnNodeBackgroundAction" abstract="true">
    <property name="startLevel" value="2"/>
    <property name="levelsUp" value="1"/>
</bean>

<bean parent="abstractFlushCacheOnVisibilityNodeBackgroundAction">
    <property name="name" value="startDateVisibilityAction"/>
</bean>

<bean parent="abstractFlushCacheOnVisibilityNodeBackgroundAction">
    <property name="name" value="endDateVisibilityAction"/>
</bean>

A node can only have one job of the same name at he same time. The job name is determined by the action name, that is why we have one action for start and one for end to allow to have both jobs defined on the same condition.

Lists

Display a list in a generic way.

To identify lists, apply a mixin to the node

[jmix:list] mixin

Sample lists defined in Jahia :

jnt:contentList, jnt:query, jnt:folder

By default, the rendering of this mixin will be used to display the list. This rendering is structured like this:

<template:include view="hidden.header"/>
<c:forEach items="${moduleMap.currentList}" var="subchild" begin="${moduleMap.begin}" end="${moduleMap.end}">
<template:module node="${node}"/>
</c:forEach>
<template:include view="hidden.footer"/>

The hidden.header and hidden.footer views allow to define parameters for the list to be displayed. The hidden prefix allows to not display these views in the list of possible views offered in the edit interface. These parameters are stored in a map accessible by modules named moduleMap.

The default parameters are as follows:

  • currentList : list of nodes to display
  • begin : beginning of a list (integer)
  • end : end of a list (integer)
  • liveOnly : makes an ajax call of the list in live state, cannot be edited in default state
  • subNodesView : this view is used to display sub Nodes
  • editable : allows edition of content (boolean, true by default)
  • emptyListMessage : message to display if list is empty

We define a variable as follows:

<c:set target="${moduleMap}" property="subNodesView" value="${subNodesView}"/>

hidden.header calls a "loader" whose role is to get all nodes to display and store them in ${moduleMap}

We store different types of content,

  • Simple lists : currentList
  • SQL2 requests : listQuerySql
  • QOM requests: listQuery

These requests or lists are evaluated in the hidden.header view.
The hidden.header view allows for managing list sorting and filters.


Summary - for a simple list:

list.jsp

<template:include view="hidden.header"/>
list.hidden.header.jsp
<template:include view="hidden.load"/>
list.hidden.load.jsp
<c:set target="${moduleMap}" property="currentList" value="${jcr:getChildrenOfType(currentNode, jcr:getConstraints(currentNode))}" />
<c:forEach items="${moduleMap.currentList}" var="subchild" begin="${moduleMap.begin}" end="${moduleMap.end}">
    <template:module node="${node}"/>
</c:forEach>

Usage of these views:

Generally speaking, only "loader" is necessary to define a specific list type, it allows to define the elements to be displayed. If there is a request, we'll define listQuery or listQuerySql, if it is sub node, currentList should be used. 

Example: 
To view the list of recent comments as component 
First, we define a type that extends jmix: list
[jnt:latestComment] > jnt:content, jmix:queryContent, jmix:list, mix:title, jmix:renderableList, jmix:studioOnly, jmix:bindedComponent
- j:subNodesView (string, choicelist[templates=jnt:post,resourceBundle,image]) nofulltext  itemtype = layout

Rendering will be performed using a file named jnt_latestComment/html/latestComment.hidden.load.jsp. It will setup the listQuery variable in the moduleMap as illustrated here:

<query:definition var="listQuery"
                  statement="select * from [jnt:post] as comments  where isdescendantnode(comments, ['${renderContext.mainResource.node.path}']) order by comments.[jcr:lastModified] desc"
                  limit="20"/>
<c:set target="${moduleMap}" property="editable" value="false"/>
<c:set target="${moduleMap}" property="listQuery" value="${listQuery}"/>

Menus

Create a menu for your site based on out-of-the-box menus.


In the Studio

Deploy and define a menu (Navigation menu) in your page.

menus_01.png

Properties to set :

  • Baseline Node : Defines the root node of the menu, it is the level 0 of your menu
  • Max depth : Sets how deep the menu will display items.
  • Start level : Sets the level the menu have to start display items
  • view of the menu items : Defines the view used to display menu items (menuElement is the default)
  • style that surround the menu. This style must be declared in your css file. Defines the class around the menu
  • unique <div> ID define in the page to reference this menu. Defines the id around the menu

menus_02.png

Add items from edit mode

When you create one of these :
- Pages
- External link
- Internal links
- Menu labels
you automatically create a menu entry.

menus_03.png

When created, the entry will appear in all menus of the page that matches the menu levels set in the Studio.

If you want that the item is only displayed in certain menus, you need to check the “Select this option if you want to specify the menu where this item should appear”, then select the menu you want.

menus_04.png

Rendering

The node type for menu is jnt:navMenu.

its definition (in modules/default/META-INF/definitions-nav-menu.cnd is :


[jnt:navMenu] > jnt:content, mix:title, jmix:siteComponent
orderable
- j:baselineNode (string,choicelist) < 'home', 'currentPage'
- j:maxDepth (long) = 2
- j:startLevel (long) = 0
- j:menuItemView (string,choicelist[templates='jmix:navMenuItem,menuItem',resourceBundle,image]) = menuElement
- j:styleName (string) nofulltext
- j:layoutID (string) nofulltext

The default associated JSP file is : jnt_navMenu/html/navMenu.jsp in the default module.


An entry is a mixin (jmix:navMenuItem) set on any type.

[jmix:navMenuItem] mixin

The default associated JSP file is

jmix_navMenuItem/html/navMenuItem.menuElement.jsp in the default module.

We must specify a view (default is menuElement) as we want to display the mixin, not the default rendering of the node.

It will search for jmix:navMenuItem under the baseNode, and display items that match the set restrictions (like level or specified menu).

All these menus use the <ul><li> structure

We provide several examples of views for menus :

  • oneLevel : one level menu without any style other than those set by the webmaster
  • simple : recursive menu without any styles (inpath, selected, etc)
  • default : complete menu with styles


Customization

Customization can be done at two different levels: the menu structure itself (navMenu.jsp), and the rendering of items (navMenuItem.menuElement.jsp).

 

Menu structure

You can add a view for your menu (jnt:navMenu) to change its structure or add javascript.

The view will be automatically available in the Layout tab’s view selector of the menu.

You just have to create a new file under the jnt_navMenu/html folder or create this folders tree in your module directory, named navMenu.<yourView>.jsp where <yourView> is your view name.

In a file named navMenu.<yourView>.properties you have to set two properties for this view, as they are needed to manage cache dependency:

cache.mainResource = true
cache.mainResource.flushParent = true


The best practice is to copy one of the predefined menus, which is close to your needs and customize it.


The beginning of the file sets the properties to display the menus. The display part is between the <ul> tags.

Rendering items


The default view for items in a menu is defined in the menuElement view.

The rendering is applied on jmix:navMenuItem, it means the default JSP file used for rendering items will be :

jahia/modules/default/jmix_navMenuItem/html/navMenuItem.menuElement.jsp

You can define your own menu items by adding the jmix:navMenuItem to your type definition.
If you want a custom display, you have to create a new view menuElement for your type definition. For example Jahia uses a custom view for jnt:page items. This choice will let you add properties such as icon (reference to a file) or alternate text for a menu item.
ex :

[jnt:navMenuText] > jnt:content, mix:title, jmix:navMenuItem
+ * (jmix:navMenuItem)

or

[jnt:page] > nt:base, jmix:nodenameInfo, jmix:observable, jmix:basemetadata, mix:title, jmix:publication, jmix:tagged, jmix:navMenuItem, jmix:hasTemplateNode

orderable
- jcr:title (string) i18n mandatory
- j:templateNode (weakreference,choicelist[templatesNode]) mandatory < jnt:template
+ * (nt:base) = nt:base version
+ * (jmix:navMenuItem)

+ * (jmix:navMenuItem) let you add sub menu items under your current menu.

You can also define your own view for items. You must define a default view for it. This view will be available to be set in the jnt:navMenu from the studio. All items in the menu will use the same view.

List filtering

Lists can be filtered when they are rendered based on the value of a property, i.e. only elements which correspond to the filter criteria will be displayed. This is accomplished by adding a filter parameter to the URL of the page containing the list to be filtered. The actual value of the filter parameter specifies how the list will be filtered using a JSON format.
A filter is defined by the combination of the following name / value pairs:
  • uuid: the Universally Unique IDentifier (UUID) identifying which list the filter should operate on
  • op: a predicate that each element of the list with the name property must satisfy to be included in the filtered result. The only supported operation at this time is the equality, specified by the "eq" value. Note that it is also possible to use the negation operator "!" to specify that only elements not satisfying the predicate will be included in the filtered result. To use the negation operator, simply preprend "!" in front of the operation name.
  • value: the value of the property used to filter the list
  • name: the name of the property presumably present on the list elements and on which the filtering will occur
  • type: the expected JCR type of the specified value. Possible values are defined by possible return values of javax.jcr.PropertyType.TYPENAME_* constants. If omitted, the specified property value will be interpreted as a String (javax.jcr.PropertyType.TYPENAME_STRING).
Optionally, you can also pass a format filter attribute used to further refine the evaluation of the filtering value. This is useful in particular to indicate which format is used for Date property values.

Date filtering

sites/ACME/home/page8.html?filter={name:"jcr:created",value:"2010-04-30",op:"eq",uuid:"226a954111-3279-475b-9129-e3110736a565",format:"yyyy-MM-dd",type:"Date"}
The above example specifies that the list identified by the "226a954111-3279-475b-9129-e3110736a565" UUID on the "sites/ACME/home/page8.html" page should be filtered to only keep elements which "jcr:created" property is equal (op attribute is equals to "eq") to "2010-04-30", which is of type "Date" with the "yyyy-MM-dd" format.
Creating a URL to filter a bound component in a JSP
<c:url var="targetURL" value="${url.mainResource}" context="/">
  <c:param name="filter" value='{name="j:tags",value:"${tag.uuid}",op:"!eq",uuid:"${boundComponent.UUID}",type:"${tag.type}"}'/>
</c:url>
The above example creates a URL targeting the list associated with the component bound to the view associated with the JSP, displaying only elements that do are not tagged with the tag associated with the tag identified by the $tag.uuid UUID (an element's tags are children nodes of the "j:tags" property for this element, each a WeakReference to the "real" tag node, therefore "j:tags" is a list of UUIDs, each identifying a tag). Of note, $tag.type is "WeakReference" so that the filter knows to interpret the value attribute as a UUID pointing to a tag.

Macros

Macros allows you to add/process
some dynamic information in your texts.

Defining a macro

All macros are found under the macros directory of your modules. They respect a simple rule: the filename of the macro defines its name.

<module>WEB-INF/macros/username.groovy

This define a macro name username.

How to use a macro

It is as simple as putting in your text a call like this ##macro_name##.

Dear ##username##,

This will render something like, "Dear John Doe," based on the user rendering the page (you have to check the option perUser for the cache in the options panel of your content to be sure that each user see his own name).

How to write a macro

For the macro you can use any scripting language JSR-223 compliant deploy on your platform (by default Groovy, Velocity and Freemarker).

username.groovy

if (currentUser.username.trim().equals("guest")) {
    print PrincipalViewHelper.getUserDisplayName(currentUser.username.trim());
} else {
    String property1 = currentUser.getProperty("j:firstName")
    if (property1 != null)
        print(property1.capitalize() + " ");
    String property2 = currentUser.getProperty("j:lastName")
    if (property2 != null)
        print(property2.capitalize())
    if (property1 == null && property2 == null)
        print(currentUser.getUsername().capitalize())
}

Default bindings available in your macros

Name Class Description
currentNode org.jahia.services.content.JCRNodeWrapper The node we are currently rendering
currentUser org.jahia.services.usermanager.JahiaUser The user currently connected
currentAliasUser org.jahia.services.usermanager.JahiaUser The user currently rendered if not the one connected
renderContext org.jahia.services.render.RenderContext The current context for rendering
currentResource org.jahia.services.render.Resource The resource associated with the current node
url org.jahia.services.render.URLGenerator An URL generator allowing to create your own URL

Default macros

  • ##username## : This macro will display the current user name
  • ##authorname## : This macro will display the author name.
  • ##creationdate## : This macro will display the content creation date.
  • ##keywords## : This macro will display the keywords for the current node.
  • ##devmode## :  This macro will display one or two information about the current content, the content id and/or the content path. e.g.: ##devmode(full)## will render something like, "Content path : /blabla/blabla Content id : balblablabla"
  • ##linktohomepage## : This macro will display a link to home page. e.g.: ##linktohomepage## will render something like, "http://www.site.com/index.htlm", but you can also make a link like this <a href="##linktohomepage##" title="Home Page">Home Page</a> and he will render like nice link, "Home Page".
  • ##userprofiledata## : This macro will display some information about the current profile. Parameter 1 should be a node name like "contents". Parameter 2 should be a properties name like "jcr:created". You can also use only one parameter in your text call like this ##userprofiledata(parameter)##. In this case parameter should be a properties of the current user like "j:firstName". e.g.: ##userprofiledata(contents,jcd:created)## will render something like, "2013-11-05T18:26:10.357+01:00".   ##userprofiledata(j:firstName)## will render something like, "Damien".

User Agent

It is possible to define dedicated templates for specific user agents. If the system finds the request is coming from a configured user agent, it can append a specific classifier to the template type, and will then look the template in another folder.

Configuration

The mapping between user agent and template types is done in the applicationContext-services.xml file, under the UserAgentFilter bean configuration :

    <bean class="org.jahia.services.channels.filters.ChannelResolutionFilter" >
        <property name="priority" value="4" />
        <property name="applyOnModes" value="live,preview" />
        <property name="userAgentMatchingRules">
            <map>
                <entry key=".*iPhone.*" value="iphone" />
                <entry key=".*iPod.*" value="iphone" />
            </map>
        </property>
    </bean>

The userAgentMatchingRules attribute defines a list of regular expression and the associated template type extension. Here, all user agents matching .*iPhone.* and .*iPod.* will have an iphone template type classifier.

Templates lookup

When using the previous configuration, for any request coming from an iPhone, the system will try to find templates for iPhone. If a standard .html request was done, it will first lookup the template into the html-iphone folder, and will then fallback on the standard html folder if the template is not available. This is done for every node and wrapper included in the page: a page can mix user-agent specific and generic templates.