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: 
Former Member

Hi again :smile:

Some of you might have read my previous post about the Rounded Tiles in UI5 and wonder what more can be done with those...

If you are not familiar with OpenUI5, go and check the great work they are doing on OpenUI5

I worked a bit on the main idea that drove me creating those round tiles and I'm happy to share the next step with you...

When I started the customisation of the tiles to be able to show as round tiles, I had an idea of what I wanted to accomplish next. I wanted to have tiles that acts as "menus" having some actions attached to them... As it can't be explained better than with a screenshot of the final result, here it is:

(Of course the different tiles colours are just for the purpose of this blog to show the display customising I implemented)

So basically, a tile can have what I will further in this blog call "actions" that are some kind of "baby tiles" on which the user press to navigate to a specific view...

It allows the grouping of actions about the same subject under the same tile and, at the same time, reduce the number of tiles necessary in the launch page of your UI5 app. The control can handle up to seven actions without any overlap.

I'll not cover here the entire source code but only the most important parts of it. Please refer to the GitHub link at the end of the post for the entire source code, and the JSFiddle for a live demo.

What follows is very technical and if you don't really care and just want to know how to use the control, scroll down till point 2.

1) The control

The code of the control can be found under WebContent / controls / roundedActionTile. This folder contains 2 files: the javascript code for the roundedActionTiles and the CSS attached to the control.

1.1) The JS code

As usual, at the beginning of the file some lines declares the control, attach the control specific css file (see 1.2 bellow) and requires the necessary standard libraries.


jQuery.sap.declare("controls.roundedActionTile");
jQuery.sap.includeStyleSheet("controls/roundedActionTile/roundedActionTile.css");
jQuery.sap.require("sap.m.StandardTile");
jQuery.sap.require("sap.ui.core.IconPool")

The control inherits from the sap.m.StandardTile object in order to keep the functionalities available for that object.

It extends the StandardTile by adding some properties (iconColor, cssClass) and the control has an attribute aTileOptions that is an array that will contain the actions attached to the tile.


metadata :{
    properties : {
    // Icon color property with default value to standard UI5 blue
        iconColor : {
            type : "string",
            defaultValue : "#007cc0"
        },
        cssClass:{
            type : "string",
            defaultValue: "defaultActionClass"
        }
    },
    events:{
        actionPress : {}
    }
},
aTileOptions: null,

The biggest part of the work is, as you can guess the rendering of the control to be able to display the actions around the tile. In the render function, you will find the code computing the positions and creating these actions. The computations are dynamic in order to handle the different number of actions. After the computation, it uses the CSS3 transform in order to rotate and translate the div created for the actions.


if(oControl.aTileOptions){// Start actions management
    var startPoint, nbrLeft, firstPosition = 0; // initialize the variable
    var rotationFactor = 51; // Fixed rotation factor (based on the action div size)
    startPoint = -135; // Starting point of the rotations
    if(oControl.aTileOptions.length%2 == 1){ // compute the number of elements to be placed on the left
        nbrLeft = (parseInt(oControl.aTileOptions.length/2));
    }else{
        nbrLeft = (parseInt(oControl.aTileOptions.length/2) - 0.5);
    }
    // Compute the first position to be taken by the created action div.
    firstPosition = startPoint - (nbrLeft*rotationFactor);
    for(var i in oControl.aTileOptions){
        var rotation = firstPosition + (rotationFactor*i); // Compute the rotation needed for the current action
        var invertedRotation = rotation>0?-rotation:Math.abs(rotation); // compute the inverse rotation (used for the content)
        rm.write("<div");// Start action container
        rm.writeAttribute("id", oControl.getId() + "-action_"+i);
        rm.writeAttribute("style", "transform: rotate("+ rotation +"deg) translate(80px,80px);"); // apply transformation
        rm.addClass("actionItem");
        rm.addClass("actionHidden");
        if(oControl.aTileOptions[i].cssClass) // apply the custom or standard CSS class
            rm.addClass(oControl.aTileOptions[i].cssClass);
        else
            rm.addClass("defaultActionClass");
        rm.writeClasses();
        if (oControl.aTileOptions[i].title) { // Write the action title as tooltip
            rm.write(" title=\"");
            rm.writeEscaped(oControl.aTileOptions[i].title);
            rm.write("\"");
        }
        rm.write(">");
        // Start Icon
        rm.write("<div");
        rm.addClass("innerActionItem"); // create the content of the action
        // apply the inverted rotation to the content of the action div
        rm.write(" style=\"color:" + oControl.aTileOptions[i].iconColor + "; transform: rotate("+ invertedRotation +"deg);\"");
        rm.writeClasses();
        rm.write(">");
        var action_icon = oControl.getActionImage(oControl.aTileOptions[i].icon);// get the icon
        action_icon.addStyleClass('roundedActionTileActionIcon');
        rm.renderControl(action_icon);
        rm.write("</div>");
        // End Icon
        rm.write("</div>");// End action container
        oControl.aTileOptions[i].sId = oControl.getId() + "-action_"+i;// add the id of the created div in the aTileOption attribute for future reference
}

As you might have notice, I save the DOM id of the div created for the actions in the aTileOptions array. This is necessary to easily lookup the elements in order to hide and show them. This will be performed by the following function:


showActions:function(bVisible){
    for(var i in this.aTileOptions){
        var elem = this.$().find("#"+this.aTileOptions[i].sId);// lookup the element by id saved during the rendering
        if(elem){
            bVisible?elem.removeClass("actionHidden"):elem.addClass("actionHidden");// hide or show the DOM element
        }
    }
}

This function is not called by the control itself but by your controller to give a maximum of flexibility to your solution.

Having done all this, we just need to attach the events on the actions, and of course, a blur event to the control to hide the actions when the tile looses the focus... This is done after the rendering by the following function:


onAfterRendering:function(){
    // Call the parent method as it's the one placing the tiles in the container
    sap.m.Tile.prototype.onAfterRendering.call(this);
    for(var i in this.aTileOptions){
        // lookup the elements in order to add the listener on the click
        var elem = this.$().find("#"+this.aTileOptions[i].sId);
        if(elem){
            elem.click(function(oEvent){
                this.actionClicked({tile: this, actionId:oEvent.currentTarget.id});
            }.bind(this));
        }
    }
    // attach the blur event on the tile to hide the actions when it looses the focus
    this.$().blur(function(){
        for(var i in this.aTileOptions){
            var elem = this.$().find("#"+this.aTileOptions[i].sId);
            if(elem){
                elem.addClass("actionHidden");
            }
        }
    }.bind(this));
}

Now we just need to trigger the actionPress event declared in the control metadata and we will have a control ready to use. The actionPress event declared in the control metadata will be fired by the function actionClicked used by the onClick of the action. The JSON object used as parameter will contain the actionTag that you will have associated to the action definition... See point 2 bellow.


actionClicked:function(oParams){
    for(var i in oParams.tile.aTileOptions){
        if(oParams.tile.aTileOptions[i].sId === oParams.actionId){
            oParams.action = { actionTag : oParams.tile.aTileOptions[i].actionTag};
            break;
        }
    }
    this.fireActionPress(oParams);
}

1.2) The CSS file

