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.

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.

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.

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.

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)

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

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

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.

Administration UI labels and descriptions

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

Creating custom component forms

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

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.

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
} );