Problem creating JSON outuput in Twig Template - twig

I'm learning to use the Craft CMS, which uses Twig templating. I'm trying to output a JSON object in Twig, but instead of 2 items in the JSON I'm getting info about a single item.
Here is my code:
{% set newsitems = craft.entries.section('newsitems').orderBy('PostDate desc').limit(100) %}
{% set response = [] %}
{% for newsitem in newsitems %}
{{ 'Here' }}
{% set response = response|merge({'type':0, 'id':newsitem.id, 'link':newsitem.sourceLink}) %}
{% endfor %}
{{ response|json_encode() }}
And here is the output I get:
Here Here {"type":0,"id":"25","link":"https:\/\/gadgets.ndtv.com"}
As can be seen, the loop executes two times ('Here' is printed 2 times) but there is only one item in the JSON array which is printed.
Am I missing something basic? Any help would be appreciated. Thanks in advance.

Twig's merge filter uses array_merge in the background.
The manual states the following
If the input arrays have the same string keys, then the later value for that key will overwrite the previous one. If, however, the arrays contain numeric keys, the later value will not overwrite the original value, but will be appended.
This is what is happening to your output, in the first iteration you've create an associative array with the key: type, id, link. In the x'th iteration you are just overwriting the values stored in said keys. The solution is also stated in the manual, numeric indices will be appended to the array instead of overwriting it.
In twig you would solve it as this:
{% set response = [] %}
{% for newsitem in newsitems %}
{% set response = response|merge([{ 'type': 0, 'id': newsitem.id, 'source': newsitem.source,},]) %}
{% endfor %}
{{ response|json_encode|raw }}
demo

Related

Check if all elements in one array are contained by another | JS Array.every() equivalent

