Skip to content
OpenCms documentation
OpenCms documentation

The task of formatters is to present contents in a nice layout and to offer layout configuration possibilities to the content editors. When realising a website project, the most requirements lay in the area of formatter JSPs and formatter configurations. Thus, OpenCms provides many utilities that should make the development of formatters easy, the most important presented here.

Below is an example content that is used throughout this chapter to learn the basics of writing a formatter. The example is a simple section content with three content fields—Title, Text, and Image. Although we do not care about the internal content definition details at this point, we should know that fields in OpenCms are typed: Title is a simple string value, Text contains HTML with inline markup, and Image is a complex type with child elements. The fields are contained in a Section wrapper element with a language attribute making it possible to store section contents in different languages. There are additional rules regarding the structure: in the example we assume that Title and Text are required elements that always exist, whilst Image is an optional element that may be completely missing. We assume that Title in contrast to Text may not be empty.

<SectionData>
  <Section language="en">
    <Title>My title</Title>
    <Text><p>Lorem ipsum dolor sit <strong>amet</strong>.</p></Text>
    <Image>
	  <path>/sites/examples/.galleries/Example-Gallery/opencms-logo.svg</path>
	</Image>
  </Section>
  <Section language="de">...</Section>
</SectionData>

The example content is just an illustration. Contents in OpenCms are indeed stored as XML contents in a very similar way. What is important to know when writing formatters is, though, that contents in OpenCms are generally stored in a semi-structured, typed, and in a multilingual way, optionally, with additional rules for data integrity.

A formatter JSP's task is to transform an XML content into nicely styled HTML markup. It does this by reading data fields from an XML content with a path-like syntax, storing the read data into a so called value wrapper and by inserting those data into the HTML markup.

Say we have the following formatter JSP:

<%@page buffer="none" session="false" trimDirectiveWhitespaces="true"%>
<%@ taglib prefix="cms" uri="http://www.opencms.org/taglib/cms"%>
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core"%>
<cms:formatter var="content">
  <div class="block">
    <h3>${content.value.Title}</h3>
    <h3>${content.value['Title']}</h3>
    <p>${content.value.Text}</p>
    <p>${content.localeValue['en']['Text']}</p>
    <img src="${content.value['Image/path']}">
    <pre>${content.filename}</pre>
  </div>
</cms:formatter>

A formatter JSP at first is a standards compliant JSP file. The JSP standard tag library (JSTL) is available by default by just defining the respective <%@taglib%> directives at the beginning. In addition to the standard tag library there is a <cms:*> tag library available.

A formatter JSP can be recognized by the OpenCms specific <cms:formatter> tag. The var attribute in the <cms:formatter> tag binds a variable that holds an XML content as explained above in an internal JSP bean representation. With content.filename, for example, the bean would return the file system path where the XML content is stored, whilst content.value gives access to all the content's data fields.

Inside the <cms:formatter> tag, there must be exactly one outer HTML tag. It does not matter if the HTML tag is a <div>, like in the example, or another HTML tag, but it must be only one to be rendered correctly. It is a best practice to give the outer HTML tag a common class name—class="block" in the example above—for all your formatters available in a template. The reason is that, on a webpage, there is usually not only one content rendered on a page but many contents of different types, one below the other. With the common class name in the outer HTML tag, you can handle margins nicely. Normally there is a defined space between the content elements, but not at the end of the last element, for example.

Values can be accessed in the way defined by the JSP expression language (EL) and with a OpenCms specific path syntax. If the data field is a simple data field with one atomic value only, for example, the Title field and its string value, one can access the value either with the EL dot notation content.value.Title or with the EL bracket notation content.value['Title']. If the data field is a complex type with child elements, the inner child element can be accessed with a OpenCms specific path expression content.value['Image/path'].

Internally, this way of data access is handled by a content access bean and a value wrapper bean. The beans offer many convenience functions for XML to HTML transformation. For example, they provide null safe access to data fields. You do not have to care whether the Image element exists when accessing the Image/path child element. Also, if you compare the output of content.value.Text and conten.localeValue['en']['Text'] you will see that the beans are aware of the current user's language context, and so on.

