Skip to content

Instantly share code, notes, and snippets.

@daKmoR
Created October 14, 2011 14:05
Show Gist options
  • Star 5 You must be signed in to star a gist
  • Fork 1 You must be signed in to fork a gist
  • Save daKmoR/1287203 to your computer and use it in GitHub Desktop.
Save daKmoR/1287203 to your computer and use it in GitHub Desktop.
GroupedForDateTimeViewHelper for Fluid
<div class="itemsListYearMonth">
<a:groupedForDateTime each="{items}" as="itemsByYear" groupBy="start" format="Y" groupKey="year">
<a:groupedForDateTime each="{itemsByYear}" as="itemsByMonth" groupBy="start" format="m" dateTimeKey="month">
<div class="yearMonth clearfix">
<div class="timeframe" data-behavior="FixTimeframe">
{month -> f:format.date(format: 'F Y')}
</div>
<f:for each="{itemsByMonth}" as="item">
<div class="item">
<p class="startEnd"><a:format.duration start="{item.start}" end="{item.end}" /></p>
<h2>{item.name}</h2>
<div class="text">
<f:format.html>{item.bodyText}</f:format.html>
</div>
</div>
</f:for>
</div>
</a:groupedForDateTime>
</a:groupedForDateTime>
</div>
<div class="itemsListYearMonth">
<div class="yearMonth clearfix">
<div class="timeframe" data-behavior="FixTimeframe">
Oktober 2011
</div>
<div class="item">
<p class="startEnd">24.–25.10.2011</p>
<h2>Laborführung</h2>
<div class="text">
<p>[...]</p>
</div>
</div>
<div class="item">
<p class="startEnd">27.10.–02.11.2011</p>
<h2>Sitzung</h2>
<div class="text">
<p>[...]</p>
</div>
</div>
</div>
<div class="yearMonth clearfix">
<div class="timeframe" data-behavior="FixTimeframe">
Dezember 2011
</div>
<div class="item">
<p class="startEnd">24.12.2011</p>
<h2>Weihnachten</h2>
<div class="text">
<p>[...]</p>
</div>
</div>
</div>
</div>
<?php
/* *
* This script belongs to the FLOW3 package "Fluid". *
* *
* It is free software; you can redistribute it and/or modify it under *
* the terms of the GNU Lesser General Public License as published by the *
* Free Software Foundation, either version 3 of the License, or (at your *
* option) any later version. *
* *
* This script is distributed in the hope that it will be useful, but *
* WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN- *
* TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser *
* General Public License for more details. *
* *
* You should have received a copy of the GNU Lesser General Public *
* License along with the script. *
* If not, see http://www.gnu.org/licenses/lgpl.html *
* *
* The TYPO3 project - inspiring people to share! *
* */
/**
* Grouped loop view helper for Datetime values.
* Loops through the specified values
*
* = Examples =
*
* <code title="Simple">
* // $items = array(
* // array('name' => 'apple', 'start' => DateTimeObject[2011-10-13 00:15:00]),
* // array('name' => 'orange', 'start' => DateTimeObject[2011-12-01 00:10:00]),
* // array('name' => 'banana', 'start' => DateTimeObject[2008-05-24 00:40:00])
* // );
* <a:groupedForDateTime each="{items}" as="itemsByYear" groupBy="start" format="Y" groupKey="year">
* {year -> f:format.date(format: 'Y')}
* <f:for each="{itemsByYear}" as="item">
* {item.name}
* </f:for>
* </a:groupedForDateTime>
* </code>
*
* Output:
* 2011
* apple
* orange
* 2010
* banana
*
* @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 or later
* @api
*/
class Tx_Assets_ViewHelpers_GroupedForDateTimeViewHelper extends Tx_Fluid_Core_ViewHelper_AbstractViewHelper {
/**
* Iterates through elements of $each and renders child nodes
*
* @param array $each The array or Tx_Extbase_Persistence_ObjectStorage to iterated over
* @param string $as The name of the iteration variable
* @param string $groupBy Group by this property
* @param string $groupKey The name of the variable to store the current group
* @param string $format The format for the datetime
* @param string $dateTimeKey The name of the variable to store the current datetime
* @return string Rendered string
* @author Bastian Waidelich <bastian@typo3.org>
* @author Thomas Allmer <at@delusionworld.com>
* @api
*/
public function render($each, $as, $groupBy, $groupKey = 'groupKey', $format = '', $dateTimeKey = 'dateTimeKey') {
$output = '';
if ($each === NULL) {
return '';
}
if (is_object($each)) {
if (!$each instanceof Traversable) {
throw new Tx_Fluid_Core_ViewHelper_Exception('GroupedForViewHelper only supports arrays and objects implementing Traversable interface' , 1253108907);
}
$each = iterator_to_array($each);
}
$groups = $this->groupElements($each, $groupBy, $format);
foreach ($groups['values'] as $currentGroupIndex => $group) {
$this->templateVariableContainer->add($groupKey, $groups['keys'][$currentGroupIndex]);
$this->templateVariableContainer->add($dateTimeKey, $groups['dateTimeKeys'][$currentGroupIndex]);
$this->templateVariableContainer->add($as, $group);
$output .= $this->renderChildren();
$this->templateVariableContainer->remove($groupKey);
$this->templateVariableContainer->remove($dateTimeKey);
$this->templateVariableContainer->remove($as);
}
return $output;
}
/**
* Groups the given array by the specified groupBy property and format for the datetime.
*
* @param array $elements The array / traversable object to be grouped
* @param string $groupBy Group by this property
* @param string $format The format for the datetime
* @return array The grouped array in the form array('keys' => array('key1' => [key1value], 'key2' => [key2value], ...), 'values' => array('key1' => array([key1value] => [element1]), ...), ...)
* @author Bastian Waidelich <bastian@typo3.org>
*/
protected function groupElements(array $elements, $groupBy, $format) {
$groups = array('keys' => array(), 'values' => array());
foreach ($elements as $key => $value) {
if (is_array($value)) {
$currentGroupIndex = isset($value[$groupBy]) ? $value[$groupBy] : NULL;
} elseif (is_object($value)) {
$currentGroupIndex = Tx_Extbase_Reflection_ObjectAccess::getProperty($value, $groupBy);
} else {
throw new Tx_Fluid_Core_ViewHelper_Exception('GroupedForViewHelper only supports multi-dimensional arrays and objects' , 1253120365);
}
$formatedDatetime = $currentGroupIndex->format($format);
$groups['dateTimeKeys'][$formatedDatetime] = $currentGroupIndex;
$currentGroupIndex = $currentGroupIndex->format($format);
$currentGroupKeyValue = $currentGroupIndex;
if (is_object($currentGroupIndex)) {
$currentGroupIndex = spl_object_hash($currentGroupIndex);
}
$groups['keys'][$currentGroupIndex] = $currentGroupKeyValue;
$groups['values'][$currentGroupIndex][$key] = $value;
}
return $groups;
}
}
?>
@solunix
Copy link

