I wrote before about a simple web app I created to teach my son maths. You can find the article here, the code here, and the live app here.
Looking at the code, you might wonder: why would you require server round-trip for every new card? Why can’t you just generate the next set of random dots on the client side?
I had the same thought, so I set about translating the code to client-side JavaScript. If you’re impatient, you can see the result here. If you want to know what I did, it was mostly these pretty painless steps:
- Paste the existing code into a
<SCRIPT>
tag in an HTML file - Add parentheses around
if
conditions - Change the format of function definitions and if clauses, by using
{}
instead of:
- Convert list comprehensions into
for
loops - Instead of assembling the DOM using string interpolation, do it piece by piece using
document.createElementNS()
and.setAttribute()
- Replace some
random.normalvariate(0,1)
withMath.random() * 2 - 1
, which doesn’t do the same thing, but works well enough - A very small amount of other cleanup
The above was enough to make the app work, but I was unhappy with needing to enter the min/max values in the URL. So I also used the excellent Tailwind CSS to add a footer and some buttons to increment/decrement these values. I wanted two-way binding between the values shown in the UI, and the values in the URL. Using a sophisticated mechanism (or a library) for such a simple app would be overkill, so I wrote some ugly code instead:
function get_values_from_hash() {
if (location.hash.length > 0) {
[lower, upper] = location.hash.split("#")[1].split('/');
lower = parseInt(lower);
upper = parseInt(upper);
} else {
[lower, upper] = [1, 5]
}
return [lower, upper]
}
function set_values(lower, upper) {
location.hash = "#" + lower.toString() + "/" + upper.toString();
document.getElementById("lower").innerHTML = lower.toString();
document.getElementById("upper").innerHTML = upper.toString();
}
function adjust_lower(n) {
[lower, upper] = get_values_from_hash();
lower = Math.max(1, lower + n);
upper = Math.max(upper, lower + 4);
set_values(lower, upper);
}
function adjust_upper(n) {
[lower, upper] = get_values_from_hash();
upper = Math.max(5, upper + n);
lower = Math.min(lower, upper - 4);
set_values(lower, upper);
}
Now the code doesn’t need a server, I figured I should make it work offline, and installable as a PWA. It turned out this was pretty simple, using some example service worker code from Google, and adding a simple web manifest and icon.
There’s one thing left to do: make the bottom bar stick to the bottom of the screen. Currently, I’m using the CSS style height: 100vh
via Tailwind’s h-screen
utility class. This works fine on desktop, and when the app is installed and launched via a mobile launcher icon.
But, when the app is opened on a mobile web browser (e.g. Chrome on Android), the visible part of the page is shorter than the screen (due to the address bar and browser navigation bar), so part/all of my bottom bar scrolls off the page. This is a well known problem with using height: 100vh
and there are lots of workarounds. I had a quick look, and none of the workarounds I saw look elegant, so I’ll fix this another day.
The app is at https://dots.twilam.com/. To see the source, just view source in your browser.