SAP Screen Personas (SSP) is a great tool to improve the user experience of SAP transactions. Hiding fields, moving them around and automating steps through scripting is straightforward and powerful, but sometimes it’s just not enough. Although very rich, the set of UI controls available in SSP is limited, we would hence need a way to integrate our own code, yet being good citizens of the SSP framework.
In this article, we’ll describe how to inject our own HTML/Javascript/CSS using SSP’s HTML Viewer and how to exchange data from and to our custom control and the SSP framework. This article, only contains a few code snippets, you can find the complete sample on github.
The HTML Viewer is part of the standard SSP standard UI controls. It can be used to render any HTML content on the screen and offers two APIs to set its content:
Technically speaking, the HTML Viewer is just a HTML iframe, that is absolutely positioned on the page. Using the content property of the HTML Viewer we will be able to inject our own code in any SSP Flavor. In the course of this article, we will use the terms HTML Viewer, iframe and frame interchangeably to refer to the UI control we just described.
The basic idea behind the approach of using the HTML Viewer [to inject custom code] should be straightforward and has already been investigated. Starting from Steve Rumsby’s article, we will investigate an approach to build a library of controls that can be used to enrich the experience of SSP Flavors.
Steve Rumsby’s article, shows us how to inject custom code and that’s a great starting point, but in order to build the library of controls, we need to find a solution to the following challenges:
We will now show how we solved the challenges listed above. For clarity we will use as an example the injection of a custom HTML input of type date in a SSP Flavor, throughout the rest of this article we will refer to the control as the “date picker” or the “control” interchangeably. Based on the approach for the date picker, it will be hopefully easy for the reader, to adapt our proposed strategy to other types of controls.
Before we describe the code to generate the date picker, we will dive into the problem of communicating with the iframe. In order for multiple Window objects to communicate without security issues, the browser offers the Window.postMessage method.
Calling Window.postMessage triggers an event that the receiving Window can subscribe to using the EventTarget.addEventListener method with message as the event type.
In order to simplify the generation of events, we implemented a new event listener which is built on top of the Window.postMessage API.
Figure 1: The EventListener
In Figure 1 we show how the EventListener interacts with the elements in the page acting as a new dispatcher of events on top of the Window.postMessage API:
When writing custom scripts in SSP, we need a way to render custom content in the HTML Viewer, but also a way to reference it and attach event listeners or programmatically update its value.
In order to render the date picker and be able to attach event listeners we came up with a model function that could be easily generalized into a base control class.
Figure 2 shows a stripped down version of the getDatePicker function, it receives as input the id of the HTML Viewer where the date picker needs to be rendered and returns an object which exposes the public APIs for the control:
function getDatePicker(iFrameId) {
function render (date) {
//this method returns the html
return datePickerHtml;
}
function setDate (date) {
iFrame.content = render(date);
}
function onDateChange (data) {
var newDate = data.value;
setDate(newDate);
for (var i = 0; i < listeners.length; i++) {
listeners[i].call(null, newDate);
}
}
EventListener.addEventListener(iFrameId, 'change', onDateChange, null);
function addEventListener (fn) {
listeners.push(fn);
}
function destroy () {
listeners = [];
EventListener.removeEventListener(iFrameId, 'change');
iFrame.content = '';
}
var iFrame = session.findById(iFrameId);
iFrame.content = render();
var listeners = [];
return {
setDate: setDate,
onChange: addEventListener,
destroy: destroy
};
}
Figure 2: The getDatePicker function
The article by Steve Rumsby mentioned earlier shows that in order to inject the content in the HTML Viewer, we need to write our source code into a big string of HTML code. As much as this approach works, it is not scalable when we deal with large amounts of code. We will list below a few utilities and recommended practices we believe would help the generation of the code, minimizing the amount of code to be written directly into a string.
Unlike Object.toString that returns a generic representation of a Javascript Object (e.g.: “[object Object]”), Function.toString returns the actual source code of the function. Both Chrome and IE11 fully support this way of serializing functions, it’s a great opportunity to write your code as Javascript and serialize it into a string using a utility.
This function should be the preferred way to generate the HTML in the render method of our custom controls.
It takes as input a tagName, an array of children (expected to be an array of Strings) and an optional array of attributes (sent as a JSON objects of key, value pairs).
It returns a string wrapped around tagName.
function wrap (tagName, children, attrs) {
if (!Array.isArray(children)) {
children = [children];
}
return getStartTag(tagName, attrs) + children.join('') + getEndTag(tagName);
}
An example of its usage, is shown below
wrap(HTML_TAG, [
wrap(HEAD_TAG, [
getScript(‘alert(“Hello World!”)’)
]),
wrap(BODY_TAG,
wrap(DIV_TAG, 'Hello World!', [getAttrObject('id', 'content')])
)
]);
This, will return the following HTML
<html>
<head>
<script>alert("Hello World");</script>
</head>
<body>
<div id=”content”>Hello World!</div>
</body>
</html>
It’s readability could definitely be improved, but this approach is less error prone than writing all the code in one line.
The wrap function presented above, can be used to build higher level functions to help us generate code even more easily.
The getScript function uses wrap to build a script tag and takes a variable number of arguments that are expected to be either strings or pointers to function. Using Function.toString on functions, the utility converts pointers to function into strings and builds the script tag.
function getScript () {
var code = [];
for (var i = 0; i < arguments.length; i++) {
if (typeof arguments[i] === 'function') {
code.push(arguments[i].toString());
} else {
code.push(arguments[i]);
}
}
return wrap(SCRIPT_TAG, code.join(';'));
}
An example
function sayHello () {
alert('Hello World');
}
getScript(sayHello);
This returns
<script>
function sayHello () {
alert("Hello World");
}
</script>
Another useful utility generates SAPUI5 bootstrap script, it’s pretty straightforward and its source code is shown below.
function getUi5BootstrapScript () {
return wrap(SCRIPT_TAG, '', [
getAttrObject('src', UI5_CORE),
getAttrObject('data-sap-ui-libs', 'sap.ui.core,sap.ui.commons'),
getAttrObject('data-sap-ui-theme', 'sap_goldreflection')
]);
}
In this article, we investigated an alternative way of extending SSP with new controls, we also offered an approach to build such custom code in a way that supporting and editing it is scalable. This approach is very powerful, but needs to be used wisely as iframes are expensive to load.
This article has been written with the help and inestimable support of daniel.fliegauf and harikrishna.lingamagunta.