solunix commented Aug 8, 2013

Hi, i get an Error if I want to Paginate a grouped array from your Helper-Class:

Oops, an error occurred!
PHP Catchable Fatal Error: Argument 1 passed to Tx_News_ViewHelpers_Widget_PaginateViewHelper::render() must implement interface TYPO3\CMS\Extbase\Persistence\QueryResultInterface, array given in typo3conf/ext/news/Classes/ViewHelpers/Widget/PaginateViewHelper.php line 76

Is it possible to let the returned Result implements QueryResultInterface ?

@gimbli
Copy link

gimbli commented Mar 11, 2016

Hi,

i have a problem by using your solution. If I integrate the ViewHelper in my fluid Template, clear the cache and reload the site. I´m happy.

But when I reload the Site a second time your ViewHelper is ignored by fluid. I found out your ViewHelper is ignored by the System Cache builder.

Disable the Site Cache or the plugin cache not solve the problem.

What can I do. Sorry for my english.

@typo3ua
Copy link

typo3ua commented Jun 27, 2017

This is code not working. Do you have this ViewHelpers for TYPO3 7.6?

@bucheggerOnline
Copy link

bucheggerOnline commented Nov 3, 2017

The problem is, that now extbase is escaping the code with htmlspecialchars. That's easy to fix.

