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: 
reiner_hille-doering
Active Contributor

As with SDK version 1.4 and 1.5 I  want to give you an overview over the new features of the Design Studio SDK in version 1.6.

This time I have more to share than I originally expected.

Using sap.m SAPUI5 Controls

You might know that Design Studio was one of the first SAP products that were based on the  SAPUI5 framework. What you might not know: There are two flavors of SAPUI5 available that are unfortunately incompatible. The team first started with a library of typical controls, e.g. buttons, input fields, containers, etc. in a package called "sap.ui.commons". This library was used in Design Studio from the beginning on. However, they were designed with the desktop browser in mind. Later the SAPUI5 team decided to create a second library especially for mobile devices called "sap.m". It was incompatible to sap.ui.commons, e.g. there was different handling of click and touch events. When SAP Fiori was started, the teams from application development decided to use "sap.m", for both, mobile and desktop. Therefore the meaning of "sap.m" changed from "mobile" to "main".

I know that many of the fellow SDK developers tried to use sap.m controls as SDK components in Design Studio, mainly as there are so many nice controls available in the sap.m library. Unfortunately this doesn't work well, e.g. events are lost and controls don't visually fit to the rest.

Therefore we decided to have a new mode in Design Studio where all components are implemented with sap.m controls.

For the Design Studio user there is not much change in this mode, except the different look and feel and some missing components in the "sap.m" mode.

For SDK components the change could be significant: As we don't know if your component will work correctly in "m-mode", by default, existing SDK components won't show up if an app is in m-mode.

If your SDK component uses handler_type="div", it is likely that it will work. Test your component and mark it in your contribution.xml as "sap.m" compliant:


<?xml version="1.0" encoding="UTF-8"?>
<sdkExtension
  xmlns="http://www.sap.com/bi/zen/sdk"
  id="com.sap.sample.coloredbox"
  title="Design Studio SDK Extension Sample Colored Box"
  version="16.0"
  vendor="SAP">
  <component
  id="ColoredBox"
  title="Colored Box"
  icon="res/icon.png"
  handlerType="div"
  modes="commons m"
  propertySheetPath="res/additional_properties_sheet/additional_properties_sheet.html">
...

For SDK components with handler_type="sapui5" it depends on the controls you use: If you use only "sap.ui.commons" controls, leave the "modes" attributes out or set modes="common". For components that use only "sap.m" controls, set modes="m". Never mix "sap.ui.commons" and "sap.m" controls in the same component. You can however create a dynamic component that uses different implementations depending on the mode: You could have one require.js module for "m" and one for "commons".

Full Require.js Support

In version 1.4 I announced limited support to load JavaScript files with require.js . This was sufficient for some use cases, but often our old loading mechanism had disadvantages. E.g. some some browsers (e.g. Firefox) have problems loading JavaScript files synchronously.

Now we completely switched to require.js to load our own JS files - and SDK components can also do so.

The old loader is still in place to stay compatible. However, I strongly recommend to use the require.js feature also in your SDK components, as you will gain several benefits.

As example we look at the contribution.xml of ColoredBox as it was in 1.5:


<?xml version="1.0" encoding="UTF-8"?>
<sdkExtension xmlns="http://www.sap.com/bi/zen/sdk"
  title="Design Studio SDK Extension Sample Colored Box"
  version="15.0"
  vendor="SAP"
  id="com.sap.sample.coloredbox">
  <component id="ColoredBox"
  title="Colored Box"
  icon="res/icon.png"
  handlerType="div"
  propertySheetPath="res/additional_properties_sheet/additional_properties_sheet.html">
  <jsInclude>res/js/component.js</jsInclude>
  <cssInclude>res/css/component.css</cssInclude>
...
  </component>
</sdkExtension>

And component.js looked like:


sap.designstudio.sdk.Component.subclass("com.sap.sample.coloredbox.ColoredBox", function() {
// Content here
});

To load you extension with require.js, use the new <requireJs> tag:


<?xml version="1.0" encoding="UTF-8"?>
<sdkExtension
  xmlns="http://www.sap.com/bi/zen/sdk"
  id="com.sap.sample.coloredbox"
  title="Design Studio SDK Extension Sample Colored Box"
  version="16.0"
  vendor="SAP">
  <component
  id="ColoredBox"
  title="Colored Box"
  icon="res/icon.png"
  handlerType="div"
    modes="commons m"
  propertySheetPath="res/additional_properties_sheet/additional_properties_sheet.html">
  <requireJs modes="commons m">res/js/component</requireJs>
