How to "shuffle" includes from twig - symfony

I have a base template that includes multiple sub-templates and the code runs something like this:
<ul class="row portfolio list-unstyled mt-3 lightbox" id="grid">
<!-- summary section -->
{{ render(controller('App\\Controller\\ReadController::summary')) }}
{{ render(controller('App\\Controller\\BookController::random', {num: 3})) }}
{{ render(controller('App\\Controller\\WikiController::random')) }}
</ul><!-- / portfolio row -->
As can be seen, these "items" appear in a fixed order:summary goes first, then 3 random and then one random.
What I intend to do is to "shuffle" these items (in the above code snippet, there will be 5 items) so that the order is different in each refresh to give the end user some variation.
Is this possible to do in Twig?
UPDATE
I used #hcoat method and it is working. Will try the shuffle filter later.

As suggested to you in the comments above you can use the Array Extension or send it through a controller. I think having a controller that sends in the random list is the way to go.
However, sometimes it is useful to randomize a list with standard twig and in such cases you can do something like the following:
// Path and pram Array, pass empty hash if no params
{% set arr = [
["App\\Controller\\ReadController::summary", {}],
["App\\Controller\\BookController::random", {'num': 3}],
["App\\Controller\\WikiController::random", {}]
] %}
// create a list and merge arr array with random key
{% set list = {} %}
{% for item in arr %}
// There is a bug in some twig verions
// so concat a letter to ensure random key works
{% set list = list|merge({ (random()~'a'):(item) }) %}
{% endfor %}
// sort the list by the random key and render the output
{% for key in list|keys|sort %}
{{ render(controller(list[key][0], list[key][1])) }}
{% endfor %}
Now the sub-templates will be rendered in a random order.

Related

Pass variable in twig to included template

