Web components

1. Web component model

Web CMS Components are the building block of the pages that are displayed for the end user. Support is provided for a user friendly way of manipulating the content of those components via the back end.

1.1. WebCmsComponentType

1.2. WebCmsComponent

WebCmsComponent

persisted entity

2. WebCmsComponentModel

WebCmsComponentModelService and WebCmsComponentModel WebCmsComponentType

The persisted entity is implemented by WebCmsComponent, but usually you will want to work with the WebCmsComponentModel. The latter represents the entire strongly typed model ready for rendering, whereas the former only has the raw attributes.

converted to a WebCmsComponent by a WebCmsComponentModelWriter converted from a WebCmsComponent by a WebCmsComponentModelReader

2.1. Default component types

WebCmsModule contains a number of default component types, backed by a set of default WebCmsComponentModel implementations. You can easily define your own component types by extending one of these base types, see the chapter Defining component types.

The following component types are added with the default data:

Type key Description Base type 1 WebCmsComponentModel

proxy

A proxy for another component that will be rendered instead.

proxy

ProxyWebCmsComponentModel

placeholder

Represents a placeholder for content provided by code or templates.

placeholder

PlaceholderWebCmsComponentModel

container

Container of multiple ordered components. The user can add or remove child components to the container.

container

ContainerWebCmsComponentModel

fixed-container

Container of a fixed set of components. The user can not add or remove child components to the container.

fixed-container

ContainerWebCmsComponentModel

text-field

A single line of plain text.

plain-text

TextWebCmsComponentModel

plain-text

Plain text component.

plain-text

TextWebCmsComponentModel

rich-text

Rich text component - supporting HTML markup and default assets if enabled by other modules.

rich-text

TextWebCmsComponentModel

html

A snippet of HTML.

markup

TextWebCmsComponentModel

image

Represents a WebCmsImage asset.

image

ImageWebCmsComponentModel

1 The Base type is the value of the type attribute on the WebCmsComponentType. Every component type must have a unique type key, but several component types can have the same base type.

See WebCmsComponentUtils for some utility functions when working with components and component types.

2.1.1. WebCmsComponentModel implementations

All component models must extend WebCmsComponentModel. A number of default implementations is provided by the WebCmsModule. It is usually the WebCmsComponentType base type (type attribute) that will determine the WebCmsComponentModel that gets instantiated.

TextWebCmsComponentModel

Represents a text component that can be either plain text, rich text or markup. In all cases an optional profile is supported that can indicate the sub-types of content that are supported.

A TextWebCmsComponentModel is a large body of text (the content property) that should be escaped or not upon rendering. The other properties are mostly relevant for the administrative user interface.

TextWebCmsComponentModel content can contain content markers.

Base types

The following base types will create a TextWebCmsComponentModel:

  • plain-text: will escape its content when rendering

  • rich-text: unescaped content with a RTE in the administration UI

  • markup: unescaped content with a HTML editor in the administration UI

Component type attributes

TextWebCmsComponentModel implementations check extra WebCmsComponentType attributes to map their properties.

Attribute Description

multiLine

If set to false, the component will behave as a single line of text. In all other cases it will behave as a multi-line text component.

rows

Size indicator: number of rows the component should have by default. Only applicable in case of a multi-line component.

profile

Optionally specify an additional text profile this component should use. The text profile can for example be used to determine settings of the rich text editor.

ContainerWebCmsComponentModel

A ContainerWebCmsComponentModel represents an ordered collection of other components (the container members). By default a container simply renders its individual members in order.

Markup containers

Containers can optionally also support markup (determined by the supportsMarkup attribute on the actual WebCmsComponentType). If markup is set, it represents the rendering template for the container and members should be rendered using content markers.

Within container markup, component content markers can use the container scope to reference a member of the container itself.

Base types

The following base types will create a ContainerWebCmsComponentModel:

  • container: allows dynamic addition/removal of members

  • fixed-container: holds a fixed set of members (useful as a base type for custom components)

Component type attributes

