Thymeleaf integration

WebCmsModule provides its own Thymeleaf dialect for working with components. It enables you to:

  • replace template fragments by component output

  • auto-create components from templates based on the template content

  • define placeholder content

The Thymeleaf dialect is a very powerful tool to convert your static templates into user modifiable components.

HTML attributes

Interacting with components is done by adding attributes to your template elements.

wcm:component

The body of the element will be replaced with the rendering of the component, if the component is found.

wcm:scope

Specify the name of the scope in which the component should be looked for - if omitted wil look in the default scope and all parent scopes. When specified, only that scope will be queried unless search-parent-scopes is explicitly present.

wcm:search-parent-scopes

Set explicitly to false if you only want the component to be looked up in the scope specified (or the default scope) but none of the parent scopes. Otherwise adding the attribute will ensure that parent scopes are always searched, even if an explicit wcm:scope was specified.

wcm:always-replace

If present the body of the element will always be replaced even if the component is not found. The actual (optional) value of this attribute is irrelevant.

Default behaviour is to render the original template markup if a component is not found, use this attribute to force empty output instead.

wcm:auto-create

If present, the component will be auto-created with the element body as input parameters. Optionally the attribute value can be be the scope name in which the component should be created. If no value is present, the value of wcm:scope will be used to determine the scope in which to create the component.

wcm:type

Type key of the WebCmsComponentType of the component. This will only be used for auto-creation of a component. If no type is specified, generic markup (html) will be assumed.

wcm:parent-create-include

Relevant only during auto-creation of a possible parent component. If the parent component is being auto-created nested components would by default be inserted as content markers or added as ProxyWebCmsComponentModel members. By providing this attribute (the actual value is ignored) you indicate the output of the component should be inserted instead.

wcm:parse-placeholders

If present on an element attributed with wcm:component, the body of the element will always be parsed and all sections marked wcm:placeholder will be made available in the WebCmsPlaceholderContentModel.

wcm:placeholder

Attribute to indicate that a section should be rendered to a placeholder. The name of the placeholder is the value of the attribute. Note that the element itself will also be part of the placeholder content, not just the body of the element.

wcm:prop:{propertyName} / wcm:property:{propertyName}

Attribute to set the property of a component. The key contains the name of the property on the component and the value of the attribute will be applied to the property when the component is auto-created.

wcm:meta:{propertyName} / wcm:metadata:{propertyName}

Attribute to set a property on the metadata attached to a component. The key contains the name of the property on the metadata attached to the component and the value of the attribute will be applied to the property when the component is auto-created.

wcm:attr:{attributeName} / wcm:attribute:{attributeName}

Attribute to register the value to either a property or metadata property on the component. The value of the attribute will be applied to the property when the component is auto-created. If the attribute name matches a property of the component, it will be applied to that property. If not, it will be treated as a metadata property.

Rendering a component

You can specify a component to render by just setting the name of the component as the value of the wcm:component attribute.

Simple rendering of component
<div wcm:component="my-component">This markup will only be rendered if that component does not exist.</div>

In the example above the entire component hierarchy for the request will be searched bottom-up for a component with that name. Only if that component does not exist will the original template be rendered. If the component is found, the content of the div element will be replaced, but the div element itself will remain.

You can still use the standard Thymeleaf dialect (eg. th:block or th:remove) to manipulate the wrapping element.
Replace the component body if the component is not found
<div wcm:component="my-component" wcm:always-replace>This markup will never be rendered.</div>

By adding the wcm:always-replace attribute you can ensure the original template markup will be suppressed. Even if the component is not found, the body of the div element will be empty.

Specifying a scope for the component

In our original example all scopes will be traversed bottom-up to find the component. If you only want to look for the component in a specific scope, simply set the wcm:scope attribute.

<div wcm:component="my-component" wcm:scope="global">Replaced by a globally shared component.</div>

When set, the component will be looked for only in that scope (unless you also set wcm:search-parent-scopes). In our example we look for a component 'my-component' in the set of shared components.

A WebCmsComponentModel is also a ViewElement and can always be rendered using the across:view element from the AcrossWebDialect as well.

Examples

This section contains a number of real-life examples for working with components from Thymeleaf.

<th:block wcm:component="page-header" wcm:scope="page" wcm:search-parent-scopes="false">
    <div class="l-page__title">Our title</div>
    <p>
        Our text
    </p>
</th>

Start by looking in the scope of the page asset, don’t look in the parent components even if you don’t find them in the page scope. If the component is not found, render the code that is written down in the thymeleaf template.

