How to range through a section's pages from within another range?

Hello,

I think I have a ‘context’ problem. I would really appreciate some help.

I have a site section “Library” which has sub-pages that will each have a content category (could be one of 3). I’ve chosen not to use the site-wide taxonomy/category system because these categories are unique to this site section, and I don’t want the auto-generated category pages. However, I would appreciate any useful feedback on this decision, too.

I have listed these categories and some aspects of them in the front matter of the Library _index.md:

[[contentCategories]]
plural = "Publications"
singular = "Publication"
url = "publications"

[[contentCategories]]
plural = "Documents"
singular = "Document"
url = "documents"

[[contentCategories]]
plural = "Other"
singular = "Other"
url = "other"

and on each piece of content within the Library that I want to list on the index page, I have set in the front matter a contentCategory, e.g.:

contentCategory = "Publication"

I have a tabbed interface on the Library index page, with following ranges, which isn’t working:

{{ range .Params.contentCategories }}
<!-- code to generate tops of tabs using .plural and .url (this works) -->
{{ range ((where .Pages "contentCategory" "eq" .Params.contentCategories.singular) sort .Pages "date") }}
<!-- code to list out each piece of library content using its front matter parameters -->
{{ end }}
{{ end }}

The first goes through the contentCategories tables to create each tab; the inner range is attempting to go through the pages within the Library section and check if the content’s contentCategory matches the singular term of the corresponding one in contentCategories, to display it within that particular category’s tab, sorted by date.

This gives the error "Error while rendering “section” in “library/”: template: library/list.html:38:13: executing “main” at <where .Pages "conten…>: error calling where: can’t iterate over

I’m guessing this this because I can’t access .Pages from within the first front matter table loop. I know this is also not the best way to do this, but I have no idea 1. what’s best and 2. how to actually write this using similar (but different) examples here and here

I am unable to share the repo due to company policy.

Please could anyone offer advice? Many thanks.

Hi,

That does look like a context issue. Introduction to Hugo Templating | Hugo

When you range over an array, the dot inside the range represents an item. So when you do:

{{ range .Params.contentCategories }}
  {{ . }}
{{ end }}

you probably get something like

