/admincrud/extensions/bootstrap/widgets/TbExtendedGridView.php
PHP | 1265 lines | 678 code | 127 blank | 460 comment | 90 complexity | 92340d872d4d56bd0adc7147c1b59ef9 MD5 | raw file
Possible License(s): LGPL-2.1, BSD-3-Clause
- <?php
- /*## TbExtendedGridView class file
- *
- * @author Antonio Ramirez <antonio@clevertech.biz>
- * @copyright Copyright © Clevertech 2012-
- * @license [New BSD License](http://www.opensource.org/licenses/bsd-license.php)
- * @package bootstrap.widgets
- */
- Yii::import('bootstrap.widgets.TbGridView');
- /**
- * TbExtendedGridView is an extended version of TbGridView.
- *
- * Features are:
- * - Display an extended summary of the records shown. The extended summary can be configured to any of the
- * {@link TbOperation} type of widgets.
- * - Automatic chart display (using TbHighCharts widget), where user can 'switch' between views.
- * - Selectable cells
- * - Sortable rows
- *
- * @property CActiveDataProvider $dataProvider the data provider for the view.
- * @property TbDataColumn[] $columns
- */
- class TbExtendedGridView extends TbGridView
- {
- /**
- * @var bool $fixedHeader if set to true will keep the header fixed position
- */
- public $fixedHeader = false;
- /**
- * @var integer $headerOffset, when $fixedHeader is set to true, headerOffset will position table header top position
- * at $headerOffset. If you are using bootstrap and has navigation top fixed, its height is 40px, so it is recommended
- * to use $headerOffset=40;
- */
- public $headerOffset = 0;
- /**
- * @var string the template to be used to control the layout of various sections in the view.
- * These tokens are recognized: {extendedSummary}, {summary}, {items} and {pager}. They will be replaced with the
- * extended summary, summary text, the items, and the pager.
- */
- public $template = "{summary}\n{items}\n{pager}\n{extendedSummary}";
- /**
- * @var array $extendedSummary displays an extended summary version.
- * There are different types of summary types,
- * please, see {@link TbSumOperation}, {@link TbSumOfTypeOperation},{@link TbPercentOfTypeGooglePieOperation}
- * {@link TbPercentOfTypeOperation} and {@link TbPercentOfTypeEasyPieOperation}.
- *
- * The following is an example, please review the different types of TbOperation classes to find out more about
- * its configuration parameters.
- *
- * <pre>
- * 'extendedSummary' => array(
- * 'title' => '', // the extended summary title
- * 'columns' => array( // the 'columns' that will be displayed at the extended summary
- * 'id' => array( // column name "id"
- * 'class' => 'TbSumOperation', // what is the type of TbOperation we are going to display
- * 'label' => 'Sum of Ids' // label is name of label of the resulted value (ie Sum of Ids:)
- * ),
- * 'results' => array( // column name "results"
- * 'class' => 'TbPercentOfTypeGooglePieOperation', // the type of TbOperation
- * 'label' => 'How Many Of Each? ', // the label of the operation
- * 'types' => array( // TbPercentOfTypeGooglePieOperation "types" attributes
- * '0' => array('label' => 'zeros'), // a value of "0" will be labelled "zeros"
- * '1' => array('label' => 'ones'), // a value of "1" will be labelled "ones"
- * '2' => array('label' => 'twos')) // a value of "2" will be labelled "twos"
- * )
- * )
- * ),
- * </pre>
- */
- public $extendedSummary = array();
- /**
- * @var string $extendedSummaryCssClass is the class name of the layer containing the extended summary
- */
- public $extendedSummaryCssClass = 'extended-summary';
- /**
- * @var array $extendedSummaryOptions the HTML attributes of the layer containing the extended summary
- */
- public $extendedSummaryOptions = array();
- /**
- * @var array $componentsAfterAjaxUpdate has scripts that will be executed after components have updated.
- * It is used internally to render scripts required for components to work correctly. You may use it for your own
- * scripts, just make sure it is of type array.
- */
- public $componentsAfterAjaxUpdate = array();
- /**
- * @var array $componentsReadyScripts hold scripts that will be executed on document ready.
- * It is used internally to render scripts required for components to work correctly. You may use it for your own
- * scripts, just make sure it is of type array.
- */
- public $componentsReadyScripts = array();
- /**
- * @var array $chartOptions if configured, the extended view will display a highcharts chart.
- */
- public $chartOptions = array();
- /**
- * @var bool $sortableRows. If true the rows at the table will be sortable.
- */
- public $sortableRows = false;
- /**
- * @var string Database field name for row sorting
- */
- public $sortableAttribute = 'sort_order';
- /**
- * @var boolean Save sort order by ajax defaults to false
- * @see bootstrap.action.TbSortableAction for an easy way to use with your controller
- */
- public $sortableAjaxSave = false;
- /**
- * @var string Name of the action to call and sort values
- * @see bootstrap.action.TbSortableAction for an easy way to use with your controller
- *
- * <pre>
- * 'sortableAction' => 'module/controller/sortable' | 'controller/sortable'
- * </pre>
- *
- * The widget will make use of the string to create the URL and then append $sortableAttribute
- * @see $sortableAttribute
- */
- public $sortableAction;
- /**
- * @var string a javascript function that will be invoked after a successful sorting is done.
- * The function signature is <code>function(id, position)</code> where 'id' refers to the ID of the model id key,
- * 'position' the new position in the list.
- */
- public $afterSortableUpdate;
- /**
- * @var bool whether to allow selecting of cells
- */
- public $selectableCells = false;
- /**
- * @var string the filter to use to allow selection. For example, if you set the "htmlOptions" property of a column to have a
- * "class" of "tobeselected", you could set this property as: "td.tobeselected" in order to allow selection to
- * those columns with that class only.
- */
- public $selectableCellsFilter = 'td';
- /**
- * @var string a javascript function that will be invoked after a selection is done.
- * The function signature is <code>function(selected)</code> where 'selected' refers to the selected columns.
- */
- public $afterSelectableCells;
- /**
- * @var array the configuration options to display a TbBulkActions widget
- * @see TbBulkActions widget for its configuration
- */
- public $bulkActions = array();
- /**
- * @var string the aligment of the bulk actions. It can be 'left' or 'right'.
- */
- public $bulkActionAlign = 'right';
- /**
- * @var TbBulkActions component that will display the bulk actions to the grid
- */
- protected $bulk;
- /**
- * @var boolean $displayExtendedSummary a helper property that is set to true if we have to render the
- * extended summary
- */
- protected $displayExtendedSummary;
- /**
- * @var boolean $displayChart a helper property that is set to true if we have to render a chart.
- */
- protected $displayChart;
- /**
- * @var TbOperation[] $extendedSummaryTypes hold the current configured TbOperation that will process column values.
- */
- protected $extendedSummaryTypes = array();
- /**
- * @var array $extendedSummaryOperations hold the supported operation types
- */
- protected $extendedSummaryOperations = array(
- 'TbSumOperation',
- 'TbCountOfTypeOperation',
- 'TbPercentOfTypeOperation',
- 'TbPercentOfTypeEasyPieOperation',
- 'TbPercentOfTypeGooglePieOperation'
- );
- /**
- *### .init()
- *
- * Widget initialization
- */
- public function init()
- {
- if (preg_match(
- '/extendedsummary/i',
- $this->template
- ) && !empty($this->extendedSummary) && isset($this->extendedSummary['columns'])
- ) {
- $this->template .= "\n{extendedSummaryContent}";
- $this->displayExtendedSummary = true;
- }
- if (!empty($this->chartOptions) && @$this->chartOptions['data'] && $this->dataProvider->getItemCount()) {
- $this->displayChart = true;
- }
- if ($this->bulkActions !== array() && isset($this->bulkActions['actionButtons'])) {
- if (!isset($this->bulkActions['class'])) {
- $this->bulkActions['class'] = 'bootstrap.widgets.TbBulkActions';
- }
- $this->bulk = Yii::createComponent($this->bulkActions, $this);
- $this->bulk->init();
- }
- parent::init();
- }
- /**
- *### .renderContent()
- *
- * Renders grid content
- */
- public function renderContent()
- {
- parent::renderContent();
- $this->registerCustomClientScript();
- }
- /**
- *### .renderKeys()
- *
- * Renders the key values of the data in a hidden tag.
- */
- public function renderKeys()
- {
- $data = $this->dataProvider->getData();
- if (empty($data)) {
- return false;
- }
- if (!$this->sortableRows || !$this->getAttribute($data[0], (string)$this->sortableAttribute)) {
- parent::renderKeys();
- }
- echo CHtml::openTag(
- 'div',
- array(
- 'class' => 'keys',
- 'style' => 'display:none',
- 'title' => Yii::app()->getRequest()->getUrl(),
- )
- );
- foreach ($data as $d) {
- echo CHtml::tag(
- 'span',
- array('data-order' => $this->getAttribute($d, $this->sortableAttribute)),
- CHtml::encode($this->getPrimaryKey($d))
- );
- }
- echo "</div>\n";
- return true;
- }
- /**
- *### .getAttribute()
- *
- * Helper function to get an attribute from the data
- *
- * @param CActiveRecord $data
- * @param string $attribute the attribute to get
- *
- * @return mixed the attribute value null if none found
- */
- protected function getAttribute($data, $attribute)
- {
- if ($this->dataProvider instanceof CActiveDataProvider && $data->hasAttribute($attribute)) {
- return $data->{$attribute};
- }
- if ($this->dataProvider instanceof CArrayDataProvider || $this->dataProvider instanceof CSqlDataProvider) {
- if (is_object($data) && isset($data->{$attribute})) {
- return $data->{$attribute};
- }
- if (isset($data[$attribute])) {
- return $data[$attribute];
- }
- }
- return null;
- }
- /**
- *### .getPrimaryKey()
- *
- * Helper function to return the primary key of the $data
- * IMPORTANT: composite keys on CActiveDataProviders will return the keys joined by comma
- *
- * @param CActiveRecord $data
- *
- * @return null|string
- */
- protected function getPrimaryKey($data)
- {
- if ($this->dataProvider instanceof CActiveDataProvider) {
- $key = $this->dataProvider->keyAttribute === null ? $data->getPrimaryKey() : $data->{$this->keyAttribute};
- return is_array($key) ? implode(',', $key) : $key;
- }
- if ($this->dataProvider instanceof CArrayDataProvider || $this->dataProvider instanceof CSqlDataProvider) {
- return is_object($data) ? $data->{$this->dataProvider->keyField}
- : $data[$this->dataProvider->keyField];
- }
- return null;
- }
- /**
- *### .renderTableHeader()
- *
- * Renders grid header
- */
- public function renderTableHeader()
- {
- $this->renderChart();
- parent::renderTableHeader();
- }
- /**
- *### .renderTableFooter()
- *
- * Renders the table footer.
- */
- public function renderTableFooter()
- {
- $hasFilter = $this->filter !== null && $this->filterPosition === self::FILTER_POS_FOOTER;
- $hasFooter = $this->getHasFooter();
- if ($this->bulk !== null || $hasFilter || $hasFooter) {
- echo "<tfoot>\n";
- if ($hasFooter) {
- echo "<tr>\n";
- /** @var $column CDataColumn */
- foreach ($this->columns as $column) {
- $column->renderFooterCell();
- }
- echo "</tr>\n";
- }
- if ($hasFilter) {
- $this->renderFilter();
- }
- if ($this->bulk !== null) {
- $this->renderBulkActions();
- }
- echo "</tfoot>\n";
- }
- }
- /**
- *### .renderBulkActions()
- */
- public function renderBulkActions()
- {
- echo '<tr><td colspan="' . count($this->columns) . '">';
- $this->bulk->renderButtons();
- echo '</td></tr>';
- }
- /**
- *### .renderChart()
- *
- * Renders chart
- * @throws CException
- */
- public function renderChart()
- {
- if (!$this->displayChart || $this->dataProvider->getItemCount() <= 0) {
- return;
- }
- if (!isset($this->chartOptions['data']['series'])) {
- throw new CException(Yii::t(
- 'zii',
- 'You need to set the "series" attribute in order to render a chart'
- ));
- }
- $configSeries = $this->chartOptions['data']['series'];
- if (!is_array($configSeries)) {
- throw new CException(Yii::t('zii', '"chartOptions.series" is expected to be an array.'));
- }
- if (!isset($this->chartOptions['config'])) {
- $this->chartOptions['config'] = array();
- }
- // ****************************************
- // render switch buttons
- $buttons = Yii::createComponent(
- array(
- 'class' => 'bootstrap.widgets.TbButtonGroup',
- 'toggle' => 'radio',
- 'buttons' => array(
- array(
- 'label' => Yii::t('zii', 'Grid'),
- 'url' => '#',
- 'htmlOptions' => array('class' => 'active ' . $this->getId() . '-grid-control grid')
- ),
- array(
- 'label' => Yii::t('zii', 'Chart'),
- 'url' => '#',
- 'htmlOptions' => array('class' => $this->getId() . '-grid-control chart')
- ),
- ),
- 'htmlOptions' => array('style' => 'margin-bottom:5px')
- )
- );
- echo '<div class="row">';
- $buttons->init();
- $buttons->run();
- echo '</div>';
- $chartId = preg_replace('[-\\ ?]', '_', 'exgvwChart' . $this->getId()); // cleaning out most possible characters invalid as javascript variable identifiers.
- $this->componentsReadyScripts[] = '$(document).on("click",".' . $this->getId() . '-grid-control", function(){
- if ($(this).hasClass("grid") && $("#' . $this->getId() . ' #' . $chartId . '").is(":visible"))
- {
- $("#' . $this->getId() . ' #' . $chartId . '").hide();
- $("#' . $this->getId() . ' table.items").show();
- }
- if ($(this).hasClass("chart") && $("#' . $this->getId() . ' table.items").is(":visible"))
- {
- $("#' . $this->getId() . ' table.items").hide();
- $("#' . $this->getId() . ' #' . $chartId . '").show();
- }
- return false;
- });';
- // end switch buttons
- // ****************************************
- // render Chart
- // chart options
- $data = $this->dataProvider->getData();
- $count = count($data);
- $seriesData = array();
- $cnt = 0;
- foreach ($configSeries as $set) {
- $seriesData[$cnt] = array('name' => isset($set['name']) ? $set['name'] : null, 'data' => array());
- for ($row = 0; $row < $count; ++$row) {
- $column = $this->getColumnByName($set['attribute']);
- if (!is_null($column) && $column->value !== null) {
- $seriesData[$cnt]['data'][] = $this->evaluateExpression(
- $column->value,
- array('data' => $data[$row], 'row' => $row)
- );
- } else {
- $value = CHtml::value($data[$row], $set['attribute']);
- $seriesData[$cnt]['data'][] = is_numeric($value) ? (float)$value : $value;
- }
- }
- ++$cnt;
- }
- // ****************************************
- // render chart
- $options = CMap::mergeArray(
- $this->chartOptions['config'],
- array('series' => $seriesData)
- );
- $this->chartOptions['htmlOptions'] = isset($this->chartOptions['htmlOptions'])
- ? $this->chartOptions['htmlOptions'] : array();
- $this->chartOptions['htmlOptions']['style'] = 'display:none'; // sorry but use a class to provide styles, we need this
- // build unique ID
- // important!
- echo '<div class="row">';
- if ($this->ajaxUpdate !== false) {
- if (isset($options['chart']) && is_array($options['chart'])) {
- $options['chart']['renderTo'] = $chartId;
- } else {
- $options['chart'] = array('renderTo' => $chartId);
- }
- $jsOptions = CJSON::encode($options);
- if (isset($this->chartOptions['htmlOptions']['data-config'])) {
- unset($this->chartOptions['htmlOptions']['data-config']);
- }
- echo "<div id='{$chartId}' " . CHtml::renderAttributes(
- $this->chartOptions['htmlOptions']
- ) . " data-config='{$jsOptions}'></div>";
- $this->componentsAfterAjaxUpdate[] = "highchart{$chartId} = new Highcharts.Chart($('#{$chartId}').data('config'));";
- }
- $configChart = array(
- 'class' => 'bootstrap.widgets.TbHighCharts',
- 'id' => $chartId,
- 'options' => $options,
- 'htmlOptions' => $this->chartOptions['htmlOptions']
- );
- $chart = Yii::createComponent($configChart);
- $chart->init();
- $chart->run();
- echo '</div>';
- // end chart display
- // ****************************************
- }
- /**
- *### .renderTableRow()
- *
- * Renders a table body row.
- *
- * @param integer $row the row number (zero-based).
- */
- public function renderTableRow($row)
- {
- $htmlOptions = array();
- if ($this->rowHtmlOptionsExpression !== null) {
- $data = $this->dataProvider->data[$row];
- $options = $this->evaluateExpression(
- $this->rowHtmlOptionsExpression,
- array('row' => $row, 'data' => $data)
- );
- if (is_array($options)) {
- $htmlOptions = $options;
- }
- }
- if ($this->rowCssClassExpression !== null) {
- $data = $this->dataProvider->data[$row];
- $class = $this->evaluateExpression($this->rowCssClassExpression, array('row' => $row, 'data' => $data));
- } elseif (is_array($this->rowCssClass) && ($n = count($this->rowCssClass)) > 0) {
- $class = $this->rowCssClass[$row % $n];
- }
- if (!empty($class)) {
- if (isset($htmlOptions['class'])) {
- $htmlOptions['class'] .= ' ' . $class;
- } else {
- $htmlOptions['class'] = $class;
- }
- }
- echo CHtml::openTag('tr', $htmlOptions) . "\n";
- foreach ($this->columns as $column) {
- echo $this->displayExtendedSummary && !empty($this->extendedSummary['columns']) ? $this->parseColumnValue(
- $column,
- $row
- ) : $column->renderDataCell($row);
- }
- echo "</tr>\n";
- }
- /**
- *### .renderExtendedSummary()
- *
- * Renders summary
- */
- public function renderExtendedSummary()
- {
- if (!isset($this->extendedSummaryOptions['class'])) {
- $this->extendedSummaryOptions['class'] = $this->extendedSummaryCssClass;
- } else {
- $this->extendedSummaryOptions['class'] .= ' ' . $this->extendedSummaryCssClass;
- }
- echo '<div ' . CHtml::renderAttributes($this->extendedSummaryOptions) . '></div>';
- }
- /**
- *### .renderExtendedSummaryContent()
- *
- * Renders summary content. Will be appended to
- */
- public function renderExtendedSummaryContent()
- {
- if (($count = $this->dataProvider->getItemCount()) <= 0) {
- return;
- }
- if (!empty($this->extendedSummaryTypes)) {
- echo '<div id="' . $this->id . '-extended-summary" style="display:none">';
- if (isset($this->extendedSummary['title'])) {
- echo '<h3>' . $this->extendedSummary['title'] . '</h3>';
- }
- foreach ($this->extendedSummaryTypes as $summaryType) {
- /** @var $summaryType TbOperation */
- $summaryType->run();
- echo '<br/>';
- }
- echo '</div>';
- }
- }
- /**
- *### .registerCustomClientScript()
- *
- * This script must be run at the end of content rendering not at the beginning as it is common with normal CGridViews
- */
- public function registerCustomClientScript()
- {
- /** @var $cs CClientScript */
- $cs = Yii::app()->getClientScript();
- $fixedHeaderJs = '';
- if ($this->fixedHeader) {
- Yii::app()->bootstrap->registerAssetJs('jquery.stickytableheaders' . (!YII_DEBUG ? '.min' : '') . '.js');
- $fixedHeaderJs = "$('#{$this->id} table.items').stickyTableHeaders({fixedOffset:{$this->headerOffset}});";
- $this->componentsAfterAjaxUpdate[] = $fixedHeaderJs;
- }
- if ($this->sortableRows) {
- $afterSortableUpdate = '';
- if ($this->afterSortableUpdate !== null) {
- if (!($this->afterSortableUpdate instanceof CJavaScriptExpression) && strpos(
- $this->afterSortableUpdate,
- 'js:'
- ) !== 0
- ) {
- $afterSortableUpdate = new CJavaScriptExpression($this->afterSortableUpdate);
- } else {
- $afterSortableUpdate = $this->afterSortableUpdate;
- }
- }
- $this->selectableRows = 1;
- $cs->registerCoreScript('jquery.ui');
- Yii::app()->bootstrap->registerAssetJs('jquery.sortable.gridview.js');
- if ($this->sortableAjaxSave && $this->sortableAction !== null) {
- $sortableAction = Yii::app()->createUrl(
- $this->sortableAction,
- array('sortableAttribute' => $this->sortableAttribute)
- );
- } else {
- $sortableAction = '';
- }
- $afterSortableUpdate = CJavaScript::encode($afterSortableUpdate);
- $this->componentsReadyScripts[] = "$.fn.yiiGridView.sortable('{$this->id}', '{$sortableAction}', {$afterSortableUpdate});";
- $this->componentsAfterAjaxUpdate[] = "$.fn.yiiGridView.sortable('{$this->id}', '{$sortableAction}', {$afterSortableUpdate});";
- }
- if ($this->selectableCells) {
- $afterSelectableCells = '';
- if ($this->afterSelectableCells !== null) {
- echo strpos($this->afterSelectableCells, 'js:');
- if (!($this->afterSelectableCells instanceof CJavaScriptExpression) && strpos(
- $this->afterSelectableCells,
- 'js:'
- ) !== 0
- ) {
- $afterSelectableCells = new CJavaScriptExpression($this->afterSelectableCells);
- } else {
- $afterSelectableCells = $this->afterSelectableCells;
- }
- }
- $cs->registerCoreScript('jquery.ui');
- Yii::app()->bootstrap->registerAssetJs('jquery.selectable.gridview.js');
- $afterSelectableCells = CJavaScript::encode($afterSelectableCells);
- $this->componentsReadyScripts[] = "$.fn.yiiGridView.selectable('{$this->id}','{$this->selectableCellsFilter}',{$afterSelectableCells});";
- $this->componentsAfterAjaxUpdate[] = "$.fn.yiiGridView.selectable('{$this->id}','{$this->selectableCellsFilter}', {$afterSelectableCells});";
- }
- $cs->registerScript(
- __CLASS__ . '#' . $this->id . 'Ex',
- '
- $grid = $("#' . $this->id . '");
- ' . $fixedHeaderJs . '
- if ($(".' . $this->extendedSummaryCssClass . '", $grid).length)
- {
- $(".' . $this->extendedSummaryCssClass . '", $grid).html($("#' . $this->id . '-extended-summary", $grid).html());
- }
- ' . (count($this->componentsReadyScripts) ? implode(PHP_EOL, $this->componentsReadyScripts) : '') . '
- $.ajaxPrefilter(function (options, originalOptions, jqXHR) {
- var qs = $.deparam.querystring(options.url);
- if (qs.hasOwnProperty("ajax") && qs.ajax == "' . $this->id . '")
- {
- options.realsuccess = options.success;
- options.success = function(data)
- {
- if (options.realsuccess) {
- options.realsuccess(data);
- var $data = $("<div>" + data + "</div>");
- // we need to get the grid again... as it has been updated
- if ($(".' . $this->extendedSummaryCssClass . '", $("#' . $this->id . '")))
- {
- $(".' . $this->extendedSummaryCssClass . '", $("#' . $this->id . '")).html($("#' . $this->id . '-extended-summary", $data).html());
- }
- ' . (count($this->componentsAfterAjaxUpdate) ? implode(
- PHP_EOL,
- $this->componentsAfterAjaxUpdate
- ) : '') . '
- }
- }
- }
- });'
- );
- }
- /**
- *### .parseColumnValue()
- *
- * @param CDataColumn $column
- * @param integer $row the current row number
- *
- * @return string
- */
- protected function parseColumnValue($column, $row)
- {
- ob_start();
- $column->renderDataCell($row);
- $value = ob_get_clean();
- if ($column instanceof CDataColumn && array_key_exists($column->name, $this->extendedSummary['columns'])) {
- // lets get the configuration
- $config = $this->extendedSummary['columns'][$column->name];
- // add the required column object in
- $config['column'] = $column;
- // build the summary operation object
- $op = $this->getSummaryOperationInstance($column->name, $config);
- // process the value
- $op->processValue($value);
- }
- return $value;
- }
- /**
- *### .getSummaryOperationInstance()
- *
- * Each type of 'extended' summary
- *
- * @param string $name the name of the column
- * @param array $config the configuration of the column at the extendedSummary
- *
- * @return mixed
- * @throws CException
- */
- protected function getSummaryOperationInstance($name, $config)
- {
- if (!isset($config['class'])) {
- throw new CException(Yii::t(
- 'zii',
- 'Column summary configuration must be an array containing a "type" element.'
- ));
- }
- if (!in_array($config['class'], $this->extendedSummaryOperations)) {
- throw new CException(Yii::t(
- 'zii',
- '"{operation}" is an unsupported class operation.',
- array('{operation}' => $config['class'])
- ));
- }
- // name of the column should be unique
- if (!isset($this->extendedSummaryTypes[$name])) {
- $this->extendedSummaryTypes[$name] = Yii::createComponent($config);
- $this->extendedSummaryTypes[$name]->init();
- }
- return $this->extendedSummaryTypes[$name];
- }
- /**
- *### .getColumnByName()
- *
- * Helper function to get a column by its name
- *
- * @param string $name
- *
- * @return CDataColumn|null
- */
- protected function getColumnByName($name)
- {
- foreach ($this->columns as $column) {
- if (strcmp($column->name, $name) === 0) {
- return $column;
- }
- }
- return null;
- }
- }
- /**
- * TbOperation class
- *
- * Abstract class where all types of operations extend from
- */
- abstract class TbOperation extends CWidget
- {
- /**
- * @var string $template the template to display label and value of the operation at the summary
- */
- public $template = '{label}: {value}';
- /**
- * @var int $value the resulted value of operation
- */
- public $value = 0;
- /**
- * @var string $label the label of the calculated value
- */
- public $label;
- /**
- * @var TbDataColumn $column
- */
- public $column;
- /**
- * Widget initialization
- * @throws CException
- */
- public function init()
- {
- if (null == $this->column) {
- throw new CException(Yii::t(
- 'zii',
- '"{attribute}" attribute must be defined',
- array('{attribute}' => 'column')
- ));
- }
- }
- /**
- * Widget's run method
- */
- public function run()
- {
- $this->displaySummary();
- }
- /**
- * Process the row data value
- *
- * @param $value
- *
- * @return mixed
- */
- abstract public function processValue($value);
- /**
- * Displays the resulting summary
- * @return mixed
- */
- abstract public function displaySummary();
- }
- /**
- * TbSumOperation class
- *
- * Displays a total of specified column name.
- *
- */
- class TbSumOperation extends TbOperation
- {
- /**
- * @var float $total the total sum
- */
- protected $total;
- /**
- * @var array $supportedTypes the supported type of values
- */
- protected $supportedTypes = array('raw', 'text', 'ntext', 'number');
- /**
- * Widget's initialization method
- * @throws CException
- */
- public function init()
- {
- parent::init();
- if (!in_array($this->column->type, $this->supportedTypes)) {
- throw new CException(Yii::t(
- 'zii',
- 'Unsupported column type. Supported column types are: "{types}"',
- array(
- '{types}' => implode(', ', $this->supportedTypes)
- )
- ));
- }
- }
- /**
- * Extracts the digital part of the calculated value.
- *
- * @param int $value
- *
- * @return bool
- */
- protected function extractNumber($value)
- {
- preg_match_all('/([+-]?[0-9]+[,\.]?)+/', $value, $matches);
- return !empty($matches[0]) && @$matches[0][0] ? $matches[0][0] : 0;
- }
- /**
- * Process the value to calculate
- *
- * @param $value
- *
- * @return mixed|void
- */
- public function processValue($value)
- {
- // remove html tags as we cannot access renderDataCellContent from the column
- $clean = strip_tags($value);
- $this->total += ((float)$this->extractNumber($clean));
- }
- /**
- * Displays the summary
- * @return mixed|void
- */
- public function displaySummary()
- {
- echo strtr(
- $this->template,
- array(
- '{label}' => $this->label,
- '{value}' => $this->total === null ? '' : Yii::app()->format->format($this->total, $this->column->type)
- )
- );
- }
- }
- /**
- * TbCountOfTypeOperation class
- *
- * Renders a summary based on the count of specified types. For example, if a value has a type 'blue', this class will
- * count the number of times the value 'blue' has on that column.
- */
- class TbCountOfTypeOperation extends TbOperation
- {
- /**
- * @var string $template
- * @see parent class
- */
- public $template = '{label}: {types}';
- /**
- * @var string $typeTemplate holds the template of each calculated type
- */
- public $typeTemplate = '{label}({value})';
- /**
- * @var array $types hold the configuration of types to calculate. The configuration is set by an array which keys
- * are the value types to count. You can set their 'label' independently.
- *
- * <pre>
- * 'types' => array(
- * '0' => array('label' => 'zeros'),
- * '1' => array('label' => 'ones'),
- * '2' => array('label' => 'twos')
- * </pre>
- */
- public $types = array();
- /**
- * Widget's initialization
- * @throws CException
- */
- public function init()
- {
- if (empty($this->types)) {
- throw new CException(Yii::t(
- 'zii',
- '"{attribute}" attribute must be defined',
- array('{attribute}' => 'types')
- ));
- }
- foreach ($this->types as $type) {
- if (!isset($type['label'])) {
- throw new CException(Yii::t('zii', 'The "label" of a type must be defined.'));
- }
- }
- parent::init();
- }
- /**
- * (no phpDoc)
- * @see TbOperation
- *
- * @param $value
- *
- * @return mixed|void
- */
- public function processValue($value)
- {
- $clean = strip_tags($value);
- if (array_key_exists($clean, $this->types)) {
- if (!isset($this->types[$clean]['value'])) {
- $this->types[$clean]['value'] = 0;
- }
- $this->types[$clean]['value'] += 1;
- }
- }
- /**
- * (no phpDoc)
- * @see TbOperation
- * @return mixed|void
- */
- public function displaySummary()
- {
- $typesResults = array();
- foreach ($this->types as $type) {
- if (!isset($type['value'])) {
- $type['value'] = 0;
- }
- $typesResults[] = strtr(
- $this->typeTemplate,
- array('{label}' => $type['label'], '{value}' => $type['value'])
- );
- }
- echo strtr($this->template, array('{label}' => $this->label, '{types}' => implode(' ', $typesResults)));
- }
- }
- /**
- * TbPercentOfTypeOperation class
- * Renders a summary based on the percent count of specified types. For example, if a value has a type 'blue', this class will
- * count the percentage number of times the value 'blue' has on that column.
- */
- class TbPercentOfTypeOperation extends TbCountOfTypeOperation
- {
- /**
- * @var string $typeTemplate
- * @see TbCountOfTypeOperation
- */
- public $typeTemplate = '{label}({value}%)';
- /**
- * @var integer $_total holds the total sum of the values. Required to get the percentage.
- */
- protected $_total;
- /**
- * @return mixed|void
- * @see TbOperation
- */
- public function displaySummary()
- {
- $typesResults = array();
- foreach ($this->types as $type) {
- if (!isset($type['value'])) {
- $type['value'] = 0;
- }
- $type['value'] = $this->getTotal() ? number_format((float)($type['value'] / $this->getTotal()) * 100, 1)
- : 0;
- $typesResults[] = strtr(
- $this->typeTemplate,
- array('{label}' => $type['label'], '{value}' => $type['value'])
- );
- }
- echo strtr($this->template, array('{label}' => $this->label, '{types}' => implode(' ', $typesResults)));
- }
- /**
- * Returns the total of types
- * @return int holds
- */
- protected function getTotal()
- {
- if (null == $this->_total) {
- $this->_total = 0;
- foreach ($this->types as $type) {
- if (isset($type['value'])) {
- $this->_total += $type['value'];
- }
- }
- }
- return $this->_total;
- }
- }
- /**
- * TbPercentOfTypeGooglePieOperation class
- *
- * Displays a Google visualization pie chart based on the percentage count of type.
- */
- class TbPercentOfTypeGooglePieOperation extends TbPercentOfTypeOperation
- {
- /**
- * @var string $chartCssClass the class name of the layer holding the chart
- */
- public $chartCssClass = 'bootstrap-operation-google-pie-chart';
- /**
- * The options
- * @var array $chartOptions
- * @see https://google-developers.appspot.com/chart/interactive/docs/gallery/piechart
- */
- public $chartOptions = array(
- 'title' => 'Google Pie Chart'
- );
- /**
- * @var array $data the configuration data of the chart
- */
- protected $data = array();
- /**
- * @see TbOperation
- * @return mixed|void
- */
- public function displaySummary()
- {
- $this->data[] = array('Label', 'Percent');
- foreach ($this->types as $type) {
- if (!isset($type['value'])) {
- $type['value'] = 0;
- }
- $this->data[] = $this->getTotal() ? array(
- $type['label'],
- (float)number_format(($type['value'] / $this->getTotal()) * 100, 1)
- ) : 0;
- }
- $data = CJavaScript::jsonEncode($this->data);
- $options = CJavaScript::jsonEncode($this->chartOptions);
- echo "<div id='{$this->id}' class='{$this->chartCssClass}' data-data='{$data}' data-options='{$options}'></div>";
- $this->registerClientScript();
- }
- /**
- * Registers required scripts
- */
- public function registerClientScript()
- {
- $chart = Yii::createComponent(
- array(
- 'class' => 'bootstrap.widgets.TbGoogleVisualizationChart',
- 'visualization' => 'PieChart',
- 'containerId' => $this->getId(),
- 'data' => $this->data,
- 'options' => $this->chartOptions
- )
- );
- $chart->init();
- $chart->run();
- /**
- * create custom chart update by using the global chart variable
- * @see TbGoogleVisualizationChart
- */
- $this->column->grid->componentsAfterAjaxUpdate[__CLASS__] =
- 'var $el = $("#' . $this->getId() . '");var data = $el.data("data");var opts = $el.data("options");
- data = google.visualization.arrayToDataTable(data);
- ' . $chart->getId() . '=new google.visualization.PieChart(document.getElementById("' . $this->getId() . '"));
- ' . $chart->getId() . '.draw(data,opts);';
- }
- }
- /**
- * TbPercentOfTypeEasyPieOperation class
- *
- * Displays an chart based on jquery.easy.pie plugin
- */
- class TbPercentOfTypeEasyPieOperation extends TbPercentOfTypeOperation
- {
- /**
- * @var string $chartCssClass the class of the layer containing the class
- */
- public $chartCssClass = 'bootstrap-operation-easy-pie-chart';
- /**
- * @var string $template
- * @see TbOperation
- */
- public $template = '<div style="clear:both">{label}: </div>{types}';
- /**
- * @var string $typeTemplate
- * @see parent class
- */
- public $typeTemplate = '<div style="float:left;text-align:center;margin:2px"><div class="{class}" data-percent="{value}">{value}%</div><div>{label}</div></div>';
- // easy-pie-chart plugin options
- // @see https://github.com/rendro/easy-pie-chart#configuration-parameter
- public $chartOptions = array(
- 'barColor' => '#ef1e25',
- // The color of the curcular bar. You can pass either a css valid color string like rgb,
- // rgba hex or string colors. But you can also pass a function that accepts the current
- // percentage as a value to return a dynamically generated color.
- 'trackColor' => '#f2f2f2',
- // The color of the track for the bar, false to disable rendering.
- 'scaleColor' => '#dfe0e0',
- // The color of the scale lines, false to disable rendering.
- 'lineCap' => 'round',
- // Defines how the ending of the bar line looks like. Possible values are: butt, round and square.
- 'lineWidth' => 5,
- // Width of the bar line in px.
- 'size' => 80,
- // Size of the pie chart in px. It will always be a square.
- 'animate' => false,
- // Time in milliseconds for a eased animation of the bar growing, or false to deactivate.
- 'onStart' => 'js:$.noop',
- // Callback function that is called at the start of any animation (only if animate is not false).
- 'onStop' => 'js:$.noop'
- // Callback function that is called at the end of any animation (only if animate is not false).
- );
- /**
- * @see TbOperation
- * @return mixed|void
- */
- public function displaySummary()
- {
- $this->typeTemplate = strtr($this->typeTemplate, array('{class}' => $this->chartCssClass));
- parent::displaySummary();
- $this->registerClientScripts();
- }
- /**
- * Register required scripts
- */
- protected function registerClientScripts()
- {
- Yii::app()->bootstrap->registerAssetCss('easy-pie-chart.css');
- Yii::app()->bootstrap->registerAssetJs('jquery.easy.pie.chart.js');
- $options = CJavaScript::encode($this->chartOptions);
- Yii::app()->getClientScript()->registerScript(
- __CLASS__ . '#percent-of-type-operation-simple-pie',
- '
- $("#' . $this->column->grid->id . ' .' . $this->column->grid->extendedSummaryCssClass . ' .' . $this->chartCssClass . '")
- .easyPieChart(' . $options . ');
- '
- );
- $this->column->grid->componentsReadyScripts[__CLASS__] =
- $this->column->grid->componentsAfterAjaxUpdate[__CLASS__] =
- '$("#' . $this->column->grid->id . ' .' . $this->column->grid->extendedSummaryCssClass . ' .' . $this->chartCssClass . '")
- .easyPieChart(' . $options . ');';
- }
- }