Just add this in the cass GroupedForDateTimeViewHelper:

/** * As this ViewHelper renders HTML, the output must not be escaped. * * @var bool */ protected $escapeOutput = false;

@MrMooky
Copy link

MrMooky commented Mar 27, 2018

Is it possible to change the output to either asc or desc?

@alsodenn
Copy link

Is there a version for TYPO3 9 LTS?

@daKmoR
Copy link
Author

daKmoR commented Apr 20, 2020

I created this 9 years ago 😱
sorry but I won't update this as I barely use TYPO3 anymore 😅

@Megafry
Copy link

Megafry commented Sep 2, 2020

Hi @daKmoR
Thanks for your code, I updated it for TYPO3 10.4 (no guarantee) :-)

{namespace a=Xyz\MyExtname\ViewHelpers}

<?php
namespace Xyz\MyExtname\ViewHelpers;

/*                                                                        *
 * This script belongs to the FLOW3 package "Fluid".                      *
 *                                                                        *
 * It is free software; you can redistribute it and/or modify it under    *
 * the terms of the GNU Lesser General Public License as published by the *
 * Free Software Foundation, either version 3 of the License, or (at your *
 * option) any later version.                                             *
 *                                                                        *
 * This script is distributed in the hope that it will be useful, but     *
 * WITHOUT ANY WARRANTY; without even the implied warranty of MERCHAN-    *
 * TABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser       *
 * General Public License for more details.                               *
 *                                                                        *
 * You should have received a copy of the GNU Lesser General Public       *
 * License along with the script.                                         *
 * If not, see http://www.gnu.org/licenses/lgpl.html                      *
 *                                                                        *
 * The TYPO3 project - inspiring people to share!                         *
 *                                                                        */
 use TYPO3Fluid\Fluid\Core\Rendering\RenderingContextInterface;
 use TYPO3Fluid\Fluid\Core\Variables\VariableExtractor;
 use TYPO3Fluid\Fluid\Core\ViewHelper;
 use TYPO3Fluid\Fluid\Core\ViewHelper\AbstractViewHelper;
 use TYPO3Fluid\Fluid\Core\ViewHelper\Traits\CompileWithRenderStatic;

 /**
 * Grouped loop view helper for Datetime values.
 * Loops through the specified values
 *
 * = Examples =
 *
 * <code title="Simple">
 * // $items = array(
 * //   array('name' => 'apple', 'start' => DateTimeObject[2011-10-13 00:15:00]),
 * //   array('name' => 'orange', 'start' => DateTimeObject[2011-12-01 00:10:00]),
 * //   array('name' => 'banana', 'start' => DateTimeObject[2008-05-24 00:40:00])
 * // );
 * <a:groupedForDateTime each="{items}" as="itemsByYear" groupBy="start" format="Y" groupKey="year">
 *   {year -> f:format.date(format: 'Y')}
 *   <f:for each="{itemsByYear}" as="item">
 *     {item.name}
 *   </f:for>
 * </a:groupedForDateTime>
 * </code>
 *
 * Output:
 * 2011
 *   apple
 *   orange
 * 2010
 *   banana
 *
 * @license http://www.gnu.org/licenses/lgpl.html GNU Lesser General Public License, version 3 or later
 * @api
 */
class GroupedForDateTimeViewHelper extends AbstractViewHelper
{
    use CompileWithRenderStatic;

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


    /**
     * @return void
     */
    public function initializeArguments()
    {
        parent::initializeArguments();
        $this->registerArgument('each', 'array', 'The array or \SplObjectStorage to iterated over', true);
        $this->registerArgument('as', 'string', 'The name of the iteration variable', true);
        $this->registerArgument('groupBy', 'string', 'Group by this property', true);
        $this->registerArgument('groupKey', 'string', 'The name of the variable to store the current group', false, 'groupKey');
        $this->registerArgument('format', 'string', 'The format for the datetime', false);
        $this->registerArgument('dateTimeKey', 'string', 'dateTimeKey The name of the variable to store the current datetime', false);
    }

