├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── composer.json └── src ├── actions └── ColumnUpdateAction.php ├── assets ├── Asset.php └── dist │ ├── advanced-grid.css │ └── advanced-grid.js └── widgets ├── DifferenceColumn.php ├── InputColumn.php ├── MultifieldColumn.php ├── ProgressColumn.php └── ToggleColumn.php /.gitignore: -------------------------------------------------------------------------------- 1 | # phpstorm project files 2 | .idea 3 | 4 | # netbeans project files 5 | nbproject 6 | 7 | # zend studio for eclipse project files 8 | .buildpath 9 | .project 10 | .settings 11 | 12 | # windows thumbnail cache 13 | Thumbs.db 14 | 15 | # composer vendor dir 16 | /vendor 17 | 18 | # composer itself is not needed 19 | composer.phar 20 | 21 | # Mac DS_Store Files 22 | .DS_Store 23 | 24 | # phpunit itself is not needed 25 | phpunit.phar 26 | # local phpunit config 27 | /phpunit.xml 28 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | Yii2 Advanced Grid Change Log 2 | ============================= 3 | 4 | 1.1.0 Under development 5 | ----------------------- 6 | 7 | 1.0.1 September 19, 2016 8 | ------------------------ 9 | 10 | - Fix #2: Division by zero fix (al.gushchin) 11 | 12 | 1.0.0 September 18, 2016 13 | ------------------------ 14 | 15 | - Initial release -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | Copyright (c) 2015, Yiister 2 | All rights reserved. 3 | 4 | Redistribution and use in source and binary forms, with or without 5 | modification, are permitted provided that the following conditions are met: 6 | 7 | * Redistributions of source code must retain the above copyright notice, this 8 | list of conditions and the following disclaimer. 9 | 10 | * Redistributions in binary form must reproduce the above copyright notice, 11 | this list of conditions and the following disclaimer in the documentation 12 | and/or other materials provided with the distribution. 13 | 14 | * Neither the name of qwe nor the names of its 15 | contributors may be used to endorse or promote products derived from 16 | this software without specific prior written permission. 17 | 18 | THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" 19 | AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE 20 | IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE 21 | DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE 22 | FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL 23 | DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 24 | SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER 25 | CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, 26 | OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE 27 | OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 28 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Yii2 Advanced Grid 2 | ================== 3 | 4 | The extension provides different columns for `yii\grid\GridView` widget. 5 | 6 | You can see examples at the [demo page](http://yiister.ru/projects/advanced-grid). 7 | 8 | Installation 9 | ------------ 10 | 11 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/). 12 | 13 | Either run 14 | 15 | ``` 16 | composer require --prefer-dist yiister/yii2-advanced-grid 17 | ``` 18 | 19 | or add 20 | 21 | ```json 22 | "yiister/yii2-advanced-grid": "~1.0" 23 | ``` 24 | 25 | to the `require` section of your composer.json. 26 | -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "yiister/yii2-advanced-grid", 3 | "description": "Advanced GridView extension for Yii framework 2", 4 | "keywords": [ 5 | "yii2", 6 | "utils", 7 | "extension", 8 | "grid", 9 | "grid-view", 10 | "column" 11 | ], 12 | "homepage": "https://github.com/yiister/yii2-advanced-grid", 13 | "type": "yii2-extension", 14 | "license": "BSD-3-Clause", 15 | "authors": [ 16 | { 17 | "name": "Pavel Fedotov", 18 | "email": "fps.06@mail.ru", 19 | "homepage": "https://github.com/fps01", 20 | "role": "Creator" 21 | } 22 | ], 23 | "support": { 24 | "issues": "https://github.com/yiister/yii2-advanced-grid/issues", 25 | "source": "https://github.com/yiister/yii2-advanced-grid" 26 | }, 27 | "require": { 28 | "php": ">=5.4.0", 29 | "yiisoft/yii2": "~2.0.0", 30 | "yiisoft/yii2-bootstrap": "~2.0.0" 31 | }, 32 | "autoload": { 33 | "psr-4": { 34 | "yiister\\grid\\": "src/" 35 | } 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /src/actions/ColumnUpdateAction.php: -------------------------------------------------------------------------------- 1 | ['is_active'], 27 | * 'app\models\OrderStatus' => ['is_system', 'is_active', 'sort_order'], 28 | * ] 29 | */ 30 | public $allowedAttributes = []; 31 | 32 | /** 33 | * @return array 34 | * @throws BadRequestHttpException 35 | * @throws NotFoundHttpException 36 | */ 37 | public function run() 38 | { 39 | $className = Yii::$app->request->post('model'); 40 | $attribute = Yii::$app->request->post('attribute'); 41 | $id = Yii::$app->request->post('id'); 42 | $value = Yii::$app->request->post('value'); 43 | if ($className === null || $attribute === null || $id === null || $value === null) { 44 | throw new BadRequestHttpException('Missing required parameters: model, attribute, id, value'); 45 | } 46 | Yii::$app->response->format = Response::FORMAT_JSON; 47 | if (isset($this->allowedAttributes[$className]) === false 48 | || in_array($attribute, $this->allowedAttributes[$className]) === false 49 | ) { 50 | throw new BadRequestHttpException; 51 | } 52 | if (null === ($model = $className::find()->where(['id' => $id])->one())) { 53 | throw new NotFoundHttpException; 54 | } 55 | /** @var ActiveRecord $model */ 56 | $model->$attribute = $value; 57 | return [ 58 | 'status' => $model->save(true, [$attribute]), 59 | 'message' => implode("\n", $model->getErrors($attribute)), 60 | ]; 61 | } 62 | } 63 | -------------------------------------------------------------------------------- /src/assets/Asset.php: -------------------------------------------------------------------------------- 1 | 0) { 36 | that.successCallback($input, data); 37 | $input.data('old-value', $input.val()); 38 | } else { 39 | that.errorCallback($input, data.message); 40 | } 41 | } 42 | }); 43 | } 44 | this.successCallback($input); 45 | }, 46 | 'errorCallback': function ($input, errorMessage) { 47 | $input.parents('.form-group').eq(0).addClass('has-error'); 48 | if (typeof console.log != 'undefined') { 49 | console.log(errorMessage); 50 | } 51 | }, 52 | 'successCallback': function ($input, data) { 53 | $input.parents('.form-group').eq(0).addClass('has-success'); 54 | } 55 | }; 56 | ToggleColumn = { 57 | 'init': function () { 58 | var that = this; 59 | jQuery('body').on('change', '[data-action="toggle-column"] input', function () { 60 | var $input = jQuery(this); 61 | var $wrapper = $input.parents('[data-action="toggle-column"]').eq(0); 62 | jQuery.ajax({ 63 | 'data': { 64 | 'attribute': $wrapper.data('attribute'), 65 | 'model': $wrapper.data('model'), 66 | 'id': $wrapper.data('id'), 67 | 'value': $input.attr('value') 68 | }, 69 | 'dataType': 'json', 70 | 'error': function (error) { 71 | that.errorCallback($input, error.message) 72 | }, 73 | 'type': 'post', 74 | 'success': function (data) { 75 | if (data.status > 0) { 76 | that.successCallback($input, data); 77 | } else { 78 | that.errorCallback($input, data.message); 79 | } 80 | }, 81 | 'url': $wrapper.data('url') 82 | }); 83 | }); 84 | }, 85 | 'errorCallback': function ($input, errorMessage) { 86 | if (typeof console.log != 'undefined') { 87 | console.log(errorMessage); 88 | } 89 | }, 90 | 'successCallback': function ($input, data) { 91 | $input.parents('[data-action="toggle-column"]').eq(0).find('label').removeClass('active').removeClass('btn-primary').addClass('btn-default').find('input').removeProp('checked'); 92 | $input.prop('checked', 'checked').parents('label').eq(0).addClass('btn-primary'); 93 | } 94 | }; 95 | -------------------------------------------------------------------------------- /src/widgets/DifferenceColumn.php: -------------------------------------------------------------------------------- 1 | \yiister\grid\widgets\DifferenceColumn::className(), 17 | * 'attribute' => 'price', 18 | * 'difference' => function ($model, $column) { 19 | * return $model->price - $model->old_price; 20 | * }, 21 | * 'differenceInPercents' => true, 22 | * 'template' => '{value} {difference}%', 23 | * ], 24 | * @package yiister\grid\widgets 25 | */ 26 | class DifferenceColumn extends DataColumn 27 | { 28 | /** 29 | * @var string|callable the string output template or the render callable 30 | * The string allows `{value}` and `{difference}` placeholders that will be replaced to real values. 31 | * Example: 32 | * 'template' => '{value} {difference}%' 33 | * 34 | * The callable gets two parameters (`$model` and `$column`) and has to return output string. 35 | * Example: 36 | * 'template' => function ($model, $column) { 37 | * $diff = $model->price - $model->old_price; 38 | * return $model->price . \yii\helpers\Html::tag( 39 | * 'span', 40 | * $diff, 41 | * ['class' => 'label label-' . ($diff < 0 ? 'success' : 'warning')] 42 | * ); 43 | * }, 44 | */ 45 | public $template = '{value} {difference}'; 46 | 47 | /** 48 | * @var string|callable the difference attribute name or the calculation callable 49 | * Attribute example: 50 | * 'difference' => 'price_diff', 51 | * 52 | * Callable example: 53 | * 'difference' => function ($model, $column) { 54 | * return $model->price - $model->old_price; 55 | * }, 56 | */ 57 | public $difference; 58 | 59 | /** 60 | * @var bool whether to show difference in percents 61 | */ 62 | public $differenceInPercents = false; 63 | 64 | /** 65 | * Render the difference value 66 | * @param $value float 67 | * @return string 68 | */ 69 | protected function renderDifference($value) 70 | { 71 | if ($value === false) { 72 | '⚠'; 73 | } 74 | return ($value < 0 ? '↓' : '↑') . ' ' . (int) $value; 75 | } 76 | 77 | /** 78 | * Calculate difference 79 | * @param $model mixed 80 | * @param $difference float 81 | * @return float 82 | */ 83 | protected function getDifferenceInPercents($model, $difference) 84 | { 85 | $value = $model->{$this->attribute}; 86 | $oldValue = $value - $difference; 87 | if ($oldValue <= 0) { 88 | return false; 89 | } 90 | return $value * 100 / $oldValue - 100; 91 | } 92 | 93 | /** 94 | * @inheritdoc 95 | */ 96 | protected function renderDataCellContent($model, $key, $index) 97 | { 98 | $differenceValue = is_callable($this->difference) 99 | ? call_user_func($this->difference, $model, $this) 100 | : $model->{$this->difference}; 101 | if (is_callable($this->template)) { 102 | return call_user_func($this->template, $model, $this); 103 | } 104 | return strtr( 105 | $this->template, 106 | [ 107 | '{value}' => $model->{$this->attribute}, 108 | '{difference}' => $this->renderDifference( 109 | $this->differenceInPercents 110 | ? $this->getDifferenceInPercents($model, $differenceValue) 111 | : $differenceValue 112 | ), 113 | ] 114 | ); 115 | } 116 | } 117 | -------------------------------------------------------------------------------- /src/widgets/InputColumn.php: -------------------------------------------------------------------------------- 1 | \yiister\grid\widgets\InputColumn::className(), 20 | * 'attribute' => 'price', 21 | * 'updateAction' => '/projects/column-update', 22 | * ], 23 | * @package yiister\grid\widgets 24 | */ 25 | class InputColumn extends DataColumn 26 | { 27 | const SIZE_LARGE = 'input-lg'; 28 | const SIZE_DEFAULT = ''; 29 | const SIZE_SMALL = 'input-sm'; 30 | 31 | /** 32 | * @var array|string the update action route 33 | */ 34 | public $updateAction = ['/site/column-update']; 35 | 36 | /** 37 | * @var string the input size 38 | */ 39 | public $size = 'input-sm'; 40 | 41 | /** 42 | * @inheritdoc 43 | */ 44 | public function init() 45 | { 46 | Asset::register($this->grid->view); 47 | $this->grid->view->registerJs("InputColumn.init();"); 48 | } 49 | 50 | /** 51 | * @inheritdoc 52 | */ 53 | protected function renderDataCellContent($model, $key, $index) 54 | { 55 | return Html::tag( 56 | 'div', 57 | Html::textInput( 58 | Html::getInputName($model, $this->attribute), 59 | $model->{$this->attribute}, 60 | [ 61 | 'class' => 'form-control ' . $this->size, 62 | 'data-action' => 'input-column', 63 | 'data-attribute' => $this->attribute, 64 | 'data-id' => $model->id, 65 | 'data-model' => get_class($model), 66 | 'data-url' => $this->updateAction, 67 | ] 68 | ), 69 | [ 70 | 'class' => 'form-group input-column-form-group', 71 | ] 72 | ); 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/widgets/MultifieldColumn.php: -------------------------------------------------------------------------------- 1 | \yiister\grid\widgets\MultifieldColumn::className(), 20 | * 'attribute' => 'name', 21 | * 'label' => 'Name + slug', 22 | * 'attributes' => ['slug'], 23 | * 'template' => '{name}
{slug}', 24 | * ], 25 | * @package yiister\grid\widgets 26 | */ 27 | class MultifieldColumn extends DataColumn 28 | { 29 | /** 30 | * @var string[] the secondary attribute names array 31 | */ 32 | public $attributes = []; 33 | 34 | /** 35 | * @var string|callable the string output template or the render callable 36 | * The string allows all attribute names from `$attributes` array and from `$attribute` string placed between `{` and `}` 37 | * Example: 38 | * 'template' => '{name}
{slug}', 39 | * 40 | * The callable gets two parameters (`$model` and `$column`) and has to return output string. 41 | * Example: 42 | * 'template' => function ($model, $column) { 43 | * $attributeValues = []; 44 | * foreach ($column->attributes as $attribute) { 45 | * $attributeValues[] = $model->{$attribute}; 46 | * } 47 | * return $model->{$column->attribute} . Html::tag('small', implode('
', $attributeValues)); 48 | * }, 49 | */ 50 | public $template; 51 | 52 | /** 53 | * @inheritdoc 54 | */ 55 | public function init() 56 | { 57 | if (is_callable($this->template) && is_string($this->template)) { 58 | throw new InvalidParamException('Unknown template. You can use a string or a callback.'); 59 | } 60 | if (empty($this->template)) { 61 | $this->template = function (ActiveRecord $model, MultifieldColumn $column) { 62 | $attributeValues = []; 63 | foreach ($column->attributes as $attribute) { 64 | $attributeValues[] = $model->{$attribute}; 65 | } 66 | return $model->{$column->attribute} . Html::tag('small', implode('
', $attributeValues)); 67 | }; 68 | } 69 | } 70 | 71 | /** 72 | * @inheritdoc 73 | */ 74 | protected function renderDataCellContent($model, $key, $index) 75 | { 76 | if (is_callable($this->template)) { 77 | return call_user_func($this->template, $model, $this); 78 | } 79 | $pairs = ['{' . $this->attribute . '}' => $model->{$this->attribute}]; 80 | foreach ($this->attributes as $attribute) { 81 | $pairs['{' . $attribute . '}'] = $model->$attribute; 82 | } 83 | return strtr($this->template, $pairs); 84 | } 85 | } 86 | -------------------------------------------------------------------------------- /src/widgets/ProgressColumn.php: -------------------------------------------------------------------------------- 1 | \yiister\grid\widgets\ProgressColumn::className(), 19 | * 'attribute' => 'reserved', 20 | * 'size' => \yiister\grid\widgets\ProgressColumn::SIZE_LARGE, 21 | * 'isStriped' => true, 22 | * 'progressBarClass' => function ($model, $column) { 23 | * return $model->{$column->attribute} > 15 24 | * ? \yiister\grid\widgets\ProgressColumn::STYLE_SUCCESS 25 | * : \yiister\grid\widgets\ProgressColumn::STYLE_WARNING; 26 | * }, 27 | * ], 28 | * @package yiister\grid\widgets 29 | */ 30 | class ProgressColumn extends DataColumn 31 | { 32 | /** 33 | * Size constants 34 | */ 35 | const SIZE_LARGE = 'progress-large'; 36 | const SIZE_DEFAULT = 'progress-default'; 37 | const SIZE_MEDIUM = 'progress-medium'; 38 | const SIZE_SMALL = 'progress-small'; 39 | 40 | /** 41 | * Style constants 42 | */ 43 | const STYLE_SUCCESS = 'progress-bar-success'; 44 | const STYLE_INFO = 'progress-bar-info'; 45 | const STYLE_WARNING = 'progress-bar-warning'; 46 | const STYLE_DANGER = 'progress-bar-danger'; 47 | 48 | /** 49 | * @var string the internal progress bar class 50 | */ 51 | private $_progressBarClass = 'progress-bar'; 52 | 53 | /** 54 | * @var bool whether to show a percents instead of an attribute value 55 | */ 56 | public $percent = true; 57 | 58 | /** 59 | * @var int the minimum attribute value 60 | */ 61 | public $minValue = 0; 62 | 63 | /** 64 | * @var int the maximum attribute value 65 | */ 66 | public $maxValue = 100; 67 | 68 | /** 69 | * @var bool whether to show the text 70 | */ 71 | public $showText = true; 72 | 73 | /** 74 | * @var string the progress bar size 75 | */ 76 | public $size = 'progress-default'; 77 | 78 | /** 79 | * @var string|callback the progress bar class 80 | * You may set a fixed progress bar class for all rows via string or a dynamic class via callback. 81 | * Callback function gets two parameters: ActiveRecord model and GridView column. 82 | * Static class example: 83 | * 'progressBarClass' => \yiister\grid\widgets\ProgressColumn::STYLE_DANGER, 84 | * 85 | * Dynamic class example: 86 | * 'progressBarClass' => function ($model, $column) { 87 | * return $model->{$column->attribute} > 15 88 | * ? \yiister\grid\widgets\ProgressColumn::STYLE_SUCCESS 89 | * : \yiister\grid\widgets\ProgressColumn::STYLE_WARNING; 90 | * }, 91 | */ 92 | public $progressBarClass; 93 | 94 | /** 95 | * @var bool whether to stripe the progress bar 96 | */ 97 | public $isStriped = false; 98 | 99 | /** 100 | * @var bool whether to animate the progress bar 101 | */ 102 | public $isAnimated = false; 103 | 104 | /** 105 | * @inheritdoc 106 | */ 107 | public function init() 108 | { 109 | Asset::register($this->grid->view); 110 | Html::addCssClass($this->options, ['progress', $this->size]); 111 | if ($this->isAnimated) { 112 | $this->_progressBarClass .= ' active'; 113 | $this->isStriped = true; 114 | } 115 | if ($this->isStriped) { 116 | $this->_progressBarClass .= ' progress-bar-striped'; 117 | } 118 | } 119 | 120 | /** 121 | * @inheritdoc 122 | */ 123 | protected function renderDataCellContent($model, $key, $index) 124 | { 125 | $percents = ($model->{$this->attribute} - $this->minValue) * 100 / ($this->maxValue - $this->minValue); 126 | $progressBarClass = $this->_progressBarClass . ' ' . (is_callable($this->progressBarClass) 127 | ? call_user_func($this->progressBarClass, $model, $this) 128 | : $this->progressBarClass); 129 | return Html::tag( 130 | 'div', 131 | Html::tag( 132 | 'div', 133 | $this->showText ? Html::tag('span', $this->percent ? $percents . '%' : $model->{$this->attribute}) : '', 134 | [ 135 | 'class' => $progressBarClass, 136 | 'style' => [ 137 | 'width' => $percents . '%', 138 | ], 139 | ] 140 | ), 141 | $this->options 142 | ); 143 | } 144 | } 145 | -------------------------------------------------------------------------------- /src/widgets/ToggleColumn.php: -------------------------------------------------------------------------------- 1 | \yiister\grid\widgets\ToggleColumn::className(), 20 | * 'attribute' => 'is_active', 21 | * 'updateAction' => '/projects/column-update', 22 | * ] 23 | * @package yiister\grid\widgets 24 | */ 25 | class ToggleColumn extends DataColumn 26 | { 27 | /** 28 | * @var array|string the update action route 29 | */ 30 | public $updateAction = ['/site/column-update']; 31 | 32 | /** 33 | * @var array of values to rendering 34 | * Data format: 35 | * [ 36 | * 'value_one' => 'The first label', 37 | * 'value_two' => 'The second label', 38 | * ] 39 | */ 40 | public $buttons = [ 41 | 0 => 'Off', 42 | 1 => 'On', 43 | ]; 44 | 45 | /** 46 | * @inheritdoc 47 | */ 48 | public function init() 49 | { 50 | Asset::register($this->grid->view); 51 | $this->grid->view->registerJs("ToggleColumn.init();"); 52 | } 53 | 54 | /** 55 | * @inheritdoc 56 | */ 57 | protected function renderDataCellContent($model, $key, $index) 58 | { 59 | $items = ''; 60 | foreach ($this->buttons as $value => $label) { 61 | $items .= Html::label( 62 | Html::radio(null, $model->{$this->attribute} == $value, ['value' => $value]) . $label, 63 | $model->{$this->attribute} == $value, 64 | [ 65 | 'class' => 'btn ' . ($model->{$this->attribute} == $value ? 'btn-primary' : 'btn-default'), 66 | ] 67 | ); 68 | } 69 | return Html::tag( 70 | 'div', 71 | $items, 72 | [ 73 | 'data-action' => 'toggle-column', 74 | 'data-attribute' => $this->attribute, 75 | 'data-id' => $model->id, 76 | 'data-model' => get_class($model), 77 | 'data-url' => Url::to($this->updateAction), 78 | 'data-toggle' => 'buttons', 79 | 'class' => 'btn-group-xs btn-group', 80 | ] 81 | ); 82 | } 83 | } 84 | --------------------------------------------------------------------------------