/protected/extensions/bootstrap/widgets/TbGroupGridView.php
PHP | 373 lines | 219 code | 47 blank | 107 comment | 54 complexity | a49d660282a2287ea55ed944f33c76b3 MD5 | raw file
- <?php
- Yii::import('bootstrap.widgets.TbGridView');
- /**
- * A Grid View that groups rows by any column(s)
- *
- * @category User Interface
- * @package widgets
- * @author Vitaliy Potapov <noginsk@rambler.ru>
- * @version 1.1
- * @see http://groupgridview.demopage.ru/
- *
- * @since 24/09/2012 added to yiibooster library
- * @author antonio ramirez <antonio@clevertech.biz>
- * @package yiibooster
- */
- /**
- * @property TbDataColumn[] $columns
- */
- class TbGroupGridView extends TbGridView
- {
- const MERGE_SIMPLE = 'simple';
- const MERGE_NESTED = 'nested';
- const MERGE_FIRSTROW = 'firstrow';
- /**
- * @var array $mergeColumns the columns to merge on the grid
- */
- public $mergeColumns = array();
- /**
- * @var string $mergeType the merge type. Defaults to MERGE_SIMPLE
- */
- public $mergeType = self::MERGE_SIMPLE;
- /**
- * @var string $mergeCellsCss the styles to apply to merged cells
- */
- public $mergeCellCss = 'text-align: center; vertical-align: middle';
- /**
- * @var array $extraRowColumns the group column names
- */
- public $extraRowColumns = array();
- /**
- * @var string $extraRowExpression
- */
- public $extraRowExpression;
- /**
- * @var array the HTML options for the extrarow cell tag.
- */
- public $extraRowHtmlOptions = array();
- /**
- * @var string $extraRowCssClass the class to be used to be set on the extrarow cell tag.
- */
- public $extraRowCssClass = 'extrarow';
- /**
- * @var array the column data changes
- */
- private $_changes;
- /**
- * Widget initialization
- */
- public function init()
- {
- parent::init();
- /**
- * check whether we have extraRowColumns set, forbid filters
- */
- if (!empty($this->extraRowColumns)) {
- foreach ($this->columns as $column) {
- if ($column instanceof CDataColumn && in_array($column->name, $this->extraRowColumns)) {
- $column->filterHtmlOptions = array('style' => 'display:none');
- $column->filter = false;
- }
- }
- }
- /**
- * setup extra row options
- */
- if (isset($this->extraRowHtmlOptions['class']) && !empty($this->extraRowCssClass)) {
- $this->extraRowHtmlOptions['class'] .= ' ' . $this->extraRowCssClass;
- } else {
- $this->extraRowHtmlOptions['class'] = $this->extraRowCssClass;
- }
- }
- /**
- * Renders the table body.
- */
- public function renderTableBody()
- {
- if (!empty($this->mergeColumns) || !empty($this->extraRowColumns)) {
- $this->groupByColumns();
- }
- parent::renderTableBody();
- }
- /**
- * find and store changing of group columns
- */
- public function groupByColumns()
- {
- $data = $this->dataProvider->getData();
- if (count($data) == 0) {
- return;
- }
- if (!is_array($this->mergeColumns)) {
- $this->mergeColumns = array($this->mergeColumns);
- }
- if (!is_array($this->extraRowColumns)) {
- $this->extraRowColumns = array($this->extraRowColumns);
- }
- //store columns for group. Set object for existing columns in grid and string for attributes
- $groupColumns = array_unique(array_merge($this->mergeColumns, $this->extraRowColumns));
- foreach ($groupColumns as $key => $colName) {
- foreach ($this->columns as $column) {
- if (property_exists($column, 'name') && $column->name == $colName) {
- $groupColumns[$key] = $column;
- break;
- }
- }
- }
- //values for first row
- $lastStored = $this->getRowValues($groupColumns, $data[0], 0);
- foreach ($lastStored as $colName => $value) {
- $lastStored[$colName] = array(
- 'value' => $value,
- 'count' => 1,
- 'index' => 0,
- );
- }
- //iterate data
- for ($i = 1; $i < count($data); $i++) {
- //save row values in array
- $current = $this->getRowValues($groupColumns, $data[$i], $i);
- //define is change occured. Need this extra foreach for correctly proceed extraRows
- $changedColumns = array();
- foreach ($current as $colName => $curValue) {
- if ($curValue != $lastStored[$colName]['value']) {
- $changedColumns[] = $colName;
- }
- }
- /**
- * if this flag = true -> we will write change (to $this->_changes) for all grouping columns.
- * It's required when change of any column from extraRowColumns occurs
- */
- $saveChangeForAllColumns = (count(array_intersect($changedColumns, $this->extraRowColumns)) > 0);
- /**
- * this changeOccurred related to foreach below. It is required only for mergeType == self::MERGE_NESTED,
- * to write change for all nested columns when change of previous column occurred
- */
- $changeOccurred = false;
- foreach ($current as $colName => $curValue) {
- //value changed
- $valueChanged = ($curValue != $lastStored[$colName]['value']);
- //change already occured in this loop and mergeType set to MERGETYPE_NESTED
- $saveChange = $valueChanged || ($changeOccurred && $this->mergeType == self::MERGE_NESTED);
- if ($saveChangeForAllColumns || $saveChange) {
- $changeOccurred = true;
- //store in class var
- $prevIndex = $lastStored[$colName]['index'];
- $this->_changes[$prevIndex]['columns'][$colName] = $lastStored[$colName];
- if (!isset($this->_changes[$prevIndex]['count'])) {
- $this->_changes[$prevIndex]['count'] = $lastStored[$colName]['count'];
- }
- //update lastStored for particular column
- $lastStored[$colName] = array(
- 'value' => $curValue,
- 'count' => 1,
- 'index' => $i,
- );
- } else {
- $lastStored[$colName]['count']++;
- }
- }
- }
- //storing for last row
- foreach ($lastStored as $colName => $v) {
- $prevIndex = $v['index'];
- $this->_changes[$prevIndex]['columns'][$colName] = $v;
- if (!isset($this->_changes[$prevIndex]['count'])) {
- $this->_changes[$prevIndex]['count'] = $v['count'];
- }
- }
- }
- /**
- * Renders a table body row.
- *
- * @param int $row
- */
- public function renderTableRow($row)
- {
- $change = false;
- if ($this->_changes && array_key_exists($row, $this->_changes)) {
- $change = $this->_changes[$row];
- //if change in extracolumns --> put extra row
- $columnsInExtra = array_intersect(array_keys($change['columns']), $this->extraRowColumns);
- if (count($columnsInExtra) > 0) {
- $this->renderExtraRow($row, $change, $columnsInExtra);
- }
- }
- // original CGridView code
- if ($this->rowCssClassExpression !== null) {
- $data = $this->dataProvider->data[$row];
- echo '<tr class="' . $this->evaluateExpression(
- $this->rowCssClassExpression,
- array('row' => $row, 'data' => $data)
- ) . '">';
- } else if (is_array($this->rowCssClass) && ($n = count($this->rowCssClass)) > 0) {
- echo '<tr class="' . $this->rowCssClass[$row % $n] . '">';
- } else {
- echo '<tr>';
- }
- if (!$this->_changes) { //standart CGridview's render
- foreach ($this->columns as $column) {
- $column->renderDataCell($row);
- }
- } else { //for grouping
- foreach ($this->columns as $column) {
- $isGroupColumn = property_exists($column, 'name') && in_array($column->name, $this->mergeColumns);
- if (!$isGroupColumn) {
- $column->renderDataCell($row);
- continue;
- }
- $isChangedColumn = $change && array_key_exists($column->name, $change['columns']);
- //for rowspan show only changes (with rowspan)
- switch ($this->mergeType) {
- case self::MERGE_SIMPLE:
- case self::MERGE_NESTED:
- if ($isChangedColumn) {
- $options = $column->htmlOptions;
- $column->htmlOptions['rowspan'] = $change['columns'][$column->name]['count'];
- $column->htmlOptions['class'] = 'merge';
- $style = isset($column->htmlOptions['style']) ? $column->htmlOptions['style'] : '';
- $column->htmlOptions['style'] = $style . ';' . $this->mergeCellCss;
- $column->renderDataCell($row);
- $column->htmlOptions = $options;
- }
- break;
- case self::MERGE_FIRSTROW:
- if ($isChangedColumn) {
- $column->renderDataCell($row);
- } else {
- echo '<td></td>';
- }
- break;
- }
- }
- }
- echo "</tr>\n";
- }
- /**
- * returns array of rendered column values (TD)
- *
- * @param string[]|TbDataColumn[] $columns
- * @param CActiveRecord $data
- * @param mixed $rowIndex
- *
- * @throws CException
- * @return mixed
- */
- private function getRowValues($columns, $data, $rowIndex)
- {
- foreach ($columns as $column) {
- if ($column instanceOf TbDataColumn) {
- $result[$column->name] = $this->getDataCellContent($column, $data, $rowIndex);
- } elseif (is_string($column)) {
- if (is_array($data) && array_key_exists($column, $data)) {
- $result[$column] = $data[$column];
- } elseif ($data instanceOf CActiveRecord && $data->hasAttribute($column)) {
- $result[$column] = $data->getAttribute($column);
- } else {
- throw new CException('Column or attribute "' . $column . '" not found!');
- }
- }
- }
- return isset($result) ? $result : false;
- }
- /**
- * renders extra row
- *
- * @param mixed $beforeRow
- * @param mixed $change
- * @param array $columnsInExtra
- */
- private function renderExtraRow($beforeRow, $change, $columnsInExtra)
- {
- $data = $this->dataProvider->data[$beforeRow];
- if ($this->extraRowExpression) { //user defined expression, use it!
- $content = $this->evaluateExpression(
- $this->extraRowExpression,
- array('data' => $data, 'row' => $beforeRow, 'values' => $change['columns'])
- );
- } else { //generate value
- $values = array();
- foreach ($columnsInExtra as $c) {
- $values[] = $change['columns'][$c]['value'];
- }
- $content = '<strong>' . implode(' :: ', $values) . '</strong>';
- }
- $colspan = count($this->columns);
- echo '<tr>';
- $this->extraRowHtmlOptions['colspan'] = $colspan;
- echo CHtml::openTag('td', $this->extraRowHtmlOptions);
- echo $content;
- echo CHtml::closeTag('td');
- echo '</tr>';
- }
- /**
- * need to rewrite this function as it is protected in CDataColumn: it is strange as all methods inside are public
- *
- * @param TbDataColumn $column
- * @param mixed $row
- * @param mixed $data
- *
- * @return string
- */
- private function getDataCellContent($column, $data, $row)
- {
- if ($column->value !== null) {
- $value = $column->evaluateExpression($column->value, array('data' => $data, 'row' => $row));
- } else if ($column->name !== null) {
- $value = CHtml::value($data, $column->name);
- }
- return !isset($value)
- ? $column->grid->nullDisplay
- : $column->grid->getFormatter()->format(
- $value,
- $column->type
- );
- }
- }