    /**
     * @param array $arguments
     * @param \Closure $renderChildrenClosure
     * @param RenderingContextInterface $renderingContext
     * @return mixed
     */
    public static function renderStatic(array $arguments, \Closure $renderChildrenClosure, RenderingContextInterface $renderingContext)
    {
        $each = $arguments['each'];
        $as = $arguments['as'];
        $groupBy = $arguments['groupBy'];
        $groupKey = $arguments['groupKey'] ? $arguments['groupKey'] : 'groupKey';
        $format = $arguments['format'] ? $arguments['format'] : '';
        $dateTimeKey = $arguments['dateTimeKey'] ? $arguments['dateTimeKey'] : 'dateTimeKey';
        $output = '';

        if ($each === null) {
            return '';
        }
        if (is_object($each)) {
            if (!$each instanceof \Traversable) {
                throw new ViewHelper\Exception('GroupedForViewHelper only supports arrays and objects implementing \Traversable interface', 1253108907);
            }
            $each = iterator_to_array($each);
        }
        $groups = static::groupElements($each, $groupBy, $format);
        $templateVariableContainer = $renderingContext->getVariableProvider();
        foreach ($groups['values'] as $currentGroupIndex => $group) {
            $templateVariableContainer->add($groupKey, $groups['keys'][$currentGroupIndex]);
            $templateVariableContainer->add($as, $group);
            $output .= $renderChildrenClosure();
            $templateVariableContainer->remove($groupKey);
            $templateVariableContainer->remove($as);
        }
        return $output;
    }

    /**
     * Groups the given array by the specified groupBy property and format for the datetime.
     *
     * @param array $elements The array / traversable object to be grouped
     * @param string $groupBy Group by this property
     * @param string $format The format for the datetime
     * @throws \TYPO3Fluid\Fluid\Core\ViewHelper\Exception
     * @return array The grouped array in the form array('keys' => array('key1' => [key1value], 'key2' => [key2value], ...), 'values' => array('key1' => array([key1value] => [element1]), ...), ...)
     * @author Bastian Waidelich <bastian@typo3.org>
     */
    protected static function groupElements(array $elements, $groupBy, $format)
    {
        $extractor = new VariableExtractor();
        $groups = ['keys' => [], 'values' => []];
        foreach ($elements as $key => $value) {
            if (is_array($value)) {
                $currentGroupIndex = isset($value[$groupBy]) ? $value[$groupBy] : null;
            } elseif (is_object($value)) {
                $currentGroupIndex = $extractor->getByPath($value, $groupBy);
            } else {
                throw new ViewHelper\Exception('GroupedForViewHelper only supports multi-dimensional arrays and objects', 1253120365);
            }

            if (strpos($format, '%') !== false) {
                $formatedDatetime = strftime($format, $currentGroupIndex->format('U'));
            } else {
                $formatedDatetime = $currentGroupIndex->format($format);
            }
            $groups['dateTimeKeys'][$formatedDatetime] = $currentGroupIndex;

            if (strpos($format, '%') !== false) {
                $currentGroupIndex = strftime($format, $currentGroupIndex->format('U'));
            } else {
                $currentGroupIndex = $currentGroupIndex->format($format);
            }

            $currentGroupKeyValue = $currentGroupIndex;
            if ($currentGroupIndex instanceof \DateTime) {
                //  $currentGroupIndex = $currentGroupIndex->format(\DateTime::RFC850);
                $formatedDatetime = $currentGroupIndex->format($format);
                $groups['dateTimeKeys'][$formatedDatetime] = $currentGroupIndex;
            } elseif (is_object($currentGroupIndex)) {
                $currentGroupIndex = spl_object_hash($currentGroupIndex);
            }

            $groups['keys'][$currentGroupIndex] = $currentGroupKeyValue;
            $groups['values'][$currentGroupIndex][$key] = $value;
        }
        return $groups;
    }
}

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment