Skip to Content

Getting Started – Introduction

Sometimes, it’s useful to create your own UI control. It’s a great idea for code reuse, and one thing I’ve really enjoyed about UI5 is how easy it is to create your own control. That said, there does seem to be a bit of confusion on this topic and many don’t know where to start – so hence, I’m writing this blog to hopefully get you kickstarted into building your own suite of awesome controls.

Ok, let us jump right in

I’m going to go ahead and assume you have the following knowledge already

  • SAPUI5 or OpenUI5 (can write your own custom application for fiori launchpad)
  • Javascript and jQuery
  • HTML and CSS

First up, create a whole new project, and some folders for your controls namespace. I highly recommend you create a library of controls in a single common namespace, and reference it from your other applications so you can reuse all your controls, rather than creating custom controls within your application (unless it really is, very application specific)

I’ve created a path of “dalrae/ui/containers” and “dalrae/ui/controls”, where I plan to seperate all my controls into two categories, today I’ll be focusing on building a a container control (a control that has a content aggregation for rendering child controls). You can create whatever kind of categories feels appropriate to you.

And today I will build a container I’m going to call “ShadowBox” (which will be a div with a shadow effect), and explain the steps I’m taking as I go.
So, I know you just want to hurry up and see the code, so here you go, I build the following skeleton to begin my new control, feel free to copy & paste this for all your new ui controls

Getting Started – Boiler Plate


sap.ui.define(
  ['sap/ui/core/Control'],
  function(Control) {
  return Control.extend("dalrae.ui.containers.ShadowBox",{
       metadata: {
            properties: {},
            aggregations: {},
       },
       renderer: function(oRm,oControl){
            //to do: render the control
       },
       onAfterRendering: function(arguments) {
            //if I need to do any post render actions, it will happen here
            if(sap.ui.core.Control.prototype.onAfterRendering) {
                 sap.ui.core.Control.prototype.onAfterRendering.apply(this,arguments); //run the super class's method first
            }
       },
  });
  }
);



Ok, so first up, notice I am extending sap.ui.core.control, so that I get all of the awesome juicy ui5 functionality SAP has built for us
Secondly, notice I’ve declared my namespace to match my folder path and class name dalrae.ui.containers.ShadowBox to match my dalrae/ui/containers/ShadowBox.js path

In the above example I have already put in a renderer function (because I intend to do custom rendering)
And also an onAfterRendering (which honestly, I probably won’t use here, but I wanted to put it there for the sake of your template)

Ok, before getting into building this container, I want to demonstrate that it works, so I will add a simple render and slap it into an xml view

I add the following code to demonstrate basic html rendering

Getting Started – Test


sap.ui.define(
  ['sap/ui/core/Control'],
  function(Control) {
  return Control.extend("dalrae.ui.containers.ShadowBox",{
       metadata: {
            properties: {},
            aggregations: {},
       },
       renderer: function(oRm,oControl){
            oRm.write("<span>HELLO WORLD</span>"); //output some html so we can see the control is working!
       },
       onAfterRendering: function(arguments) {
            //if I need to do any post render actions, it will happen here
            if(sap.ui.core.Control.prototype.onAfterRendering) {
                 sap.ui.core.Control.prototype.onAfterRendering.apply(this,arguments); //run the super class's method first
            }
       },
  });
  }
);



And to use it in my xml view, I import the namespace, then I should see my “HELLO WORLD” text in my view


<mvc:View xmlns="sap.m"
xmlns:mvc="sap.ui.core.mvc"
xmlns:dalraeContainers="dalrae.ui.containers"
xmlns:core="sap.ui.core"
controllerName="dalrae.doco.Page">
       <Page title="D'Alrae UI5 Library" enableScrolling="true">
       <headerContent>
       </headerContent>
       <content>
            <dalraeContainers:ShadowBox />
       </content>
  </Page>
</mvc:View>



And viola! there it is

HelloWorld.PNG

Easy once you’ve seen a simple wireframe right?

Building the Control

You’ve probably already figured out that the oRm object is a renderer, and the oControl object is your control instance

The oRm is class type sap.ui.core.RenderManager
Bookmark that link for later and do your own research

