Component controllers

Contents

Special controllers used to render page components.

Component controllers are only used in context of the Site Engine

Introduction

For a hands on introduction to rendering pages with the Enonic framework, visit the My first site tutorial.

Component controllers are 1:1 aligned with their respective page component definitions and must also be placed in specific locations within the code structure.

They have the same capabilities as regular controller, you may take advantage of page contributions, and the fact that they are always executed in the context of a content item.

Markup response from a component must have a single root element.

Context

Component rendering is initiated by the Site Engine. The controllers are derived when a client requests a specific page via the site engine:

HTTP request/site/<project>/<branch>/path/to/contentresolve componentexecute controller.

This means you will always have a specific content item in your context.

API functions such as portalLib.getContent(), and portalLib.getComponent() will automatically return values based on the context.

Thanks to the site engine’s built-in access control you are also guaranteed that the current user has read permissions to the contextual content item.

Page controllers

Page controlles must be placed next to your page descriptor i.e. src/main/resources/site/pages/<page-name>/<page-name.js>.

Sample page controller using the context
const portal = require('/lib/xp/portal');

exports.get = function(req) {

  const content = portal.getContent();
  return {
    body: `<html>
            <body>
              <h1>${content.displayName}</h1>
            </body>
          </html>`
  };
};

Part controllers

Part controlles must be placed next to your part descriptor i.e. src/main/resources/site/parts/<part-name>/<part-name.js>.

Sample part controller using the form value
const portal = require('/lib/xp/portal');

exports.get = function(req) {

  const config = portal.getComponent();
  return {
    body: `<div>
              <h1>${config.heading}</h1>
          </div>`
  };
};

Layout controllers

Layout controlles must be placed next to your layout descriptor i.e. src/main/resources/site/layouts/<layout-name>/<layout-name.js>.

Sample layout using Thymeleaf to render two regions
var portal = require('/lib/xp/portal');
var thymeleaf = require('/lib/thymeleaf');

exports.get = function (req) {
    var component = portal.getComponent();

    return {
        body: thymeleaf.render(resolve('./layout-two-column.html'), {
            leftRegion: component.regions["left"],
            rightRegion: component.regions["right"],
        })
    };

};
Thymeleaf view to render layout
<div class="row">
  <div data-portal-region="left">
    <div data-th-each="component : ${leftRegion.components}" data-th-remove="tag">
      <div data-portal-component="${component.path}" data-th-remove="tag" />
    </div>
  </div>

  <div data-portal-region="right">
    <div data-th-each="component : ${rightRegion.components}" data-th-remove="tag">
      <div data-portal-component="${component.path}" data-th-remove="tag" />
    </div>
  </div>
</div>

Regions

Component hierarcies are created via regions. Rendering a region essentially boils down to rendering the components of that region.

To render a component, simply leave a component placeholder in your page or layout’s response markup. Component placeholders are then identified and rendered by the component rendering step of the site engine.

As components may contain new regions, with new placeholders, this process will be repeated until there are no more placeholders left in the response.

Component Placeholders

Placeholders are formatted as HTML comments - and includes the component path. Like this example:

<!--# COMPONENT /main/0/left/1 -->

For text/html responses, the site engine will look for component placeholders. For each placeholder, the component’s controller is executed and the output merged into the response.

JavaScript example

A basic approach to rendering the main region of a page might look something like this:

Manually create component placeholders
var portal = require('/lib/xp/portal');

exports.get = function(req) {

  // Get components of the main region
  const content = portal.getContent();
  const components = content.page.regions["main"].components || [];

  // Render with placeholders
  return {
    body: `<html><head>Placeholder demo</head>
            <body>
              <main data-portal-region="main">
                ${components.map((c) => `<!--# COMPONENT ${c.path} -->`).join('\n')}
              </main>
            </body>
          </html>`
  };
}

Templating example

Using Thymeleaf templating, placeholder generation might look like this:

Page controller passing region to template
var thymeleaf = require('/lib/thymeleaf');

exports.get = function(req) {

  var portal = require('/lib/xp/portal');

  // Get the contextual content item
  var content = portal.getContent();
  var mainRegion = content.page.regions["main"];

  // Render and return the result
  return {
    body: thymeleaf.render(view, mainRegion);
  };
}
Thymeleaf template creating placeholders
<!DOCTYPE html>
<html>
  <head>
  </head>
  <body>
    <h1>Page with placeholders</h1>
    <main data-portal-region="main">
      <div data-th-each="component : ${mainRegion.components}" data-th-remove="tag">
        <div data-portal-component="${component.path}" data-th-remove="tag" />
      </div>
    </main>
  </body>
</html>

Fragments

Fragments essentially rendered via their existing controllers. However, as fragments are just subsets of a page, the will likely be rendered without any styling, header or footer.

To mitigate this, the recommended approach is adding a controller mapping to help spice up the visual appearance.

Add a controller mapping for the portal:fragment content type. This will then be executed every time a fragment is rendered.

site.xml
<mapping controller="/site/pages/default/default.js">
  <match>type:'portal:fragment'</match>
</mapping>
It can often be convenient to reuse your page controller, as this is already doing the job.

In the markup produced by the controller, leave a placeholder for the fragment:

<!--# COMPONENT / -->

The site engine will then render the fragment’s components here.

Using Thymeleaf, the placeholder may be generated with the following line of code:

<div data-portal-component="fragment" data-th-remove="tag"/>

Text controllers

Unlike the other components types, Text components are built-in to XP. This includes a standard controller that will render the component.

Optionally bypass the built-in controller by running your own code rather than creating a placeholder for the text components.

Contents

Contents