# felt fragments
(a kind of ontological ouroboros)

7 Guis in ClojureScript: Timer, CRUD, Circle Drawer

This post is part 3 of a series:


Timer

Reading the description of the Timer task, I started worrying about the concurrency aspect. Will I need to pull some dependencies to deal with it, or will it be a small mess of timeout IDs floating around in the model? The progress bar and range slider had me worried too.

Timeouts or interval

The first thing I wanted to solve for this task was a timer that I could start and stop.

I had two options: a tick function that runs the desired action and optionally schedules itself to run in the future, or an interval that runs the desired action. The interval would be created and destroyed to start and stop the timer.

I wanted to keep things simple, so I tried the tick function approach first. I had a straightforward data model with the elapsed time and the timer duration:

(def state (r/atom {:elapsed 0 :duration 17}))

To start the timer, we call tick. Every time tick runs, it increments :elapsed. The ticking function has some simple logic that checks if :elapsed is less than :duration and schedules itself to run a second later. As soon as the elapsed time is equal to or greater than the duration, the function will not schedule another run and the timer stops.

To debug the timer, I created a simple component to display the value of :elapsed and added it to the app.

I tried the tick functionality in the REPL a few times, and changed the value of :duration while the timer was running, and everything was working.

What about restarting the timer?

The next thing I wanted to have was a “reset” event that I could call when the code was reloaded in the browser. This functionality required a bit more than just setting :elapsed to 0. I couldn’t just call tick on reload because another call might be already scheduled. My first idea was to add a :running? flag to the model, and toggle that in a start function. It would be toggled again in tick when the elapsed time was equal to or greater than the desired duration. It turned out to be an easy and simple solution: to reset the timer, I just needed to set :elapsed to 0 and call start.

I guessed that I had about half of the functionality ready at this point, and I hadn’t run into any problems. I was feeling good!

Competing signals

When I started, I was worried about coordinating the ticking timer and the user changing the timer’s duration. The way I had implemented the functionality made this trivial: change the value of :duration in the model and call start.

At this point, I was so glad that everything was going so well that I spent a bit of time changing the tick interval from 1 second to 100 milliseconds.

Fancier user controls and a pleasant surprise

I knew about the new(ish) built-in range slider and progress bar controls, but I had never used them. In my previous work experience, I always had an SDK with custom controls, following specific guidelines and visual style particular to the app I was building.

I didn’t want to bring any external dependencies yet, so I was hoping that the built-in controls would be enough, and they were. I implemented and wired up the progress display and the range slider and quickly completed the task.

Faster than expected

I was able to code a straightforward solution relatively quickly. I was expecting at least some trouble with timers, but I had none.

The expectations for this task were to test a timer process that updates the elapsed time and runs concurrently with the user’s interactions while being performant and making clear that the signal is a timer tick. I think my solution fulfils all of these requirements. The code is here.


CRUD

The next task, CRUD, seemed straightforward. Many applications are a more complicated version of this exercise: you have a list of something, and you need to add new things, edit the existing information and finally delete some of it.

A new challenge

I got to work confidently and quickly realised that this exercise had a novel component: the list of people can be filtered.

The first thing that came to mind was maintaining a filtered list of people, updated every time the full list or the filter changed. I gave it a little more thought and decided against it because I didn’t want to keep two separate lists in sync.

The next idea was to have a function that generated the filtered list and use that as the source for the UI component instead. I wasn’t sure it would work, and I was a bit surprised when it did. It then dawned on me that something like @db/state is just a reader macro for (deref db/state), so it’s just a function call! The only downside is that the filtering function is called every time anything in the state changes, which is inefficient. I could solve this by having another atom just for the list of people and the filter prefix, but I decided not to spend time on that.

The joy of Flexbox

The rest of the exercise felt very easy. The spec mentioned the challenge of “building a non-trivial layout”, but thanks to Flexbox, that’s (usually) not a problem. I haven’t tried the latest layout engine added to the browser: CSS Grid, but it didn’t seem necessary this time.

An issue with <select>

I spent some time trying to figure out why sometimes when I selected a person from the list, the change event wasn’t fired. To reproduce this behaviour, you need to:

  • Select a person on the list
  • Focus the “Filter Prefix” field
  • The selected person stays selected, but the list is no longer focused.
  • Filter the list, making sure the selected person stays visible (the filtering will clear the “Forename” and “Surname” fields).
  • Click on the selected person again: the change event won’t fire, and the “Forename” and “Surname” fields remain empty.

I tried a few things on my code, but this is a known issue with React’s Select.

In the end, the exercise was less complicated than I expected. I remember using very rigid patterns of MVC back in the Adobe Flex + Cairngorm days, but none of that felt necessary this time. The pattern I’ve been using of defining events that mutate the state directly and dereferencing the state directly from the views was enough.

Check out the app and the code. I think my implementation is easy to follow and fulfils the requirements spelt out in the 7 GUIs spec.


Circle Drawer

Coming soon…