Skip to content

Commit 5e41864

Browse files
committed
feature #985 [TwigComponent] Add documentation about passing blocks to embedded components (sneakyvv, weaverryan)
This PR was merged into the 2.x branch. Discussion ---------- [TwigComponent] Add documentation about passing blocks to embedded components | Q | A | ------------- | --- | Bug fix? | no | New feature? | no | Tickets | Fix #... | License | MIT This adds documentation of the feature added in #920 Commits ------- 0bbb2b8 Fixing rst syntax acaa41a proofing ae5a458 Add simple example of outerBlocks without nested components to ease in 111ac3a Allow referring to outerblocks from non-nested components b0913d4 Fix typos 8e1d94b Moving away from "embedded component" wording in docs 305661c Added suggestions of PR feedback 1e85030 Add version info 444686c Add documentation about passing blocks to embedded components and their context
2 parents 7b16cef + 0bbb2b8 commit 5e41864

File tree

7 files changed

+167
-12
lines changed

7 files changed

+167
-12
lines changed

src/TwigComponent/doc/index.rst

Lines changed: 122 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -765,13 +765,15 @@ independently. If you're using `Live Components`_, then there
765765
*are* some guidelines related to how the re-rendering of parent and
766766
child components works. Read `Live Nested Components`_.
767767

768-
Embedded Components
769-
-------------------
768+
.. _embedded-components:
769+
770+
Passing Blocks to Components
771+
----------------------------
770772

771773
.. tip::
772774

773-
Embedded components (i.e. components with blocks) can be written in a more
774-
readable way by using the `Component HTML Syntax`_.
775+
The `Component HTML Syntax`_ allows you to pass blocks to components in an
776+
even more readable way.
775777

776778
You can write your component's Twig template with blocks that can be overridden
777779
when rendering using the ``{% component %}`` syntax. These blocks can be thought of as
@@ -830,7 +832,122 @@ The ``with`` data is what's mounted on the component object.
830832

831833
.. note::
832834

833-
Embedded components *cannot* currently be used with LiveComponents.
835+
The ``{% component %}`` syntax *cannot* currently be used with LiveComponents.
836+
837+
Inheritance & Forwarding "Outer Blocks"
838+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
839+
840+
.. versionadded:: 2.10
841+
842+
The ``outerBlocks`` variable was added in 2.10.
843+
844+
The content inside a ``{% component ... %}`` tag should be viewed as living in
845+
its own, independent template, which extends the component's template. This means that
846+
any blocks that live in the "outer" template are not available inside the ``{% component %}`` tag.
847+
However, a special ``outerBlocks`` variable is added as a way to refer to those blocks:
848+
849+
.. code-block:: html+twig
850+
851+
{% extends 'base.html.twig' %}
852+
853+
{% block call_to_action %}<strong>Attention! Free Puppies!</strong>{% endblock %}
854+
855+
{% block body %}
856+
{% component Alert %}
857+
{% block content %}{{ block(outerBlocks.call_to_action) }}{% endblock %}
858+
{% endcomponent %}
859+
{% endblock %}
860+
861+
The ``outerBlocks`` variable becomes specially useful with nested components. For example,
862+
imagine we want to create a ``SuccessAlert`` component that's usable like this:
863+
864+
.. code-block:: html+twig
865+
866+
{# templates/some_page.html.twig #}
867+
{% component SuccessAlert %}
868+
{% content %}We will successfully <em>forward</em> this block content!{% endblock %}
869+
{% endcomponent %}
870+
871+
But we already have a generic ``Alert`` component, and we want to re-use it:
872+
873+
.. code-block:: html+twig
874+
875+
{# templates/Alert.html.twig #}
876+
<div class="alert alert-{{ type }}">
877+
{% block content %}{% endblock %}
878+
</div>
879+
880+
To do this, the ``SuccessAlert`` component can grab the ``content`` block that's passed to it
881+
via the ``outerBlocks`` variable and forward it into ``Alert``:
882+
883+
.. code-block:: twig
884+
885+
{# templates/SuccessAlert.html.twig #}
886+
{% component Alert with { type: 'success' } %}
887+
{% block content %}{{ blocks(outerBlocks.content) }}{% endblock %}
888+
{% endcomponent %}
889+
890+
Note that to pass a block multiple components down, each component needs to pass it.
891+
892+
Context / Variables Inside of Blocks
893+
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
894+
895+
The content inside of the ``{% component ... %}`` should be viewed as living in its own,
896+
independent template, which extends the component's template. This has a few interesting consequences.
897+
898+
First, once you're inside of ``{% component ... %}``, the ``this`` variable represents
899+
the component you're now rendering *and* you have access to all of that component's variables:
900+
901+
.. code-block:: twig
902+
903+
{# templates/SuccessAlert.html.twig #}
904+
{{ this.someFunction }} {# this refers to SuccessAlert #}
905+
906+
{% component Alert with { type: 'success' } %}
907+
{{ this.someFunction }} {# this refers to Alert! #}
908+
909+
{{ type }} {# references a "type" prop from Alert #}
910+
{% endcomponent %}
911+
912+
Conveniently, in addition to the variables from the ``Alert`` component, you still have
913+
access to whatever variables are available in the original template:
914+
915+
.. code-block:: twig
916+
917+
{# templates/SuccessAlert.html.twig #}
918+
{% set name = 'Fabien' %}
919+
{% component Alert with { type: 'success' } %}
920+
Hello {{ name }}
921+
{% endcomponent %}
922+
923+
Note that ALL variables from upper components (e.g. ``SuccessAlert``) are available to lower
924+
components (e.g. ``Alert``). However, because variables are merged, variables with the same name
925+
are overridden by lower components (that's also why ``this`` refers to the embedded, or
926+
"current" component).
927+
928+
The most interesting thing is that the content inside of ``{% component ... %}`` is
929+
executed as if it is "copy-and-pasted" into the block of the target template. This means
930+
you can access variables from the block you're overriding! For example:
931+
932+
.. code-block:: twig
933+
934+
{# templates/SuccessAlert.html.twig #}
935+
{% for message in messages %}
936+
{% block alert_message %}
937+
A default {{ message }}
938+
{% endblock %}
939+
{% endfor %}
940+
941+
When overriding the ``alert_message`` block, you have access to the ``message`` variable:
942+
943+
.. code-block:: twig
944+
945+
{# templates/some_page.html.twig #}
946+
{% component SuccessAlert %}
947+
{% block alert_message %}
948+
I can override the alert_message block and access the {{ message }} too!
949+
{% endblock %}
950+
{% endcomponent %}
834951
835952
Component HTML Syntax
836953
---------------------

src/TwigComponent/src/BlockStack.php

Lines changed: 0 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -36,13 +36,6 @@ public function convert(array $blocks, int $targetEmbeddedTemplateIndex): array
3636
// identifies the block definition.
3737
$hostEmbeddedTemplateIndex = $this->findHostEmbeddedTemplateIndex();
3838

39-
if (0 === $hostEmbeddedTemplateIndex) {
40-
// If there is no embedded template index, that means we're in a normal template.
41-
// It wouldn't make sense to make these available as outer blocks,
42-
// since the block is already printed in place.
43-
continue;
44-
}
45-
4639
// Change the name of outer blocks to something unique so blocks of nested components aren't overridden,
4740
// which otherwise might cause a recursion loop when nesting components.
4841
$newName = self::OUTER_BLOCK_PREFIX.$blockName.'_'.mt_rand();
Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,22 @@
1+
<?php
2+
3+
/*
4+
* This file is part of the Symfony package.
5+
*
6+
* (c) Fabien Potencier <[email protected]>
7+
*
8+
* For the full copyright and license information, please view the LICENSE
9+
* file that was distributed with this source code.
10+
*/
11+
12+
namespace Symfony\UX\TwigComponent\Tests\Fixtures\Component;
13+
14+
use Symfony\UX\TwigComponent\Attribute\AsTwigComponent;
15+
16+
/**
17+
* @author Bart Vanderstukken <[email protected]>
18+
*/
19+
#[AsTwigComponent]
20+
final class BasicComponent
21+
{
22+
}
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
{% block body %}{% endblock %}
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
<div>
2+
{% block content %}{% endblock %}
3+
</div>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
{% extends 'base.html.twig' %}
2+
3+
{% block foo %}Hello world!{% endblock %}
4+
5+
{% block body %}
6+
<twig:BasicComponent>
7+
{{ block(outerBlocks.foo) }}
8+
</twig:BasicComponent>
9+
{% endblock %}

src/TwigComponent/tests/Integration/EmbeddedComponentTest.php

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -80,6 +80,16 @@ public function testBlockCanBeUsedWithinNestedViaTheOuterBlocks(): void
8080
);
8181
}
8282

83+
/**
84+
* Rule 5 bis: A block inside an extending template can be use inside a component in that template and is NOT rendered in the original location.
85+
*/
86+
public function testBlockCanBeUsedViaTheOuterBlocks(): void
87+
{
88+
$output = self::getContainer()->get(Environment::class)->render('embedded_component_blocks_outer_blocks_extended_template.html.twig');
89+
$this->assertStringContainsStringIgnoringIndentation('<div>Hello world!</div>', $output);
90+
$this->assertStringNotContainsString("Hello world!\n<div", $output);
91+
}
92+
8393
/**
8494
* Rule 8: Defining a block for a component overrides any default content that block has in the component's template.
8595
* This also means that when passing block down that you will lose that default content.

0 commit comments

Comments
 (0)