Universal Wrapper

The Universal Wrapper is solution that facilitates communication between a Neptune Launchpad and an SAP Fiori App. By utilizing this wrapper, developers can register and listen to events, retrieve data, and handle all communication aspects in a secure and efficient manner while maintaining clear separation of logic between the two environments.

universal wrapper 0
Figure 1. A graphical overview

The Universal Wrapper fundamentally is an app and acts as an intermediary layer between the Neptune Launchpad and an SAP Fiori App, enabling smooth communication and enhancing the app’s capabilities. It achieves this by encapsulating the Fiori App within a custom-configured wrapper application. This wrapper application acts as a bridge, enabling communication and data exchange without encountering Cross-Origin Resource Sharing (CORS) issues.

Below is an example of an inbox application running on www.first-domain.neptune-software.com which embeds a Fiori application running on www.second-domain.neptune.com

universal wrapper 1

Wrapper Deployment

Please note that the Universal Wrapper is currently in the early stages of development. As a result, the deployment process may involve complexity and require careful attention. However, ongoing improvements are being made to enhance the ease of deployment in future versions. This guide provides an overview of the deployment process for the Universal Wrapper, outlining the steps involved.

Prerequisites

  • You have installed Visual Studio Code

  • You have installed the SAP Fiori Tools – Extension Pack extension in Visual Studio Code

Some developers might need the NPM package yo. This can be achieved by opening the console in the Visual Studio Code and running the command npm install yo

Procedure

  1. Unzip the Universal Wrapper ( app name: nepcontainerv1 ) and open the project in Visual Studio Code

  2. Navigate to the Controller folder and open the nfc.message.js file in there it’s necessary to change the line 5 and 6 to the correct Domains and save

    In the later versions, the location of the domains might be different
  3. Configure the Deployment Settings. The configuration is done by running the command npm run deploy-config in the command line. After execution, you will be asked to enter some information.

    universal wrapper 2
    If you encounter difficulty selecting the target system during the configuration process, you can resolve this issue by canceling the current configuration. Afterward, open the command palette by pressing Ctrl+Shift+P and search for Open application Generator. Select this option to create a dummy app and configure the SAP system within it. Once the configuration is complete, you can retry steps 1 and 3 to successfully proceed with the deployment process.
  4. Deploy the application. The deployment is done by running the command npm run deploy in the command line

After completing all the above steps, you will achieve to deploy the Universal Wrapper in your SAP Fiori Server.

Configuring your base application

The example provided below serves as a basic illustration

To enable data retrieval and handle communication and logic aspects, adjustments are required for the base app. Below is an example of configuring the base app to accept the Fiori app using the Universal container. The example involves a simple app that opens the Fiori app and navigates to a page displaying barcode details.

The component tree has the following structure:

universal wrapper 3

Regarding the components:

  • The scrollContainerIframe will hold an iframe element that will be mentioned in the nfc script.

  • The nfc script contains all the logic that allows the communication between the Fiori app and the base app.

