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 creating extension components for Design Studio.

Update: everywhere in the screenshots where you see "css!..css/component.css", mentally substitute "css!../css/component.css".  The github project has already been fixed.

When I first started writing this tutorial, the current generally available (GA) version of Design Studio was 1.5.   When we released 1.6, we added a few major features to the extensions SDK.  I've already touched on one of the major new features in Part 6a and Part 6b, data binding.  There is another that I've wanted to touch on for months now, but have been waiting until the tutorial gauge had reached feature completion; RequireJS.

What is RequireJS and what is it used for?


JavaScript is not like most programming languages.  In most other languages, if you want to use a library, you can use some sort of import statement; usually using one of the keywords "import", "include" or "using", which are all synonymous with regard to what they actually do.  The syntax varies from language to language, but the usual pattern is that you'll declare which external libraries that you want to access and then you've got a handle to them within your current code.  JavaScript ticks differently and has no notion of import.  In its traditional domain of being embedded within <script> tags in html, this is not really a problem.  Anything that you want to be accessible outside the current script of .js file, you simply make a global variable.  This can be annoying in the browser, but usually worked around.  It becomes a major problem in a server side environment, such as Node.js or Rhino.  (Trivia: Design Studio scripts are actually executed as JavaScript in Rhino)

  • But what if the global variable trick is not an option?  This works in html, but files imported via jsInclude don't share a variable scope, so this won't work.

  • What if load order matters?  If A is dependent on B, it does not do to have A load first.  B must be loaded first! 


Suppose I wanted to use Jason Davies' word cloud extensions to D3.  It is an actual extension to D3 and adds a wordcloud() function.  If simply add d3.layout.cloud.js  to your extension's res/js folder and add a <jsInclude> element contribution.xml, the component won't load.  Those of you who attended the advanced Design Studio session at TechEd in 2015 may recall the geo heatmap component.  That uses leaflet and would not have worked either, if I'd simply have used multiple jsIncludes.  In order to get leaflet to work in that component, I integrated the entire leaflet library into component.js;  giving me a single file.

In a nutshell, RequireJS is a framework that lets JavaScript import libraries.  It allows you to make use of libraries without any single file integration voodoo!

RequireJS and Design Studio


There has been limited support for RequireJS since Design Studio 1.4, but with 1.6, we made it the core way of handling includes.  In fact, the old way of creating includes is deprecated.  It still works if you want to use it, but we recommend using the new RequireJS way.  Just to be clear, the following elements are all depricated:

  • cssInclude

  • stdInclude

  • jsInclude


They have all been replaced by a single requireJS element.  In fact, you no longer need to explicitly declare the standard JavaScript and CSS libraries used in your component.  You'll refer to them directly from your component.js file.

So let's get started in updating our gauge so that it uses RequireJS!

Contribution.xml


The old xxInclude statements need to go.  So either comment out, or delete the following three lines:
<stdInclude kind="d3"/>

<jsInclude>res/js/component.js</jsInclude>

<cssInclude>res/css/component.css</cssInclude>

We'll replace it with a single requireJS element:
<requireJs modes="commons m">res/js/component</requireJs>

Note that we no longer add the .js extension when declaring the file path!  Also note the modes attribute!  This let's you decide which UI5 mode (the radio button choice between commons and m, made by the designer at project creation) is supported by which component JavaScript files.  If you are not using UI5 libraries in your component, you don't have to choose and can run in both.  If you are using UI5 the implementations will be different and you can use the classic (commons) UI5 library for designers running in commons and the Fiori (m) library for designers choosing that mode.   Even if you are not using UI5, you may still want to differentiate between the modes if you are using different CSS styling.  Our gauge is not using UI5 and is not differentiating its's CSS styling based on the mode, so we set it for both.

This new modes attribute is also in the component element.  The modes setting on the requireJS element is relevant to the individual requireJS entry.  The modes setting on the component sets it for the entire component.
<component

databound="false"

group=""

handlerType="div"

icon="res/gauge.png"

id="SCNGauge"

propertySheetPath="res/additional_properties_sheet/additional_properties_sheet.html"

title="Gauge"

tooltip=""

visible="true"

modes="commons m">






















JavaScript file is used in... modes attribute says
M only modes="m"
Commons only modes="commons"
Both Commons and M modes="commons m"

If no modes attribute is in the element (component or requireJS), then it defaults to modes='commons'.  Important! Important!  Important!  In Lumira 2.0 Designer (the artist formerly known as Design Studio), commons is no longer supported.  That means that the modes="m" or modes="commons m" attributes must be in the component declaration!

 

Explaining the Component.js Changes


The second time you migrate a component.js file over to using requireJS, it will take you about 30 seconds.  For this first time, we'll do a slow and comprehensive walkthrough.  First, we'll show the structure of the old component.js file, and the new requireJS friendly component.js file.  Then we'll walk through its features how we go from onw to the other.

The old one:



The new one:



Our new RequireJS based component.js file starts with a define() function containing two parameters, a list of includes and then an anonymous function, which contains our component subclass.  This results, broadly speaking, in three major "zones" .  They are shown in the diagram, below:

  • The list of includes.  It always includes the component itself and then any standard, external and css libraries.



  • The parameters of the anonymous function, which act as aliases for the included files.  Every include in the above list must have a corresponding parameter in the anonymous function.



  • The body of the anonymous function, which is the component subclass body itself.



The define() function include list



  • The first include in the include list is always the same: a boilerplate reference to the SDK component.



  • Next comes and standard includes and external libraries, in the order that we want them loaded.  Since d3 is still a standard library in the Design Studio SDK, we don't need to refer to any hosted versions, but can simply say "d3"; using the same name as we did in the old stdInclude element..  We are not including any other libraries, but if we were, they'd come after d3.



  • Lastly, we include our CSS stylesheet.   For our gauge, we previously had one stdInclude and one cssIncludeThis means  including the component itself, any standard includes and finally the CSS includes.  Note the syntax!  It starts with "css!".  This is a parser aid that signals the use of a css stylesheet.  This is followed by a relative path, from the component.js file to the CSS stylesheet file.


css!<relativepath>

In our case, the relative path is up one directory level, over to the css sibling and down to the component.css file.



The anonymous function parameters



  • The first parameter is Component.  This is boilerplate.  As our first include is a reference to the component, we need it to be the first parameter here.  It needs to be "Component", with a capital C, as that is what we are extending.



  • As the standard include d3 was the second include in the list, it needs to be the second parameter.  We'll call it d3 here, in line with what it was called in the old, stdInclude system.  We are actually free to call it anything we want, but rather than refactoring 1000 lines of code to change references from "d3" to "potato, we"ll leave it.  Tthe important thing is that we know we could if we wanted to!



  • The third item in the list was the CSSinclude.  We don't actually reference that in code, so we'll just add a placeholder parameter.  We'll call it unusedDummy, because it is a dummy parameter that we never use.



Component.subclass() changes



  • Taking the pre-existing contents of the component.js, we remove "sap.designstudio.sdk" from the start of the Component subclass declaration.  Then we copy+paste the entire contents into the new define() anonymous function.




That's it!  As usual,  current state of the project is available in a public github repository.  Next time, we'll create an installable archive for your extension.

4 Comments