Iteration on basis of integer in TYPO3 Fluid

103 views Asked by At

Is it possible to create an iteration that does not iterate over arrays but uses an integer instead? This is the example provided in the documentation:

<f:for each="{0:1, 1:2, 2:3, 3:4}" ...>

I am looking for the possibility to insert an integer which then defines the number of iterations. In JavaScript it would look like this:

for (let a = 0; a < iterations; a++) {...}
2

There are 2 answers

0
Garvin Hicking On BEST ANSWER

The core doesn't ship this, if you want to avoid a custom viewhelper and maybe even have a dependency on EXT:vhs you could use this one:

https://viewhelpers.fluidtypo3.org/fluidtypo3/vhs/5.0.1/Iterator/For.html

0
Doku On

You could create your own ViewHelper.

Below is my implementation of such a ViewHelper:

use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
use TYPO3Fluid\Fluid\Core\ViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;
use TYPO3\CMS\Extbase\Utility\DebuggerUtility;
    
final class LoopViewHelper extends AbstractViewHelper
{
    use CompileWithRenderStatic;

    /**
     * @var bool
     */
    protected $escapeOutput = false;

    public function initializeArguments()
    {
        parent::initializeArguments();
        $this->registerArgument('increment', 'int', 'The number determing the increment e.g. 2 -> 1, 3, 5, ... (should be positive - absolute value is used!)', false, 1);
        $this->registerArgument('start', 'int', 'The starting value to count from', false, 0);
        $this->registerArgument('end', 'int', 'The value to count to e.g. 7 and increment 3 -> 1, 3, 6 ( using <=)', true);
        $this->registerArgument('reverse', 'boolean', 'If TRUE, iterates in reverse e.g from start to finish with decrement (using increment value) instead of increment', false, false);
        $this->registerArgument('iterator', 'string', 'The name of the variable to store iteration information (index, isFirst, isLast, isEven, isOdd)', false, 'iterator');
    }

    /**
     * @param array $arguments
     * @param \Closure $renderChildrenClosure
     * @param RenderingContextInterface $renderingContext
     * @return string
     * @throws ViewHelper\Exception
     */
    public static function renderStatic(
        array $arguments, 
        \Closure $renderChildrenClosure, 
        RenderingContextInterface $renderingContext
    ): string {
        $templateVariableContainer = $renderingContext->getVariableProvider();
        if (!isset($arguments['end'])) {
            return '';
        }

        if ($arguments['reverse']) {
            $iterationData = [
                'index' => $arguments['end'],
            ];
            $end = $arguments['start'];
            $stepValue =  abs($arguments['increment']) * -1;
            $comparison = 'compareGreater';
        } else {
            $iterationData = [
                'index' => $arguments['start'],
            ];
            $end = $arguments['end'];
            $stepValue = abs($arguments['increment']);
            $comparison = 'compareLesser';
        }

        $output = '';
        for ($i = $iterationData['index']; self::$comparison($i, $end); $i = $i + $stepValue) {
            // $iterationData['isFirst'] = $arguments['start'] === $i;
            // $iterationData['isLast'] = $i === $iterationData['total'] + $stepValue;
            $iterationData['index'] = $i;
            $iterationData['isEven'] = ($i + 1) % 2 === 0;
            $iterationData['isOdd'] = !$iterationData['isEven'];
            $templateVariableContainer->add($arguments['iterator'], $iterationData);
            
            $output .= $renderChildrenClosure();
            $templateVariableContainer->remove($arguments['iterator']);
        }
        return $output;
    }

    private static function compareGreater($left, $right): bool
    {
      return $left >= $right;
    }

    private static function compareLesser($left, $right): bool
    {
      return $left <= $right;
    }
}

Fluid:

{namespace wow=Vendor\Ext\ViewHelpers}
<wow:loop start="1" end="53" iterator="iterator">
  {iterator.index}
</wow:loop>

Edit: After checking out Garvin Hickings' answer, I took a look at the vhs implementation of this view helper, and it's much cleaner than my version. So, for those who don't want to rely on vhs, you can just use this single ViewHelper.

I'll keep my previous implementation above for anyone who's interested in an alternative approach.