...
  </component>
</sdkExtension>

The tag name <jsInclude> is replaced by <requireJs> and the extension ".js" is omitted - simple. You also need to specify for which modes (sap.ui.commons, sap.m, or both) the module is loaded. Using this feature you can have different modules for different modes.

The <cssInclude> and potential <stdInclude> tags can be removed. Why?

Let's look at the changed component.js:


define(["sap/designstudio/sdk/component", "css!../css/component.css"], function(Component, css) {
  Component.subclass("com.sap.sample.coloredbox.ColoredBox", function() {
// Content here
  });
});

Using "define" we make our handler a require.js module. The fist parameter is an array of other require.js modules or CSS files that your module needs. As an SDK module it requires the base "class" for all SDK handlers. Before this, the base class was requested using the global variable "sap.designstudio.sdk.Component". Now the same is "injected" as  a local variable "Component" in response to the required module "sap/designstudio/sdk/component". The second required module is a CSS file - or to be more precise: "css!" marks a requirs.js plugin for loading CSS files which is always available. The path "../css/component.css" is the relative path to the CSS file. The result of the css file is injected into local variable "css" - which is not really needed for a CSS file and is here only to show the symmetry between the required modules array and the callback function's parameters.

The require.js support also makes the <stdInclude> feature obsolete. The following snippet from Sparkline's handler JavaScript file:


