eleventy icon indicating copy to clipboard operation
eleventy copied to clipboard

What is the best way to pass a block from a layout to a parent layout?

Open nhoizey opened this issue 4 years ago • 16 comments

I'm using Nunjucks layouts, and the layout attribute in Front Matter for layout chaining, so that a page.njk layout uses a base.njk layout.

As I read it, the Addendum about existing Templating features states that “Eleventy’s layout system exists a layer above Nunjucks' Template Inheritance exposed with {% extends %}“.

So I thought {% extends %} was implicit when using layout chaining, but a {% block %} defined in my page.njk layout never arrives into base.njk.

I tried to add an explicit {% extends %} like I saw in Phil Hawksworth's code, but I get the base.njk included twice, kind of recursively

If I remove the layout attribute from the Front Matter of the page.njk layout, its content doesn't arrive into base.njk anymore.

I must be missing something obvious, so how is it supposed to work?

Thanks

nhoizey avatar Jan 13 '20 10:01 nhoizey

If I recall correctly, in Eleventy, Nunjucks happily passes content through to the specified layout without using extends if you are just populating the content. When you have multiple blocks that you wish to pass through to a layout, then you need to use extends.

At least, this seems to work for me™

I'm struggling to find the documentation I thought had lead me to that conclusion.

philhawksworth avatar Jan 13 '20 11:01 philhawksworth

@philhawksworth what I see in your aforementioned post.njk layout is that you have an explicit {% extends … %}, but no Front Matter and no content outside {% block … %}s.

So it looks like it's either one or the other:

  • Eleventy layout chaining with Front Matter layout property OR
  • an {% extends … %} and a series of {% block … %}s

The later seems to be the best (or even the only way) to "pass" an HTML fragment (a "block") to the parent (extended) layout.

nhoizey avatar Jan 14 '20 20:01 nhoizey

Oh, looks like @zachleat already answered this is "by design": https://github.com/11ty/eleventy/issues/834#issuecomment-569474008

That's not why I understood when reading the Addendum about existing Templating features, maybe we could rephrase it.

nhoizey avatar Jan 14 '20 21:01 nhoizey

Aha. Yeah, there it is. Nice one @nhoizey!

philhawksworth avatar Jan 15 '20 11:01 philhawksworth

Default support for blocks would be super nice.

In the mean time, I've set up these tags to recreate similar functionality, maybe it's of help to someone else.

module.exports = function (eleventyConfig) {

    const layoutblocks = [];

    eleventyConfig.addShortcode('renderlayoutblock', function(name) {
        return (this.page.layoutblock || {})[name];
    });

    eleventyConfig.addPairedShortcode('layoutblock', (content, name) {
        if (!this.page.layoutblock) this.page.layoutblock = {};
        this.page.layoutblock[name] = content;
    });

}

Define a layoutblock block with name foo like this in the layout template:

{% renderlayoutblock 'foo' %}

Set the contents of layoutblock with name foo in a document like this:

{% layoutblock 'foo' %}
Hello World
{% endlayoutblock %}

rikschennink avatar Feb 01 '21 10:02 rikschennink

@rikschennink Thanks for the suggestion! this.page.layoutblock doesn't work because this.page is undefined. And the variable const layoutblocks = [] is obsolete as it's not used. But this worked for me:

module.exports = function (eleventyConfig) {

    eleventyConfig.addShortcode('renderlayoutblock', function(name) {
        return (this.layoutblock || {})[name];
    });

    eleventyConfig.addPairedShortcode('layoutblock', (content, name) {
        if (!this.layoutblock) this.layoutblock = {};
        this.layoutblock[name] = content;
    });
}

gluecksmensch avatar Feb 17 '21 11:02 gluecksmensch

@gluecksmensch good one.

Previous iteration I stored data in const layoutblocks = [] but that didn't work ( obviously :D ) forgot to remove.

I guess with my templates page is defined, didn't realise this wasn't always the case, nice improvement 👍

rikschennink avatar Feb 17 '21 11:02 rikschennink

@rikschennink @gluecksmensch as we showed before in the thread, you can use Nunjucks' {% extend … %} directly, without additional shortcode, but you can't use Eleventy's layout Front Matter with it.

