Image gallery using data from .json files

On my new site, the staging version of which is hosted on Cloudflare pages and whose repository is here, and using the method described in this page of the Hugo docs, I’m trying to set up an image gallery in the following way:

1. css

.photos {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(200px, 1fr));
  grid-gap: 20px;
  align-items: stretch;
}
.photos img {
  border: 1px solid #ccc;
  box-shadow: 2px 2px 6px 0px rgba(0, 0, 0, 0.3);
  max-width: 100%;
}

2. data
2.1. tree

data
└── photos
    β”œβ”€β”€ archie-and-peggy
    β”‚   └── archie-at-three-weeks.json
    β”œβ”€β”€ archie_and_peggy.json
    β”œβ”€β”€ black-and-white
    β”‚   β”œβ”€β”€ a-boy-in-a-south-madagascar-village.json
    β”‚   β”œβ”€β”€ abandoned-ship-at-moynak.json
    β”‚   β”œβ”€β”€ duck-collection-in-new-york-apartment.json
    β”‚   β”œβ”€β”€ manhattan-skyline-from-brooklyn.json
    β”‚   β”œβ”€β”€ masai-mara-national-reserve.json
    β”‚   β”œβ”€β”€ pastries-in-patisserie-valerie.json
    β”‚   β”œβ”€β”€ st-stephen-s-cathedral-in-vienna.json
    β”‚   β”œβ”€β”€ statue-of-hermes-at-the-villa-medicis.json
    β”‚   β”œβ”€β”€ the-burggarten-in-vienna.json
    β”‚   β”œβ”€β”€ the-cloister-at-quattro-santi-coronati-rome.json
    β”‚   β”œβ”€β”€ the-cloisters-at-new-college-oxford.json
    β”‚   β”œβ”€β”€ the-fountain-at-place-du-grand-mezel-in-geneva.json
    β”‚   β”œβ”€β”€ the-statue-of-st.-praxedes.json
    β”‚   └── traffic-at-tulear-madagascar.json
    β”œβ”€β”€ black_and_white.json
    β”œβ”€β”€ colour
    β”‚   β”œβ”€β”€ a-village-in-south-madagascar.json
    β”‚   β”œβ”€β”€ beduin-in-petra.json
    β”‚   β”œβ”€β”€ bicycle-made-of-wire.json
    β”‚   β”œβ”€β”€ children-in-south-madagascar-village.json
    β”‚   β”œβ”€β”€ demonstration-for-animal-rights.json
    β”‚   β”œβ”€β”€ driving-through-the-aral-sea.json
    β”‚   β”œβ”€β”€ far-east-meets-near-east.json
    β”‚   β”œβ”€β”€ kenyan-artifact.json
    β”‚   β”œβ”€β”€ modelling-at-grand-palais.json
    β”‚   β”œβ”€β”€ my-cat-peggy-aged-six-months.json
    β”‚   β”œβ”€β”€ on-what-used-to-be-the-aral-sea.json
    β”‚   β”œβ”€β”€ peggy-wanting-cuddles.json
    β”‚   β”œβ”€β”€ soldiers-in-kiev.json
    β”‚   β”œβ”€β”€ tents-in-the-karapakalstan-desert.json
    β”‚   β”œβ”€β”€ the-chor-minor-at-bukhara.json
    β”‚   β”œβ”€β”€ the-city-walls-at-khiva.json
    β”‚   β”œβ”€β”€ the-new-building-at-magdalen-college.json
    β”‚   └── village-children-in-madagascar.json
    β”œβ”€β”€ colour.json
    β”œβ”€β”€ israel
    β”‚   β”œβ”€β”€ a-bar-mitzvah-at-zion-gate.json
    β”‚   β”œβ”€β”€ an-ice-cream-parlour-in-west-jerusalem.json
    β”‚   β”œβ”€β”€ arab-women-playing-at-the-beach-tel-aviv.json
    β”‚   β”œβ”€β”€ boys-playing-in-the-jewish-quarter.json
    β”‚   β”œβ”€β”€ jews-praying-at-temple-mount.json
    β”‚   β”œβ”€β”€ mohammedans-in-jerusalem.json
    β”‚   β”œβ”€β”€ the-ethiopian-quarter-at-holy-sepulchre-church.json
    β”‚   β”œβ”€β”€ the-margosa-hotel-in-jaffa.json
    β”‚   β”œβ”€β”€ the-native-bazaar-near-the-holy-sepulchre.json
    β”‚   β”œβ”€β”€ the-promised-land.json
    β”‚   └── two-worlds-collide-at-temple-mount.json
    β”œβ”€β”€ israel.json
    β”œβ”€β”€ me
    β”œβ”€β”€ me.json
    β”œβ”€β”€ paris
    β”‚   β”œβ”€β”€ a-baker-in-rue-des-rosiers.json
    β”‚   β”œβ”€β”€ a-marais-street-at-night.json
    β”‚   β”œβ”€β”€ at-paris-photo-2017.json
    β”‚   β”œβ”€β”€ eiffel-tower-from-mur-de-la-paix.json
    β”‚   β”œβ”€β”€ french-tin-soldiers.json
    β”‚   β”œβ”€β”€ gare-du-nord-paris-on-the-day-after-the-november-2015-shootings.json
    β”‚   β”œβ”€β”€ montmartre-in-february.json
    β”‚   β”œβ”€β”€ musee-du-jeu-de-paume.json
    β”‚   β”œβ”€β”€ pont-alexandre-iii-paris-france.json
    β”‚   β”œβ”€β”€ teddy-bear-in-a-paris-flat.json
    β”‚   β”œβ”€β”€ the-anri-sala-exhibition-at-the-pompidou-centre.json
    β”‚   β”œβ”€β”€ the-ile-de-la-cite-in-autumn.json
    β”‚   β”œβ”€β”€ the-paris-zombie-march.json
    β”‚   β”œβ”€β”€ the-paris-zombies.json
    β”‚   β”œβ”€β”€ the-pompidou-centre-in-autumn.json
    β”‚   β”œβ”€β”€ the-pompidou-centre-in-mid-winter.json
    β”‚   β”œβ”€β”€ tricky-moment-in-rue-des-rosiers.json
    β”‚   └── tuileries-gardens-in-late-winter.json
    └── paris.json