I'm looping over a collection of blog posts (Twig for loop) which appearance depends on tags given.
Here a quick example: I want to display all blog posts that have the tags "foo" and "bar".
It seems pretty easy to check if a post has one of the tags.
However it seems that checking if both tags are contained by a blog post isn't trivial.
What I want to accomplish is what the array.every() method in javascript does.
That's my current solution which works as expected but feels kinda fiddly and overcomplicated:
{% set given_tags_array = data.tags|split(',') %}
{% for post in posts %}
{% set post_categories_array = post.categories|map(category => category.name) %}
{% set bool_buffer_array = [] %}
{# push comparison result in bool array #}
{% for tag in given_tags_array %}
{% set bool_buffer_array = bool_buffer_array|merge([tag in post_categories_array]) %}
{% endfor %}
{# only display posts where all tags match #}
{% if false in bool_buffer_array %}
{% else %}
{# post data goes here #}
{% endif %}
{% endfor %}
As you can see inside the posts loop I'm checking if every given tag (i.e. "foo" and "bar") is part of the post categories array. I'm pushing the comparison result (boolean) to an empty array to check for any false values afterwards.
Why an array? I tried using a simple boolean variable but if any of the given tags is in the post categories array it resolves to true, which isn't exactly what I want.
So something like that doesn't work for me unfortunately:
{% for post in posts %}
{% set post_categories_array = post.categories|map(category => category.name)|sort|join('') %}
{% if given_tags_array|filter(given_tag => given_tag in post_categories_array) %}
{# post data goes here #}
{% endif %}
{% endfor %}
With this method I'm always doing an or comparison instead of an and comparison...
So...am I missing something and is there a simpler way to do that twig only?
Using the code you've already provided:
{% set temp = given_tags_array|filter(given_tag => given_tag in post_categories_array) %}
The filter filter returns a new array, this means temp should contain as many elements as your given_tags_array, if they are all inside the post_categories_array.
So if I'm not mistaken you could change your check to the following
{% for post in posts %}
{% set post_categories_array = post.categories|map(category => category.name)|sort|join('') %}
{% set temp = given_tags_array|filter(given_tag => given_tag in post_categories_array) %}
{% if temp|length == given_tags_array|length %}
{# display post #}
{% endif %}
{% endfor %}

"Illegal offset type in isset or empty" when using a Twig macro

Using Grav v1.3.8 (running on PHP 5.6.30), I'm currently getting a Server error ("Illegal offset type in isset or empty") when trying to render a Twig template that is using a macro.
What's interesting is that this only happens when I use the macro by itself. As soon as I append a filter, like trim, everything works as expected.
The (shortened) macro file, helpers.twig:
{% macro ascii(str) %}
{% spaceless %}
{{ str|replace({
'Á': 'A',
'À': 'A',
'Â': 'A',
'Ã': 'A',
....
'ƒ': 'f'
})
}}
{% endspaceless %}
{% endmacro ascii %}
The template (MCVE):
{% import 'macros/helpers.twig' as helpers %}
{% set img = helpers.ascii('günter-berger.jpg') %}
{% if page.media[img] is defined %}
<img src="{{ page.media[img].url }}">
{% endif %}
This will produce the error. I narrowed it down to the if line. Apparently, the macro is working fine, but the condition will throw an error if fed the output of it, unfiltered. Adding any filter, like trim or lower, will get it to work again.
In other words, these work:
{% if page.media['günter-berger.jpg'] is defined %}
{% if page.media[helpers.ascii('günter-berger.jpg')|trim] is defined %}
But this will throw an error:
{% if page.media[helpers.ascii('günter-berger.jpg')] is defined %}
However, trying the same thing on twigfiddle, all three seem to work there.
Maybe an issue with Grav? Can someone point out any possible causes?
I forgot this, but a macro does not return a string but instead returns an instance of a Twig_Markup
{% set test = macro.ascii('Ghünter.jpg') %}
{{ dump(test) }}
Output : object(Twig_Markup)#10679 (2) { ["content":protected]=> string(11) "Ghunter.jpg" ["charset":protected]=> string(5) "UTF-8" }
Because the return type is an object you get this notification as you can't use objects as index. By using a filter on this instance, the magic method __toString method will be called, causing it to return a string, thus making it useable as index for an array
The only was to bypass this, would be writing a filter instead of a macro

how to use split twig template field content type in drupal 8?

I need your help to solve the problem with split in twig.
I want to separate my var {{label}} in an array,
try using
{% set array = label | split (" ")%}
returns empty
I want to separate a field node in an array,
try using
{% set array = content.field_fieldname | split (" ")%}
returns empty
if someone helps me identify what I'm doing wrong, or I indicates a guide and / or tutorial that can solve my problem. Thanks
How do you check that the array is empty?
{% set label = 'this is a label'%}
{% set array = label | split (" ")%}
{% for elem in array %}
{{loop.index}} - {{ elem}}
{% endfor %}
Check this code online here
{{ label }} is in the template as an object, I solved get the string
{% set title = items[0]['content']['#context']["value"]|split(' ', 2) %}
<h1>{{ title[0] }}</h1>
{{ title[1] }}

Twig Access Array Index?

Is it possible to directly access an array index from within a Twig template?
Here's my setup, using Silex:
return $app['twig']->render('template', array('numbers' => array('one', 'two', 'three')));
so can I do something like this?
{{numbers[0]}}
Just before posting this I realized, that's exactly what you can do, but as I didn't find the answer anywhere in the docs or google (correct me if I'm wrong), I've posted this anyway.
{{numbers[0]}}
The answer of Adam, is correct, only to make it clear and improve,
you can have access directly to array index
{{ myArray[0] }}
if you need to access in a loop
{% set arrayOfItems = ['ZERO', 'ONE'] %}
{% set myArray = ['APPLE', 'ORANGE'] %}
{% for oneItem in arrayOfItems %}
<p>{{ oneItem }} equals {{ myArray[loop.index0] }}</p>
{% endfor %}
in this example I used an array inside a non related loop so the result is:
ZERO equals APPLE
ONE equals ORANGE
Thats actually something what doesnt work for me when using Twig with shopware 6.
I try to access an object like
{{ page.cart.lineItems.elements[0].quantity }}
what will lead into a parsing error of the Twig Template
I can use
{{ page.cart.lineItems.elements | first }}
to get the first Element, but dont know how i can then access a property of this first element

How do you translate array items and join them?

Using twig, how can I translate all items in an array and join them with a slash?
Do I have to use an additional variable or is there a cleverer method?
For the moment, I'm doing something like this:
{% set labels = [] %}
{% for feature in menu_item.features %}
{% set labels = labels|merge([feature|trans([], 'features')]) %}
{% endfor %}
{{ labels | join(' / ')}}
It sucks.
Why not just output the content while you're looping ?
{% for feature in menu_item.features %}
{% if loop.index0 > 0 %}/{% endif %}
{{feature|trans}}
{% endfor %}
Maybe I'm late to the party, but you can now do this easily with the map filter:
{{ menu_item.features|map(feature => feature|trans)|join(' / ') }}
See documentation:
Twig >v1.41: https://twig.symfony.com/doc/1.x/filters/map.html
Twig >v2.10: https://twig.symfony.com/doc/2.x/filters/map.html
Twig v3.x: https://twig.symfony.com/doc/3.x/filters/map.html
Not everything should be done within the "view".
This type of code is probably much better placed within your controller logic and then passed into the view as the merged+joined result. Because in your example all you're doing is compiling a result which can much more easily be done within code.

Resources