YiiWheels
  • Package
  • Class
  • Tree

Packages

  • None
  • yiiwheels
    • behaviors
    • widgets
    • widgets
      • ace
      • box
      • datepicker
      • daterangepicker
      • datetimepicker
      • detail
      • fileupload
      • fileuploader
      • gallery
      • google
      • grid
        • behaviors
        • operations
      • highcharts
      • maskInput
      • maskmoney
      • modal
      • multiselect
      • rangeslider
      • redactor
      • select2
      • sparklines
      • switch
      • timeago
      • timepicker
      • toggle
      • typeahead

Classes

  • WhGridView
  • WhGroupGridView
  • WhImageColumn
  • WhRelationalColumn
  1 <?php
  2 /**
  3  * WhGroupGridView class
  4  *
  5  * A Grid View that groups rows by any column(s)
  6  *
  7  * @author Antonio Ramirez <amigo.cobos@gmail.com>
  8  * @copyright Copyright &copy; 2amigos.us 2013-
  9  * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
 10  * @package YiiWheels.widgets.grid
 11  * @uses Yiistrap.widgets.TbHtml
 12  * @uses Yiistrap.widgets.TbGridView
 13  *
 14  * @author         Vitaliy Potapov <noginsk@rambler.ru>
 15  * @version        1.1
 16  * @see            http://groupgridview.demopage.ru
 17  */
 18 
 19 Yii::import('bootstrap.widgets.TbGridView');
 20 
 21 class WhGroupGridView extends TbGridView
 22 {
 23 
 24     const MERGE_SIMPLE = 'simple';
 25     const MERGE_NESTED = 'nested';
 26     const MERGE_FIRSTROW = 'firstrow';
 27 
 28     /**
 29      * @var array $mergeColumns the columns to merge on the grid
 30      */
 31     public $mergeColumns = array();
 32 
 33     /**
 34      * @var string $mergeType the merge type. Defaults to MERGE_SIMPLE
 35      */
 36     public $mergeType = self::MERGE_SIMPLE;
 37 
 38     /**
 39      * @var string $mergeCellsCss the styles to apply to merged cells
 40      */
 41     public $mergeCellCss = 'text-align: center; vertical-align: middle';
 42 
 43     /**
 44      * @var array $extraRowColumns the group column names
 45      */
 46     public $extraRowColumns = array();
 47 
 48     /**
 49      * @var string $extraRowExpression
 50      */
 51     public $extraRowExpression;
 52 
 53     /**
 54      * @var array the HTML options for the extrarow cell tag.
 55      */
 56     public $extraRowHtmlOptions = array();
 57 
 58     /**
 59      * @var string $extraRowCssClass the class to be used to be set on the extrarow cell tag.
 60      */
 61     public $extraRowCssClass = 'extrarow';
 62 
 63     /**
 64      * @var array the column data changes
 65      */
 66     private $_changes;
 67 
 68     /**
 69      * Widget initialization
 70      */
 71     public function init()
 72     {
 73         parent::init();
 74 
 75         /**
 76          * check whether we have extraRowColumns set, forbid filters
 77          */
 78         if (!empty($this->extraRowColumns)) {
 79             foreach ($this->columns as $column) {
 80                 if ($column instanceof CDataColumn && in_array($column->name, $this->extraRowColumns)) {
 81                     $column->filterHtmlOptions = array('style' => 'display:none');
 82                     $column->filter = false;
 83                 }
 84             }
 85         }
 86         /**
 87          * setup extra row options
 88          */
 89         if (isset($this->extraRowHtmlOptions['class']) && !empty($this->extraRowCssClass)) {
 90             $this->extraRowHtmlOptions['class'] .= ' ' . $this->extraRowCssClass;
 91         } else {
 92             $this->extraRowHtmlOptions['class'] = $this->extraRowCssClass;
 93         }
 94     }
 95 
 96     /**
 97      * Renders the table body.
 98      */
 99     public function renderTableBody()
100     {
101         if (!empty($this->mergeColumns) || !empty($this->extraRowColumns)) {
102             $this->groupByColumns();
103         }
104 
105         parent::renderTableBody();
106     }
107 
108     /**
109      * Find and store changing of group columns
110      */
111     public function groupByColumns()
112     {
113         $data = $this->dataProvider->getData();
114         if (count($data) == 0) {
115             return;
116         }
117 
118         if (!is_array($this->mergeColumns)) {
119             $this->mergeColumns = array($this->mergeColumns);
120         }
121         if (!is_array($this->extraRowColumns)) {
122             $this->extraRowColumns = array($this->extraRowColumns);
123         }
124 
125         //store columns for group. Set object for existing columns in grid and string for attributes
126         $groupColumns = array_unique(array_merge($this->mergeColumns, $this->extraRowColumns));
127         foreach ($groupColumns as $key => $colName) {
128             foreach ($this->columns as $column) {
129                 if (property_exists($column, 'name') && $column->name == $colName) {
130                     $groupColumns[$key] = $column;
131                     break;
132                 }
133             }
134         }
135 
136 
137         //values for first row
138         $lastStored = $this->getRowValues($groupColumns, $data[0], 0);
139         foreach ($lastStored as $colName => $value) {
140             $lastStored[$colName] = array(
141                 'value' => $value,
142                 'count' => 1,
143                 'index' => 0,
144             );
145         }
146 
147         //iterate data
148         for ($i = 1; $i < count($data); $i++) {
149             //save row values in array
150             $current = $this->getRowValues($groupColumns, $data[$i], $i);
151 
152             //define is change occurred. Need this extra foreach for correctly proceed extraRows
153             $changedColumns = array();
154             foreach ($current as $colName => $curValue) {
155                 if ($curValue != $lastStored[$colName]['value']) {
156                     $changedColumns[] = $colName;
157                 }
158             }
159 
160             /**
161              * if this flag = true -> we will write change (to $this->_changes) for all grouping columns.
162              * It's required when change of any column from extraRowColumns occurs
163              */
164             $saveChangeForAllColumns = (count(array_intersect($changedColumns, $this->extraRowColumns)) > 0);
165 
166             /**
167              * this changeOccurred related to foreach below. It is required only for mergeType == self::MERGE_NESTED,
168              * to write change for all nested columns when change of previous column occurred
169              */
170             $changeOccurred = false;
171             foreach ($current as $colName => $curValue) {
172                 //value changed
173                 $valueChanged = ($curValue != $lastStored[$colName]['value']);
174                 //change already occured in this loop and mergeType set to MERGETYPE_NESTED
175                 $saveChange = $valueChanged || ($changeOccurred && $this->mergeType == self::MERGE_NESTED);
176 
177                 if ($saveChangeForAllColumns || $saveChange) {
178                     $changeOccurred = true;
179 
180                     //store in class var
181                     $prevIndex = $lastStored[$colName]['index'];
182                     $this->_changes[$prevIndex]['columns'][$colName] = $lastStored[$colName];
183                     if (!isset($this->_changes[$prevIndex]['count'])) {
184                         $this->_changes[$prevIndex]['count'] = $lastStored[$colName]['count'];
185                     }
186 
187                     //update lastStored for particular column
188                     $lastStored[$colName] = array(
189                         'value' => $curValue,
190                         'count' => 1,
191                         'index' => $i,
192                     );
193 
194                 } else {
195                     $lastStored[$colName]['count']++;
196                 }
197             }
198         }
199 
200         //storing for last row
201         foreach ($lastStored as $colName => $v) {
202             $prevIndex = $v['index'];
203             $this->_changes[$prevIndex]['columns'][$colName] = $v;
204 
205             if (!isset($this->_changes[$prevIndex]['count'])) {
206                 $this->_changes[$prevIndex]['count'] = $v['count'];
207             }
208         }
209     }
210 
211     /**
212      * Renders a table body row.
213      * @param int $row
214      */
215     public function renderTableRow($row)
216     {
217         $change = false;
218         if ($this->_changes && array_key_exists($row, $this->_changes)) {
219             $change = $this->_changes[$row];
220             //if change in extracolumns --> put extra row
221             $columnsInExtra = array_intersect(array_keys($change['columns']), $this->extraRowColumns);
222             if (count($columnsInExtra) > 0) {
223                 $this->renderExtraRow($row, $change, $columnsInExtra);
224             }
225         }
226 
227         // original CGridView code
228         if ($this->rowCssClassExpression !== null) {
229             $data = $this->dataProvider->data[$row];
230             echo '<tr class="' . $this->evaluateExpression(
231                     $this->rowCssClassExpression,
232                     array('row' => $row, 'data' => $data)
233                 ) . '">';
234         } else if (is_array($this->rowCssClass) && ($n = count($this->rowCssClass)) > 0) {
235             echo '<tr class="' . $this->rowCssClass[$row % $n] . '">';
236         } else {
237             echo '<tr>';
238         }
239 
240 
241         if (!$this->_changes) { //standart CGridview's render
242             foreach ($this->columns as $column) {
243                 $column->renderDataCell($row);
244             }
245         } else { //for grouping
246             foreach ($this->columns as $column) {
247                 $isGroupColumn = property_exists($column, 'name') && in_array($column->name, $this->mergeColumns);
248                 if (!$isGroupColumn) {
249                     $column->renderDataCell($row);
250                     continue;
251                 }
252 
253                 $isChangedColumn = $change && array_key_exists($column->name, $change['columns']);
254 
255                 //for rowspan show only changes (with rowspan)
256                 switch ($this->mergeType) {
257                     case self::MERGE_SIMPLE:
258                     case self::MERGE_NESTED:
259                         if ($isChangedColumn) {
260                             $options = $column->htmlOptions;
261                             $column->htmlOptions['rowspan'] = $change['columns'][$column->name]['count'];
262                             $column->htmlOptions['class'] = 'merge';
263                             $style = isset($column->htmlOptions['style']) ? $column->htmlOptions['style'] : '';
264                             $column->htmlOptions['style'] = $style . ';' . $this->mergeCellCss;
265                             $column->renderDataCell($row);
266                             $column->htmlOptions = $options;
267                         }
268                         break;
269 
270                     case self::MERGE_FIRSTROW:
271                         if ($isChangedColumn) {
272                             $column->renderDataCell($row);
273                         } else {
274                             echo '<td></td>';
275                         }
276                         break;
277                 }
278 
279             }
280         }
281 
282         echo "</tr>\n";
283     }
284 
285     /**
286      * Returns array of rendered column values (TD)
287      * @param string[]|TbDataColumn[] $columns
288      * @param CActiveRecord $data
289      * @param mixed $rowIndex
290      * @throws CException
291      * @return mixed
292      */
293     private function getRowValues($columns, $data, $rowIndex)
294     {
295         foreach ($columns as $column) {
296             if ($column instanceOf TbDataColumn) {
297                 $result[$column->name] = $this->getDataCellContent($column, $data, $rowIndex);
298             } elseif (is_string($column)) {
299                 if (is_array($data) && array_key_exists($column, $data)) {
300                     $result[$column] = $data[$column];
301                 } elseif ($data instanceOf CActiveRecord && $data->hasAttribute($column)) {
302                     $result[$column] = $data->getAttribute($column);
303                 } else {
304                     throw new CException('Column or attribute "' . $column . '" not found!');
305                 }
306             }
307         }
308         return isset($result) ? $result : false;
309     }
310 
311     /**
312      * Renders extra row
313      * @param mixed $beforeRow
314      * @param mixed $change
315      * @param array $columnsInExtra
316      */
317     private function renderExtraRow($beforeRow, $change, $columnsInExtra)
318     {
319         $data = $this->dataProvider->data[$beforeRow];
320         if ($this->extraRowExpression) { //user defined expression, use it!
321             $content = $this->evaluateExpression(
322                 $this->extraRowExpression,
323                 array('data' => $data, 'row' => $beforeRow, 'values' => $change['columns'])
324             );
325         } else { //generate value
326             $values = array();
327             foreach ($columnsInExtra as $c) {
328                 $values[] = $change['columns'][$c]['value'];
329             }
330 
331             $content = '<strong>' . implode(' :: ', $values) . '</strong>';
332         }
333 
334         $colspan = count($this->columns);
335 
336         echo '<tr>';
337         $this->extraRowHtmlOptions['colspan'] = $colspan;
338         echo CHtml::openTag('td', $this->extraRowHtmlOptions);
339         echo $content;
340         echo CHtml::closeTag('td');
341         echo '</tr>';
342     }
343 
344     /**
345      * need to rewrite this function as it is protected in CDataColumn: it is strange as all methods inside are public
346      *
347      * @param TbDataColumn $column
348      * @param mixed $row
349      * @param mixed $data
350      *
351      * @return string
352      */
353     private function getDataCellContent($column, $data, $row)
354     {
355         if ($column->value !== null) {
356             $value = $column->evaluateExpression($column->value, array('data' => $data, 'row' => $row));
357         } else if ($column->name !== null) {
358             $value = CHtml::value($data, $column->name);
359         }
360 
361         return !isset($value)
362             ? $column->grid->nullDisplay
363             : $column->grid->getFormatter()->format(
364                 $value,
365                 $column->type
366             );
367     }
368 }
369 
YiiWheels API documentation generated by ApiGen 2.8.0