2.2. .json file content

I’m using two types of .json file:

2.2.1. a-boy-in-a-south-madagascar-village.json
There is one .json file for each image, stored in one the folders corresponding to a category that will then be rendered as a separate gallery. (At this stage, I’m only using the id part of the dataset, which contains the final component in the image’s URL. The remainder will serve for a lightbox with a caption that I’ll add later.)

{
  "alt": "A boy in a South Madagascar villager",
  "caption": "Ampanihy is one of the poorest parts of Madagascar",
  "id": "dadc7948-0dcb-4ad7-b614-bde264f9cc00",
  "camera": "iPhone X back dual camera 4mm f/1.8",
  "flickr": "52909011237",
  "geolocation": {
    "latitude": "25.009123",
    "longitude": "44.126013"
  }
}

2.2.2. black_and_white.json
In addition to the .json files for each image, there’s also a .json file for each image folder, which includes a description and a Flickr album id to which I want to provide a link in due course.

{
  "title": "Black and white",
  "description": "Most of my favorite photographsβ€”in keeping with my minimalist approach to art and to designβ€”are black and white: these ones were shot all over the world.",
  "flickr": "72157622017095408"
}

3. /content/_index.md

---
title: "Photos"
date: 2019-05-05T15:26:38+00:00
slug: "photos"
description: "Photographs by Donald Jenkins"
summary: "As Apple started producing increasingly seriously good cameras in their phones, I started using my [phone](https://www.flickr.com/photos/astorg/albums/72157621916056260) for the vast majority of my pictures. Many of the pictures on this page were taken with one. For serious pictures, have a look at  [Flickr](https://www.flickr.com/photos/astorg), for more frequent updates, go to  [Instagram](https://instagram.com/donaldjenkins_/). I tend to prefer working in black and white, but that isn’t by any means a systematic rule."
---

4. /layout/photos/section.html

{{ define "main" }}
<article class="archive">
  <div id="content">
    {{ .Summary }}
    <div class="columns is-multiline">
      <h2 id="black-and-white" class="title">Black and white</h2>
      <p class="subtitle">{{ index .Site.Data.photos.black_and_white "description" | markdownify }}</p>
      <div class="photos">
      {{ range $.Site.Data.photos.black_and_white }}
        {{ partial "widgets/gallery-image.html" . }}
      {{ end }}
      </div>
    </div>
  </div>
</article>
{{ end }}