<th:block wcm:component="page-header" wcm:scope="page">
    <div class="l-page__title">Our title</div>
    <p>
        Our text
    </p>
</th>

Start by looking if the component exists in the page asset, but look in the parent scope(s) if you don’t find the component in the page scope. If the component is nowhere to be found, the content in the thymeleaf template itself is rendered.

<th:block wcm:component="page-header" wcm:scope="global">
    <div class="l-page__title">Our title</div>
    <p>
        Our text
    </p>
</th>

Look in the global scope for the component, skipping over any and all more granular scopes. If the component is nowhere to be found, the content in the thymeleaf template itself is rendered.

Auto-create a component from markup

The presence of wcm:auto-create will automatically create a component for you if it does not yet exist.

<div wcm:component="my-component" wcm:auto-create>This markup will only be rendered if that component does not exist.</div>

In our example we now create 'my-component' upon first rendering of the template. Because we did not specify an explicit component type, the default type will be used: a HTML TextWebCmsComponentModel will be created. The processed body of the div element will be set as the content of our text component.

As with the component type, because we did not specify an explicit scope, the component will be added to the default scope: usually the asset being rendered.

Specifying component type

Adding a component type is done with the wcm:type attribute.

<div wcm:component="my-component" wcm:auto-create wcm:type="rich-text">This markup will only be rendered if that component does not exist.</div>

We still create a TextWebCmsComponentModel, except it will now be of rich-text type. The value of the wcm:type attribute must be a known WebCmsComponentType type key.

The component type you want to create must have a registered WebCmsComponentAutoCreateStrategy for auto-creation to be successful.

Specifying creation scope

The wcm:auto-create attribute can optionally have a value.

<div wcm:component="my-component" wcm:auto-create="global">This markup will only be rendered if that component does not exist.</div>

In our example we look for my-component in the default scope and all its parents (including global). If the component is not found, we now auto-create it in the global scope instead of the default.

You can combine the use of wcm:scope with a scope in wcm:auto-create. Be careful though because if you auto-create the component in a scope that is in fact not searched for the component, you will re-create on every request.

Specifying properties and metadata

Several attributes are available to specify the value for properties of the component or metadata attached to the component.

<img wcm:component="someImage" wcm:type="image" wcm:auto-create
     wcm:prop:sortIndex="10" wcm:attr:title="Image example"
     wcm:meta:altText="An image of a deer" wcm:metadata:image="classpath:installers/deer.jpg" />

In the above example a component will be created of which both properties and metadata properties will be set on creation. In this case, the properties sortIndex and title of the WebCmsComponentModel will be modified as well as the properties altText and image on the metadata instance attached to the component.

Properties can also be defined using a nested element syntax for more complex use cases, for example:

<div id="attribute-elements" wcm:component="attribute-elements" wcm:type="image-with-alt" wcm:auto-create>
    <wcm:component-attribute name="sortIndex" value="12"/>
    <wcm:component-attribute th:if="${false}" name="sortIndex" value="13"/>
    <wcm:component-property name="title" th:text="'Updated component title'">Component title</wcm:component-property>
    <wcm:component-metadata name="altText"><strong th:text="${5+6}"></strong>[[${'numbers'}]]</wcm:component-metadata>
    This content should never show...
</div>

Auto-creation rules and scopes

  • if you don’t specify a component type - the default component type will be created

  • if you don’t specify a scope on a component - a default scope will be used:

    • outside a parent component the default scope will be asset, and parents will be searched if not specified within the asset scope.

    • inside a parent component that is not a container: the default scope will be asset and parent scopes will be searched if not specified

    • inside a parent component that is a container: the default scope will be the container and parents will not be searched

  • if your component is a container:

    • any placeholders in the body will be added as placeholder child components and the body markup will refer to those components

    • any scoped component will be added as a proxy component referring to the original scoped component and the body markup will refer to the proxy components

Special scopes:

  • default: always refers to the lowest level - first one to perform the lookup (usually asset) - you would not normally use this scope name as it is the default if a scope name is omitted

  • asset: components attached to the current asset being rendered, usually an asset specific name (e.g. article) will be available as well, but both will refer to the same components

  • global: always refers to the global scope, components that have no owner and are shared across domain

  • domain: components that have no owner but are only available on the specific domain

  • container: this is a reserved scope name that ensures the component is added as a container member - you would not normally use this scope name as it is the default if omitted inside a container component

NOTE In a single domain configuration, scope domain will always be the same as scope global.

Using placeholders in a markup component