A fundamental task of a formatter JSP is to deal with empty or not existing data fields. In the example XML content we assume that Title is a required element and may not be empty, whilst Text may be empty but must exist, and Image may not exist at all. Empty data fields have sometimes the unpleasent characteristic to disturb the layout of the HTML markup, for example, by leaving unwanted empty spaces. When developing a formatter, it is thus especially important to test with contents that have empty or not existing fields. The value wrapper bean offers utility functions to deal with missing elements and empty values. Of special flexibility is the isSet utility function as shown in the example below. It tests for empty values, whitespace only, null, and existence for simple types in the same way as for complex types.

With <cms:formatter value="value">, there is a shorthand syntax available to access content values. The short syntax replaces the constantly repeating content.value.* notation into value.* only as shown in the example below:

<%@page%>
<cms:formatter var="content" value="value">
  <div class="block">
    <h3>${value.Title}</h3>
    <c:if test="${value.Text.isSet}">
      <p>${value.Text}</p>
    </c:if>
    <c:if test="${value.Image.isSet)}">
      <img src="${value['Image/path']}">
    </c:if>
  </div>
</cms:formatter>

A very useful thing is that there can be more than one formatter for a content type. If you have a look at one of the many modern CSS frameworks out there, you will find certain UI components recurring that are typical of today's web design: cards, panels, alerts, media lists, and so on. Those UI components are entirely different in their display. If one has a closer look at them, though, one will notice that they often consist of a combination of always the same data fields: a short title, a longer text that may have inline formattings, an image (maybe the thumbnail of a video), and a link. Some of the UI components may omit the image or the link and consist of a title and a text only. But in abstract terms, the combination of these four data fields can be called a common ground of web UI components.

It is good practice for OpenCms template development to keep content types as abstractly as possible and leave the display to the formatters. What does this mean? Instead of defining a card content type, a panel content type, an alert content type, and a media list content type, it is better to create one section content type—defining a title, text, image, and link field—and then to equip this content type with different formatters: a cards formatter, a panels formatter, an alerts formatter, and a media list formatter.

Which formatter JSP can display which content types is configured in so called formatter configurations. Have a look into the formatters/ folders in the examples module.

Formatters in OpenCms are stored in modules, by convention in a folder named formatters/. It is a common approach to name the formatter configuration and the formatter JSP in such a way that both, the content type, and the form of display are present in the file name. In our example, you would choose the file names as follows, for example:

formatters/section-card.xml
formatters/section-card.jsp
formatters/section-alert.xml
formatters/section-alert.jsp

And so on. The following two example JSPs illustrate a card formatter and an alert formatter, both presenting the same section content in two different ways. 

Card formatter

<cms:formatter var="content" val="value">
  <div class="card block">
    <c:if test="${value.Image.isSet}">
      <div class="card-image">
        <figure class="image">
          <img src="${value.Image.toImage}"/>
        </figure>
      </div>
    </c:if>
    <div class="card-content">
      <div class="content">
        <h2>${value.Title}</h2>
        <p>${value.Text}</p>
      </div>
    </div>
  </div>
</cms:formatter>

Alert formatter

<cms:formatter var="content" val="value">
  <div class="notification is-primary">
    <button class="delete"></button>
    <h3>${value.Title}</h3>
    <p>${value.Text}</p>
  </div>
</cms:formatter>

Whilst a content type can be displayed in different variations with the help of different formatters, the formatters themselves can have display variations. A simple example: an alert UI component typically occurs in four different states—success, warning, info, error—with each state represented by a another color like green, orange, blue, and red.

Combinations of design variations for a UI component can be realized with the help of Element settings. Element settings are defined in formatter configrations. The easiest way to develop element settings is by opening a formatter configuration in the OpenCms workplace. Open the explorer, navigate to the formatter configuration, right click on the file and choose "edit".

