Documentation for `values` filter is missing
I was about to create a feature request to add the ability to access array items relative to the current one in a loop because using index access (e.g. items[loop.index0 - 1]) doesn't work in many cases, including simple arrays that got sorted with the sort filter.
But I found out that problem can be solved with the values filter. It seems to be available in my environment but this filter is missing in the docs, even though a keys filter is documented. Is there a reason for that or is that a mistake?
The values filter is not part of the Twig CoreExtension. It might have been registered by a third-party extension you have added in your environment
What about using {% for value in items %}?
@stof You're right. The values filter is coming from Craft CMS. It was #2081 plus the fact that there's a keys filter that made me believe it's part of Twig. But that means my original problem actually cannot be solved the way I did it in this specific case.
@fabpot Yes, I am using that kind of loop. The actual problem I'm facing is a little more complex. I have an array like this:
{% set items = ['b', 'c', 'a'] %}
Which is like the following in PHP:
[0 => 'b', 1 => 'c', 2 => 'a']
Now I use the sort filter to sort the array by value. It took me a while to figure out that the result actually looks like this:
[2 => 'a', 0 => 'b', 1 => 'c']
Notice the keys being shuffled. I do understand why this happens. Now I loop over this sorted array which in itself is still fine:
{% for item in items|sort %}{# ... #}{% endfor %}
But inside this loop I now need to change the output depending on the item of the previous loop iteration. So my intuition was to do the following:
{% for item in items|sort %}
{% set prevItem = items[loop.index0 - 1] %}
{% endfor %}
But this doesn't work because of the indexes all being shuffled. To sum it up:
- Iteration 1:
item == 'a'- expected:
prevItemto be not defined - actually:
prevItem == 'c'
- expected:
- Iteration 2:
item == 'b'- expected:
prevItem == 'a' - actually:
prevItemis not defined (out of range)
- expected:
- Iteration 3:
item == 'c'- expected:
prevItem == 'b' - actually:
prevItem == 'b'(correct by accident)
- expected:
The values filter does solve this issue in a way by "re-indexing" the array because I can do this:
{% for item in items|sort|values %}{# ... #}{% endfor %}
I just played around and found a way to solve this in pure Twig (Playground link):
{% set items = ['b', 'c', 'a'] %}
{% set sortedItems = items|sort %}
{% set indexes = sortedItems|keys %}
{% for key, item in sortedItems %}
{% set prevIndex = indexes[loop.index0 - 1] ?? null %}
{% set prevItem = prevIndex is not null ? sortedItems[prevIndex] %}
{% set nextIndex = indexes[loop.index0 + 1] ?? null %}
{% set nextItem = nextIndex is not null ? sortedItems[nextIndex] %}
* ({{ key }} => {{ item }})
* PREV: ({{ prevIndex|default('-') }} => {{ prevItem|default('-') }})
* NEXT: ({{ nextIndex|default('-') }} => {{ nextItem|default('-') }})
{% endfor %}
It is still a bit lengthy though. Is it worth finding a shortcut for this, maybe also from a performance stand point? Essentially it would be helpful to get an array item using its positional index instead of its array key.
In Twig 4 this will become easier with the new loop.previous and loop.next variables.
That is great to hear and should cover most use-cases.
While writing this comment and thinking more about this I just realized a few things though: |slice(0) actually has the same effect as a hypothetical |values, which means it could make my example a bit simpler already. But more importantly, this problem could be completely avoided if we had somehow access to the sort() function of PHP. Then, simple index access like {{ sortedItems[loop.index0 - 1] }} would just work as expected.
Now, I don't know, is Twig a library that prefers to be "smart"? If so, a way to add access to it would be if the sort filter did something like the following under the hood:
if (array_is_list($array)) {
sort($array);
} else {
asort($array);
}
If not, can we have explicit access to PHP sort()? Either through a separate filter or an argument for the current sort filter? What are your thoughts on this?