PageRenderTime 26ms CodeModel.GetById 14ms RepoModel.GetById 1ms app.codeStats 0ms

/protected/extensions/booster/widgets/TbGroupGridView.php

https://gitlab.com/Griffolion/Final-Year-Project
PHP | 427 lines | 264 code | 49 blank | 114 comment | 61 complexity | bf98a9d8163383beacbb8f540fdd10ed 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('booster.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. * Registers necessary client scripts.
  87. */
  88. public function registerClientScript()
  89. {
  90. $id=$this->getId();
  91. if($this->ajaxUpdate===false)
  92. $ajaxUpdate=false;
  93. else
  94. $ajaxUpdate=array_unique(preg_split('/\s*,\s*/',$this->ajaxUpdate.','.$id,-1,PREG_SPLIT_NO_EMPTY));
  95. $options=array(
  96. 'ajaxUpdate'=>$ajaxUpdate,
  97. 'ajaxVar'=>$this->ajaxVar,
  98. 'pagerClass'=>$this->pagerCssClass,
  99. 'loadingClass'=>$this->loadingCssClass,
  100. 'filterClass'=>$this->filterCssClass,
  101. 'tableClass'=>$this->itemsCssClass,
  102. 'selectableRows'=>$this->selectableRows,
  103. 'enableHistory'=>$this->enableHistory,
  104. 'updateSelector'=>$this->updateSelector,
  105. 'filterSelector'=>$this->filterSelector
  106. );
  107. if($this->ajaxUrl!==null)
  108. $options['url']=CHtml::normalizeUrl($this->ajaxUrl);
  109. if($this->ajaxType!==null)
  110. $options['ajaxType']=strtoupper($this->ajaxType);
  111. if($this->enablePagination)
  112. $options['pageVar']=$this->dataProvider->getPagination()->pageVar;
  113. foreach(array('beforeAjaxUpdate', 'afterAjaxUpdate', 'ajaxUpdateError', 'selectionChanged') as $event)
  114. {
  115. if($this->$event!==null)
  116. {
  117. if($this->$event instanceof CJavaScriptExpression)
  118. $options[$event]=$this->$event;
  119. else
  120. $options[$event]=new CJavaScriptExpression($this->$event);
  121. }
  122. }
  123. $options=CJavaScript::encode($options);
  124. $cs=Yii::app()->getClientScript();
  125. $cs->registerCoreScript('jquery');
  126. $cs->registerCoreScript('bbq');
  127. if($this->enableHistory)
  128. $cs->registerCoreScript('history');
  129. // $cs->registerScriptFile($this->baseScriptUrl.'/jquery.yiigridview.js',CClientScript::POS_END);
  130. $cs->registerPackage('group-grid-view');
  131. $cs->registerScript(__CLASS__.'#'.$id,"jQuery('#$id').yiiGroupGridView($options);");
  132. }
  133. /**
  134. * Renders the table body.
  135. */
  136. public function renderTableBody()
  137. {
  138. if (!empty($this->mergeColumns) || !empty($this->extraRowColumns)) {
  139. $this->groupByColumns();
  140. }
  141. parent::renderTableBody();
  142. }
  143. /**
  144. * find and store changing of group columns
  145. */
  146. public function groupByColumns()
  147. {
  148. $data = $this->dataProvider->getData();
  149. if (count($data) == 0) {
  150. return;
  151. }
  152. if (!is_array($this->mergeColumns)) {
  153. $this->mergeColumns = array($this->mergeColumns);
  154. }
  155. if (!is_array($this->extraRowColumns)) {
  156. $this->extraRowColumns = array($this->extraRowColumns);
  157. }
  158. //store columns for group. Set object for existing columns in grid and string for attributes
  159. $groupColumns = array_unique(array_merge($this->mergeColumns, $this->extraRowColumns));
  160. foreach ($groupColumns as $key => $colName) {
  161. foreach ($this->columns as $column) {
  162. if (property_exists($column, 'name') && $column->name == $colName) {
  163. $groupColumns[$key] = $column;
  164. break;
  165. }
  166. }
  167. }
  168. //values for first row
  169. $lastStored = $this->getRowValues($groupColumns, $data[0], 0);
  170. foreach ($lastStored as $colName => $value) {
  171. $lastStored[$colName] = array(
  172. 'value' => $value,
  173. 'count' => 1,
  174. 'index' => 0,
  175. );
  176. }
  177. //iterate data
  178. $rowcount = count($data);
  179. for ($i = 1; $i < $rowcount; $i++) {
  180. //save row values in array
  181. $current = $this->getRowValues($groupColumns, $data[$i], $i);
  182. //define is change occured. Need this extra foreach for correctly proceed extraRows
  183. $changedColumns = array();
  184. foreach ($current as $colName => $curValue) {
  185. if ($curValue != $lastStored[$colName]['value']) {
  186. $changedColumns[] = $colName;
  187. }
  188. }
  189. /**
  190. * if this flag = true -> we will write change (to $this->_changes) for all grouping columns.
  191. * It's required when change of any column from extraRowColumns occurs
  192. */
  193. $saveChangeForAllColumns = (count(array_intersect($changedColumns, $this->extraRowColumns)) > 0);
  194. /**
  195. * this changeOccurred related to foreach below. It is required only for mergeType == self::MERGE_NESTED,
  196. * to write change for all nested columns when change of previous column occurred
  197. */
  198. $changeOccurred = false;
  199. foreach ($current as $colName => $curValue) {
  200. //value changed
  201. $valueChanged = ($curValue != $lastStored[$colName]['value']);
  202. //change already occured in this loop and mergeType set to MERGETYPE_NESTED
  203. $saveChange = $valueChanged || ($changeOccurred && $this->mergeType == self::MERGE_NESTED);
  204. if ($saveChangeForAllColumns || $saveChange) {
  205. $changeOccurred = true;
  206. //store in class var
  207. $prevIndex = $lastStored[$colName]['index'];
  208. $this->_changes[$prevIndex]['columns'][$colName] = $lastStored[$colName];
  209. if (!isset($this->_changes[$prevIndex]['count'])) {
  210. $this->_changes[$prevIndex]['count'] = $lastStored[$colName]['count'];
  211. }
  212. //update lastStored for particular column
  213. $lastStored[$colName] = array(
  214. 'value' => $curValue,
  215. 'count' => 1,
  216. 'index' => $i,
  217. );
  218. } else {
  219. $lastStored[$colName]['count']++;
  220. }
  221. }
  222. }
  223. //storing for last row
  224. foreach ($lastStored as $colName => $v) {
  225. $prevIndex = $v['index'];
  226. $this->_changes[$prevIndex]['columns'][$colName] = $v;
  227. if (!isset($this->_changes[$prevIndex]['count'])) {
  228. $this->_changes[$prevIndex]['count'] = $v['count'];
  229. }
  230. }
  231. }
  232. /**
  233. * Renders a table body row.
  234. *
  235. * @param int $row
  236. */
  237. public function renderTableRow($row)
  238. {
  239. $change = false;
  240. if ($this->_changes && array_key_exists($row, $this->_changes)) {
  241. $change = $this->_changes[$row];
  242. //if change in extracolumns --> put extra row
  243. $columnsInExtra = array_intersect(array_keys($change['columns']), $this->extraRowColumns);
  244. if (count($columnsInExtra) > 0) {
  245. $this->renderExtraRow($row, $change, $columnsInExtra);
  246. }
  247. }
  248. // original CGridView code
  249. if ($this->rowCssClassExpression !== null) {
  250. $data = $this->dataProvider->data[$row];
  251. echo '<tr class="' . $this->evaluateExpression(
  252. $this->rowCssClassExpression,
  253. array('row' => $row, 'data' => $data)
  254. ) . '">';
  255. } else if (is_array($this->rowCssClass) && ($n = count($this->rowCssClass)) > 0) {
  256. echo '<tr class="' . $this->rowCssClass[$row % $n] . '">';
  257. } else {
  258. echo '<tr>';
  259. }
  260. if (!$this->_changes) { //standart CGridview's render
  261. foreach ($this->columns as $column) {
  262. $column->renderDataCell($row);
  263. }
  264. } else { //for grouping
  265. foreach ($this->columns as $column) {
  266. $isGroupColumn = property_exists($column, 'name') && in_array($column->name, $this->mergeColumns);
  267. if (!$isGroupColumn) {
  268. $column->renderDataCell($row);
  269. continue;
  270. }
  271. $isChangedColumn = $change && array_key_exists($column->name, $change['columns']);
  272. //for rowspan show only changes (with rowspan)
  273. switch ($this->mergeType) {
  274. case self::MERGE_SIMPLE:
  275. case self::MERGE_NESTED:
  276. if ($isChangedColumn) {
  277. $options = $column->htmlOptions;
  278. $column->htmlOptions['rowspan'] = $change['columns'][$column->name]['count'];
  279. $column->htmlOptions['class'] = 'merge';
  280. $style = isset($column->htmlOptions['style']) ? $column->htmlOptions['style'] : '';
  281. $column->htmlOptions['style'] = $style . ';' . $this->mergeCellCss;
  282. $column->renderDataCell($row);
  283. $column->htmlOptions = $options;
  284. }
  285. break;
  286. case self::MERGE_FIRSTROW:
  287. if ($isChangedColumn) {
  288. $column->renderDataCell($row);
  289. } else {
  290. echo '<td></td>';
  291. }
  292. break;
  293. }
  294. }
  295. }
  296. echo "</tr>\n";
  297. }
  298. /**
  299. * returns array of rendered column values (TD)
  300. *
  301. * @param string[]|TbDataColumn[] $columns
  302. * @param CActiveRecord $data
  303. * @param mixed $rowIndex
  304. *
  305. * @throws CException
  306. * @return mixed
  307. */
  308. private function getRowValues($columns, $data, $rowIndex)
  309. {
  310. foreach ($columns as $column) {
  311. if ($column instanceOf TbDataColumn) {
  312. $result[$column->name] = $this->getDataCellContent($column, $data, $rowIndex);
  313. } elseif (is_string($column)) {
  314. if (is_array($data) && array_key_exists($column, $data)) {
  315. $result[$column] = $data[$column];
  316. } elseif ($data instanceOf CActiveRecord && $data->hasAttribute($column)) {
  317. $result[$column] = $data->getAttribute($column);
  318. } else {
  319. throw new CException('Column or attribute "' . $column . '" not found!');
  320. }
  321. }
  322. }
  323. return isset($result) ? $result : false;
  324. }
  325. /**
  326. * renders extra row
  327. *
  328. * @param mixed $beforeRow
  329. * @param mixed $change
  330. * @param array $columnsInExtra
  331. */
  332. private function renderExtraRow($beforeRow, $change, $columnsInExtra)
  333. {
  334. $data = $this->dataProvider->data[$beforeRow];
  335. if ($this->extraRowExpression) { //user defined expression, use it!
  336. $content = $this->evaluateExpression(
  337. $this->extraRowExpression,
  338. array('data' => $data, 'row' => $beforeRow, 'values' => $change['columns'])
  339. );
  340. } else { //generate value
  341. $values = array();
  342. foreach ($columnsInExtra as $c) {
  343. $values[] = $change['columns'][$c]['value'];
  344. }
  345. $content = '<strong>' . implode(' :: ', $values) . '</strong>';
  346. }
  347. $colspan = count($this->columns);
  348. echo '<tr class="extrarow">';
  349. $this->extraRowHtmlOptions['colspan'] = $colspan;
  350. echo CHtml::openTag('td', $this->extraRowHtmlOptions);
  351. echo $content;
  352. echo CHtml::closeTag('td');
  353. echo '</tr>';
  354. }
  355. /**
  356. * need to rewrite this function as it is protected in CDataColumn: it is strange as all methods inside are public
  357. *
  358. * @param TbDataColumn $column
  359. * @param mixed $row
  360. * @param mixed $data
  361. *
  362. * @return string
  363. */
  364. private function getDataCellContent($column, $data, $row)
  365. {
  366. if ($column->value !== null) {
  367. $value = $column->evaluateExpression($column->value, array('data' => $data, 'row' => $row));
  368. } else if ($column->name !== null) {
  369. $value = CHtml::value($data, $column->name);
  370. }
  371. return !isset($value)
  372. ? $column->grid->nullDisplay
  373. : $column->grid->getFormatter()->format(
  374. $value,
  375. $column->type
  376. );
  377. }
  378. }