Skip to Content
Technical Articles
Author's profile photo Sagi Yehuday

How to Extend a Standard SapUI5 Control

There comes a time in every project’s lifecycle when you realize you should start using extended controls in your XML views.

For example,using the same Value Help dialogs several times in the same project can be quite common, but copying and pasting the same code would be difficult to maintain.

Another case would be when you’re assigning a custom “displayFormat” to your datepickers repeatedly throughout your project for every datepicker in several XML views.

In this post we will take a standard control, set some of its properties, add a bit of functionality to it and then reuse it as many times as needed in our project.

Extending a SAPUI5 Standard control ,or as some people would call Inheriting from it will in the long run, simplify your code and make it easier to maintain.

In our example we will take the standard Input control and extend its functionality by :

  1. Attaching a value help dialog with a list of materials.
  2. Passing parameters from its parent XML view.
  3. Handling selection events.
  4. Adding  basic input validation.

When we’re done we’ll be able to use the extended control over and over in our XML views without copying and pasting the same code in our views and controllers.

All of the above functionality will be encapsulated in a single file called “SelectMaterial.js” which will provide the new control that we can then use in our XML view like so:

<my:SelectMaterial value="{viewModel>/matnr}" lgort="3001" onSelect="onMatnrSelect" width="160px"/>

 

Full Source code for this project can be found here:
https://github.com/Sagi44/ExtendedSAPUI5Control

 

So the first step is to add a new folder called “Controls” under webapp.

Under “Controls” add a file called “SelectMaterial.js” with the following code:

sap.ui.define([
	"sap/ui/core/Control",
	"sap/m/Input",
	"sap/m/MessageToast",
	"sap/ui/model/Filter"
], function(Control, Input, MessageToast, Filter) {
	"use strict";

	return Input.extend("sapui5.demo.customcontrols.controls.SelectMaterial", {

		metadata: {
			properties: {
				value: {
					type: "string",
					defaultValue: ""
				},
				text: {
					type: "string",
					defaultValue: ""
				},
				showValueHelp: {
					type: "boolean",
					defaultValue: true
				},
				lgort: {
					type: "string",
					defaultValue: "0000"
				}
			},
			aggregations: {

			},
			events: {
				"onSelect": {
					allowPreventDefault: true,
					parameters: {
						"material": {
							type: "object"
						},
						"materialDescription": {
							type: "string"
						}
					}
				}
			},
			renderer: null
		},

		init: function() {

			Input.prototype.init.call(this);

			this.attachChange(this.onChange);

			this.attachValueHelpRequest(this.onValueHelpRequest);

			this.attachOnSelect(this.onSelect);
		},

		renderer: function(oRm, oInput) {
			sap.m.InputRenderer.render(oRm, oInput);
			oRm.write("<Label>");
			oRm.write(oInput.getText());
			oRm.write("</Label>");
		},

		setLgort: function(sValue) {
			this.setProperty("lgort", sValue, true);
			return this;
		},

		getLgort: function() {
			return this.getProperty("lgort");
		},

		onValueHelpRequest: function(oEvent) {
			// function name speaks for itself
			var that = this;
			// get selected Lgort
			var lgort = this.getLgort();

			if (!lgort) {
				MessageToast.show("Please Select Storage Location");
				return;
			}

			var oModel = new sap.ui.model.json.JSONModel();
			oModel.loadData("service/data.json");

			oModel.attachRequestCompleted(function() {

				that.setModel(oModel, "materials");

				var oTemplate = new sap.m.StandardListItem("DialogMatnrItems", {
					title: "{Material}",
					description: "{MaterialDescription}"
				});

				var oDialog = new sap.m.SelectDialog("dialogParentTableMatnr", {
					title: "Select Material",
					confirm: function(oConfirmEvent) {
						that.closeDialogMatnrItems(oConfirmEvent);
					},
					cancel: function(oCancelEvent) {
						that.closeDialogMatnrItems(oCancelEvent);
					},
					search: function(oEvt) {
						var sValue = oEvt.getParameter("value");
						if (!sValue || sValue === "") {
							oEvent.getSource().getBinding("items").filter([]);
						} else {
							var filter = new Filter("MaterialDescription", sap.ui.model.FilterOperator.Contains, sValue);
							var oBinding = oEvent.getSource().getBinding("items");
							oBinding.filter([filter]);
						}
					}
				});
				oDialog.setModel(that.getModel("materials"));
				oDialog.bindAggregation("items", "/results", oTemplate);
				oDialog.open();

			});

		},

		closeDialogMatnrItems: function(oEvent) {

			var oDialog = oEvent.getSource();
			oDialog.destroyItems();
			oDialog.destroy();

			if (oEvent.getParameter("selectedItem") && oEvent.getParameter("selectedItem").getBindingContext()) {
				var obj = oEvent.getParameter("selectedItem").getBindingContext().getObject();
				// save select dialog object in control properties

				this.setValue(obj.Material);
				this.setText(obj.MaterialDescription);
				// fire event to notify view of selection
				this.fireOnSelect({
					"material": obj.Material,
					"materialDescription": obj.MaterialDescription
				});

			}
		},

		onChange: function(oEvent) {

			// fires on focus leave or when user presses Enter

			if (oEvent.type === "sapfocusleave") {
				// ignore lost focus
				return false;
			}
			if (!oEvent.target.value) {
				// ignore if notempty control
				return false;
			}

			var lgort = this.getLgort();
			if (!lgort) {
				MessageToast.show("Please Select Storage Location");
				return;
			}

			// get control value and try to get its description from model
			var material = oEvent.target.value;
			var that = this;
			var oModel = new sap.ui.model.json.JSONModel();
			oModel.loadData("service/data.json");

			oModel.attachRequestCompleted(function() {

				that.setModel(oModel, "materials");
				var oData = oModel.getData();

				for (var i = 0; i < oData.results.length; i++) {
					if (oData.results[i].Material === material) {
						that.setValue(material);
						that.setText(oData.results[i].MaterialDescription);
						that.fireOnSelect({
							"material": material,
							"materialDescription": oData.results[i].MaterialDescription
						});
						return;
					}
				}

				MessageToast.show("Material Number " + material + " Does not Exist.");

			});

		},

		onSelect: function(oEvent) {
			
		}

	});
});

