Technology Blogs by Members
Explore a vibrant mix of technical expertise, industry insights, and tech buzz in member blogs covering SAP products, technology, and events. Get in the mix!
cancel
Showing results for 
Search instead for 
Did you mean: 
lordofthestack
Explorer

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() {
//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() {
//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



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() {
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


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() {
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>




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() {
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

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() {
if(sap.ui.core.Control.prototype.onAfterRendering) {
sap.ui.core.Control.prototype.onAfterRendering.apply(this,arguments);
}
},

});

}
);




Now my browser looks like this..



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

14 Comments
Labels in this area