define(["d3", "sap/designstudio/sdk/component"], function(d3, Component) {
  Component.subclass("com.sap.sample.sparkline.Sparkline", function() {
  var that = this;
  var sparkline_data = undefined;
  var cssstyle = undefined;
  var path = undefined;
  this.init = function() {
  var container = this.$()[0];
  var graph = d3.select(container).append("svg:svg").attr("width", "100%").attr("height", "100%").attr("viewBox", "0 0 1000 1000").attr("preserveAspectRatio", "none");
  //appending an svg:path element
  path = graph.append("svg:path");
  };

...

The D3 library is required as "built-in" library and injected into local variable "d3".

Currently we support the following built-in libraries:

Module NameDescriptionAlternative
"jquery"jQueryAlso always available via global variables "jQuery" and "$"

"d3"

D3 (d3.v3.js)Also available via global variable "d3", if requested by other components or <stdInclude kind="d3">.
"_"Underscore (underscore.js)Currently always available via global variable "_"
"sap/designstudio/sdk/component"Base class for DIV-SDK-ComponentsBackward compatible global variable "sap.designstudio.sdk.Component".
"sap/designstudio/sdk/datasource"Base class for pure SDK data sourcesBackward compatible global variable "sap.designstudio.sdk.DataSource". 
"sap/designstudio/sdk/databuffer"Base class for SDK datasources using Data BufferBackward compatible global variable "sap.designstudio.sdk.DataBuffer". 

Note: Don't use the backward compatible global in required modules, this could give strange results!

Instead requiring a built-in module, you can also require your own modules or third-party libraries that come with your extension. You could even use your own copy of d3 - if you are sure that it comes as AMD-enabled module that doesn't create global variables:


define(["./tp/myd3", "sap/designstudio/sdk/component"], function(d3, Component) {
  Component.subclass("com.sap.sample.sparkline.Sparkline", function() {
// ...

Commercial SDK components still must not bring their own copy of built-in libraries, as this will bloat the loaded JavaScript files, but at least it works now.

Technical Components

Since a few releases Design Studio supports so-called technical components that have no visual representation and accordingly don't appear in the Components palette. Now such component can be implemented with the SDK. A good example is the "Timer" sample component that triggers an event after a configurable time. The sample has been productized as official component in Design Studio 1.6, but it still ships as a sample.

The most important change is setting group="TECHNICAL_COMPONENTS" in contribution.xml. This will remove the component from palette and let it appear in the "Technical Components" context menu. The second change is in in the JavaScript handler


this.init = function() {
  this.$().css("display", "none");
};

You see that a technical component is still a normal SDK component with handler_type="div", but the root DIV is not rendered.

Complex Properties and Arrays

So far the type system for properties of SDK components was quite limited: Besides the data-bound properties there were only primitive types available. But most components need some structured or array-like properties.

SDK components often simply used a String-typed property and encoded some data via JSON.stringify. But this approach has several disadvantages:

  • In biapp-files and bookmarks, such properties appear as an unstructured blob, while regular structured properties have an XML format that is better for diagnostics.
  • Users can't directly enter such JSONs, thus the component need an additional properties sheet to enter data in such a property.
  • Working with ZTL script functions on such a property is quite cumbersome, e.g. you have to JSON.parse it, modify, serialize it back etc.

Now he added native support for structures properties and arrays. Here is the concept:

If a property has the new type "Object", it must contain nested properties. This will form a structure.


      <property
        id="item"
        type="Object"
        title="Item">
        <property
          id="text"
          type="Text"
          title="Text"/>
        <property
          id="key"
          type="String"
          title="Key">
         </property>   
      </property>

You can nest all property types into an Object property, except data-bound properties and ScriptTexts. But you can could nest properties of type Object into others, producing a deep structure.


The standard property view has built-in editing support for nested properties.


The other new property type is "Array". It must contain exactly one property specifying the type of the array. The following example creates a so-called simple array for integers:


<property
      id="intArray"
      type="Array"
      title="Int Array">
      <property
        id="item"
        type="int"
        title="Item"> 
      </property>
    </property>

You can also use a property of type "Object" as array type - forming a table-like property.


<property
      id="items"
      type="Array"
      title="Items">
      <property
        id="item"
        type="Object"
        title="Item">
        <property
          id="text"
          type="Text"
          title="Text"/>
        <property
          id="key"
          type="String"
          title="Key">
          <option name="keyfield" value="true"/>
         </property>
        <property
          id="image"
          type="Url"
          title="Image">
  <option name="kind" value="Image" />
        </property>
      </property>
    </property>

The standard property view has built-in support for simple and table-like arrays. Deep nesting however is not supported in arrays.

The example screenshot and XML snippet above are taken from the new UI5List sample component. They also demonstrate the new <option> features"keyfield" and "kind" that can be applied to properties. Option "keyfield" tells the editor that the property value in this columns should be unique. The option "kind" for properties of type "Url" gives the editor a hint to provide automatic browse and upload feature for pictures and some other file types.

Both, structured properties and arrays are passed to the JavaScript handler as a JSON or JSON array. Thus your setter will receive a valid JavaScript object - and the setter must return a JavaScript object that can be serialized with JSON.parse.

The same is also the case for accessing the property in ZTL functions. You can access the property via this.<propertyName>. Because of some internal logic you must however re-assign it back in case your changed it. Here  a snipped from UI5List's contribution.ztl:


void addItem(String key, String text) {*
    var current = this.items || [];
    current.push({
      key: key,
      text: text,
      image: ""
    });
    this.items = current; // Write back changed object
  *}

You can access and modify structured properties and arrays also via your Additional Properties Sheet (APS). Again you access them via JSON:


this.items = function(value) {
  if (value === undefined) {
  return this._items;
  } else {
  this._items = value;
  return this;
  }
  };
// ...
$("#form").submit(function() {
  var array = that.items() || [];
  array.push({
  key: $("#key").val(),
  text: $("#text").val()
  });
  that.items(array).firePropertiesChanged(["items"]);
  return false;
  });

Call ZTL Functions from Component and APS

One powerful feature that came into the SDK in 1.4 is the possibility to enrich the SDK component's possibilities calling BIAL functions whenever needed: The JavaScript handler running in the browser could call a function written in server-side JavaScript defined in contribution.ztl. However, this was more a concept than a feature and hard to use. E.g. you had to define private properties and events to transport data from browser to sever and vice versa.

Now the SDK provides a handy function that does all this under the hood.

E.g. assume your SDK component at some point of time needs a list of dimensions. Then define a private function in your contribution.ztl:


@Visibility(private)
  // The method is called by the callZTLFunction feature. The return type is not correct,
  // but as it can't be used in scripts, this doesn't care.
  String getDimensions(String axis) {*
     if (!this.getDataSource())
        return null;
     if (axis) {
        return this.getDataSource().getDimensions(axis);
     }
     return this.getDataSource().getDimensions();
  *}

The call "this.getDataSource().getDimensions()" consists of a navigation to the data source of your component (this.getDataSource()) and the BIAL script function "getDimensions()" returning a list of dimensions.

Now you can easily call this private function from your component.js code:


that.callZTLFunction("getDimensions", function(result) {
  // do something with the dimension array
}, "ROWS");

The function callZTLFunction(sFunction, fResultCallback, parameters...) expects the name of the function to call (parameter sFunction), a callback function that receives the result (parameter fResultCallback) and an arbitrary number of parameter to pass in to your ZTL function.

Internally this feature uses the same principles that came with 1.4 - but under the hood uses some built-in properties and events. Therefore all parameters and results must be JSON-serializeable and the execution is asynchronous.

There was the wish from some famous Design Studio SDK coder to be able to use the same trick also from a

Additional Properties Sheet - and voilá, here you are: You can use the exact same API from an APS JavaScript. Internally is works differently, but the ZTL script is executed in the normal runtime - also in Design Mode. You can however only read data with such a script. Modifying operations, e.g. setFilter are not allowed to be called from the APS and will bring up an error message in the Designer's error log.

Examples for using callZTLFunction, both from component and APS are contained in the new UI5List sample.

APS Improvements

By the way, there are a few little improvements in APS API:

  1. Now there are also beforeUpdate and afterUpdate callbacks available that have the same behavior as the corresponding functions in component JS.
  2. The order for finding changed properties has changed a bit. This makes transactional behavior easier to achieve. The order is now:
    1. init or componentSelected
    2. beforeUpdate
    3. getter for property 1.
    4. getter for property 2.
    5. ...
    6. setter for propertyv 1 - in the case that new value and value returned from getter are different
    7. setter for property 2 - in the case that new value and value returned from getter are different
    8. ...
    9. afterUpdate
    10. ...
  3. There is a new function "log(message, severity)" available, where "message" is your text and "severity" is "info", "warn" or "error". Depending on the given severity and the configured log level, the message will appear in Design Studio's Error log view and in Debug mode also in the JavaScript console.

Update SDK Components without Restart

The logging feature is one of the features to make SDK component development easier. Another is a feature that virtually removes the need to restart your target Design Studio while developing your component.

You might know that you can easily debug and change your JavaScript code without restarting Design Studio: Regardless if there are problems in Design Mode, simply launch a test app with your component in your favorite external browser. In the debug tools of the browser disable the browser cache and debug. Whenever you need to change something, change your JavaScript file and hit F5 on your browser.

If the change was however in the contribution.xml or contribution.ztl file - e.g. because you added a property - you had to restart Design Studio. Now you simply switch to Debug mode by pressing Ctrl+Alt+Shift+D. Now you will see the new menu entry Tools->Refresh SDK Extensions. This function will clear several caches and make almost all changes immediately active. For some changes it might be necessary to close and reopen the current editor or the additional properties view.

The Debug mode also makes development of Design Mode behavior easier. The JavaScript console shows detailed information about what's happening in the main browser, and the scripting tab allows to evaluate JavaScript there. For APS debugging it also greatly helps to  increase the log level in Tools->Preferences->Application Design->Support Settings and to configure the Error Log viewer to have a very high limit of logging events.

For in-depth debugging of APS logic however is still best to attach a Microsoft Studio Web debugger to your target Design Studio's Internet Explorer instances. In most cases the free Express edition should be sufficient.

The SDK component update without restart is also available on the BI Platform. The runtime will detect changes in deployed SDK extensions and make the changes available without BIP restart. As within the Designer this is the case for most changes, but not for completely new SDK components - which still require a restart.

One More Thing

Besides the stuff mentioned so far I have worked on a new feature that makes it easy to bind Design Studio data to SAPUI5 controls. It might later become a part of the Design Studio SDK but was not fully finished for 1.6. I only mention here that something will be coming to save you from investing a lot of time and energy in other solutions to map SDK Result Set JSONs to SAPUI5 tables etc.

You see that you still have an interesting job creating Design Studio extensions and I'm looking forward to seeing more interesting ones from you - the Design Studio SDK community.

25 Comments