Embed Implicit Default
Twig Embed with Implicit Default Block
TLDR
{% embed 'wrapper.twig' %}
Default block content
{% endembed %}
{# wrapper.twig #}
<div class='bg-slate-300'>
<div class='max-w-7xl py-16 '>
<p>{% block default '' %}</p>{# --> renders "Default block content" #}
</div>
</div>
Warning This extension introduces a breaking change to twig. However, it will most likely not affect too many of your files, is probably quite easy to work around, and is most definitely worth it. See details.
Contents
Rationale
In vanilla twig, to override a slot of a parent template via an embed, we need to declare a block.
{% embed 'wrapper.twig' %}
{% block content %}
<p>This is default block content</p>
{% endblock %}
{% endembed %}
{# example wrapper.twig #}
<div class='bg-slate-300'>
<div class='max-w-7xl py-16 '>
{% block content %}{% endblock %}
</div>
</div>
When following best practices to keep code modular and dry, you'll typically create components for various code chunks, but this results in rather bloated and unreadable markup, especially as you start to nest (and this is only two levels!):
{% embed 'wrapper.twig' %}
{% block default %}
<p>
This is a CTA, so naturally it includes a
{% embed 'button.twig' %}
{% block default %}Button{% endblock %}
{% endembed %}
</p>
{% endblock %}
{% endembed %}
The explicit block declarations are handy for extending layouts (where you'll need 'header', 'footer', and 'sidebar', for example), but in most cases, embed
is probably just being used to include components where you need to pass in some content, and for these cases, typically only one content slot is required.
So we've sacrificed simplicity of the base case for greater flexibility ... but what if we could have both?
Usage
This twig extension allows the use of an implicit default block in embeds:
{% embed 'wrapper.twig' %}
<p>This is default block content</p>
{% endembed %}
{# same as this, which still will work #}
{% embed 'my_subtemplate.twig' %}
{% block default %}
<p>This is default block content</p>
{% endblock %}
{% endembed %}
The longer example above could become
{% embed 'wrapper.twig' %}
<p>
This is a CTA, so naturally it includes a
{% embed 'button.twig' %}Button{% endembed %}
</p>
{% endembed %}
To specify where that default content goes in the component template, follow the wrapper.twig
example above, or, if your default block has no/simple default content, you can use twig's block shortcut syntax:
{% block default %}{% endblock %}
{# same as #}
{% block default '' %}
This works because all this extension does is parse the initial content within the embed into a block named 'default'.
Notes
The implicit default content can be combined with (any number of) named blocks:
{% embed 'wrapper.twig' %}
<p>This is default block content</p>
{% block special_slot %}
Special content
{% endblock %}
{% endembed %}
{# example wrapper.twig #}
<div class='bg-slate-300'>
<div class='max-w-7xl py-16 '>
{% block default '' %}
{% block special_slot '' %}
</div>
</div>
The implicit default content needs to be placed before any explicit blocks (See this issue):
{# Will throw error: "A template that extends another one cannot include content outside Twig blocks." #}
{% embed 'wrapper.twig' %}
{% block special_slot %}
Special content
{% endblock %}
<p>This is default block content</p>
{% endembed %}
If you have both implicit default content and an explicit default block, an error will be thrown (just as when you declare two blocks with the same name):
{# Will throw error: "The block 'default' has already been defined" #}
{% embed 'wrapper.twig' %}
<p>This is default block content</p>
{% block default %}
Other default content???
{% endblock %}
{% endembed %}
Breaking Change
Twig, by default, does not allow content that outputs within embed
tags but outside block
tags. In this respect, this extension doesn't change any defined behavior; it only defines what previously was an error. However, twig does allow non-outputting content in such a context, for example:
{% embed 'wrapper.twig' %}
{% set a = 'foo bar' %}
{% block content %}
<p>{{ a }}</p>
{% endblock %}
{% endembed %}
Using this extension, that set
tag would get absorbed as part of the default block, and a
would be undefined in the following block.
In this case, the solution is simple: move the set
tag outside the embed
scope. I suspect most instances of this change will be equally straightforward to adapt for this extension. If you run into something more complex, please file an issue and I will try to deal with or document that case.
Getting Started
composer require acalvino/craft-embed-implicit-default
./craft plugin/install embed-implicit-default
Or just find it on the plugin store and install from there.
To install this plugin, copy the command above to your terminal.
This plugin doesn't have any reviews.