Synchronization of Parent-Child CMS Components: A working example

When a CMS Component contains other components, it is a requirement that any changes of the childrens trigger the synchronization of the parent. It is difficult to get this right, as there are limitations on the identification of changes on the WCMS. This is what has worked for me.

Introduction

The requirement is to show tabs in the pages accessible by the registered users. The order and other properties like the title may be changed by the customer on the WCMS. Currently each page has only one group of tabs but this may change in the future.

Data model

hybris/bin/custom/customcms/resources/customcms-items.xml
<relation code="TabGroup2AbstractTabCMSComponent" generate="true" localized="false" autocreate="true">
        <deployment table="tabgroup2tab" typecode="30099" />
        <sourceElement qualifier="tabGroups" type="TabGroupCMSComponent" cardinality="many"/>
        <targetElement qualifier="tabs" type="AbstractTabCMSComponent" cardinality="many" collectiontype="list"
                                   ordered="true"/>
</relation>
 
<itemtype code="TabGroupCMSComponent"
                  autocreate="true" generate="true"
                  extends="AbstractCMSComponentContainer"
                  jaloclass="customcms.jalo.components.TabGroupCMSComponent">
        <description>It holds a group of tabs. The CMS cockpit sees the changes on the childs because it
                inherites from AbstractCMSComponent
        </description>
</itemtype>
 
<itemtype code="AbstractTabCMSComponent" generate="true"
                  extends="AbstractCMSComponent"
                  autocreate="true"
                  abstract="false"
                  jaloclass="customcms.cms.jalo.components.AbstractTabCMSComponent">
        <description>Represents a tab component hold by a tab group.</description>
        <attributes>
                <attribute qualifier="tag" type="java.lang.String">
                        <persistence type="property"/>
                        <description>A tag used for identifying the component within its parent component.</description>
                </attribute>
                <attribute qualifier="title" type="localized:java.lang.String">
                        <persistence type="property"/>
                </attribute>
                <attribute qualifier="tabbodyCaption" type="localized:java.lang.String">
                        <persistence type="property"/>
                </attribute>
                <attribute qualifier="tabbodyInfo" type="localized:java.lang.String">
                        <persistence type="property"/>
                </attribute>
        </attributes>
</itemtype>

The important points to consider are:

  • The parent component inherits from AbstractCMSComponentContainer.
  • The child component isn't abstract
  • The child component inherits from AbstractCMSComponent.

Here is the definition of the childs which doesn't have anything special:

hybris/bin/custom/customcms/resources/customcms-items.xml
<itemtype code="ReportsCMSComponent" generate="true" extends="AbstractTabCMSComponent"
                  autocreate="true"
                  jaloclass="customcms.jalo.components.ReportsCMSComponent">
        <description>Component for requesting reports for customer</description>
        <attributes/>
</itemtype>
 
<itemtype code="EnableOrdersCMSComponent" generate="true" extends="AbstractTabCMSComponent"
                  autocreate="true"
                  jaloclass="customcms.jalo.components.EnableOrdersCMSComponent">
        <description> Orders - 'Enable orders' Component</description>
        <attributes/>
</itemtype>
 
<itemtype code="OrderHistoryCMSComponent" generate="true" extends="AbstractTabCMSComponent"
                  autocreate="true"
                  jaloclass="customcms.jalo.components.OrderHistoryCMSComponent">
        <description> Orders - 'All orders' Component</description>
        <attributes/>
</itemtype>

Synchronization Service

In the custom CMS cockpit extension the new attributes must be configured.

