Use a template to set a variable - symfony

I'm using craftcms and the template language they use is Twig.
The _layout/base.html:
<!DOCTYPE html>
<html>
<head>
{% include '_layout/_seo_default.html %}
</head>
_layout/_seo_default.html:
{% if seo is not defined %}
{% set seo = {title: "Default values for things", description:"Default Description"} %}
{% endif %}
<title>{{ seo.title }}</title>
<meta name="description" content="{{ seo.description }}">
I have a blog/_entry template which shows an entry from the CMS based on the url. The blog/_entry.html:
{% extends '_layout/base.html' %}
{% block main %}
{# i want this include to set a variable used in _seo_default.html #}
{% include '_seo/_from_article_type_entry.html' with {entry: entry} %}
<article>
html irrelevant to question
</article>
{% endblock %}
The _seo/_from_article_type_entry.html
{% set seo = { title: entry.title, description: entry.short_description } %}
The idea was that i would be able to map the fields to the correct keys in one template / at one place. So i don't have to reuse it for the news/blog/story templates the client wants. But the 'seo' variable set in _seo/_from_article_type_entry.html does not get set (either not at all, or not in time that the _layout/_seo_default.html picks it up, and default values are always used.
If i replace the {% include '_seo/_from_article_type_entry.html' with {entry: entry} %} line in blog/_entry.html with the contents of _seo/_from_article_type_entry.html it does work, so it just doesn't seem to get set in the include. But I can't figure out what i'm missing. Or maybe i'm trying to do something that Twig is not supposed to do.
In either case, any help would be very welcome :)

Included templates have their own variable scope, templates outside the included one can't access these variables as seen here

Related

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 %}

How to override template block from a twig extension?

