Skip to Content
Technical Articles

SAP Analytics Cloud, Analytics Application Slideshow Controller

In this short tutorial, we will build a SAP Analytics Cloud, Analytics Application Slideshow controller. The application is displayed in the big screen and people can interact with it to play the slideshow with a wireless device via Bluetooth.

Let’s prepare what we need to build:

  • HTML5 App
  • SAP Analytics Cloud, Analytics Application
  • BBC micro:bit Setup

HTML5 App

Create an HTML5 app with the codes below and hosted in NodeJS server or in SAP Cloud Platform.

In these scripts, it tries to search the available micro:bit and register the necessary Bluetooth services to listen to the button press-events for button A and B.
Once the event is triggered (handleCharacteristicValueChanged1 for button A and handleCharacteristicValueChanged2 for button B), it will send the relevant message to the child frame where the SAP Analytics Application resides.

<html>
<head>
    <title>SAP Analytics Cloud Slideshows</title>
    <meta name="description" content="SAP Analytics Cloud">
    <meta charset="utf-8">
    <meta http-equiv="X-UA-Compatible" content="IE=edge,chrome=1">
    <meta name="viewport" content="width=device-width, minimum-scale=1.0, initial-scale=1, user-scalable=yes">
    <meta name="mobile-web-app-capable" content="yes">
    <meta name="apple-mobile-web-app-capable" content="yes">
    
    <script src="./web/bower_components/webcomponentsjs/webcomponents-lite.min.js"></script>
    <!-- Polymer components -->
    <link rel="import" href="./web/bower_components/paper-progress/paper-progress.html">
    <link rel="import" href="./web/bower_components/paper-button/paper-button.html">
    <link rel="import" href="./web/bower_components/iron-icons/iron-icons.html">
    <link rel="import" href="./web/bower_components/iron-icons/image-icons.html">
    <link rel="import" href="./web/bower_components/paper-icon-button/paper-icon-button.html">
    <link rel="import" href="./web/bower_components/paper-card/paper-card.html">
    <link rel="import" href="./web/bower_components/paper-dialog/paper-dialog.html">
    <link rel="import" href="./web/bower_components/paper-toggle-button/paper-toggle-button.html">
    <link rel="import" href="./web/bower_components/iron-flex-layout/iron-flex-layout.html">

    <link rel="import" href="./web/bower_components/paper-dialog/paper-dialog.html">
    <link rel="import" href="./web/bower_components/paper-button/paper-button.html">
    <link rel="import" href="./web/bower_components/paper-input/paper-input.html">

    <link rel="import" href="./web/bower_components/paper-styles/color.html">
    <link rel="stylesheet" href="./web/bower_components/paper-styles/demo.css">

    <style is="custom-style">
        paper-progress {
            width: 100%;
        }
        
        paper-progress.blue {
            paper-progress-active-color: var(--paper-light-blue-500);
            paper-progress-secondary-color: var(--paper-light-blue-100);
        }
        
        paper-toggle-button.blue {
            --paper-toggle-button-checked-bar-color: var(--paper-light-blue-500);
            --paper-toggle-button-checked-button-color: var(--paper-light-blue-500);
            --paper-toggle-button-checked-ink-color: var(--paper-light-blue-500);
            --paper-toggle-button-unchecked-bar-color: var(--paper-light-blue-900);
            --paper-toggle-button-unchecked-button-color: var(--paper-light-blue-900);
            --paper-toggle-button-unchecked-ink-color: var(--paper-light-blue-900);
        }
        
        paper-button {
            display: block;
            width: 100px;
            height: 40px;
            min-width: 0em;
            margin: 0.2em 0.2em;
        }
        
        paper-button.blue {
            color: var(--paper-light-blue-500);
            paper-button-flat-focus-color: var(--paper-light-blue-50);
        }
        
        #cards {
            margin-left: auto;
            margin-right: auto;
            max-width: 300px;
        }
        
        paper-card {
            --paper-card-header-text: {
                font-size: 10px;
            }
            ;
            margin-bottom: 1px;
            margin-top: 1px;
            width: 100%;
        }
        
        .flex {
            @apply(--layout-horizontal);
        }
        
        paper-button.indigo {
            background-color: var(--paper-indigo-500);
            color: white;
            --paper-button-raised-keyboard-focus: {
                background-color: var(--paper-pink-a200) !important;
                color: white !important;
            }
            ;
        }
    </style>