var nfc = {
    openFiori: false,
    tilesParameter: {},
    // used in both URL
    // name of wrapper application: znep_u_wrapper

    //fioriDomain: window.location.origin, // same domains scenarios
    fioriDomain: 'https://my-fiori-domain.neptune-software.com', // different domains scenarios
    launchpad: '/sap/bc/ui5_ui5/ui2/ushell/shells/abap/FioriLaunchpad.html',
    launchpadParameters: [
        'spnego=disabled',
        'saml2=disabled',
        'sap-ushell-config=headerless',

    ],
    launchpadParametersTo: [
        'spnego=disabled',
        'saml2=disabled'
        // 'sap-ushell-config=headerless'
    ],
    // used to create the URL for the Universal Wrapper
    hashNavigationStandAlone: '#Shell-runStandaloneApp',
    standAloneParameters: [
        'sap-ushell-SAPUI5.Component=wrapper.neptune.Component',
        'sap-ushell-url=/sap/bc/ui5_ui5/sap/znep_u_wrapper'
    ],
    // used to create the URL for the approval

    hashNavigationMyInbox: '#',

    iframeReady: false,
    iframe: '',
    lastTimeStamp: 0,

    // here we attach the Universal Wrapper with the Fiori App inside of it
    init: function(data) {
        nfc.hashNavigationMyInbox = nfc.hashNavigationMyInbox + data.FioriApp;
        nfc.tilesParameter = data.FioriAppParameters;

        if (!nfc.iframeReady) {
            var renderTimer = setInterval(function() {

                let container = 'scrollContainerIframe';
                if (sap.n) {
                    container = sap.n.currentView.sId + '--' + container
                }
                let parentObject = document.getElementById(container);
                if (parentObject === null) return;

                window.addEventListener("message", nfc.messageRecived, false);

                let childIframe = document.createElement("iframe");

                let url = nfc.fioriDomain + nfc.launchpad;

                url = nfc.addParameter(url, nfc.launchpadParameters);
                url = url + nfc.hashNavigationStandAlone;
                url = nfc.addParameter(url, nfc.standAloneParameters);

                childIframe.setAttribute("src", url);

                childIframe.id = 'nepIframeMain';
                childIframe.style.width = "100%";
                childIframe.style.height = "100%";
                parentObject.appendChild(childIframe);

                window.clearInterval(renderTimer);
                nfc.iframeReady = 'true';
            }, 100);

        } else {
            let payload = nfc.buildPayload('NewUrl', nfc.tilesParameter);


            window.frames[0].postMessage(payload, nfc.fioriDomain);
        }

    },
    addParameter: function(url, parameters) {

        $.each(parameters, function(i, parameter) {
            if (i === 0) {
                url = url + '?' + parameter;
            } else {
                url = url + '&' + parameter;
            }
        });
        return url;
    },

    messageRecived: function(message) {

        if (message.origin !== nfc.fioriDomain) return;

        switch (message.data.event) {
            case 'Ready':

                let payload = nfc.buildPayload('Init', nfc.tilesParameter);
                nfc.iframe = message.source;
                nfc.iframe.postMessage(payload, message.origin);

                break;

            case 'Pressed':
                if (nfc.lastTimeStamp === message.timeStamp) return;
                nfc.lastTimeStamp = message.timeStamp;
                //  alert(message.data.action);
                if (message.data.action === 'Submit') {
                    setTimeout(function() {

                        butMasterUpdate.firePress();
                        handleNavigation();

                    }, 3000);
                }
                if (message.data.action === 'back') {
                    //alert(message.data.action);
                    AppCache.Back();

                }

                break;

            case 'Plugin':
                switch (message.data.action) {
                    case 'BarcodeScanner':
                        if (window?.cordova?.plugins?.barcodeScanner) {
                            cordova.plugins.barcodeScanner.scan(
                                // result callback function
                                function(result) {
                                    setTimeout(function() {
                                        nfc.iframe.postMessage({
                                            "event":"Plugin",
                                            "pluginID": "barcodeScanner",
                                            "status": "ok",
                                            "value": result
                                        }, nfc.fioriDomain);
                                    }, 0);
                                },
                                // error callback function
                                function(error) {
                                     nfc.iframe.postMessage({
                                        "event":"Plugin",
                                        "pluginID": "barcodeScanner",
                                        "status": "error",
                                        "value": ""
                                    }, nfc.fioriDomain);
                                },
                                // options object
                                {
                                    "preferFrontCamera": false,
                                    "showFlipCameraButton": true,
                                    "orientation": "landscape"
                                }
                            );
                        } else if (window?.ZXing?.BrowserMultiFormatReader) {
                            let selectedDeviceId;

                            codeReader = new ZXing.BrowserMultiFormatReader()
                            codeReader.listVideoInputDevices()
                                .then((videoInputDevices) => {
                                    selectedDeviceId = videoInputDevices[0].deviceId;
                                    App.to(oPage);
                                    startScan(selectedDeviceId);
                                })
                                .catch((err) => {
                                     nfc.iframe.postMessage({
                                        "event":"Plugin",
                                        "pluginID": "barcodeScanner",
                                        "status": "error",
                                        "value": ""
                                    }, nfc.fioriDomain);
                                })
                        }

                        break;
                    default:

                }
                break;

            case 'Error':
                if (message.data.action === 'init') {
                    DialogMissingRole.open();
                }
                break;
            default:

        }
    },
    buildPayload: function(step, data) {
        let url = nfc.fioriDomain + nfc.launchpad;
        url = nfc.addParameter(url, nfc.launchpadParametersTo);
        url = url + nfc.hashNavigationMyInbox;
        url = nfc.addParameter(url, data);

        let payload = {
            event: step,
            url: url,
            catchButtons: [
                //here we can pass to the fiori buttons names and the action we want to receive on neptune as soon as the
button is pressed
                {
                    id: 'backBtn',
                    action: 'back',
                    removeDefaultAction: true,
                    done: false
                }, {
                    id: '__xmlview1--productListPage-navButton',
                    action: 'back',
                    removeDefaultAction: true,
                    done: false
                }
            ],
            //here we can pass to the fiori which are the eventListener we whant to enable.
            eventListener: {
                dialog: true

            },

            hideObject: ["shellAppTitle",
                "sf",
                "meAreaHeaderButton"
            ],
            plugins: []
        };
        debugger;
        if (window?.cordova?.plugins?.barcodeScanner || window?.ZXing?.BrowserMultiFormatReader ) {
            payload.plugins.push({
                "provider": "cordova",
                "plugin": "barcodeScanner"
            });
        }

        return payload;
    }
};

