How to sort ViewHelper array? - typo3

I have the following template:
<f:for each="{matchday.matches}" as="match" iteration="matchIterator">
<f:if condition="{match.home_team._id} == {team}">
<f:render section="match" arguments="{match: match, is_home: 1, round_number: matchday.round_number}" />
</f:if>
<f:if condition="{match.away_team._id} == {team}">
<f:render section="match" arguments="{match: match, is_home: 0, round_number: matchday.round_number}" />
</f:if>
All entries are sorted by Id. matches has an attribute play_date which contains the correct date of the match.
How I can sort the array by date before output?
I already tried using SortViewHelper but it didn't work:
<f:for each="{matchday.matches->v:iterator.sort(order: 'ASC', sortBy:'play_date')}" as="match" iteration="matchIterator">

Related

Create arrays and objects with fluids f:variable

Is it possible to create an array or an object with f:variable in TYPO3 9.x?
The example below does not work, because it treat the object as an string.
<f:variable name="person">{firstname:'Max', lastname:'Mustermann'}</f:variable>
Update: This example is working:
<f:variable name="person" value="{firstname:'Max', lastname:'Mustermann'}" />
Why is the tag syntax treaten differently?
What's interesting, is the fact that you can 'build' arrays in fluid and pass them as arguments to a partial.
Example:
<f:render partial="Components/ImageGallery" section="ImageGallery" arguments="{
imageGallery: {
0:{imageurl:'https://www.placehold.it/640x480',imagealt:'First Image', },
1:{imageurl:'https://www.placehold.it/640x480',imagealt:'Second Alt'}
}
}" />
It would be great, if it is possible in fluid to build this object with f:variable. Yes i know, i could assign this object, but that is not my intention.
Here you have an example partial from https://github.com/Startpiloten/startpilot
<html xmlns:f="http://typo3.org/ns/TYPO3/CMS/Fluid/ViewHelpers" data-namespace-typo3-fluid="true">
<f:if condition="{file}">
<f:if condition="{breakpoints}">
<f:else>
<f:variable name="breakpoints" value="{settings.breakpoints}" />
</f:else>
</f:if>
<f:link.typolink parameter="{file.link}" class="picturelink">
<picture>
<f:for each="{breakpoints}" as="breakpoint" iteration="i_breakpoints">
<f:if condition="{i_breakpoints.isLast}">
<f:else>
<source srcset="{f:uri.image(image: file, maxWidth: breakpoint.maxWidth, cropVariant: breakpoint.cropVariant)}" media="({breakpoint.media}: {breakpoint.size}px)">
</f:else>
<f:then>
<source srcset="{f:uri.image(image: file, maxWidth: breakpoint.maxWidth, cropVariant: breakpoint.cropVariant)}" media="({breakpoint.media}: {breakpoint.size}px)">
<img class="img-fluid" src="{f:uri.image(image: file, maxWidth: breakpoint.maxWidth, cropVariant: breakpoint.cropVariant)}" alt="{file.alternative}" title="{file.title}">
</f:then>
</f:if>
</f:for>
</picture>
</f:link.typolink>
</f:if>
<f:comment>
<!---Render Image with custom sizes from TS settings--->
<f:render partial="ImageRender" arguments="{file:file, breakpoints:settings.breakpoints}" />
<!---Render Image with custom sizes -- START --->
<f:variable name="breakpoints" value="{
0:{media:'max-width', size:375, maxWidth:375, cropVariant:'mobile'},
1:{media:'max-width', size:480, maxWidth:480, cropVariant:'mobile'},
2:{media:'max-width', size:767, maxWidth:767, cropVariant:'tablet'},
3:{media:'max-width', size:991, maxWidth:991, cropVariant:'tablet'},
4:{media:'max-width', size:1279, maxWidth:1279, cropVariant:'default'},
5:{media:'max-width', size:1479, maxWidth:1479, cropVariant:'default'},
6:{media:'min-width', size:1480, maxWidth:2000, cropVariant:'default'}
}"/>
<f:render partial="ImageRender" arguments="{file:image, breakpoints:breakpoints}" />
<!---Render Image with custom sizes -- END --->
<!---Render Image with default sizes -- START --->
<f:render partial="ImageRender" arguments="{file:image}" />
</f:comment>
</html>