hybris/bin/custom/customcmscockpit/resources/customcmscockpit/customcmscockpit-spring-services.xml
<beans xmlns="http://www.springframework.org/schema/beans"
       xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
       xsi:schemaLocation="http://www.springframework.org/schema/beans
                                                http://www.springframework.org/schema/beans/spring-beans-3.0.xsd"
       default-autowire="byName">
 
    <alias alias="synchronizationService" name="customSynchronizationService" />
    <bean id="customSynchronizationService" class="de.hybris.platform.cmscockpit.sync.CMSSynchronizationService" scope="tenant" autowire="byName">
        <property name="relatedReferencesTypesMap">
            <map>
                <entry key="AbstractPage">
                    <list>
                        <value>AbstractPage.restrictions</value>
                        <value>AbstractPage.contentSlots</value>
                        <value>ContentSlotForPage.contentSlot</value>
                        <value>ContentSlot.cmsComponents</value>
                        <value>AbstractCMSComponentContainer.simpleCMSComponents</value>
                        <value>AbstractCMSComponentContainer.currentCMSComponents</value>
                        <value>RotatingImagesComponent.banners</value>
                        <value>AbstractCMSComponent.restrictions</value>
                        <value>abstractMediaContainerComponent.media</value>
                        <value>TabGroupCMSComponent.tabs</value>
                    </list>
                </entry>
                <entry key="AbstractCMSComponent">
                    <list>
                        <value>AbstractCMSComponentContainer.simpleCMSComponents</value>
                        <value>AbstractCMSComponentContainer.currentCMSComponents</value>
                        <value>RotatingImagesComponent.banners</value>
                        <value>CMSProductCarouselComponent.carouselElements</value>
                        <value>ContentSlot.cmsComponents</value>
                        <value>AbstractCMSComponentContainer.simpleCMSComponents</value>
                        <value>AbstractCMSComponentContainer.currentCMSComponents</value>
                        <value>RotatingImagesComponent.banners</value>
                        (...)                        
                        <value>MediaContainer.medias</value>
                        <value>NavigationBarCollectionComponent.components</value>
                        <value>NavigationBarComponent.navigationNode</value>
                        <value>NavigationBarComponent.link</value>
                        <value>TabGroupCMSComponent.tabs</value>
                    </list>
                </entry>
                <entry key="CMSNavigationNode">
                    <list>
                        <value>CMSNavigationNode.children</value>
                        <value>CMSNavigationNode.entries</value>
                        <value>CMSNavigationNode.links</value>
                        <value>CMSNavigationEntry.item</value>
                        <value>CMSLinkComponent</value>
                    </list>
                </entry>
                <entry key="ContentSlot">
                    <list>
                        <value>ContentSlot.cmsComponents</value>
                        <value>abstractMediaContainerComponent.media</value>
                        <value>NavigationBarCollectionComponent.components</value>
                        <value>NavigationBarComponent.navigationNode</value>
                        <value>NavigationBarComponent.link</value>
                    </list>
                </entry>
                <entry key="MediaContainer">
                    <list>
                        <value>MediaContainer.medias</value>
                    </list>
                </entry>
                <entry key="TabGroupCMSComponent">
                    <list>
                        <value>TabGroupCMSComponent.tabs</value>
                    </list>
                </entry>
                <entry key="AbstractTabCMSComponent">
                    <list>
                        <value>AbstractTabCMSComponent.title</value>
                        <value>AbstractTabCMSComponent.tabGroups</value>
                    </list>
                </entry>
            </map>
        </property>
        <property name="modelService" ref="modelService"/>
        <property name="cockpitTypeService" ref="cockpitTypeService"/>
        <property name="searchRestrictionsDisabled" value="true"/>
    </bean>
 
(...)
 
</beans>

Some of the configuration is probably duplicated but it works.

Localization (optional)

The new components must be localized as any other Hybris type. 

hybris/bin/custom/customcms/resources/localization/customcms-locales_en.properties
### Localization for type TabGroupCMSComponent
type.tabgroupcmscomponent.name=Tab group component
type.tabgroupcmscomponent.tabs.name=Tabs
 
### Localization for type AbstractTabCMSComponent
type.abstracttabcmscomponent.name=Abstract Tab component
type.abstracttabcmscomponent.tabgroups.name=Tab groups
type.abstracttabcmscomponent.tabbodycaption.name=Section caption
type.abstracttabcmscomponent.tabbodyinfo.name=Section text
type.abstracttabcmscomponent.tag.name=Tag
type.abstracttabcmscomponent.title.name=Title.

Jalo Classes

Two methods must be implemented:

package customcms.jalo.components;
 
import de.hybris.platform.cms2.jalo.contents.components.SimpleCMSComponent;
import de.hybris.platform.jalo.SessionContext;
 
import java.util.List;
 
 
public class TabGroupCMSComponent extends GeneratedTabGroupCMSComponent
{
    @Override
    public Boolean isContainer(SessionContext sessionContext) {
        return Boolean.TRUE;
    }
 
 
    @Override
    public List<SimpleCMSComponent> getCurrentCMSComponents(SessionContext sessionContext) {
        return this.getSimpleCMSComponents(sessionContext);
    }
}
 
package customcms.jalo.components;
 
import de.hybris.platform.jalo.SessionContext;
 
public class AbstractTabCMSComponent extends GeneratedAbstractTabCMSComponent
{
 
    @Override
    public Boolean isContainer(SessionContext sessionContext) {
        return Boolean.TRUE;
    }
}

CMS Cockpit Configuration