I have set the rendering of an navigation menu en an separate twig template. Reason is the the menu is generated on two different places in the application.
Now I want to give a parameter to the navigation menu so I know from with place it is generated are these some small differences.
I tried the following code but the variable is not know in the navigation menu template:
{% set menuType = 'user' %}
{% include 'MyBundle:nav.html.twig' with menuType %}
Also tried:
{% include 'MyBundle:nav.html.twig' with {'menuType': 'user'} %}
In both case twig generated the error that {{ menuType }} does not exists?
Surprisingly, while I thought it would be possible to do like you to pass a simple variable, it appears only arrays are accepted as passed values. (While the two examples of the include doc are arrays, this isn't specifically precised.)
In your case, you have to write it this way:
{% set menuType = 'user' %}
{% include 'MyBundle:nav.html.twig' with {menuType:menuType} only %}
Note: I've added the only keyword to disable the access to the context.
Without it you don't need to pass the variable to the included templates as they will have access to them. (It's a good practice to disable it.)
Here is a Twigfiddle with some tests and dumps: https://twigfiddle.com/gtnqvv
{% set menuType = 'user' %}
{% set vars = {'foo': 'bar'} %}
{% set z = 'bob' %}
{# vars dumps ommitted here, see the fiddle. #}
{#% include '1.html.twig' with menuType only %#}
{% include '1.html.twig' with {menuType:menuType} only %}
{% include '2.html.twig' with vars only %}
{% include '3.html.twig' with {z:z} only %}
{#% include '3.html.twig' with z only %#}
The first and last commented lines doesn't work, as you know, here is the error:
Uncaught TypeError: Argument 1 passed to Twig_Template::display() must
be of the type array, string given
The second works as you want, you just have to make it an array. (Strange anyway)
The third line is a test from the Twig doc, and the fourth is a test with another variable name, just to be sure.
As long as a variable is available, or included with the parent template, it is available to any included or child template.
Ex:
Controller:
return $this->render('CmsBundle:EmailBulk:edit.html.twig', array(
'entity' => $entity,
'form' => $editForm->createView(),
'tokens' => $tokens
));
Then, edit.html.twig:
{% block body -%}
<div class="panel panel-default animated fadeIn delay-400">
<div class="panel-heading">
blah blah blah
</div>
<div class="panel-body">
{{ include('CmsBundle:EmailBulk:form.html.twig') }}
</div>
</div>
{% endblock %}
The 'form' variable from the controller is available to the included template form.html.twig
I created a sample twigfiddle for you here:
https://twigfiddle.com/fpzv26
You need something like this:
{% set vars = { 'menuType' : 'user'} %}
{% include 'MyBundle:nav.html.twig' with vars %}
I use this in my code and it works for me. The var foo is used in baz.html.twig directly:
{% set foo = 'foo' %}
{{ include ('MyBundle:bar:baz.html.twig') }}
In twig docs it says:
Included templates have access to the variables of the active context. [...]
The context is passed by default to the template but you can also pass additional variables

Iterate lists/content in block template twig Drupal 8

How would I be able to supersede the hierarchical dependencies in Drupal 8's twig engine to be able to loop within the i.e Lists/Views which is assigned to a block. So we would have a template: block--views-block--[machine-name]-1.html.twig You will be required to have the variable {{ content }}
Which then recursively buries itself down to field templates. Its completely killing me that one would need so many levels to produce on block of content.
I would like to iterate within the top custom block template the list.
Attempted
{% for key, value in _context %}
<li>{{ key }}</li>
{% endfor %}
To evaluate what is available to iterate down into the object but with no luck. I did though find a nice overriding object structure to reach the field attributes but that was within the field level
item.content['#item'].entity.uri.value
Thanks
i use this to "generate" a picture from my
node--news--full.html.twig
<div class="col-md-3">
{{ content.field_newsbild }}
</div>
the twig debug suggests some filenames. i took this:
field--node--field-newsbild--news.html.twig
and in there i wrote:
{% for item in items %}
<img alt="" src="{{ file_url(item.content['#item'].entity.uri.value) }}" class="img-responsive" {{ attributes }} >
{% endfor %}
hope i'll help a bit.

An elegant way to get Jekyll collection item by name?

In Jekyll 2.5.3, I use albums collection (because I need not only data stored, but also pages generated).
When Jekyll Data Files are used, you can get a data for particular item as simple as: site.data.albums[foo]
But with collections, things are much worse. All those ways I've tried just do nothing:
site.albums[foo]
site.collections.albums[foo]
site.collections.albums.docs[foo]
site.collections.albums.files[foo]
So I need to:
Loop through all collection items
For each of them get a bare name
Compare this name with some target name
If the name matches, finally assign collection item data to some variable to use
Any better suggestions?
I have just done this today, you are correct with your assertions. Here's how I did it:
<!-- this is in a partial with a 'name' parameter -->
{% for item in site.albums %}
{% assign name = item.path | split:"/" | last | split:"." | first %}
{% if name == include.name %}
{% assign collectionItem = item %}
{% endif %}
{% endfor %}
Usage
{{ collectionItem.title }}
{{ collectionItem.url }}
{{ collectionItem.path }}
It can even be used to populate values in other partials, like so:
{% include card.html title=workItem.title bg=workItem.card.bg href=workItem.url %}
As of Jekyll 3.2, you can use the filter where_exp to filter a collection by any of its properties. So if I have the following item in an albums collection:
---
title: My Amazing Album
---
...
I can grab the first match for an item by that name:
{% assign album = site.albums
| where_exp:"album", "album.title == 'My Amazing Album'"
| first %}
Note that I use the first filter because where_exp returns an array of matched items.
And then use it any way I like:
<h1>{{ album.title }}</h1>
I can't vouch for the build performance of this method, but I have to imagine it's better than a Liquid loop.

Use placeholders in translation using tags

In Symfony / Twig, I could use tags by using percentages in my translated block. For example:
Hello {{nickname}}
would become
{% trans %}Hello %nickname%{% endtrans %}
This works as expected. The array with placeholders that I pass to Twig, are automatically mapped to %placeHolder%. No extra work involved. So this works with my PHP array from the controller being:
Array('nickname' => 'rolandow')
When I want to use the nickname inside the translation block, all I have to do is surround it with percentages %. Unfortunately, this doesn't seem to work when I pass it to trans.
Now I would like to translate a whole block of text, using tags. I can't figure out how I can use the tags in my translation. So, my twig would look something like this:
{{ say.hello|trans }}
And my translation snippet
<trans-unit id="1">
<source>say.hello</source>
<target>Hello %nickName%, how are you doing today? lots-of-text-here</target>
</trans-unit>
I got it working by using this in my template, but it feels like doing things twice. I now need to put the array of placeholder into the trans function again. For example:
{{ say.hello|trans('%nickName%' : nickName) }}
If I want to use other tags that are given to twig in my controller, I need to pass them to the translator as well. Can't I just pass the complete array somehow?
{{ say.hello|trans('%nickname%': 'rolandow') }}
There are several questions here so let's cover them.
1) Twig's behaviour is not like a Doctrine query, where each parameter must be bounded. You can pass an array that contains unused parameters to trans, so if you don't want to specify {'key': 'value', 'key2': 'value2'...} to the filter, just pass the entire array (example: | trans(array)). That's #Luke point.
2) You can translate block of texts using several ways, the most simple is {% set %}. The {% set %} tag can be used two ways :
{% set var = expression %} or {% set var1, var2 = expression1, expression2 %} is the most known and used way: you just put some value inside one or several variables.
{% set var %} block of text {% endset %} allow you to set an entire block of text inside that variable. This is useful if you want to put that block into a filter (such as, escape, or in your case, trans).
So to translate a block of text, you'll do something like:
{% set variable %}
block to translate %placeholder%
{% endset %}
{{ variable | trans(array) }}
Anyway, I don't see any interest of translating a whole block in one time : we use | trans generally after a property (such as say.hello), and I can't imagine your xlf/yml translation file with such a design. If you want to use the translator just to fulfill placeholders, just use Twig as it is written for that job :-)
3) About replacing placeholder by %placeholder% in your parameters array's keys : the point of Twig is: put what you want as placeholder. In such a way, if your translated sentence contains several %, you can use $something$, #something# or even something as placeholder.
If your array keys does not contain those %, you need to add them, you don't have any choice. If you really want to do it on a Twig file, you can create a macro that will do the job for you, and put it in a file you import in your base layout.
Something like :
{% macro trans_pct(property, params) %}
{% set newParams = [] }
{% if params %}
{% for key, value in params %}
{% set newParams['%' ~ key ~ '%'] = value %}
{% endfor %}
{% endif %}
{{ property | trans(newParams) }}
{% endmacro %}
And then use it with {{ _self.trans_pct('hello.say', array) | trim }}.
Notes :
_self is the template where is stored the macro (see the documentation for more details).
trim is used because I wrote the macro with indentation and line breaks (that's cleaner to read). Those spaces are, by default, printed.

Extending twig for generate html code

I have to generate something like star rating and I have to generate some html for styling ect.
<div class="star on"><i>*</i></div>
<div class="star on"><i>*</i></div>
<div class="star on"><i>*</i></div>
<div class="star"><i></i></div>
<div class="star"><i></i></div>
I want to render using a twig function passing active stars parameters.
{{ stars(4) }}
Is correct use twig functions for generate html code?
Or maybe should I use {% include ... %}
No need in overengineering for such simple task.
If you generate your array in Controller, then it could look like this:
$stars = array(
true,
true,
true,
false,
false,
);
Then you could render your HTML in Twig:
{% for star in stars %}
<div class="star{{ star ? ' on' }}"<i>{{ star ? '*' }}</i></div>
{% endfor %}
In case if you would like to operate with Twig only, I recommend you to use macro:
{% macro stars(stars, total) %}
{% for item in 1..total %}
{{ item }}<br>
{% if item <= stars %}
<div class="star on"><i>*</i></div>
{% else %}
<div class="star"><i></i></div>
{% endif %}
{% endfor %}
{% endmacro %}
If you've defined your macro in the same template, you should call it via _self, if in another file - just like a function, but not forget to import your file into needed twig. See chapter about macros (linked above).
Following call will produce HTML structure that you described in your question:
{{ _self.stars(3,5) }}
See the Extending Twig section of its docs. According to the table in the first section on that page, using functions for content generation is natural. I create a lot of Twig functions and I suggest you create one to solve your problem.
BTW, your function can render a separate template with HTML code — do not generate the HTML code right in your Twig function's PHP code. To render a separate template from your Twig function, inject the service_container service into it, get the templating service and call the render() method on it:
return $this->container->get('templating')->render($pathToYourCustomTemplate);
Usually, it's best to inject the needed services individually, but if you inject the templating service instead of service_container, you'll get a cyclic dependencies problem. That's why injecting the whole container into Twig extensions is a reasonable exception.

Resources