The magic begins here :

	return Input.extend("sapui5.demo.customcontrols.controls.SelectMaterial"

We are inheriting all the goodness of the standard input Control and later on adding our own custom functionality.

The metadata section of our control is where we set our default properties. For example “showValueHelp” is set to true by default.

The “lgort” property is a custom property that will allow us to pass values to the control from our XML view. Its default value is “0000”.

The metadata section also defines a custom event called “onSelect” which we will fire to notify our view that the user has successfully selected a valid Material from the value help list. This event will also fire an Event object with the selected values “material” and “materialDescription”.

The next important thing to notice is the “init” function of the extended control. Here we need to attach the standard Input control’s basic events to our own custom functions.

this.attachValueHelpRequest(this.onValueHelpRequest);

In our function “onValueHelpRequest” we will query our data source of choice, populate and display a value help dialog.

 

Next, if the user has selected an item from the list, we will need to fire an event to let the view know about this.

				this.fireOnSelect({
					"material": obj.Material,
					"materialDescription": obj.MaterialDescription
				});

We can then handle the event in our XML view :

<my:SelectMaterial value="{viewModel>/matnr}" lgort="3001" onSelect="onMatnrSelect" width="160px"/>

and of course in our view controller :

		onMatnrSelect: function(oEvent) {
			
			// Called  whenever Matnr has been changed (either by pressing Enter, lost Focus,  or Value Help Selection )
			var matnr = oEvent.getParameter("material");
			var matnrDescription = oEvent.getParameter("materialDescription");

			// do something with selection 
			MessageToast.show("You selected :" + matnr + " " + matnrDescription);

			// view model is updated
			var model = this.getView().getModel("viewModel");
			MessageToast.show("Model Matnr =  :" + model.getProperty("/matnr"));
			
		}

 

I hope this post has given you an outline on how to build upon existing standard controls and create your own extended controls.

Let me know if you have and suggestions or remarks.

Regards,
Sagi

 

Full Source code for this project can be found here:
https://github.com/Sagi44/ExtendedSAPUI5Control

Assigned Tags

      7 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Klaus Kronawetter
      Klaus Kronawetter

      Hi Sagi,

      thanks for the detailed tutorial. I have to admit, that although I have already developed a few custom SAPUI5 apps, I have successfully avoided custom controls until now. Mainly because of one reason: if something doesn't work, you are on your own. There is no SAP note that can repair your custom control 😉

       

      Two questions though:

      1. Is it really necessary to declare the "value" and "text" properties in the custom control or would they get inherited from the sap.m.Input control nonetheless?
      2. How can we use a custom control across multiple apps?

       

      Thanks for your effort.

      BR, Klaus

      Author's profile photo Marco Beier
      Marco Beier

      Hey Klaus,

      the following blog might help you out or even answer what you were looking for in your 2nd question, although already being ~2yrs old.

      https://blogs.sap.com/2018/01/15/step-by-step-procedure-to-create-sapui5-library-with-custom-controls-and-consume-the-library-into-sapui5-applications/

      Best Regards,

      Marco 🙂

      Author's profile photo Marco Beier
      Marco Beier

      Hey again,

      I think the answer to question one would be that there is no need to declare the "value" property again as this is being inherited from it's parent (like sap.m.Input and sap.m.InputBase). However, I couldn't find the text property within those controls, so it makes sense declaring this within the metadata of the exstension. I'm not sure though, maybe I've overlooked something and there is a "text" property already declared somewhere.

      Please correct me if I'm wrong. 🙂

      Best Regards,

      Marco

      Author's profile photo Marco Beier
      Marco Beier

      Great Blog Sagi Yehuday!

      Much like Klaus Kronawetter, I have also successfully avoided extending standard controls so far.

      However I'd like to mention that the declaration of ...

      	"sap/ui/core/Control",

      within sap.ui.define is not needed in your example (correct me if I'm wrong).

      I would also like to ask if it's necessary to have the 'renderer: null' declared within the control metadata as you're defining a renderer method later on.

      Thanks again for this great Blog!

      Best Regards,

      Marco

      Author's profile photo Sagi Yehuday
      Sagi Yehuday
      Blog Post Author

      Marco Beier Yes you're right,  the “sap/ui/core/Control” injection can be omitted as is the

      “renderer: null” declaration.

      Many Thanks for your feedack!

       

      Author's profile photo Florian Weil
      Florian Weil

      Thank you very much for your blog article. It helped me to understand the API better and gave me an idea how to extend one Component.

      Your sample questions my understanding of XML.composites. Do I understand correctly, that you add a Label to your Input component within your render function? If yes, can you shortly explain why extending directly from a Component/Control is chosen before a XML.composite approach (https://sapui5.hana.ondemand.com/#/topic/b83a4dcb7d0e46969027345b8d32fd44) with (property) aggregation forwarding (https://sapui5.hana.ondemand.com/#/topic/64a5e1775bf04d4883db18c9de7d83bd). I thought XML.composite and its controller makes sense if you have to work with 1+ components within your view. And it should be chosen if you have to create a new component from several components. Maybe I misunderstood the concept.

       

      Thanks again for your blog post!

      Author's profile photo Marco Beier
      Marco Beier

      Hey Florian Weil,

      although I'm not the creator of this blog I can assure you that you've understood the concept behind XMLComposite-Controls correctly - though, don't confuse controls with components!

      I think the reasoning behind manually adding the label within the renderer is to show that you're able to manually change what is rendered when creating a custom control but also refers to the fourth point of what Sagi Yehuday! wanted to demonstrate, which is 'Adding  basic input validation' as he shows what our input is by displaying it within the added/rendered label control.

      Hope I could help. 🙂

      Best Regards,

      Marco