Now I’m going to make this thing actually useful, and document my progress

This is going to be a container for other controls, so I need to add an aggregation, and I’ll use the standard name of “content” for ease of use

This is disgustingly easy to do, I just add my aggregation to the metadata and also optionally specify it as the default aggregation (by making it default, I don’t have to specify the <content> tag in my xml view, it will be assumed, just like in a VBox or HBox)

Better yet, all properties and aggregations have the get/set methods created automatically for you! so we can just go ahead and use it

First thing I’m going to do is get content to render, I’m not going to explain all the features of the oRm here, but will provide a few comments in the code.


sap.ui.define(
    ['sap/ui/core/Control'],
    function(Control) {
        return Control.extend("dalrae.ui.containers.ShadowBox",{
            metadata: {
                properties: {},
                aggregations: {
                    content: {
                        type: "sap.ui.core.Control"
                    }
                },
                defaultAggregation: "content",
            },
 
            renderer: function(oRm,oControl){
                //first up, render a div for the ShadowBox
                oRm.write("<div");
     
                //next, render the control information, this handles your sId (you must do this for your control to be properly tracked by ui5).
                oRm.writeControlData(oControl);
                oRm.write(">");
     
                //next, iterate over the content aggregation, and call the renderer for each control
                $(oControl.getContent()).each(function(){
                    oRm.renderControl(this);
                });
     
                //and obviously, close off our div
                oRm.write("</div>")
            },
 
            onAfterRendering: function(arguments) {
                if(sap.ui.core.Control.prototype.onAfterRendering) {
                 sap.ui.core.Control.prototype.onAfterRendering.apply(this,arguments);
                }
            },
 
        });
    }
);



Now Ill add a couple of controls to the content in my xml view like so


<dalraeContainers:ShadowBox>
       <Label text="Well well well" />
       <Input value="we have a container" />
</dalraeContainers:ShadowBox>


And when run in the browser, I can see the label and input controls rendered

Demo1.PNG

Building the Control – Properties

Next, I’m going to allow the developer to customise the width and height of this container

To do so, I’ll add them as properties to the metadata, and add a style attribute in my renderer


sap.ui.define(
    ['sap/ui/core/Control'],
    function(Control) {
        return Control.extend("dalrae.ui.containers.ShadowBox",{
            metadata: {
                properties: {
                    width: {
                        type: "sap.ui.core.CSSSize", //this is optional, but it helps prevent errors in your code by enforcing a type
                        defaultValue: "100%" //this is also optional, but recommended, as it prevents your properties being null
                    },
                    height: {
                        type: "sap.ui.core.CSSSize",
                        defaultValue: "auto"
                    }
                },
                aggregations: {
                    content: {
                        type: "sap.ui.core.Control"
                    }
                },
                defaultAggregation: "content",
            },
  
            renderer: function(oRm,oControl){
                //first up, render a div for the ShadowBox
                oRm.write("<div");
                //render width & height properties
                oRm.write(" style=\"width: " + oControl.getWidth() + "; height: " + oControl.getHeight() + ";\"");
                //next, render the control information, this handles your sId (you must do this for your control to be properly tracked by ui5).
                oRm.writeControlData(oControl);
                oRm.write(">");
      
                //next, iterate over the content aggregation, and call the renderer for each control
                $(oControl.getContent()).each(function(){
                    oRm.renderControl(this);
                });
      
                //and obviously, close off our div
                oRm.write("</div>")
            },
  
            onAfterRendering: function(arguments) {
                if(sap.ui.core.Control.prototype.onAfterRendering) {
                 sap.ui.core.Control.prototype.onAfterRendering.apply(this,arguments);
                }
            },
  
        });
    }
);



Now I can set a width and/or height in my xml and can see it taking effect in my browser


        <dalraeContainers:ShadowBox
            width="200px"
            >
            <Label text="Well well well" />
            <Input value="we have a container" />
        </dalraeContainers:ShadowBox>


Demo2.PNG

Building the Control – CSS

Finally, we get to the good part, what this control is meant to do, is look pretty

So to do that, we’ll use some CSS, and we’ll do it with a css file (not in the style tag, which would be repeated for each instance)
Because this is a UI Control you can’t expect the developer to import your css, the control itself will ensure the css it needs gets imported.