</head>

<body unresolved>
    <div id="cards">
        <paper-card heading="SAP Analytics Cloud Demo">
            <div class="card-content">
                <paper-toggle-button class="blue" id="connect">Connect</paper-toggle-button>
                <paper-progress id="progress" indeterminate></paper-progress>
            </div>
        </paper-card>
        <paper-dialog id="no-bluetooth">
            <h2>No Web Bluetooth</h2>
            <p>The Web Bluetooth API is missing. Please enable it at chrome://flags/#enable-web-bluetooth and try again.</p>
        </paper-dialog>
        <paper-dialog id="errorDialog">
            <h2>Error</h2>
            <p>Could not connect to bluetooth device!</p>
        </paper-dialog>
    </div>
    <br>
    <script>
        'use strict';
        document.addEventListener('WebComponentsReady', () => {
            let connectToggle = document.querySelector('#connect');
            let progress = document.querySelector('#progress');
            let dialog = document.querySelector('#errorDialog');
            let gattServer;
            let commandService;
            let writeCharacteristic;
            let writeClientReqCharacteristic;
            let writeClientEvtCharacteristic;
            let busy = false;
            let commandQueue = [];
            progress.hidden = true;


			function bindEvent(element, eventName, eventHandler) {
                if (element.addEventListener) {
                    element.addEventListener(eventName, eventHandler, false);
                } else if (element.attachEvent) {
                    element.attachEvent('on' + eventName, eventHandler);
                }
            }

            var iframeSource = '__REPLACE_WITH_SAP_ANALYTICS_CLOUD_APP_URL__';
            // Create the iframe
            var iframe = document.createElement('iframe');
			iframe.setAttribute('src', iframeSource);
	        iframe.setAttribute('id', 'the_iframe');
	        iframe.setAttribute('frameborder', 0);
	        iframe.setAttribute('style', 'overflow: hidden; height: 100%; width: 100%; position: absolute;');

        	document.body.appendChild(iframe);

			// Send a message to the child iframe        
			var iframeEl = document.getElementById('the_iframe');
			// Send a message to the child iframe
			var sendMessage = function(msg) {
				// Make sure you are sending a string, and to stringify JSON
				iframeEl.contentWindow.postMessage(msg, '*');
			};

            // Listen to message from child window
            bindEvent(window, 'message', function(e) {
                if (e.data !== "embed:ready" && !e.data.event_type) {
                    console.log({
                        message: e.data
                    });
                }
            });

			function handleCharacteristicValueChanged1(event) {
			   //Button A
			   let value = event.target.value;
                let a = [];
                // Convert raw data bytes to hex values just for the sake of showing something.
                // In the "real" world, you'd use data.getUint8, data.getUint16 or even
                // TextDecoder to process raw data bytes.
                for (let i = 0; i < value.byteLength; i++) {
                  a.push('0x' + ('00' + value.getUint8(i).toString(16)).slice(-2));
                }
                if (a.join(' ') == '0x01') {
                	sendMessage("1");
					console.log("Button A");
				}
			   
			}

			function handleCharacteristicValueChanged2(event) {
				//Button B
				let value = event.target.value;
                let a = [];
                // Convert raw data bytes to hex values just for the sake of showing something.
                // In the "real" world, you'd use data.getUint8, data.getUint16 or even
                // TextDecoder to process raw data bytes.
                for (let i = 0; i < value.byteLength; i++) {
                  a.push('0x' + ('00' + value.getUint8(i).toString(16)).slice(-2));
                }
                if (a.join(' ') == '0x01') {
                	sendMessage("-1");
					console.log("Button B");
				}
			}

            /**
             * Connect to command characteristic.
             */
            connectToggle.addEventListener('click', () => {
                if (gattServer != null && gattServer.connected) {
                    if (gattServer.disconnect) {
                        console.log('Disconnecting...');
                        gattServer.disconnect();
                    }
                    resetVariables();
                } else {
                    console.log('Connecting...');
                    connectToggle.checked = true;
                    progress.hidden = false;
                    if (writeCharacteristic == null) {
                        navigator.bluetooth.requestDevice({
                                filters: [{
                                    namePrefix: 'BBC micro:bit',
                                }],
                                optionalServices: ['e95d9882-251d-470a-a062-fa1922dfa9a8'] //BUTTONSERVICE_SERVICE_UUID
                            })
                            .then(device => {
                                console.log('Connecting to GATT Server...');
                                return device.gatt.connect();
                            })
                            .then(server => {
                                console.log('> Found GATT server');
                                gattServer = server;
                                // Get command service
								return gattServer.getPrimaryService('e95d9882-251d-470a-a062-fa1922dfa9a8'); //BUTTONSERVICE_SERVICE_UUID
                            })
                            .then(service => {
                                console.log('> Found command service');
                                commandService = service;
								return commandService.getCharacteristic('e95dda90-251d-470a-a062-fa1922dfa9a8'); //BUTTON1STATE_CHARACTERISTIC_UUID
                            })
                            .then(characteristic => {
							   console.log('> Found write characteristic - Start Notification Button 1');

							   return characteristic.startNotifications().then(_ => {
								   console.log('> Notifications started');
								   characteristic.addEventListener('characteristicvaluechanged',
									   handleCharacteristicValueChanged1);
							   });
						   })
						   .then(service => {
							   console.log('> Found command service Button B');
							   return commandService.getCharacteristic('e95dda91-251d-470a-a062-fa1922dfa9a8'); //BUTTON2STATE_CHARACTERISTIC_UUID
						   })
						   .then(characteristic2 => {
							   console.log('> Found write characteristic - Start Notification Button 2');

							   return characteristic2.startNotifications().then(_ => {
								   console.log('> Notifications started');
								   characteristic2.addEventListener('characteristicvaluechanged',
									   handleCharacteristicValueChanged2);
								   progress.hidden = true;
							   });
						   })
                        .catch(handleError);
                    } else {
                        progress.hidden = true;
                    }
                }
            });

            
            /**
             * Check if browser supports Web Bluetooth API.
             */
            if (navigator.bluetooth == undefined) {
                document.getElementById("no-bluetooth").style.display = "block";
                document.getElementById("no-bluetooth").open();
            }
            /**
             * Reset the app variable states.
             */
            function resetVariables() {
                busy = false;
                progress.hidden = true;
                gattServer = null;
                commandService = null;
                writeCharacteristic = null;
                writeClientReqCharacteristic = null;
                writeClientEvtCharacteristic = null;
                connectToggle.checked = false;
            }
            /**
             * API error handler.
             */
            function handleError(error) {
                console.log(error);
                resetVariables();
                dialog.open();
            }
        });
    </script>
