Skip to Content

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!

To report this post you need to login first.

14 Comments

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

  1. 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

    (0) 
    1. Angel Puertas 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!

      (0) 
      1. 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

        (0) 
  2. Arun Sambargi

    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

    (0) 
  3. Gaurav Subramanian

    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.

    (0) 
    1. 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

      (0) 
    1. Angel Puertas 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

      (0) 

Leave a Reply