Study Guide: How to Create a Recommended Reading Function

Step 1. Create the data file(s)

In this example we use JSON format, but Hugo handles YAML as well.

Records can be listed in one or more data files, for example by author, title, language, or whatever. They can also all be listed in one file. How you organize the data is up to you. Hugo automatically searches every file in the directory /data/books/ for matching data. Here’s an example JSON data file.

/data/books/example.json

[
    {
        "title": "Example Title",
        "author": "John Doe",
        "description": "A book about...",
        "link": "/docs/example-file.pdf",
        "tags": ["example"],
        "stars": 4
    },
   	 
    {
        "title": "The Port Huron Statement",
        "author": "Tom Hayden",
        "description": "A broad critique of the political...",
        "link": "/docs/port-huron-statement.pdf",
        "tags": ["imperialism", "democracy"],
        "stars": 4
    },

    {
        "title":"Healing Resistance",
        "subtitle":"A Radically Different Response to Harm",
        "author":"Kazu Haga",
        "publisher":"Parallax Press",
        "published":"January 14, 2020",
        "link":"https://wagingnonviolence.org/",
        "image": "/img/resources/healing_resistance_cover.jpg",
        "description": "In Kingian Nonviolence, a philosophy developed...",
        "tags": ["non-violence", "ahimsa", "democracy"],
        "stars": 4
    },

    {
        "title": "Democracy and Education",
        "image": "/img/banners/john-dewey.png",
        "author": "John Dewey",
        "publisher": "University of Virginia",
        "published": "1916",
        "link": "/docs/democracy-and-education-by-john-dewey.pdf",
        "description": "John Dewey's *Democracy and Education* is...",
        "tags": ["education", "democracy"],
        "stars": 5
    }

]

2. Add .Site.Params

By adding default values to the site’s main config file, content pages will automatically show a reasonable “Recommended Reading” list with minimal effort. These values can be overridden on a page-by-page basis.

[project_root]/config.toml

[params]
...
[params.recommended_reading]
    enable = true
    data_set = "books"
    taxonomy = "topics"
    title = "Recommended Reading"
    require_description = true
    truncate_description = 500
    default_image = "/img/banners/ancient-scroll.jpg"
    default_max = 10

3. Create the partial

/layouts/partials/recommended-reading.html

This code is still rough. In this example, the Shortcode matches the data’s tag field to the page’s topic taxonomy field. To use a different taxonomy, edit the two places in the Shortcode that reference .Params.topics. Someday maybe I’ll figure out how to concatenate strings and variables to construct more dynamicrange statements.

<!-- Show some vars during development. -->
{{- $debug := true -}}
{{- $result := slice -}}

{{- $max := int $.Site.Params.recommended_reading.default_max -}}
{{- with $.Params.recommended_reading.max }}{{ $max = int . }}{{ end -}}

<!-- 
    Get the data.
    TODO: Why must we range twice? Converting between map and slice?
-->
{{- $data_set := string $.Site.Params.recommended_reading.data_set -}}
{{- range (index .Site.Data $data_set) -}}
{{- range $item := sort . "stars" -}}
    <!-- Topic taxonomy pages are a special case. Match on page title. -->
    {{- if and (eq $.Type "topics") (in $item.tags (lower $.Title)) (ne $item.draft true) -}}
        {{- $result = $result | append $item -}}
        {{- $max = 999 -}}
    {{- else -}}
        <!-- On regular pages, match on terms in the taxonomy "topics". -->
        {{- range $.Params.topics -}}
            {{- if and (in $item.tags (lower .)) (ne $item.draft true) -}}
                {{- $result = $result | append $item -}}
            {{- end -}}
        {{- end -}}
    {{- end -}}
{{- end -}}
{{- end -}}

