Accessing params from including page when using RenderShortcodes

Summary

I have a whole big pile of pages that are the same, except that they
have links and names and etc that are unique-per-page.

I would like to be able to have many content pages, each of which sets
some page specific parameters in its front matter and then pulls in a
big shared Markdown file that references those parameters.

Setup

I’m using

$ ~/bin/hugo env
hugo v0.125.0-a32400b5f4e704daf7de19f44584baf77a4501ab+extended darwin/amd64 BuildDate=2024-04-16T15:04:41Z VendorInfo=gohugoio
GOOS="darwin"
GOARCH="amd64"
GOVERSION="go1.22.2"
github.com/sass/libsass="3.6.5"
github.com/webmproject/libwebp="v1.3.2"

on a reasonably up to date Mac.

Demo/example/test case

There’s a demo/test case project here on GitHub

Details

I have a whole big pile of pages that are the same, except that they
have links and names and etc that are unique-per-page.

I would like to be able to have many content pages, each of which sets
some page specific parameters in its front matter and then pulls in a
big shared Markdown file that references those parameters.

The big shared Markdown file is complex enough that it’s better to use
Markdown than to try to manage the corresponding HTML by hand. I
think that this means that I can’t solve my problem with a partial or
template. In particular, the big shared Markdown file includes
shortcodes.

I almost have a solution via RenderShortcodes (see below), but it
doesn’t quite work, the included page can’t access parameters from the
including page.

What I have in mind is something like dynamic scoping in a programming
language, searching back up through the call stack at run time to find
the value of a variable.

My immediate need would be satisfied if I could access the calling
page’s parameters. Alternatively, being able to provide a map of
values through the shortcode to RenderShortcodes could also work.

This is a little test case that has the structure that I want. It
defines an “include” shortcode that invokes RenderShortcodes and also
outputs the calling and called pages’ parameters. There are two
pages, p1.md is the “includer” (it generates output) and p2.md is
the “includee”.

This demo site started life as the test data for the
TestRenderShortcodesBasic test for the RenderShortcodes feature. I
deleted some bits and added others to illustrate my point.

Running the demo/test case

Run hugo in the top level directory.

You should see an error like (ellipses are bits from my directory
layout):

ERROR Param "p1" not found: [...]/content/p2.md:9:15"

If you read through the resulting public/p1/index.html you can see
that the p1 parameter is available in p1.md but is not in p2.md.

A related, minor problem

I can’t figure out how to avoid having p2.md rendered as a page of
it’s own. I’ve tried:

  • Adding it to ignoreFiles (or the module equiv.) keeps the page
    from being generated, but also seems to keep it from being included
    in p1.md.
  • I’ve put it in /assets and tried accessing it via Resources, but
    that doesn’t return a Page so RenderShortcodes isn’t available.

Thoughts?

Interesting links:

This works:

git clone --single-branch -b hugo-forum-topic-49366 https://github.com/jmooring/hugo-testing hugo-forum-topic-49366
cd hugo-forum-topic-49366
hugo server

With one caveat… headings and footnotes in the included page will not contribute to the including page’s table of contents or footnotes, respectively. You can always use something like tocbot.js to create the TOC on the including page.

The published site excludes P2:

public/
├── p1/
│   └── index.html
├── favicon.ico
└── index.html

The trick in all of this is to pass the RawContent of the included page through .Page.RenderString within the context of the including page.

Thank you for this. I can use it to solve the problem at hand.

One note, this only gives access to the calling page’s front matter. Fields/params defined in the included file aren’t available. That’s not a problem for my immediate need (yay!), but I wonder if there’s a way (or a feature request) for accessing fields/params back through the “call stack” a la dynamic scoping in a programming language.

I also learned about setting build fields (list, render) in front matter to keep the included page from being rendered on its own. Thank you for this too!

To close the loop, I changed my include shortcode to use jmooring’s code:

{{- with $page := .Get 0 }}
  {{- with site.GetPage $page }}
    {{- .RawContent | $.Page.RenderString }}
  {{- else }}
    {{- errorf "The %q shortcode was unable to get page %s: see %s" $.Name $page $.Position  }}
  {{- end }}
{{- else }}
  {{- errorf "The %q shortcode requires a positional parameter, the logical path to the child page: see %s" .Name  .Position  }}
{{- end -}}

and I added

build:
  list: never
  render: never

to the frontmatter in my included file to keep it from being listed/rendered.

Another bit of followup…

I have this working in our real project after resolving one stumbling block, an infinite recursion problem.

The big file that’s being included had several links to other sections in the same document, using the sections’ generated header ids. The links were originally in markdown format, [some words](#anchor).

After a bunch of fiddling around, I found that changing them to [some words]({{< ref "#anchor" >}}) works, although it generates a warning that the ref should be used with {{% ... %}}.

Using {{% ... %}} leads back to the infinite recursion problem.

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