PageRenderTime 60ms CodeModel.GetById 31ms RepoModel.GetById 0ms app.codeStats 0ms

/protected/extensions/bootstrap/widgets/TbGroupGridView.php

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