├── .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 |
--------------------------------------------------------------------------------