Filtering pages based on a page param in Hugo

I asked this question on SO, but I think this is a better forum for it.

I have a metadata fields in some of my pages like so

---
dateStartOfEvent: "2022-06-26T00:00:00+02:00"
type: event
---

I would like to:

  1. collect all the pages where type: event
    {{- $cond1 := where .Site.RegularPages "Type" "event" }}
    {{- $events := $cond1 }}

  2. from the result of 1 above, I would like to find the pages that have dateStartOfEvent set
    {{- $cond2 := where $events ".Params.datestartofevent" "!=" "nil" }}

  3. from the result of 2 above, I would like to find the pages where dateStartOfEvent is after today
    {{- $cond3 := where $events (time .Params.dateStartOfEvent).After now }}

  4. finally, iterate over the first 10 events and list them
    {{- $events_num := (.Site.Params.widgets.events_num | default 10) }}
    {{- range first $events_num $events }}
    ...

I am facing a problem in step 3 where I get the following error: <time .Params.dateStartOfEvent>: error calling time: unable to cast <nil> of type <nil> to Time. Why? In the previous filter $cond2 I had already (in theory) removed all the pages where dateStartOfEvent did not exist, so why is this error occuring?

Since I am super new to Hugo and golang, I would also love to know if there is a better and shorter (more idiomatic) way for doing the above? For example, in JS I would just chain a bunch of filters.

There are syntax errors in the above construct.

There is no .After in Hugo there is the function after that:

slices an array to only the items after the Nth item.

Also now is not a variable, instead it is another function that returns:

the current local time

There is an example in the Documentation about showing future events in a list:

thanks for response. I am embarrassed about .After. I assumed that time.After would be an analog of time.Before (in the other direction), but I was wrong.

I looked at the example you linked, and that is pretty much exactly what I want to do. However, (in order to understand this better), I am still confused as to why my logic is not working. I now have the following

{{- $cond1  := where .Site.RegularPages "Type" "event" }}
{{- $events := $cond1 }}

{{- $cond2  := where $events ".Params.datestartofevent" "ne" "nil" }}
{{- $events := $cond2 }}

{{- $cond3  := where $events (time .Params.dateStartOfEvent) "gt" now }}
{{- $events := $cond3 }}

I still get the error <time .Params.dateStartOfEvent>: error calling time: unable to cast of type to Time

Shouldn’t $cond2weed out any pages where dateStartOfEvent doesn’t exist or is not set? (an aside: all my events pages do have a dateStartOfEvent, so actually $cond2is not even needed, but I am inserting it there just to future-proof the project just in case someone else creates and event page and forgets to add that param).

Thanks in advance for any guidance

Don’t quote nil.

2 Likes

Don’t quote nil.

That is ZEN. Just saying.

1 Like

thanks for the hint. I followed your advice however the outcome is still the same

{{- $cond2  := where $events ".Params.datestartofevent" "ne" nil }}
{{- $events := $cond2 }}

{{- $cond3  := where $events (time .Params.dateStartOfEvent) "gt" now }}
{{- $events := $cond3 }}

the above croaks with <time .Params.dateStartOfEvent>: error calling time: unable to cast of type to Time. I also printed out the value of dateStartOfEvent, the param exists in all the pages in the range

<ul>
{{- range  $events }}
    <li>{{ printf "%#v" .Params.dateStartOfEvent }}]</li>
{{- end }}
</ul>

why is time .Params.dateStartOfEvent not casting the string to time in the where filter? Seems I am doing something fundamentally wrong and I have no idea what

Try formatting the date with something like:

{{ dateFormat "2006-01" .Params.dateStartOfEvent }}

OR

{{ time.Format "2006-01" .Params.dateStartOfEvent }}

just tried that but sorry, no luck. I get the same error as before

{{- $events := where .Site.RegularPages "Type" "event" }}
{{- $events := ($events.ByParam "datestartofevent") }}
{{- $events := where (time.Format "2006-01" .Params.dateStartOfEvent) "gt" now }}

execute of template failed: template: partials/widgets/events.html:5:26: executing "partials/widgets/events.html" at <time.Format>: error calling Format: unable to cast <nil> of type <nil> to Time

per the docs, the signature of the where function is

