Skip to Content
Technical Articles
Author's profile photo Pranav Kandpal

Demystifying Custom Widgets for SAP Analytics Cloud

Introduction

While investigating on a custom solution for SAC widget , I realized there is still a lot of Mist around Custom Widget , primarily towards its hosting as well as creating a basic working version of a custom widget. The aim of this blog is to demystify the basics around custom widget and navigate through the custom widget world smoothly.

The blog will try to cover the below 2 aspects:

  • Deployment of Custom Widget in GitHub.
  • Creation of a simple custom widget which can be used as a ‘Hello world’ for navigating through the world of custom widgets.

Brief Background

Custom widgets is the Robin to the Batman , of course SAC being the Batman here (a bit of goofing around for the DC fans :D). No doubt Custom widget is the sidekick which SAC needs for extensions and adding more powerful features which are not delivered out of the box by SAC. But the question still remain – How do custom widgets provide extensions to SAC apps?

As we all know , SAC is a web based analytical product from SAP (as the name suggests as well) which displays the dashboards in a Web Browser. Web browsers use a combination of HTML , CSS and JS (JavaScript) files to act as an interpreter.

So in short it reads the text from these files and displays the result (which is what we see in the web browser). When you run a SAC dashboard and inspect the page , it will show the list of various files which SAC uses for creating the powerful results of the Dashboards.

You must be wondering , Why am i even mentioning all this? Well the answer to this another question – how is custom widget the sidekick of SAC and how can it be used to add extensions. As I mentioned earlier , a web browser uses JS and CSS files for interpreting what needs to be displayed as an output of the Web Page. A custom widget is also interpreted by the JS and CSS files. Its logic is written in the JS file and its styling is present in the CSS file. All we need to do is to add these files to the Web Page and once added , it will be read by the interpreter of the web page and perform the magic :).

Since SAC now provides the feature of bring your own widget with the custom widget option , this means we can bring our own JS and CSS files (by the custom widget) and add more features to your existing applications. Once a custom widget is added to a SAP Analytical Application , this means when this page will run , it will add also the JS and CSS files of the custom widget for the interpreter of the Web Page :).

Now since this Web Page and the CSS needs to be added to the Web Page , it needs to exist somewhere. Of course we can create these files locally in our PC and host it locally but when this Page will be loaded globally (lets say by another user in a different location) , these files will not be present for the user and the custom widget will not be loaded.Therefore these files need to be present somewhere globally which can be called by anyone across the internet in this whole wide world.

This is where the deployment of the custom widget comes into picture. These JS and CSS files are hosted/deployed somewhere and using a custom JSON file the location information of these files  is added to SAC (and of course more info).

So in short the most important files for a custom widget are :

  • JS Files – File holding the function relevant info
  • CSS Files – File for styling
  • JSON File – File which is loaded in SAC for providing the details about the custom widget.

 

‘Hello World’ of Custom Widget

Since we discussed some background around Custom Widget , now lets quickly dive into the most important part of this Blog. The deployment of a Custom widget in GitHub. However to deploy a custom widget , we need the files for custom widget. So lets begin with creating a very basic widget which I would refer to as the ‘Hello World’ for Custom widgets.

Lets make a simple widget which is a button and can be added to the SAC apps – I know there are buttons already present within SAC , but my aim is to showcase how :

  • Custom DOMS / Elements can be added to the SAC app
  • Event Listeners can be added for the Custom Widgets
  • Styling could be added later if needed.

To showcase these features , the easiest option is button and this is why i call it our ‘Hello World’.

Without further ado , lets jump over to our most import files for this solution.

JSON File

Well the JSON file is basically a file which contains some basic information about the Custom widget which is added to SAC to inform SAC about the details about this Custom Widget.

SAC Custom Widget Developer Guide provides more details about the custom widget and the section 6.1 within this guide gives more details about the JSON file , the object and properties. JSON file is basically it stores simple data structures and objects in JavaScript Object Notation (JSON) format.

The most important details relevant for us are below :

  • Root Object – This is where the attributes of your Custom widget are defined like the Name , Version , description etc. This is elaborated more in the text that follows.
  • Web Component Object : This contains information about the JS Files.
  • Properties object : This is where the properties of the custom widget could be added. A property would have its own data type and can contain a value
  • Method Object : Methods which can be used to perform some operations by the SAC app later or used internally for any specific logic.
  • Event Object : The Event object contains detail about the events which will be triggered by the Custom Widget.