The following section refers to the WCMS Cockpit. This extension will be replaced by SmartEdit and is deprecated since SAP Hybris 6.7. SmartEdit will support the old features and new ones.

The changes on the child are identified when the tag property is used in the cockpit configuration of the parent is used:

hybris/bin/customcmscockpit/resources/customcmscockpit-config/cmsmanagergroup/contentEditor_TabGroupCMSComponent.xml
<?xml version="1.0" encoding="UTF-8"?>
<content-editor hideEmpty="false" hideReadOnly="false" groupCollections="false">
  <template>
    <![CDATA[
        <div class="contentEditorWrapper">
            <div class="contentEditorHeader">
                <b>$label</b>
            </div>
            <div class="contentEditorLine">
                <cockpit code="property" value="TabGroupCMSComponent.visible"/>
            </div>
            <div class="contentEditorLine">
                <cockpit code="property" value="TabGroupCMSComponent.tabs"/>
            </div>
        </div>
    ]]>
  </template>
</content-editor>

The Cockpit configuration of the childs doesn't contain anything special:

hybris/bin/customcmscockpit/resources/customcmscockpit-config/cmsmanagergroup/contentEditor_AbstractTabCMSComponent.xml
<?xml version="1.0" encoding="UTF-8"?>
<content-editor hideEmpty="true" hideReadOnly="true" groupCollections="true">
  <template>
    <![CDATA[
        <div class="contentEditorWrapper">
            <div class="contentEditorHeader">
                <b>$label</b>
            </div>
            <div class="contentEditorLine">
                <cockpit code="property" value="AbstractTabCMSComponent.tag"/>
            </div>
            <div class="contentEditorLine">
                <cockpit code="property" value="AbstractTabCMSComponent.title"/>
            </div>
            <div class="contentEditorLine">
                <cockpit code="property" value="AbstractTabCMSComponent.tabbodyCaption"/>
            </div>
            <div class="contentEditorLine">
                <cockpit code="property" value="AbstractTabCMSComponent.tabbodyInfo"/>
            </div>
        </div>
    ]]>
  </template>
</content-editor>
 
hybris/bin/customcmscockpit/resources/customcmscockpit-config/cmsmanagergroup/editorArea_AbstractTabCMSComponent.xml
<?xml version="1.0" encoding="UTF-8"?>
<editor>
 
    <group qualifier="general" visible="true" initially-opened="true">
        <label lang="de">Stammdaten</label>
        <label lang="en">General</label>
        <property qualifier="CMSItem.name"/>
        <property qualifier="CMSItem.catalogVersion" editor="shortListEditor"/>
        <property qualifier="AbstractTabCMSComponent.title"/>
        <property qualifier="AbstractTabCMSComponent.tabbodyCaption"/>
        <property qualifier="AbstractTabCMSComponent.tabbodyInfo"/>
    </group>
 
    <group qualifier="visibility" visible="true" initially-opened="false">
        <label lang="de">Context Visibility</label>
        <label lang="en">Context Visibility</label>
        <property qualifier="AbstractCMSComponent.restrictions">
            <parameter>
                <name>allowCreate</name>
                <value>true</value>
            </parameter>
        </property>
        <property qualifier="AbstractCMSComponent.onlyOneRestrictionMustApply"/>
    </group>
 
        <group qualifier="admin" visible="false" initially-opened="false">
                <label key="config.general.administration" />
                <property qualifier="Item.pk" />
                <property qualifier="Item.creationTime" />
                <property qualifier="Item.modifiedtime" />
        </group>
 
    <custom-group
            class="de.hybris.platform.cockpit.services.config.impl.UnassignedEditorSectionConfiguration"
            qualifier="unassigned"
            initially-opened="false"
            visible="false">
        <label lang="de">Andere</label>
        <label lang="en">Other</label>
    </custom-group>
 
</editor>

Views

The JSP file must include CMS tags which render the childs as any other CMS component container:

hybris/bin/custom/customstorefront/web/webroot/WEB-INF/views/cms/tabgroupcmscomponent.jsp
<%@ taglib prefix="c" uri="http://java.sun.com/jsp/jstl/core" %>
<%@ taglib prefix="spring" uri="http://www.springframework.org/tags" %>
<%@ taglib prefix="form" uri="http://www.springframework.org/tags/form" %>
<%@ taglib prefix="links" uri="http://www.springframework.org/tags/form" %>

(...)
<c:forEach var="tabcomponent" items="${tabcomponents}" begin="0">
   <cms:component component="${tabcomponent}"/>
</c:forEach>

(...)

–Based on Hybris 5.5.1

Discussion

Enter your comment. Wiki syntax is allowed: