Skip to Content
Author's profile photo Angel Puertas

How to create custom control from scratch

Why create a custom control

SAPUI5 offers several simple controls like TextField, Label… and really complex controls like ThingCollecion, Table, etc. All SAPUI5 controls are listed here:

https://sapui5.netweaver.ondemand.com/sdk/#content/Controls/index.html

It is not necessary create a custom control from scratch if:


sap.ui.commons.Button.extend("MyButton", { //inherit Button definition                                      
  metadata: {
    events: {
      "hover" : {}  //new event definition hover
    }
  },
  //hover event handler
  onmouseover : function(evt) {
    this.fireHover();
  },
  renderer: {} //Standard renderer method is not overridden
});






When our requirements doesn’t fit standard SAPUI5 controls and we have no choice we can create custom controls.

What is a control and how it works

A control defines its appearance and behavior. All SAPUI5 controls extend from sap.ui.core.Control. In the other hand sap.ui.core.Element are parts of Controls but without renderer method. For example a Menu (Control) has different MenuItem (Element) and Menu renders its MenuItems.

Main structure of controls:

  • properties. Allows define its appearance and behavior on initialization.
  • aggregations. Lets group controls, variables, etc. Lets define some kind of containers inside a control. For example sap.ui.table.Table has different aggregations like columns, rows, etc.
  • associations. Controls can be associated with others that are not part of them. For example if we want to render a collection with next/prev functionality we could develop a previousItem / nextItem associations.
  • events. Control events should be related to higher level events more than standard DOM events (click, mouseover, etc). For example if we develop a Control which renders many tabs, tabSelect could be an event when a tab is selected.
  • appearance. Definition of our control in screen area. Every control has a render method in order to be rendered in HTML code.

My requirement: Custom autocomplete field like Google Gmail recipients.

I need a control that lets us:

  • add/remove different values
  • find values with autocomplete function
  • see all added values

Example Google Gmail recipients field:

/wp-content/uploads/2014/02/sh000_384397.png

Step by step

1. Create new library AutoCompleteValueHolder in new package /control:

sh001.PNG

2. Define a basic template on AutoCompleteValueHolder.js in order to test if it works:


sap.ui.core.Control.extend("control.AutoCompleteValueHolder", {
    metadata : {
      properties: {},
      aggregations: {},
      events: {}
    },
    init: function() {       
    },
    renderer : {     
        render : function(oRm, oControl) {
          oRm.write('Hello, this is a new control :)');
        }
    }
});






3. Load your control library in html file:


sap.ui.localResources('control');
jQuery.sap.require("control.AutoCompleteValueHolder");






4. Use your new control in a view:


var yourNewControl = new control.AutoCompleteValueHolder('yourNewControl');






sh002.PNG

5. Add custom properties, aggregations and events:


  metadata : {
    properties: {
      "codePropertyName": {type : "string", defaultValue: "code"}, //Define a model property representing an item code
      "descriptionPropertyName": {type : "string", defaultValue: "description"}, //Define a model property representing an item description
      "path": {type : "string", defaultValue: "/"}, //Define our model binding path
      "model": {type : "any", defaultValue: new sap.ui.model.json.JSONModel()} //Define our model
    },
    aggregations: {
      "_layout" : {type : "sap.ui.layout.HorizontalLayout", multiple : false, visibility: "hidden"} //Grouping of selected items and search text field
    },
    events : {    
        "selectedItem": {},
        "deletedItem": {},
        "deletedAllItems": {}
    }
  }
 






6. Initialize control:

This method will be called when an AutoCompleteValueHolder is instantiated