You can define placeholder sections in your template and allow other components to include them. Using placeholders is handy for fixed dynamic content that is not a component in itself, but you would like to provide some flexibility on positioning the content.

<div wcm:component="my-component" wcm:parse-placeholders>
    Template content...
    <div wcm:placeholder="my-placeholder">Placeholder content</div>
</div>

If you want your component to access placeholder content from the template, you must attribute your component element with wcm:parse-placeholders. When present, the original template markup will always be processed to generate the placeholder content. There is no limit to the number of placeholders defined in a segment, but be aware that those placeholders are only available within that section (eg. during the rendering of my-component).

Any element attributed with wcm:placeholder defines placeholder content. The attribute value is the name of the placeholder.

The element on which the attribute is present is also part of the placeholder content. In the example above the placeholder content would be: <div>Placeholder content</div>.

Rendering placeholder content in markup components

The presence of wcm:parse-placeholders ensures that placeholder content will be processed and made available during rendering. Rendering the actual placeholder however is always up to the component.

Markup components can render placeholders by using placeholder content markers.

Assume my-component is a TextWebCmsComponentModel with the following content:

My placeholder: @@wcm:placeholder(my-placeholder)@@
My other placeholder: @@wcm:placeholder(my-other-placeholder)@@

Upon rendering the template fragment specified above, the following output would be the result:

<div>
    My placeholder: <div>Placeholder content</div>
    My other placeholder:
</div>

Because there is no placeholder content my-other-placeholder defined, an empty string is rendered.

Example auto-creation of markup with a placeholder

When rendering an existing component all markup outside the placeholders is simply ignored. When auto-creating the component however, that markup is still used to generate the default content of the component.

Assume we auto-create our component:

<div wcm:component="my-component" wcm:parse-placeholders wcm:auto-create>
    Template content...
    <div wcm:placeholder="my-placeholder">Placeholder content</div>
</div>

This would result in a TextWebCmsComponentModel with the following content:

Template content...
@@wcm:placeholder(my-placeholder)@@

Nesting components

Like with placeholders, a markup component can include other components using component content markers.

Assume you have a TextWebCmsComponentModel with the following content: My component: @@wcm:component(header,global,false)@@.
And on the global level the header component is a TextWebCmsComponentModel with content my header.

When rendering the first component, the output would be My component: my header.

Component content marker parameters

A component content marker always requires 3 attributes that are equivalents of the Thymeleaf dialect attributes:

  • component name (equivalent of wcm:component)

  • initial scope to look for the component (equivalent of wcm:scope)

  • true/false if parent scopes should or should not be searched (equivalent of wcm:search-parent-scopes)

If a component is not found, an empty string is added to the output and the marker removed.

Auto-create nested components

When nesting components in template markup, nested components will always be replaced by a component content marker.

The following markup:

<div wcm:component="my-component" wcm:auto-create>
    My title: <span wcm:component="title">title</span>
</div>

Would result in a TextWebCmsComponentModel with the content My title: <span>@@wcm:component(title,default,true)@@</span>.

Because my-component is not a container, component title will not get auto-created unless it is in turn attributed with wcm:auto-create.
Including nested component output

In some cases you don’t want to include a content marker for another component, but include the actual component output instead. You can do so by adding the wcm:parent-create-include attribute.

Let’s change our example markup to the following:

<div wcm:component="my-component" wcm:auto-create>
    My title: <span wcm:component="title" wcm:parent-create-include>title</span>
</div>

Assume component title is a TextWebCmsComponentModel with Some title as content. Upon first rendering my-component would get created with the title component output included: My title: <span>Some title</span>.

Auto-create a simple container

Apart from simple markup components like TextWebCmsComponentModel you can also auto-create ContainerWebCmsComponentModel components.

Let’s change our example markup to the following:

<div wcm:component="my-container" wcm:type="container" wcm:auto-create>
    <th:block wcm:component="title">Title</th:block>
    <th:block wcm:component="body">Body</th:block>
</div>

Rendering the above example will create my-container as a ContainerWebCmsComponentModel. The container will have 2 members: title and body, both being TextWebCmsComponentModel implementations with their respective processed template markup as content.

Because title and body are component children within a container type, they do not require the wcm:auto-create attribute themselves. It is assumed they should be created automatically as members of the container - if the container itself gets auto-created.

Nesting container components

You’re not limited to using a single level of containers for auto-creation. Consider the following example:

<div wcm:component="my-container" wcm:type="container" wcm:auto-create>
    <th:block wcm:component="title">Title</th:block>
    <th:block wcm:component="body" wcm:type="container">
        <th:block wcm:component="intro">Intro</th:block>
        <th:block wcm:component="main-text">Main text</th:block>
    </th:block>