map[plural:Publications singular:Publication url:publications] map[....

each map[... being an item of [[contentCategories]].

As you can see, that dot has no Pages to range over. I’d probably suggest to set .Pages to a variable outside the outer range, and then range over that variable, something like

{{ $pages := .Pages }}
{{ range .Params.contentCategories }}
  {{ range $pages }}
  {{ end }}
{{ end }} 

You could create a minimal repo with just enough content to demonstrate the problem.

Hi,

Thank you so much for such a fast reply. I’m grasping the dot a little more - yes that’s right - the maps output like that if I do the dot thing under the first loop.

I’ve tried the following:

{{ $pages := .Pages  }}
{{ range .Params.contentCategories }}
<p>tabs</p>
{{ range ((where $pages "contentCategory" "eq" .singular) sort $pages "date") }}
<p>article cards</p>
{{ end }}
{{ end }}

That’s putting the new $pages variable in what I hope are the right places plus adjusting the previous .Params.contentCategories.singular to just .singular based off what you said about context (is that right?) but it’s now giving another error:
“error calling where: contentCategory isn’t a field of struct type *hugolib.Page”

My understanding of that error message is that it’s saying that (some) pages don’t have contentCategory set? Which is true, it is set only on the pieces of Library content that are relevant (5 of 7 content items within Library; rest of site pages don’t have contentCategory set at all in front matter). Surely the fact it isn’t declared on all ‘pages’ should be ok? I’m not sure what the error message really means.

You probably need to do something like

{{ range ((where $pages "Params.contentCategory" "eq" .singular) sort $pages "date") }}

ref: where | Hugo (the second range example)


What that’s saying is that contentCategory is not one of the predefined Page Variables (i.e. it’s a custom front matter param, so you have to access it from .Params field of Page)

Thank you so much, that makes sense - I’ve been able to get the loops themselves working with no errors.

However, the eq evaluation is failing. Each iteration is returning all 7 pieces of content within the library, regardless of the check against the content category. It is sorting by date with the 2 that aren’t categorised at the bottom.

It also needs the full Params.contentCategories.singular instead of just .singular - .singular throws no errors but fails to generate any inner content.

Now there is:

{{ $pages := .Pages  }}
{{ range .Params.contentCategories }}
<!-- begin tab HTML -->
{{ range ((where $pages "Params.contentCategory" "eq" .Params.contentCategories.singular) sort $pages "date") }}
<!-- library content card HTML -->
{{ end }}
<!-- end tabs -->
{{ end }}

I think this is almost the inverse of the previous problem. I think it’s now in $pages in the inner loop, which has no “.Params.contentCategories.singular” - so now somehow need to grab the current contentCategories.singular and evaluate that. I don’t know why it isn’t complaining, though, and putting all 7 on the page.

I’ve tried this:

{{ $pages := .Pages  }}
{{ range .Params.contentCategories }}
{{ $currentCategory := .Params.contentCategories.singular }}
 <!-- begin tab HTML -->
{{ range ((where $pages "Params.contentCategory" "eq" $currentCategory ) sort $pages "date") }}
<!-- library content card HTML -->
{{ end }}
<!-- end tabs -->
{{ end }}

However that has the same result with all 7 in each tab. Reducing .Params.contentCategories.singular in the variable again fails to generate any content in the inner loop.

Feel like I’m missing something here, or the eq operation isn’t structured correctly?

Hi,

So I tried to create a setup as you described in your original post. Could you see if this gets you roughly where you’re going?

  {{ $p := .Pages }}
  {{ range .Params.contentCategories }}
    {{ .singular }}: <br>
    {{ range (sort (where $p "Params.contentCategory" "eq" .singular) "Date")}}
      - {{ .Title }} <br>
    {{ end }}
    <hr>
  {{ end }}

I think the sort syntax was in the wrong order previously: https://gohugo.io/functions/sort/

If you are still getting issues, I would strongly suggest putting together a minimal repo. We don’t need your full project, just enough sample content and layouts to demonstrate your issue. It makes helping easier. :slight_smile:

Hello,

Thank you so much for trying again. I copy-pasted that into the page directly and I just can’t get it to work. It reads like it should work, and I now understand how you’ve structured it (appreciate that, thank you).

It correctly prints the 3 .singular values, but it doesn’t generate anything within each of those. I tried (at this point, just for hoots) removing the sort element of the function and changed .singular within the where to .Params.contentCategories.singular and it prints all 7 content titles -

{{ $p := .Pages }}
{{ range .Params.contentCategories }}
{{ .singular }}: <br>
{{ range where $p "Params.contentCategory" "eq" .Params.contentCategories.singular }}
- {{ .Title }} <br>
{{ end }}
<hr>
{{ end }}

I’m assuming your code was working for you and I’m wondering what I’m doing that’s different. I know I could share a repo but it’s difficult for a few reasons which I’m really sorry about.

Would an if statement work? That might need to create more ranges though. Should I try structuring the data another way? I’m running out of time and am considering hard coding it instead.

Hi,

So I had done my testing with cc as the param in the content files, and edited to contentCategory in my response. I had a :man_facepalming: moment when I thought it could possibly be casing: https://gohugo.io/variables/page/#page-level-params

Page-level .Params are only accessible in lowercase

So I tested again, but now I used contentCategory = Document on the front matter, and .Params.contentCategory, in the range but I could not get it to print all content. I could get it to not print anything, but that does not look like what you’re getting. So here’s a few more things you could try:


{{ $p := .Pages }}

{{ range .Params.contentcategories }}
{{ .singular }}: <br>
{{ range (where $p "Params.contentcategory" "eq" .singular) }}
  - {{.Params.contentcategory }} - {{ .Title }} <br>
{{ end }}
<hr>
{{ end }}
<hr>

Note the all lowercase Params.contentcategory


{{ $p := .Pages }}

{{where $p "Params.contentcategory" "eq" "Other"}}
{{where $p "Params.contentcategory" "eq" "Document"}}
{{where $p "Params.contentcategory" "eq" "Publication"}}

Do these lines {{where $p "Params.contentcategory" "eq" "..."}} print out the correct number of pages that they should have? You should get something like

Pages(1) Pages(2) Pages(0)


How are you assigning contentCategory in the front matter of your content?

contentCategory = "Document"
2 Likes

The above looks correct, but this should be both simpler and more efficient:

range (where $pages "Params.contentCategory" .singular).ByDate

Two notes to the above:

  1. The default operator when none given is eq
  2. The default sort order for pages in Hugo is ByWeight (which is weight and then date, title), so you may consider just drop the sort and do: range (where $pages "Params.contentCategory" .singular) – but that depends on your use case.

Hi both,

Thank you so much - I finally have some working code that’s a combo from both answers.

Pointyfer to answer your questions - absolutely looks like a casing issue. Surprised the rest of my site hasn’t collapsed, I’ve not been consistent at all. I will need to check everything. The printing lines also correctly printed the volume of each content type, and yes that’s exactly how the contentCategory is assigned in the front matter.

If page-level params are only accessible in lowercase, I’m guessing any I’ve camelCased should be rewritten and updated accordingly in the templates to prevent any future issues?

The code that works is:

{{ $pages := .Pages  }}
{{ range .Params.contentcategories }}
<!-- open tab panel code -->
{{ range (where $pages "Params.contentcategory" .singular).ByDate.Reverse }}
<!-- content card code -->
{{ end }}
<!-- close tab panel code -->
{{ end }}

It looks so clear and simple to have it written out like that. I’ll need to study the templating language much more. Thanks again!!