Symfony2: render inside block causes duplicate sub request - symfony

I have a Sf2 application with a twig template extending a layout. Inside the template i want to fill a block by using twig's render tag. Unfortunately the render tag causes two sub requests instead of the expected single one. But if i place the render tag outside of the block, everything works just fine*.
*of course except that the result is rendered simply at the top of the page instead inside a block, but it's only one additional sub request.
What may cause that two sub requests are made if - and only if - the render tag is placed within a block?
DIRTY DETAILS
In the layout there's a block for an additional navigational menu (topmenu):
layout.twig.html
<div class="row" id="secondary-nav">
<div class="span12">
{% block topmenu %}{% endblock %}
</div>
</div>
Duplicate sub request example
And this is my view, it uses a {% render ... %} tag to fill the topmenu block by issuing a sub request to the "Navigation" controller:
index.html.twig
{% extends "FooBundle::layout.html.twig" %}
{% block topmenu %}
{% render "FooBundle:Navigation:index" with { 'active': 'feedback' } %}
{% endblock %}
So far, so good, but doing it this way the sub request is issued twice for some unknown reason. It's not displayed twice, but i see the SQL query count increased and the symfony2 profiler shows two identical sub requests too.
Single sub request example
If i place the {% render ... %} tag outside of any block like this, it is causing only one single sub request:
index.html.twig
{% extends "FooBundle::layout.html.twig" %}
{% render "FooBundle:Navigation:index" with { 'active': 'feedback' } %}
{% block topmenu %}
{% endblock %}

NARF! Finally, after spending X hours on this issue, i found out that i had a check in my layout.html.twig, to see if the topmenu block has any contents:
{% if block('topmenu') %} ... {% endif %}
...
{% block topmenu %}{% endblock %}
This led to the second, unwanted sub request.
Fix
To still have the ability to check for the content but not requesting twice, i'm loading the block into a variable first and then outputting the variable instead of using {% block topmenu ...%}:
{% set topmenu = block('topmenu') %}
{% if topmenu %} ... {% endif %}
{{ topmenu | raw }}
Luckily i do not need to extend the block really, it's just a placeholder.

Related

Symfony2 Twig: embedding a template - Where to put JS

