CGridView.php 24 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670
  1. <?php
  2. /**
  3. * CGridView class file.
  4. *
  5. * @author Qiang Xue <qiang.xue@gmail.com>
  6. * @link http://www.yiiframework.com/
  7. * @copyright 2008-2013 Yii Software LLC
  8. * @license http://www.yiiframework.com/license/
  9. */
  10. Yii::import('zii.widgets.CBaseListView');
  11. Yii::import('zii.widgets.grid.CDataColumn');
  12. Yii::import('zii.widgets.grid.CLinkColumn');
  13. Yii::import('zii.widgets.grid.CButtonColumn');
  14. Yii::import('zii.widgets.grid.CCheckBoxColumn');
  15. /**
  16. * CGridView displays a list of data items in terms of a table.
  17. *
  18. * Each row of the table represents the data of a single data item, and a column usually represents
  19. * an attribute of the item (some columns may correspond to complex expression of attributes or static text).
  20. *
  21. * CGridView supports both sorting and pagination of the data items. The sorting
  22. * and pagination can be done in AJAX mode or normal page request. A benefit of using CGridView is that
  23. * when the user browser disables JavaScript, the sorting and pagination automatically degenerate
  24. * to normal page requests and are still functioning as expected.
  25. *
  26. * CGridView should be used together with a {@link IDataProvider data provider}, preferably a
  27. * {@link CActiveDataProvider}.
  28. *
  29. * The minimal code needed to use CGridView is as follows:
  30. *
  31. * <pre>
  32. * $dataProvider=new CActiveDataProvider('Post');
  33. *
  34. * $this->widget('zii.widgets.grid.CGridView', array(
  35. * 'dataProvider'=>$dataProvider,
  36. * ));
  37. * </pre>
  38. *
  39. * The above code first creates a data provider for the <code>Post</code> ActiveRecord class.
  40. * It then uses CGridView to display every attribute in every <code>Post</code> instance.
  41. * The displayed table is equiped with sorting and pagination functionality.
  42. *
  43. * In order to selectively display attributes with different formats, we may configure the
  44. * {@link CGridView::columns} property. For example, we may specify only the <code>title</code>
  45. * and <code>create_time</code> attributes to be displayed, and the <code>create_time</code>
  46. * should be properly formatted to show as a time. We may also display the attributes of the related
  47. * objects using the dot-syntax as shown below:
  48. *
  49. * <pre>
  50. * $this->widget('zii.widgets.grid.CGridView', array(
  51. * 'dataProvider'=>$dataProvider,
  52. * 'columns'=>array(
  53. * 'title', // display the 'title' attribute
  54. * 'category.name', // display the 'name' attribute of the 'category' relation
  55. * 'content:html', // display the 'content' attribute as purified HTML
  56. * array( // display 'create_time' using an expression
  57. * 'name'=>'create_time',
  58. * 'value'=>'date("M j, Y", $data->create_time)',
  59. * ),
  60. * array( // display 'author.username' using an expression
  61. * 'name'=>'authorName',
  62. * 'value'=>'$data->author->username',
  63. * ),
  64. * array( // display a column with "view", "update" and "delete" buttons
  65. * 'class'=>'CButtonColumn',
  66. * ),
  67. * ),
  68. * ));
  69. * </pre>
  70. *
  71. * Please refer to {@link columns} for more details about how to configure this property.
  72. *
  73. * @property boolean $hasFooter Whether the table should render a footer.
  74. * This is true if any of the {@link columns} has a true {@link CGridColumn::hasFooter} value.
  75. * @property CFormatter $formatter The formatter instance. Defaults to the 'format' application component.
  76. *
  77. * @author Qiang Xue <qiang.xue@gmail.com>
  78. * @package zii.widgets.grid
  79. * @since 1.1
  80. */
  81. class CGridView extends CBaseListView
  82. {
  83. const FILTER_POS_HEADER='header';
  84. const FILTER_POS_FOOTER='footer';
  85. const FILTER_POS_BODY='body';
  86. private $_formatter;
  87. /**
  88. * @var array grid column configuration. Each array element represents the configuration
  89. * for one particular grid column which can be either a string or an array.
  90. *
  91. * When a column is specified as a string, it should be in the format of "name:type:header",
  92. * where "type" and "header" are optional. A {@link CDataColumn} instance will be created in this case,
  93. * whose {@link CDataColumn::name}, {@link CDataColumn::type} and {@link CDataColumn::header}
  94. * properties will be initialized accordingly.
  95. *
  96. * When a column is specified as an array, it will be used to create a grid column instance, where
  97. * the 'class' element specifies the column class name (defaults to {@link CDataColumn} if absent).
  98. * Currently, these official column classes are provided: {@link CDataColumn},
  99. * {@link CLinkColumn}, {@link CButtonColumn} and {@link CCheckBoxColumn}.
  100. */
  101. public $columns=array();
  102. /**
  103. * @var array the CSS class names for the table body rows. If multiple CSS class names are given,
  104. * they will be assigned to the rows sequentially and repeatedly. This property is ignored
  105. * if {@link rowCssClassExpression} is set. Defaults to <code>array('odd', 'even')</code>.
  106. * @see rowCssClassExpression
  107. */
  108. public $rowCssClass=array('odd','even');
  109. /**
  110. * @var string a PHP expression that is evaluated for every table body row and whose result
  111. * is used as the CSS class name for the row. In this expression, you can use the following variables:
  112. * <ul>
  113. * <li><code>$row</code> the row number (zero-based)</li>
  114. * <li><code>$data</code> the data model for the row</li>
  115. * <li><code>$this</code> the grid view object</li>
  116. * </ul>
  117. * The PHP expression will be evaluated using {@link evaluateExpression}.
  118. *
  119. * A PHP expression can be any PHP code that has a value. To learn more about what an expression is,
  120. * please refer to the {@link http://www.php.net/manual/en/language.expressions.php php manual}.
  121. * @see rowCssClass
  122. * @deprecated in 1.1.13 in favor of {@link rowHtmlOptionsExpression}
  123. */
  124. public $rowCssClassExpression;
  125. /**
  126. * @var string a PHP expression that is evaluated for every table body row and whose result
  127. * is used as additional HTML attributes for the row. The expression should return an
  128. * array whose key value pairs correspond to html attribute and value.
  129. * In this expression, you can use the following variables:
  130. * <ul>
  131. * <li><code>$row</code> the row number (zero-based)</li>
  132. * <li><code>$data</code> the data model for the row</li>
  133. * <li><code>$this</code> the grid view object</li>
  134. * </ul>
  135. * The PHP expression will be evaluated using {@link evaluateExpression}.
  136. *
  137. * A PHP expression can be any PHP code that has a value. To learn more about what an expression is,
  138. * please refer to the {@link http://www.php.net/manual/en/language.expressions.php php manual}.
  139. * @since 1.1.13
  140. */
  141. public $rowHtmlOptionsExpression;
  142. /**
  143. * @var boolean whether to display the table even when there is no data. Defaults to true.
  144. * The {@link emptyText} will be displayed to indicate there is no data.
  145. */
  146. public $showTableOnEmpty=true;
  147. /**
  148. * @var mixed the ID of the container whose content may be updated with an AJAX response.
  149. * Defaults to null, meaning the container for this grid view instance.
  150. * If it is set false, it means sorting and pagination will be performed in normal page requests
  151. * instead of AJAX requests. If the sorting and pagination should trigger the update of multiple
  152. * containers' content in AJAX fashion, these container IDs may be listed here (separated with comma).
  153. */
  154. public $ajaxUpdate;
  155. /**
  156. * @var string the jQuery selector of the HTML elements that may trigger AJAX updates when they are clicked.
  157. * These tokens are recognized: {page} and {sort}. They will be replaced with the pagination and sorting links selectors.
  158. * Defaults to '{page}, {sort}', that means that the pagination links and the sorting links will trigger AJAX updates.
  159. * Tokens are available from 1.1.11
  160. *
  161. * Note: if this value is empty an exception will be thrown.
  162. *
  163. * Example (adding a custom selector to the default ones):
  164. * <pre>
  165. * ...
  166. * 'updateSelector'=>'{page}, {sort}, #mybutton',
  167. * ...
  168. * </pre>
  169. * @since 1.1.7
  170. */
  171. public $updateSelector='{page}, {sort}';
  172. /**
  173. * @var string a javascript function that will be invoked if an AJAX update error occurs.
  174. *
  175. * The function signature is <code>function(xhr, textStatus, errorThrown, errorMessage)</code>
  176. * <ul>
  177. * <li><code>xhr</code> is the XMLHttpRequest object.</li>
  178. * <li><code>textStatus</code> is a string describing the type of error that occurred.
  179. * Possible values (besides null) are "timeout", "error", "notmodified" and "parsererror"</li>
  180. * <li><code>errorThrown</code> is an optional exception object, if one occurred.</li>
  181. * <li><code>errorMessage</code> is the CGridView default error message derived from xhr and errorThrown.
  182. * Useful if you just want to display this error differently. CGridView by default displays this error with an javascript.alert()</li>
  183. * </ul>
  184. * Note: This handler is not called for JSONP requests, because they do not use an XMLHttpRequest.
  185. *
  186. * Example (add in a call to CGridView):
  187. * <pre>
  188. * ...
  189. * 'ajaxUpdateError'=>'function(xhr,ts,et,err,id){ $("#"+id).text(err); }',
  190. * ...
  191. * </pre>
  192. */
  193. public $ajaxUpdateError;
  194. /**
  195. * @var string the name of the GET variable that indicates the request is an AJAX request triggered
  196. * by this widget. Defaults to 'ajax'. This is effective only when {@link ajaxUpdate} is not false.
  197. */
  198. public $ajaxVar='ajax';
  199. /**
  200. * @var mixed the URL for the AJAX requests should be sent to. {@link CHtml::normalizeUrl()} will be
  201. * called on this property. If not set, the current page URL will be used for AJAX requests.
  202. * @since 1.1.8
  203. */
  204. public $ajaxUrl;
  205. /**
  206. * @var string the type ('GET' or 'POST') of the AJAX requests. If not set, 'GET' will be used.
  207. * You can set this to 'POST' if you are filtering by many fields at once and have a problem with GET query string length.
  208. * Note that in POST mode direct links and {@link enableHistory} feature may not work correctly!
  209. * @since 1.1.14
  210. */
  211. public $ajaxType;
  212. /**
  213. * @var string a javascript function that will be invoked before an AJAX update occurs.
  214. * The function signature is <code>function(id,options)</code> where 'id' refers to the ID of the grid view,
  215. * 'options' the AJAX request options (see jQuery.ajax api manual).
  216. */
  217. public $beforeAjaxUpdate;
  218. /**
  219. * @var string a javascript function that will be invoked after a successful AJAX response is received.
  220. * The function signature is <code>function(id, data)</code> where 'id' refers to the ID of the grid view,
  221. * 'data' the received ajax response data.
  222. */
  223. public $afterAjaxUpdate;
  224. /**
  225. * @var string a javascript function that will be invoked after the row selection is changed.
  226. * The function signature is <code>function(id)</code> where 'id' refers to the ID of the grid view.
  227. * In this function, you may use <code>$(gridID).yiiGridView('getSelection')</code> to get the key values
  228. * of the currently selected rows (gridID is the DOM selector of the grid).
  229. * @see selectableRows
  230. */
  231. public $selectionChanged;
  232. /**
  233. * @var integer the number of table body rows that can be selected. If 0, it means rows cannot be selected.
  234. * If 1, only one row can be selected. If 2 or any other number, it means multiple rows can be selected.
  235. * A selected row will have a CSS class named 'selected'. You may also call the JavaScript function
  236. * <code>$(gridID).yiiGridView('getSelection')</code> to retrieve the key values of the currently selected
  237. * rows (gridID is the DOM selector of the grid).
  238. */
  239. public $selectableRows=1;
  240. /**
  241. * @var string the base script URL for all grid view resources (eg javascript, CSS file, images).
  242. * Defaults to null, meaning using the integrated grid view resources (which are published as assets).
  243. */
  244. public $baseScriptUrl;
  245. /**
  246. * @var string the URL of the CSS file used by this grid view. Defaults to null, meaning using the integrated
  247. * CSS file. If this is set false, you are responsible to explicitly include the necessary CSS file in your page.
  248. */
  249. public $cssFile;
  250. /**
  251. * @var string the text to be displayed in a data cell when a data value is null. This property will NOT be HTML-encoded
  252. * when rendering. Defaults to an HTML blank.
  253. */
  254. public $nullDisplay='&nbsp;';
  255. /**
  256. * @var string the text to be displayed in an empty grid cell. This property will NOT be HTML-encoded when rendering. Defaults to an HTML blank.
  257. * This differs from {@link nullDisplay} in that {@link nullDisplay} is only used by {@link CDataColumn} to render
  258. * null data values.
  259. * @since 1.1.7
  260. */
  261. public $blankDisplay='&nbsp;';
  262. /**
  263. * @var string the CSS class name that will be assigned to the widget container element
  264. * when the widget is updating its content via AJAX. Defaults to 'grid-view-loading'.
  265. * @since 1.1.1
  266. */
  267. public $loadingCssClass='grid-view-loading';
  268. /**
  269. * @var string the jQuery selector of filter input fields.
  270. * The token '{filter}' is recognized and it will be replaced with the grid filters selector.
  271. * Defaults to '{filter}'.
  272. *
  273. * Note: if this value is empty an exception will be thrown.
  274. *
  275. * Example (adding a custom selector to the default one):
  276. * <pre>
  277. * ...
  278. * 'filterSelector'=>'{filter}, #myfilter',
  279. * ...
  280. * </pre>
  281. * @since 1.1.13
  282. */
  283. public $filterSelector='{filter}';
  284. /**
  285. * @var string the CSS class name for the table row element containing all filter input fields. Defaults to 'filters'.
  286. * @see filter
  287. * @since 1.1.1
  288. */
  289. public $filterCssClass='filters';
  290. /**
  291. * @var string whether the filters should be displayed in the grid view. Valid values include:
  292. * <ul>
  293. * <li>header: the filters will be displayed on top of each column's header cell.</li>
  294. * <li>body: the filters will be displayed right below each column's header cell.</li>
  295. * <li>footer: the filters will be displayed below each column's footer cell.</li>
  296. * </ul>
  297. * @see filter
  298. * @since 1.1.1
  299. */
  300. public $filterPosition='body';
  301. /**
  302. * @var CModel the model instance that keeps the user-entered filter data. When this property is set,
  303. * the grid view will enable column-based filtering. Each data column by default will display a text field
  304. * at the top that users can fill in to filter the data.
  305. * Note that in order to show an input field for filtering, a column must have its {@link CDataColumn::name}
  306. * property set or have {@link CDataColumn::filter} as the HTML code for the input field.
  307. * When this property is not set (null) the filtering is disabled.
  308. * @since 1.1.1
  309. */
  310. public $filter;
  311. /**
  312. * @var boolean whether to hide the header cells of the grid. When this is true, header cells
  313. * will not be rendered, which means the grid cannot be sorted anymore since the sort links are located
  314. * in the header. Defaults to false.
  315. * @since 1.1.1
  316. */
  317. public $hideHeader=false;
  318. /**
  319. * @var boolean whether to leverage the {@link https://developer.mozilla.org/en/DOM/window.history DOM history object}. Set this property to true
  320. * to persist state of grid across page revisits. Note, there are two limitations for this feature:
  321. * <ul>
  322. * <li>this feature is only compatible with browsers that support HTML5.</li>
  323. * <li>expect unexpected functionality (e.g. multiple ajax calls) if there is more than one grid/list on a single page with enableHistory turned on.</li>
  324. * </ul>
  325. * @since 1.1.11
  326. */
  327. public $enableHistory=false;
  328. /**
  329. * Initializes the grid view.
  330. * This method will initialize required property values and instantiate {@link columns} objects.
  331. */
  332. public function init()
  333. {
  334. parent::init();
  335. if(empty($this->updateSelector))
  336. throw new CException(Yii::t('zii','The property updateSelector should be defined.'));
  337. if(empty($this->filterSelector))
  338. throw new CException(Yii::t('zii','The property filterSelector should be defined.'));
  339. if(!isset($this->htmlOptions['class']))
  340. $this->htmlOptions['class']='grid-view';
  341. if($this->baseScriptUrl===null)
  342. $this->baseScriptUrl=Yii::app()->getAssetManager()->publish(Yii::getPathOfAlias('zii.widgets.assets')).'/gridview';
  343. if($this->cssFile!==false)
  344. {
  345. if($this->cssFile===null)
  346. $this->cssFile=$this->baseScriptUrl.'/styles.css';
  347. Yii::app()->getClientScript()->registerCssFile($this->cssFile);
  348. }
  349. $this->initColumns();
  350. }
  351. /**
  352. * Creates column objects and initializes them.
  353. */
  354. protected function initColumns()
  355. {
  356. if($this->columns===array())
  357. {
  358. if($this->dataProvider instanceof CActiveDataProvider)
  359. $this->columns=$this->dataProvider->model->attributeNames();
  360. elseif($this->dataProvider instanceof IDataProvider)
  361. {
  362. // use the keys of the first row of data as the default columns
  363. $data=$this->dataProvider->getData();
  364. if(isset($data[0]) && is_array($data[0]))
  365. $this->columns=array_keys($data[0]);
  366. }
  367. }
  368. $id=$this->getId();
  369. foreach($this->columns as $i=>$column)
  370. {
  371. if(is_string($column))
  372. $column=$this->createDataColumn($column);
  373. else
  374. {
  375. if(!isset($column['class']))
  376. $column['class']='CDataColumn';
  377. $column=Yii::createComponent($column, $this);
  378. }
  379. if(!$column->visible)
  380. {
  381. unset($this->columns[$i]);
  382. continue;
  383. }
  384. if($column->id===null)
  385. $column->id=$id.'_c'.$i;
  386. $this->columns[$i]=$column;
  387. }
  388. foreach($this->columns as $column)
  389. $column->init();
  390. }
  391. /**
  392. * Creates a {@link CDataColumn} based on a shortcut column specification string.
  393. * @param string $text the column specification string
  394. * @return CDataColumn the column instance
  395. */
  396. protected function createDataColumn($text)
  397. {
  398. if(!preg_match('/^([\w\.]+)(:(\w*))?(:(.*))?$/',$text,$matches))
  399. throw new CException(Yii::t('zii','The column must be specified in the format of "Name:Type:Label", where "Type" and "Label" are optional.'));
  400. $column=new CDataColumn($this);
  401. $column->name=$matches[1];
  402. if(isset($matches[3]) && $matches[3]!=='')
  403. $column->type=$matches[3];
  404. if(isset($matches[5]))
  405. $column->header=$matches[5];
  406. return $column;
  407. }
  408. /**
  409. * Registers necessary client scripts.
  410. */
  411. public function registerClientScript()
  412. {
  413. $id=$this->getId();
  414. if($this->ajaxUpdate===false)
  415. $ajaxUpdate=false;
  416. else
  417. $ajaxUpdate=array_unique(preg_split('/\s*,\s*/',$this->ajaxUpdate.','.$id,-1,PREG_SPLIT_NO_EMPTY));
  418. $options=array(
  419. 'ajaxUpdate'=>$ajaxUpdate,
  420. 'ajaxVar'=>$this->ajaxVar,
  421. 'pagerClass'=>$this->pagerCssClass,
  422. 'loadingClass'=>$this->loadingCssClass,
  423. 'filterClass'=>$this->filterCssClass,
  424. 'tableClass'=>$this->itemsCssClass,
  425. 'selectableRows'=>$this->selectableRows,
  426. 'enableHistory'=>$this->enableHistory,
  427. 'updateSelector'=>$this->updateSelector,
  428. 'filterSelector'=>$this->filterSelector
  429. );
  430. if($this->ajaxUrl!==null)
  431. $options['url']=CHtml::normalizeUrl($this->ajaxUrl);
  432. if($this->ajaxType!==null) {
  433. $options['ajaxType']=strtoupper($this->ajaxType);
  434. $request=Yii::app()->getRequest();
  435. if ($options['ajaxType']=='POST' && $request->enableCsrfValidation) {
  436. $options['csrfTokenName']=$request->csrfTokenName;
  437. $options['csrfToken']=$request->getCsrfToken();
  438. }
  439. }
  440. if($this->enablePagination)
  441. $options['pageVar']=$this->dataProvider->getPagination()->pageVar;
  442. foreach(array('beforeAjaxUpdate', 'afterAjaxUpdate', 'ajaxUpdateError', 'selectionChanged') as $event)
  443. {
  444. if($this->$event!==null)
  445. {
  446. if($this->$event instanceof CJavaScriptExpression)
  447. $options[$event]=$this->$event;
  448. else
  449. $options[$event]=new CJavaScriptExpression($this->$event);
  450. }
  451. }
  452. $options=CJavaScript::encode($options);
  453. $cs=Yii::app()->getClientScript();
  454. $cs->registerCoreScript('jquery');
  455. $cs->registerCoreScript('bbq');
  456. if($this->enableHistory)
  457. $cs->registerCoreScript('history');
  458. $cs->registerScriptFile($this->baseScriptUrl.'/jquery.yiigridview.js',CClientScript::POS_END);
  459. $cs->registerScript(__CLASS__.'#'.$id,"jQuery('#$id').yiiGridView($options);");
  460. }
  461. /**
  462. * Renders the data items for the grid view.
  463. */
  464. public function renderItems()
  465. {
  466. if($this->dataProvider->getItemCount()>0 || $this->showTableOnEmpty)
  467. {
  468. echo "<table class=\"{$this->itemsCssClass}\">\n";
  469. $this->renderTableHeader();
  470. ob_start();
  471. $this->renderTableBody();
  472. $body=ob_get_clean();
  473. $this->renderTableFooter();
  474. echo $body; // TFOOT must appear before TBODY according to the standard.
  475. echo "</table>";
  476. }
  477. else
  478. $this->renderEmptyText();
  479. }
  480. /**
  481. * Renders the table header.
  482. */
  483. public function renderTableHeader()
  484. {
  485. if(!$this->hideHeader)
  486. {
  487. echo "<thead>\n";
  488. if($this->filterPosition===self::FILTER_POS_HEADER)
  489. $this->renderFilter();
  490. echo "<tr>\n";
  491. foreach($this->columns as $column)
  492. $column->renderHeaderCell();
  493. echo "</tr>\n";
  494. if($this->filterPosition===self::FILTER_POS_BODY)
  495. $this->renderFilter();
  496. echo "</thead>\n";
  497. }
  498. elseif($this->filter!==null && ($this->filterPosition===self::FILTER_POS_HEADER || $this->filterPosition===self::FILTER_POS_BODY))
  499. {
  500. echo "<thead>\n";
  501. $this->renderFilter();
  502. echo "</thead>\n";
  503. }
  504. }
  505. /**
  506. * Renders the filter.
  507. * @since 1.1.1
  508. */
  509. public function renderFilter()
  510. {
  511. if($this->filter!==null)
  512. {
  513. echo "<tr class=\"{$this->filterCssClass}\">\n";
  514. foreach($this->columns as $column)
  515. $column->renderFilterCell();
  516. echo "</tr>\n";
  517. }
  518. }
  519. /**
  520. * Renders the table footer.
  521. */
  522. public function renderTableFooter()
  523. {
  524. $hasFilter=$this->filter!==null && $this->filterPosition===self::FILTER_POS_FOOTER;
  525. $hasFooter=$this->getHasFooter();
  526. if($hasFilter || $hasFooter)
  527. {
  528. echo "<tfoot>\n";
  529. if($hasFooter)
  530. {
  531. echo "<tr>\n";
  532. foreach($this->columns as $column)
  533. $column->renderFooterCell();
  534. echo "</tr>\n";
  535. }
  536. if($hasFilter)
  537. $this->renderFilter();
  538. echo "</tfoot>\n";
  539. }
  540. }
  541. /**
  542. * Renders the table body.
  543. */
  544. public function renderTableBody()
  545. {
  546. $data=$this->dataProvider->getData();
  547. $n=count($data);
  548. echo "<tbody>\n";
  549. if($n>0)
  550. {
  551. for($row=0;$row<$n;++$row)
  552. $this->renderTableRow($row);
  553. }
  554. else
  555. {
  556. echo '<tr><td colspan="'.count($this->columns).'" class="empty">';
  557. $this->renderEmptyText();
  558. echo "</td></tr>\n";
  559. }
  560. echo "</tbody>\n";
  561. }
  562. /**
  563. * Renders a table body row.
  564. * @param integer $row the row number (zero-based).
  565. */
  566. public function renderTableRow($row)
  567. {
  568. $htmlOptions=array();
  569. if($this->rowHtmlOptionsExpression!==null)
  570. {
  571. $data=$this->dataProvider->data[$row];
  572. $options=$this->evaluateExpression($this->rowHtmlOptionsExpression,array('row'=>$row,'data'=>$data));
  573. if(is_array($options))
  574. $htmlOptions = $options;
  575. }
  576. if($this->rowCssClassExpression!==null)
  577. {
  578. $data=$this->dataProvider->data[$row];
  579. $class=$this->evaluateExpression($this->rowCssClassExpression,array('row'=>$row,'data'=>$data));
  580. }
  581. elseif(is_array($this->rowCssClass) && ($n=count($this->rowCssClass))>0)
  582. $class=$this->rowCssClass[$row%$n];
  583. if(!empty($class))
  584. {
  585. if(isset($htmlOptions['class']))
  586. $htmlOptions['class'].=' '.$class;
  587. else
  588. $htmlOptions['class']=$class;
  589. }
  590. echo CHtml::openTag('tr', $htmlOptions)."\n";
  591. foreach($this->columns as $column)
  592. $this->renderDataCell($column, $row);
  593. echo "</tr>\n";
  594. }
  595. /**
  596. * A seam for people extending CGridView to be able to hook onto the data cell rendering process.
  597. *
  598. * By overriding only this method we will not need to copypaste and modify the whole entirety of `renderTableRow`.
  599. * Or override `renderDataCell()` method of all possible CGridColumn descendants.
  600. *
  601. * @param CGridColumn $column The Column instance to
  602. * @param integer $row
  603. * @since 1.1.17
  604. */
  605. protected function renderDataCell($column, $row)
  606. {
  607. $column->renderDataCell($row);
  608. }
  609. /**
  610. * @return boolean whether the table should render a footer.
  611. * This is true if any of the {@link columns} has a true {@link CGridColumn::hasFooter} value.
  612. */
  613. public function getHasFooter()
  614. {
  615. foreach($this->columns as $column)
  616. if($column->getHasFooter())
  617. return true;
  618. return false;
  619. }
  620. /**
  621. * @return CFormatter the formatter instance. Defaults to the 'format' application component.
  622. */
  623. public function getFormatter()
  624. {
  625. if($this->_formatter===null)
  626. $this->_formatter=Yii::app()->format;
  627. return $this->_formatter;
  628. }
  629. /**
  630. * @param CFormatter $value the formatter instance
  631. */
  632. public function setFormatter($value)
  633. {
  634. $this->_formatter=$value;
  635. }
  636. }