There are several questions coming up frequently, both in our SDK workshops and here in the forum. Thus I thought it might be a good idea to blog about some typical problems that you as Design Studio developer could be running into.

The first thing I want to write about is the different ways how an SDK component could integrate resources, such as images, CSS files, fonts etc. Probably most components need such resources. Using the resources is just normal HTML and JavaScript, but first we need to differentiate two kinds of resources, depending who defines which file to use:

  • Static resources: Those come with the SDK component and are not intended to be modify by the user of the component. Typical examples are decorations of UI element, e.g. the icon shown on a button.
  • Dynamic resources: Those are defined by the user of a component, e.g. if your component allows the user to define a background image using the Design Studio Properties window.

Static Resources

The easiest way to include static resources is a CSS file. The SDK has the <cssInclude> tag where you can reference one or more CSS files that come with your SDK extension. As soon as at least one instance of your SDK component is contained in the app, the CSS file is automatically loaded into the page. How this works is shown in the ColoredBox example – making the box having a black border with rounded corners.

You may have seen that the round boarder is not shown when you drop the first ColoredBox into an empty application. Reason is the feature: The Design Studio runtime analyzes an app before the HTML is produced. It will include all needed JS and CSS files into the header. If the first ColoredBox is added into an empty app, the Runtime will dynamically load the new JS files. But it doesn’t load the new CSS files. Fortunately this issue has been resolved in Design Studio 1.3 SP1, thus with an up-to-date Design Studio you will see round corners from start.

As you can include CSS files, it is also easy to include pictures through CSS: If you e.g. want to modify the ColoredBox into a PictureBox, add a picture into the CSS folder or a subfolder. The reference it with a relative path from the CSS:

/wp-content/uploads/2014/08/tree_520829.png


.coloredBox {

      border-radius:25px;

      border-width: 4px;

      border-color: black;

      border-style: solid;

      background-image: url(“img/Desert.jpg”);

       background-size: 100%;

}


To see it running, you also need to remove the “color” property getter/setter, as it would overwrite the background image with a background color.

/wp-content/uploads/2014/08/picturebox_520804.png

If you want to select one of multiple static images, best use several CSS classes and modify the assigned class in your JS code.

You might ask: “Why not simply use an IMG element?” There are some good reasons to prefer a styled DIV over an IMG:

  • You have a lot of possibilities to control the appearance, sizing etc. of the picture using CSS3 attributes, e.g. background-repeat and background-size.
  • The users of your component can use custom CSS files to replace your images with their own.
  • The URL to your image is relative to the CSS. As Design Studio correctly calculates the URL of the CSS file on all platforms, the exact path the image isn’t something your need to care about.

The last point is somehow the “killer argument” to go with CSS whenever possible. Unfortunately there are situations where you must specify the exact URL of your image or other resource, e.g. in SVG image elements.

Fortunately there is a little trick to calculate the path of anything that comes with your SDK component: As you component.js is loaded into the page with a <script> element, at the moment of loading it is the last <script> tag on the page. This fact can be used to calculate the “src” path:


(function() {

var scriptSrc = $(“script:last”).attr(“src”);


sap.designstudio.sdk.Component.subclass(“com.sap.sample.coloredbox.ColoredBox”, function() {

       this.init = function() {

              var imageUrl = scriptSrc.replace(“js/component.js”, “css/img/Desert.jpg”);

              var image = $(“<img/>”).width(“100%”).height(“100%”).attr(“src”, imageUrl);

              this.$().append(image);

       };

    });

})();


I have modified the handler to be encapsulated in a self-calling function to get a private variable scope. In the first line I use jQuery to get the “src” attribute of the last script element in a variable. This is later used to calculate the path to the image by replacing the relative path to component.js.

Dynamic Resources

Dynamic resources, e.g. images that the Design Studio user associates with their app, are even more dependent on the Design Studio platform.

In local mode, those resources reside inside of the folder of the app in the local repository. In BIP those resources are global BIP repository object, in NetWeaver there are stored as MIMEs in ABAP and on HANA they are again local repository objects.

Thus the exact URL is not known upfront. Therefore the SDK comes with the property type “Url” that automatically creates the correct URL depending on the platform. Unfortunately we can’t provide picker dialogs for those Url properties in the designer, as the platform handle different resource types quite differently.

Thus a good tip of user of an SDK component having Url properties on BIP or NetWeaver:

  1. Add an image component.
  2. Use the Image’s “Image” property picker to upload the image and fill the property correctly.
  3. Copy the property value to the SDK component’s URL property.

Now, what will you do if your SDK component wants to display may different images, e.g. depending on data?

Simply create a “template” Url property and fill it with a path pointing to the place where all the images are located. Then your code can replace the template name with the real name.

In our training class sample showing a Scatterplot chart with country maps we used this trick:

First we have the Url property “placeholderimage” with getter/setter:

this.placeholderimage = function(value) {

      if (value === undefined) {

             return _placeholderimage;

   } else {

           _placeholderimage = value;

            return this;

   }

};