On some of my pages I want to embed a toolbar with some generic functionality like sharing, reporting and bookmarking. Currently I do it like this
{# page-detail.html.twig #}
{% extends 'layout.responsive.html.twig' %}
{% block content %}
{% include "toolbar/index.html.twig" with {'foo':'bar'} %}
{# further content... #}
{% endblock %}
As the features of the toolbar require some information about the context it's embedded in, I pass this information in the with-clause. So far so good.
Now, all the buttons are invoking some JavaScript on clicking. Modal windows are popping up, dialogs are displayed, etc. I would like to put the related JS inside the toolbar template but here is the problem:
All my JS-libraries are loaded at the bottom of the page before the closing body-tag. Means, in my toolbar template I have no access to them.
So I thought of the following solutions which I am not really happy with:
move the inclusion of the JS-libraries to the top (loading js slows down page display, also JS would be scattered)
put the respective JS in the javascripts-block in "page-detail.html.twig" (toolbar code is separated from the template code and mixed with the JS-Code needed there)
use the "embed"-Tag and define a JS-Block in the toolbar-Template (won't help as I can access the blocks in the embedding parent template only within the "embed"-Tag)
use the "use"-Tag instead. Well that sounds best so far, however, Code would look like this (assuming toolbar-template defines the blocks "toolbar_content" and "toolbar_js"):
Code:
{# page-detail.html.twig #}
{% extends 'layout.responsive.html.twig' %}
{% use "toolbar/index.html.twig" %}
{% block content %}
{% block toolbar_content %}
{{ parent() }}
{% endblock %}
{# further content... #}
{% endblock %}
{% block javascripts %}
{{ parent() }}
{% block toolbar_js %}
{{ parent() }}
{% endblock %}
... local JS
{% endblock %}
Also in this solution I cannot pass the environment variables anymore using the "with"-clause as it is now used for resolving name-conflicts of the blocks.
I feel like I am missing something. How is it done right? It seems like such a common task.
Additional information
I think not essential for the question but for more clarity, here is how my layout template looks like (not complete just the relevant blocks).
{# layout.responsive.html.twig #}
{% block stylesheets %}
{# bootstrap.css, custom.css, ... #}
{% endblock %}
{% block content %}
{% endblock %}
{% block javascripts %}
{# jquery, bootstrap, ... #}
{% endblock %}
And my toolbar:
{# toolbar/index.html.twig #}
<div class="toolbar btn-toolbar" role="toolbar">
<div class="btn-group" role="group">
<button type="button" class="btn btn-default" data-toggle="modal" data-target="#report">Report</button>
</div>
<!-- more buttons -->
</div>

symfony twig extends: 'only extend if'

In twig there is the 'extends' tag, as found here; http://twig.sensiolabs.org/doc/tags/extends.html#conditional-inheritance
Now what I wanna do is something along the lines of the following example from that page:
{% extends standalone ? "minimum.html" : "base.html" %}
But rather than having 2 templates to extend from, I just want to extend from a template if a specific condition is met.
Now I've tried things such as:
{% extends boolean ? "template.html.twig" : "" %}
and:
{% if boolean %}
{% extends "template.html.twig" %}
{% endif %}
but the former gives an error saying it cannot find a template (since "" obviously isnt a valid path), and the latter just doesn't appear to do anything at all (or rather, it loads for a while and ends up not showing anything)
I've tried some other approaches, but couldn't come up with anything, so figured I'd ask here if I might be missing something.
Thanks in advance for any replies :)
EDIT: To sum up my intent; I am wondering if I can tell my template to only extend if a certain condition is met, and otherwise skip the extend step. (if condition then extend else do nothing)
Twig files are generated into PHP classes.
The extends tag should be the first tag in the template, as:
the {% extends %} tag will be converted to the PHP extends so the child template will inherit from the parent template.
the {% if %} tag is generated as a PHP if, inside a method of the template class, so you can't use {% if %} to extend some class or not.
Anyway, you can extends a variable coming from your context, so you should put your condition in the controller.
if ($boolean) {
$template = 'hello.twig';
} else {
$template = 'world.twig';
}
$this->render("MyBundle:MyFeature:child.html.twig", array('template' => $template);
And then in child.html.twig:
{% extends template %}
I came with this hack: added empty layout only with content block. Seems to be working :) I can pass variable from controller and page is loaded with or without layout.
<!-- base.html.twig -->
<head>
...stuff...
</head>
<body>
{% block content %}{% endblock %}
</body>
<!-- empty.html.twig -->
{% block content %}{% endblock %}
<!-- some_page.html.twig -->
{% extends boolean ? 'base.html.twig' : 'empty.html.twig' %}
{% block content %}
Now this is my real content
{% endblock %}
In pure twig language, it could be something like this :
{% if app.request.pathinfo starts with '/react' %}
{% set extendPath = "::react_base.html.twig" %}
{% else %}
{% set extendPath = "CoreBundle::layout.html.twig" %}
{% endif %}
{% extends extendPath %}

Redefining a block initially defined in an included view in the parent view with twig

I have a view A that includes a view B that defines a block Z. Can I have a view C that extends A and redefines block Z?
Need not be said that if i paste B into A (without using an include) it works.
For a practical example:
A is the base of a project.
B is the header of a project that includes a menu.
C is any page of the project.
I define a {% block active %}{% endblock %} for each item of the menu in B, that i want to redefine by {% block active %} class="active" {% endblock %} in C to apply certain styles to the current active menu item.
If this is not the way to go, which is the best workaround/solution?
Unfortunately, it's not possible or it wouldn't be as easy.
Why not bind a variable to the template from the controller and passing it to the nav through the include?
{% include MyAwesomeBundle:Modules:nav.html.twig with {activeItem: activeItem, activeItemClassName: activeItemClassName} %}
An alternative to what you want
{# in base layout template #}
{% block nav %}
{% include MyAwesomeBundle:Modules:nav.html.twig %}
{% endblock %}
{# in page template #}
{% extends base-layout.html.twig %}
{% block nav %}
{% include MyAwesomeBundle:Modules:nav_for_news.html.twig %} {# nav_for_news.html.twig extends nav.html.twig #}
{% endblock %}
This is the closest you can get from what you want
Check out the parent() twig function it's useful.

How to check if a block exists in a twig template - Symfony2

Imagine I have something like this in my twig template
{% block posLeft %}
-----
{%endblock%}
Is there any way to check for existance of the posLeft block without calling to:
block("posLeft")
And check the return value of the posBlock to varify the existance. I am a newbie in Symfony2 + Twig.
You can solve it like this, if you want to display a certain block only if it has content. Hope, this is what you're looking for.
Example index.html.twig
{% set _block = block('dynamic') %}
{% if _block is not empty %}
{{ _block|raw }}
{% endif %}
Example part.html.twig
{% extends "index.html.twig" %}
{% block dynamic %}
Block content goes here.
{% endblock %}
You can do it like this:
{% if block('posLeft') %}
...
{% endif %}
But it is not efficient if you need output of rendered block.
So if you will need block output you should assign it to variable in the first place
and then do assertions
The other answers here do not work for twig 2.1 (I've not tested on ~2.0), so here's a small update:
{% if block('dynamic') is defined %}
{{ block('dynamic')|raw }}
{% endif %}
Note that the line to render the block is not:
{% block dynamic %}
{# this wont work #}
{% endblock %}
This wont work because the block will be rendered during compile, and so the test will return true that it exists (as its tested at runtime). So you need to render the block with {{ block('dynamic')|raw }} instead as this doesn't actually define the block in the template.
First check, which Twig version you are using inside your Symfony project, because the answers here are only for Twig 1.
If you are using Twig 2 you are lucky. According to the Twig documentation, you can use the defined test to check if the block exists in the current template context.
{% if block("dynamic") is defined %}
...
{% endif %}
I have written a little TwigExtension to check if the block gets called inside the if statement and it seems like Twig only really checks if the block exists and does not call it.
The link to the docs: https://twig.symfony.com/doc/2.x/functions/block.html
If you are using Twig 1, the old answer at https://stackoverflow.com/a/13806784/6458657 is still correct.
Twig 2.x
{{ (block("posLeft")) ?? '' }}
If you want to display a block if it's defined or not in one line. Might be a little kludgy but satisfies my needs without a bunch of if..then logic.
Just want to provide another example which worked for me.
<body
{% if block('ngapp') is not empty %}ng-app="{% block ngapp %}{% endblock %}"{% endif %}
>
This allows me in child templates declare {% block ngapp 'myApp' %} and have it displayed within parent.
This was needed because on some pages I was bootstrapping Angular manually via (angular.bootstrap('moduleName', rootElement)) and Angular does not like empty ng-app='' directive and breaks in weird ways.
The accepted answer didn't work for me in Twig 3.3.10, throwing an error that the block wasn't defined.
To solve this and define a block whose contents are conditionally wrapped in a container, only if any block content is set, this worked:
Parent template with block definition and wrapper element
{# parent.twig #}
<h1>Hello. I am the parent.</h1>
{% if block('sidebar') is not empty %}
<div class="sidebar-container">
{% block sidebar %}{% endblock %}
</div>
{% endif %}
Child template setting block content
{% extends 'parent.twig' %}
{% block sidebar %}
Sidebar content from child template
{% endblock %}
Output – block content inside wrapper:
<h1>Hello. I am the parent.</h1>
<div class="sidebar-container">
Sidebar content from child template
</div>
Child template not setting block content
{% extends 'parent.twig' %}
{# sidebar block not set in this one... #}
Output – no empty wrapper element:
<h1>Hello. I am the parent.</h1>

Symfony2 Assetic + Twig Template JavaScript Inheritance

My problem:
I have 3 templates:
main.html.twig (main layout file)
layout.html.twig (a bundle specific layout override which contains some bundle specific JS tags)
create.html.twig (a page specific template file which also contains some page specific JS tags)
I am defining a block called 'javascript' in my base layout (main.html.twig), then overriding it (but calling {{ parent() }} in layout.html.twig. This works fine, and the JS tags from the main template file are still included above those in the layout.html.twig template.
I then do the same in the create.html.twig file, overriding the block as follows:
{% block javascripts %}
{{ parent() }}
{% javascripts '#BundleName/Resources/public/js/application.album.uploader.js'
'#BundleName/Resources/public/js/jquery.uploadify.js'
'#BundleName/Resources/public/js/swfuploadify.js' filter='?yui_js' %}
<script src='{{ asset_url }}' type='text/javascript'></script>
{% endjavascripts %}
{% endblock %}
At this point, instead of just overriding the javascript block in the parent (layout.html.twig) and including all the scripts defined in the templates above it, it does the following:
Dumps the <script> tags in the middle of the output (which causes an error, because in my main.html.twig file I am only including the jQuery library at the end of the HTML markup
Then it also dumps the scripts out along with the rest of the others (as I would expect it to)
I am not sure what is causing the scripts to be dumped in the middle of the create.html.twig template, and I'm also confused as to why they're being dumped to the screen twice (once in the middle of the create and then once at the bottom along with all the rest of my scripts from main.html.twig and layout.html.twig.
Has anyone got any ideas? Let me know if anything is unclear or if I can provide some more information.
EDIT:
File contents are below...
main.html.twig: https://gist.github.com/7f29353eaca0947528ce
layout.html.twig: https://gist.github.com/734947e9118b7765715e
create.html.twig: https://gist.github.com/c60c8d5c61e00ff86912
EDIT 2:
I've been having another look at the issue this morning and it looks as though its doing the same thing for stylesheets. I tried to define a new block called pagescripts in my layout.html.twig and then use the block in my create.html.twig but this had the same outcome, it just seems to dump the scripts and stylesheets wherever I use the
{% block pagescripts %}
(scripts here)
{% endblock}
I found the issue. In create.html.twig I was defining my {% block javascripts %} content inside inside my {% block content %}, so I assume Twig was rendering the output of the javascripts block inside the content block.
Moving the {% block javascripts %} content outside of the {% block content %} block fixed the issue.
Here is an example of main.html.twig:
<body>
{% block stylesheets %}
{% endblock %}
{% block jsscript %}
{% endblock %}
{% block content %}
{% endblock %}
</body>
into your create.html.twig
{% extends '::base.html.twig' %}
{% block jsscript %}
my javascript files...
{% endblock %}
{% block content %}
<h1>Create</h1>
<form action="{{ path('entity_create') }}" method="post" {{ form_enctype(form) }}>
{{ form_widget(form) }}
<p>
<button type="submit">Create</button>
</p>
</form>
{% endblock %}
If you still have issue, you can add a document ready function :
$(document).ready(function() {
// put all your jQuery goodness in here.
});

Resources