ContainerWebCmsComponentModel implementations check extra WebCmsComponentType attributes to map their properties.

Attribute Description

supportsMarkup

If set to true then markup on the container will be used for rendering if there is markup set.

PlaceholderWebCmsComponentModel

A PlaceholderWebCmsComponentModel has only a single relevant property: the name of the placeholder whose content should be rendered. By default if no placeholder content is available, this component will render an empty string.

Base type: placeholder

ProxyWebCmsComponentModel

A ProxyWebCmsComponentModel holds a reference to another component (the proxy target) and renders that component instead. Use a ProxyWebCmsComponentModel if you want to add an already existing component as a member of a container.

Base type: proxy

ImageWebCmsComponentModel

An ImageWebCmsComponentModel holds a reference to a WebCmsImage asset. Default rendering of an ImageWebCmsComponentModel is not very useful and developers should provide their own rendering by either updating the WebCmsComponentType with a custom template or by providing the rendering infrastructure (see Thymeleaf rendering).

For the ImageWebCmsComponentModel to function, a valid WebCmsImageConnector bean should be present. See the chapter Image model for more information.

3. Activating component support

Any WebCmsObject - this includes all assets and components themselves - can have web components linked to it. Enabling components for WebCmsObject implementations: wire WebCmsObjectComponentViewsConfiguration: registerComponentsAssociation

4. Rendering components

4.1. Creating shared web components

Table 1. Shared web components
Name Type Mandatory Description Default Value

Component Type

Component Type

Yes

Type of the component, selectable from the list of predefined component types.

None

Title

String

Yes

The name of the component. Displayed in the admin section.

None

Name

String

Yes

The unique identifier with which you can approach the component in your Thymeleaf template. Should be unique, and can be auto-generated based on Title

None

4.2. Using components in your controllers

A WebCmsComponentModelHierarchy is attached to the ApplicationContext of the current request and allows components to be looked up in the various scopes. By default the global and domain scope are registered. Using the WebCmsComponentModelHierarchy, it is possible to register additional scopes, attach/replace/remove components to a scope and define aliases for a scope.

The WebCmsComponentModelHierarchy is used to retrieve components in the thymeleaf dialect and is used to search for components in various scopes.

Note: The WebCmsComponentModelHiercharchy is a request scoped bean, so it can simply be autowired. It should however not be wired inside a controller handler method as it has a parameterless constructor.

Example of registering a custom component to the asset scope:

@Controller
@RequiredArgsConstructor
public class MyController {

  private final WebCmsComponentModelHierarchy componentModelHierarchy;

  @WebCmsArticleMapping(articleType="blog")
  public void myHandler(){
    WebCmsComponentModelSet componentModelSet = new WebCmsComponentModelSet();
    TextWebCmsComponentModel text = new TextWebCmsComponentModel();
    text.setContent("some text");
    text.setName("my-custom-component");
    componentModelSet.add(text);
    componentModelHierarchy.registerComponentsForScope(componentModelSet, "asset");
  }
}

4.3. Placeholders

WebCmsPlaceholderContentModel

4.3.1. Content markers

WebCmsModule provides content marker infrastructure to work with special marker snippets inside textual content. Upon rendering, these markers would be processed and the resulting output would be rendered instead.

Example text with content markers
Sample content markers
Insert component content: @@wcm:component(title,global,false)@@
Insert placeholder content: @@wcm:placeholder(myplaceholder)@@

Two default markers are supported:

  • placeholder content markers indicate placeholder content should be rendered at the marker’s location

  • component content markers indicate a component should be rendered at the marker’s location

Content marker format

A WebCmsContentMarker consists of 2 parts:

  • a key indicating the type of content marker - the key is required and cannot contain whitespace

  • an optional string of parameters that is supported by that type

In text a content marker is always surrounde by @@.

Example content markers:
  • @@my.marker@@

    • key: my.marker

  • @@my.marker(my, params)@@

    • key: my.marker

    • parameter string: my, params