There are some more object types which are relevant but for our small use case , these shall suffice. If you want more details about the JSON file objects , please check the SAC Developer Guide , Section 6.1

Throughout this blog i would refer to my GitHub repository which can be referenced from here .

The JSON file for our custom widget can be opened from here

Well the file is pretty straightforward and would look like below :

{
	"name": "mycustomwidget",
	"description": "Hello World",
	"newInstancePrefix": "mycustomwidget",
	"eula": "",
	"vendor": "PranavKandpal",
	"license": "",
	"id": "mycustomwidget",
	"version": "1.0.1",
	"icon": "",
	"webcomponents": [
		{
			"kind": "main",
			"tag": "custom-button",
			"url": "https://kandpalpranav.github.io/CustomWidget/Webcomponent.js",
			"integrity": ""	,
			"ignoreIntegrity": true
			
		}		
	],
	"properties": {
		"width": {
			"type": "integer",
			"default": 192
		},
		"height": {
			"type": "integer",
			"default": 32
		}
	},
	"methods": {
		"clear": {
			"description": "Clear the resultset"
		}
	},
	"events": {
		"onClick": {
			"description": "Called when the user clicks the button."
		}
	}
}

 

Since its a json file , it stores simple data structures and objects in JavaScript Object Notation (JSON) format.

The first and beginning half of the file has some basic information which is stored within the Key and Value pairs. Information like name , description , id and so on which is at the beginning of the JSON. Since these are like attributes of your custom widget any relevant text information can be provided here.

Then comes the interesting part which is the Web Component. Web component will refer to the various JS files. This is where you can define files which will control the functioning and styling of your custom widget. In our example JSON file , the Web Component information is as below :

"webcomponents": [
		{
			"kind": "main",
			"tag": "custom-button",
			"url": "https://kandpalpranav.github.io/CustomWidget/Webcomponent.js",
			"integrity": ""	,
			"ignoreIntegrity": true
			
		}		
	],

The properties associated with a Web Component are as below :

Kind :  What kind of component this Web Component represents.Possible values are:

  •  “main” (the actual custom widget)
  • “styling” (the Styling Panel of the custom widget)
  •  “builder” (the Builder Panel of the custom widget)

In our example we just want to build out Custom Widget so we use the main as Kind type.

Tag : Unique name of the custom element (its HTML tag name) of this Web Component.

URL : This is the most important part of our Web Component. This URL refers to the JS file which will contain the logic for the Custom Widget execution. As i mentioned earlier we will host our files in GitHub you will notice that in the url , there is a url with github in it. Lets leave this point open for now and discuss it later.

Integrity : String containing the hash value of the Web Component JavaScript file. Lets also discuss this point later once the JS file has been talked about more in detail.

There is also the possibility of adding some properties about custom widget , information about methods which will be used by the custom widget and events which will be triggered by this custom widget. This information is present within our file in the bottom half.

To be honest since we have a pretty basic widget this information isn’t the most important one for us for now, however for a beginning i have added properties for our widget which is the width and height of our widget , a method which can be used to perform some actions and within the events section we have a onclick event to handle the click event of our button (from our custom widget).

If you are wanting more information about the various possibilities of the JSON file please refer to the standard SAP documentation from SAC Custom Widget Developer Guide .

JavaScript Web Component

Now lets look at the second most interesting piece of this blog, the JS Web component. Lets decouple this JS and try to understand this in a more readable form.

The JS file which i will refer for this code is in the Github repository and can be opened directly from here

Lets begin with the basics here. The first task for us is to insert to create the Template of our Custom Widget. It totally depends on the use case how you want to define this. In our ‘Hello World’ example , we want to create a Button.

