Technical Articles
UI5 Tips: Manipulating the sap.m.TabContainer close buttons with custom CSS
Here’s a ui5tip to show how you can change the look and feel of the sap.m.TabContainer
with a minimal amount of custom CSS. If you want to try this for yourself, be sure to check out the sample application from github.
The sap.m.TabContainer
The sap.m.TabContainer
provides a simple, no-nonsense widget to build a tabbed user interface (check out the samples). Tabs can be added via the items aggregation, which should contain a collection of sap.m.TabContainerItem
‘s.
While this control generally suits my needs, it has one feature I find problematic: each tabs always has a close button, which appears as a little ‘X’ icon in the right side of the tab. If the user clicks it, it will actually ‘close’ the tab, that is: the respective sap.m.TabContainerItem
will be removed from the TabContainer.
See the screenshot below to see what the default looks like (close buttons highligthed in red):
Suppressing the close action
The openui5 samples show how you can suppress that behavior: you can write an event handler for the itemClose
event, and then call the preventDefault()
method on the event:
(In the view xml:)
<m:TabContainer itemClose="onTabContainerItemClose"> <m:items> <m:TabContainerItem> ... </m:TabContainerItem> <m:TabContainerItem> ... </m:TabContainerItem> </m:items> </m:TabContainer>
(In the controller javascript:)
onTabContainerItemClose: function(event){ event.preventDefault(); }
Obviously, it would be strange if we’d always prevent the tab from being closed: Suppressing the default action of closing the tab only makes sense in a context where the user is supposed to be able to close the tab at all, and in such a case this could be used to pop up a dialog to let the user choose if they really meant to close the tab or want to keep it open.
But the use case I frequently encounter is that the tab should not be closeable in the first place. While suppressing the close action would ensure the tab is never closed, it would confuse and anger the user, as the close button itself would still be there, inviting users to perform an action that can never be fulfilled.
Using the other Tab widget
One might suggest to use the sap.m.IconTabBar
widget instead of the sap.m.TabContainer
. The sap.m.IconTabBar
takes sap.m.IconTabFilter
‘s in its items collection, and these do not have a close button.
Now, in some cases the sap.m.IconTabBar
/sap.m.IconTabFilter
may suit your needs and then you’re fine. However I find that it has a number of other drawbacks (which I won’t get into here).
Besides, the sap.m.IconTabBar
introduces a similar problem, but the other way around: whereas we cannot get rid of the close button in the sap.m.TabContainer
, we cannot ever have a close button in the sap.m.IconTabBar
. What we really want, is a property or something like that, which will let us control whether the tab will have a close button or not.
CSS to the rescue
In the previous section we argued that we’d really like to be able to control for each individual tab whether they have a close action at all, for example, by setting a property.
To add a property one would normally have to extend a ui5 control, and attach some code so that the property setting can somehow influence the behavior of the control – in this case, control whether or not the close button will be displayed. While this is probably possible (I haven’t tried it for this case), it does seem like an extraordinary measure for such a humble request.
I found that a similar effect can be achieved by applying some custom CSS in combination with standard ui5 features. That’s what this entire sample is about.
With this tip you can:
- hide all close buttons for an entire
sap.m.TabContainer
- hide the close button on an individual
sap.m.TabContainerItem
- show the close button on an individual
sap.m.TabContainerItem
in case the close buttons are hidden by default on thesap.m.TabContainer
All this functionality requires the inclusion of some css. In the sample this is all isolated in a single ui5-customization.css
file, which is included into the application by declaring it the manifest.json.
Hiding all close buttons
To hide all the close buttons for all sap.m.TabContainerItem
in the items collection of a particular sap.m.TabContainer
, simply add the noCloseButtons
style class:
<m:TabContainer class="noCloseButtons" > <m:items> <!-- note: close button will be hidden by default for each m:TabContainerItem --> ... </m:items> </m:TabContainer>
This works because the class
property in the ui5 xml view is rendered to the html dom directly. So whatever html elements that ui5 creates to implement the TabControl widget will then be selectable with a class selector in css, and this is how we can relatively simply influence the look of our TabContainer through css.
In our ui5-customization.css
file, this is how we use noCloseButtons
class to hide the buttons:
div.sapMTabContainer.noCloseButtons > .sapMTabStripContainer > .sapMTabStrip > .sapMTSTabsContainer > .sapMTSTabs > .sapMTabStripItem > .sapMTSItemCloseBtnCnt { visibility: hidden; }
(Note the initial selector, div.sapMTabContainer.
noCloseButtons
and the chain of >
child selectors which target the actual bit of html that is used to render the close button, which is simply hidden by setting the css visibility
property to hidden
.)
In the sample application, you can see this behavior in action in the App.view.xml. This contains the code for the outermost tabcontainer. A screenshot is shown below and as you can see both tabs (“Hide individually” and “Show individually”) do not have a close button:
Hiding an individual close button
If we can add a custom css class to sap.m.TabContainer
to hide all close buttons, then surely it should be possible to follow the same approach for an individual sap.m.TabContainerItem
, right? Yes, it should, but sadly, we cannot. (The reason is that sap.m.TabContainer
is a subclass of sap.m.Control
, which provides a addStyleClass()
method, whereas sap.m.TabContainerItem
is a subclass of sap.ui.core.Element
, which does not have such a method)
Now, let’s take a step back and think about how we used the css style class on the sap.m.TabContainer
to hide all the close buttons. By setting the custom css style class on the sap.m.TabContainer
, the html dom was changed to include the custom class, and we could then use that in a css selector.
So even if we cannot apply a css style class to a sap.m.TabContainerItem
, might there be another way that would allow us to influence how ui5 writes the html dom so we may write a css selector in our custom css? It turns out that such a feature exists in the shape of a feature called ui5 custom data.
The custom data aggregation is provided by sap.ui.core.Element
and thus available to its subclasses, including sap.m.TabContainerItem
. A custom data item is an arbitrary key/value pair, and by setting its writeToDom
property, ui5 will render it to the html dom as a html data attribute.
To see what it looks like in our sample, take a look at TabContainerItemWithHiddenCloseButton.fragent.xml
, which uses it to hide the close button in the second sap.m.TabContainerItem
, in an otherwise normal sap.m.TabContainer
:
<m:TabContainerItem id="item2" name="No Close Button" > <m:customData> <core:CustomData writeToDom="true" key="noCloseButton" value="true"/> </m:customData> ... </m:TabContainerItem>
Because the CusomtData
‘s writeToDom
property is set to true
, ui5 will generate a html data attribute to the html dom that looks something like this:
data-noclosebutton='true'
And in our ui5-customization.css
file, the following rule is intended to pick that up and hide the close button:
div.sapMTabContainer > .sapMTabStripContainer > .sapMTabStrip > .sapMTSTabsContainer > .sapMTSTabs > .sapMTabStripItem[data-noclosebutton='true'] > .sapMTSItemCloseBtnCnt { visibility: hidden; }
As you can see it is very similar to the rule we used to hide all close buttons on any sap.m.TabContainer
having the noCloseButtons
class, except now the class is missing and instead we use a css predicate selector based on the data attribute:
.sapMTabStripItem[data-noclosebutton='true']
The screenshot below shows what it looks like in the app. Note that the tab named “Default” has the close button as usual, but the one named “No Close Button – the one with the custom data attribute – does not show a close button:
Showing an individual close button
The final hack in this sample combines the style class and the custom data attribute. CSS allows us to write a selector that takes both the presence of the css style class as well as the presence of a html data attribute into account. We can put this to good use if we want to have a sap.m.TabContainer
that hides all close buttons by default, but undo the hiding of the close button of specific sap.m.TabContainerItem
‘s, based on the value of a data attribute (which is in turn controlled by the ui5 Custom Data feature).
You an see this in action in the TabContainerItemWithHiddenCloseButtons.fragment.xml
file of the example:
<m:TabContainer class="noCloseButtons" > <m:items> <m:TabContainerItem name="Default"> ... </m:TabContainerItem> <m:TabContainerItem name="Show Close Button"> <m:customData> <core:CustomData key="noCloseButton" value="false" writeToDom="true" /> </m:customData> .. </m:TabContainerItem> </m:items> </m:TabContainer>
Again the second sap.m.TabContainerItem
has a CustomData
item with the key noCloseButton
, but now the value is true
so as to override the effect of the noCloseButtons
style class applied to the sap.m.TabContainer
.
In our our ui5-customization.css
file, the following rule is intended to pick that up and show the close button:
div.sapMTabContainer > .sapMTabStripContainer > .sapMTabStrip > .sapMTSTabsContainer > .sapMTSTabs > .sapMTabStripItem[data-noclosebutton='false'] > .sapMTSItemCloseBtnCnt { visibility: visible; }
The screenshot below shows what it looks like when you run the sample application:
Finally
Did you like this tip? Do you have a better tip? Feel free to post a comment and share your approach to the same or similar problem.
Want more tips? Find other posts with the ui5tips tag!