Is it possible to override a template block from a twig extension? How do I do it?
Edit:
I have a block in my master layout template, it is called {% block emailMenu %}, the question is, is it possible to override this block, not from another template but from inside a twig custom function?
I guess I'm confused as the best way to handle my situation, my email menu will change from page to page depending on several factors, and I thought of making a twig function to be called from the layout or from another template, the reason I am thinking along these lines is to keep my other templates free from a lot of logic, logic that I'd rather have with pure PHP. Any thoughts would be appreciated.
You can just render additional controller (not a twig extension) which renders own template (possibly dependent on the page), which overrides base template block.
I think, overriding block from twig extensions - it is not the twig purposed for.
You must create a base template:
{# src/BW/MainBundle/Resources/views/Main/base.html.twig #}
<!DOCTYPE html>
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
<title>{% block title %}Test Application{% endblock %}</title>
</head>
<body>
<div id="sidebar">
{% block sidebar %}
<ul>
<li>Home</li>
<li>Blog</li>
</ul>
{% endblock %}
</div>
<div id="content">
{% block body %}{% endblock %}
</div>
</body>
</html>
And then extends parent template with extends keyword:
{# src/Acme/BlogBundle/Resources/views/Blog/index.html.twig #}
{% extends 'BWMainBundle:Main:base.html.twig' %}
{% block title %}My cool blog posts{% endblock %}
{% block body %}
{% for entry in blog_entries %}
<h2>{{ entry.title }}</h2>
<p>{{ entry.body }}</p>
{% endfor %}
{% endblock %}
Also read this docs about templating in Symfony using Twig.

Use Twig Custom Set Variables From an Include

I'm trying to include a twig file with a bunch of custom set variables and then use the variables in the multiple other template files. Similar to how including a PHP file works.
I don't seem to have access to the variables set inside the include in my index file.
Is there any way to do this?
Sample Code *Edited
Included File:
{# variables.html #}
{% set width = "100" %}
{% set height = "250" %}
Template File:
{# index.html #}
{% include 'variables.html' %}
{{ width }}
{{ height }}
Expected Outcome:
100 250
Actual Outcome:
// Nothing gets output
I was just trying to do the same thing you were and came up with the following:
Created snippets.twig to maintain all these mini variables. In your case, you might call it variables.twig. In this file, I used a macro without any arguments. I was creating formatted entry date markup that I can use across all my templates and it looked like this:
{% macro entry_date() %}
<time datetime="{{post.post_date|date('m-d-Y')}}">{{post.post_date|date('F j, Y')}}</time>
{% endmacro %}
note that the parenthesis after the name declaration were imperative
In my main layout file, layout.twig, I referenced this macro via an import statement so it would be accessible in all child templates:
{% import "snippets.twig" as snippets %}
<!doctype html>
...
In my template files, I now have snippets accessible and can query it like any other variable:
{{ snippets.entry_date }}
UPDATE
This doesn't seem to correctly run code. If you're just storing static content, you should be good. You can also pass args to the macro so I imagine you could make some magic happen there but I haven't tried it.
As far as I know it is only possible with {% extends %} tag. Instead of including template with variables you should extend it.
Example:
variables.tpl:
{% set some_variable='123' %}
... more variables ...
{% block content %}
{% endblock %}
template.tpl
{% extends 'variables.tpl' %}
{% block content %}
{{ some_variable }}
... more code which uses variables assigned in variables.tpl ...
{% endblock %}
You can use a template extension : https://symfony.com/doc/current/templating/twig_extension.html
post|entry_date

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>

Twig Assetic Stylesheets Among Several Templates

I'm trying to add stylesheets to an array so that as twig templates extend through a second and third levels the aggregated styles will carry through.
This topic is related to Combining Assetic Resources across inherited templates
From config.yml, I made a global array mystyles so that we can add to the list of necessary styles as we "bubble up" through the rendering process.
twig:
debug: %kernel.debug%
strict_variables: %kernel.debug%
globals:
mystyles: []
The first template called from my action is from CommunicatorBundle/Resources/views/Admin/Workspace.html.twig and I've set the specific style for this page called admin.workspace.css.
{% extends "DJCommunicatorBundle::base.html.twig" %}
{% set mystyles = ["#DJCommunicatorBundle/Resources/public/css/admin.workspace.css"]|merge(mystyles) %}
It extends CommunicatorBundle/Resources/views/base.html.twig which has it's own requirement data-table.css.
{% extends "DJSharedBundle::base.html.twig" %}
{% set mystyles = ["#DJCommunicatorBundle/Resources/public/css/data-table.css" ]|merge(mystyles) %}
Finally, we load the outermost template, SharedBundle/Resources/views/base.html.twig, which has it's own styles to add before all others.
<head>
{% set mystyles = ['#DJSharedBundle/Resources/public/css/global.css', '#DJSharedBundle/Resources/public/css/grid.990.9-col.css']|merge(mystyles) %}
{% stylesheets {{ mystyles }} %}
<link rel="stylesheet" href="{{ asset_url }}" type="text/css" />
{% endstylesheets %}
</head>
However, it breaks at this line
{% stylesheets {{ mystyles }} %}
inspite of this type of test that prints the array that I expect in the proper order
{{ mystyles|join(', ') }}
It seems that the {% stylesheets %} tag wants something like the following snippit to work (which is understandably not an array object, but a whitespace separated list of delimited strings).
{% stylesheets
'#DJSharedBundle/Resources/public/css/global.css'
'#DJSharedBundle/Resources/public/css/grid.990.9-col.css'
'#DJCommunicatorBundle/Resources/public/css/data-table.css'
'#DJCommunicatorBundle/Resources/public/css/admin.workspace.css'
%}
<link rel="stylesheet" href="{{ asset_url }}" type="text/css" />
{% endstylesheets %}
Even then, I tried setting a string to such a long value and printing it, but this doesn't work either:
{%
set str = "#DJSharedBundle/Resources/public/css/global.css
#DJSharedBundle/Resources/public/css/grid.990.9-col.css
#DJCommunicatorBundle/Resources/public/css/data-table.css
#DJCommunicatorBundle/Resources/public/css/admin.workspace.css"
%}
{% stylesheets {{ str }} %}
I feel like the global should be a viable solution, though not currently working. Hopefully I'm close. What might fix this?
This is not possible right now because Assetic statically analyzes your templates to find any assets you've defined there. Supporting these dynamic cases is on the roadmap.

Resources