How an issue in Calendar ui.integration.cards turned out to be a feature. Kind of
In my previous blog I wrote about my experience using the integration cards.
And I mentioned an issue that sometimes prevented appointments from being displayed in the Calendar timeline.
So, today we are going to try to investigate the issue diving deeper into the ui5 integration library (and beyond) code itself, and see if we can understand what causes such behaviour and how to probably fix it.
TL;DR As you could guess from the blog title, it turned out to be “an expected behaviour” but still with a possible bug in its implementation. As I did not care about the “intended” behaviour, I just patched the stuff to do what I needed.
And in case you’re still interested in some details, let’s go to the issue investigation and fixing.
Suppose we have a Calendar card and some appointments we would like to display in it
In normal case it would look like this:
As you can notice, the selected date is not Today (10th of October has a border around the date)
The orange stuff below some dates is called specialDates, and it is calculated in my CardExtension code, so as we have it for some particular date we would assume there’s at least one appointment for that date.
So, what happens if we select Today and expect to see some appointments?
Well, it turns out, that nothing is displayed for some reason:
How could that be?
Well, it looks like that in some certain cases the appointments for Today (and only for Today) are not rendered.
Obviously that does not mean it always hides Today’s appointments as it would clearly be a bug, that would have been fixed fast.
So, there must be some certain condition which would make appointments disappear.
To be honest, after I found out the solution, I was kinda upset I didn’t get it from the start, but anyway, let’s see how we can find it.
The First and the most important point and key takeaway – always use UI5 Diagnostics tool and Control Tree to understand whats going on on the ui5 side.
So, let’s see the controls that are involved in our case:
Ok, what does it say to us?
There’s that guy sap.ui.integration.cards.CalendarContent which we of course expect to see.
It has some content inside, and we are interested in the sap.f.PlanningCalendarInCard which extends sap.m.PlanningCalendar
PlanningCalendar seem to have some internal table to handle rows aggregation, and in our case we see that there’s a sap.m.internal.PlanningCalendarRowListItem which serves as a container to our row elements and extends regular sap.m.ColumnListItem.
The actual guy that is responsible for the displayed appointments seem to be PlanningCalendarRowTimeline which extends sap.ui.unified.CalendarRow.
At this point we could just assume that we need to go to sap.ui.unified.CalendarRowRenderer to find some clues (and we would be right about that), but lets go the long way.
Lets see the actual code.
The second point – make sure you toggle Debug sources in Diagnostics tool or simply add sap-ui-debug=true to local storage so that you can see beautiful -dbg.js sources in your browser’s Developer Tools instead of obfuscated ones (the switch forces ui5 core to load debug sources).
Indeed we can see that CalendarContent has a PlanningCalendarInCard inside
What’s important here is that the inside the rows aggregation we put some sap.f.PlanningCalendarInCardRow (which extends sap.m.PlanningCalendarRow). Remember this guy, it will eventually come back to us.
OK, but what’s next?
Well, we will soon find out that there’s nothing interesting in sap.f.PlanningCalendarInCard implementation, so we go directly to sap.m.PlanningCalendar
There we can of course find a proof that addRow function creates some PlanningCalendarRowListItem as expected
The definition is also expected to be:
Ok, now let’s see what this PlanningCalendarRowTimeline happens to be?
It turns out that it is declared inside the PlanningCalendar together with its renderer:
And indeed it (renderer) extends sap.ui.unified.CalendarRowRenderer
Ok, so let finally go there and see what happens
The thing we are looking for is renderAppointments which in turn for our case calls renderSingleDayInterval function.
And this is where we can guess we have found something:
So, what’s happening here is after we got some appointments, we want to adjust start and end indices of rendered ones based on results of some function _calculateVisibleAppointments.
But where are we going to find it?
Well, I told you to remember that PlanningCalendarInCardRow guy – and here he comes.
Indeed we can find this function there:
And as soon as you would see that if (bToday) expression, you would almost definitely shout “Bingo!”
Here’s the link to the implementation in 1.82.1 release I use in my solution.
From what we can understand the idea is that for current day it tries to calculate the “upcoming” appointments (iNearestAppointmentIndex), and in case there are none, it makes sure nothing is rendered at all.
I cannot say what would require this behaviour as for the other days all appointments are always displayed, but you can clearly see they extended the regular sap.m.PlanningCalendarRow exactly for this reason.
And I actually think there still is a bug, because when there’s at least one upcoming (unfinished to be more precise) appointment, today’s appointments are still displayed starting from the index 0, while I would probably expect it to “hide” them.
Anyway, as we now have found out everything we wanted, let’s fix” that.
But how are we going to change the SAP provided source framework code?
That means we can make our code override some object’s prototype on the fly! Isn’t it awesome?! 🙂
So, in my case I found the CardExtension to be the most appropriate place to patch that implementation to suit my needs:
You can also notice I overrode _getMoreButton implementation because the original author was lazy to include i18n stuff there.
You can read more about that AppMgr thing in my previous blog as well.
Et voila, we can see that this works as expected:
So again, here are the key takeaways (which are pretty obvious actually):
- Remember that you can override the object prototypes on the fly
- But you have to make sure to patch the stuff before it is going to be instantiated (but actually function being called)
- And unfortunately sometimes some objects would be “invisible” from the global scope
- Use the UI5 Diagnostics Tool and your browser Dev Tools to find the stuff you need to override
- Make sure to toggle debug sources for you convenience
Thanks for reading and good luck in your projects.