POSTS DON'T GROW ON TREES

Posted on 28 August 2024 by admin

Browsing some blogs I've visited in the past for inspiration in what to include my blog sidebar, an interesting structure caught my eye. It was a collapsible directory tree for all posts in a blog, categorised primarily by year and then by month; a wonderfully simple idea, but intuitive and useful.

The first challenge was figuring out how to break down the post object list into the tree structure. Consulting the docs, I came across the {% regroup %} Django template tag. This handy tag enabled me to create new groups of posts by year, and then by month within each year in a pretty clear manner:

<ul class="years">
    {% regroup tree_posts by pub_date.year as year_list %}
    {% for year in year_list %}
        <li class="year reveal">
            {{ year.grouper }}
            <ul class ="months">
                {% regroup year.list by pub_date.month as month_list %}
                {% for month in month_list %}
                    <li class="month">
                        {{ month.list.0.pub_date|date:"F" }}
                        <ul class="dates">
                            {% for post in month.list %}
                                <li>
                                    <a href="{% url 'post_detail_view' post_id=post.post_id %}">
                                        {{ post.title }}
                                    </a>
                                </li>
                            {% endfor %}
                        </ul>
                    </li>
                {% endfor %}
            </ul>
        </li>
    {% endfor %}
</ul>

In this initial implementation, all years in the tree would be expanded by default; the months would remain collapsed to prevent clutter upon loading. Next up was applying interactivity using JavaScript. This involved adding a mouse click event listener to all year and month li elements that toggled a reveal class, and applied an inline style to their child ul element (if it existed) with a ternary operator:

const yearMonthElements = document.querySelectorAll(".year, .month");

yearMonthElements.forEach(function (element) {
    element.style.display = "block";
    element.addEventListener("click", function (event) {
        event.stopPropagation();
        element.classList.toggle("reveal");
        const childUl = element.querySelector("ul");

        if (childUl) {
            childUl.style.display = childUl.style.display === "none" ? "block" : "none";
        }
    })
});

Just to note, the line event.stopPropagation(); served an important purpose, as I had discovered mouse clicks on a nested list item, i.e. the month, "propagate" up the tree and cause the top-level list items, i.e. the year, to also be triggered. This line prevented that behaviour.

Then, to keep the month ul elements collapsed:

const datesElements = document.querySelectorAll(".dates");

datesElements.forEach(function (element) {
    element.style.display = "none";
})

Of course, this was the only the first version of the post tree HTML and logic. Eventually, I enhanced it with default inline styles to remove the unnecessary style assignments with JavaScript, data attributes for scripting use, and minor formatting changes. The JS logic was improved too, so that the tree by default only revealed the most recent year and month's posts.

<ul class="years">
    {% regroup tree_posts by pub_date.year as year_list %}
    {% for year in year_list %}
        <li class="year" data-year="{{ year.grouper }}">{{ year.grouper }}
            <ul class="months" style="display: none; opacity: 0;">
                {% regroup year.list by pub_date.month as month_list %}
                {% for month in month_list %}
                    <li class="month"
                        data-month="{{ month.list.0.pub_date|date:'m' }}">{{ month.list.0.pub_date|date:"F" }}
                        <ul class="dates" style="display: none; opacity: 0;">
                            {% for post in month.list %}
                                <li>
                                    <a href="{% url 'blog:post_detail' year=post.pub_date.year month=post.pub_date.month|stringformat:'02d' slug=post.slug %}"
                                        class="post-clamp">{{ post.title }}
                                    </a>
                                </li>
                            {% endfor %}
                        </ul>
                    </li>
                {% endfor %}
            </ul>
        </li>
    {% endfor %}
</ul>

Here is an illustrative example of the final version:

PNGS & SVGS

Posted on 25 July 2024 by admin

Creating the clone Blogger homepage, it became apparent to me that, despite the large number of images in the original site, it's probably best to avoid using source images for copyright reasons. I considered my options: should I substitute them all with lookalike royalty-free clipart, no matter how long it took? Should I condense/truncate my version to reduce the number of substitutions needed?

After careful consideration, I made the call to substitute all images — not with clipart, but with illustrative geometric shapes. The shape colours would be representative of every image they replaced, and have similar drop shadow effects where applicable. As the original site used the srcset attribute for responsive img elements, I figured I would do the same as good practice in learning how srcset works and in creating optimised images for the web. The process of creating these raster images was tedious but simple. 

Eventually, the time came to tackle substituting SVG elements, which consisted of the logo and some UI. Admittedly, I had to refresh myself on the ins and outs of the format, including which software to use for it. Soon enough I got the hang of creating and editing vector graphics and could finally embed them into the clone site.

That wasn't the end of the story, however, as I'd noticed the original site's SVG markup appeared to be far less verbose than that of the graphics I had exported. Somewhat expectedly, the difference came down to me using a "plain SVG" export option, where an "optimised SVG" was also available and much more akin to the source SVG.