Additional Blogs by SAP
cancel
Showing results for 
Search instead for 
Did you mean: 
Former Member

Introduction

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

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:

  1. the url property, which should point to a remote URL containing a webpage and
  2. the content property which accepts a string of HTML code and renders it.

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.

Pitfalls of the HTML Viewer

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:

  • Interacting with the control:
    • As the control lives in an iframe, its DOM reference is not directly accessible due to security reasons, this means that it is not possible to attach event listeners nor to update its properties. We will use the browser’s Window.postMessage API, which allows the communication between Window objects in Javascript;
    • Even if we could access the DOM reference, we would not be able to update its properties. This is not possible because whenever something changes on the screen, a re-render of the whole screen is performed and the old content of the HTML Viewer is restored.
  • Setting the content property of the HTML Viewer forces us to write all of our code in a single string of HTML. This approach is obviously not scalable for controls requiring lots of code.

Interacting with the control

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.

A new event listener

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:

  1. 1) The EventListener listens for messages posted to the window object.
  2. 2) Whenever the content of an iframe needs to generate an event, it posts a new message to its parent Window. The message contains the id of the frame generating the event, then event name and an optional set of parameters.
  3. 3) Using the EventListener.addEventListener(iFrameId, eventName, callbackFunction, handler) API, it is possible to get notified whenever the frame with iFrameId posts the eventName event. We will see below, how our date picker uses such API to generate a change event whenever a new date is selected.

The date picker “class”

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:

  1. onChange: this method accepts a callback function to be called every time a new date is selected. Internally, the date picker uses the EventListener described above to generate the event. It should be clear at this point, that the EventListener is not supposed to be used directly, but its used by our controls to retrigger events through their public interfaces.
  2. setDate: this method is used to change the date value of the date picker. Internally, we are forced to regenerate the content of our HTML Viewer every time the new value is set.
  3. destroy: this method simply cleans up the listeners and the content of the HTML Viewer.

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

Generating the code

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.

Use the toString method of Function

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.

A set of utility functions to generate HTML code

wrap(tagName, children, attributes)

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.

getScript()

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>

getUi5BootstrapScript()

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

Conclusions

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.

2 Comments