How to add dynamic CSS using databinding in SAPUI5
The requirement
A common requirement that I get in UI5 is to make some sort of visual change based on data in UI5, this is quite commonly to change the colour of a text control but it can be so much more. In this post I'm going to go over what I think is the simplest way to apply this using noting but direct model binding and CSS.
"But that should be easy" I hear you cry, would we not just directly apply the class by binding to the "class" attribute? That would be easy and sadly isn't something that UI5 currently supports.
There are other controls that do support coloured text through binding such as the ObjectStatus control but they only allow for basic states. We might want to add in something more complex than coloured text such as a coloured circle or "traffic lights" as my functional consultant will often say in their quest to make the UI5 app look like the backend.
The solutions
There are various solutions to this problem as a whole, I will go over the most common solution that I see given on the internet which I think is a bit rubbish and then my own solution. Though when I say 'my own solution' I'm not re-inventing any sort of wheel here as CSS query selectors have been a thing for a very long time.
There are alternatives to even the solutions I'm going to show here, if you've got anything better/ a different take then please let me know in the comments! I've had a colleague extend controls and then have them re-render when the class attribute gets updated with data binding which I don't disagree with entirely but I find it a bit overkill for this requirement.
Let's assume that I have some data in a named model called "myData" and the key is called "colour" and inside of this key I'm going to have a value of either "red", "green" or "yellow".
Inside of our CSS file we're going to have the following classes starting out:
.red{
color: red;
}
.green{
color: green;
}
.yellow{
color: yellow;
}
The controller solution
The controller solution is honestly my least favourite solution but it is the mostly commonly pasted around that I see and so I thought I'd put it here so we can talk about the benefits and drawbacks.
Assuming that we have an XML view with a text control our XML will likely look something like this:
<Text id="myTextControl" text="{myData>/colour}" />
I've given it an ID of 'myTextControl' because we will want to access this control in our controller and so this is a relatively common way to do so. (note: I don't really see that an id is necessary for most controls in UI5 and so I advice against unnecessary use of id)
Inside of our controller we need to find an appropriate place (maybe the .then of our promise) to manipulate our control and add the style class as seen below.
var myDataModel = this.getView().getModel("myData");
var colourOfText = myDataModel.getProperty("/colour");
var textControl = this.getView().ById("myTextControl");
textControl.addStyleClass(colourOfText);
I really do not like this solution, it requires me to get my data from my model or do this from inside of our promise .then function and if placed elsewhere need to make sure our data exists.
If we change our data then we need to again update our style classes by removing old one's and adding the new one.
Overall It adds an extra layer of complexity/ values to keep track of and does not scale/ work for something like if this. Additionally what if the control was inside an items aggregation? It might work for your use case but only in the most basic examples and shouldn't be used for real production applications.
Not to mention that we're doing styling inside of our controller which isn't ideal and breaks our general convention of MVC.
The custom data solution
This is my favourite solution, it requires very little effort, works with 2 way binding and overall is what I suggest that people do when asked about this problem which has happened rather consistently over the years.
First of all we need to include some the sap.ui.core in our XML view, do this at the top of your XML view and you might already have it added but if not add this line next to the rest:
xmlns:core="sap.ui.core"
Next we need to add some custom data in our XML, this will bring our XML upto speed to look like the following:
<Text text="{myData>/colour}">
<customData>
<core:CustomData key="colour" value="{myData>/colour}" writeToDom="true" />
</customData>
</Text>
^^Note that it's important to include the attribute "writeToDom" here as this will actually write our custom data attribute to the DOM so that our browser and therefore more importantly our CSS can see it.
Additionally we need to import the core to our XML with the following definition 'xmlns:core="sap.ui.core"' if you haven't already made use of the core in your current view.
Lastly we need to adjust/ change the names of our CSS classes:
[data-colour="red"]{
color: red;
}
[data-colour="green"]{
color: green;
}
[data-colour="yellow"]{
color: yellow;
}
Then boom we can have something that looks like this (with some extra CSS for our circles):
or as to prove a point using two way data binding we could have something like this:
This solution as I said above is great, while it does make our XML a bit bigger that is exactly where we should be placing logic relating to the view of our UI5 controls.
The downside to this is that it might not be entirely intuitive to those unfamiliar with the DOM and CSS and the fact that we have to specifically name our classes based on our data/expected data. That's honestly a rather minor issue and you can namespace/ add in more complex keys to make sure you don't bleed into other applications CSS when dealing with deployments on the Fiori launchpad.
Conclusion
There we have it, a quite robust and easy to implement solution to changing colour/ applying custom CSS to your UI5 applications via either the controller (not recommended) or via the use of custom data which I personally recommend/ prefer.
This isn't to say the only solution to this sort of problem but it's the one that I've found to be the most effective and easiest to implement. Feel free to follow me on Twitter for more blog post updates, ramblings and the occasional photo of food :D
footnote
25/11/2021: Today while using this same method I noted that the use of a boolean in our "value" did not seem to render/ work properly. If you are trying this method with a boolean, this might be the cause and I suggest you try and just use a simple string instead.
···