Показать список элементов, сгруппированных по годам и месяцам в TYPO3 Fluid
У меня есть модель, где одно поле является датой. Я хочу отобразить элементы из этой модели, сгруппированные по годам и месяцам, вот так:
== 2013 ==
=== April ===
* Element 1
* Element 2
=== March ===
* Element 3
...
== 2012 ==
...
Что если лучший способ добиться этого? Должен ли я создать вложенный массив непосредственно в Controler? Или есть способ отображать заголовки года и месяца только с помощью шаблона Fluid? Или я должен написать собственный ViewHelper для извлечения и отображения заголовков года и месяца?
2 ответа
Наконец, я решил эту проблему с помощью пользовательского ViewHelper, созданного на основе GroupedBy ViewHelper, созданного на основе https://gist.github.com/daKmoR/1287203 и адаптированного для extbase.
Вот полный код для ViewHelper, расположенный в MyExt/Classes/ViewHelpers/GropuedForDateTimeViewHelper.php
<?php
namespace vendor\MyExt\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! *
* */
/**
* 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 \TYPO3\CMS\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
* @throws \TYPO3\CMS\Fluid\Core\ViewHelper\Exception
* @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 \TYPO3\CMS\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
* @throws \TYPO3\CMS\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 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 = \TYPO3\CMS\Extbase\Reflection\ObjectAccess::getPropertyPath($value, $groupBy);
} else {
throw new \TYPO3\CMS\Fluid\Core\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 (is_object($currentGroupIndex)) {
$currentGroupIndex = spl_object_hash($currentGroupIndex);
}
$groups['keys'][$currentGroupIndex] = $currentGroupKeyValue;
$groups['values'][$currentGroupIndex][$key] = $value;
}
return $groups;
}
}
?>
И вот пример использования шаблона:
{namespace m=vendor\MyExt\ViewHelpers}
<f:layout name="Default" />
<f:section name="main">
<m:groupedForDateTime each="{myitems}" as="myitemsyear" groupBy="date" format="%Y" groupKey="year" dateTimeKey="yearKey">
<h2>{year}</h2>
<m:groupedForDateTime each="{myitemsyear}" as="myitemsmonth" groupBy="date" format="%B" groupKey="month" dateTimeKey="monthKey">
<h3>{month}</h3>
<ul>
<f:for each="{myitemsmonth}" as="myitem">
<li>{myitem}</li>
</f:for>
</ul>
</m:groupedForDateTime>
</m:groupedForDateTime>
</f:section>
Вот простое решение, которое не было добавлено здесь должным образом:
Для группировки по годам и месяцам добавьте в модель поля как переходные:
...
/**
* @var int
* @transient
*/
protected $year;
/**
* @var int
* @transient
*/
protected $month;
/**
* @return int
*/
public function getYear()
{
if (!$this->year) {
$this->year = $this->getDateTimeField()->format('Y');
}
return $this->year;
}
/**
* @return int
*/
public function getMonth()
{
if (!this->month) {
$this->month = $this->getDateTimeField('n');
}
return $this->month;
}
Затем в шаблоне:
<f:groupedFor each="{list}" as="groupedByYear" groupBy="year" groupKey="groupYear">
<f:groupedFor each="{groupedByYear}" as="groupedByMonth" groupBy="month" groupKey="groupMonth" />
...
</f:groupedFor>
</f:groupedFor>
Сначала вам нужно перебрать ваш набор результатов в контроллере, сохранить его в массив и убедиться, что в каждой строке извлечена дата для отдельных индексов для year
а также month
,
В таком случае вы сможете использовать <f:groupedFor ...>
посмотреть помощник.
Другой вариант - добавление этих полей (year
а также month
) для вашей модели и установки правильных значений при сохранении / обновлении объекта. Используя этот подход, вы избежите необходимости итерации контроллера, упомянутой выше, buuuutttt... если вы собираетесь обращаться к этим записям с помощью общего бэкэнда TYPO3, вам нужно будет использовать некоторые обработчики постобработки, чтобы установить эти поля после операций с базой данных.