YiiWheels
  • Package
  • Class
  • Tree

Packages

  • None
  • yiiwheels
    • behaviors
    • widgets
    • widgets
      • ace
      • box
      • datepicker
      • daterangepicker
      • datetimepicker
      • detail
      • fileupload
      • fileuploader
      • gallery
      • google
      • grid
        • behaviors
        • operations
      • highcharts
      • maskInput
      • maskmoney
      • modal
      • multiselect
      • rangeslider
      • redactor
      • select2
      • sparklines
      • switch
      • timeago
      • timepicker
      • toggle
      • typeahead

Classes

  • WhGridView
  • WhGroupGridView
  • WhImageColumn
  • WhRelationalColumn
  1 <?php
  2 /**
  3  * WhGridView class
  4  *
  5  * This grid is an extended version of TbGridView.
  6  *
  7  * Features are:
  8  *  - Display an extended summary of the records shown. The extended summary can be configured to any of the
  9  *  WhOperation type of widgets.
 10  *  - Automatic chart display (using WhHighCharts widget), where user can 'switch' between views.
 11  *
 12  * @author Antonio Ramirez <amigo.cobos@gmail.com>
 13  * @copyright Copyright &copy; 2amigos.us 2013-
 14  * @license http://www.opensource.org/licenses/bsd-license.php New BSD License
 15  * @package YiiWheels.widgets.grid
 16  * @uses Yiistrap.widgets.TbHtml
 17  * @uses Yiistrap.widgets.TbGridView
 18  */
 19 Yii::import('bootstrap.helpers.TbHtml');
 20 Yii::import('bootstrap.widgets.TbGridView');
 21 
 22 class WhGridView extends TbGridView
 23 {
 24     /**
 25      * @var bool $fixedHeader if set to true will keep the header fixed  position
 26      */
 27     public $fixedHeader = false;
 28 
 29     /**
 30      * @var integer $headerOffset, when $fixedHeader is set to true, headerOffset will position table header top position
 31      * at $headerOffset. If you are using bootstrap and has navigation top fixed, its height is 40px, so it is recommended
 32      * to use $headerOffset=40;
 33      */
 34     public $headerOffset = 0;
 35 
 36     /**
 37      * @var string the template to be used to control the layout of various sections in the view.
 38      * These tokens are recognized: {extendedSummary}, {summary}, {items} and {pager}. They will be replaced with the
 39      * extended summary, summary text, the items, and the pager.
 40      */
 41     public $template = "{summary}\n{items}\n{pager}\n{extendedSummary}";
 42 
 43     /**
 44      * @var array $extendedSummary displays an extended summary version.
 45      * There are different types of summary types,
 46      * please, see {@link TbSumOperation}, {@link TbSumOfTypeOperation},{@link TbPercentOfTypeGooglePieOperation}
 47      * {@link TbPercentOfTypeOperation} and {@link TbPercentOfTypeEasyPieOperation}.
 48      *
 49      * The following is an example, please review the different types of TbOperation classes to find out more about
 50      * its configuration parameters.
 51      *
 52      * <pre>
 53      *  'extendedSummary' => array(
 54      *      'title' => '',      // the extended summary title
 55      *      'columns' => array( // the 'columns' that will be displayed at the extended summary
 56      *          'id' => array(  // column name "id"
 57      *              'class' => 'TbSumOperation', // what is the type of TbOperation we are going to display
 58      *              'label' => 'Sum of Ids'     // label is name of label of the resulted value (ie Sum of Ids:)
 59      *          ),
 60      *          'results' => array(   // column name "results"
 61      *              'class' => 'TbPercentOfTypeGooglePieOperation', // the type of TbOperation
 62      *              'label' => 'How Many Of Each? ', // the label of the operation
 63      *              'types' => array(               // TbPercentOfTypeGooglePieOperation "types" attributes
 64      *                  '0' => array('label' => 'zeros'),   // a value of "0" will be labelled "zeros"
 65      *                  '1' => array('label' => 'ones'),    // a value of "1" will be labelled "ones"
 66      *                  '2' => array('label' => 'twos'))    // a value of "2" will be labelled "twos"
 67      *          )
 68      *      )
 69      * ),
 70      * </pre>
 71      */
 72     public $extendedSummary = array();
 73 
 74     /**
 75      * @var string $extendedSummaryCssClass is the class name of the layer containing the extended summary
 76      */
 77     public $extendedSummaryCssClass = 'extended-summary';
 78 
 79     /**
 80      * @var array $extendedSummaryOptions the HTML attributes of the layer containing the extended summary
 81      */
 82     public $extendedSummaryOptions = array();
 83 
 84     /**
 85      * @var array $componentsAfterAjaxUpdate has scripts that will be executed after components have updated.
 86      * It is used internally to render scripts required for components to work correctly.  You may use it for your own
 87      * scripts, just make sure it is of type array.
 88      */
 89     public $componentsAfterAjaxUpdate = array();
 90 
 91     /**
 92      * @var array $componentsReadyScripts hold scripts that will be executed on document ready.
 93      * It is used internally to render scripts required for components to work correctly. You may use it for your own
 94      * scripts, just make sure it is of type array.
 95      */
 96     public $componentsReadyScripts = array();
 97 
 98     /**
 99      * @var array $chartOptions if configured, the extended view will display a highcharts chart.
100      */
101     public $chartOptions = array();
102 
103     /**
104      * @var bool whether to make the grid responsive
105      */
106     public $responsiveTable = false;
107 
108     /**
109      * @var boolean $displayExtendedSummary a helper property that is set to true if we have to render the
110      * extended summary
111      */
112     protected $displayExtendedSummary;
113 
114     /**
115      * @var WhOperation[] $extendedSummaryTypes hold the current configured TbOperation that will process column values.
116      */
117     protected $extendedSummaryTypes = array();
118 
119     /**
120      * @var array $extendedSummaryOperations hold the supported operation types
121      */
122     protected $extendedSummaryOperations = array(
123         'yiiwheels.widgets.grid.operations.WhSumOperation',
124         'yiiwheels.widgets.grid.operations.WhCountOfTypeOperation',
125         'yiiwheels.widgets.grid.operations.WhPercentOfTypeOperation',
126         'yiiwheels.widgets.grid.operations.WhPercentOfTypeEasyPieOperation',
127         'yiiwheels.widgets.grid.operations.WhPercentOfTypeGooglePieOperation'
128     );
129 
130     /**
131      * Widget initialization
132      */
133     public function init()
134     {
135         if (preg_match(
136                 '/extendedsummary/i',
137                 $this->template
138             ) && !empty($this->extendedSummary) && isset($this->extendedSummary['columns'])
139         ) {
140             $this->template .= "\n{extendedSummaryContent}";
141             $this->displayExtendedSummary = true;
142         }
143 
144         $this->attachBehavior('ywplugin', array('class' => 'yiiwheels.behaviors.WhPlugin'));
145         $this->attachBehavior('ywchart', array('class' => 'yiiwheels.widgets.grid.behaviors.WhChart'));
146 
147         parent::init();
148     }
149 
150     /**
151      * Renders grid content
152      */
153     public function renderContent()
154     {
155         parent::renderContent();
156         $this->registerCustomClientScript();
157     }
158 
159     /**
160      * Helper function to get an attribute from the data
161      * @param CActiveRecord $data
162      * @param string $attribute the attribute to get
163      * @return mixed the attribute value null if none found
164      */
165     protected function getAttribute($data, $attribute)
166     {
167         if ($this->dataProvider instanceof CActiveDataProvider && $data->hasAttribute($attribute)) {
168             return $data->{$attribute};
169         }
170 
171         if ($this->dataProvider instanceof CArrayDataProvider || $this->dataProvider instanceof CSqlDataProvider) {
172             if (is_object($data) && isset($data->{$attribute})) {
173                 return $data->{$attribute};
174             }
175             if (isset($data[$attribute])) {
176                 return $data[$attribute];
177             }
178         }
179         return null;
180     }
181 
182     /**
183      * Helper function to return the primary key of the $data
184      * IMPORTANT: composite keys on CActiveDataProviders will return the keys joined by comma
185      * @param CActiveRecord $data
186      * @return null|string
187      */
188     protected function getPrimaryKey($data)
189     {
190         if ($this->dataProvider instanceof CActiveDataProvider) {
191             $key = $this->dataProvider->keyAttribute === null ? $data->getPrimaryKey() : $data->{$this->dataProvider->keyAttribute};
192             return is_array($key) ? implode(',', $key) : $key;
193         }
194         if ($this->dataProvider instanceof CArrayDataProvider || $this->dataProvider instanceof CSqlDataProvider) {
195             return is_object($data) ? $data->{$this->dataProvider->keyField}
196                 : $data[$this->dataProvider->keyField];
197         }
198 
199         return null;
200     }
201 
202     /**
203      * Renders grid header
204      */
205     public function renderTableHeader()
206     {
207         $this->renderChart();
208         parent::renderTableHeader();
209     }
210 
211     /**
212      * Renders the table footer.
213      */
214     public function renderTableFooter()
215     {
216         $hasFilter = $this->filter !== null && $this->filterPosition === self::FILTER_POS_FOOTER;
217         $hasFooter = $this->getHasFooter();
218         if ($hasFilter || $hasFooter) {
219             echo "<tfoot>\n";
220             if ($hasFooter) {
221                 echo "<tr>\n";
222                 /** @var $column CDataColumn */
223                 foreach ($this->columns as $column) {
224                     $column->renderFooterCell();
225                 }
226                 echo "</tr>\n";
227             }
228             if ($hasFilter) {
229                 $this->renderFilter();
230             }
231             echo "</tfoot>\n";
232         }
233     }
234 
235     /**
236      * Renders a table body row.
237      * @param integer $row the row number (zero-based).
238      */
239     public function renderTableRow($row)
240     {
241         $htmlOptions = array();
242         if ($this->rowHtmlOptionsExpression !== null) {
243             $data = $this->dataProvider->data[$row];
244             $options = $this->evaluateExpression(
245                 $this->rowHtmlOptionsExpression,
246                 array('row' => $row, 'data' => $data)
247             );
248             if (is_array($options)) {
249                 $htmlOptions = $options;
250             }
251         }
252 
253         if ($this->rowCssClassExpression !== null) {
254             $data = $this->dataProvider->data[$row];
255             $class = $this->evaluateExpression($this->rowCssClassExpression, array('row' => $row, 'data' => $data));
256         } elseif (is_array($this->rowCssClass) && ($n = count($this->rowCssClass)) > 0) {
257             $class = $this->rowCssClass[$row % $n];
258         }
259 
260         if (!empty($class)) {
261             if (isset($htmlOptions['class'])) {
262                 $htmlOptions['class'] .= ' ' . $class;
263             } else {
264                 $htmlOptions['class'] = $class;
265             }
266         }
267 
268         echo TbHtml::openTag('tr', $htmlOptions);
269         foreach ($this->columns as $column) {
270             echo $this->displayExtendedSummary && !empty($this->extendedSummary['columns']) ? $this->parseColumnValue(
271                 $column,
272                 $row
273             ) : $column->renderDataCell($row);
274         }
275         echo TbHtml::closeTag('tr');
276     }
277 
278     /**
279      * Renders summary
280      */
281     public function renderExtendedSummary()
282     {
283         if (!isset($this->extendedSummaryOptions['class'])) {
284             $this->extendedSummaryOptions['class'] = $this->extendedSummaryCssClass;
285         } else {
286             $this->extendedSummaryOptions['class'] .= ' ' . $this->extendedSummaryCssClass;
287         }
288         echo '<div ' . TbHtml::renderAttributes($this->extendedSummaryOptions) . '></div>';
289     }
290 
291     /**
292      * Renders summary content. Will be appended to
293      */
294     public function renderExtendedSummaryContent()
295     {
296         if (($count = $this->dataProvider->getItemCount()) <= 0) {
297             return;
298         }
299 
300         if (!empty($this->extendedSummaryTypes)) {
301             echo '<div id="' . $this->id . '-extended-summary" style="display:none">';
302             if (isset($this->extendedSummary['title'])) {
303                 echo '<h3>' . $this->extendedSummary['title'] . '</h3>';
304             }
305             foreach ($this->extendedSummaryTypes as $summaryType) {
306                 /** @var $summaryType TbOperation */
307                 $summaryType->run();
308                 echo '<br/>';
309             }
310             echo '</div>';
311         }
312     }
313 
314     /**
315      * Registers required css, js and scripts
316      * Note: This script must be run at the end of content rendering not at the beginning as it is common with normal
317      * CGridViews
318      */
319     public function registerCustomClientScript()
320     {
321         /* publish assets dir */
322         $path = __DIR__ . DIRECTORY_SEPARATOR . 'assets';
323         $assetsUrl = $this->getAssetsUrl($path);
324 
325         /** @var $cs CClientScript */
326         $cs = Yii::app()->getClientScript();
327 
328         $fixedHeaderJs = '';
329         if ($this->fixedHeader) {
330             $cs->registerScriptFile(
331                 $assetsUrl . '/js/jquery.stickytableheaders' . (!YII_DEBUG ? '.min' : '') . '.js',
332                 CClientScript::POS_END);
333             $fixedHeaderJs = "$('#{$this->id} table.items').stickyTableHeaders({fixedOffset:{$this->headerOffset}});";
334             $this->componentsAfterAjaxUpdate[] = $fixedHeaderJs;
335         }
336 
337         $cs->registerScript(__CLASS__ . '#Wh' . $this->id,
338             '$grid = $("#' . $this->id . '");' .
339             $fixedHeaderJs . '
340             if ($(".' . $this->extendedSummaryCssClass . '", $grid).length)
341             {
342                 $(".' . $this->extendedSummaryCssClass . '", $grid).html($("#' . $this->id . '-extended-summary", $grid).html());
343             }
344             ' . (count($this->componentsReadyScripts) ? implode("\n", $this->componentsReadyScripts) : '') . '
345             $.ajaxPrefilter(function (options, originalOptions, jqXHR) {
346                 var qs = $.deparam.querystring(options.url);
347                 if (qs.hasOwnProperty("ajax") && qs.ajax == "' . $this->id . '")
348                 {
349                     options.realsuccess = options.success;
350                     options.success = function(data)
351                     {
352                         if (options.realsuccess) {
353                             options.realsuccess(data);
354                             var $data = $("<div>" + data + "</div>");
355                             // we need to get the grid again... as it has been updated
356                             if ($(".' . $this->extendedSummaryCssClass . '", $("#' . $this->id . '")))
357                             {
358                                 $(".' .
359             $this->extendedSummaryCssClass .
360             '", $("#' . $this->id .
361             '")).html($("#' . $this->id .
362             '-extended-summary", $data).html());
363         }
364         ' .
365             (count($this->componentsAfterAjaxUpdate)
366                 ? implode("\n", $this->componentsAfterAjaxUpdate)
367                 : '') .
368             '
369         }
370     }
371 }
372 });'
373         );
374     }
375 
376     /**
377      * Helper function to get a column by its name
378      * @param string $name
379      * @return CDataColumn|null
380      */
381     public function getColumnByName($name)
382     {
383         foreach ($this->columns as $column) {
384             if (strcmp($column->name, $name) === 0) {
385                 return $column;
386             }
387         }
388         return null;
389     }
390 
391     /**
392      * Creates column objects and initializes them.
393      */
394     protected function initColumns()
395     {
396         parent::initColumns();
397         if($this->responsiveTable) {
398             $this->attachBehavior('ywresponsive', array('class' => 'yiiwheels.widgets.grid.behaviors.WhResponsive'));
399             $this->writeResponsiveCss($this->columns, $this->id);
400         }
401     }
402 
403     /**
404      * Parses the value of a column by an operation
405      * @param CDataColumn $column
406      * @param integer $row the current row number
407      * @return string
408      */
409     protected function parseColumnValue($column, $row)
410     {
411         ob_start();
412         $column->renderDataCell($row);
413         $value = ob_get_clean();
414 
415         if ($column instanceof CDataColumn && array_key_exists($column->name, $this->extendedSummary['columns'])) {
416             // lets get the configuration
417             $config = $this->extendedSummary['columns'][$column->name];
418             // add the required column object in
419             $config['column'] = $column;
420             // build the summary operation object
421             $op = $this->getSummaryOperationInstance($column->name, $config);
422             // process the value
423             $op->processValue($value);
424         }
425         return $value;
426     }
427 
428     /**
429      * Each type of 'extended' summary
430      * @param string $name the name of the column
431      * @param array $config the configuration of the column at the extendedSummary
432      * @return mixed
433      * @throws CException
434      */
435     protected function getSummaryOperationInstance($name, $config)
436     {
437         if (!isset($config['class'])) {
438             throw new CException(Yii::t(
439                 'zii',
440                 'Column summary configuration must be an array containing a "type" element.'
441             ));
442         }
443 
444         if (!in_array($config['class'], $this->extendedSummaryOperations)) {
445             throw new CException(Yii::t(
446                 'zii',
447                 '"{operation}" is an unsupported class operation.',
448                 array('{operation}' => $config['class'])
449             ));
450         }
451 
452         // name of the column should be unique
453         if (!isset($this->extendedSummaryTypes[$name])) {
454             $this->extendedSummaryTypes[$name] = Yii::createComponent($config);
455             $this->extendedSummaryTypes[$name]->init();
456         }
457         return $this->extendedSummaryTypes[$name];
458     }
459 
460 }
461 
YiiWheels API documentation generated by ApiGen 2.8.0