The Blog Listing Page
2023-05-15Hello 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 slice
d
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
-
Static Site Generator ↩