<Setting>
  <PropertyName>alert-state</PropertyName>
  <DisplayName>Alert state</DisplayName>
  <Widget>select</Widget>
  <Default>has-background-success-light</Default>
  <WidgetConfig>has-background-success-light:Success|
      has-background-danger-light:Danger|
      has-background-warning-light:Warning|
      has-background-info-light:Info</WidgetConfig>
</Setting>

The example shows element settings for the four states an alert component can have. An element setting consists of an internal key (PropertyName), a human readable name (DisplayName), and can have a default value (Default). Numerous widgets are availabel to make element settings easy to edit for the content editor. In the example, a select widget is choosen from which's value list the content editor can select an alert state.

The WidgetConfig element contains configurations that are widget specific. The syntax for the select widget, as shown in the example, has the form of key-value pairs such as has-background-success-light:Success separated by a pipe symbol (|). Defined in such a way, the content editor is offered a drop down list in which she or he can select the desired alert state. The state selected can finally be read out in the formatter JSP as follows: 

<cms:formatter var="content" val="value">
  <div class="box block ${cms.element.settings['type']}">
    <h3>${value.Title}</h3>
    <p>${value.Text}</p>
  </div>
</cms:formatter>

In the simple example, the settings key such as has-background-success-light just serves as an HTML class name turning the HTML alert into the according state.

Inline editing is a special feature since not supported by every content management system. When editors initially create a new content, the form editor is used in OpenCms. At an advanced stage of content authoring though, when editors need to make minor textual corrections only, it is of great help if an inline editor is available. While the form editor interrupts the flow of reading and correcting each time, with an inline editor, content editors can stay in the flow.

Since it is relatively easy to enable inline editing, and the feature is of great help for content editors, you should not miss this opportunity when writing a formatter JSP.

As a third attribute in the <cms:formatter> tag, the rdfa attribute now comes into play. rdfa is an abbreviation for RDF anntotation. The RDF annotation denotes that a certain HTML markup tag belongs to a certain XML content element storing the field value. In this way, OpenCms can turn a certain HTML markup tag that displays a content field into an inline editing field and at the same time knows where to store a field value when edited inline.

The example below uses rdfa annotations for three content elements—Title, Text and Image.

In the case of textual fields such as Title and Text, the rdfa annotations must be placed directly at the HTML tags surrounding the textual content values. When hovering over a textual value where inline editing is enabled, a pencil is shown to the content editor. A click into the textual value turns the text output into a text input field.

For nontextual fields such as the Image field, the rdfa annotation is placed at some ancestor <div> tag and results in an edit point at the right-hand side of the inline editor. When hovering such a right-hand edit point, the RDF annotated block element is getting highlighted.

Inline editing works for non empty fields only.

In declaring the rdfa attribute directly in the formatter tag, the short syntax rdfa.* instead of the longer syntax content.rdfa.* is available, as with the value attribute above.

<cms:formatter var="content" val="value" rdfa="rdfa">
  <div class="content block">
    <div ${not value.Image.isSet ? rdfa.Image : ""}>
      <c:if test="${value.Image.isSet}">
        <img src="${value.Image.toImage}" ${rdfa.Image}/>
      </c:if>
    </div>
    <div>
      <h2 ${rdfa.Title}>${value.Title}</h2>
      <div ${rdfa.Text}>${value.Text}</div>
    </div>
    <hr>
  </div>
</cms:formatter>

Image drag-and-drop is another helpful feature that can ease content authoring significantly. In some phases of a website's livetime it may be necessary to replace many or maybe even all images of a website. Inline editing would only be of small help here, because a dialog still has to be opened for each image. This again disturbs the workflow of content editors. With the image drag-and-drop feature, an image on a web page can be replaced directly with another image from the gallery dialog by dragging and dropping. This means that the gallery dialog has to be opened once only, and after that, all images on a page can be replaced conveniently.

Since it is relatively easy to enable image drag-and-drop, and the feature is of great value for content editors, you should not miss to support this opportunity in your formatter JSP.

