Lists of content divided by posts' first letter

Hey,

I need to make a list of posts divided as this:

A
— A true Story
— Another Story

B
— Become a writer
— Born Wild

There is no need to add new taxonomy such as “first letter” because I need just to cut first letter. How can I make such nested list?

Hello @khabaroff

it took me a while to get the snippet working. I assume that all your content files have a non-empty string as title. Otherwise you might get an error message due to $firstChar.

I’ve annotated each step of the template logic so it’s easier understand and to modify the snippet:

<!-- create a list with all uppercase letters -->
{{ $letters := split "ABCDEFGHIJKLMNOPQRSTUVWXYZ" "" }}

<!-- range all pages sorted by their title -->
{{ range .Data.Pages.ByTitle }}
  <!-- get the first character of each title. Assumes that the title is never empty! -->
  {{ $firstChar := substr .Title 0 1 | upper }}
 
  <!-- in case $firstChar is a letter -->
  {{ if $firstChar | in $letters }}
    <!-- get the current letter -->
    {{ $curLetter := $.Scratch.Get "curLetter" }}
    <!-- if $curLetter isn't set or the letter has changed -->
    {{ if ne $firstChar $curLetter }}
    <!-- update the current letter and print it -->
      {{ $.Scratch.Set "curLetter" $firstChar }}
      <h3>{{ $firstChar }}</h3>
    {{ end }}

   -- {{ .Title }}<br>
  {{ end }}
{{ end }} 

At the end it looks like this:

Have fun!

7 Likes

That is a great example, thanks for the mini tutorial.

I have a question - it looks like you are creating custom variables - with $letters and $firstChar? I don’t see anything in the docs pertaining to what those are, all I found was Scratch. What are the $letters := type things and what is the difference between them and Scratch?

@digitalcraftsman Very nice, what a great example. Thanks for taking the time to make it.

Correct, that is creating variables. Those variables would only be available within the content of that snippet where as with Scratch, they’d exists throughout the Hugo build (so you could use them in other templates for example).

The variables themselves are a thing of Go Templates: template package - text/template - pkg.go.dev

2 Likes

ok, good to know, I thought Scratch was only within the snippet.

So why then does his example include scratch as well as a regular variable? is there something limiting about the other variable that the whole thing could not be done with it?

Rather than {{ $.Scratch.Set "curLetter" $firstChar }} could it be {{ "curLetter" = $firstChar }}?

Maybe I’m misunderstanding myself.

That’s great! Thank you!

The thing is, that .Data.Pages.ByTitle gives 0 results, may be because as i’m searching posts on page template. And .Site.Pages.ByTitle gives tags pages that I don’t need. I did non figured out how to get all posts inside page template :frowning:

I added the variable $firstChar simply for better readability and to keep the code DRY since it’s used in multiple places.

.Scratch is used to store the current letter beyond an iteration. An alternative approach would be to use a variable outside the loop, similar to $letters. However, as far as I know it’s not possible to reassign a variable in Go templates. Hence I had to use .Scratch.

1 Like

To exclude them you could try to use .Site.RegularPages instead.

Do you’ve a repo to share? I guess it’s easier to fix this issue with the actual source files.

Got it! Now I need to reuse code to solve this

My post have this structure
Blondie — Parallel Lines (1978)
Blackstreet — Blackstreet (1994)
Big Black — Songs About Fucking (1987)

I cut year string (substr .Title -2 4) but I need to do sort then as something like
{{ range sort .Site.RegularPages }}{{ substr .Title -2 4 }}
correct?

Output: https://khabaroff.com/1000/page/years/

Here is repo

awesome, thanks for the explanation this has been a very helpful thread for me and I have no interest in the op’s idea, I just try to read everything in the hopes of finding little nuggets like this!

In your example you want to sort your content based on the year (in the title) instead of displaying them in alphabetical order, what my snippet does.

I guess it’s easier to add a new front matter parameter , e.g. year, that contains the year from the title. This way you can group your content based on a parameter defined in the content.

Here’s the corresponding example in the docs:

{{ range .Data.Pages.GroupByParam "year" }}
<h3>{{ .Key }}</h3>
<ul>
{{ range .Pages }}
    <li>
      <a href="{{ .Permalink }}">{{ .Title }}</a>
    </li>
    {{ end }}
</ul>
{{ end }}

Thank you,
I just want to find solution without front matter :slight_smile:

Get substring (1), sort (2) and group (3) (as in your example with alphabet order). First part seemed most tricky, but now I see that the most tricky is first part.

Ordering pages based on a substring in the title is indeed tricky. At the moment I’m not sure how this can be done. :thinking:

Maybe there is a way
to get all posts as array A
to make additional array with substrings B
make an ordered array with years (substrings) C

We search C[0] in B. If we get one, we get B element index i and then we print A[i]. Then go to next B element.

We search C[1] in B. If we get one, we get B element index i and then we print A[i]. Then go to next B element.

That might work, need to learn syntax =)

Any ideas here? :frowning: