matthew.kunjum.men

Blog

Projects

Credits

The Blog Listing Page

2023-05-15

Hello world! This blog/ssg1 setup is a ways away from done, but I’m pleased enough with what I have that I think it’s worth presenting as is. One of the things I wanted to talk about was the JS that makes the blog listing page work.

Graceful (JS) Degradation

For starters, I wanted to make sure the listing page would work even without JS, so I have Jekyll setup to generate a paginated (static) layouts page by default. I then wrapped that in <noscript> tags so I could inject Dynamism! where Dynamism! here means searching and client side pagination. This will probably break if you’re using noscript, but if you’re using noscript you knew what you were signing up for.

Wrapping the DOM

I find the dom apis kinda generally clunky, so inspired by xeact I wrote a handful of wrapper functions to make this and any future fun frontend things more pallateable.

Starting with the least interesting, I’ve shortened document.querySelector to q. querySelector might be a sharper knife than I need all the time, but because it works so straightforwardly, I basically never see a need to use any other dom query mechanism, even for things that are special cased like getting an element by ID.

A light step up from q is Element.clear, which removes all the child nodes from an element.

const Element = {
  clear(e) {
    while (e.hasChildNodes()) {
      e.removeChild(e.lastChild)
    }
    return e;
  }
...
}

I’m a little surprised this isn’t built in functionality tbh.

Alongside Element.clear is Element.setChildren, which takes a list of nodes or strings alongside an element and replaces the elements existing children with the passed in parameters.

const Element = {
...
  setChildren(e, ...children) {
    Element.clear(e);
    e.append(...children);
  }

}

Those two live in the pseudo-namespace “Element” because they’re basically extension methods. I could monkey patch them onto the built in Element class if I really wanted to with very few misgivings since the only place this code is being used is for this blog but ehhh.

My most interesting DOM wrapper function is called e, and is significantly inspired by hiccup from the clojure/cljs world. e takes up to three arguments and is used to generate DOM elements. The first argument is a selector in the same vein as hiccup. It starts with an element name, and is optionally followed by an #id or .class.list. The second is an optional object of properties that just get Object.assign‘d onto the created element. Thing I learned making this: Object.assign calls setters/proxy methods! The last argument is an optional list of children that just get fed into the created element.

Using e looks something like this:

let val = 0;
const form = e('form', [
  e('input', {
    type: 'number',
    name: 'value',
    onchange: () => { 'update things'; }
    }),
  e('label#id', {htmlFor: 'value'}, ['The Value is 0']),
  e('input.class-one.class-two', {type: 'submit', onclick: ev => ev.preventDefault()})
]);

which IMO is a big improvement on the default way to write that without the helper.

Fuse.js

One last thing worth mentioning before we get to the meat of the post: the listing page uses the Apache licensed fuse.js for fuzzy searching. Thank you to all fuse.js contributors and maintainers!

It’s pretty nice.

The Listings

I don’t see a ton of value in going through the code line-by-line, so here’s the rough overview. When the page loads, it gets a JSON array of posts with URLs, descriptions, titles, and tags. This array is then used to make a map of post objects to the matching DOM element representation.

When text gets entered in the search bar, the list of posts gets filtered to the applicable search results (returning the same objects that were provided as candidates). These then get sliced into only the relevant values for the page at hand, and the list of results gets its contents bodily replaced with the elements associated with the results.

Aside This is one of very few times I've found it useful that Map allows for object keys based solely on object identity.

Further improvements

Stuffing the search and maybe page number into a URL param and parsing the URL param to re-populate the search field is probably good form, even if it’s not a thing anyone would likely miss. There’s a setTimeout(..., 0) in a function and I forgot why it’s there. Also making the page size be settable by the user would probably be good as well as maybe batching “renders”.

Either way, I’m reasonably proud of it!

The source code is embedded in a <script> tag on /blog if you want to look at the code.

Footnotes

  1. Static Site Generator