The WebCmsContentMarkerService is the service for working with content markers in general. Default rendering support is available for Thymeleaf templates, see the WebCmsModule Thymeleaf dialect.

Content markers are supported in all default component types.

Placeholder content markers

A placeholder content marker indicates that placeholder content should be rendered at the marker’s location. The key is wcm:placeholder and the parameter is the name of the placeholder whose content should be rendered.

Example: @@wcm:placeholder(some.placeholder)@@

Component content markers

A component content marker indicates a component should be rendered at the location of the marker. The key is wcm:component and 3 parameters are required:

  • the component name

  • the scope in which to look for the component

  • should parent scopes be searched as well: true/false

Whitespace is not supported in the parameter string.

Examples:

  • @@wcm:component(my.component.name,default,true)@@

    • will search for my.component in the default scope and all parent scopes

  • @@wcm:component(my.component.name,global,false)@@

    • will search for my.component in the global scope only (as global is the last scope the parent flag is of no meaning)

  • @@wcm:component(my.component.name,container,false)@@

    • will search for my.component in the container members - only relevant in the context of a ContainerWebCmsComponentModel being rendered

Creating custom content markers

See WebCmsModule Thymeleaf dialect on how to add content marker support for component rendering.

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

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

Auto-creation of components from markup

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

4.4.2. Examples

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

  • Only look in the scope of the page asset. If not present, don’t do anything.

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

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.

Auto-create a markup component

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.

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)@@

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

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

6. Defining component types

You can easily define your own component types. Depending on your needs you can extend one of the base types or create a fully custom implementation.

6.1. Extending the base types

