Get twig variable from a specific route - symfony

I have a template where the left column is used to display the top sell items.
here's a my master template code:
<html><body>
{% block menu %}
{% endblock menu %}
{% block columnleft %}
{% include 'TestBundle:Default:top_items.html.twig'%}
{% endblock columnleft %}
{% block body %}
{% endblock body %}
</body></html>
my top_items.html.twig code:
<table>
{% for entity in entities %}
<tr>
<td style="padding: 0px"><img src="{{entity.picture}}"
width="60px" height="60px" class="img-rounded"></td>
<td style="padding: 0px; vertical-align: middle">{{entity.name}}</td>
</tr>
{% endfor %}
Question:
Is there any chance to get 'entities' variable from a specific route(localhost/statistics/top_meals) that return an array as response?
I'm asking for this because I have many views, and passing the 'entities' variable using {%include 'TestBundle:Default:top_items.html.twig' with {'entities': array_var}%} need to be called in each view.
Thanks

You are way better of with render controller call:
<html><body>
{% block menu %}
{% endblock menu %}
{% block columnleft %}
{% render(controller('TestBundle:Default:topItems')) %}
{% endblock columnleft %}
{% block body %}
{% endblock body %}
</body></html>
and then create the controller:
/**
* #Template()
*/
public function topItems(){
// SOME LOGIC
return array(
'topItems' => $topItems
)
}

Related

Template does not always see its variables

