Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

proposal: text/template: execute "parent" template when inheriting #48266

Closed
paulsmith opened this issue Sep 8, 2021 · 9 comments
Closed

proposal: text/template: execute "parent" template when inheriting #48266

paulsmith opened this issue Sep 8, 2021 · 9 comments

Comments

@paulsmith
Copy link

paulsmith commented Sep 8, 2021

This describes a proposal to change the text/template language to support executing a previously defined template with the same name within a template's current definition, which can be useful when a template inherits from another.

Other template languages, for example, Jinja and Django, support the concept of inheritance, where a base or parent template can be overridden in part by defining sub-template within itself, of which a child template can then redefine (or choose to leave default). This is a natural fit for contexts such as web page templates, where the shell of the page, which defines things like headers and footers, navigation, and stylesheets and common JavaScript includes, remains the same across multiple pages, but individual pages want to set their own title, main body content, and so forth. Go's text/template already supports template inheritance via the {{block}} and {{define}} constructs. Crucially, however, other languages support child templates accessing the default contents of parent blocks (in the case of Jinja and Django, via the super() function or {{ block.super }} tag, respectively) from within the template being overridden. This is a useful property to have, for example, in the case of a web page, where a base template may define a site-wide title that child templates can include while also adding their own specific page title.

By construction, templates may only reside with one association. However, it can be useful to have templates inherit from previously defined ones, as mentioned. When a template has the same name as one that has been previously defined and associated, the current behavior is that the new template entirely replaces the previously defined one. What is proposed then is both a) a way for a template to reference within its definition the template that it is redefining and b) a way for template associations to track successive generations of templates with the same name (because they may inherit to an arbitrary depth), in the order in which they are redefined.

One way to address a) would be to introduce the parent keyword to the template language. In template execution nodes, instead of a name string of the template to be executed, the keyword parent could be used instead to indicate that the template of the same name (i.e., the one currently being defined) but of the previous generation should be executed at that point in the template definition. If we use the parent-child metaphor to describe the relationship between two templates with the same name where one is defined after the other in the same association, then a child could produce the contents of its parent with {{template parent .}}.

Example

To motivate this change and explain its behavior, let's consider an example. We'll review a potentially common use-case for it, that of a base web page template and subsequent overriding templates.

This example has a base template, a template for an index page and an about page, and a template for a contact page nested under the about section.

The base template:

<!DOCTYPE html>
<html>
	<head>
		<title>{{block "title" .}}My website{{end}}</title>
	</head>
	<body>
		<main>
			{{block "content" .}}{{end}}
		</main>
		<footer>
			{{block "footer" .}}
			<p>Thanks for visiting!</p>
			{{end}}
		</footer>
	</body>
</html>

Suppose the base template is parsed in Go and named base.html.

import "html/template"
var base *template.Template
func init() {
	base = template.Must(template.ParseFiles("base.html"))
}

This template has 4 associated templates: base.html, title, content, and footer.

The index page template index.html:

{{template "base.html" .}}
{{define "content"}}<p>Welcome to my website!</p>{{end}}

This template is executed as normal:

func indexHandler(w http.ResponseWriter, r *http.Request) {
	t := template.Must(base.Clone())
	template.Must(t.ParseFiles("index.html"))
	t.ExecuteTemplate(w, "index.html", nil)
}

Which produces, as expected:

<!DOCTYPE html>
<html>
	<head>
		<title>My website</title>
	</head>
	<body>
		<main>
			<p>Welcome to my website!</p>
		</main>
		<footer>
			<p>Thanks for visiting!</p>
		</footer>
	</body>
</html>

This is standard template inheritance as it exists today.

The about page template about.html:

{{template "base.html" .}}
{{define "title"}}About me - {{template parent .}}{{end}}
{{define "content"}}<p>About me ...</p>{{end}}

Notice the use of {{template parent .}} in the definition of the title template.

var about *template.Template
func aboutHandler(w http.ResponseWriter, r *http.Request) {
	about = template.Must(base.Clone())
	template.Must(about.ParseFiles("about.html"))
	about.ExecuteTemplate(w, "about.html", nil)
}

Which produces:

<!DOCTYPE html>
<html>
	<head>
		<title>About me - My website</title>
	</head>
	<body>
		<main>
			<p>About me ...</p>
		</main>
		<footer>
			<p>Thanks for visiting!</p>
		</footer>
	</body>
</html>

Finally, to demonstrate that the use of parent can be nested arbitrarily deep, the contact page contact.html:

{{template "base.html" .}}
{{define "title"}}Contact me - {{template parent .}}{{end}}
{{define "content"}}{{template parent .}}<p>Contact me at ...</p>{{end}}

The intent is to include bits of the about page in the contact page.

func contactHandler(w http.ResponseWriter, r *http.Request) {
	t := template.Must(about.Clone())
	template.Must(t.ParseFiles("contact.html"))
	t.ExecuteTemplate(w, "contact.html", nil)
}

Note that the about template is cloned in this step.

This produces:

<!DOCTYPE html>
<html>
	<head>
		<title>Contact me - About me - My website</title>
	</head>
	<body>
		<main>
			<p>About me ...</p><p>Contact me at ...</p>
		</main>
		<footer>
			<p>Thanks for visiting!</p>
		</footer>
	</body>
</html>

Previous related discussion

In the issue where template inheritance was first discussed, which ultimately led to block being added to the language, this comment mentions accessing the parent template contents from within a child template. However, it does not appear this comment or the notion was ever followed-up on.

@seankhliao
Copy link
Member

cc @robpike

@gopherbot
Copy link

Change https://golang.org/cl/348669 mentions this issue: text/template: add the 'parent' keyword

@robpike
Copy link
Contributor

robpike commented Sep 8, 2021

cc @adg

@ianlancetaylor ianlancetaylor added this to Incoming in Proposals (old) Sep 8, 2021
@bokwoon95
Copy link

fwiw I don't really think text/template or html/template have template inheritence, more like they allow template redefinition (without any parent-child semantics).

If the parent template does need to be accessed by a child template, currently it can be done by giving them different names:

https://play.golang.org/p/kqYH2h1wW24

var parenttext = `
{{ define "content" }}{{ template "content.parent" }}{{ end }}

{{ define "content.parent" -}}
this is the parent
{{ end }}
`

var childtext = `
{{ define "content" -}}
{{ template "content.parent" -}}
this is the child
{{ end }}
`

func main() {
    parent := template.Must(template.New("parent").Parse(parenttext))

    child := template.Must(template.New("parent").Parse(parenttext))
    child = template.Must(child.New("child").Parse(childtext))

    fmt.Println("== parent output ==")
    parent.ExecuteTemplate(os.Stdout, "content", nil)
    fmt.Println("== child output ==")
    child.ExecuteTemplate(os.Stdout, "content", nil)
}

output

== parent output ==
this is the parent
== child output ==
this is the parent
this is the child

@rsc
Copy link
Contributor

rsc commented Oct 13, 2021

It's true that the template package does not have the kind of inheritance found in some other packages. But I'm confused about what this can mean. The "parent" of a "child" would have to be the template that called the child, but that template has already emitted output, so why does it make sense to reinvoke the parent a second time?

I'm not sure that this idea of inheritance fits into the model that we already have for templates.

@rsc
Copy link
Contributor

rsc commented Oct 13, 2021

This proposal has been added to the active column of the proposals project
and will now be reviewed at the weekly proposal review meetings.
— rsc for the proposal review group

@rsc rsc moved this from Incoming to Active in Proposals (old) Oct 13, 2021
@jimmyfrasche
Copy link
Member

Another related issue, with an alternative design: #23774

@rsc rsc moved this from Active to Likely Decline in Proposals (old) Oct 27, 2021
@rsc
Copy link
Contributor

rsc commented Oct 27, 2021

Based on the discussion above, this proposal seems like a likely decline.
— rsc for the proposal review group

@rsc rsc moved this from Likely Decline to Declined in Proposals (old) Nov 3, 2021
@rsc
Copy link
Contributor

rsc commented Nov 3, 2021

No change in consensus, so declined.
— rsc for the proposal review group

@rsc rsc closed this as completed Nov 3, 2021
@golang golang locked and limited conversation to collaborators Nov 3, 2022
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.
Projects
No open projects
Development

Successfully merging a pull request may close this issue.

7 participants