PageRenderTime 24ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/app/protected/extensions/yiibooster/widgets/TbGroupGridView.php

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