init: function() {
  var oControl = this;
  //Creation of search autocomplete field
  var searchField = new sap.ui.commons.AutoComplete(this.getId() + '-searchField',{
    maxPopupItems: 5,
    displaySecondaryValues: true,
    change: function change(event) {
      if (event.mParameters.selectedItem != null) { //If user selects a list item, a new item is added to _layout aggregation
    //Every new item consist in a TextField and a Image
        var newValueField = new sap.ui.commons.TextField({
          width: '100px',
          editable: false
        });
  //TextField shares model with other TextFields and Autocomplete. We select correct path (user selection)
        newValueField.setModel(event.getSource().getModel());
        newValueField.bindProperty("value", event.getSource().getParent().getParent().mProperties.descriptionPropertyName);
        newValueField.bindElement(event.mParameters.selectedItem.oBindingContexts.undefined.sPath);
        newValueField.addStyleClass('autoCompleteValueHolder_valueField');  //Custom style
  //Image let's us delete an Item. Press event will destroy TextField and Image from _layout aggregation
        var newValueImage = new sap.ui.commons.Image({
          src: 'data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAAwAAAAMCAYAAABWdVznAAAABmJLR0QA/wD/AP+gvaeTAAAACXBIWXMAAAsTAAALEwEAmpwYAAAAB3RJTUUH3gIKEw06nF/eLQAAAb9JREFUKM9lkkFIFHEUxn//mf3TrJVOLjvu7phBBB0M3VyNTYg85CU6ZLfqmpAwRVBseqg1CI0OzZ47ePRkh9xQiAgWgqIOXUpNhHbBDGdFSxYX2m3+HZoNpe/yHt/73oP3vgfADHsxI8T/XCOZDuIkGHO9SWcMZKM2CnL+VMqZAGO3lgkw8rFDBe/+qMrbllsFsQhiNhZxy9kxlbetwmTQpANcgesnhy6MSF+iW9H0sw2vWtwf7u8aOHsvrCRmsvNI+e2H9Vl4rwOcgI/11dJBX5I+IJtQLU3nTCs6aDVH+L72lYVXr3NLlZ1Hb8DXp4A74C9Xay+P/9zaEeLXoFnXCdcV3nqR4ufFuw/LP7LP4fcUECrJENTqfAKqvo+/+o2atw2AsJqpKsVysOtmWxsCYAjkmXjkcVqoWx0b28xHTUBw3tuiZJm8U1puac3LPIUaALeP2c6Xnk5VAfXicEm3JXGw1M3MdtqqAWulLqlxvyvlnymUw3PZEYSHVpa4l4i4gADEcj7krfT3qSXu8MBCclXFNA+AqGDeP2je6dxkHyOnT/U437AMYF+Ivm5WhPW8wrGmMBIMaeC  wB5M5PywZXUzgAAAABJRU5ErkJggg==',
          press: function(event){
            var valueLayout = event.getSource().getParent();
            var autoCompleteHolderLayout = event.getSource().getParent().getParent().getParent().mAggregations._layout;
            autoCompleteHolderLayout.removeContent(valueLayout);
 //Fire deletedItem event
          oControl.fireDeletedItem({
          allItems: oControl.getSelectedValues()
          });
          },
          width: '12px'
        });                        
        newValueImage.addStyleClass('autoCompleteValueHolder_valueImage'); //Custom style
  //Wrapping container for TextField and Image
        var valueLayout = new sap.ui.layout.HorizontalLayout({content: [newValueField, newValueImage]});
        valueLayout.addStyleClass('autoCompleteValueHolder_valueLayout');
  //Insert wrapping layout into 0 position
        event.getSource().getParent().getParent().mAggregations._layout.insertContent(valueLayout, 0);
        var content = event.getSource().getParent().getParent().mAggregations._layout.getContent();
//fire selectedItem event
oControl.fireSelectedItem({
          newItem: {
          code: event.mParameters.selectedItem.mProperties.additionalText,
          description: event.mParameters.selectedItem.mProperties.text
          },
          allItems: oControl.getSelectedValues()
          });
  //Reset value from autocomplete search field
        var search = content[content.length-1];
        search.setValue('');
      }
    }
  });
  searchField.addStyleClass('autoCompleteValueHolder_search'); //Custom style
  //_layout aggregation creation
  var layout = new sap.ui.layout.HorizontalLayout(this.getId() + '-valuesLayout',{allowWrapping: true});
  layout.addContent(searchField);
  layout.addStyleClass('autoCompleteValueHolder_valuesLayout');
  //Set _layout aggregation into our control
  this.setAggregation("_layout", layout);
}