Then we assign the SVG element the URL depending on Country of the data source cell – all using D3.


svgimgs.attr(“xlink:href”, function(d, index) {

     var dimValForPicture = getTextForDimension(index, _imagedim);

     var url = “”;

     if (dimValForPicture && _placeholderimage) {

       url = _placeholderimage.replace(“DEFAULT”, dimValForPicture);

     }

     return url;

});


In current version of Design Studio, the template image should exist, even it it is not used. Else there will be ugly entries in the log file. In next Design Studio release this should be fixed, so you could make the template URL property hidden and assign a reasonable default value to it.


So far my first SDK blog. If you have any areas of interest, please let me know. I’ll try to consider those proposals in my next blog.

To report this post you need to login first.

13 Comments

You must be Logged on to comment or reply to a post.

  1. Mike Howles

    Reiner,

    Glad to see you posting!  Great first topic!  I have one question related to how you derive the script src path:

    var scriptSrc = $(“script:last”).attr(“src”);


    How is it guaranteed that the last script tag will be the particular SDK component’s script tag and not either some other SDK component’s?  Is this because you are performing a closure and it is executed before potential subsequent SDK script tags are then included?


    I was paranoid about this scenario in a component of my own so I did what you have recommended with a slight change of just pulling all the script tags and examining the ‘src’ attribute to see if it was the JS file I wanted the path of or not.  Is this check necessary or will your example work no matter how many other component JS files may follow?

    (0) 
    1. Reiner Hille-Doering Post author

      Hi Michael,

      thanks 🙂 . Indeed it is guaranteed to be the correct SRC: The scripts are loaded without “async”  attributes. In this case the browser will first evaluate the script body before proceeding with next script. And as there is no multithreading. But the

      $(“script:last”).attr(“src”)

      needs to be called immediate, and not when it is needed. That’s the reason why I access the “scriptSrc” from closure scope much later.

      (0) 
      1. Mike Howles

        Perfect!  Glad to hear it’s synchronous loading and not asynchronous so we can rely on the execution flow being reliable.  Thanks, again!  Great stuff.

        (0) 
  2. Mustafa Bensan

    Hi Reiner,

    This is a very helpful post which addresses one of the issues I have had, so thank you.  I have some related questions and a suggestion for your next blog as follows:

    Questions:

    1)  The component.js (“NotificationBar.js”) code provided in this NotificationBar Component example seems to apply a variation of the URL determination approach you have described and requires a default image property.  The main code is as follows:

    var lastScriptCalled = $(“script:last“);

    var fullUrlOfLastScriptCalled = lastScriptCalled.attr(“src“);

    var cutIndexOnMainEntryPoint = fullUrlOfLastScriptCalled.indexOf(“res/nb/”);

    var org_kalisz_karol_scn_pack_NotificationBar_accessUrlForRes = fullUrlOfLastScriptCalled.substring(0, cutIndexOnMainEntryPoint) + “res/nb/”;

    .

    .

    .

    setDefaultImage :function(value) {

                  if(this._DefaultImage == value) {

                         return;

                  } else {

                         this._DefaultImage = value;

                         this._pImagePrefix = value.substring(0, value.lastIndexOf(“/”) + 1);            

                  }

           },

           getDefaultImage: function() {

                  return this._DefaultImage;

           },

    .

    .

    .

    initDesignStudio :function() {

                  this._oNotificationBar = this;

                  var that = this._oNotificationBar;

                

                  this._pAccessPath = org_kalisz_karol_scn_pack_NotificationBar_accessUrlForRes;

                

                  this._oCommonNotifier = new sap.ui.ux3.Notifier({

                         title : “Common Notifications”,

                         icon: this._pAccessPath + “scat_public.png”

                  });

    Can you clarify how the above example differs from the approach you have documented and when/why it is appropriate to use each?

    2)  What approach would you suggest for determining the URL of and accessing other static resource types packaged with the SDK component, such as a .JSON or .XML file?

    Blog Suggestion:

    I’d be particularly interested to see a blog that provides recommendations and code samples for implementing data binding for SDK components that are based on a UI5 control (i.e. handler type “sapui5”), showing techniques for both complex result set structures (i.e. with hierarchies) and simple result set structures (i.e. without hierarchies), with examples for both an entire result set and subset of a result set (like column or row).

    Thanks!

    Mustafa.

    (0) 
    1. Reiner Hille-Doering Post author

      Hi Mustafa,

      great that you liked the blog. About your questions:

      1) Karol uses exactly the same approach. In my variant I did the following points differently.

      • I avoided global variables by putting the src detection logic into a self-calling functions.
      • I mad it a bit more compact.
      • I used .replace, and Karol used indexOf.

      Karols uses a base URL to calculate path of many icons. In My code one would simply use multiple .replace target strings.

      2) You can do it exactly as outlined: Replace the relative path or your component.js with the relative path of your XML, JSON etc. Then you can e.g. use AJAX to download the file, E.g.

      $.ajax({

                 url: url,

              }).done(function(data) {

               // use your data

      });

      Your suggestions makes a lot a sense. What about describing a sample that connects a SAPUI5 Table with Design Studio data? This would be a good sample for combining Design Studio databinding with SAPUI5 JSON databinding.

      (0) 
      1. Mustafa Bensan

        Hi Reiner,

        Thanks for the clarification regarding my URL determination questions. 

        Yes, for your next blog I think a sample based on a SAPUI5 Table bound to Design Studio data would be very informative.  In fact, to demonstrate a more comprehensive example it would be ideal if you could base it on the TreeTable control.  In this case, to the extent that you have time, addressing the following scenarios would be very interesting:

        1)  Data-bound property of type ResultSet with a BW hierarchy dimension and multiple measures

        In this scenario a hierarchy is displayed in the first column (“tree” column) of the table and measures displayed in additional standard columns.

        2)  Data-bound property of type ResultSet with a flattened BW hierarchy dimension and multiple measures

        In this scenario the hierarchy is flattened across multiple standard columns, with each level of the hierarchy in a separate column.  Measures are displayed in additional standard columns.

        3) Multiple data-bound properties of type ResultCellList with a BW hierarchy dimension and multiple measures

        In this scenario multiple data-bound properties consisting of a single dimension column selection with hierarchy and multiple measure column selections, are all bound to the same UI5 TreeTable.  The purpose of this scenario would be to demonstrate how a larger dataset can be returned to the table compared to the first two scenarios above.  This is on the assumption that the SDK limit of 10,000 data cells is based on each data-bound property rather than the datasource itself.

        I look forward to your post.

        Regards,

        Mustafa.

        (0) 
    2. Karol Kalisz

      Hello Mustafa,

      I agree that my code is not as short as it could be, but I learned to write code which I understand also after many years for support reasons. This is why the variables have meaningfull names and are defined as static variables. I optimize only parts which needs to be quick for performance reasons – as every optimization is making the code a bit less understandable.

      Probably the way how Reiner makes this is the better one, as I have asked Reiner before how to make it – and have implemented what I have understood 😉

      Anyway, both versions are working well. My has probably a bit higher memory footprint in the browser.

      Regards, Karol

      (0) 
      1. Mustafa Bensan

        Hi Karol,

        Thanks for your feedback.  My question was more out of curiosity to understand the reasoning behind the two approaches.  There is always more than one way to code a solution 🙂 .  In fact, I liked your very understandable and well documented coding style.  I think it’s a great example of how to prepare a professionally authored component, with a the copyright statement in the header of the JavaScript file and the License terms in the contribution.xml file.

        Regards,

        Mustafa.

        (0) 
        1. Karol Kalisz

          Hi,

          There is no deep reason, this is what I could understand and worked for me…

          Indeed – I have made one mistake, the first 2 variables were not in “my own static scope”.

          It was:

          var lastScriptCalled = $(“script:last”);

          var fullUrlOfLastScriptCalled = lastScriptCalled.attr(“src”);

          var cutIndexOnMainEntryPoint = fullUrlOfLastScriptCalled.indexOf(“res/nb/”);

          I have changed for now to (just to assure no collisions):

          var org_kalisz_karol_scn_pack_NotificationBar_lastScriptCalled = $(“script:last”);

          var org_kalisz_karol_scn_pack_NotificationBar_fullUrlOfLastScriptCalled = org_kalisz_karol_scn_pack_NotificationBar_lastScriptCalled.attr(“src”);

          var org_kalisz_karol_scn_pack_NotificationBar_cutIndexOnMainEntryPoint = org_kalisz_karol_scn_pack_NotificationBar_fullUrlOfLastScriptCalled.indexOf(“res/nb/”);

          For the approach of Reiner, I need to try it – I must admit, I am not a JS developer, I prefer Java side.

          Some construct in JS are quite magic for me. And in general I try to not use code which I do not understand (for the maintenance reasons). It means, as soon I will get the understanding of this “self-calling function in function” and I will get it integrated into the handler I probably will change.

          But as the “static” approach is also not too bad for this purpose, it is not highest priority. The example is currently for the non-SAPUI5 component, I would need to adjust it.

          Regards, Karol

          (0) 
  3. David Kretzler

    Hi Reiner,

    to display images with my component I used your code:

    “scriptSrc.replace(…)”
    It worked totally fine.

    Since I have installed Design Studio Release 1.5 Version 15.0.6 this part leads to problems. If this code is executed the Design Studio does not display anything of my component.

    My code:

    At top:

    (function() {

    var scriptSrc = $(“script:last”).attr(“src”);

    sap.designstudio.sdk.Component .subclass(…

    In the body:

    … imagesrc=”‘ + scriptSrc.replace(“js/component.js”, flagarray[i]) + ‘” …

    Do you have an idea how I can solve my problem?

    Regards,

    David

    (0) 

Leave a Reply