my css file “css/dalrae.css” looks like this (it creates a shadow)


.dalrShadowBox {
    -webkit-box-shadow: 5px 5px 5px rgba(0,0,0,0.5);
    -moz-box-shadow: 5px 5px 5px rgba(0,0,0,0.5);
    box-shadow: 5px 5px 5px rgba(0,0,0,0.5);
    border-radius: 4px;
    padding: 4px;
}



my new control code looks like this

(note the new init function, fired when the control is first instantiated)


sap.ui.define(
    ['sap/ui/core/Control'],
    function(Control) {
        return Control.extend("dalrae.ui.containers.ShadowBox",{
            metadata: {
                properties: {
                    width: {
                        type: "sap.ui.core.CSSSize", //this is optional, but it helps prevent errors in your code by enforcing a type
                        defaultValue: "100%" //this is also optional, but recommended, as it prevents your properties being null
                    },
                    height: {
                        type: "sap.ui.core.CSSSize",
                        defaultValue: "auto"
                    }
                },
                aggregations: {
                    content: {
                        type: "sap.ui.core.Control"
                    }
                },
                defaultAggregation: "content",
            },
  
            init: function() {
                //initialisation code, in this case, ensure css is imported
                var libraryPath = jQuery.sap.getModulePath("dalrae.ui"); //get the server location of the ui library
                jQuery.sap.includeStyleSheet(libraryPath + "/../css/dalrae.css"); //specify the css path relative from the ui folder
            },
  
            renderer: function(oRm,oControl){
                //first up, render a div for the ShadowBox
                oRm.write("<div");
      
                //add this controls style class (plus any additional ones the developer has specified)
                oRm.addClass("dalrShadowBox");
                oRm.writeClasses(oControl);
      
                //render width & height properties
                oRm.write(" style=\"width: " + oControl.getWidth() + "; height: " + oControl.getHeight() + ";\"");
      
      
                //next, render the control information, this handles your sId (you must do this for your control to be properly tracked by ui5).
                oRm.writeControlData(oControl);
                oRm.write(">");
      
                //next, iterate over the content aggregation, and call the renderer for each control
                $(oControl.getContent()).each(function(){
                    oRm.renderControl(this);
                });
      
                //and obviously, close off our div
                oRm.write("</div>")
            },
  
            onAfterRendering: function(arguments) {
                if(sap.ui.core.Control.prototype.onAfterRendering) {
                 sap.ui.core.Control.prototype.onAfterRendering.apply(this,arguments);
                }
            },
  
        });
    }
);



Now, without changing the xml, the browser renders like so
Demo3.PNG

Building the Control – Background and Margins

So ShadowBox has a shadow, as expected, but it needs a background (like white), but I didn’t put that in my css class because I do want that customisable by the developer (so just apply the same logic as width and height). While I’m at it ,may as well add a margin property too so that it doesn’t hug the edge of the screen (of course you can do this with inbuilt css classes, but hey… I like to make things easy)

new control code


