diff --git a/src/posts/2023/drying-up-now-page-templates-eleventy.md b/src/posts/2023/drying-up-now-page-templates-eleventy.md new file mode 100644 index 00000000..2008ce1e --- /dev/null +++ b/src/posts/2023/drying-up-now-page-templates-eleventy.md @@ -0,0 +1,147 @@ +--- +date: '2023-09-03' +title: Drying up now page templates and normalizing data in Eleventy +draft: false +tags: + - Eleventy + - development +--- +My now page consists of a number of similar sections — some bespoke text, a number of media grids and lists. The text is repeated once, the lists are easily templated out in [Liquid.js](https://liquidjs.com/) and the media grids are all quite similar. Given the prominence of the media grids on the page and the number of them I decided to consolidate them into a single template while also normalizing the data passed into them as best I could. + +If you want to skip all the reading, the diff for this [is here](https://github.com/cdransf/coryd.dev/commit/6dda493d7b6c0435bac8ee2a55179e9e1afb7acd). There were a few steps to take to get this done. First I went ahead and took an existing media template and went to work reconciling the slight differences between the artist, album, book, tv and movie displays as follows: + +{% raw %} + +```liquid +{% if data.size > 0 %} + {% assign media = data | normalizeMedia %} +

+ {% tablericon icon title %} +
{{ title }}
+

+
+ {% for item in media limit: count %} + {% assign alt = item.alt | strip %} + +
+
+
+ {% if item.title %} +
{{ item.title }}
+ {% endif %} + {% if item.subtext %} +
+ {{ item.subtext }} +
+ {% endif %} +
+ {%- capture size %}{% if shape == 'square' %}225px{% else %}180px{% endif %}{% endcapture -%} + {% image item.image, alt, 'rounded-lg w-full h-full', size, loading %} +
+
+ {% endfor %} +
+{% endif %} +``` + +{% endraw %} + +We pass in the data to populate the section as `data` which is passed to the `normalizeMedia` filter and assigned to a `media` variable. The `normalizeMedia` filter does a few things: + +```javascript +module.exports = { + normalizeMedia: (media) => + media.map((item) => { + let normalized = { + image: item['image'], + url: item['url'], + } + if (item.type === 'album') { + normalized['title'] = item['title'] + normalized['alt'] = `${item['title']} by ${item['artist']}` + normalized['subtext'] = item['artist'] + } + if (item.type === 'artist') { + normalized['title'] = item['title'] + normalized['alt'] = `${item['plays']} plays of ${item['title']}` + normalized['subtext'] = `${item['plays']} plays` + } + if (item.type === 'book') normalized['alt'] = item['title'] + if (item.type === 'movie') { + normalized['title'] = item['title'] + normalized['alt'] = `${item['title']} - ${item['summary']}` + } + if (item.type === 'tv') { + normalized['title'] = item['title'] + normalized['alt'] = `${item['title']} from ${item['name']}` + normalized['subtext'] = `${item.name} • ${item.episode}` + } + return normalized + }), +} +``` + +Most notably the filter populates an object with the properties known to be common to each media type. Next, it iterates through the `media` object, checks the `type` property and mutates the `normalized` object to include properties specific to the given media type. When returned, our normalized media object can then be iterated through to populate a given media grid. For example, the `tv` media type maps `'title'` to `'title'`, `${item['title']} from ${item['name']}` to `'alt'` and so forth. This allows each different media type to be rendered in a grid that supports an object shape like this: + +```typescript +{ + image: string, + url: string, + title: string, + alt: string, + subtext?: string +}[] +``` + +This normalization is made easier by updating the shape of the objects returned by my site's `_data` files to better align with the object shape required by the `normalizeMedia` filter. For example (from `artists.js`): + +```javascript +return { + title: artist['name'], + plays: artist['playcount'], + rank: artist['@attr']['rank'], + image: + `https://cdn.coryd.dev/artists/${artist['name'].replace(/\s+/g, '-').toLowerCase()}.jpg` || + 'https://cdn.coryd.dev/artists/missing-artist.jpg', + url: artist['mbid'] + ? `https://musicbrainz.org/artist/${artist['mbid']}` + : `https://musicbrainz.org/search?query=${encodeURI(artist['name'])}&type=artist`, + type: 'artist', +} +``` + +This aligns `image` and `url` with the expected defaults in `normalizeMedia` with the remaining data being mapped inside of the `if (item.type === 'artist')` condition to align with the expectations of the `media-grid.liquid` template. Whew. + +Now, leveraging this looks like the following: + +{% raw %} + +```liquid +{% render "partials/now/media-grid.liquid", data:artists, icon: "microphone-2", title: "Artists", shape: "square", count: 8, loading: 'eager' %} +``` + +{% endraw %} + +We use the [liquid.js render tag](https://liquidjs.com/tags/render.html) and pass in a number of things: `data` which accepts the data returned by our `artists.js` data file, an `icon` of our choosing, a `title` of our choosing, a `shape` (as artist/albums are square and our remaining sets are rendered as vertical images), a `count` of items to render and the `loading` strategy to leverage (`artists` load above the fold and, hence, adopt the `'eager'` strategy — the rest are below the fold and default to `'lazy'`). My full `now.liquid` file now looks like this: + +{% raw %} + +```liquid +--- +layout: main +--- +{% render "partials/header.liquid", site: site, page: page, nav: nav %} +{{ content }} +{% render "partials/now/media-grid.liquid", data:artists, icon: "microphone-2", title: "Artists", shape: "square", count: 8, loading: 'eager' %} +{% render "partials/now/media-grid.liquid", data:albums, icon: "vinyl", title: "Albums", shape: "square", count: 8, loading: 'lazy' %} +{% render "partials/now/albumReleases.liquid", albumReleases:albumReleases %} +{% render "partials/now/media-grid.liquid", data:books, icon: "books", title: "Books", shape: "vertical", count: 6, loading: 'lazy' %} +{% render "partials/now/links.liquid", links:links %} +{% render "partials/now/media-grid.liquid", data:movies, icon: "movie", title: "Movies", shape: "vertical", count: 6, loading: 'lazy' %} +{% render "partials/now/media-grid.liquid", data:tv, icon: "device-tv", title: "TV", shape: "vertical", count: 6, loading: 'lazy' %} +

This is a + now page, and if you have your own site, + you should make one too.

+``` + +{% endraw %}