You can add a custom WebCmsComponentType by importing it via YAML or by adding it via the WebCmsComponentTypeRepository. Component types to import should be defined in the section types - component where the key of a component type definition is the`unique WebCmsComponentType typeKey.

Example YAML component type definition
types:
  component:
    my-component:
      name: Custom component
      description: Custom HTML component with additional metadata.
      attributes:
        type: markup
        metadata: "my.component.MyMetadata"

A WebCmsComponentType has a collection of attributes. This is a map of String key/value pairs. Adding attributes is a way to provide processing metadata without having to create custom WebCmsComponentType implementations.

Default attributes

All base types support 3 default attributes that pack a lot of punch in creating custom component types:

Attribute name Description

type

Contains the base type for the component type.

Examples: container, markup …​

template

Optional: identifier of the (Thymeleaf) template that should be used for rendering the resulting WebCmsComponentModel for a component of this type.

Example: th/mymodule/mytemplate :: fragment

metadata

Optional: name of the class that should be used for the component metadata.

Example: my.module.components.MyComponentMetadata

Custom template

Any component type can have a template attribute set that identifies the (Thymeleaf) template that should be used for rendering the component instances. If a template is specified it will always be used for rendering and any other WebCmsComponentModelRenderer for that component type will be skipped.

You can customize rendering the default WebCmsComponentModel implementations just by specifying a custom template.

Property renderTemplate on WebCmsComponentModel allows you to set a custom template per-component instead of per component type. If a renderTemplate is set on the component itself, it will take precedence over the WebCmsComponentType template attribute. Setting renderTemplate is not enabled by default in the administration UI.
Metadata

Any WebCmsComponentModel has a metadata property that refers to an object that has all custom metadata for a component. The type of metadata object is configured by setting the class name as the metadata attribute on the WebCmsComponentType. The metadata instance will always be created as a prototype using the BeanFactory and as such supports autowiring other beans from the ApplicationContext.

A metadata class must:

  • be serializable to and from JSON using Jackson ObjectMapper

    • Jackson annotations on the metadata class are supported if necessary to define (de-)serializer rules

    • special care should be taken not to serializer any beans that might have been wired

  • implement a valid equals() method in order to have change detection and better import/export performance

  • be easily cloneable:

    • have either a no-arguments public constructor and provide setters/getters for all relevant properties

    • OR implement EntityWithDto or Cloneable

If the administration UI is active, a form for metadata properties will automatically be generated using the EntityModule. This does require you to register the metadata type as an entity in the EntityRegistry.

Default (de-)serialization for WebCmsObject implementations is provided by the WebCmsModule by dispatching to the WebCmsDataConversionService. This means metadata properties can often refer to other web cms types without requiring custom configuration.

See creating a custom component type if you want to have a custom metadata class, change serialization or admin UI rendering.

Providing a component template

When defining a custom component type, you can link a componentTemplate component to the WebCmsComponentType definition. If present, that component will be used as a template for a new component of your custom component type. Class properties as well as any child components will be copied to a new component your type.

When your component type and componentTemplate are a container type, any TextWebCmsComponentModel member of the template component can have special markers in its content. These markers will be replaced when copying the template to a newly created component:

  • @@container.name@@ will be replaced with the name of the newly created container

  • @@container.title@@ will be replaced with the title of the newly created container

The componentTemplate should have the same class implementation as the component type being defined. For example when creating a fixed-container, the componentTemplate must implement ContainerWebCmsComponentModel. Providing custom metadata is not supported on the template component, the metadata will be reset when creating a new component.

6.1.1. Example: custom container component

This is an example of a custom component that is a fixed container with 2 member components. A custom metadata class allows configuring the position of the members (left or right), the custom template uses the metadata to determine what the output should be.

Component definition
types:
  component:
    left-right:
      name: Left or Right
      description: Renders 2 components in a specific layout.
      attributes:
        type: fixed-container
        template: "th/mymodule/components :: left-right"
        metadata: "mymodule.LeftRightMetadata"
      wcm:components:
        componentTemplate:
          componentType: container
          wcm:components:
            one:
              componentType: rich-text
              title: One
              sortIndex: 1
              body: "@@container.name@@"
            two:
              componentType: rich-text
              title: Two
              sortIndex: 2
              body: "@@container.title@@"

When creating a new left-right component the members of componentTemplate will be cloned into the new container. The text components one and two will get their default content set with respectively the name and title of the new left-right component.

The sortIndex will determine the order of the components in the administration UI.

Metadata class
@Data   // Use Lombok @Data to generate getters, setters and equals() method
public class LeftRightMetadata
{
    enum Layout
    {
        LEFT_TO_RIGHT,
        RIGHT_TO_LEFT
    }

    /**
     * Determines the order of rendering one and two.
     */
    @NotNull
    private Layout layout = Layout.LEFT_TO_RIGHT;
}

// Register the metadata class as an entity as to activate the administration UI
@Configuration
@ConditionalOnAdminUi
class LeftRightConfiguration implements EntityConfigurer {
    @Override
    public void configure( EntitiesConfigurationBuilder entities ) {
        entities.create().entityType( LeftRightMetadata.class, true );
    }
}

The metadata only has a single property layout. When the administration UI is active (presence of EntityModule and AdminWebModule) the layout value can be selected in the user interface. The default administration UI uses the the EntityModule to build the metadata form, so we register the LeftRightMetadata as an entity.

If you do not want to use the EntityModule to generate the metadata form, you can provide a custom WebCmsComponentModelMetadataAdminRenderer. See create a custom component type for more information.
Thymeleaf template: th/mymodule/components
<th:block th:fragment="left-right(component)"
          th:with="metadata=${component.metadata}">
    <section th:if="${metadata.layout.name() eq 'LEFT_TO_RIGHT'}">
        <div class="left"><across:view element="${component.getMember('one')}" /></div>
        <div class="right"><across:view element="${component.getMember('two')}" /></div>
    </section>
    <section th:if="${metadata.layout.name() eq 'RIGHT_TO_LEFT'}">
        <div class="left"><across:view element="${component.getMember('two')}" /></div>
        <div class="right"><across:view element="${component.getMember('one')}" /></div>
    </section>
</th:block>

The Thymeleaf template inspects the metadata layout property and renders members one and two in a fixed location. As a WebCmsComponentModel is a ViewElement using an across:view node takes care of rendering the member components.

6.2. Filtering selectable WebCmsComponentType options

To filter the provided dropdown options for a WebCmsComponent, the user can perform two actions:

  • options that should never be present by default

  • selected set of options that should be present.

These actions are provided through the DefaultAllowedComponentTypeFetcher. Should you like to provide your own logic to filter the selectable options, for one or more types, you will have to provide your own implementations of the WebCmsAllowedComponentTypeFetcher interface.

6.2.1. Non-selectable WebCmsComponentTypes

To prevent a WebCmsComponentType from showing up in the list unless explicitly specified, the user has to provide the component type with the componentRestricted attribute, with its value set to true. This ensures that unless the WebCmsComponentType is specified as an option for the WebCmsObject through a WebCmsTypeSpecifierLink, it will never be shown as an option.

Examples of non-selectable WebCmsComponentTypes are the default placeholder, proxy and fixed-container component types.

Example
types:
  component:
    fixed-container:
      objectId: "wcm:type:component:fixed-container"
      name: Fixed container
      description: Container of a fixed set of components.
      attributes:
        type: fixed-container
        componentRestricted: true
  • A fixed-container should never be available as a selectable option unless specified (through a WebCmsTypeSpecifierLink)

6.2.2. Selectable WebCmsComponentType options

In the case where a component type may only contain other specific component types, the type should be provided by the childComponentsRestricted attribute, with its value set to true. The provided component type options will then be equal to the specified WebCmsTypeSpecifierLink links with link type allowed-component.

Example

types:
  component:
    partner-container:
      name: Partners
      attributes:
        type: container
        childComponentsRestricted: true
      wcm:types:
        - typeSpecifier: "wcm:type:component:partner"
          linkType: allowed-component
  • A partner-container should only contain components of the type partner

6.3. Custom component type

Apart from extending one of the base types and using a custom template or metadata class, you can also pretty much customize any part of the component related infrastructure by providing specific interface implementations. This allows you to create a fully custom component type and read/write/render or manage it in whichever way you like.

The following list of the component related interfaces and their role:

Interface Description

WebCmsComponentModelReader

Converts from a WebCmsComponent to the relevant WebCmsComponentModel implementation. If you want to have the default metadata support, consider extending AbstractWebCmsComponentModelReader.

WebCmsComponentModelWriter

Saves a WebCmsComponentModel to the backing repository. Provides the backing WebCmsComponent. Consider extending AbstractWebCmsComponentModelWriter if you want default metadata support.

WebCmsComponentModelRenderer

Renders a WebCmsComponentModel in a Thymeleaf template.

WebCmsComponentModelContentAdminRenderer

Provides a ViewElementBuilder for managing the content of a WebCmsComponentModel in the administration UI.

WebCmsComponentModelMetadataAdminRenderer

Provides a ViewElementBuilder for managing the metadata of a WebCmsComponentModel in the administration UI.

WebCmsComponentModelMembersAdminRenderer

Provides a ViewElementBuilder for managing child components (usually container members) of a WebCmsComponentModel in the administration UI.

WebCmsComponentAutoCreateStrategy

Builds a WebCmsComponentModel when it is being auto-created, for example during first render of a template. Gets the processed template markup as input parameters.

All component related interfaces use the same processing approach:

  • all beans of that type are detected and ordered

  • a supports() method is used to check if the bean applies for a certain component

  • the first bean that applies will be used

As the implementations are always queried in order, customizing an implementation is a matter of:

  • providing your implementation as a bean

  • implementing supports() to match for all component types you want

  • ensure your bean is ordered (use @Order or implement Ordered) before any other that might also apply for that component type

6.4. Customizing the components administration UI

WebCmsModule provides a default administration UI built on AdminWebModule and EntityModule.

The default administration UI uses a form of tabs to display the different sections (eg. content, members, metadata) of a component. Only sections relevant for the component type will be shown to the user.

6.4.1. Administration UI labels and descriptions

The default administration UI for components supports configuring the labels. See the appendix on message codes.

6.4.2. Creating custom component forms

You can customize the forms being rendered by providing custom xAdminRenderer implementations. This allows you to build

6.4.3. Configuring rich-text and markup components

WebCmsModule supports rich text components and HTML components out of the box, using TinyMCE and CodeMirror respectively. Rich-text components are identified with the base type rich-text, markup with markup.

YAML definition of default Rich text and HTML component types
rich-text:
  objectId: "wcm:type:component:rich-text"
  name: Rich text
  description: Rich text component - supporting HTML markup and default assets if enabled by other modules.
  attributes:
    type: rich-text
    profile: rich-text
html:
  objectId: "wcm:type:component:html"
  name: HTML
  description: A snippet of HTML.
  attributes:
    type: markup

In addition to the base type, a component type can define a profile attribute value, denoting a custom profile that should be used to initialize the editor for that component type. The default rich-text component type uses a profile called rich-text as well.

You can customize the profile configuration using Javascript. This is done by registering profiles on the WebCmsComponentProfileRegistry. A profile registration requires the following three attributes:

  • componentType: this is the base type of the component (eg. rich-text or markup)

  • profileName: a unique profile name

  • profile: JSON object that holds the actual profile data (for example the TinyMCE configuration object)

Profiles can be inherited. If your profile data has a _parentProfile attribute, the value is expected to be the name of another profile for the same component type. The final profile will be the data of the parent profile merged together with the requested profile. Values from the parent will be replaced by values from the requested profile. Every profile can have a (optional) single parent.

Example registration of custom rich-text type
(function ( $ ) {
  /**
   * Limited rich-text profile: inherit from the _base profile
   * and only show bold/italic buttons.
   */
  WebCmsComponentProfileRegistry.registerProfileForComponentType(
    'rich-text', /* component type */
    'limited', /* profile name */
     {
      _parentProfile: '_base',
      toolbar1: 'bold italic'
    }
  );
})(jQuery);
Custom profiles must be registered in Javascript. This usually happens before initialization of form elements (as the profiles should already be registered when that happens), but it must happen after the inclusion of the TextWebCmsComponentAdminResources package and in the javascript page end phase. The reason for this is that the profile registry and the default profiles are defined during JAVASCRIPT_PAGE_END of the WebCmsModule resources.
Default profiles

The following default profiles are available:

Component type Profile name Usage

rich-text

_base

Base profile for technical configuration of TinyMCE. Sets up things like the image picket configuration, toolbar behaviour etc. It’s probably best to have your custom profiles inherit from this one.

rich-text

default

Default profile for a TinyMCE editor: configures default plugins and toolbars. Extends _base.

rich-text

rich-text

Actual profile used by the default Rich text component. Inherits from default but adds no custom settings - as such is identical to default out of the box.

markup

default

Default CodeMirror configuration used by the HTML component. Inherits from a _base profile even though no _base is defined. You can either replace default with your custom settings or register the _base profile to extend default indirectly.

6.4.4. Detecting tab switching

Sometimes tab switching needs to be detected in order to re-render the client-side interface. Any element that has data attribute data-wcm-component-refresh set will receive the wcm:componentRefresh event whenever a component tab is being activated.

// Example refreshing the RTE when a component tab is being switched
$( '[data-wcm-markup-type=markup]', node ).each( function () {
    var cm = CodeMirror.fromTextArea( $( this )[0], {} );
    $( this ).on( 'wcm:componentRefresh',
                  function () {
                      cm.refresh();
                  } )
            .attr( 'data-wcm-component-refresh', 'true' );  // ensure we receive the event
} );

7. Importing components

7.1. Single value importing

The TextWebCmsComponentModel and ImageWebCmsComponentModel support updating existing components using a single attribute value.

This is the same as assigning the following property:

Component type Property

TextWebCmsComponentModel

content

ImageWebCmsComponentModel

image

Example: the following 2 import blocks would be equivalent
assets:
  article:
    - title: My article
      wcm:components:
        body:
          content: Update the article body text
assets:
  article:
    - title: My article
      wcm:components:
        body: Update the article body text