Using HTML Templates in AngularJS Widgets
When using AngularJS (or some other third parties javascript frameworks), in some cases, an HTML widget may require some static code injected from the Backoffice Framework. To achieve it, HTML templates may be used.
Context
This document shows how to use simple templates to solve typical problems that may occur when writing a widget with third party javascript frameworks. This document does not describe all the possibilities that the Apache Velocity
supports, yet the most common use cases are presented.
The steps mentioned in this tutorial build upon the configuration from the Extending an AngularJS Widget.
Resolving Identities
Context
Procedure
Source Files at This Stage - Identities
You can check the source files at this stage.
See full content of angularwidget.html
<widget ng-controller="SampleAngularController">
<h1 class="caption ${id}_spare" cng-local="label.title.hello"></h1>
<h3 class="caption ${id}_spare">{{sessionInfo.customAttributes['name']}} ({{sessionInfo.currentUser}})! {{adapter.cng().local('label.title.sockets')}}</h3>
<div ng-if="!input" cng-global="label.noinput"></div>
<table ng-if="input">
<tr>
<td class="header" cng-local="label.socket.input"></td>
<td class="header" cng-local="label.socket.output"></td>
</tr>
<tr>
<td>
<table>
<tr ng-repeat="(key, value) in input" ng-class="{true:'even', false:'odd'}[$even]">
<td class="label">{{key}}</td>
<td class="value">{{value}}</td>
</tr>
</table>
</td>
<td>
<table>
<tr ng-repeat="(key, value) in output" ng-class="{true:'even', false:'odd'}[$even]">
<td class="label">{{key}}</td>
<td class="value">{{value}}</td>
</tr>
</table>
</td>
</tr>
</table>
<cng-event type="socket" id="input" handler="socketInput(socket, data)" />
<hr/>
<div class="${id}_spare>
<h3 class="caption" cng-local="label.title.model"></h3>
<div cng-local="label.model.registered"></div>
<table>
<tr ng-repeat="(key, value) in model.changes" ng-class="{true:'even', false:'odd'}[$even]">
<td class="label">{{key}}</td>
<td class="value">{{value}}</td>
<td ng-if="(settings && settings.enableModelEditing) || sessionInfo.admin" ng-click="removeModelChange(key)"><a href="#">X</a></td>
</tr>
</table>
<div ng-if="(settings && settings.enableModelEditing) || sessionInfo.admin">
<div cng-local="label.model.instruction"></div>
<table>
<tr class="even">
<td class="label" cng-local="label.model.property"></td>
<td class="value"><input type="text" ng-model="property"></td>
</tr>
<tr class="odd">
<td class="label" cng-local="label.model.value"></td>
<td id="value" class="value">{{input[property]}}</td>
</tr>
<tr class="even">
<td class="label" cng-local="label.model.change"></td>
<td class="value"><input type="text" ng-model="newValue"></td>
</tr>
<tr class="odd">
<td colspan="2" class="commit"><button type="button" ng-click="commitModelChanges(property, newValue)" cng-local="label.model.commit"/></td>
</tr>
</table>
</div>
<cng-event type="model" handler="modelChanged(model, property)" />
<hr/>
</div>
<cng-event type="ready" handler="init()" />
</widget>
See full content of angularwidget.js
SampleAngularController = function($scope) {
$scope.socketInput = function(socketId, data) {
showMessage(LEVEL_INFO, $scope.adapter.cng().local("message.socket"));
$scope.input = jQuery.extend(true, {}, data);
var changes = $scope.model.changes;
if (changes) {
for(var key in changes) {
if (typeof(data[key]) != "undefined") {
data[key] = changes[key];
}
}
}
$scope.output = data;
$scope.adapter.cng().socketEvent("output", data);
if (!$scope.model.statistics) {
$scope.model.statistics = { $class: "example.cng.angular.model.SearchStatistics"}; // Notice! We explicitly point a Java class
$scope.model.statistics.stats = {};
}
if (data.typeCode) {
var stats = $scope.model.statistics.stats[data.typeCode];
if (!stats) {
stats = {};
stats.typeCode = data.typeCode;
stats.count = 0;
$scope.model.statistics.stats[data.typeCode] = stats;
}
stats.count += 1;
$scope.adapter.cng().model("statistics", $scope.model.statistics);
$scope.adapter.cng().socketEvent("stats", $scope.model.statistics);
}
jQuery("." + $scope.adapter.cng().id() + "_spare").hide();
};
$scope.modelChanged = function(model) {
showMessage(LEVEL_WARNING, $scope.adapter.cng().local("message.model"));
$scope.property = null;
$scope.model = model;
};
$scope.commitModelChanges = function(key, value) {
var changes = $scope.model.changes;
if (!changes) {
changes = {};
}
changes[key] = value;
$scope.adapter.cng().model('changes', changes);
};
$scope.removeModelChange = function(key) {
var changes = $scope.model.changes;
delete changes[key];
$scope.adapter.cng().model('changes', changes);
};
$scope.init = function() {
if (!$scope.model) {
$scope.model = {};
}
$scope.statistics = $scope.adapter.cng().model("statistics");
var settings = $scope.settings["initialChanges"];
if (settings && !$scope.model.changes) {
var changes = JSON.parse(settings);
$scope.model.changes = changes;
$scope.helper.cockpit().model("changes", changes);
}
};
};
AngularCNG.init("angularwidget", SampleAngularController);
Implementing HTML Templates in Other Backoffice Framework Functionalities
You can implement HTML templates in other Backoffice Framework functionalities.
Using HTML Templates for Localization
In some cases, it may be easier to use template placeholders for localized labels.
Procedure
The angularCNG/backoffice/resources/widgets/angularwidget/angularwidget.html file:
<widget ng-controller="SampleAngularController">
<h1 class="caption ${id}_spare" cng-local="label.title.hello"></h1>
<h3 class="caption ${id}_spare">{{sessionInfo.customAttributes['name']}} ({{sessionInfo.currentUser}})! $labels.local.label.title.sockets</h3>
<!-- ... -->
<div class="${id}_spare>
<!-- ... -->
</div>
<!-- ... -->
</widget>
Source Files at This Stage - Localization
You can check the source files at this stage.
See full content of angularwidget.html
<widget ng-controller="SampleAngularController">
<h1 class="caption ${id}_spare" cng-local="label.title.hello"></h1>
<h3 class="caption ${id}_spare">{{sessionInfo.customAttributes['name']}} ({{sessionInfo.currentUser}})! $labels.local.label.title.sockets</h3>
<div ng-if="!input" cng-global="label.noinput"></div>
<table ng-if="input">
<tr>
<td class="header" cng-local="label.socket.input"></td>
<td class="header" cng-local="label.socket.output"></td>
</tr>
<tr>
<td>
<table>
<tr ng-repeat="(key, value) in input" ng-class="{true:'even', false:'odd'}[$even]">
<td class="label">{{key}}</td>
<td class="value">{{value}}</td>
</tr>
</table>
</td>
<td>
<table>
<tr ng-repeat="(key, value) in output" ng-class="{true:'even', false:'odd'}[$even]">
<td class="label">{{key}}</td>
<td class="value">{{value}}</td>
</tr>
</table>
</td>
</tr>
</table>
<cng-event type="socket" id="input" handler="socketInput(socket, data)" />
<hr/>
<div class="${id}_spare>
<h3 class="caption" cng-local="label.title.model"></h3>
<div cng-local="label.model.registered"></div>
<table>
<tr ng-repeat="(key, value) in model.changes" ng-class="{true:'even', false:'odd'}[$even]">
<td class="label">{{key}}</td>
<td class="value">{{value}}</td>
<td ng-if="(settings && settings.enableModelEditing) || sessionInfo.admin" ng-click="removeModelChange(key)"><a href="#">X</a></td>
</tr>
</table>
<div ng-if="(settings && settings.enableModelEditing) || sessionInfo.admin">
<div cng-local="label.model.instruction"></div>
<table>
<tr class="even">
<td class="label" cng-local="label.model.property"></td>
<td class="value"><input type="text" ng-model="property"></td>
</tr>
<tr class="odd">
<td class="label" cng-local="label.model.value"></td>
<td id="value" class="value">{{input[property]}}</td>
</tr>
<tr class="even">
<td class="label" cng-local="label.model.change"></td>
<td class="value"><input type="text" ng-model="newValue"></td>
</tr>
<tr class="odd">
<td colspan="2" class="commit"><button type="button" ng-click="commitModelChanges(property, newValue)" cng-local="label.model.commit"/></td>
</tr>
</table>
</div>
<cng-event type="model" handler="modelChanged(model, property)" />
<hr/>
</div>
<cng-event type="ready" handler="init()" />
</widget>
Enabling HTML Templates for Configuration
Context
As decided earlier, some of the widget instances should not have the option to configure changes at runtime. You have implemented that by adding the setting enableModelEditing in the definition.xml file and using it in ng-if directive. It already works, yet a form is still sent to a web browser (though not displayed). Widget configuration does not change during its life, so actually, the form might be omitted during the template processing and thereby not be sent to the web browser.
Procedure
<widget>
<!-- ... -->
<div class="${id}_spare">
<!-- ... -->
<table>
<tr ng-repeat="(key, value) in model.changes" ng-class="{true:'even', false:'odd'}[$even]">
<!-- ... -->
#if ( $settings.enableModelEditing )
<td ng-click="removeModelChange(key)"><a href="#">X</a></td>
#end
</tr>
</table>
#if ( $settings.enableModelEditing )
<div cng-local="label.model.instruction"></div>
<table>
<!-- ... -->
</table>
#end
<!-- ... -->
</div>
<!-- ... -->
</widget>
Source Files at This Stage - Configuration
You can check the source files at this stage.
See full content of angularwidget.html
<widget ng-controller="SampleAngularController">
<h1 class="caption ${id}_spare" cng-local="label.title.hello"></h1>
<h3 class="caption ${id}_spare">{{sessionInfo.customAttributes['name']}} ({{sessionInfo.currentUser}})! $labels.local.label.title.sockets</h3>
<div ng-if="!input" cng-global="label.noinput"></div>
<table ng-if="input">
<tr>
<td class="header" cng-local="label.socket.input"></td>
<td class="header" cng-local="label.socket.output"></td>
</tr>
<tr>
<td>
<table>
<tr ng-repeat="(key, value) in input" ng-class="{true:'even', false:'odd'}[$even]">
<td class="label">{{key}}</td>
<td class="value">{{value}}</td>
</tr>
</table>
</td>
<td>
<table>
<tr ng-repeat="(key, value) in output" ng-class="{true:'even', false:'odd'}[$even]">
<td class="label">{{key}}</td>
<td class="value">{{value}}</td>
</tr>
</table>
</td>
</tr>
</table>
<cng-event type="socket" id="input" handler="socketInput(socket, data)" />
<hr/>
<div class="${id}_spare>
<h3 class="caption" cng-local="label.title.model"></h3>
<div cng-local="label.model.registered"></div>
<table>
<tr ng-repeat="(key, value) in model.changes" ng-class="{true:'even', false:'odd'}[$even]">
<td class="label">{{key}}</td>
<td class="value">{{value}}</td>
#if ( $settings.enableModelEditing )
<td ng-click="removeModelChange(key)"><a href="#">X</a></td>
#end
</tr>
</table>
#if ( $settings.enableModelEditing )
<div cng-local="label.model.instruction"></div>
<table>
<tr class="even">
<td class="label" cng-local="label.model.property"></td>
<td class="value"><input type="text" ng-model="property"></td>
</tr>
<tr class="odd">
<td class="label" cng-local="label.model.value"></td>
<td id="value" class="value">{{input[property]}}</td>
</tr>
<tr class="even">
<td class="label" cng-local="label.model.change"></td>
<td class="value"><input type="text" ng-model="newValue"></td>
</tr>
<tr class="odd">
<td colspan="2" class="commit"><button type="button" ng-click="commitModelChanges(property, newValue)" cng-local="label.model.commit"/></td>
</tr>
</table>
#end
</div>
<cng-event type="model" handler="modelChanged(model, property)" />
<hr/>
</div>
<cng-event type="ready" handler="init()" />
</widget>
Enabling HTML Templates for Session Information
Procedure
<widget>
<!-- ... -->
<div class="${id}_spare">
<!-- ... -->
<table>
<tr ng-repeat="(key, value) in model.changes" ng-class="{true:'even', false:'odd'}[$even]">
<!-- ... -->
#if ( $settings.enableModelEditing || $sessionInfo.admin )
<!-- ... -->
#end
</tr>
</table>
#if ( $settings.enableModelEditing || $sessionInfo.admin)
<!-- ... -->
#end
<!-- ... -->
</div>
<!-- ... -->
</widget>
As a result, we get a fully working HTML template widget written using AngularJS.
Session info
Source Files at This Stage - Session Info
You can check the source files at this stage.
See full content of angularwidget.html
<widget ng-controller="SampleAngularController">
<h1 class="caption ${id}_spare" cng-local="label.title.hello"></h1>
<h3 class="caption ${id}_spare">{{sessionInfo.customAttributes['name']}} ({{sessionInfo.currentUser}})! $labels.local.label.title.sockets</h3>
<div ng-if="!input" cng-global="label.noinput"></div>
<table ng-if="input">
<tr>
<td class="header" cng-local="label.socket.input"></td>
<td class="header" cng-local="label.socket.output"></td>
</tr>
<tr>
<td>
<table>
<tr ng-repeat="(key, value) in input" ng-class="{true:'even', false:'odd'}[$even]">
<td class="label">{{key}}</td>
<td class="value">{{value}}</td>
</tr>
</table>
</td>
<td>
<table>
<tr ng-repeat="(key, value) in output" ng-class="{true:'even', false:'odd'}[$even]">
<td class="label">{{key}}</td>
<td class="value">{{value}}</td>
</tr>
</table>
</td>
</tr>
</table>
<cng-event type="socket" id="input" handler="socketInput(socket, data)" />
<hr/>
<div class="${id}_spare>
<h3 class="caption" cng-local="label.title.model"></h3>
<div cng-local="label.model.registered"></div>
<table>
<tr ng-repeat="(key, value) in model.changes" ng-class="{true:'even', false:'odd'}[$even]">
<td class="label">{{key}}</td>
<td class="value">{{value}}</td>
#if ( $settings.enableModelEditing || $sessionInfo.admin )
<td ng-click="removeModelChange(key)"><a href="#">X</a></td>
#end
</tr>
</table>
#if ( $settings.enableModelEditing || $sessionInfo.admin)
<div cng-local="label.model.instruction"></div>
<table>
<tr class="even">
<td class="label" cng-local="label.model.property"></td>
<td class="value"><input type="text" ng-model="property"></td>
</tr>
<tr class="odd">
<td class="label" cng-local="label.model.value"></td>
<td id="value" class="value">{{input[property]}}</td>
</tr>
<tr class="even">
<td class="label" cng-local="label.model.change"></td>
<td class="value"><input type="text" ng-model="newValue"></td>
</tr>
<tr class="odd">
<td colspan="2" class="commit"><button type="button" ng-click="commitModelChanges(property, newValue)" cng-local="label.model.commit"/></td>
</tr>
</table>
#end
</div>
<cng-event type="model" handler="modelChanged(model, property)" />
<hr/>
</div>
<cng-event type="ready" handler="init()" />
</widget>
Sources
You can check the source files at this stage.
angularCNG/backoffice/resources/widgets/angularwidget/definition.xml
<widget-definition xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://www.hybris.com/schema/cockpitng/widget-definition.xsd"
id="example.cng.angular.widgets.angularwidget">
<name>Angular Sample Widget</name>
<description>Sample widget using AngularJS</description>
<defaultTitle>AngularJS Sample Widget</defaultTitle>
<author>hybris</author>
<version>1.0</version>
<view src="angularwidget.html" />
<keywords>
<keyword>AngularJS</keyword>
</keywords>
<sockets>
<input id="input" type="java.lang.Object" />
<output id="output" type="<T>" />
<output id="stats" type="example.cng.angular.model.SearchStatistics" />
</sockets>
<settings>
<setting key="enableModelEditing" type="java.lang.Boolean" default-value="false"/>
<setting key="initialChanges" type="java.lang.String" default-value="{ "globalOperator" : "AND" }"/>
<setting key="isTemplate" type="java.lang.Boolean" default-value="true"/>
</settings>
</widget-definition>
angularCNG/backoffice/resources/cng/js/js-libraries.xml
<libraries xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="http://www.hybris.com/schema/cockpitng/libraries.xsd">
<library>
<name>angular</name>
</library>
<library protocol="RESOURCE" url="widgets-angular.js" /> <!-- this is a local library for extension-only use -->
<library protocol="CLASSPATH" url="alerts.js" /> <!-- this is a dependency that can be defined by any other extension -->
</libraries>
angularCNG/resources/cockpitng/cng/alerts.js
const LEVEL_ERROR = "ERROR";
const LEVEL_WARNING = "Warning";
const LEVEL_INFO = null;
showMessage = function (level, message) {
if (level === undefined || level == null) {
alert(message);
} else {
alert(level + ": " + message);
}
};
angularCNG/backoffice/resources/cng/js/widgets-angular.js
AngularCNG.init = function(widgetName, controller, services) {
return new AngularCNG( "widgets." + widgetName ).controller(controller, services);
};
angularCNG/backoffice/resources/widgets/angularwidget/angularwidget.html
<widget ng-controller="SampleAngularController">
<h1 class="caption ${id}_spare" cng-local="label.title.hello"></h1>
<h3 class="caption ${id}_spare">{{sessionInfo.customAttributes['name']}} ({{sessionInfo.currentUser}})! $labels.local.label.title.sockets</h3>
<div ng-if="!input" cng-global="label.noinput"></div>
<table ng-if="input">
<tr>
<td class="header" cng-local="label.socket.input"></td>
<td class="header" cng-local="label.socket.output"></td>
</tr>
<tr>
<td>
<table>
<tr ng-repeat="(key, value) in input" ng-class="{true:'even', false:'odd'}[$even]">
<td class="label">{{key}}</td>
<td class="value">{{value}}</td>
</tr>
</table>
</td>
<td>
<table>
<tr ng-repeat="(key, value) in output" ng-class="{true:'even', false:'odd'}[$even]">
<td class="label">{{key}}</td>
<td class="value">{{value}}</td>
</tr>
</table>
</td>
</tr>
</table>
<cng-event type="socket" id="input" handler="socketInput(socket, data)" />
<hr class="${id}_spare"/>
<div class="${id}_spare">
<h3 class="caption" cng-local="label.title.model"></h3>
<div cng-local="label.model.registered"></div>
<table>
<tr ng-repeat="(key, value) in model.changes" ng-class="{true:'even', false:'odd'}[$even]">
<td class="label">{{key}}</td>
<td class="value">{{value}}</td>
#if ( $settings.enableModelEditing || $sessionInfo.admin )
<td ng-click="removeModelChange(key)"><a href="#">X</a></td>
#end
</tr>
</table>
#if ( $settings.enableModelEditing || $sessionInfo.admin )
<div cng-local="label.model.instruction"></div>
<table>
<tr class="even">
<td class="label" cng-local="label.model.property"></td>
<td class="value"><input type="text" ng-model="property"></td>
</tr>
<tr class="odd">
<td class="label" cng-local="label.model.value"></td>
<td id="value" class="value">{{input[property]}}</td>
</tr>
<tr class="even">
<td class="label" cng-local="label.model.change"></td>
<td class="value"><input type="text" ng-model="newValue"></td>
</tr>
<tr class="odd">
<td colspan="2" class="commit"><button type="button" ng-click="commitModelChanges(property, newValue)" cng-local="label.model.commit"/></td>
</tr>
</table>
#end
<cng-event type="model" handler="modelChanged(model, property)" />
<hr/>
</div>
<cng-event type="ready" handler="init()" />
</widget>
angularCNG/backoffice/resources/widgets/angularwidget/angularwidget.js
SampleAngularController = function($scope) {
$scope.socketInput = function(socketId, data) {
showMessage(LEVEL_INFO, $scope.adapter.cng().local("message.socket"));
$scope.input = jQuery.extend(true, {}, data);
var changes = $scope.model.changes;
if (changes) {
for(var key in changes) {
if (typeof(data[key]) != "undefined") {
data[key] = changes[key];
}
}
}
$scope.output = data;
$scope.adapter.cng().socketEvent("output", data);
if (!$scope.model.statistics) {
$scope.model.statistics = { $class: "example.cng.angular.model.SearchStatistics"}; // Notice! We explicitly point a Java class
$scope.model.statistics.stats = {};
}
if (data.typeCode) {
var stats = $scope.model.statistics.stats[data.typeCode];
if (!stats) {
stats = {};
stats.typeCode = data.typeCode;
stats.count = 0;
$scope.model.statistics.stats[data.typeCode] = stats;
}
stats.count += 1;
$scope.adapter.cng().model("statistics", $scope.model.statistics);
$scope.adapter.cng().socketEvent("stats", $scope.model.statistics);
}
jQuery("." + $scope.adapter.cng().id() + "_spare").hide();
};
$scope.modelChanged = function(model, property) {
showMessage(LEVEL_WARNING, $scope.adapter.cng().local("message.model") + ": " + property);
$scope.property = null;
$scope.newValue = null;
$scope.model = model;
};
$scope.commitModelChanges = function(key, value) {
var changes = $scope.model.changes;
if (!changes) {
changes = {};
}
changes[key] = value;
$scope.adapter.cng().model('changes', changes);
};
$scope.removeModelChange = function(key) {
var changes = $scope.model.changes;
delete changes[key];
$scope.adapter.cng().model('changes', changes);
};
$scope.init = function() {
if (!$scope.model) {
$scope.model = {};
}
$scope.statistics = $scope.adapter.cng().model("statistics");
var settings = $scope.settings["initialChanges"];
if (settings && !$scope.model.changes) {
var changes = JSON.parse(settings);
$scope.model.changes = changes;
$scope.adapter.cng().model("changes", changes);
}
};
};
AngularCNG.init("angularwidget", SampleAngularController);
angularCNG/backoffice/resources/widgets/angularwidget/labels/labels.properties
label.title.sockets = Below, you may see what has been received through an input socket label.title.hello = Hello! This example shows how to write widgets using 3rd party js libraries message.socket = New socket message received label.socket.input=Input label.socket.output=Output label.title.model = Here you may see, how a widget integrates with model label.model.instruction = Type property name, to change its value label.model.commit = Commit change label.model.property = Property label.model.value = Current value label.model.change = New value label.model.registered = Registered changes message.model = Model changed
angularCNG/resources/angularCNG-backoffice-labels/labels_fr.properties
label.noinput = Rien ne s'est passé, j'attends le message...
angularCNG/backoffice/resources/widgets/angularwidget/labels/labels_fr.properties
label.title.sockets=Au-dessous, vous pouvez voir ce qui a été reçu via prise d'entrée. label.title.hello=Salut! Cet exemple montre comment écrire widget en utilisant des librairies externes. message.socket=Message réçu via prise d'entrée.
angularCNG/backoffice/resources/widgets/angularwidget/angularwidget.css
.yw-labelstyle
{
color: red;
}
.caption {
font-weight: bold;
color: #13275a;
}
table {
border: none;
margin: 0;
}
.caption {
font-weight: bold;
color: #13275a;
}
table .header {
text-align: center;
font-weight: bold;
background-color: #13275a;
color: #c2e1ef;
}
table .label {
text-align: right;
}
table .value {
text-align: left;
}
table .even {
background-color: #c4c4c4;
}
table .odd {
background-color: #6699cc;
}
table .commit {
text-align: center;
}
widget a {
text-decoration: none;
color: #303030;
}
widget a:hover {
text-decoration: underline;
color: #303030;
}
angularCNG/resources/angularCNG-backoffice-spring.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.xsd">
<bean id="angularCNGLabelLocator" class="com.hybris.cockpitng.util.labels.ResourcesLabelLocator" scope="singleton" init-method="init" lazy-init="false">
<property name="location" value="/angularCNG-backoffice-labels/"/>
<property name="name" value="labels"/>
</bean>
<bean id="typeSearchStatisticsObjectMapperFactory" class="example.cng.angular.json.TypeSearchStatisticsMapperConfiguration" />
<bean parent="abstractJSONMapperExtender" lazy-init="false" init-method="extend" destroy-method="clean">
<property name="additionalValues">
<list merge="true" value-type="com.hybris.cockpitng.json.ObjectMapperConfiguration">
<ref bean="typeSearchStatisticsObjectMapperFactory" />
</list>
</property>
</bean>
<bean id="userNameSessionDecorator" class="example.cng.angular.session.UserNameSessionDecorator">
<property name="userService" ref="userService" />
</bean>
<bean parent="abstractJsWidgetSessionInfoCollectorExtender" lazy-init="false" init-method="extend" destroy-method="clean">
<property name="additionalValues">
<list merge="true" value-type="com.hybris.cockpitng.util.js.JsWidgetSessionInfoDecorator">
<ref bean="userNameSessionDecorator" />
</list>
</property>
</bean>
</beans>
of the