where COLLECTION KEY [optional OPERATOR] MATCH

maybe it is not possible to transform the key in line. I can’t figure out what is going on.

  1. Please look at this example again. In particular:

    If you restrict front matter to the TOML format, and omit quotation marks surrounding date fields, you can perform date comparisons without casting.

    There is a subtle but very important difference between this:

    # TOML
    my_custom_date = 2022-01-30T11:15:30-08:00
    

    and these:

    # TOML
    my_custom_date = "2022-01-30T11:15:30-08:00"
    
    # YAML
    my_custom_date: 2022-01-30T11:15:30-08:00
    
    # JSON
    {
      "my_custom_date": "2022-01-30T11:15:30-08:00"
    }
    
  2. Please share your project repository if you need additional assistance.

I did see that, thanks. Converting the frontmatter to TOML would involve going back to all the existing events pages and redoing the data. I will do that if I determine there is actually no way forward the way I am approaching the problem (that is, if inline transformation of keys is not possible in a where filter).

Unfortunately I can’t share the project repo as the project happens to be private, but really, it is a sum total of the following

pages with frontmatter like so

---
dateStartOfEvent: "2022-06-26T00:00:00+02:00"
type: event
---

and the partial template like so

{{/* 1. collect all the pages where type is event */}}
{{- $events := where .Site.RegularPages "Type" "event" }}

{{/* 2. from #1, find pages with "dateStartOfEvent" */}}
{{- $events := where $events ".Params.datestartofevent" "ne" nil }}

{{/* 3. from #2, find pages where "dateStartOfEvent" is after today */}}
{{- $events := where $events (time .Params.dateStartOfEvent) "gt" now }}

{{/* 4. finally, iterate over the first 10 events and list them */}}
{{- $events_num := (.Site.Params.widgets.events_num | default 10) }}  
{{- range first $events_num $events }}
...

I always get the error on step 3. Interestingly, if I remove step 3 and put the same logic inside a range loop, I don’t get an error

{{- if $events }}
<ul>
    {{- range $events }}

        {{ if (isset .Params "datestartofevent") }}
            {{ $t := (time .Params.dateStartOfEvent) }}

            {{ if gt $t now }}
                <li>{{ .Title }}</li>
            {{- end }}
        {{- end }}
        
    {{- end }}
</ul>
{{- end }}

The above, however, produces a slightly different result from what I want to accomplish. Plus, I am really interested in understanding why I can’t get my initial approach to work

This is correct. That is why the example shows two implementations; one for TOML dates, and one for TOML/YAML/JSON strings.

many thanks… this is exactly what I wanted|needed to know. Now I can look for alternative approaches. It might be worth adding this tidbit to the docs so others don’t go down the path I did

We tend to document what you can do instead of the many things you can’t do.

If you feel strongly about this, please submit a detailed suggestion for improving the docs:
https://github.com/gohugoio/hugoDocs/issues

Thanks.

Another approach, if your custom dates are strings instead of TOML dates, is to perform a string comparison in the where clause, but this could get a little wobbly if your date formats are inconsistent or contain different offsets.

The comparison operators (eq, gt, ge, etc.) can be used with strings as well.

{{ range where (where site.RegularPages "Type" "event") "Params.datestartofevent" "ge" (now.Format "2006-01-02T15:04:05-07:00") }}

Note that there’s no need to check for nil.

Finally, to clarify an earlier comment, the .After and .Before methods can be used on time.Time values.

{{ $d := time.AsTime "2022-01-30T08:00:00-08:00" }}
{{ $d.After (time.AsTime "2022-02-01") }} --> false
{{ $d.Before (time.AsTime "2022-02-01") }} --> true

But these are not useful in date comparisons (string or date values) in where clauses.

ok, thanks for the detailed advice. I might just bite the bullet and convert all my existing fm metadata to toml from the current yaml since I’d rather not do string comparisons of dates

Have a look at hugo convert toTOML . By default this is considered (correctly) unsafe, and will tell you it won’t do it without passing the --unsafe flag. Don’t do that.

Instead:

hugo convert toTOML -o /tmp/junk

Then examine the results. Dates will be quoted, but you can easily fix those after conversion in your IDE with a regex replace.

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