Spin the Whee- I Mean, the Subtitle Randomizer!

Table of Contents

So if you haven’t noticed, every time you view that main title bar, the subtitle has a little extra tagline on the end of it… sometimes, sometimes it doesn’t. Well, that randomizes on every request. And here, we talk about the smallest thing I’ve made, to date: the tagline picker for that.

The Taglines

Well before we start doing things, I need some taglines to use… at /taglines.txt, there is (at the time of writing) this text file:

I Like to Know Things ;)
If Google was a human...
I bet I'd be good at Jeopardy!...
Experts in computers AND tacky attire!
I do this for fun
Only have to pay one bill... <i>electrical</i>
Objective-C, Objectively bad
class Person extends Nerd
Hosting providers need not apply
There's no place like 127.0.0.1
No need to reboot it... *racks shotgun*
How many different hobbies can one man HAVE?!
Taglines... gotta read 'em all!
Net::ERR_NOTFOUND https://teknikaldomain.me/assumeControlTaglineTrojan.js
I reject your reality, and substitute my own!
POST, dang you!
Windows? Where we're going, we don't need <i>Windows</i>
<a href="https://www.youtube.com/channel/UCXOlekZqsiHRLN6EXBUWRow" target="_blank">I'm on YouTube!</a>
Those letters do have a meaning. Care to figure it out?
I got guns and I got puns, what else do you need to know about me?
AMENDMENT 35: No person shall be found with fewer than thirteen times their own body weight of federally provisioned munitions.
I am a computer GOD!
I <i>am</i> the one who types!
The lord of cheese
<COUNT>

And yes, there’s a few jokes in here, a few references, it’s just me being me, this is what I do.

The Script

Okay, the part that you actually clicked on this for. Well, first things first, here is the actual script:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
function setTagline(taglineRaw) {
    taglines = taglineRaw.split("\n");
    var thisTagline = taglines[Math.floor(Math.random()*taglines.length)];

    // Legit just a workaround for an Atom feature: auto newline at EOF
    // Yeah, just not display anything. It's not a bug, it's a feature, I swear!
    if (thisTagline == "") {
        return;
    }

    if (thisTagline == "<COUNT>") {
        thisTagline = taglines.length + " taglines and counting...";
    }
    subtitle = document.getElementsByClassName("subtitle")[0];
    subtitle.innerHTML = subtitle.innerHTML + " | " + thisTagline;
}

var xmlHttp = new XMLHttpRequest();
xmlHttp.onreadystatechange = function() {
    if (xmlHttp.readyState == 4 && xmlHttp.status == 200)
        setTagline(xmlHttp.responseText);
}

base = window.location.href.match(/http.?:\/\/.*?\//gm)[0];
xmlHttp.open("GET", base + "taglines.txt", true);
xmlHttp.send(null);

There’s two parts, and this script executes the second half before the first. The actual entry point is line 18, creating an XMLHttpRequest. Before we talk about the rest, let’s go over that.

XMLHttpRequest

An XMLHttpRequest, or XHR from this point, is, despite the name, basically a way for JavaScript code on a webpage to make another HTTP request and get the response from it. If you remember my Cloudflare worker, XHR is the web JS equivalent of the fetch() API as was used over there.

Making a request like this has a few steps:

  1. Create request object
  2. Set request method and URL
  3. Perform request
  4. Read and process the results

Okay, with that out of the way, on to the explanation.

Setup

Lines 19-22 we’ll talk about in a bit.

Line 24 is a RegEx (Regular Expression, a very complex and very useful way of parsing through and extracting information out of a pre-formatted string) on window.location.href, which is what the address bar at the top of your browser is saying the current page is. In other words, I’m asking for what page the user is reading this moment. Running a RegEx like this will return an array of matches, which in our case will have only one result: what section of the URL matches this RegEx, and no more. Because we then specifically grab item 0 from this array, which is the first (and only) one, we extract that match, which is the protocol and hostname. This is used in the next line, which is open()ing the XHR.

Line 25 is where we prep the request. The HTTP method is GET, the URL is the hostname from earlier plus taglines.txt. For, say, the article https://teknikaldomain.me/code/full-text-rss-feeds, the base variable will hold https://teknikaldomain.me/, which is why I’m not including protocol (https://) or slash between the host and file, because they’re already included from the match before. The reason I went through the trouble of coding that is so that this code can work both deployed (to teknikaldomain.me), and in dev mode (localhost:1313) as well.

The final true argument tells if this is an asynchronous request. Async requests execute in the background and run a callback function when finished, synchronous requests pause the browser thread until they finish. Now since it’s considered bad form (some browsers disallow it entirely) to do synchronous work on the browser main thread (which this will run on), we async it. Because of this, we still need some way for the request to communicate that it’s working or has finished, and the usual method of doing this is having the async part attempt to execute a common and well-known function, if one exists.

These are called events, and the function that is run is called a callback (or event handler), since the async code calls back to the main code to tell it something. A very common pattern in event-driven programming is to start the name of the handler with on, followed by the particular thing that happened. For example, OnCompleted, OnSocketConnect, OnProcessCompleted, etc. XHRs have a few events that trigger in their life, one is onreadystatechange, which, as the name implies, is fired when the ready state changes, which is a number from 0 to 4 indicating the current status of that XHR. I register this with a function that makes sure that the ready state is 4 (sent, got response, all done), and the status response is 200 (OK), then we call the actual routine: setTagline() with the response (which is the list of taglines.)

Finally in our prep, line 26 is what actually sends the request, with no data at all in said request, since this is a GET request. GET requests ask the server for something, meaning 99% of the time, we aren’t sending it anything ourselves.

setTagline()

First, we split the file data into an array on lines. After this, a random item is picked with no weighting, meaning an even distribution, using Math.rand(), which returns a floating-point value between 0 and 1. Multiplying this by the number of items in the list means that it will always randomly select an item from the list with no missed spots.

After that, my totally real and planned feature, the final line of taglines.txt is blank, thus giving a 1/n chance of having no extra tagline at all.1

After that, the singular line in there that is <COUNT> is a special token that is replaced with an X taglines and counting... string, that shows the number of lines in the list. The reason I used a token like that is so that this one has the same probability of occurring as the rest of the lines.

Finally, we set it. First, the subtitle itself belongs to a CSS class, subtitle, and it the singular element of that class. Therefore, i can get the list of elements in that class with document.getElementsByClassName(), and the first one (element 0) will be my subtitle. once I see this, i replace it’s entire contents with.. it’s entire contents plus a separator and the selected line. The benefit of just replacing everything is that i can insert raw HTML into the lines, and that will get parsed, which is why some have formatting, and one is actually a clickable link.

This code runs pretty much on every page, and since every page has this construct as the header, every time you navigate around or refresh the page, it’ll re-roll what you’re seeing.

Trust me, the list shown in this post will be obsolete soon, once it does, can you see if you can find them all? (Opening dev tools or typing in the URL directly to view the raw list don’t count!) Bonus challenge, how many do you recognize?


  1. Because that’s totally not a workaround for the fact that every editor I use will automatically insert a new line on saving a file. ↩︎