sap.ui.define(
    ['sap/ui/core/Control'],
    function(Control) {
     
        return Control.extend("dalrae.ui.containers.ShadowBox",{
            metadata: {
                properties: {
                    width: {
                        type: "sap.ui.core.CSSSize", //this is optional, but it helps prevent errors in your code by enforcing a type
                        defaultValue: "auto" //this is also optional, but recommended, as it prevents your properties being null
                    },
                    height: {
                        type: "sap.ui.core.CSSSize",
                        defaultValue: "auto"
                    },
                    background: {
                        type: "sap.ui.core.CSSColor",
                        defaultValue: "#ffffff"
                    },
                    margin: {
                        type: "sap.ui.core.CSSSize",
                        defaultValue: "5px"
                    }
                },
                aggregations: {
                    content: {
                        type: "sap.ui.core.Control"
                    }
                },
                defaultAggregation: "content",
            },
         
            init: function() {
                //initialisation code, in this case, ensure css is imported
                var libraryPath = jQuery.sap.getModulePath("dalrae.ui"); //get the server location of the ui library
                jQuery.sap.includeStyleSheet(libraryPath + "/../css/dalrae.css"); //specify the css path relative from the ui folder
            },
         
            renderer: function(oRm,oControl){
                //first up, render a div for the ShadowBox
                oRm.write("<div");
             
                //add this controls style class (plus any additional ones the developer has specified)
                oRm.addClass("dalrShadowBox");
                oRm.writeClasses(oControl);
             
                //render width & height & background properties
                oRm.write(" style=\"width: " + oControl.getWidth()
                                + "; height: " + oControl.getHeight()
                                + "; background-color: " + oControl.getBackground()
                                + "; margin: " + oControl.getMargin()
                                + "\"");
             
                //next, render the control information, this handles your sId (you must do this for your control to be properly tracked by ui5).
                oRm.writeControlData(oControl);
                oRm.write(">");
             
                //next, iterate over the content aggregation, and call the renderer for each control
                $(oControl.getContent()).each(function(){
                    oRm.renderControl(this);
                });
             
                //and obviously, close off our div
                oRm.write("</div>")
            },
         
            onAfterRendering: function(arguments) {
                if(sap.ui.core.Control.prototype.onAfterRendering) {
                 sap.ui.core.Control.prototype.onAfterRendering.apply(this,arguments);
                }
            },
         
        });
     
    }
);



Now my browser looks like this..

Demo5.PNG

We’re done! there’s a fully functional ShadowBox control for applying a nice dropshadow panel for controls to sit inside of!

See you later…

This was my first blog on SCN, hope you enjoyed it
Next up, I will expand on this topic and demonstrate

  1. How to extend a standard UI5 control
  2. Adding a fully accessible “press” event to your custom control

@LordOfTheStack

To report this post you need to login first.

10 Comments

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

  1. Klaus Kronawetter

    Great post! Just one question: where do you keep your dalrae/ui/containers and dalrae/ui/controls libraries and how are they accessible from different applications?

    (0) 
    1. Phillip Smith Post author

      Excellent question,
      (in fact it pointed out a mistake I made in the init() function, which I’ve now fixed)

      In the context of gateway, the ui library sits in its own bsp, and you must set up a reference to it from another bsp.

      I have two folders:
      zdalrae
      zdemo

      the control sits in
      zdalrae/ui/containers/ShadowBox.js

      and here I have a demo application, component located at
      zdemo/Component.js
      (with a namespace of dalrae.demo)

      in that component, I have to register the zdalrae.ui namespace as ../zdalrae/ui so that the zdemo application knows how to find it

      to do so needs 2 lines of code, and it looks like this

      init : function() {
      //import dalrae ui library
      var currentPath = jQuery.sap.getModulePath(“dalrae.demo”); //specify the name of this app to find its path (in this case my component is dalrae.demo.Component so therefore dalrae.demo is the namespace, this will be specific to each and every app)
      jQuery.sap.registerModulePath(“dalrae.ui”,currentPath + “../zdalrae/ui”); //relative path from here to the ui library (should in theory always be in the same tier of folder). this registers the namespace of the ui library
      sap.ui.core.UIComponent.prototype.init.apply(this,arguments);
      this.getRouter().initialize();
      }
      (0) 
  2. saurabh vakil

    Hi Phil,

    Excellent blog! I am trying to follow this and create a ShadowBox control. But in the step Building the Control – CSS I get an error ShadowBox.js:35 Uncaught TypeError: oControl.getClass is not a function for the line

    oRm.write(” class=\”dalrShadowBox ” + oControl.getClass() + “\””);

    Can you please help me in getting over this?

    Regards,

    Saurabh

    (0) 
    1. Phillip Smith Post author

      Hi Saurabh,

      You might be missing the property definition for Class. In this case I added a custom property “Class” to use instead of the original (infact there is a better way I could’ve gone about this, which I may update at a later date)

      metadata needs this

      1.                     Class: { //this allows the developer to also apply their own style classes 
      2.                         type: “string”
      3.                         defaultValue: “” 
      4.                     } 
      (0) 
      1. saurabh vakil

        Thanks Phil! I somehow missed adding the Class property in the metadata. I have now got the custom control with the box shadow working perfectly.

        (0) 

Leave a Reply