Reputation: 2191
there are several questions on how to append a block with twig. The answer is always using inheritance and use and then call the parent(). Somehow I don't know how this works in my specific case:
base.html.twig
{% block content %}{% endblock %}
{% block appendable %}
{% endblock %}
{% block another_appendable %}
{% endblock %}
site.html.twig
{% extends base.html.twig %}
{% block content %}
{# Here use/include/embed, i dont know #}
{% use sub1.html.twig %}
{% use sub2.html.twig %}
{% endblock content %}
sub1.html.twig
Some content that should be directly rendered
{% block appendable %}
some stuff that should be added to appendable
{% endblock %}
{% block another_appendable %}
This content should be added to "another appendable"
{% endblock %}
sub2.html.twig
{% block appendable %}
additional stuff that should be appended
{% endblock %}
I would like that both contents from sub1 and sub2 are rendered within appendable. How could I achieve that?
Upvotes: 2
Views: 6618
Reputation: 74018
If you only define blocks in the sub templates, you may utilize the block
function and substitute explicitly:
base.html.twig
{% block content %}{% endblock %}
{% block appendable %}{% endblock %}
{% block another_appendable %}{% endblock %}
site.html.twig
{% extends base.html.twig %}
{% block appendable %}
{{ block('appendable', 'sub1.html.twig') }}
{{ block('appendable', 'sub2.html.twig') }}
{% endblock %}
{% block another_appendable %}
This content should be added to "another appendable"
{{ block('another_appendable', 'sub1.html.twig') }}
{% endblock %}
sub1.html.twig
{% block appendable %}
some stuff that should be added to appendable
{% endblock %}
{% block another_appendable %}
This content should be added to "another appendable"
{% endblock %}
sub2.html.twig
{% block appendable %}
additional stuff that should be appended
{% endblock %}
Upvotes: 0
Reputation: 3370
I'd like to share my own solution for this problem. I implemented my own Twig extension, which implements custom tag widget
(I used Twig tag embed
as a source).
WidgetNode.php:
namespace Artprima\Bundle\TwigWidgetTagBundle\Twig\Extension;
/**
* Class WidgetNode
*
* @author Denis V
*
* @package Artprima\Bundle\TwigWidgetTagBundle\Twig\Extension
*/
class WidgetNode extends \Twig_Node_Include
{
// we don't inject the module to avoid node visitors to traverse it twice (as it will be already visited in the main module)
public function __construct($filename, $index, \Twig_Node_Expression $variables = null, $only = false, $ignoreMissing = false, $lineno, $tag = null)
{
parent::__construct(new \Twig_Node_Expression_Constant('not_used', $lineno), $variables, $only, $ignoreMissing, $lineno, $tag);
$this->setAttribute('filename', $filename);
$this->setAttribute('index', $index);
}
/**
* Compiles the node to PHP.
*
* @param $compiler \Twig_Compiler A Twig_Compiler instance
*/
public function compile(\Twig_Compiler $compiler)
{
$compiler->addDebugInfo($this);
if ($this->getAttribute('ignore_missing')) {
$compiler
->write("try {\n")
->indent()
;
}
$this->addGetTemplate($compiler);
$compiler->raw('->displayBlock(');
$compiler->string('widget');
$compiler->raw(', ');
$this->addTemplateArguments($compiler);
$compiler->raw(");\n");
if ($this->getAttribute('ignore_missing')) {
$compiler
->outdent()
->write("} catch (Twig_Error_Loader \$e) {\n")
->indent()
->write("// ignore missing template\n")
->outdent()
->write("}\n\n")
;
}
}
protected function addGetTemplate(\Twig_Compiler $compiler)
{
$compiler
->write("\$this->env->loadTemplate(")
->string($this->getAttribute('filename'))
->raw(', ')
->string($this->getAttribute('index'))
->raw(")")
;
}
}
WidgetTokenParser.php:
namespace Artprima\Bundle\TwigWidgetTagBundle\Twig\Extension;
/**
* Class WidgetTokenParser
*
* @author Denis V
*
* @package Artprima\Bundle\TwigWidgetTagBundle\Twig\Extension
*/
class WidgetTokenParser extends \Twig_TokenParser_Include
{
/**
* Parses a token and returns a node.
*
* @param \Twig_Token $token A Twig_Token instance
*
* @return \Twig_NodeInterface A Twig_NodeInterface instance
*/
public function parse(\Twig_Token $token)
{
$stream = $this->parser->getStream();
$parent = $this->parser->getExpressionParser()->parseExpression();
list($variables, $only, $ignoreMissing) = $this->parseArguments();
// inject a fake parent to make the parent() function work
$stream->injectTokens(array(
new \Twig_Token(\Twig_Token::BLOCK_START_TYPE, '', $token->getLine()),
new \Twig_Token(\Twig_Token::NAME_TYPE, 'extends', $token->getLine()),
new \Twig_Token(\Twig_Token::STRING_TYPE, '__parent__', $token->getLine()),
new \Twig_Token(\Twig_Token::BLOCK_END_TYPE, '', $token->getLine()),
));
$module = $this->parser->parse($stream, array($this, 'decideBlockEnd'), true);
// override the parent with the correct one
$module->setNode('parent', $parent);
$this->parser->embedTemplate($module);
$stream->expect(\Twig_Token::BLOCK_END_TYPE);
return new WidgetNode($module->getAttribute('filename'), $module->getAttribute('index'), $variables, $only, $ignoreMissing, $token->getLine(), $this->getTag());
}
public function decideBlockEnd(\Twig_Token $token)
{
return $token->test('endwidget');
}
/**
* Gets the tag name associated with this token parser.
*
* @return string The tag name
*/
public function getTag()
{
return 'widget';
}
}
TemplateTagsExtension.php:
namespace Artprima\Bundle\TwigWidgetTagBundle\Twig\Extension;
/**
* Class TemplateTagsExtension
*
* @author Denis V
*
* @package Artprima\Bundle\TwigWidgetTagBundle\Twig\Extension
*/
class TemplateTagsExtension extends \Twig_Extension
{
/**
* @inheritdoc
*/
public function getTokenParsers()
{
return array(
new WidgetTokenParser(),
);
}
/**
* Returns the name of the extension.
*
* @return string The extension name
*/
public function getName()
{
return 'template_tags';
}
}
services.yml:
parameters:
artprima.twig.extension.template_tags.class: Artprima\Bundle\TwigWidgetTagBundle\Twig\Extension\TemplateTagsExtension
services:
artprima.twig.extension.template_tags:
class: %artprima.twig.extension.template_tags.class%
tags:
- { name: twig.extension }
views/Blocks/widget.html.twig:
{# please note, that only "widget" block is rendered, all other blocks can be used inside the "widget" block #}
{# if you don't define the "widget" block, nothing will be rendered #}
{% block widget %}
<div class="{{ block('widget_box_class') }}">
{{ block('widget_header') }}
{{ block('widget_body') }}
</div>
{% endblock %}
{% block widget_header %}
<div class="{{ block('widget_header_class') }}">
{{ block('widget_title') }}
{% if display_toolbar is defined and display_toolbar %}{{ block('widget_toolbar') }}{% endif %}
</div>
{% endblock %}
{% block widget_body %}
<div class="{{ block('widget_main_class') }}">
{{ block('widget_main') }}
</div>
{% endblock %}
{% block widget_title %}
<h5 class="widget-title">{{ block('widget_title_text') }}</h5>
{% endblock %}
{% block widget_title_text %}(undefined){% endblock %}
{% block widget_toolbar %}
<div class="widget-toolbar">
{{ block('widget_toolbar_inner') }}
</div>
{% endblock %}
{% block widget_toolbar_inner %}{% endblock %}
{% block widget_box_class %}{% spaceless %}widget-box{% endspaceless %}{% endblock %}
{% block widget_main_class %}{% spaceless %}widget-main{% endspaceless %}{% endblock %}
{% block widget_main %}{% endblock %}
{% block widget_header_class %}{% spaceless %}widget-header{% endspaceless %}{% endblock %}
views/Dashboard/widgets/sample.html.twig
{% widget "ArtprimaSampleBundle:Blocks:widgets.html.twig" %}
{% block widget_title_text %}{{ "widget.records_creation_history"|trans }}{% endblock %}
{% block widget_main_class %}{% spaceless %}no-padding {{ parent() }}{% endspaceless %}{% endblock %}
{% block widget_main %}
<table class="table table-striped table-bordered table-hover no-margin-bottom">
<thead>
<tr>
<th>Description</th>
<th>Amount</th>
</tr>
</thead>
<tbody>
<tr>
<td>{{ "widget.number_of_countries.created"|trans }}</td>
<td>{{ dashboard.countries.created }}</td>
</tr>
<tr>
<td>{{ "widget.number_of_users.created"|trans }}</td>
<td>{{ dashboard.users.created }}</td>
</tr>
</tbody>
</table>
{% endblock %}
{% endwidget %}
So, as you can see, with my extension, it is possible to include a template an re-use the blocks within it. If you need multiple widgets, you can have multiple widget
tags in your template using the same source template and the block content will not overlap. Essentially, it works like embedding the template using Twig's embed
(and I used this tag as a source for my extension), but with the only (and major) difference - it renders ONLY the block called "widget". All other blocks are ignored but can be used inside the "widget" block.
Upvotes: 0
Reputation: 553
Let's go. I had this same problem and this solution works for me:
base.html.twig
{% block content %}{% endblock %}
site.html.twig
{% extends base.html.twig %}
{% use sub1.html.twig with appendable as appendableContent, another_appendable as another_appendableContent %}
{% block content %}
{% block appendable -%}
{{ block('appendableContent') }}
{% endblock %}
{% block another_appendable -%}
{{ block('another_appendableContent') }}
{% endblock %}
{% endblock %}
sub1.html.twig
{% use sub2.html.twig with appendable as appendableContentAlternative %}
{% block appendable %}
some stuff that should be added to appendable<br/><br/>
{{ block('appendableContentAlternative') }}
{% endblock %}
{% block another_appendable %}
This content should be added to "another appendable"<br/><br/>
{% endblock %}
sub2.html.twig
{% block appendable %}
additional stuff that should be appended<br/><br/>
{% endblock %}
According my research this technique is called "horizontal reuse", and here's the doc:
http://twig.sensiolabs.org/doc/tags/use.html
Upvotes: 5
Reputation: 31919
To include a template, you need to use the include
keyword, not the use
keyword:
{% block appendable %}
{# Assuming your sub1 template is in AcmeDemoBundle/Resources/views/MySub/sub1.html.twig #}
{% include "AcmeDemoBundle:MySub:sub1.html.twig" %}
{% endblock appendable %}
AcmeDemoBundle:MySub:sub1.html.twig could look like this:
<b>Put directly your code there, no need to use the blocks.</b>
If you wish, you can use the {{ parent() }}
keyword to use inheritance. For example, if you want to include sub1.html.twig
by default but append sub2.html.twig
in your child template, you can do the following:
base.html.twig
{% block content %}
{% include "AcmeDemoBundle:MySub:sub1.html.twig" %}
{% endblock %}
site.html.twig
{% extends base.html.twig %}
{% block content %}
{# render what happens in the parent content block #}
{{ parent() }}
{# and append sub2.html.twig as well #}
{% include "AcmeDemoBundle:MySub:sub2.html.twig" %}
{% endblock content %}
Upvotes: 2