Add to the second position of a Fluid array

Currently I have this code that wraps every 2 items with <li></li>
<f:for each="{modules}" as="module" iteration="loop">
<f:cycle values="{0: 1, 1: 0}" as="header">
<li>
</f:cycle>
...stuff here...
<f:cycle values="{0: 0, 1: 1}" as="footer">
</li>
</f:cycle>
</f:for>
Lets say {modules} = [A,B,C,D] and output is something like this:
<li>AB</li>
<li>CD</li>
What I need to do is add custom html to the second position. So my desired output is:
<li>AX</li>
<li>BC</li>
<li>D</li>
How can I do this?
To your updated question I would say, you may want to solve this issue with your own Helper class before adding modules to the View through the Controller.
It is however possible to build it simple in the template:
Here is the documentation about the for ViewHelper.
For simplicity I would do the following:
<f:for each="{modules}" as="module" iteration="loop">
<f:if condition="{loop.isFirst}">
<f:then>
<li>
{module}
##extra_code##
</li>
</f:then>
<f:else>
<f:if condition="{loop.isEven}">
<li>
</f:if>
...stuff here...
<f:if condition="{loop.isOdd} || {loop.isLast}">
</li>
</f:if>
</f:else>
</f:if>
</f:for>
Note: The first element will be "odd" because isOdd takes the current interation (based on "cycle"), which starts with 1.
For the first element there is your own block. You can take the first module and write your own code. It will run only once.
From the second element to the last it will use the 2 element in one li logic. Here is only one interesting thing, that we need take isOdd OR isLast to always close the last li, even if you have an even number of elements.
You can do this like below.
<f:for each="{modules}" as="module" iteration="iteration">
<f:if condition="{iteration.isFirst}">
<f:then>
<li>
</f:then>
<f:else>
<f:if condition="{iteration.index}%2">
<f:then></f:then>
<f:else><li></f:else>
</f:if>
</f:else>
</f:if>
<!--
Here Your Content
-->
<f:if condition="{iteration.isLast}">
<f:then>
</li>
</f:then>
<f:else>
<f:if condition="{iteration.cycle}%2">
<f:then></f:then>
<f:else></li></f:else>
</f:if>
</f:else>
</f:if>
</f:for>

list items per property (here date) in the list view