The CSS file used here is pretty straightforward as it's all CSS3 classes used for the control rendering.

Please note the "defaultActionClass" which is the class used by default if you don't specify any for the tiles or for the tile actions.


.defaultActionClass{
color: #007cc0;
border: 1px solid #007cc0;
background-color: rgb(255, 255, 255);
}

2) Using the control

2.1) The view

The roundedActionTiles are meant to be placed in a standard TileContainer (sap.m.TileContainer) that you need to declare in the view.


this.oTilesContainer =new sap.m.TileContainer();
this.oTilesContainer.setHeight("100%");
this.oTilesContainer.setVisible(true);
return new sap.m.Page({
  title : "Rounded Actions Tiles",
  enableScrolling : false,
  content : [
    this.oTilesContainer
  ]
});

2.2) The controller

In the controller, load the control using the "require"


jQuery.sap.require("controls.roundedActionTile.RoundedActionTile");

Assuming that you have declared (or loaded from your backend) a structure as follow:


tiles : [
  {
    title : "Tile 1",
    icon : "Chart-Tree-Map",
    options : [
      {
        title : "Add",
        icon : "add",
        cssClass: "exampleClass1",
        actionTag: "Tile 1 - Action 1"
      }, {
        title : "Browse",
        icon : "search",
        actionTag: "Tile 1 - Action 2"
      }
    ]
  },
  ...

You just need to create the controls as:


for ( var c in this.tiles) {
  var tileItem = new controls.RoundedActionTile();
  tileItem.setTitle(this.tiles[c]["title"]);
  tileItem.setIcon("sap-icon://" + this.tiles[c]["icon"]);
  if (this.tiles[c]["cssClass"])
    tileItem.setCssClass(this.tiles[c]["cssClass"]);
  if (this.tiles[c]["iconColor"])
    tileItem.setIconColor(this.tiles[c]["iconColor"]);
  if (this.tiles[c]["options"]) {
    tileItem.setTileOptions(this.tiles[c]["options"]);
  }
  this.tiles[c].object = tileItem;
  tileItem.attachPress({
    index : c
  }, this.showOptions, this);
  tileItem.attachActionPress(this.handleAction, this);
  this.getView().oTilesContainer.addTile(tileItem);
}

and the rounded tiles with actions will be created.

Of course in your controller you need a method to handle the click on tiles to show the actions or, if no action has been defined on the tile, do what you need to do upon clicking on a tile... This function will use the "showActions" function defined on the control (as described in 1.1)


showOptions : function(oEvent, oParams) {
  //Hide the visible actions (if any)
  for ( var c in this.tiles) {
    this.tiles[c].object.showActions(false);
  }
  // if the tile has actions, show them, else handle the "normal tile click"
  if(this.tiles[oParams.index]["options"])
    this.tiles[oParams.index].object.showActions(true);
  else
    this.handleAction(oEvent, oParams);
}

And then, last part of the code is to do the actual processing (navigate to a view, ...) upon clicking an action or a tile without action


handleAction : function(oEvent, oParams) {
  if(oEvent.getParameter('action'))
    alert(oEvent.getParameter('action').actionTag);
  else
    alert(oEvent.getParameter('id'));
}

I hope you will enjoy this control, let me know your thoughts and comments :smile:

Happy coding :wink:

Link to the GitHub repository: dafooz/roundedActionTilesUI5 · GitHub

Link to the JSFiddle for a live demo: Rounded Actions Tile in UI5 - JSFiddle (Note that the code has been slightly modified in the fiddle in order to have all the Javascript inline)

9 Comments
Labels in this area