7. Control rendering:

This method will produce html code:


renderer : {
  render : function(oRm, oControl) {
    var layout = oControl.getAggregation("_layout");
    layout.getContent()[0].setModel(oControl.getModel());
    var template = new sap.ui.core.ListItem({
      text: "{"+oControl.getDescriptionPropertyName()+"}",
      additionalText: "{"+oControl.getCodePropertyName()+"}"
    });
    layout.getContent()[0].bindItems(oControl.getPath(), template);
  oRm.write("<span");
    oRm.writeControlData(oControl);
    oRm.writeClasses();
    oRm.write(">");
    oRm.renderControl(layout); //Reuse standard HorizontalLayout render method.
    oRm.write("</span>");
  }
}






8. Custom methods

This custom methods lets us get selected items or clear all selected items:


getSelectedValues: function() {
  var content = this.getAggregation("_layout").getContent();
  var result = [];
  if (content != null && content.length > 1) {
    //Get all selected item into result
    for (var i=0; i<content.length-1; i++) {
      var model = content[i].getContent()[0].getModel();
      var path = content[i].getContent()[0].getBindingContext().sPath;
      result.push(model.getProperty(path));
    }
  }
  return result;
},
clearSelectedValues: function() {
  if (this.getAggregation("_layout").getContent() != null && this.getAggregation("_layout").getContent().length > 1) {
    //Delete all selected items (SubLayouts containing TextField+Image) from _layout aggregation
    while (this.getAggregation("_layout").getContent().length > 1) {
    this.getAggregation("_layout").removeContent(0);
//fire deletedAllItemsEvent
this.fireDeletedAllItems({});
  }
  this.getAggregation("_layout").rerender(); //ReRenders _layout aggregation
},
updateModel : function (newModel, newPath, codePropertyName, descriptionPropertyName) {
    this.setModel(newModel);
    this.setPath(newPath);
    this.setCodePropertyName(codePropertyName);
    this.setDescriptionPropertyName(descriptionPropertyName);
 
    var layout = this.getAggregation("_layout");  
    layout.getContent()[0].setModel(this.getModel());
      
        var template = new sap.ui.core.ListItem({
                                                 text: "{"+this.getDescriptionPropertyName()+"}",
                                                 additionalText: "{"+this.getCodePropertyName()+"}"
                                               });
        layout.getContent()[0].bindItems(this.getPath(), template);   
    }






9. Final result:

JS Bin – Collaborative JavaScript Debugging

sh003.PNG

sh004.PNG

EDIT (11/02/2014): Added some enhancements (three new events, and repetead item control).

Any suggestion or feedback will be welcome 🙂

Enjoy!

Assigned Tags

      15 Comments
      You must be Logged on to comment or reply to a post.
      Author's profile photo Jason Moors
      Jason Moors

      Hi Angel,

      Great blog, I think it would be great to get some guidelines/standards for creating custom controls, similar to the jQuery plugins.

      I guess it may come as OpenUI5 develops, but would also be great to have somewhere to share custom controls (perhaps github to start with), rather than everyone reinventing the wheel.

      Many thanks,

      Jason

      Author's profile photo Angel Puertas
      Angel Puertas
      Blog Post Author

      Hi Jason,

      Thanks. Sincerely It was a little bit confusing trying to understand how manager custom controls.

      It was very helpful download OpenUI5 source code to understand how to manage aggregations/properties in order to renderize your custom control. This link is very useful:

      OpenUI5

      Kind regards!

      Author's profile photo Jason Moors
      Jason Moors

      Hi Angel,

      Thanks, agree creating custom controls is quite complicated, I've experimented a little myself.

      Custom controls is one area where I can see people contributing/reusing now that the library has been open sourced.

      The current document outlines how to create a basic control, but would be good to define some standards, naming conventions, classnames etc for custom controls, to ensure there is some consistency.

      Many thanks,

      Jason

      Author's profile photo Christopher Solomon
      Christopher Solomon

      Well written and documented! THANKS!

      Author's profile photo Vivek Singh Bhoj
      Vivek Singh Bhoj

      Great Blog

      Very well written and very well explained

      Thanks for sharing

      Regards,

      Vivek

      Author's profile photo Former Member
      Former Member

      Hi Jason,

      Thank you for the great blog.

      How can we add CSS to the drop down suggestion that appears?

      Regards,

      Atul

      Author's profile photo Jason Moors
      Jason Moors

      Hi Atul,

      This is Angel's blog, not mine.. 🙂

      Have you tried overriding the .sapUiLbx class?

      Regards,

      Jason

      Author's profile photo bhaskar gupta
      bhaskar gupta

      Hi,

      Great block..

      I am new sapui5 and want to extend autocomplete for default suggest values on focusin.

      Author's profile photo Former Member
      Former Member

      Hi Jason,

      Thanks for the Blog.

      Had to make the below minor change to render to make it work when hiding and unhiding the control with values present in the control. I have placed the new control in a VBOX.

          renderer : {   

              render : function(oRm, oControl) {

         var layout = oControl.getAggregation("_layout");   
         var v = layout.getContent();
        

                  v[v.length-1].setModel(oControl.getModel());

                  var template = new sap.ui.core.ListItem({

                                                           text: "{"+oControl.getDescriptionPropertyName()+"}",

                                                           additionalText: "{"+oControl.getCodePropertyName()+"}"

                                                         });

                  v[v.length-1].bindItems(oControl.getPath(), template);

               

         oRm.renderControl(layout);   

              }

          },

      Thanks,

      Arun Sambargi

      Author's profile photo SAP Seeker
      SAP Seeker

      Dear Angel,

      Thanks for your blog. We have 1.18.12 (HANA SP7) and our business is in need to sap.m.MultiComboBox, but which is available from version 1.22 (HANA SP8). I am trying to copy the JS files namely sap.m.MultiComboBox and it's dependant sap.m.MultiComboBoxRenderer to local control folder as you mentioned in your blog, but unable to create a new control out of it.

      When i try to create a control, I am getting below error.

      "Uncaught TypeError: undefined is not a function"

      Since the error is from sap-ui-core.js, I am not able to find the exact reason. Whether upgrading the sap.m library to latest version in our system would help ? If yes, any specific OSS note that would update that ? It would be of great help, if you can please help me to solve this issue.

      Thanks and Regards,

      Gaurav.

      Author's profile photo Krishna Kishor Kammaje
      Krishna Kishor Kammaje

      Thanks for the blog Angel. Have a question. Why did you keep '_layout' in aggregations? Since there will be one value anytime, don't you think it fits better in properties?

      Author's profile photo Maximilian Lenkeit
      Maximilian Lenkeit

      As a rule of thumb, controls should always(!) be passed as aggregations and not as properties. Using an aggregation, a parent-child relation is created, data binding changes are populated, events are fired the the control properly re-rendered if require. If you used a property instead, you would need to take care of all these things yourself 😉

      - Max

      Author's profile photo Former Member
      Former Member

      You helped me a lot, Angel, thanks. I'm still stuck on some minor things, but your blog gave me a big push forward.

      Author's profile photo Angel Puertas
      Angel Puertas
      Blog Post Author

      You are welcome! It was really hard to build the first control and then I thought it would help post it on the scn. It is not usual create custom controls, but sometimes it is required.

      Kind regards

      Author's profile photo Manoj kumar
      Manoj kumar

      autocomplete is depricated now. what we have another option in order to achieve this?