Journal
Lazy loading tracked time in the schedule timeline
In the previous post about visualizing tracked time in Cushion’s schedule timeline, I mentioned that I was ready to launch it. That was true at the time, when I was loading all of a user’s time-tracking entries for the timeline, but then I had a thought—maybe I shouldn’t do that. Throughout Cushion’s history, I’ve underestimated how much people use Cushion, and how long they’ve been users, so I’ve been bitten by people who decide to import the thousands of clients from their decades of freelancing, for example (and yes, this really happened). In this case, I know that there are people who’ve been using Cushion’s time-tracking since the day it launched, so it might not be a good idea to load all of their entries since 2017 all at once. Instead, I decided to delay the launch and spend a week building a decent lazy loading solution.
Lazy loading works by only loading content once the user scrolls to it. This is pretty popular (and now easy to do) with images on the web, using <img loading="lazy">
. Lazy loading was also used with infinite scroll back when people thought that was a good idea. In my case, since Cushion is potentially loading hundreds (if not thousands) of time entries, I only want to load the entries that are within the date range that the user is viewing. As soon as the user scrolls to a date range that they haven’t loaded already, Cushion will then load the entries for that date range, and so on. If a user scrolls back to date ranges where they have been before, I don’t want Cushion to load those entries again. Capiche?
I accomplish this by creating a “viewed date range”. When the user lands on the timeline, I set the viewed date range to the currently visible date range and fetch the entries for that range. (I actually pad the date range a few months on both ends, so Cushion won’t need to make a request as soon as the user scrolls a pixel.) From there, if the user scrolls close to the edge of the viewed date range, I expand the viewed date range on the relevant edge and pad it again. Since this behavior doesn’t need to happen on every scroll event, I actually throttle the code to trigger every half second. This helps to batch the request for new entries, instead of firing constantly and taking down Cushion’s servers.
From here, I watch for changes in the viewed date range and only fetch the new date range that has been added—no need to include the already loaded date range. With lazy loading in place, I actually needed an extra safe guard to make it work as expected because the code that draws the path of tracked time assumes that the entries are sorted by date. This helps a lot with performance, as I don’t need to sort them when drawing the path. Unfortunately, with lazy loading, I introduce the chance of certain entries being fetched out of order from the existing sorted entries. To appease the SVG path gods here, I still let the individual project timelines assume that the entries are sorted, but I assign a new chore to the overall timeline to sort all the entries before divvying them up between projects. I don’t love that I need to do this, but it only happens once upon fetching new entries, so I’ll consider this a win.
This wasn’t a meaty post, but I figured this was worthwhile to point out these kind of scenarios. Sometimes you feel like you’re an inch away from launching a new feature, then you realize that it’s not necessarily missing something, but you would feel much better about launching by doing the right thing and including an extra safe guard. In case you were wondering, I feel so much better about this launch because of this.