</body>
</html>

Replace the variable iframeSource with the SAP Analytics Cloud, Analytics Application URL.

var iframeSource = '__REPLACE_WITH_SAP_ANALYTICS_CLOUD_APP_URL__';

SAP Analytics Cloud, Analytics Application

  • Insert 10 Tab Strips and add the image inside each Tab Strip.
  • Cover the header of the Tab Strip for example using a shape.
  • Insert Web Page and add the link to HTML5 app.
  • Under Scripting > Script Variables, insert index variable and set the default value to 1.
  • Also insert Total_tab variable and set the default value to the total number of Tab Strip which is 10.
  • Under Canvas > onPostMessageReceived, insert the following scripts:
    console.log(message);
    Index = ConvertUtils.stringToInteger(message) + Index;
    console.log(Index);
    
    if(Index > Total_Tab) {
    	Index = 1;
    }
    if(Index < 1) {
    	Index = Total_Tab;
    }
    
    var tabx = ConvertUtils.numberToString(Index);
    
    TabStrip_1.setSelectedKey("Tab_" + tabx);​

  • Save the app.

micro:bit Setup

All the necessary setup is completed now we can see it in action.

Run the App

Open the link to the HTML5 App and click Connect button to connect to the micro:bit.

Press the button A on micro:bit to move to the next slide and button B to move to the earlier slide.

See it it in action:

 

Be the first to leave a comment
You must be Logged on to comment or reply to a post.