Technology Blogs by SAP
Learn how to extend and personalize SAP applications. Follow the SAP technology blog for insights into SAP BTP, ABAP, SAP Analytics Cloud, SAP HANA, and more.
cancel
Showing results for 
Search instead for 
Did you mean: 
david_stocker
Advisor
Advisor
This is part of a tutorial series on developing your first custom widget.  The landing post for the series can be found here.

 

This is part 3 of the Your first SAP Analytics Cloud Custom Widget tutorial series. As usual, the final results are on github, for your perusal. If you have not been following since the beginning, I suggest going back to the introduction post and start from the beginning. Last time, we prepared our widget for connecting to SAP Analytics Cloud’s widget infrastructure. This time, we will set up a property for the widget text and make it editable in the styling pane.

At a high level, we are going to do two things, each of which will have its own subtasks:

  • We will add a widgetText property.

  • We will start editing the builder panel template to add a field to the styling panel, where the user can set their data.


Define the Property in the Metadata


As usual, we’ll want to make minor edits to the metadata to ensure that this version of the widget is unique, as far as SAP Analytics Cloud is concerned. Please increment the numeric suffixes of name, id, and newInstancePrefix. Inside the web components element, increment the the tag element, so that it reads com-sap-sample-helloworld3.

Now add a property element, for the widget text. Call it widgetText. Each child element of the properties element defines a property. Its name will be the name of the property, widgetText in this case. This element has three properties in turn:

  • type – this can be “string”, “number”, “boolean”, “integer” or a list version of any of these (“string[]”, “number[]”, “boolean[]” or “integer[]”)

  • description – This optional property allows you to give a description for the property.

  • default – this optional property allows you to define the default value of the property.


Our type will be a “string” and we’ll set the default value to “Hello World”.
"properties": {
"widgetText": {
"type": "string",
"description": "The text to be displayed in the widget",
"default" : "Hello World"
}
}

 

Getters and Setters in the main web component


Properties live in the SDK framework. What they actually represent in the web component or what happens when you set one is up to you. When a property is changed or read, the getters and setters for the property are called in the main web component. We’ll be tying the widgetText property to the _tagText attribute in the web component. Outside of the main web component, we will be referring to widgetText and inside it we will refer to _tagText. This is a deliberate choice on my part for this tutorial. In my own widgets, I’d synchronise the naming conventions to make maintenance easier, but here I want to demonstrate that the names don’t actually need to be the same.

The getter – When the getter is called, we are simply going to read the value of _tagText and return it.

The setter – This method is fired with the incoming value of the property that it is tied to; when this is fired, widgetText in this case. We will change the value of _tagText to whatever was passed.
//Getters and Setters
get widgetText() {
return this._tagType;
}

set widgetText(value) {
this._tagText = value;
}

 

After all the setters have run we’ll need to make sure that the widget is redrawn, so that the changes take effect. We don’t want to do this in the setter itself. This is because if we have several properties and do a property update, our widget will re-render several times. We only have one property in our widget, we would get away with it. Nevertheless, it is better to call redraw() in onCustomWidgetAfterUpdate(), so that it only runs one time per property update.
//When the custom widget is updated, the Custom Widget SDK framework executes this function after the update
onCustomWidgetAfterUpdate(oChangedProperties) {
this.redraw();
}

 
Note! If you followed the above, your widget will render twice when it first loads. You won’t notice it with the naked eye, but you will see it in the debugger. If you use the finished Step 3 from github, it won’t flicker. The topic of callbacks, redraw strategies and best practices is its own topic; which I’ll write a separate blog post on. I don’t want to go off on that tangent now, so I’ll just mention that this is the reason for the github version being slightly different.

 

The Styling Panel


Now we will add something to the styling panel, to allow us to enter the text. We’ll start by giving the custom tag a reasonable name ( com-sap-sample-helloworld3-aps in this case ) and adding a hardcoded HTML form to the shadow DOM.
customElements.define("com-sap-sample-helloworld3-aps", HelloWorldAps);

template.innerHTML = `
<form id="form">
<fieldset>
<legend>Custom Widget Text</legend>
<table>
<tr>
<td>Text</td>
<td><input id="aps_text" type="string"></td>
</tr>
</table>
</fieldset>
</form>
`;

 

 

In the constructor() method, we attach the shadow DOM. In addition, we’ll add a listener to the standard browser form submit event. By default, a form will send a “submit” event to the server; which is the SAP Analytics Cloud Widget SDK Framework in this case. The SDK framework does not know what to do with a form submit event. It does know how to react to another event, called “propertiesChanged”. So we want to block the standard “submit” event and send “propertiesChanged” instead.

To do this, we’ll:

  • Add an event listener to our constructor() for submit.

  • Bind this event to a method called _submit().

  • In submit, we’ll block the standard “submit” event from being sent, by executing preventDefault() on the event.

  • Dispatch a custom event, called “propertiesChanged”. In this event, we’ll pass a small

  • JavaScript object called detail. It contains a child object called properties, which in tern contains a list of key-value pairs; property names and new values.


constructor() {
super();
this._shadowRoot = this.attachShadow({mode: "open"});
this._shadowRoot.appendChild(template.content.cloneNode(true));
this._shadowRoot.getElementById("form").addEventListener("submit", this._submit.bind(this));
}

_submit(e) {
e.preventDefault();
this.dispatchEvent(new CustomEvent("propertiesChanged", {
detail: {
properties: {
widgetText: this.widgetText
}
}
}));
}

 

We also need getters and setters, to keep the value in the form synchronised with what the current widget property actually is.
set widgetText(newText) {
this._shadowRoot.getElementById("aps_text").value = newText;
}

get widgetText() {
return this._shadowRoot.getElementById("aps_text").value;
}

 

You are done editing the styling panel web component. Save it, host it alongside the main web component and note the URL. Go back to the metadata .json and add a second web component entry. This time it will be of kind “styling” (to end up in the styling panel, “tag” of whatever you called it in the web component and “url” is the one you just noted.
"webcomponents": [
{
"kind": "main",
"tag": "com-sap-sample-helloworld3",
"url": "https://davidhstocker.github.io/SAC_CustomWidget_HelloWorld_3/webcomponent.js",
"integrity": "",
"ignoreIntegrity" : true
},
{
"kind": "styling",
"tag": "com-sap-sample-helloworld3-aps",
"url": "https://davidhstocker.github.io/SAC_CustomWidget_HelloWorld_3/aps_webcomponent.js",
"integrity": "",
"ignoreIntegrity" : true
}
]

 

Load the new .json and add the new widget to your app.





 

Next time, we’ll add a script API to set the text value and header tag.
3 Comments