Image drag-and-drop can be enabled via a content.imageDnd[content.value.Image.path] annotation, either on the <img> tag itself or at a surrounding <div> tag. It has always the form ${content.imageDnd[<XPath to the schema element of the image>]} where path is the one you get via content.value.<ImageElement>.path.

As with inline editing, image drag-and-drop only works for nonempty image fields.

<cms:formatter var="content" val="value" rdfa="rdfa">
  <div class="block">
    <div ${content.imageDnd[value.Image.path]}>
      <img src="${value.Image.toImage}" ${rdfa.Image}/>
    </div>
    <hr>
  </div>
</cms:formatter>

Responsive images are central to web design. The images of a website should be delivered in different sizes depending on the size of the client device. This saves bandwidth and achieves better loading times and ensures that—in the case of larger images—the central motif is always visible on small screens. For the latter, scaling alone is not sufficient, but an image cropping method is needed.

OpenCms integrates a server-side image processing library to scale and crop images. The image processing library is available by default. No additional plugin must be installed. Image manipulation features are available for the content editor as well as for the template developer. The content editor can use the image library in the form of an image editing tool integrated into the gallery dialog, the template developer in the form of a JSP image bean.

The common approach with an OpenCms website is that content editors at first upload an image in a as good as possible quality. The resolution of the uploaded image may be even too high for web presentation. The content editor can crop the image with the help of the image editing tool and in this way offers different image cutouts for differen client devices, for example. The template developer in the next step ensures the technical optimization of image sizes.

The example below shows a formatter JSP that uses the server-side image processing library. The example assumes an XML content with two images available, one cropped image for small devices (<800px) and one for larger devices (>=800px). The first <source> element provides an image for small screens. The second <source> element holds the original landsape image as shown on larger screens.

<cms:formatter var="content" val="value">
  <c:set var="imageBeanCropped" value="${value.ImageCropped.toImage}"/>
  <c:set var="imageBean" value="${value.Image.toImage}"/>
  <c:set target="${imageBeanCropped}" property="srcSets" value="${imageBeanCropped.scaleWidth[250]}" />
  <c:set target="${imageBeanCropped}" property="srcSets" value="${imageBeanCropped.scaleWidth[350]}" />
  <c:set target="${imageBean}" property="srcSets" value="${imageBean.scaleWidth[500]}" />
  <c:set target="${imageBean}" property="srcSets" value="${imageBean.scaleWidth[700]}" />
  <div class="clearfix">
    <picture>
      <source media="(max-width: 799px)" srcset="${imageBeanCropped.srcSet}">
      <source media="(min-width: 800px)" srcset="${imageBean.srcSet}">
      <img src="${imageBean.src}" alt="">
    </picture>
  </div>
</cms:formatter>

Formatters are not only the relevant tool for displaying contents containing text and images, but also for displaying responsive layouts such as grids or columns. Text-image contents like cards or sections can then be inserted into such responsive layouts.

The technique for implementing responsive layouts with formatters is called Nested container.

Say we have a layout XML content like this:

<LayoutData>
  <Layout language="en">
    <Title>Row 2 columns (3 - 9)</Title>
    <Variant>3-9</Variant>
  </Layout>
</LayoutData>

The XML content in principle has the same structure as the section content above. Though, the XML content this time does not contain textual and image data, but the configuration of a page layout where Title contains a human readeble description of the page layout and Variant a machine readable code. The layout is designed such that it all in all spans 3 + 9 = 12 grid cells. In the very example, the left column spans 3 grid cells and the right column spans 9 grid cells. There might be more such layout contents with variants such as 12, 6-6, 4-4-4, 3-3-3-3, 9-3 that together would provide the repertoire of responsive page layouts available for a website.

A formatter JSP transforming the layout content into the according HTML markup could look like this:

<cms:formatter var="content" val="value">
  <c:choose>
    <c:when test="${value.Variant eq '12'}">
      <div class="columns">
        <div class="column is-full">
          <cms:container name="maincol" type="element"/>
        </div>
      </div>
    </c:when>
    <c:when test="${value.Variant eq '3-9'}">
      <div class="columns">
        <div class="column is-one-quarter">
          <cms:container name="leftcol" type="element">
            <div class="notification">Drop new contents of type element here.</div>
          </cms:container>
        </div>
        <div class="column">
          <cms:container name="rightcol" type="element">
            <div class="notification">Drop new contents of type element here.</div>
          </cms:container>
        </div>
      </div>
    </c:when>
    <c:otherwise>Unknown layout variant.</c:otherwise>
  </c:choose>
</cms:formatter>

The example JSP uses a CSS framework where the is-full class name results in a full-width column. The is-one-quarter class name makes the left column occupying 3 of 12 grid cells whilst the right column automatically occupies the remaining 9 cells, without a class name given.

The <cms:container> tag is a nested container. It is the area where content editors can drop new contents. Make sure that the name of the container is unique for one hierarchy level of the nested container structure. leftcol and rightcol are the names for the 3-9 layout variant example. When realizing a 3-3-3 variant, for example, col1, col2, col3 would be suitable container names.

With the type attribute—type="element" in the example, which is a freely choosen name—you can help the content editor with content nesting. It is the aim to allow any content type, card or alert for example, to be inserted into a layout column. But probably it is not intended that a layout column can be inserted into another layout column again. With the type attribute of <cms:container> you can control the nesting of elements.

Use the formatter configurations to configure content nesting. Say we have the following formatters:

formatters/section-card.xml (element)
formatters/section-alert.xml (element)
formatters/layout.xml
(layout)
formatters/layout.jsp (element)

The layout.jsp would contain <cms:container type="element"> definitions, and section-card.xml and section-alert.xml would define the element container type too. layout.xml defines the layout container type and thus cannot be inserted into another layout. 

JSP caching is probably one of the most powerful internal features OpenCms offers but is also one of the most difficult to understand. Why is caching important? Think of a normal HTML page consisting of a header, a body, and a footer. The output of an HTML page is often not longer than several hundred lines of HTML code. Be aware, though, that in a content management system, an HTML page is dynamically composed on request by many different data:

  • Step 1: when calling some index.html page, a container page is loaded that stores which text-image and layout contents appear on a page in which order, in which nesting, and with which formatting; the appearance of a (nested, formatted) content on a page is called a container element
  • Step 2: the content, the formatter configuration including (default) element settings are loaded for each container element
  • Step 3: finally the formatter JSP is evaluated against the referenced content, the formatter, and the element settings in the locale the user did request

It is obvious that this request chain needs time. Let it be only 20 container elements on a page, this can already mean several hundred database queries, parsing steps, and transformations per user request. To guarantee good performance, caching is a must.

As a template developer, you should not rely on the existence of an external website cache, but use the OpenCms internal Flex cache. The flex cache is an in-memory key value store that saves the finally generated HTML markup of a formatter JSP as the value. The key encodes all the context information (locale specific content values, formatter, element settings, and so on) that led to a particular HTML output.

The flex cache offers a defined set of cache directives to react on context information. For formatter JSPs, these are the most relevant directives:

  • container-element: a cache entry is created for each combination of content, formatter, and element settings
  • locale: a cache entry in addition is created per user locale
  • uri: a cache entry in addition is created per requested URI

Cache directives are set for each formatter JSP individually with the Properties dialog. There is a property named cache that expects a list of flex cache directives separated by semicolon. A typical flex cache setting for a formatter JSP looks like this: 

container-element;uri;locale

There is a Cache app available with which one can understand the creation of flex cache entries. Note that the flex cache is active for published contents only. As long as you are logged into the workplace, the flex cache is not active and you cannot check your cache settings.

There are numerous situations where the container-element;uri;locale setting is not enough for a formatter JSP. Let's say, your formatter JSP creates some HTML output depending on a request parameter like this:

<cms:formatter>
  <div>${param.name}</div>
</cms:formatter>

The JSP snippet would produce different HTML markup depending of the value of the name request parameter. In this case, the formatter JSP in addition to to container-element;uri;locale would declare a params directive.