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 |
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 |
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 |
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 |
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.
<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. |
<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.
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:
|
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>
.
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.
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
.
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.
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. |
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.