我有一个模型,其中一个字段是日期。我想显示该模型中的元素,按年份和月份分组,如下所示:
== 2013 ==
=== April ===
* Element 1
* Element 2
=== March ===
* Element 3
...
== 2012 ==
...
如果实现这一目标的最佳方法是什么?我应该直接在控制器中构建一个嵌套数组吗?或者有没有办法只使用 Fluid 模板来显示年月标题?或者我应该编写一个自定义 ViewHelper 来提取和显示年份和月份标题?
最后,我通过使用自定义 ViewHelper 解决了这个问题,该 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>
首先,您需要在控制器中迭代您的结果集,将其保存到数组中,并确保每一行都提取了日期以分隔索引year
和month
。
在这种情况下,您将能够使用<f:groupedFor ...>
视图助手。
其他选项是将这些字段(year
和month
)添加到您的模型中,并在保存/更新对象时设置适当的值。使用这种方法,您将避免上面提到的控制器迭代的需要,buuuutttt...如果您要使用常见的 TYPO3 后端访问这些记录,您将需要使用一些后处理钩子在数据库操作后设置这些字段。
此处未正确添加的简单解决方案是:
对于年份和月份分组,将字段作为瞬态添加到您的模型中:
...
/**
* @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>