Call a template defined in an included file?

1. Caddy version (caddy version):

v2.4.2 h1:chB106RlsIaY4mVEyq9OQM5g/9lHYVputo/LAX2ndFg=

2. How I run Caddy:

a. System environment:

Archlinux

b. Command:

sudo ./caddy run --config Caddyfile2

d. My complete Caddyfile or JSON config:

Caddyfile2:

{
	debug
}

*:80 {
	log {
		level debug
		output stdout
	}

	root * html
	templates
	file_server
}

3. The problem I’m having:

I am building an html site with templating. But when I include a file, I seem not to be able to define templates in one file call it in another one. This is the case in both directions (defining in the including, calling in the included, and vice versa). For instance, I have two files in a subdirectory of my html root, index.html and include.html:

W0002/include.html:

{{define "footer"}}
<div>
  <p>baz</p>
</div>
{{end}}

bar

W0002/index.html:

{{$title := "Foo" }}
{{include "W0002/include.html"}}

<!DOCTYPE html>
<html>
	<head>
		<title>{{$title}} &mdash; Test Templating</title>
	</head>
	<body>
		<main>
			<h1>{{$title}}</h1>
		</main>
		{{template "footer"}}
	</body>
</html>

When I call W0002/index.html in the browser, I get a 500 error and the log below.

4. Error messages and/or full log output:

2021/06/18 06:51:53.064	DEBUG	http.handlers.file_server	sanitized path join	{"site_root": "html", "request_path": "/W0002/index.html", "result": "html/W0002/index.html"}
2021/06/18 06:51:53.064	DEBUG	http.handlers.file_server	opening file	{"filename": "html/W0002/index.html"}
2021/06/18 06:51:53.065	ERROR	http.log.error.log0	template: /W0002/index.html:13:13: executing "/W0002/index.html" at <{{template "footer"}}>: template "footer" not defined	{"request": {"remote_addr": "[::1]:54456", "proto": "HTTP/1.1", "method": "GET", "host": "localhost", "uri": "/W0002/index.html", "headers": {"Connection": ["keep-alive"], "Sec-Fetch-User": ["?1"], "Sec-Fetch-Mode": ["navigate"], "Sec-Fetch-Dest": ["document"], "Accept-Encoding": ["gzip, deflate, br"], "User-Agent": ["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36"], "Accept": ["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"], "Pragma": ["no-cache"], "Cache-Control": ["no-cache"], "Sec-Ch-Ua": ["\"Chromium\";v=\"91\", \" Not;A Brand\";v=\"99\""], "Sec-Ch-Ua-Mobile": ["?0"], "Dnt": ["1"], "Upgrade-Insecure-Requests": ["1"], "Sec-Fetch-Site": ["none"], "Accept-Language": ["de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7,es;q=0.6,fr;q=0.5,pt;q=0.4"]}}, "duration": 0.000869988, "status": 500, "err_id": "jg5vgs2vm", "err_trace": "templates.(*Templates).executeTemplate (templates.go:315)"}
2021/06/18 06:51:53.065	ERROR	http.log.access.log0	handled request	{"request": {"remote_addr": "[::1]:54456", "proto": "HTTP/1.1", "method": "GET", "host": "localhost", "uri": "/W0002/index.html", "headers": {"Sec-Fetch-User": ["?1"], "Connection": ["keep-alive"], "Accept-Encoding": ["gzip, deflate, br"], "Sec-Fetch-Mode": ["navigate"], "Sec-Fetch-Dest": ["document"], "Sec-Ch-Ua": ["\"Chromium\";v=\"91\", \" Not;A Brand\";v=\"99\""], "Sec-Ch-Ua-Mobile": ["?0"], "Dnt": ["1"], "Upgrade-Insecure-Requests": ["1"], "User-Agent": ["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36"], "Accept": ["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"], "Pragma": ["no-cache"], "Cache-Control": ["no-cache"], "Sec-Fetch-Site": ["none"], "Accept-Language": ["de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7,es;q=0.6,fr;q=0.5,pt;q=0.4"]}}, "common_log": "::1 - - [18/Jun/2021:08:51:53 +0200] \"GET /W0002/index.html HTTP/1.1\" 500 0", "duration": 0.000869988, "size": 0, "status": 500, "resp_headers": {"Server": ["Caddy"], "Etag": ["\"quvxq66q\""], "Content-Type": ["text/html; charset=utf-8"], "Last-Modified": ["Fri, 18 Jun 2021 06:51:42 GMT"], "Accept-Ranges": ["bytes"], "Content-Length": ["242"]}}
2021/06/18 06:51:53.065	ERROR	http.log.access.log0	handled request	{"request": {"remote_addr": "[::1]:54456", "proto": "HTTP/1.1", "method": "GET", "host": "localhost", "uri": "/W0002/index.html", "headers": {"User-Agent": ["Mozilla/5.0 (X11; Linux x86_64) AppleWebKit/537.36 (KHTML, like Gecko) Chrome/91.0.4472.101 Safari/537.36"], "Accept": ["text/html,application/xhtml+xml,application/xml;q=0.9,image/avif,image/webp,image/apng,*/*;q=0.8,application/signed-exchange;v=b3;q=0.9"], "Pragma": ["no-cache"], "Cache-Control": ["no-cache"], "Sec-Ch-Ua": ["\"Chromium\";v=\"91\", \" Not;A Brand\";v=\"99\""], "Sec-Ch-Ua-Mobile": ["?0"], "Dnt": ["1"], "Upgrade-Insecure-Requests": ["1"], "Sec-Fetch-Site": ["none"], "Accept-Language": ["de-DE,de;q=0.9,en-US;q=0.8,en;q=0.7,es;q=0.6,fr;q=0.5,pt;q=0.4"], "Connection": ["keep-alive"], "Sec-Fetch-User": ["?1"], "Sec-Fetch-Mode": ["navigate"], "Sec-Fetch-Dest": ["document"], "Accept-Encoding": ["gzip, deflate, br"]}}, "common_log": "::1 - - [18/Jun/2021:08:51:53 +0200] \"GET /W0002/index.html HTTP/1.1\" 500 0", "duration": 0.000869988, "size": 0, "status": 500, "resp_headers": {"Accept-Ranges": ["bytes"], "Content-Length": ["242"], "Server": ["Caddy"], "Etag": ["\"quvxq66q\""], "Content-Type": ["text/html; charset=utf-8"], "Last-Modified": ["Fri, 18 Jun 2021 06:51:42 GMT"]}}