I do this here for example: https://github.com/nhoizey/precious-prana.com/blob/master/src/_includes/layouts/evenement.njk#L2

And here's the base.njk layout definig blocks: https://github.com/nhoizey/precious-prana.com/blob/master/src/_includes/layouts/base.njk

nhoizey avatar Feb 22 '21 09:02 nhoizey

11ty newbie here. I hit this same issue, and followed the pattern from @nhoizey, however all the block sections are missing in the generated html.

index.njk:

{% extends "layout.njk" %}

{% block title %} Home page in HTML {% endblock %}

{% block header %}
    <meta charset="utf-8" />
{% endblock %}

{% block content %}
<p>
    Welcome to my 11ty website.
</p>

{% include "footer.njk" %}

{% endblock %}

_includes/layout.njk

<!DOCTYPE html>
<html>
<head>
    <title>{{ title }}</title>
    <style>
        .active { background: yellow }
    </style>
    {{ header | safe }}
</head>
<body>
    <div>
        <a href="/" class="{{ 'active' if '/' == page.url }}">Home</a>
        <a href="/README" class="{{ 'active' if '/README/' == page.url }}">README</a>
    </div>
    {{ content | safe }}
</body>
</html>

Generated index.html:

<!DOCTYPE html>
<html>
<head>
    <title></title>
    <style>
        .active { background: yellow }
    </style>
    
</head>
<body>
    <div>
        <a href="/" class="active">Home</a>
        <a href="/README" class="">README</a>
    </div>
    
</body>
</html>

Full source code is here: https://github.com/surferjeff/scratch/tree/main/hello-11ty

surferjeff avatar Sep 03 '22 05:09 surferjeff

@surferjeff Looking at @nhoizey's repo, I think you need to define your blocks in your _includes/layout.njk file, similar to this:

https://github.com/nhoizey/precious-prana.com/blob/83380d5a9e489d1e4c25c4077c68ac2461bc16a4/src/_includes/layouts/base.njk?rgh-link-date=2021-02-22T09%3A38%3A39Z#L27-L30

-    <title>{{ title }}</title>
+    {% block title %}
+    <title>{{ title }}</title>
+    <meta property="og:title" content="{{ title }}" />
+    {% endblock %}

Then you can either set the title via front matter, or override it by explicitly setting the {% block title %} block:

{% block title %}
<title>Precious Prana, faire pétiller</title>
<meta property="og:title" content="Precious Prana, faire pétiller" />
{% endblock %}

(see https://github.com/nhoizey/precious-prana.com/blob/f7edfa0b1da0e36e9c52115dd2228a6e9629e899/src/_includes/layouts/homepage.njk#L3-L6)

pdehaan avatar Sep 03 '22 15:09 pdehaan

@surferjeff Try something like this:

<!DOCTYPE html>
<html>
<head>
    {% block title %}
    <title>{{ title }}</title>
    {% endblock %}
    <style>
        .active { background: yellow }
    </style>
    {% block header %}
    {{ header | safe }}
    {% endblock %}
</head>
<body>
    <div>
        <a href="/" class="{{ 'active' if '/' == page.url }}">Home</a>
        <a href="/README" class="{{ 'active' if '/README/' == page.url }}">README</a>
    </div>
    {% block content %}
    {{ content | safe }}
    {% endblock %}
</body>
</html>

And…

{% extends "layout.njk" %}

{% block title %}
    <title>Home page in HTML</title>
{% endblock %}

{% block header %}
    <meta charset="utf-8" />
{% endblock %}

{% block content %}
    <p>Welcome to my 11ty website.</p>
    {% include "footer.njk" %}
{% endblock %}

Or set the title via front matter, if you don't want to set the <title> tags and override other stuff:

---
title: Override title
---

{% extends "layout.njk" %}

{% block header %}
    <meta charset="utf-8" />
{% endblock %}

{% block content %}
    <p>Welcome to my 11ty website.</p>
    {% include "footer.njk" %}
{% endblock %}

pdehaan avatar Sep 03 '22 15:09 pdehaan

Ah, I see now. Thanks for the quick replies!

surferjeff avatar Sep 03 '22 16:09 surferjeff