My idea is that the <div>...</div> section would then be repeated each of the sets of images in each of the subfolders in data/photos (in this case, β€˜colour’, β€˜paris’, β€˜israel’, β€˜archie & peggy’ and β€˜me’.

5. partials/widgets/gallery-image.html

  {{ range .id }}
  {{ $id := .id }}
  {{ $image_url := printf "%s%s" .Site.Params.cloudflare_images $id }}
  <img
    src="{{ $image_url }}/w=420,gamma=0.36,quality=45"
    alt=""
  />
  {{ end }}

All images are stored in Cloudflare Images, with the URL being set as a parameter in config.toml (as cloudflare_images). The URL for the image is then concatenated by the partial for each of the .json files in the corresponding directory. By the way, I succeeded in setting up a similar workflow for my articles page.

Unfortunately, the above setup produces the following error:

Error: Error building site: failed to render pages: render of β€œsection” failed: β€œ/Users/donaldjenkins/sites/donaldjenkins/layouts/photos/section.html:10:11”: execute of template failed: template: photos/section.html:10:11: executing β€œmain” at <partial β€œwidgets/gallery-image.html” .>: error calling partial: β€œ/Users/donaldjenkins/sites/donaldjenkins/layouts/partials/widgets/gallery-image.html:1:11”: execute of template failed: template: partials/widgets/gallery-image.html:1:11: executing β€œpartials/widgets/gallery-image.html” at <.id>: can’t evaluate field id in type string

Can anyone point to what I’m getting wrong?

1 Like

You can’t range over id since that’s a string, not an array or object/dict.

Yes, absolutely. I figured that was the issue and fixed it.

The issue was, inter alia, due to my code parsing through a string, as pointed out by chrillek.

The solution, in case anyone encounters the same problem is as follows (and can be seen here):

1. css

.photos {
  display: grid;
  grid-template-columns: repeat(auto-fill, minmax(250px, 1fr));
  gap: 1rem;
  align-items: stretch;
}
.photos img {
  object-fit: cover;
  height: $card-size;
  border-radius: $very-rounded;
  cursor: pointer;
}

2. data

2.1. tree

data
└── photos
    β”œβ”€β”€ archie_and_peggy
    β”‚   β”œβ”€β”€ a-happy-pair.json
    β”‚   β”œβ”€β”€ archie-at-home.json
    β”‚   β”œβ”€β”€ archie-at-three-weeks.json
    β”‚   β”œβ”€β”€ cat-tree-pair.json
    β”‚   β”œβ”€β”€ emerging-from-a-nap.json
    β”‚   β”œβ”€β”€ his-first-few-days.json
    β”‚   β”œβ”€β”€ morning-affection.json
    β”‚   β”œβ”€β”€ my-cat-peggy-aged-six-months.json
    β”‚   β”œβ”€β”€ peggy-aged-one-month.json
    β”‚   β”œβ”€β”€ peggy-jenkins.json
    β”‚   β”œβ”€β”€ peggy-wanting-cuddles.json
    β”‚   β”œβ”€β”€ pensive-burmese.json
    β”‚   β”œβ”€β”€ playing-ball.json
    β”‚   └── wise-beyond-his-years.json
    β”œβ”€β”€ black_and_white
    β”‚   β”œβ”€β”€ a-boy-in-a-south-madagascar-village.json
    β”‚   β”œβ”€β”€ abandoned-ship-at-moynak.json
    β”‚   β”œβ”€β”€ duck-collection-in-new-york-apartment.json
    β”‚   β”œβ”€β”€ manhattan-skyline-from-brooklyn.json
    β”‚   β”œβ”€β”€ masai-mara-national-reserve.json
    β”‚   β”œβ”€β”€ pastries-in-patisserie-valerie.json
    β”‚   β”œβ”€β”€ st-stephen-s-cathedral-in-vienna.json
    β”‚   β”œβ”€β”€ statue-of-hermes-at-the-villa-medicis.json
    β”‚   β”œβ”€β”€ the-burggarten-in-vienna.json
    β”‚   β”œβ”€β”€ the-cloister-at-quattro-santi-coronati-rome.json
    β”‚   β”œβ”€β”€ the-cloisters-at-new-college-oxford.json
    β”‚   β”œβ”€β”€ the-fountain-at-place-du-grand-mezel-in-geneva.json
    β”‚   β”œβ”€β”€ the-statue-of-st.-praxedes.json
    β”‚   └── traffic-at-tulear-madagascar.json
    β”œβ”€β”€ colour
    β”‚   β”œβ”€β”€ a-village-in-south-madagascar.json
    β”‚   β”œβ”€β”€ beduin-in-petra.json
    β”‚   β”œβ”€β”€ bicycle-made-of-wire.json
    β”‚   β”œβ”€β”€ children-in-south-madagascar-village.json
    β”‚   β”œβ”€β”€ demonstration-for-animal-rights.json
    β”‚   β”œβ”€β”€ driving-through-the-aral-sea.json
    β”‚   β”œβ”€β”€ far-east-meets-near-east.json
    β”‚   β”œβ”€β”€ kenyan-artifact.json
    β”‚   β”œβ”€β”€ modelling-at-grand-palais.json
    β”‚   β”œβ”€β”€ my-cat-peggy-aged-six-months.json
    β”‚   β”œβ”€β”€ on-what-used-to-be-the-aral-sea.json
    β”‚   β”œβ”€β”€ peggy-wanting-cuddles.json
    β”‚   β”œβ”€β”€ soldiers-in-kiev.json
    β”‚   β”œβ”€β”€ tents-in-the-karapakalstan-desert.json
    β”‚   β”œβ”€β”€ the-chor-minor-at-bukhara.json
    β”‚   β”œβ”€β”€ the-city-walls-at-khiva.json
    β”‚   β”œβ”€β”€ the-new-building-at-magdalen-college.json
    β”‚   └── village-children-in-madagascar.json
    β”œβ”€β”€ index
    β”‚   β”œβ”€β”€ archie_and_peggy.json
    β”‚   β”œβ”€β”€ black_and_white.json
    β”‚   β”œβ”€β”€ colour.json
    β”‚   β”œβ”€β”€ israel.json
    β”‚   β”œβ”€β”€ me.json
    β”‚   └── paris.json
    β”œβ”€β”€ israel
    β”‚   β”œβ”€β”€ a-bar-mitzvah-at-zion-gate.json
    β”‚   β”œβ”€β”€ an-ice-cream-parlour-in-west-jerusalem.json
    β”‚   β”œβ”€β”€ arab-women-playing-at-the-beach-tel-aviv.json
    β”‚   β”œβ”€β”€ boys-playing-in-the-jewish-quarter.json
    β”‚   β”œβ”€β”€ jews-praying-at-temple-mount.json
    β”‚   β”œβ”€β”€ mohammedans-in-jerusalem.json
    β”‚   β”œβ”€β”€ the-ethiopian-quarter-at-holy-sepulchre-church.json
    β”‚   β”œβ”€β”€ the-margosa-hotel-in-jaffa.json
    β”‚   β”œβ”€β”€ the-native-bazaar-near-the-holy-sepulchre.json
    β”‚   β”œβ”€β”€ the-promised-land.json
    β”‚   └── two-worlds-collide-at-temple-mount.json
    β”œβ”€β”€ me
    β”‚   β”œβ”€β”€ at-conference-olivaint.json
    β”‚   β”œβ”€β”€ at-french-embassy-dakar.json
    β”‚   β”œβ”€β”€ at-la-treille-in-geneva.json
    β”‚   β”œβ”€β”€ at-the-beach-in-knokke-le-zoute.json
    β”‚   β”œβ”€β”€ at-the-western-wall.json
    β”‚   β”œβ”€β”€ donald-at-fiac.json
    β”‚   β”œβ”€β”€ in-bedford-ny.json
    β”‚   β”œβ”€β”€ m-a-degree-ceremony-at-the-sheldonian.json
    β”‚   β”œβ”€β”€ passover-dinner-in-tel-aviv.json
    β”‚   β”œβ”€β”€ photoshoot-by-mathieu-camille-collin.json
    β”‚   β”œβ”€β”€ portrait-by-niels-ackermann.json
    β”‚   └── with-archie.json
    └── paris
        β”œβ”€β”€ a-baker-in-rue-des-rosiers.json
        β”œβ”€β”€ a-marais-street-at-night.json
        β”œβ”€β”€ at-paris-photo-2017.json
        β”œβ”€β”€ eiffel-tower-from-mur-de-la-paix.json
        β”œβ”€β”€ french-tin-soldiers.json
        β”œβ”€β”€ gare-du-nord-paris-on-the-day-after-the-november-2015-shootings.json
        β”œβ”€β”€ montmartre-in-february.json
        β”œβ”€β”€ musee-du-jeu-de-paume.json
        β”œβ”€β”€ pont-alexandre-iii-paris-france.json
        β”œβ”€β”€ teddy-bear-in-a-paris-flat.json
        β”œβ”€β”€ the-anri-sala-exhibition-at-the-pompidou-centre.json
        β”œβ”€β”€ the-ile-de-la-cite-in-autumn.json
        β”œβ”€β”€ the-paris-zombie-march.json
        β”œβ”€β”€ the-paris-zombies.json
        β”œβ”€β”€ the-pompidou-centre-in-autumn.json
        β”œβ”€β”€ the-pompidou-centre-in-mid-winter.json
        β”œβ”€β”€ tricky-moment-in-rue-des-rosiers.json
        └── tuileries-gardens-in-late-winter.json

2.2. .json file content [a-boy-in-a-south-madagascar-village.json]

{
  "alt": "A boy in a South Madagascar villager",
  "caption": "Ampanihy is one of the poorest parts of Madagascar",
  "id": "dadc7948-0dcb-4ad7-b614-bde264f9cc00",
  "camera": "iPhone X back dual camera 4mm f/1.8",
  "flickr": "52909011237",
  "geolocation": {
    "latitude": "25.009123",
    "longitude": "44.126013"
  }
}

3. Layout

    <div id="black-and-white">
      <h2 class="title">{{ index .Site.Data.photos.index.black_and_white "title" | markdownify }}</h2>
      <p class="subtitle">{{ index .Site.Data.photos.index.black_and_white "description" | markdownify }}</p>
      <div class="photos">
        {{ range $.Site.Data.photos.black_and_white }}
        {{ partial "widgets/gallery-image.html" . }}
        {{ end }}
      </div>
    </div>

4. Partial

{{ $id := .id }}
{{ $alt := .alt }}
{{ $image_url := printf "%s%s" site.Params.cloudflare_images $id }}
  <img
  src="{{ $image_url }}/w=420,fit=cover,quality=60"
  alt="{{ $alt }}"
/>

I’m certain there’s a better way of doing this, so if anyone has any suggestions to simplify the workflow, I’d be interested.

I’d go for EXIF/IPTC data instead of duplicating them in JSON. And privacy use page bundles, where you can simply iterate over all images in the directory.

I definitely would have liked to d that, but about a third of my photos don’t have EXIF data because they are scans of old family photos dating from before the digital era. Am I right in believing there’s no trivial way to add EXIF/IPTC metadata to images that don’t have them? Of course one way of dealing with this would be a conditional, using EXIF/IPTC if available and the JSON params as fallback otherwise.

Regarding location, there seems to be a brilliant way of using it in maps, based on just latitude and longitude, here: if anyone knows of a better way, I’d be interested.

I was simply looking at your data. If you have a camera like iPhone, that will certainly be part of the EXIF data. Otoh, if they’re old photos, you don’t know the camera anyway, I guess. And I find it more straightforward to add title, keywords etc to the image so that they are embedded instead of putting them in a separate file that gets easily separated from the image file. Especially with hexadecimal file names as in your case.

That depends on what β€œtrivial” means to the personal doing the work.

  1. You can do it individually or en masse from a data source using exiftool.
  2. You can do it individually with applications such as Lightroom, Shotwell, even Google photos.

The challenge with #2 is making sure the application can read/write the tags you need in a location that’s useful for the image consumer (in this case Hugo, which can only read EXIF data).

At least for captions, there is one advantage to sourcing them separately from the EXIF/IPTC data: you can include links in them, as you can see with this example.

But I think the idea setup would be (1) to process the photographs in the way jmooring suggested to amend/complete the image metadata where possible, (2) source the caption, location and any other information you want to display directly from them, but (3) optionally use separately-provided data for titles and captions if you want a quick way to overrule the EXIF/IPTC.

1 Like

In the past I would systematically process photograph EXIF data in Lightroom, which I definitely didn’t find trivial but on the contrary very tiresome: this eventually prompted me to set up a way of automatically connecting them to my WordPress site.

The above shows the lightbox in my previous WordPress site, which displayed EXIF/IPTC info, in contrast to my current Hugo lightbox, to which I included another example above in my reply to chrillek, which does not do this yet.

This had advantages because one could make use of WordPress’s and Lightroom’s relatively extensive plugin system, and in this case combine them to obtain something close to the result I wanted. The drawbacks, on top of the absurdity inherent to a PHP-fuelled dynamic site, were having to put up with a plugin ecosystem over which one had no control and which untamed could surprisingly rapidly descend into an inextricable mess.

Switching to Hugo and deciding to store all my images on Cloudflare Images gave me back control over both content and speed, but the process of achieving the same degree of information and some sort of organised image workflow is only just beginning.

1 Like

This topic was automatically closed 2 days after the last reply. New replies are no longer allowed.