5. What I already tried:

When I comment out the call {{template "footer"}}, I get the expected result:

bar
Foo

where “bar” is from the included file and ends up right before the doctype definition:


bar
<!DOCTYPE html>
<html>
	<head>
		<title>Foo &mdash; Test Templating</title>
	</head>
	<body>
		<main>
			<h1>Foo</h1>
		</main>
		
	</body>
</html>

(Also, the {{define "footer"}} does have the effect that the “baz part” does not end up in the resulting html, only those parts of the included file that are not in a define block.)

So, to me, this looks like templating as such is working, only calling templates defined elsewhere does not.

For context, what I actually want to achieve is this (maybe there are ways to do it better?):

  • Call a specific page, say W0002/abc.html,
  • in this page, define variables and an inner template that are required for rendering specific contents,
  • include code that is the same for W0002/abc.html, W0002/def.html, W0003/xyz.html from a template templates/base.html. This contains the doctype definition and top-level <html> tags. At one point, it calls an inner template defined in W0002/abc.html,
  • render everything at the W0002/abc.html url.

Now, either I {{define "outer"}} content in templates/base.html and call it with {{template "outer"}} in W0002/abc.html, which gives me the error as above.
Or I directly {{include "templates/base.html"}} the general stuff without putting it in a template definition. Then it errors out when this general stuff in templates/base.html calls the {{template "inner"}}, because that’s defined in where I started from, W0002/abc.html.

6. Links to relevant resources:

Wouldn’t this need to be a relative path to the other file? So just include.html, since they’re in the same directory? Just a guess.

Or you may want to use /W0002/include.html which is a path relative to the defined root instead of relative to the current file. I think.

Thank you for your suggestions. I think it is not a problem of the inclusion not working.

With {{include "./include.html"}}, I get:

2021/06/18 07:38:15.356	ERROR	http.log.error.log0	template: /W0002/index.html:2:2: executing "/W0002/index.html" at <include "./include.html">: error calling include: open html/include.html: no such file or directory

Similarly, with {{include "include.html"}}, I get:

2021/06/18 07:42:50.833	ERROR	http.log.error.log0	template: /W0002/index.html:2:2: executing "/W0002/index.html" at <include "include.html">: error calling include: open html/include.html: no such file or directory

With {{include "/W0002/include.html"}}, I get the same error message as described initially:

2021/06/18 07:39:57.578	ERROR	http.log.error.log0	template: /W0002/index.html:13:13: executing "/W0002/index.html" at <{{template "footer"}}>: template "footer" not defined

IIUC, the inclusion of “bar” when I remove the template call and just stick with file inclusion indicates that it knows which file to process.

Reading the code for include here:

I think how it works is the included file gets executed as a template in its own context, then the resulting bytes are returned. So meta stuff like defines don’t bubble up, I think.

As a workaround, I guess you can wrap your include with the define instead? But that might not have the same effect you wanted.

I think it might be possible to copy the defined templates back into the parent, but I’m not sure if that’s necessarily desirable because then includes could clobber the defined templates in the parent if they have the same name I guess.

Anyways, /cc @matt who will understand the nuances better.