(function () {
    let tmpl = document.createElement('template');
    tmpl.innerHTML = 
    `<button type="button" id="myBtn">Helper Button</button>` ;   

 

The first 3 lines after the function definition takes care of this. We create a template and add an HTML for the button which has a Text ‘Helper Button’. However to add this piece of HTML code to your existing Web Page , which in our case is the SAC Analytic app , we need to explicitly add it as a custom element. This is done by using the Define method of Custom Elements.

The customElements define() method is used to define a new custom element which has a syntax of :

customElements.define( name, constructor, options );

Parameters:

  • name: It specifies the name for the new custom element. The name of custom elements must contain hyphen.
  • constructor: It specifies the constructor for the new custom element.
  • options: It specifies the object that controls how the element is defined. It is an optional parameter.

Now this is what we do at the end of our JS Code ( Line 30) , with the below code and use a constructor which is called PerformanceHelp :

customElements.define('custom-button', PerformanceHelp);

We define the details of our class from Line 6 onwards – to make it more readable lets make use of a function called init.

 class PerformanceHelp extends HTMLElement {
        constructor() {
            super();
            this.init();           
        }

The moment this custom element is now added to the SAC app , this Class would be called. Please note the class always extends HTMLElement.

The main piece of work happens within the Init method.

        init() {            
              
            let shadowRoot = this.attachShadow({mode: "open"});
            shadowRoot.appendChild(tmpl.content.cloneNode(true));
            this.addEventListener("click", event => {
            var event = new Event("onClick");
            this.fireChanged();           
            this.dispatchEvent(event);
            });           
        }

 

Within this method we use the appendChild method. The appendChild() method of the Node interface adds a node to the end of the list of children of a specified parent node. If the given child is a reference to an existing node in the document, appendChild() moves it from its current position to the new position. More details can be checked from here

So basically our button gets added to our WebPage node with this piece of code however we also need to ensure that the click of our button can be handled as well.

This is what is done with the addEventListener method. Within this method we use a fireChanged method to control the logic for what needs to be done when this event is actually clicked. In our case if you look within this method , we just write a message to the console , stating that the button was clicked.

Alright now we are all set for the deployment of our basic version of custom widget.

Deployment of Custom Widget

Well deployment is one of my core focus as to why I initially though of writing this blog. Incase you have a server to host your custom widget , life is pretty easy however looking for a free , reliable deployment server is the tricky part here.

So lets resolve it by using Github. Github can be used also a deployment server. Github provides this amazing feature called GitHub Pages which can be used as a website for your projects directly from your repository. The only downside is that it is publicly hosted.

Basic details about Github Pages can be found on the official page of GitHub Pages

Well the process for deployment is pretty straightforward , so lets quickly get started

Step 1 Create a Repository

The first step is to create a repository however the repository has to be public and should have the name username.github.io, where username is your username (or organization name) on GitHub.

Lets do so with an example. I am using a Github with the username kandpalpranav so basically i need to create a new repository which is called kandpalpranav.github.io

 

Some Background on Pages and Deployment

Once this repository is created , open the repository and from the settings please navigate over to Pages as shown in the below image :

 

Within Pages , you can control the deployment of your website , and chose the source of deployment. In general this setting is by default automatically created once a repository as shown above is created however if the settings are missing , please enable them as shown below :

Please note : I made the selection of root node as the branch for deployment. Anything under the root node will be deployed and can be accessed via the path (we will discuss this later).

Also within this section of pages , you will find how your website can be accessed. The below screenshot mentions where the site is live at and when exactly was the last deployment

Since now our site is live , lets try to understand how to navigate through our files and website.

My Repository currently just has a Readme.Md file :

 

Now to open this file directly via the link -> https://kandpalpranav.github.io/README.md

When we navigate over to this link , we will actually see the content of the Readme file ( the content righ now is what you see below)

Now lets make this relevant for us. We need to deploy our Web Component (JS File) , so lets upload this file. As shown below , i create a new folder called CustomWidget and added the file Webcomponent.js to it

 

Now to access this file we need to follow the path which is kandpalpranav.github.io/CustomWidget/Webcomponent.js  (URL would look like “https://kandpalpranav.github.io/CustomWidget/Webcomponent.js” )

When we open this link we basically open the source code of our JS file which is what our custom widget needs for enabling the widget main functionalities in SAC. If you open this link it will give you a result like below ;

And that is , the deployment is completed. Now we can use this file for our Custom Widget JSON File.

The deployment status of your website could also be checked using Actions as shown below :

 

Any change that you make to any file would lead to a new Page build and Deployment. Once the deployment is successful , the changes will start reflecting in your webpage.

A successful deployment would look like below :

 

Step 2 : Change the URL value in JSON File

Alright now lets go back to our JSON File (here) :

If you take a look at the webcomponents object , the key and value pair for url has a url which is

“https://kandpalpranav.github.io/CustomWidget/Webcomponent.js”
This is basically the same address which i talked about earlier in the WebComponent JS section above. This is where our WebComponent JS file is deployed in Github.
"webcomponents": [
		{
			"kind": "main",
			"tag": "custom-button",
			"url": "https://kandpalpranav.github.io/CustomWidget/Webcomponent.js",
			"integrity": ""	,
			"ignoreIntegrity": true
			
		}		
	],

Once the file URL is provided , the JSON file is capable of reading it and handing it over to SAC so it can retrieve this file.

This JSON file is needed for upload to SAC as a detail about the custom widget. Please download it and change the URL (in case of a custom hosting of WebComponent).

In case you want to host your own version please change the url to <username.github.io/exact path of your Web Component JS File>

 

Step 3 : Add Custom Widget in SAC 

This is the easiest part. Open Analytic application , navigate over to custom widget and add the custom widget with the ‘+’ symbol

 

 

Once you press this SAC would expect a JSON file. Please upload the JSON file you downloaded and changed in Step 2.

Once uploaded successfully , the widget should show up as below :

Since we have disabled the integrity check , you would see a warning but this can be ignored for purely development purpose.

Step 4 : Create a SAC App with the Custom Widget

And now since we have our widget available lets use it in our SAC app. We will now create a new SAC Analytical application and add the custom widget . In your SAC Analytical App you should see the new custom widget as shown below :

Once this widget is added to your SAC app , it will start reflecting as a button like below in the canvas :

An viola , your Custom widget is ready. We create a new custom widget which is a button , added an event listener to it , deployed it in GitHub and added it as an extension to the SAC App. Of course we need to test it. Lets dig a bit deeper into it in the next step

Step 5 : Testing the custom widget

Once you execute the SAC application with the custom widget in it and open the developers tool , you would notice in the pages that the WebComponent.JS file loaded when the SAC app is called , as shown in the below screenshot

 

Here you would notice that my Github Page is called and along with it the Webcomponent.js file is retrieved as well. Well the call basically happens because of the URL which we provided in the JSON file which we uploaded.

Please note incase you are facing issues in loading your widget , most likely the deployment is incorrect or the URL you are using is wrong.

Now incase you are able to see this JS file (in whichsoever location it is hosted) . Please open this file to put a breakpoint inside the FireChanged method. When you now click this button it will stop in the Firechanged method which is where the logic could be written.

In our example we just wanted to demonstrate a simple how to check the flow of steps and i think a console.log statement shall suffice.

 

With these set of steps i would say that we completed the execution of first ‘hello world’ of custom widget. Ofcourse we use an element which is present within SAC analytical apps by default so in a more realistic scenario we need to change this to a more relevant DOM.

I hope this blog would help someone , somewhere 🙂 !

Cheers,

Pranav

Assigned Tags

      7 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Jean-Michel Mathieu
      Jean-Michel Mathieu

      Hello Pranav.

      Thanks a lot for this interesting blog, gives me more clarity on the custom widget implementation.

      However I have tried to reuse it and enhance it with a simple function : when the user clicks on the button, the page scrolls all the way to the top. I have placed the button low in the designer so there is scrolling, but nothing happens. in the firechanged() method, I have tried to add a scrollTo or scrollTop function but it does not work.

      Any ideas or tips on how I can solve this ?

      On top of it, it would be intersesting, maybe in a next blog, to explain from this simple example, how to add different JS function that cannot be achieved in the app designer.

      Thanks a lot for your help.

      JM

      Author's profile photo Pranav Kandpal
      Pranav Kandpal
      Blog Post Author

      Hi Jean-Michel,

       

      Thanks alot for your comment and suggestion :). I will try to come up with another blog later with your ideas.

      Regarding your issue - did you manage to solve it? If not , did you try to put a break point in the firechanged method to check if the code atleast reaches to this method? Also how exactly are you using the scrollTo method? Did you use window.scrollto(x,y) ? I would expect it to work this way to be honest.

      Do let me know if you are still facing this issue and i can check later accordingly :). Have a nice start to 2023 in the meanwhile.

      Best Regards,

      Pranav

      Author's profile photo Jean-Michel Mathieu
      Jean-Michel Mathieu

      Hi Pranav,

      Thanks a lot for your answer and Happy New Year to you as well. I have tried using window.scrollto(0,0) but it does not seem to work. I am quite sure it reaches the method because I get a message in the Console log and if I add an window.alert () function in the firechanged method, I have a popup as well so it seems to work. I would be more than happy if you have any suggestion.

      Thanks again for your support.

      Kind Regards,

      Javascript%20for%20Scroll%20top%20function

      Javascript for Scroll top function

      Jean-Michel

      Author's profile photo Pranav Kandpal
      Pranav Kandpal
      Blog Post Author

      Hi Jean-Michel,

       

      I tried something similar to your described issues and i also faced a similar issue at my end. I also tried with various other combinations like window.scrollBy , document.body.scrollTop and many other functions but none of them work.

      I suspect that the scroll is not the scroll of the body but a scroll of another element inside that body. Maybe we need to check this further to find the right element. I will try to investigate further and in case i find soemthing i will let you know. Maybe you could also check in this direction.

      Best Regards,

      Pranav

       

      Author's profile photo Jean-Michel Mathieu
      Jean-Michel Mathieu

      Hello Pranav,

      I found a code that works, your suggestion led me to find the proper scrollable element. After few tries on our SAC tenant, I made it work with the attached code.

      Basically we have to query the document to find the scrollable elements and pass this element to the scrollTo function. I have put in the console.log the different scrollable elements returned by the function.

      (function () {
          let tmpl = document.createElement('template');
          tmpl.innerHTML = 
          `<button type="button" id="myBtn">Scroll up</button>` ;   
         
          class Scrollup_btn extends HTMLElement {
              constructor() {
                  super();
                  this.init();           
              }
      
              init() {            
                  let shadowRoot = this.attachShadow({mode: "open"});
                  shadowRoot.appendChild(tmpl.content.cloneNode(true));
                  this.addEventListener("click", event => {
                    var event = new Event("onClick");
                    this.fireChanged();           
                    this.dispatchEvent(event);
                  });
                 
                }
      
              fireChanged() {
                  console.log(scrollableElements);
                  console.table(scrollableElements);
                  console.log(elementScrolling);            
                  elementScrolling.scrollTo(0,0);
                 		        }        
              
          }
      
          customElements.define('com-button-scrollup', Scrollup_btn);
      
          const isScrollable = element => element.scrollHeight > element.clientHeight;
          const scrollableElements = Array.from(document.querySelectorAll('*[style*="overflow"]')).filter(isScrollable);
          const elementScrolling = scrollableElements[1];
      })();
      

      Scroll up Javascript updated

      I believe it can be optimized to make it more generic. Maybe you can test on your side and let me know if there is a better way to do it.

      Thanks a lot for your help !

      Kind Regards,

      Jean-Michel

      Author's profile photo Jean-Michel Mathieu
      Jean-Michel Mathieu

      Quick update : after testing on several application it appears that based on where the button is placed it might not work. If placed in a panel, the scroll up will not work. The reason is because in the elementScrolling, it is not always the index 1 that is the right element. To be sure, you have to use the __panel3 element. I have tested across several apps and tenants, and it works consistently. Now maybe we could make this more dynamic, but it works.

      (function () {
        let tmpl = document.createElement('template');
        tmpl.innerHTML = 
        `<button type="button" id="myBtn">Scroll up</button>` ;   
       
        class Scrollup_btn extends HTMLElement {
            constructor() {
                super();
                this.init();           
            }
      
            init() {            
                let shadowRoot = this.attachShadow({mode: "open"});
                shadowRoot.appendChild(tmpl.content.cloneNode(true));
                this.addEventListener("click", event => {
                  var event = new Event("onClick");
                  this.fireChanged();           
                  this.dispatchEvent(event);
                });
               
              }
              
            fireChanged() {              
                __panel3.scrollTo({
                        top: 0,
                        left: 0,
                        behavior: 'smooth'
                                  });
                           }        
        }
      customElements.define('com-button-scrollup', Scrollup_btn);
      })();
      
      Author's profile photo Pranav Kandpal
      Pranav Kandpal
      Blog Post Author

      Hi Jean-Michel ,

       

      Thanks a lot for the check and responses :). I think it makes much more sense and helps in getting more clarity.

      Kind Regards,

      Pranav