i have a simple extbase extension with a model "item".
Each item has a title, a description and a date.
How can i list my items according to the date?
e.G.
24.06.17:
item 2
item 6
item 7
25.06.14:
item 1
...
i search the logic to realize this (in the view or in the controller)
In your code you can sort your record by date.
In fluid you can use the vhs viewhelpers set:
https://fluidtypo3.org/viewhelpers/vhs/master/Variable/SetViewHelper.html
Something likes (I don't test it):
<f:for each="items" as="item">
<f:if condition="{item.date}=={current}">
<f:then>
{item.title}
</f:then>
<f:else>
<br/>{item.date}: {item.title}
</f:else>
</f:if>
<v:variable.set name="current" value="{item.date}" />
</f:for>
You can use vhs viewhelpers like below.
<f:for each="{dataset -> vhs:iterator.sort(sortBy: 'date')}" as="item">
.........
.........
</f:for>

Fluid access other array in for loop

I have to arrays like:
$business = array(0 => 'Car', 1 => 'IT');
$counts = array(0 => 15, 1 => 33);
I assign both array in my fluid template and iterate over the array business.
<f:for each="{business}" as="b" key="key">
<li>
<f:link.action action="business" arguments="{current_business: b.uid}">
<f:if condition="{counts.key} > 0">
<f:then>
{b.title} {counts.key}
</f:then>
<f:else>
{b.title}
</f:else>
</f:if>
</f:link.action>
</li>
</f:for>
I don't get an ouput for {counts.key}, should i access this an other way?
This can be done using the v:variable.get-ViewHelper from the extension vhs. Instead of {counts.key} use
{v:variable.get(name: 'counts.{key}')}
or
<v:variable.get name="counts.{key}"/>.
Of course you can do it as Jost showed by manipulating data within the view , it's perfectly valid, on the other hand probably combining arrays within your controllers into associative array(s) will be just more comfortable, like:
$business = array(
0 => array('title' => 'Car', 'count' => '15'),
1 => array('title' => 'IT', 'count' => '33'),
);
view
<f:for each="{business}" as="b">
<li>
<f:link.action action="business" arguments="{current_business: b.uid}">
<f:if condition="{b.count} > 0">
<f:then>
{b.title} ({b.count})
</f:then>
<f:else>
{b.title} (no items)
</f:else>
</f:if>
</f:link.action>
</li>
</f:for>
Note: If in real case that's a collection of model objects, you can just add transient - count field into your model - without TCA and SQL declarations - in this case you will be able to set values "on the fly" on this field within the controller and use them as common model field in the views but they won't be saved to DB.

how to: find the length of a typoscript defined array in fluid?

I define an array in my extensions setup.txt typoscript like so:
settings{
halls {
0 = Default
1 = Mississauga
2 = Halifax
3 = Niagara Falls
4 = Oakville
5 = Pickering
}}
how in my fluid template can I find the length of the halls array? I want an if condition to do one thing if length is greater than 1 and another if not.
I am using typo3 v4.5.32 with extbase 1.3.
Thank you.
I tried this but it puts out two <th> tags instead of just one. The first if is meant to prove that there is more than one element, the next is meant to print out a <th> only if it is the first element.
<f:for each="{settings.halls}" as="hall" key="number" iteration="itemIteration">
<f:if condition="{itemIteration.isFirst != itemIteration.isLast}">
<f:if condition="{itemIteration.index == 0}">
<th>
<f:translate key="tx_bpscoupons_domain_model_coupon.hall" />
</th>
</f:if>
</f:if>
</f:for>
Why?
UPDATE: here is my work around for a lack of "is array length > 1" ferature in fluid
<f:for each="{settings.halls}" as="hall" key="number" iteration="itemIteration">
<f:if condition="{itemIteration.isFirst}">
<f:then>
<f:if condition="{itemIteration.isLast}">
// if both first and last there is only one so skip
</f:if>
</f:then>
<f:else>
<f:if condition="{itemIteration.isLast}">
//if there is more than one element in halls then eventually you'll get here once so print out th tags
<th>
<f:translate key="tx_bpscoupons_domain_model_coupon.hall" />
</th>
</f:if>
</f:else>
</f:if>
</f:for>
and to print out data:
<f:for each="{settings.halls}" as="hall" key="number" iteration="itemIteration">
<f:if condition="{itemIteration.isFirst}">
<f:then>
<f:if condition="{itemIteration.isLast}">
<f:then>
</f:then>
<f:else>
<f:if condition="{number} == {coupon.hall}">
<td>{hall}</td>
</f:if>
</f:else>
</f:if>
</f:then>
<f:else>
<f:if condition="{number} == {coupon.hall}">
<td>{hall}</td>
</f:if>
</f:else>
</f:if>
</f:for>
ugly but seems to work.
UPDATE 2 :
DOH!!! I missed total, just what I needed but I was looking for length or count, so this code is what I want:
<f:for each="{settings.halls}" as="hall" key="number" iteration="itemIteration">
<f:if condition="{itemIteration.total} > 1">
<f:if condition="{itemIteration.isLast}">
<th><f:translate key="tx_bpscoupons_domain_model_coupon.hall" /></th>
</f:if>
</f:if>
</f:for>
You can find a length of an array with the f:count ViewHelper. Just like other ViewHelper it can be used as an inline notation (You can find more here)
The correct code would be:
<f:if condition="{settings.halls -> f.count()} > 1">
<f:then>
//your code for then
</f:then>
<f:else>
//your code for else
</f:else>
</f:if>
<f:if condition="{f:count(subject: settings.halls)} > 1"> do something </f:if>
see update#2 above for the answer. to reiterate:
<f:for each="{settings.halls}" as="hall" key="number" iteration="itemIteration">
<f:if condition="{itemIteration.total} > 1">
<f:if condition="{itemIteration.isLast}">
<th><f:translate key="tx_bpscoupons_domain_model_coupon.hall" /></th>
</f:if>
</f:if>
</f:for>
For those of you who are looking for a custom viewhelper for this:
class ArrayCountViewHelper extends \TYPO3\CMS\Fluid\Core\ViewHelper\AbstractViewHelper {
/**
* Get array count
*
* #param mixed $array
* #return string
*/
public function render($array) {
if (!is_array($array))
return 0;
return count($array);
}
}