Thank you. I have elaborated my original idea, resp. actual requirement a bit at the end of point 5 in the original posting.

The problem seems to be that I start from a specific page, include general stuff, but then, in the middle of the general stuff, I need specific knowledge again. Maybe a workaround would be to not work with valid and complete html elements being templated and passed around, but with “everything that comes before” the specific stuff and “everything that comes after”, which would be, as it were, “halves” of a valid html document.

I would assume this must surely be a requirement that other people also had?

If someone wanted to try a version of include that doesn’t execute the template separately and instead brought it in unevaluated, feel free – it’s not something that’s a priority for me to work on right now (because I haven’t needed it, nor seen a lot of reuqests for it), but you’re welcome to experiment and submit a PoC as a PR.

(And this is where feature requests usually drop off, honestly. It’s easy to ask for a feature but very few people want to do the work to implement it. Another option is companies can fund its development.)

I hope I didn’t come across as rude or just demanding new features to suit my needs. I wasn’t even thinking of it as a feature request, but rather asking how other people with similar requirements (who I maybe wrongly assumed would exist) have solved or worked around this. Would you say, for instance, something along the lines of the following is what, in your experience, people are doing? Is it a recommendable approach, or will it fail once things become more complex?:

include_one.html:

<!DOCTYPE html>
<html>
	<head>

include_two.html:

	</head>
	<body>
		<main>

include_three.html:

      <div>
        <p>baz</p>
      </div>
    </main>
	</body>
</html>

my_page.html:

{{$title := "Foo" }}

{{include "include_one.html"}}

<title>{{$title}} &mdash; Test Templating</title>

{{include "include_two.html"}}

<h1>{{$title}}</h1>

{{include "include_three.html"}}

When I then call my_page.html, this works, but it seems quite cumbersome once it’s real life web pages… Or maybe I am conceptualising the problem in a completely wrong way?

On the other hand, if you say it makes sense to see it the way I do, makes sense as a requirement, and also as a feature request, I can try if I can get something together in terms of PoC. I am really most uncertain about whether I am thinking about it in the right way…

Digging some more, I may have found both a PoC and another approach in an old, closed issue (#2094). Will report back.

Oh sorry, you’re good – I couldn’t tell, it looked like a feature request, and I just wanted to let you know the reality that many times feature requests go unimplemented because either the funding isn’t there or people aren’t willing to contribute the feature. (Sometimes I reject feature requests from the core of Caddy, but most of them could be implemented in other repositories as modules.)

Which, I guess you can see, in that issue you just linked to:

No longer interested in this feature

So, it sounds useful; but for the record, the way you’re doing it above (3 includes) isn’t too uncommon in my experience. I sometimes use 3-4 includes per page to bring in header, footer, nav, etc.

I think the approach that @tw4452852 mentioned in the issue I have linked to above will work for me:

specific1.html:

{{$content := `
<div class="col-md-12">
  <div id="main">
    <p>Lorem ipsum ...</p>
  </div>
</div>
`}}

{{$spec_info := dict "id" "W0002" "author" "Foo Bar" "title" "Confessions of a Junior Developer" "year" "2021" "content" $content}}

{{include "template.html" $spec_info }}

template.html:

{{$map := index .Args 0}}
{{$id := $map.id}}
{{$author := $map.author}}
{{$title := $map.title}}
{{$year := $map.year}}
{{$content := $map.content}}
{{$external_file := (list "/" $id "/" $id "_toc.html" | join "")}}

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8"/>
        <title>{{$author}}: {{$title}} - My Homepage</title>
        <meta name="author"      content="{{$author}}"/>
        <meta name="description" content="Reading view of {{$author}}: {{$title}}"/>
        {{with $map.prev}}
            <link rel="prev"     href="{{.}}"/>
        {{end}}
        {{with $map.next}}
            <link rel="next"     href="{{.}}"/>
        {{end}}
    </head>

    <body id="body">
        <div class="container">
            <div class="blabla">More boilerplate: Menus, Javascript etc. with one or another bit from $spec-info sprinkled in.</div>
            <!-- Main text -->
            <div id="wrapperWork">
                {{ $content }}
            </div>
        </div>
    </body>

</html>

In this way, I can accomodate very complex html and still need to have only two files: all the specific/varying information is in the specific*.html files, most of the general stuff is in template.html. Also, all the html tags are opened and closed again nearby and the files have more or less valid html markup (unlike in the include_one.html/include_two.html/include_three.html/… approach described above). The only not-so-elegant aspect is that there is a huge html snippet passed in a variable $content, but maybe that’s not even inelegant; anyway, that’s a price I am glad to pay.

Thanks for helping me think through this.

PS.
For what it’s worth, and maybe it’s already obvious: the huge html snippet in the “specific” pages is generated automatically from XML source files.

2 Likes

This topic was automatically closed after 30 days. New replies are no longer allowed.