Your First Extension: Part 13 – RequireJS
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?
- 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.
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:
So let’s get started in updating our gauge so that it uses RequireJS!
The old xxInclude statements need to go. So either comment out, or delete the following three lines:
We’ll replace it with a single requireJS element:
<requireJs modes=”commons m”>res/js/component</requireJs>
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.
|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.
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.
- 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.
I don't know what I was thinking when I added the title. jThe sInclude element gets replaced by requireJS and I title the thing includeJS?
Would the boilerplate need to be changed to a make an extension work in Lumira Designer?
Yes. the modes attribute of the component element was optional in 1.6 and now it is required in 2.0. Also, commons is no longer supported as a mode, so that attribute must either read modes="m" or modes="commons m" . No other changes are needed.