Pomodoro Clock (FCC Speedrun Project #5)
The Pomodoro Technique and I don’t get along very well — You’re supposed to work for 25 minutes, then take a 5-minute break. But by the time I’ve been working on a project for 25 minutes, I’m either annoyed being interrupted by the timer, or else annoyed at the fact that I’m expected to content myself with a mere 5-minute break. What are you supposed to be able to do in just 5 minutes?!
Making a pomodoro clock, on the other hand, turns out to be pretty fun. This project took me a little bit longer than the past few — but I also ended up with a product that just might convince me to give the Pomodoro Technique another try.
Reflections
I haven’t committed to a front-end framework yet, so I ended up using plain JavaScript for this project. That made interacting with the DOM a bit more challenging, but I also understand the DOM a bit better than before.
Other than that, this project included a little bit of a lot of things, from CSS layout difficulties and event delegation all the way to asynchronicity.
Figuring out layout:
Starting out, I wrote a “first draft” for a JavaScript timer before I even looked at the layout for this project, but I eventually reached a point where I needed to be able to see what I was doing. So I took a break and switched over to HTML and CSS:
There’s a little bit of interactivity — clicking the play button starts the timer, and clicking the settings icon gives you the ability to switch between pomodoros and breaks, and to adjust the length of each pomodoro or break period.
Even with this simple layout, I did run into a couple of snags. Fortunately, when my inline-block elements just wouldn’t play nice, I thought back to something that I had read in Shay Howe’s series Learn to Code HTML & CSS. He describes a common “gotcha” that can occur when working with inline-block elements:
Remember, because inline-block elements are displayed on the same line as one another, they include a single space between them. When the size of each single space is added to the
width
and horizontalmargin
values of all the elements in the row, the total width [can become] too great, pushing the last […] element to a new row.
When I first tried to lay out the settings in my HTML, things were overlapping and being pushed onto new lines:
Once I’d identified the problem, I was able to use HTML comments to remove blank spaces between elements:
After that fix, things were lining up much more nicely:
Event Delegation (sans jQuery):
If you took a close look at the code snippet above, you might have noticed that almost every element has its own unique id. I’m not going to pretend that’s a good thing. I’ll just say that trying to identify and modify HTML elements without jQuery turned out to be a bit more difficult than I had anticipated.
On the other hand, I did learn to use event delegation. (I’d done it before with jQuery, but never thought too much about how it works, or about trying to use it with “just” JavaScript.) Instead of attaching a different event listener to every clickable item in my settings panel, I used just one event listener, and then used event delegation to figure out which item was actually clicked:
I can almost guarantee that there are better ways to do this — ways that don’t involve attaching ids to everything and then going and chopping them up inside of your JavaScript. But it’s the idea that I’m focusing on here, because it’s a kind of neat one:
Clicking on any element on a page triggers an “event”. And that click “event” will travel up through the structure of the HTML document from element to parent element to parent element until some event listener “hears” it and does something about it.
In this case, I am using one event listener for the whole settings panel. If I click on something within my settings panel — say I go to increase the length of my “break” timer to 6 minutes — that triggers a click event. There’s no event listener on that <i> element to hear it, but the event is still triggered. That event then travels to the parent (a div containing the time and arrow icons), and then to that element’s parent (the ‘Breaktime’ row in my settings panel), and then to that element’s parent (the settings panel itself). Finally, my event listener “hears” the event and reports back that, “Hey, someone just clicked on the ‘breaktime-incr’ icon!”
Fun stuff!
Asynchronicity
Which brings us to asynchronicity. I like asynchronicity, but I like it better when I actually understand what it’s doing and am leveraging it for my own purposes. In this project, I think it got the better of me.
I was using intervals — asynchronicity — to calculate and update the amount of time elapsed, like so:
There might not be enough context to get exactly what’s going on from just this code snippet — but the gist of it is this: I have a function that calculates how much time is left on the timer (using JavaScript’s Date object)— and then calls a function “cb” (for callback) to update the clock and report whether the timer is done running. And this function is set up to fire about 50 times a second (or so — more on THAT in a bit).
I also have a function elsewhere in my code that “pauses” the countdown timer. If I call that “pause” function, then the next time my 50-times-a-second function fires, it will cancel the interval, and stop the timer. And that’s that.
Except when it isn’t.
Because the timer doesn’t stop immediately. There’s a space of time — probably around 1/50 of a second — between when I tell my “pause” function to stop the timer, and when the timer actually stops… and it turns out that that 1/50th of a second — 20 milliseconds — is PLENTY long enough to cause problems.
There was one point in my code where I was trying to (1) cancel the ‘pomodoro’ timer, (2) reset the ‘pomodoro’ timer, and (3) switch over to the ‘breaktime’ timer. I could cancel the pomodoro timer and set the time remaining back to 25 minutes just fine. But the switch over to the ‘breaktime’ timer just wasn’t working. Sometimes I could even SEE the clock switching over to 5 minutes, for just a fraction of second — but then it would (stubbornly!) blip back to 25 minutes. Not quite what I was going for.
What was happening? After I canceled the countdown timer, the ‘setInterval’ function didn’t “order/request” any more calls of the function inside , the one that calculated the time remaining and updated the display. But I still had to wait — for maybe 20 milliseconds — for the already-ordered function calls to be carried out.
I was in fact switching the display over to “breaktime” — but I was doing it too quickly, before the timer had time to stop running! This isn’t the greatest solution to the problem, but it did work:
Basically, I’m waiting around until I’m pretty sure that my last function call should have run, and then updating the timer and display AFTER that.
Dates and Intervals
Speaking of intervals… The first time I tried to implement a timer (quite a while back), I used ‘setInterval’: I set it to fire once a second and figured that after it had fired 60 times, one minute would have passed.
I misunderstood how the ‘setInterval’ function in particular — and asynchronous functions in general — actually work. When you use setInterval to call a function once per second, what you’re actually doing is calling the function at most once per second. Basically, ‘setInterval’ waits a second — or however long — and then your function gets put at the back of line, to be executed whenever your computer gets around to it. Sometimes it’s right away. Sometimes it isn’t.
If your computer is busy, it might take longer than normal to run. Your browser might also limit how often ‘setInterval’ can fire, especially on out-of-focus tabs. Look what happens in Chrome when I switch to a different tab:
What’s going on here? Each time my 50-times-a-second function gets called, it uses Date() to compare the current time to the starting time. Starting out, the function is being called about 50 times a second, and the same time is being logged to the console around 50 times in a row. As you would expect.
But when I navigate to a different tab, and leave my timer running in the background, my function is only getting called about once per second! This is a feature of Chrome — they limit setInterval to firing once per second in an inactive tab, and limit resources to background tabs in general. So that’s something else to be aware about when using functions like ‘setInterval’ and ‘setTimeout’.
This time around, I still used setInterval… but I used it differently. Instead of using setInterval to count seconds, I used setInterval to make repeated calls to a function that then calculated the number of seconds that had passed by comparing the time at which my timer started (or was resumed from pause) and the current time.
Closing thoughts
I’m still not sure I’m sold on the idea of using Pomodoro Timers , but I sure learned a lot making this one! I’m looking forward to taking a more proactive approach to managing asynchronicity in my next few projects.
Next up: Local Weather