{{- if gt (len $result) 0 -}}

    <!-- Randomly sort the list each time the site is rebuilt. -->
    {{- $result = (shuffle $result) -}}

    {{- with .Params.recommended_reading.title -}}
        <h3>{{ . }}</h3>
    {{- else -}}
        <h3>{{ $.Site.Params.recommended_reading.title }}</h3>
    {{- end -}}
    
    <!-- Build a dynamic the range. -->  
    {{- $r := printf "first %d (where ( %s | uniq ) " $max $result -}}

    {{- if ($.Site.Params.recommended_books.require_description) -}}
        {{- $r = printf "%s $s" "\".description\" \"gt\" \"\"" -}}
    {{- end -}}

    <!-- Show debug vars only if debug flag is set and we're viewing the dev server. -->
    {{- if and (eq $debug true) (eq .Site.IsServer true) -}}
        <div style="background-color:#ccc; padding:12px;">
        <h4>Debug</h4>
        <div>$.Type: {{ $.Type }}</div>
        <div>$data_set: {{ $data_set }}</div>
        <div>len $result: {{  len $result }}</div>
        <div>$max: {{ $max }}</div>
        </div>
    {{- end -}}

    <!-- 
        Display the data, eliminating possible duplicates.
        TODO: Add option to display records missing descriptions. 
        See: https://discourse.gohugo.io/t/using-a-variable-in-a-range/18109/5
    -->
    {{- range first $max (where ($result | uniq) ".description" "gt" "") -}}
    <hr style="border-top:solid #ccc black;">
    <div class="row">
        <div class="col-md-3">
            <div class="image">

            {{- $book_title := .title -}}


                {{- with .link -}}<a href="{{ . }}" target="_blank" title="View {{ $book_title }}">{{- end -}}
                {{ with .image -}}
                    <img src="{{ . }}" class="img-responsive" alt="book cover" />
                {{- else -}}
                    <img src="{{ $.Site.Params.recommended_reading.default_image }}" class="img-responsive" alt="default book cover" />
                {{- end }}
                {{- with .link -}}</a>{{- end -}}

                {{- with .stars -}}
                    <div class="hidden-xs" style="display:block; text-align:center; font-size:10pt;">
                        {{ range $num := (seq (int .)) }}&#11088;{{ end }}
                    </div>
                {{- end -}}
                
                <p>&nbsp;</p>
            </div>
        </div>

        <div class="col-md-9" style="padding-top:0 margin-top:0;">
            <H4 style="padding-top:0 margin-top:0;">
                {{- with .link -}}<a href="{{ . }}" target="_blank">{{- end -}}   
                {{ $book_title | title | markdownify | safeHTML -}}
                {{- with .subtitle }}: {{ .  | title | markdownify}}{{ end -}}
                {{- with .link -}}</a>{{- end -}}
            </h4>
            
            {{ with .author -}}
                <div class="description">
                    <strong>&ndash; <a href="/authors/">{{ . | markdownify | title }}</a></strong>
                </div>
            {{- end -}}
            
            {{- with .description -}}
                {{- $description := . }}
                {{- $truncate := $.Site.Params.recommended_reading.truncate_description -}}
                {{- if (eq $.Site.Params.recommended_reading.require_description true) -}}
                    <div class="description" style="margin-left:2em; padding-top: 0; padding-bottom: 2px;">
                        {{- if (gt $truncate 0 ) -}}
                            {{ $description | truncate $truncate | markdownify }}
                        {{- else -}}
                            {{ $description markdownify }}
                        {{- end -}}
                    </div>
                {{- end -}}
            {{- end -}}
        
            {{ with .link -}}
                <p class="read-more pull-right"><a href="{{ . }}" target="_blank" class="btn btn-default" style="border-radius:1.5em" title="View {{ $book_title }}">
                {{- if strings.HasSuffix . "pdf" }}<img src="/img/pdf-file-icon.png" width="25px" />{{ end }}&nbsp;View</a>
                </p>
            {{ end -}}
        </div>
    </div>
    {{ end }}
{{- end -}}

4. Add the partial to the template layout files

{{< partial “recommended-reading.html” . }}

As will all partials, don’t forget the dot. That is what passed the .Page context to the partial.

5. Add optional .Page.Params

Page params override the default params in the site config file. The following setting would prevent the partial from showing on the page. If enable is changed to true, the partial will be displayed, the title will be “Read This Now!”, and up to 100 entries will be listed.

recommended_reading: 
	title: Read This Now!
	enable: false
	max: 100


Related Content

Source: https://class.ronliskey.com/study/hugo/hugo-how-to-create-a-recommended-reading-feature-in-hugo/