1 <?php
2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 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 30
31 public $mergeColumns = array();
32
33 34 35
36 public $mergeType = self::MERGE_SIMPLE;
37
38 39 40
41 public $mergeCellCss = 'text-align: center; vertical-align: middle';
42
43 44 45
46 public $extraRowColumns = array();
47
48 49 50
51 public ;
52
53 54 55
56 public = array();
57
58 59 60
61 public = 'extrarow';
62
63 64 65
66 private $_changes;
67
68 69 70
71 public function init()
72 {
73 parent::init();
74
75 76 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 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 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 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
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
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
148 for ($i = 1; $i < count($data); $i++) {
149
150 $current = $this->getRowValues($groupColumns, $data[$i], $i);
151
152
153 $changedColumns = array();
154 foreach ($current as $colName => $curValue) {
155 if ($curValue != $lastStored[$colName]['value']) {
156 $changedColumns[] = $colName;
157 }
158 }
159
160 161 162 163
164 $saveChangeForAllColumns = (count(array_intersect($changedColumns, $this->extraRowColumns)) > 0);
165
166 167 168 169
170 $changeOccurred = false;
171 foreach ($current as $colName => $curValue) {
172
173 $valueChanged = ($curValue != $lastStored[$colName]['value']);
174
175 $saveChange = $valueChanged || ($changeOccurred && $this->mergeType == self::MERGE_NESTED);
176
177 if ($saveChangeForAllColumns || $saveChange) {
178 $changeOccurred = true;
179
180
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
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
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 213 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
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
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) {
242 foreach ($this->columns as $column) {
243 $column->renderDataCell($row);
244 }
245 } else {
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
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 287 288 289 290 291 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 313 314 315 316
317 private function ($beforeRow, $change, $columnsInExtra)
318 {
319 $data = $this->dataProvider->data[$beforeRow];
320 if ($this->extraRowExpression) {
321 $content = $this->evaluateExpression(
322 $this->extraRowExpression,
323 array('data' => $data, 'row' => $beforeRow, 'values' => $change['columns'])
324 );
325 } else {
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 346 347 348 349 350 351 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