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