In the buildPayload method, you have the capability to pass button names and define the corresponding actions to be triggered in Neptune when the buttons are pressed. Additionally, this method allows you to specify the event listeners that you want to enable for the Fiori app.

Within this example, we are including the following:

Button Names and Actions

Pass the names of the buttons that you want to display in the Fiori app. Define the corresponding actions that should be triggered in Neptune when each button is pressed. This enables seamless communication and synchronization between the Fiori app and the Neptune environment.

Event Listeners

Specify the event listeners that you want to enable within the Fiori app. These event listeners can be configured to listen for specific events or actions within the app and trigger corresponding functions or processes in Neptune. This functionality enhances interactivity and allows for dynamic communication between the app and Neptune.

The buildPayload method gives you the freedom to easily incorporate custom changes based on the specific data and functionality you need to retrieve from the Fiori app.

Now, we will to attach the Init script. Overall, this script is setting up event listeners and initializing some functionality related to the NFC.

var codeReader = "";
// InitLoad
sap.ui.getCore().attachInit(function(data) {


    setTimeout(function() {
        nfc.init(data);

    }, 200);
});


if (sap.n) {
    sap.n.Shell.attachBeforeClose(function(oEvent, defaultAction, closeType) {
        window.removeEventListener("message", nfc.messageRecived, false);

    });
}

Regarding the functionality of the barcode scanner, include the following in the PWA_ZXing script:

function startScan( selectedDeviceId) {

    codeReader.decodeFromVideoDevice(selectedDeviceId, 'video', (result, err) => {
        if (result) {
            App.back();
            codeReader.reset();
             nfc.iframe.postMessage({
                "event":"Plugin",
                "pluginID": "barcodeScanner",
                "status": "ok",
                "value": result
            }, nfc.fioriDomain);

        }
        if (err && !(err instanceof ZXing.NotFoundException)) {
            App.back();
            codeReader.reset();
             nfc.iframe.postMessage({
                "event":"Plugin",
                "pluginID": "barcodeScanner",
                "status": "error",
                "value": ""
            }, nfc.fioriDomain);

        }
    });
}

function stopScan() {
App.back();
codeReader.reset();
nfc.iframe.postMessage({
"event":"Plugin",
"pluginID": "barcodeScanner",
"status": "error",
"value": ""
}, nfc.fioriDomain);

}

Lastly, we configure the following to finalize the app.

Place the following in the AjaxError Event of the oPageIframe component:

sap.ui.core.BusyIndicator.hide();

Add the below in the Press Event of the oButtonScanEnd button:

stopScan();

Same for the ButtonClose button:

DialogMissingRole.close();

Place the following in the content of the oHTMLObject component:

<video id=video width=100% height=400 style=border: 1px solid gray></video>

To utilize the functionalities of the barcode scanner, incorporate the following script tag within the header of your launchpad:

<script src="https://unpkg.com/@zxing/library@latest/umd/index.min.js"></script>

Summary

Neptune Software recognizes the potential of embedding Fiori apps within a Neptune Launchpad, and we are committed to simplifying this process. We are actively working on developing more guides and tutorials to assist you in achieving seamless integration.