</div>

In this case the following components would be created:

  • my-container as ContainerWebCmsComponentModel

    • member: title as TextWebCmsComponentModel

    • member: body as ContainerWebCmsComponentModel

      • member: intro as TextWebCmsComponentModel

      • member: main-text as TextWebCmsComponentModel

No additional wcm:auto-create attributs are required as all nested components have a container as direct parent.

Using placeholders in containers

Much like a regular markup component, a container can also use placeholders that are defined in the template. Where a markup component uses a placeholder content markers to render the placeholder content, a ContainerWebCmsComponentModel will get a member component of type PlaceholderWebCmsComponentModel instead.

<div wcm:component="my-container" wcm:type="container" wcm:auto-create>
    <th:block wcm:component="title">Title</th:block>
    <div wcm:placeholder="body">
        <div wcm:component="footer" />
    </th:block>
</div>

This would auto-create the following components:

  • my-container as ContainerWebCmsComponentModel

    • member: title as TextWebCmsComponentModel

    • member: body as PlaceholderWebCmsComponentModel with body as the placeholder name

In the above example the footer component reference is outside of the container section as it is inside the placeholder block. This means that all ties with the container will be severed: the normal scope lookup will apply and the component will not get auto-created unless it also has the wcm:auto-create attribute.

Linking to other components

A ContainerWebCmsComponentModel can hold ProxyWebCmsComponentModel members that refer to other components that are not container members. If your template refers to a scoped component inside a container, a proxy will get auto-created as well.

<div wcm:component="my-container" wcm:type="container" wcm:auto-create>
    <th:block wcm:component="title">Title</th:block>
    <th:block wcm:component="footer" wcm:scope="global">Replace by the global footer</th:block>
</div>

In this case member footer would be a ProxyWebCmsComponentModel that is linked to the global component with the name footer. If the global footer component is not found however, no member would have been created either, as a proxy only links to an existing component.

You can of course still auto-create the global component as well - just as if it were outside a container:

<div wcm:component="my-container" wcm:type="container" wcm:auto-create>
    <th:block wcm:component="title">Title</th:block>
    <th:block wcm:component="footer" wcm:scope="global" wcm:auto-create>Replace by the global footer</th:block>
</div>

Now the global footer component would first get created if it doesn’t exist yet and then a proxy member would be added to the container.

Containers with markup

Unless a container has markup support active, all template markup outside of wcm:component blocks will simply be ignored. If markup is supported however, the markup will contain component content markers much like in the case of markup components.

However, in the case of container markup, only component content markers will be added that refer to container members. The container members in turn might be ProxyWebCmsComponentModel instances or might be PlaceholderWebCmsComponentModel instances.

A full example for a markup supporting container:

<div wcm:component="my-container" wcm:type="markup-container" wcm:auto-create>
    Title: <th:block wcm:component="title">Title</th:block>
    <div wcm:placeholder="body">
        <div wcm:component="footer" wcm:scope="global" />
    </th:block>
    Footer: <th:block wcm:component="footer" wcm:scope="global">Replace by the global footer</th:block>
</div>

This would auto-create:

  • my-container as ContainerWebCmsComponentModel

    • member: title as TextWebCmsComponentModel

    • member: body as PlaceholderWebCmsComponentModel with body as the placeholder name

    • member: footer as ProxyWebCmsComponentModel linked to the global footer component (same as is rendered inside the placeholder)

The markup of my-container would only link to container members:

Title: @@wcm:component(title,container,false)@@
@@wcm:component(body,container,false)@@
Footer: @@wcm:component(footer,container,false)@@

Custom rendering of component

Web components in Thymeleaf are rendered using a WebCmsComponentModelRenderer implementation. You can easily create your own implementation for custom rendering:

  • create your own implementation of WebCmsComponentModelRenderer

  • implement the supports() method to ensure it is used for the correct types

  • register your implementation as a bean so it can be picked up by the rendering infrastructure

If you want to override the default rendering, you must ensure your implementation is registered before the default implementations. You can do so by ordering your beans (using an @Order annotation or implementing the Ordered interface).

If you want to use content markers in your content snippets, you can use the WebCmsComponentContentModelWriter to render the content with Thymeleaf.

Custom content markers

Content containing content markers can easily be written to Thymeleaf output using the WebCmsComponentContentModelWriter. If you want to implement your own custom content markers you must provide an implementation of WebCmsComponentContentMarkerRenderer as a bean.