Using JSONFeed Support To Drive An On This Day Module

I've been a bit un-anchored this weekend: although it's felt like I should do something, I've not really known what to do with myself.

After a bit of aimless wandering online, I decided to build an "On this day" module for my site: something which automatically displays and links to old posts published on the same day of the year (which, at time of writing, is 6 Oct).

It came as much from idle curiosity as anything. Whilst I'm not the most prolific blogger out there, there are nearly 20 years of posts, so surely any given day should have a reasonable chance of matching at least one older post.

This post talks about building a small module which consumes a generic JSONFeed format listing.


Site Plugin

The first thing that I needed was something to expose a JSONFeed.

I use Nikola to manage and publish my site and, helpfully, there's a jsonfeed plugin for it.

There are also JSONFeed plugins for a wide range of other platforms (including Wordpress, Drupal, Hugo and Eleventy) meaning that I don't need to worry about compatibility if I ever fancy a change.

The plugin creates a JSON feed at /feed.json:

{
    "version": "https://jsonfeed.org/version/1.1",
    "user_comment": "This feed allows you to read the posts from this site in any feed reader that supports the JSON Feed format. To add this feed to your reader, copy the following URL — https://www.bentasker.co.uk/feed.json — and add it your reader.",
    "title": "www.bentasker.co.uk",
    "home_page_url": "https://www.bentasker.co.uk/",
    "feed_url": "https://www.bentasker.co.uk/feed.json",
    "description": "The website of Ben Tasker, IT Manager, Linux Specialist and software developer. I'm based in Suffolk in the UK but work with customers worldwide",
    "items": [
        {
            "id": "https://www.bentasker.co.uk/posts/documentation/general/scheduling-recurring-gitlab-tickets.html",
            "url": "/posts/documentation/general/scheduling-recurring-gitlab-tickets.html",
            "title": "Creating Recurring Gitlab Issues",
            "date_published": "2024-09-30T15:55:00+00:00",
            "authors": [
                {
                    "name": "Ben Tasker",
                    "url": "/authors/ben-tasker.html"
                }
            ],
            "author": {

Site Template Change

I made a small change to my site's template to add a div for any output to be written into

<div id="on-this-day"></div>

Technically, I could have injected this from Javascript, but as my other modules all have placeholder div's in the template this kept things consistent.


Javascript

My site is static HTML, so any dynamic population of content has to happen on the client's side.

This is why my template change inserted an empty div rather than including a header tag: Javascript is used to inject the header so that any visitor to my site with javascript disabled won't be presented with a title but no content.

We start with some basic boilerplate:

// Fetch a URL and pass the contents into a callback
function fetchPage(e, t, n) {
    var r;
    (r = window.XMLHttpRequest ? new XMLHttpRequest : new ActiveXObject("Microsoft.XMLHTTP")).onreadystatechange = function() {
        4 == r.readyState && (200 == r.status ? t(r.responseText) : n(r.responseText))
    }, r.open("GET", e, !0), r.send()
}

// A failure function to be passed into fetchPage
function errorResult(resp){
    console.log(resp);
}

fetchPage places a XMLHTTP Request against a URL and then passes a callback the response. Strictly speaking, it should be using the Fetch API, but fetchPage was already in my site's javascript from years ago. If you're using jQuery, you can simply use get() instead.

It's the callback function which does all of the real work.

The function onThisDay parses the JSON and then iterates through the listed items looking for anything which has a publish date matching the current date (in format m-d):

function onThisDay(resp){
    // Get today as month-day
    var d = new Date();
    // Javascript 0 indexes month numbers, so add 1
    var today = (d.getMonth()+1) + "-" + d.getDate();

    var matches = [];

    j = JSON.parse(resp);
    for (var i=0; i < j.items.length; i++){
        d = new Date(Date.parse(j.items[i].date_published));
        post_day = (d.getMonth()+1) + "-" + d.getDate();

        if (post_day == today){
            j.items[i].isoDate = d.toISOString().split('T')[0]
            matches.push(j.items[i])
        }
    }

    if (matches.length > 0){
        writeModule(matches);
    }
}

If matches are found, they're passed into a function which generates the modules HTML

function writeModule(matches){
    // Grab the element we'll write into
    var container = document.getElementById('on-this-day');   

    var h3 = document.createElement('h3');
    h3.innerText = "On This Day";
    container.append*****(h3);

    var ul = document.createElement('ul');
    var li;
    var cnt = 0;

    // Iterate through the matches, appending them
    for (var i=0; i<matches.length; i++){
        li = document.createElement('li');

        a = document.createElement('a');
        a.href = matches[i].url;
        a.append*****(document.createTextNode(matches[i].title));
        li.append*****(a);

        li.append*****(
            document.createTextNode(" (" + matches[i].isoDate + ")")
            );

        ul.append*****(li);

        // Limit to 5 posts
        cnt++;
        if (cnt >= 5){
            break;
        }
    }

    container.append*****(ul);
    container.append*****(
        document.createTextNode(
                matches.length + 
                " posts were published on this day of the year"
            )
        );
}

Note: There are lots of way that this could be done more nicely, particularly if you're already using a JS framework.

The functionality is triggered by checking that the container div exists (to avoid exceptions if it doesn't) before calling fetchPage with onThisDay passed as the callback:

// Check the div exists, if not, don't bother
if (document.getElementById('on-this-day')){
    fetchPage("/feed.json", onThisDay, errorResult)
}

The result looks something like this:

Screenshot of the On This Day module

There's definitely some prettifying that could happen, but the underlying functionality works.

There's a copy of the javascript on Github.


Frequency of Display

Having created the module, I started to wonder how often it was that the module would actually display anything.

To find out, I threw together some javascript to construct a table of dates and then ran a modified version of onThisDay to populate it's cells:

function onThisDay(resp){    
    j = JSON.parse(resp);
    for (var i=0; i < j.items.length; i++){
        d = new Date(Date.parse(j.items[i].date_published));
        post_day = (d.getMonth()+1) + "-" + d.getDate();

        td = document.getElementById(post_day);
        if (td.innerHTML.length == 0){
            c = 0;
        }else{
            c = parseInt(td.innerHTML);
        }
        c++;
        td.innerHTML = c;
    }    
}

This resulted in a simple table, indicating how many posts were associated with each day of the year:

A table indicating posts per day. There are relatively few days of the year that don't have anything

There's a copy of the code that I used to generate this table on Github, it's ugly, but that's because I just opened Developer Tools and hacked it in to generate the table once.

I'm a little disappointed to see that I've never published anything on February 29th - it'll be three and a half years until I next get a chance to correct that.

The 4th of March makes a pretty serious showing in these numbers, but that's driven by 2017: that's when I archived items out of Joomshopping (I'd closed the shop section a few years before as a result of the cost of complying with the EU's changes to VAT place-of-supply).