Page components

Contents

Page components

Pages are composed from a set of editorially manageable components.

Regions

Regions are not strictly components themselves, but enable placement of components, kind of like a folder. Regions have a fixed name, that is known to the component the region belongs to.

In Content Studio, regions are visually displayed as "dropzones" for components.

To exist, regions must actually be defined within another component. This definition is placed within the application code. Only page and layout components support regions.

Page

The page component is always the root entry of any component structure. A page component may define zero, one, or multiple regions. Each region must have a unique name.

Conventionally, pages define a single region called "main".

To create a page component you must create the following files and folders in your project structure:

page component project structure
/src/
  main/
    resources/
      site/
        pages/
          <component-name>/
            <component-name>.js
            <component-name>.xml

Replace <component-name> with your preferred name.

The name you choose for a component will be used in the underlying data of the stored page. So choose your name wisely.

Page descriptor

The xml file placed in the component structure serves several purposes.

Example page descriptor
<page>
  <display-name i18n="component.page.name">My first page</display-name> (1) (2)
  <description>Front page of our site</description> (3)
  <form/> (4)
  <regions>
    <region name="main"/> (5)
  </regions>
</page>
1 display-name provides a display name used by the editorial interface
2 display-name/i18n optionally specify localization key
3 description Description field shown when creating a part in content studio
4 form allows the definition of a configuration form based on the schema system
5 region optionally specify regions for the page

Page controller

A page controller handles requests to the page. The controller is a required file written in JavaScript and must be named [page-name].js. A controller exports a method for each type of HTTP request that should be handled. The handle method has the request object as a parameter and returns the response object.

Example page controller
// Handles a GET request
exports.get = function(req) {}

// Handles a POST request
exports.post = function(req) {}

Here’s a simple controller that acts on the GET request method.

Example simple get request
exports.get = function(req) {

  return {
    body: '<html><head></head><body><h1>My first page</h1></body></html>',
    contentType: 'text/html'
  };

};

Render-view

If you feel like concatenating strings to create an entire web page is a little too much hassle, Enonic XP also supports views. A view is rendered using a rendering engine; we currently support XSLT, Mustache and Thymeleaf rendering engines. This example will use Thymeleaf.

To make a view, create a file my-first-page.html in the view folder.

Example view file
<!DOCTYPE html>
<html>
  <head>
  </head>
  <body>
    <h1>My first page, with a view!</h1>
  </body>
</html>

In our [page-name].js file, we will need to parse the view to a string for output. Here is where the Thymeleaf engine comes in. Using the Thymeleaf rendering engine is easy; here is how we do it.

Example controller with thymeleaf
var thymeleaf = require('/lib/xp/thymeleaf');

exports.get = function(req) {

  // Resolve the view
  var view = resolve('/site/view/my-first-page.html');

  // Define the model
  var model = {
    name: "John Doe"
  };

  // Render a thymeleaf template
  var body = thymeleaf.render(view, model);

  // Return the result
  return {
    body: body,
    contentType: 'text/html'
  };

};

Unlike controllers and descriptors, view files can reside anywhere in your project and have any valid file name. This allows for code reuse as multiple page components can share the same view. If the view file is in the same folder as the page controller then it can be resolved with only the file name resolve('file-name.html'). Otherwise, the full path should be used, starting with a '/' as in the example above.

Dynamic-content

We can send dynamic content to the view from the controller via the model parameter of the render function. We then need to use the rendering engine specific syntax to render it. The controller file above passed a variable called name and here is how to extract its value in the view using Thymeleaf syntax.

<!DOCTYPE html>
<html>
  <head>
  </head>
  <body>
    <h1>My first page, with a view!</h1>
    <h2>Hello <span data-th-text="${name}">World</span></h2>
  </body>
</html>

More on how to use Thymeleaf can be found in the official Thymeleaf documentation

Regions

To be able to add components like images, component parts, or text to our page via the Page Editor drag and drop interface, we need to create at least one region. Regions can be declared in the page descriptor. Each region will be referenced by name.

Example regions in a [page].xml
<page>
  <display-name>My first page</display-name>
  <config />
  <regions>
    <region name="main"/>
  </regions>
</page>

You will also need to handle regions in the controller.

Example page controller using regions
var portal = require('/lib/xp/portal');

// Get the current content. It holds the context of the current execution
// session, including information about regions in the page.
var content = portal.getContent();

// Include info about the region of the current content in the parameters
// list for the rendering.
var mainRegion = content.page.regions["main"];

// Extend the model from previous example
var model = {
    name: "Michael",
    mainRegion: mainRegion
};

To make the Page Editor understand that an element is a region, it needs an attribute called data-portal-region with value being name of the region.

Example view using regions
<!DOCTYPE html>
<html>
  <head>
  </head>
  <body>
    <h1>My first page, with a view!</h1>
    <h2>Hello <span data-th-text="${name}">World</span></h2>
    <div 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>
    </div>
  </body>
</html>

We can now use the Page Editor drag and drop interface to drag components into our page.

Layout

Layouts are used in conjunction with regions to organize the structure of the various component parts that will be placed on the page via Page Editor drag and drop. Layouts can be dropped into the page regions and then parts can be dragged into the layout. This allows multiple layouts (two-column, three-column, etc.) on the same page and web editors can change things around without touching any code. Making a layout is similar to making pages and part components. Layouts cannot be nested.

Layout contains - like pages and parts - a descriptor, a controller and a view, and should be placed in the folder site/layouts/[layout-name]

Descriptor

The layout descriptor defines regions within the layout where parts can be placed with the Page Editor. The file must be named [layout-name].xml.

Example layout descriptor
<layout>
  <display-name>70/30</display-name>
  <config/>
  <regions>
    <region name="left"/>
    <region name="right"/>
  </regions>
</layout>

View

A layout view defines the markup for the layout component. The sample view below is created in Thymeleaf, but it could be created in any view engine that is supported.

Example layout defined in Thymeleaf
<div class="row">
  <div data-portal-region="left" class="col-sm-8">
    <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" class="col-sm-4" >
    <div data-th-each="component : ${rightRegion.components}" data-th-remove="tag">
      <div data-portal-component="${component.path}" data-th-remove="tag" />
    </div>
  </div>
</div>
The HTML generated for the layout view must have a single root element.

Styling

For a layout to have any meaning, some styling must be applied to the view. The desired CSS should be placed in the /assets folder of the application, and included in the page where the layout should be supported. For example, the view my-first-page.html supports Bootstrap layouts:

Example page descriptor
<head>
    <meta charset="utf-8"/>
    <meta name="viewport" content="width=device-width, initial-scale=1.0"/>
    <link data-th-href="${portal.assetUrl({'_path=css/bootstrap.min.css'})}" href="../assets/css/bootstrap.min.css" rel="stylesheet"/>
</head>

Contents