hybris Cockpit Framework Expert Customization Explained. Part I.
hybris Cockpit framework was introduced many years ago and now it is being decommissioned. However, due to various reasons, new projects and almost all of the existing are actively using this framework for extending hybris cockpits.
It is one of the most challenging areas in hybris, because the framework is underdocumented, the code is very old. This article will shed some light on the topic and make difficult things clearer. I hope that this information will become a good addition to the official documentation.
The parameter “use” of the tag “window” defines an implementation of the component. For the main window the implementation class is de.hybris.platform.cockpit.components.impl.MainWindow.
adds all the perspectives defined in the beans of
into a collection named
(attribute of UICockpitSession). Cockpit Framework applies restrictions to this list, the results are saved in
.
customCsaURI commonly refers to the zul template with Javascript (specific for the perspective).
Note, that this bean extends BasePerspective:
As we see, BasePerspective has a very simple template (/cockpit/basePerspective.zul):
There is one component defined in the perspective template, BasePerspectiveComponent.
BasePerspectiveComponent initializes the following subcomponents (if they are defined):
Note, that the bean ValidationNavigationArea extends BaseNavigationArea.
as a result, we see these three custom sections:
ConstraintSectionRenderer:
is used to intercept the following events:
Cockpit core architecture
The Hybris Cockpit framework is based on ZK Framework 3.6, a rich internet application framework that enables desktop-like GUIs within a web browser. The starting page of all cockpits is index.zul. Let’s start our journey with this file.<window xmlns="http://www.zkoss.org/2005/zul" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns:h="http://www.w3.org/1999/xhtml" xmlns:zk="http://www.zkoss.org/2005/zk" xsi:schemaLocation="http://www.zkoss.org/2005/zul http://www.zkoss.org/2005/zul/zul.xsd " id="mainWin" mode="embedded" height="100%" sclass="hywin" use="de.hybris.platform.cockpit.components.impl.MainWindow" shadow="false">
</window>
</window>
MainWindow and perspectives
“Perspectives” are something like “tabs” in UI, but with a bit different look. Each perspective is based on the same page structure, if you use the built-in BasePerspectiveComponent. MainWindow creates a UICockpitSession. It manages e.g. the available cockpit perspectives. You can define your own perspective by creating a bean in the following way:<bean id="MyCockpitSession" class="de.hybris.platform.cockpit.session.impl.UISessionImpl" scope="session" init-method="registerAdditionalPerspectives">
...
<property name="availablePerspectives">
<list>
<ref bean="MyPerspective1"/>
<ref bean="MyPerspective2"/>
</list>
</property>
..
</bean>
...
<property name="availablePerspectives">
<list>
<ref bean="MyPerspective1"/>
<ref bean="MyPerspective2"/>
</list>
</property>
..
</bean>
registerAdditionalPerspectives
PerspectivePluginList.class
avPerspUnrestricted
availablePerspectives
<property name="cachePerspectivesEnabled" value="true" />
<property name="requestHandler" ref="CMSRequestHandler" />
<property name="dragOverPerspectivesEnabled" value="true" />
<property name="sendEventsImmediately" value="false" />
<property name="pushContainers">
<list value-type="de.hybris.platform.cockpit.session.impl.PushCreationContainer">
<ref bean="WorkflowPushContainer" />
<ref bean="CommentPushContainer" />
</list>
</property>
<property name="requestHandler" ref="CMSRequestHandler" />
<property name="dragOverPerspectivesEnabled" value="true" />
<property name="sendEventsImmediately" value="false" />
<property name="pushContainers">
<list value-type="de.hybris.platform.cockpit.session.impl.PushCreationContainer">
<ref bean="WorkflowPushContainer" />
<ref bean="CommentPushContainer" />
</list>
</property>
- dragOverPerspectivesEnabled – you can drag the object over perspective name. Hybris will change the perspective to allow you to drop the object to the other perspective’s area. by default, dragOverPerspEnabled is false. Did you know that you can drag a product from the Product perspective to Catalog and link it to one of the categories? This parameters enables it.
- requestHandler defines the request event handlers for jump-in URLs. By default, the following events are defined: “activation”, “search”, “celum”, “msg”. The product cockpit extension extends this list with “wf” (workflow). CMSCockpit extends them with “cmsnavigation“, “pageviewnavigation“, “liveedit“, “liveeditpagenavigation” and “wf“. Example of jump-in URL: https://localhost:9002/admincockpit/index.zul?persp=admincockpit.perspective.validation&events=msg&msg-title=Test%20Message
<bean id="CMSRequestHandler" class="de.hybris.platform.cockpit.session.impl.DefaultRequestHandler" scope="session">
<property name="requestEventHandlers">
<map>
<entry key="activation">
<bean class="de.hybris.platform.cockpit.session.impl.ActivationEventHandler">
<property name="prefix" value="act" />
</bean>
</entry>
<entry key="search">
<bean class="de.hybris.platform.cockpit.session.impl.SearchEventHandler">
<property name="prefix" value="srch" />
</bean>
</entry>
...
</bean>
<property name="requestEventHandlers">
<map>
<entry key="activation">
<bean class="de.hybris.platform.cockpit.session.impl.ActivationEventHandler">
<property name="prefix" value="act" />
</bean>
</entry>
<entry key="search">
<bean class="de.hybris.platform.cockpit.session.impl.SearchEventHandler">
<property name="prefix" value="srch" />
</bean>
</entry>
...
</bean>
- sendEventsImmendiately defines the event processing mode. If true, global events are pushed into perspective event handlers immediately. Otherwise, the event cache is used and there is a delay.
- pushContainers defines PushComponents. Push component enables server to push the messages in the cockpit (reverse AJAX). Hybris injects them into MainWindow. For each bean hybris creates a controller. PushComponent operates like a cronjob inside the cockpit app. One of the parameters of the push component is update interval. For example, WorkflowPushContainer
<bean id="WorkflowPushContainer" class="de.hybris.platform.cockpit.session.impl.PushCreationContainer" scope="session">
<constructor-arg value="de.hybris.platform.importcockpit.session.impl.CronJobsPushController" index="0"/>
<constructor-arg index="1">
<map>
<entry key="updateInterval" value="500"/>
</map>
</constructor-arg>
</bean>
<constructor-arg value="de.hybris.platform.importcockpit.session.impl.CronJobsPushController" index="0"/>
<constructor-arg index="1">
<map>
<entry key="updateInterval" value="500"/>
</map>
</constructor-arg>
</bean>
<bean id="ValidationPerspective" class=" de.hybris.platform.admincockpit.session.impl.AbstractConstraintPerspective" scope="session" parent="BasePerspective">
<property name="uid" value="admincockpit.perspective.validation" /> <!-- unique ID of the perspective -->
<property name="label" value="admincockpit.perspective.validation" /> <!-- localization key for the label of the perspective -->
<property name="customCsaURI" value="/admincockpit/validationCSA.zul" /> <!-- file with optional custom client side actions -->
<property name="uid" value="admincockpit.perspective.validation" /> <!-- unique ID of the perspective -->
<property name="label" value="admincockpit.perspective.validation" /> <!-- localization key for the label of the perspective -->
<property name="customCsaURI" value="/admincockpit/validationCSA.zul" /> <!-- file with optional custom client side actions -->
<bean id="BasePerspective" class="de.hybris.platform.cockpit.session.impl.BaseUICockpitPerspective" abstract="true" scope="session">
<property name="viewURI" value="/cockpit/basePerspective.zul"/>
<property name="activationEffectEnabled" value="false"/>
<property name="effectDuration" value="0.7"/>
<property name="moveTargetX" value="130"/>
<property name="moveTargetY" value="50"/>
<property name="effectBorderColor" value="#BCD2EF"/>
<property name="cockpitTypeService" ref="cockpitTypeService"/>
<property name="uiConfigurationService" ref="uiConfigurationService"/>
<property name="popupEditorArea">
<bean id="DefaultPopupEditor" parent="BasePopupEditor"/>
</property>
</bean>
<property name="viewURI" value="/cockpit/basePerspective.zul"/>
<property name="activationEffectEnabled" value="false"/>
<property name="effectDuration" value="0.7"/>
<property name="moveTargetX" value="130"/>
<property name="moveTargetY" value="50"/>
<property name="effectBorderColor" value="#BCD2EF"/>
<property name="cockpitTypeService" ref="cockpitTypeService"/>
<property name="uiConfigurationService" ref="uiConfigurationService"/>
<property name="popupEditorArea">
<bean id="DefaultPopupEditor" parent="BasePopupEditor"/>
</property>
</bean>
<div width="100%" height="100%" use="de.hybris.platform.cockpit.components.BasePerspectiveComponent"></div>
- baseNavigationComponent in the navigationArea
- BasePopupEditorComponent in popupEditorArea
- BrowserContainer in browserArea
- BaseEditorAreaComponent in editorArea
- CustomCsaIncludeHelper in customCsaURI (*CSA.zul).
</pre>
<bean id="ValidationPerspective" class=" de.hybris.platform.admincockpit.session.impl.AbstractConstraintPerspective" scope="session" parent="BasePerspective">
....
<property name="navigationArea">
<ref bean="ValidationNavigationArea" />
</property>
...
</bean>
<bean id="ValidationNavigationArea" class="de.hybris.platform.admincockpit.session.impl.AdmincockpitNavigationArea" scope="session" parent="BaseNavigationArea">
<property name="sectionModel">
<ref bean="ValidationNavigationAreaModel"/>
</property>
<property name="infoSlotRenderer">
<bean class="de.hybris.platform.cockpit.components.navigationarea.renderer.InfoBoxRenderer"/>
</property>
<property name="sections">
<list>
<ref bean="ValidationConstraintsSection" />
<ref bean="ValidationConstraintGroupsSection" />
<ref bean="AdmincockpitUndoSection" />
</list>
</property>
<property name="cockpitTypeService" ref="cockpitTypeService"/>
</bean>
<bean id="ValidationPerspective" class=" de.hybris.platform.admincockpit.session.impl.AbstractConstraintPerspective" scope="session" parent="BasePerspective">
....
<property name="navigationArea">
<ref bean="ValidationNavigationArea" />
</property>
...
</bean>
<bean id="ValidationNavigationArea" class="de.hybris.platform.admincockpit.session.impl.AdmincockpitNavigationArea" scope="session" parent="BaseNavigationArea">
<property name="sectionModel">
<ref bean="ValidationNavigationAreaModel"/>
</property>
<property name="infoSlotRenderer">
<bean class="de.hybris.platform.cockpit.components.navigationarea.renderer.InfoBoxRenderer"/>
</property>
<property name="sections">
<list>
<ref bean="ValidationConstraintsSection" />
<ref bean="ValidationConstraintGroupsSection" />
<ref bean="AdmincockpitUndoSection" />
</list>
</property>
<property name="cockpitTypeService" ref="cockpitTypeService"/>
</bean>
NavigationArea
To customize navigation area you may :- Create a new navigationArea bean with the OOTB implementation (BaseUICockpitNavigationArea). However, you can use you custom values of beans parameters or custom implementations for the beans referenced in the navigationArea parameters, such as:
-
andviewURI. You can introduce your own zul files instead of OOTBheaderURIandbaseNavigation.zul. You can use your own implementations of components in your own zul files to replace OOTBleft_section_header.zulandNavigationAreaComponentrespectively.LeftSectionHeaderComponent
-
This parameters defines a list of prototype-type beans that implementssectionsFor each bean you can specify the custom renderer.NavigationPanelSection
-
.contentSlotRenderer
-
.infoSlotRenderer
-
- Create a new navigationArea bean with the custom implementation. See above for the example from admincockpit.
- Redefine a Navigation Area component by extending NavigationAreaComponent.
public void render(SectionPanel panel, Component parent, Component captionComponent, Section section) {
Vbox vbox = new Vbox();
Label constrainedTypeLabel = new Label(Labels.getLabel("na.constrainedSection.constrained_type_label") + ": ");
vbox.appendChild(constrainedTypeLabel);
....
parent.appendChild(vbox);
}
Vbox vbox = new Vbox();
Label constrainedTypeLabel = new Label(Labels.getLabel("na.constrainedSection.constrained_type_label") + ": ");
vbox.appendChild(constrainedTypeLabel);
....
parent.appendChild(vbox);
}
Browser Area
In order to customize the Browser Area you can:- Create a new browserArea bean with the OOTB implementation (DefaultConfigurableBrowserArea or DefaultSearchBrowserArea). You can use you custom values of beans parameters or custom implementations for the beans referenced in the navigationArea parameters, such as:
-
. You can introduce your own zul template to replace OOTBviewURIYou can use your own implementations of the components in your zul file to replace OOTBbaseSearchBrowserArea.zulandBrowserContainer.CenterAreaContainer
-
This parameters defines a list of prototype-type beans that implementssupportedBrowserIdsFor each bean you can specify the custom renderer. For example, CSCockpit (deprecated now) uses three browsers: csTicketPoolBrowser, csBasketBrowser, csEndCallBrowser.WidgetDashboardBrowserModel
-
defines a default browser from the list above. The value is a reference to a bean, not a real id. For example, for the example above, .defaultBrowserId
-
.showCreateDefaultBrowserButton
-
. You can specify a type to browse by default. “Product” means that the browserarea will display a list of products. It is defined only in DefaultSearchBrowserArea.rootSearchTypeCode
-
is a boolean parameter. Defines what class should be used for the area, DefaultExtendedSearchBrowserModel or DefaultSearchBrowserModel.extendedSearchBrowser
-
- Create a new browserArea bean with the custom implementation. For example, CMS Cockpit goes this way. Their custom implementation
extends cockpit’sCatalogBrowserAreaDefaultSearchBrowserArea
- Redefine a Navigation Area component by extending
orBaseContainer.CenterAreaContainer
- Browsers. These are basically tabs at the top of the area. You can create/remove tabs via browser.
- Mainarea component.
- Caption component. According to QueryCaptionBrowserComponent, it contains query text box, search and advanced search buttons, save query button, advanced search related stuff, a split button.
- Toolbar component. By default, DefaultSearchContentBrowser defines toolbar component.
Editor Area
EditorAreaListener
- currentObjectChanged
- nextItemClicked
- previousItemClicked
- valuesStored
- valuesUpdated
- browseItemClicked
- currentObjectUpdated
- valueChanged.
Popup Editor Area
You can’t open another level of editor by default as you possibly used to do with HMC. There are ways to allow nested popups in multiple levels. This feature can be activated for any cockpit with one of following properties:# activate only for specific cockpit productcockpit.default.popUpEditor.allowOverlap=true # activate for all cockpits default.popUpEditor.allowOverlap=trueThe result will be that pencil icons will appear also for editor rows in popup editor area. Clicking the pencil icon will simulate next level and back button will appear on top. There are a big topic on how to operate wizards and popup windows created by ZK. This topic is for the next part. To sum up, in this article I explained the very basics of Cockpit Framework, perspectives, areas and their components, their architecture and customization approach. This is just the tip of the iceberg. I’ll come back soon with other topics about Cockpit Framework and expert customization. Stay tuned!
© Rauf Aliev, November 2016
Wojtek (@jestemwojtek)
28 November 2016 at 02:52
Hi,
have you had a chance to work with the new cockpit framework and backoffice? I would be very curious to see a comparison of the old framework against the new.
Regards,
Wojtek
Rauf Aliev
28 November 2016 at 02:57
One of the topics of forthcoming articles. New backoffice is better, of course
Leonardo Zamariola
29 November 2016 at 20:53
Great article, Rauf! This kind of cockpit has a lot of customization options and unfortunately a not that good documentation (as you said).
Leonardo
29 November 2016 at 20:54
Great article Aliev! This kind of cockpit has a lot of customization options and unfortunately a not that good documentation (as you said).
Vishal
14 September 2017 at 06:08
Nice article Alive!! I am facing some problem in cockpit customization like
When I was clicked on Listview in NavigationArea that time BrowserArea want to updated
I am written some code for updating the browser area is given below but not updating browser UI
CODE :
BrowserModel focusBrowserModel = UISessionUtils.getCurrentSession().getCurrentPerspective().getBrowserArea().getFocusedBrowser();
UISessionUtils.getCurrentSession().getCurrentPerspective().getBrowserArea().addVisibleBrowser(focusBrowserModel, true);
UISessionUtils.getCurrentSession().getCurrentPerspective().getBrowserArea().update();
focusBrowserModel.focus();
focusBrowserModel.updateItems();
Please help me regarding the same