I will first present to you the incriminated templates.
To begin with, I have my layout template with the JQuery. To avoid multiple "$(document).ready()" I placed a block after the general JavaScript.
{# layout.html.twig #}
<script type="text/javascript">
$(document).ready(function(){
//Some JS
{% block jquery %}
{% endblock %}
});
</script>
{# Some HTML & JS #}
<body>
<!-- Some HTML -->
<div>
{% block content %}
{% endblock %}
</div>
<!-- Some more HTML -->
</body>
It is extended by the template layout_cart.html.twig.
{# layout_cart.html.twig #}
{% extends 'AppBundle::layout.html.twig' %}
{% block content %}
<h1>{% block titre %}{% endblock %}</h1>
<div id="content-container">
{% block subcontent %}
{% endblock %}
</div>
{% endblock %}
Which is extended itself by panier.html.twig
{# panier.html.twig #}
{% extends AppBundle:layout_cart.html.twig #}
{# Some code #}
{% block jquery %}
// Some JavaScript
{% for produit in produits %}
// Generating some JavaScript
{% endfor %}
{{ dump(produits) }}
{% endblock %}
{% block subcontent %}
{% if produits is defined and produits|length > 0 %}
{{ dump(produits) }}
{# Interacting with the variables #}
{% endif %}
{% endblock %}
The dump in the jquery renders this :
<pre class='xdebug-var-dump' dir='ltr'>
<b>array</b> <i>(size=0)</i>
<i><font color='#888a85'>empty</font></i>
</pre>
While in the subcontent it renders my objects collection.
Am I missing something ?

Symfony2 layout composition or inheritance

I can't understand some principles about layout composition in sf2.
I have this App/Namespace/MyBundle/Resources/views/layout.html.twig template:
{% extends '::base.html.twig' %}
{% block title %}Main Title{% block subtitle %}{% endblock %}{% endblock %}
{% block head %}<h1>Placeholdertitle</h1>{% endblock %}
{% block body %}{% endblock %}
{% block sidebar %}{% endblock %}
How should I make the head.html.twig to replace the head block?
Currently I have this App/Bundle/Resources/views/head.html.twig template:
{% extends 'AppNamespaceMyBundle::layout.html.twig' %}
{% block head -%}
<h1>
Main Title
</h1>
{% endblock %}
Then I load a page with this index.html.twig:
{% extends 'AppNamespaceMyBundle::layout.html.twig' %}
{% block subtitle %} | Categories{% endblock %}
{% block body -%}
<h1>Categories list</h1>
<table class="records_list">
<thead>
<tr>
<th>Id</th>
<th>Text</th>
<th>Actions</th>
</tr>
</thead>
<tbody>
{% for entity in entities %}
<tr>
<td>{{ entity.id }}</td>
<td>{{ entity.text }}</td>
<td>
<ul>
<li>
show
</li>
<li>
edit
</li>
</ul>
</td>
</tr>
{% endfor %}
</tbody>
</table>
<ul>
<li>
<a href="{{ path('category_new') }}">
Create a new entry
</a>
</li>
</ul>
{% endblock %}
And here I get the "Placeholdertitle" instead of the "Main Title", meaning that the head.html.twig isn't used. How am I supposed to use that head.html.twig to be in the layout.html.twig?
From what you are explaining, I guess that you want to render Layout + Head inside your Index.
You can use the following schema:
Layout <extended by> Head <extended by> Index. This way, when you will render Index, Twig will render both Head and Layout.
Another solution is to use the use.
PS: You cannot extends both head and layout in the same index file, as Twig says: Template inheritance is one of the most powerful Twig's feature but it is limited to single inheritance;
Did you try to remove the "-" in your head block in head.html.twig ?

Using variables from inside symfony 2 / twig blocks

I have this
base.html.twig
<html>
{% block template_scope_vars %}
{% endblock %}
<head></head>
<body>
{% block content %}
{% endblock %}
</body>
</html>
user.html.twig
extends base...
{%block template_scope_vars %}
{% set test= "bippo" %}
{%endblock%}
{%block content %}
{{ test }}
{% endblock %}
Error
Variable "test" does not exist
Ok please please don't tell me that I don't have to use blocks for this, the use case is more difficult than this simple example obviously. The idea is that test gets available in the base.html.twig as well, as well as any sub-templates of it that I might be using in future
for the special use case I had I could use this. It is a shame though, there is no neater solution. All other solution I could come up with, would require to create a new file for each set of template scope variables. Which plain sucks
base.html.twig
<div id="menu"> //this is like a sidebar menu
<div class="menuitem {% block foo %}{% endblock %}">
Foo
</div>
<div class="menuitem {% block bar %}{% endblock %}">
Bar
</div>
<div>
{% block content %}
{% endblock %}
Then in
foo.html.twig
{% extends "MyBundle::base.html.twig" %}
{% block foo %}active{% endblock %}
{% block content %}
Some content specific for foo
{% endblock %}
bar.html.twig
{% extends "MyBundle::base.html.twig" %}
{% block bar%}active{% endblock %}
{% block content %}
Some content specific for bar
{% endblock %}
and then you set the css for the background of e.g.
CSS
menuitem.active {
background-color: red;
}
base.html.twig
<div id="menu"> //this is like a sidebar menu
<div class="menuitem{{ active == 'foo' ? ' active' : '' }}">
Foo
</div>
<div class="menuitem{{ active == 'bar' ? ' active' : '' }}">
Bar
</div>
<div>
{% block content %}
{% endblock %}
foo.html.twig
{% extends "MyBundle:Whatever:base.html.twig" %}
{% block content %}
{{ content }}
{% endblock %}
Controller
/**
* #Route("/foo", defaults={"param" = null}, name="foo")
* #Route("/foo/{param}", name="foo_with_param")
* #Template()
*/
public function fooAction($param)
{
// This would really be getting data from the Model
if ($param == 'something') {
$content = 'Some content';
$active = 'foo';
} else {
$content = 'Different content';
$active = 'bar';
}
return array(
'content'=> $content,
'active' => $active,
);
}
When rendering foo.html.twig, base.html.twig will get the variable "active".

Is a Twig block empty? - Symfony 2

I have a twig template with the following block:
{% block dashboard %}
{% include "::user_dashboard.html.twig" %}
{% endblock dashboard %}
Later in that template, I want to set a class on a div based on whether or not there is anything in that block (i.e., by default, it will have the include above, but children of this template may override it and empty it out).
What I had (that somewhat worked) was ...
{% set _dashboard = block('dashboard') %}
{% set _mainWidth = ( _dashboard|trim is empty ? "no-dashboard" : "with-dashboard" ) #}
<div id="main" class="{{ _mainWidth }}">
The problem here is that whole dashboard block gets called twice. This wouldn't bother me too much except that block renders a few controller actions, i.e. ...
{% render "UserWidget:userAppMenu" %}
... and the code in that action is being called twice. For various reasons, not the least of which is performance, this messes with some of the stuff in that dashboard block.
So, my question is ... is there any way to tell if that block is empty without loading it twice? Is there something really simple I'm missing or is this even possible?
Thanks!
EDIT:
Here is my full template if it helps clarify things:
{% extends '::base.html.twig' %}
{% block layout %}
{% block header %}
{% include "::header.html.twig" %}
{% endblock header %}
<div id="container" class="row-fluid">
{% block dashboard %}
{% include "::user_dashboard.html.twig" %}
{% endblock dashboard %}
{% set _dashboard = block('dashboard') %}
{% set _mainWidth = ( _dashboard|trim is empty ? "no-dashboard" : "with-dashboard" ) %}
<div id="main" class="{{ _mainWidth }}">
<h1 class="page-title">{% block page_title %}{% endblock %}</h1>
{% block main_filters %}{% endblock %}
{% if app.session.flashbag.has('message') %}
<div class="alert alert-block alert-success">
<ul>
{% for flashMessage in app.session.flashbag.get('message') %}
<li>{{ flashMessage }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% if app.session.flashbag.has('warning') %}
<div class="alert alert-block alert-success">
<ul>
{% for flashWarning in app.session.flashbag.get('warning') %}
<li>{{ flashWarning }}</li>
{% endfor %}
</ul>
</div>
{% endif %}
{% block body %}{% endblock %}
{% block footer %}
{% include "::footer.html.twig" %}
{% endblock footer %}
</div>
</div>
{% endblock layout %}
Here you can see on lines 11 and 15 - both of those actually seem to include and process what is in that include.
What about this? This way the block should only be rendered once, when you call block('dashboard').
{# at top of twig #}
{% set _dashboard = block('dashboard') %}
{# where ever you include your block #}
<div>
{{ _dashboard|raw }}
</div>
{# and your main #}
{% set _mainWidth = ( _dashboard|trim is empty ? "no-dashboard" : "with-dashboard" ) #}
<div id="main" class="{{ _mainWidth }}">

Append content to block from multiple subtemplates

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?
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
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>
Using Inheritance
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 %}
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).
Extension
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 }
Usage example
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 %}
Summary
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.
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 %}

Resources