Permalink
Cannot retrieve contributors at this time
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
1413 lines (1295 sloc)
48.7 KB
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<?php | |
/** | |
* @package yii2-dynagrid | |
* @author Kartik Visweswaran <kartikv2@gmail.com> | |
* @copyright Copyright © Kartik Visweswaran, Krajee.com, 2015 - 2022 | |
* @version 1.5.4 | |
*/ | |
namespace kartik\dynagrid; | |
use Exception; | |
use kartik\base\Config; | |
use kartik\base\Lib; | |
use kartik\base\Widget; | |
use kartik\dialog\Dialog; | |
use kartik\dynagrid\models\DynaGridConfig; | |
use kartik\dynagrid\models\DynaGridSettings; | |
use kartik\grid\GridView; | |
use Yii; | |
use yii\base\InvalidConfigException; | |
use yii\base\Model; | |
use yii\data\ActiveDataProvider; | |
use yii\data\ArrayDataProvider; | |
use yii\data\BaseDataProvider; | |
use yii\data\DataProviderInterface; | |
use yii\data\Sort; | |
use yii\data\SqlDataProvider; | |
use yii\db\ActiveQuery; | |
use yii\db\ActiveQueryInterface; | |
use yii\db\QueryInterface; | |
use yii\helpers\ArrayHelper; | |
use yii\helpers\Html; | |
use yii\helpers\Inflector; | |
use yii\helpers\Json; | |
/** | |
* Enhance GridView by allowing you to dynamically edit grid configuration. The dynagrid allows you to set your own grid | |
* theme, pagesize, and column order/display settings. The widget allows you to manage the order and visibility of | |
* columns dynamically at runtime. It also allows you to save this configuration or retrieve the saved configuration | |
* to/from session, cookie, or database. | |
* | |
* @author Kartik Visweswaran <kartikv2@gmail.com> | |
* @since 1.0 | |
*/ | |
class DynaGrid extends Widget | |
{ | |
use DynaGridTrait; | |
/** | |
* @var string session storage type | |
*/ | |
const TYPE_SESSION = 'session'; | |
/** | |
* @var string cookie storage type | |
*/ | |
const TYPE_COOKIE = 'cookie'; | |
/** | |
* @var string database storage type | |
*/ | |
const TYPE_DB = 'db'; | |
/** | |
* @var string fix column to the left | |
*/ | |
const ORDER_FIX_LEFT = 'fixleft'; | |
/** | |
* @var string fix column to the right | |
*/ | |
const ORDER_FIX_RIGHT = 'fixright'; | |
/** | |
* @var string fix column to the middle | |
*/ | |
const ORDER_MIDDLE = 'middle'; | |
/** | |
* @var string the module identifier if this widget is part of a module. If not set, the module identifier will | |
* be auto derived based on the \yii\base\Module::getInstance method. This can be useful, if you are setting | |
* multiple module identifiers for the same module in your Yii configuration file. To specify children or grand | |
* children modules you can specify the module identifiers relative to the parent module (e.g. `admin/content`). | |
*/ | |
public $moduleId; | |
/** | |
* @var string the type of storage for the dynagrid configuration. | |
* - [[DynaGrid::TYPE_SESSION]]: Save the config in a session variable for the current session. | |
* - [[DynaGrid::TYPE_COOKIE]]: Save the config in a cookie for retrieval. You need to setup the | |
* [[Module::cookieSettings]] property to control the cookie expiry and other settings. | |
* - [[DynaGrid::TYPE_DB]]: Save the config to a database. You need to setup the [[Module::dbSettings]] | |
* property to setup the database table and attributes for storage. | |
*/ | |
public $storage; | |
/** | |
* @var string the initial grid theme to be set | |
*/ | |
public $theme; | |
/** | |
* @var boolean whether settings are stored specific to each user | |
*/ | |
public $userSpecific; | |
/** | |
* @var boolean whether to update only the name, when editing and saving a filter or sort. This is applicable | |
* only for [[$storage]] set to [[Dynagrid::TYPE_DB]]. If set to `false`, it will also overwrite the current | |
* `filter` or `sort` settings. | |
*/ | |
public $dbUpdateNameOnly = false; | |
/** | |
* @var boolean whether to show the personalize button group. Defaults to `true`. | |
*/ | |
public $showPersonalize = true; | |
/** | |
* @var boolean whether to show the filter save widget button. Defaults to `true`. | |
*/ | |
public $showFilter = true; | |
/** | |
* @var boolean whether to show the sort save widget button. Defaults to `true`. | |
*/ | |
public $showSort = true; | |
/** | |
* @var boolean whether to enable multiple sort. Defaults to `true`. | |
*/ | |
public $enableMultiSort = true; | |
/** | |
* @var boolean whether to allow setup of the pagination. Defaults to `true`. | |
*/ | |
public $allowPageSetting = true; | |
/** | |
* @var boolean whether to allow display/setup of the theme. Defaults to `true`. | |
*/ | |
public $allowThemeSetting = true; | |
/** | |
* @var boolean whether to allow display/setup of the filter in the personalize grid form | |
*/ | |
public $allowFilterSetting = true; | |
/** | |
* @var boolean whether to allow display/setup of the sort in the personalize grid form | |
*/ | |
public $allowSortSetting = true; | |
/** | |
* @var array widget options for \kartik\widgets\GridView that will be rendered by the DynaGrid widget | |
*/ | |
public $gridOptions; | |
/** | |
* @var boolean whether the DynaGrid configuration button class should match the grid panel style. | |
*/ | |
public $matchPanelStyle; | |
/** | |
* @var array the HTML attributes for the dynagrid personalize toggle button which will render the DynaGrid | |
* configuration form within a Bootstrap Modal container. | |
*/ | |
public $toggleButtonGrid; | |
/** | |
* @var array the HTML attributes for the filter configuration button which will render the Filter settings form | |
* within a Bootstrap Modal container. | |
*/ | |
public $toggleButtonFilter; | |
/** | |
* @var array the HTML attributes for the sort configuration button which will render the Sort settings form | |
* within a Bootstrap Modal container. | |
*/ | |
public $toggleButtonSort; | |
/** | |
* @var array HTML options for the DynaGrid widget | |
*/ | |
public $options; | |
/** | |
* @var array the sortable widget options | |
*/ | |
public $sortableOptions; | |
/** | |
* @var array the HTML attributes for the sortable columns header | |
*/ | |
public $sortableHeader = ['class' => 'alert alert-info dynagrid-sortable-header']; | |
/** | |
* @var array the grid columns configuration | |
*/ | |
public $columns; | |
/** | |
* @var string the message to display after applying and submitting the configuration and until refreshed grid is | |
* reloaded | |
*/ | |
public $submitMessage; | |
/** | |
* @var string the message to display after deleting the configuration and | |
* until refreshed grid is reloaded | |
*/ | |
public $deleteMessage; | |
/** | |
* @var array HTML attributes for the submission message container | |
*/ | |
public $messageOptions; | |
/** | |
* @var string the confirmation warning message before deleting a personalization configuration or setting. | |
*/ | |
public $deleteConfirmation; | |
/** | |
* @var array configuration settings for the Krajee dialog widget that will be used to render alerts and | |
* confirmation dialog prompts | |
* @see http://demos.krajee.com/dialog | |
*/ | |
public $krajeeDialogSettings = []; | |
/** | |
* @var array the HTML attributes for the save/apply action button. If this is set to `false`, it will not be | |
* displayed. The following special variables are supported: | |
* - `icon`: _string_, the icon class suffix for the button. Defaults to `save`. | |
* - `prefix`: _string_, the icon class prefix for the button. Defaults to `glyphicon glyphicon-` for [[bsVersion]] | |
* set to `3.x` and `fas fa-` for [[bsVersion]] set to `4.x`. | |
* - `label`: _string_, the label for the action button. Defaults to empty string. | |
* - `title`: _string_, the title for the action button. Defaults to `Save grid settings`. | |
*/ | |
public $submitButtonOptions = []; | |
/** | |
* @var array|boolean the HTML attributes for the reset action button. If this is set to `false`, it will not be | |
* displayed. The following special variables are supported: | |
* - `icon`: _string_, the icon class suffix for the button. Defaults to `repeat` for [[bsVersion]] | |
* set to `3.x` and `redo` for [[bsVersion]] set to `4.x`. | |
* - `prefix`: _string_, the icon class prefix for the button. Defaults to `glyphicon glyphicon-` for [[bsVersion]] | |
* set to `3.x` and `fas fa-` for [[bsVersion]] set to `4.x`. | |
* - `label`: _string_, the label for the action button. Defaults to empty string. | |
* - `title`: _string_, the title for the action button. Defaults to `Abort any changes and reset settings`. | |
*/ | |
public $resetButtonOptions = []; | |
/** | |
* @var array|boolean the HTML attributes for the delete/trash action button. If this is set to `false`, it will | |
* not be displayed. The following special variables are supported: | |
* - `icon`: _string_, the icon class suffix for the button. Defaults to `trash` for [[bsVersion]] | |
* set to `3.x` and `trash-alt` for [[bsVersion]] set to `4.x`. | |
* - `prefix`: _string_, the icon class prefix for the button. Defaults to `glyphicon glyphicon-` for [[bsVersion]] | |
* set to `3.x` and `fas fa-` for [[bsVersion]] set to `4.x`. | |
* - `label`: _string_, the label for the action button. Defaults to empty string. | |
* - `title`: _string_, the title for the action button. Defaults to `Remove saved grid settings`. | |
*/ | |
public $deleteButtonOptions = []; | |
/** | |
* @var string the icon that will be displayed on the dynagrid personalize button as well as the personalize modal | |
* dialog header. This is not HTML encoded. Defaults to `<i class="glyphicon glyphicon-wrench"></i>` for | |
* [[bsVersion]] set to `3.x` and `<i class="fas fa-fw fa-wrench"></i>` for [[bsVersion]] set to `4.x`. | |
*/ | |
public $iconPersonalize; | |
/** | |
* @var string the icon that will be displayed as the label for grid filter personalization button. This is not | |
* HTML encoded. Defaults to `<i class="glyphicon glyphicon-filter"></i>` for [[bsVersion]] set to `3.x` and | |
* `<i class="fas fa-fw fa-filter"></i>` for [[bsVersion]] set to `4.x`. | |
*/ | |
public $iconFilter; | |
/** | |
* @var string the icon that will be displayed as the label for grid sort personalization button. This is not | |
* HTML encoded. Defaults to `<i class="glyphicon glyphicon-sort"></i>` for [[bsVersion]] set to `3.x` and | |
* `<i class="fas fa-fw fa-sort"></i>` for [[bsVersion]] set to `4.x`. | |
*/ | |
public $iconSort; | |
/** | |
* @var string the icon for the save button within the dynagrid configuration form. This is not HTML encoded. | |
* Defaults to `<i class="glyphicon glyphicon-save"></i>` for [[bsVersion]] set to `3.x` and | |
* `<i class="fas fa-save"></i>` for [[bsVersion]] set to `4.x`. | |
*/ | |
public $iconConfirm; | |
/** | |
* @var string the icon for the save button within the dynagrid configuration form. This is not HTML encoded. | |
* Defaults to `<i class="glyphicon glyphicon-remove"></i>` for [[bsVersion]] set to `3.x` and | |
* `<i class="fas fa-times"></i>` for [[bsVersion]] set to `4.x`. | |
*/ | |
public $iconRemove; | |
/** | |
* @var string the icon that will be displayed for each VISIBLE column heading in the column reordering pane. | |
* This is not HTML encoded. Defaults to `<i class="glyphicon glyphicon-eye-open"></i>` for [[bsVersion]] | |
* set to `3.x` and `<i class="fas fa-eye"></i>` for [[bsVersion]] set to `4.x`. | |
*/ | |
public $iconVisibleColumn; | |
/** | |
* @var string the icon that will be displayed for each HIDDEN column heading in the column reordering pane. | |
* This is not HTML encoded. Defaults to `<i class="glyphicon glyphicon-eye-close"></i>` for [[bsVersion]] | |
* set to `3.x` and `<i class="fas fa-eye-slash"></i>` for [[bsVersion]] set to `4.x`. | |
*/ | |
public $iconHiddenColumn; | |
/** | |
* @var string the icon that will be displayed separating the visible and hidden columns in the column reordering | |
* pane. This is not HTML encoded. Defaults to `<i class="glyphicon glyphicon-resize-horizontal"></i>` for | |
* [[bsVersion]] set to `3.x` and `<i class="fas fa-arrows-alt-h"></i>` for [[bsVersion]] set to `4.x`. | |
*/ | |
public $iconSortableSeparator; | |
/** | |
* @var array the cached columns configuration | |
*/ | |
protected $_columns = []; | |
/** | |
* @var array the user configured visible widget columns | |
*/ | |
protected $_visibleColumns = []; | |
/** | |
* @var array the hidden widget columns for user configuration | |
*/ | |
protected $_hiddenColumns = []; | |
/** | |
* @var array the stored visible keys | |
*/ | |
protected $_visibleKeys = []; | |
/** | |
* @var integer the grid pagesize | |
*/ | |
protected $_pageSize; | |
/** | |
* @var integer the grid filter id | |
*/ | |
protected $_filterId = null; | |
/** | |
* @var integer the grid sort id | |
*/ | |
protected $_sortId = null; | |
/** | |
* @var array the dynagrid detail configuration settings (for filter and sort) | |
*/ | |
protected $_detailConfig = []; | |
/** | |
* @var Module the current module | |
*/ | |
protected $_module; | |
/** | |
* @var string request param name which will show the grid configuration submitted | |
*/ | |
protected $_requestSubmit; | |
/** | |
* @var DynaGridConfig model | |
*/ | |
protected $_model; | |
/** | |
* @var boolean flag to check if the grid configuration form has been submitted | |
*/ | |
protected $_isSubmit = false; | |
/** | |
* @var boolean flag to check if the pjax is enabled for the grid | |
*/ | |
protected $_isPjax; | |
/** | |
* @var string the identifier for the grid settings modal dialog | |
*/ | |
protected $_gridModalId; | |
/** | |
* @var string the identifier for the filter settings modal dialog | |
*/ | |
protected $_filterModalId; | |
/** | |
* @var string the identifier for the sort settings modal dialog | |
*/ | |
protected $_sortModalId; | |
/** | |
* @var string the unique element identifier for the hidden filter input | |
*/ | |
protected $_filterKey; | |
/** | |
* @var string the unique element identifier for the hidden sort input | |
*/ | |
protected $_sortKey; | |
/** | |
* @var string the identifier for pjax container | |
*/ | |
protected $_pjaxId; | |
/** | |
* @var DynaGridStore the storage instance | |
*/ | |
protected $_store; | |
/** | |
* @var array the configuration for icons set as `$key => $setting`, where `$key` is the icon property in DynaGrid | |
* widget and the `$setting` is an array of 2 values - the first value in the array is the icon suffix CSS class for | |
* Bootstrap 3.x and the second value in the array is the icon suffix CSS class for Bootstrap 4.x. | |
*/ | |
protected static $_icons = [ | |
'iconVisibleColumn' => ['eye-open', 'eye'], | |
'iconHiddenColumn' => ['eye-close', 'eye-slash'], | |
'iconSortableSeparator' => ['resize-horizontal', 'arrows-alt-h'], | |
'iconPersonalize' => ['wrench', 'wrench fa-fw'], | |
'iconFilter' => ['filter', 'filter fa-fw'], | |
'iconSort' => ['sort', 'sort fa-fw'], | |
'iconConfirm' => ['ok', 'check'], | |
'iconRemove' => ['remove', 'times'], | |
]; | |
/** | |
* Is column visible | |
* | |
* @param mixed $column | |
* | |
* @return bool | |
* @throws Exception | |
*/ | |
protected static function isVisible($column) | |
{ | |
return !(is_array($column) && !ArrayHelper::getValue($column, 'visible', true)); | |
} | |
/** | |
* Get the default action button option settings | |
* | |
* @param string $type the button type | |
* | |
* @return array the button settings | |
* @throws InvalidConfigException|Exception | |
*/ | |
protected function getDefaultButtonOptions($type) | |
{ | |
$notBs3 = !$this->isBs(3); | |
if ($type === 'submit') { | |
return [ | |
'type' => 'button', | |
'icon' => 'save', | |
'label' => Yii::t('kvdynagrid', 'Apply'), | |
'title' => Yii::t('kvdynagrid', 'Save grid settings'), | |
'class' => 'btn btn-primary', | |
'data-pjax' => false, | |
]; | |
} | |
if ($type === 'reset') { | |
return [ | |
'type' => 'reset', | |
'icon' => $notBs3 ? 'redo' : 'repeat', | |
'label' => Yii::t('kvdynagrid', 'Reset'), | |
'title' => Yii::t('kvdynagrid', 'Abort any changes and reset settings'), | |
'class' => 'btn '.$this->getDefaultBtnCss(), | |
'data-pjax' => false, | |
]; | |
} | |
if ($type === 'delete') { | |
return [ | |
'type' => 'button', | |
'icon' => $notBs3 ? 'trash-alt' : 'trash', | |
'label' => Yii::t('kvdynagrid', 'Trash'), | |
'title' => Yii::t('kvdynagrid', 'Remove saved grid settings'), | |
'class' => 'btn btn-danger', | |
'data-pjax' => false, | |
]; | |
} | |
return []; | |
} | |
/** | |
* Gets the columns for the dynagrid | |
* | |
* @return array | |
*/ | |
public function getColumns() | |
{ | |
return $this->gridOptions['columns']; | |
} | |
/** | |
* @inheritdoc | |
* @throws InvalidConfigException | |
* @throws Exception | |
*/ | |
public function run() | |
{ | |
$this->initWidget(); | |
echo Html::tag('div', GridView::widget($this->gridOptions), $this->options); | |
parent::run(); | |
} | |
/** | |
* Initialize the module based on module identifier | |
* @throws InvalidConfigException | |
*/ | |
protected function initModule() | |
{ | |
if (!isset($this->moduleId)) { | |
$this->_module = Module::getInstance(); | |
if (isset($this->_module)) { | |
$this->moduleId = $this->_module->id; | |
return; | |
} | |
$this->moduleId = Module::MODULE; | |
} | |
$this->_module = Config::getModule($this->moduleId, Module::class); | |
if (empty($this->gridOptions['bsVersion']) && isset($this->bsVersion)) { | |
$this->gridOptions['bsVersion'] = $this->bsVersion; | |
} | |
if (isset($this->_module->bsVersion) && !isset($this->bsVersion)) { | |
$this->bsVersion = $this->_module->bsVersion; | |
} | |
} | |
/** | |
* Initializes icon properties with default values | |
* @throws InvalidConfigException|Exception | |
*/ | |
protected function initIcons() | |
{ | |
$notBs3 = !$this->isBs(3); | |
$prefix = $this->getDefaultIconPrefix(); | |
foreach (static::$_icons as $icon => $setting) { | |
if (!isset($this->$icon)) { | |
$css = !$notBs3 ? $setting[0] : $setting[1]; | |
$this->$icon = Html::tag('i', '', ['class' => $prefix.$css]); | |
} | |
} | |
} | |
/** | |
* Initializes widget settings and options | |
* | |
* @throws InvalidConfigException | |
*/ | |
protected function initWidget() | |
{ | |
if (empty($this->options['id'])) { | |
throw new InvalidConfigException( | |
"You must setup a unique identifier for DynaGrid within \"options['id']\"." | |
); | |
} | |
$this->initModule(); | |
$this->_gridModalId = $this->options['id'].'-grid-modal'; | |
$this->_filterModalId = $this->options['id'].'-filter-modal'; | |
$this->_sortModalId = $this->options['id'].'-sort-modal'; | |
$this->_filterKey = $this->options['id'].'-filter-key'; | |
$this->_sortKey = $this->options['id'].'-sort-key'; | |
$this->_pjaxId = $this->options['id'].'-pjax'; | |
foreach ($this->_module->dynaGridOptions as $key => $setting) { | |
if (is_array($setting) && !empty($setting) && !empty($this->$key)) { | |
$this->$key = ArrayHelper::merge($setting, $this->$key); | |
} elseif (!isset($this->$key)) { | |
$this->$key = $setting; | |
} | |
} | |
$this->initIcons(); | |
if (empty($this->columns) || !is_array($this->columns)) { | |
throw new InvalidConfigException("The 'columns' configuration must be setup as a valid array."); | |
} | |
if (empty($this->gridOptions['dataProvider']) && empty($this->gridOptions['filterModel'])) { | |
throw new InvalidConfigException( | |
"You must setup either the gridOptions['filterModel'] or gridOptions['dataProvider']." | |
); | |
} | |
if (!empty($this->gridOptions['filterModel']) && !method_exists($this->gridOptions['filterModel'], 'search')) { | |
throw new InvalidConfigException( | |
"The gridOptions['filterModel'] must implement a 'search' method in order to apply saved filters." | |
); | |
} | |
if (empty($this->gridOptions['dataProvider'])) { | |
$this->initDataProvider($this->gridOptions['filterModel']); | |
} | |
/** @var DataProviderInterface $dataProvider */ | |
$dataProvider = $this->gridOptions['dataProvider']; | |
/** @noinspection PhpStrictComparisonWithOperandsOfDifferentTypesInspection */ | |
if ($dataProvider->getSort() === false) { | |
$this->showSort = false; | |
$this->allowSortSetting = false; | |
} | |
if ($dataProvider->getPagination() === false) { | |
$this->allowPageSetting = false; | |
} | |
if (empty($this->gridOptions['filterModel'])) { | |
$this->showFilter = false; | |
$this->allowFilterSetting = false; | |
} | |
if (empty($this->theme)) { | |
$this->theme = $this->_module->defaultTheme; | |
} | |
if (!isset($this->_pageSize) || $this->_pageSize === null) { | |
$this->_pageSize = $this->_module->defaultPageSize; | |
} | |
$this->_requestSubmit = $this->options['id'].'-dynagrid'; | |
$this->_model = new DynaGridConfig(['moduleId' => $this->moduleId]); | |
$this->_isSubmit = !empty($_POST[$this->_requestSubmit]) && $this->_model->load(Yii::$app->request->post()) && | |
$this->_model->validate(); | |
$this->_store = new DynaGridStore( | |
[ | |
'id' => $this->options['id'], | |
'moduleId' => $this->moduleId, | |
'storage' => $this->storage, | |
'userSpecific' => $this->userSpecific, | |
'dbUpdateNameOnly' => $this->dbUpdateNameOnly, | |
] | |
); | |
$this->prepareColumns(); | |
$this->configureColumns(); | |
$this->applyGridConfig(); | |
$this->_isPjax = ArrayHelper::getValue($this->gridOptions, 'pjax', false); | |
if ($this->_isPjax) { | |
$this->gridOptions['pjaxSettings']['options']['id'] = $this->_pjaxId; | |
} | |
$this->initGrid(); | |
} | |
/** | |
* Can the column be reordered | |
* | |
* @param mixed $column | |
* | |
* @return boolean | |
* @throws Exception | |
*/ | |
protected function canReorder($column) | |
{ | |
$mid = self::ORDER_MIDDLE; | |
return !(is_array($column) && ArrayHelper::getValue($column, 'order', $mid) != $mid); | |
} | |
/** | |
* Initialize the data provider | |
* | |
* @param Model $searchModel | |
*/ | |
protected function initDataProvider($searchModel) | |
{ | |
/** @noinspection PhpPossiblePolymorphicInvocationInspection */ | |
$this->gridOptions['dataProvider'] = $searchModel->search(Yii::$app->request->getQueryParams()); | |
} | |
/** | |
* Prepares the columns for the dynagrid | |
*/ | |
protected function prepareColumns() | |
{ | |
$this->_columns = $this->columns; | |
$columns = []; | |
foreach ($this->columns as $column) { | |
if (is_array($column)) { | |
unset($column['order']); | |
} | |
$columns[] = $column; | |
} | |
$this->gridOptions['columns'] = $columns; | |
} | |
/** | |
* Reconfigure columns with unique keys | |
* @throws InvalidConfigException | |
*/ | |
protected function configureColumns() | |
{ | |
$columnsByKey = []; | |
foreach ($this->_columns as $column) { | |
$columnKey = $this->getColumnKey($column); | |
for ($j = 0; true; $j++) { | |
$suffix = ($j) ? '_'.$j : ''; | |
$columnKey .= $suffix; | |
if (!array_key_exists($columnKey, $columnsByKey)) { | |
break; | |
} | |
} | |
$columnsByKey[$columnKey] = $column; | |
} | |
$this->_columns = $columnsByKey; | |
} | |
/** | |
* Generate an unique column key | |
* | |
* @param mixed $column | |
* | |
* @return false|string | |
* @throws InvalidConfigException | |
*/ | |
protected function getColumnKey($column) | |
{ | |
if (!is_array($column)) { | |
$matches = $this->matchColumnString($column); | |
$columnKey = $matches[1]; | |
} elseif (!empty($column['attribute'])) { | |
$columnKey = $column['attribute']; | |
} elseif (!empty($column['label'])) { | |
$columnKey = $column['label']; | |
} elseif (!empty($column['header'])) { | |
$columnKey = $column['header']; | |
} elseif (!empty($column['class'])) { | |
$columnKey = $column['class']; | |
} else { | |
$columnKey = null; | |
} | |
return hash('crc32', $columnKey); | |
} | |
/** | |
* Finds the matches for a string column format | |
* | |
* @param string $column | |
* | |
* @return array | |
* @throws InvalidConfigException | |
*/ | |
protected function matchColumnString($column) | |
{ | |
$matches = []; | |
if (!Lib::preg_match('/^([\w\.]+)(:(\w*))?(:(.*))?$/u', $column, $matches)) { | |
throw new InvalidConfigException( | |
"Invalid column configuration for '{$column}'. The column must be specified ". | |
"in the format of 'attribute', 'attribute:format' or 'attribute:format: label'." | |
); | |
} | |
return $matches; | |
} | |
/** | |
* Applies the current grid configuration | |
* @throws InvalidConfigException | |
*/ | |
protected function applyGridConfig() | |
{ | |
$config = $this->getGridConfig(); | |
if ($this->_isSubmit) { | |
$delete = ArrayHelper::getValue($_POST, 'deleteFlag', 0) == 1; | |
$this->saveGridConfig($config, $delete); | |
return Yii::$app->controller->refresh(); | |
} else { | |
$this->loadGridConfig($config); | |
$this->setWidgetColumns(); | |
$this->loadAttributes($this->_model); | |
} | |
$this->applyFilter(); | |
$this->applySort(); | |
$this->applyPageSize(); | |
$this->applyTheme(); | |
$this->applyColumns(); | |
return null; | |
} | |
/** | |
* Gets the current grid configuration | |
* | |
* @param boolean $current whether it is the currently set grid configuraton | |
* | |
* @return array | |
* @throws InvalidConfigException | |
*/ | |
protected function getGridConfig($current = false) | |
{ | |
if ($current) { | |
return [ | |
'page' => $this->_pageSize, | |
'keys' => $this->_visibleKeys, | |
'theme' => $this->theme, | |
'filter' => $this->_filterId, | |
'sort' => $this->_sortId, | |
]; | |
} | |
return !$this->_isSubmit ? $this->_store->fetch() : [ | |
'page' => $this->_model->pageSize, | |
'theme' => $this->_model->theme, | |
'keys' => Lib::explode(',', $_POST['visibleKeys']), | |
'filter' => $this->_model->filterId, | |
'sort' => $this->_model->sortId, | |
]; | |
} | |
/** | |
* Update configuration | |
* | |
* @param array $config the dynagrid configuration | |
* @param boolean $delete the deletion flag | |
* @throws InvalidConfigException | |
*/ | |
protected function saveGridConfig($config, $delete) | |
{ | |
if ($delete) { | |
$this->_store->delete(); | |
} else { | |
$this->_store->save($config); | |
} | |
} | |
/** | |
* Load grid configuration from specific storage | |
* | |
* @param array $config the configuration to load | |
* | |
* @throws InvalidConfigException | |
*/ | |
protected function loadGridConfig($config = []) | |
{ | |
if ($config === false) { | |
$this->_visibleKeys = []; //take visible keys from grid config | |
$this->_pageSize = $this->_module->defaultPageSize; //take pagesize from module configuration | |
foreach ($this->_columns as $key => $column) { | |
if (static::canReorder($column) && static::isVisible($column)) { | |
$this->_visibleKeys[] = $key; | |
} | |
} | |
} else { | |
$this->parseData($config); | |
} | |
} | |
/** | |
* Parses the encoded grid configuration and fetches | |
* - grid master settings: the theme, pagesize, visible keys | |
* - grid detail settings: filter and sort configuration | |
* | |
* @param array $data the stored data to be parsed | |
* | |
* @return void | |
* @throws InvalidConfigException | |
*/ | |
protected function parseData($data) | |
{ | |
if (!is_array($data) || empty($data)) { | |
return; | |
} | |
$this->_pageSize = ArrayHelper::getValue($data, 'page', $this->_module->defaultPageSize); | |
$this->theme = ArrayHelper::getValue($data, 'theme', $this->_module->defaultTheme); | |
if ($this->storage === self::TYPE_DB) { | |
$this->_filterId = $this->_store->fetch('filterAttr'); | |
$this->_sortId = $this->_store->fetch('sortAttr'); | |
} else { | |
$this->_filterId = ArrayHelper::getValue($data, DynaGridStore::STORE_FILTER, ''); | |
$this->_sortId = ArrayHelper::getValue($data, DynaGridStore::STORE_SORT, ''); | |
} | |
if (!empty($data['keys'])) { | |
$this->_visibleKeys = $data['keys']; | |
} | |
$this->parseDetailData(DynaGridStore::STORE_FILTER); | |
$this->parseDetailData(DynaGridStore::STORE_SORT); | |
} | |
/** | |
* Parses the grid detail configuration (for filter or sort). | |
* | |
* @param string $category one of 'filter' or 'sort' | |
* @throws InvalidConfigException | |
*/ | |
protected function parseDetailData($category) | |
{ | |
$dtlKey = "_{$category}Id"; | |
if (!empty($this->$dtlKey)) { | |
$store = new DynaGridStore( | |
[ | |
'id' => $this->options['id'], | |
'moduleId' => $this->moduleId, | |
'storage' => $this->storage, | |
'userSpecific' => $this->userSpecific, | |
'dbUpdateNameOnly' => $this->dbUpdateNameOnly, | |
'category' => $category, | |
'dtlKey' => $this->$dtlKey, | |
] | |
); | |
$config = $store === null ? false : $store->fetch(); | |
if ($config !== false) { | |
$this->_detailConfig[$category] = $config; | |
} | |
} | |
} | |
/** | |
* Sets widget columns for display in [[\kartik\sortable\Sortable]] widget | |
* @throws InvalidConfigException | |
*/ | |
protected function setWidgetColumns() | |
{ | |
$this->_visibleColumns = $this->getSortableHeader($this->_model->getAttributeLabel('visibleColumns')); | |
$this->_hiddenColumns = $this->getSortableHeader($this->_model->getAttributeLabel('hiddenColumns')); | |
$visibleSettings = []; | |
// Ensure visible keys is not empty. If it is so, then grid will display all columns. | |
$this->_visibleKeys = array_filter($this->_visibleKeys); | |
$showAll = !is_array($this->_visibleKeys) || empty($this->_visibleKeys); | |
$indicator = Html::tag('span', $this->iconVisibleColumn, ['class' => 'icon-visible-column']). | |
Html::tag('span', $this->iconHiddenColumn, ['class' => 'icon-hidden-column']); | |
foreach ($this->_columns as $key => $column) { | |
$order = ArrayHelper::getValue($column, 'order', self::ORDER_MIDDLE); | |
$disabled = $order != self::ORDER_MIDDLE; | |
$widgetColumns = [ | |
'content' => (empty($indicator) ? '' : $indicator.' ').$this->getColumnLabel($key, $column), | |
'options' => ['id' => $key], | |
]; | |
if ($showAll && !$disabled) { | |
$visibleSettings[$key] = $widgetColumns; | |
} elseif (in_array($key, $this->_visibleKeys) && !$disabled) { | |
$visibleSettings[$key] = $widgetColumns; | |
} else { | |
$this->_hiddenColumns[] = $widgetColumns + ['disabled' => $disabled]; | |
} | |
} | |
if ($showAll) { | |
$this->_visibleColumns = $visibleSettings; | |
$this->_visibleKeys = array_keys($this->_visibleColumns); | |
} else { | |
foreach ($this->_visibleKeys as $key) { | |
if (!empty($visibleSettings[$key])) { | |
$this->_visibleColumns[] = $visibleSettings[$key]; | |
} | |
} | |
} | |
} | |
/** | |
* Generates the config for sortable widget header | |
* | |
* @param string $label | |
* | |
* @return array | |
*/ | |
protected function getSortableHeader($label) | |
{ | |
return [ | |
[ | |
'content' => $label, | |
'disabled' => true, | |
'options' => $this->sortableHeader, | |
], | |
]; | |
} | |
/** | |
* Fetches the column label | |
* | |
* @param mixed $key the column key | |
* @param mixed $column the column object / configuration | |
* | |
* @return string | |
* @throws InvalidConfigException | |
*/ | |
protected function getColumnLabel($key, $column) | |
{ | |
if (is_string($column)) { | |
$matches = $this->matchColumnString($column); | |
$attribute = $matches[1]; | |
if (isset($matches[5])) { | |
return $matches[5]; | |
} //header specified is in the format "attribute:format:label" | |
return $this->getAttributeLabel($attribute); | |
} else { | |
$label = $key; | |
if (is_array($column)) { | |
if (!empty($column['label'])) { | |
$label = $column['label']; | |
} elseif (!empty($column['header'])) { | |
$label = $column['header']; | |
} elseif (!empty($column['attribute'])) { | |
$label = $this->getAttributeLabel($column['attribute']); | |
} elseif (!empty($column['class'])) { | |
$class = Lib::explode('\\', $column['class']); | |
$label = Inflector::camel2words(end($class)); | |
} | |
} | |
return Lib::trim(Lib::strip_tags(Lib::str_replace(['<br>', '<br/>'], ' ', $label))); | |
} | |
} | |
/** | |
* Generates the attribute label | |
* | |
* @param $attribute | |
* | |
* @return string | |
*/ | |
protected function getAttributeLabel($attribute) | |
{ | |
$provider = $this->gridOptions['dataProvider']; | |
/** @var Model $model */ | |
if ($provider instanceof ActiveDataProvider && $provider->query instanceof ActiveQueryInterface) { | |
/** @var ActiveQuery $query */ | |
$query = $provider->query; | |
$model = new $query->modelClass; | |
return $model->getAttributeLabel($attribute); | |
} elseif ($provider instanceof ActiveDataProvider && $provider->query instanceof QueryInterface) { | |
return Inflector::camel2words($attribute); | |
} else { | |
$models = $provider->getModels(); | |
if (($model = reset($models)) instanceof Model) { | |
return $model->getAttributeLabel($attribute); | |
} else { | |
return Inflector::camel2words($attribute); | |
} | |
} | |
} | |
/** | |
* Load configuration attributes into DynaGridConfig model | |
* | |
* @param DynaGridConfig $model | |
* @throws InvalidConfigException | |
*/ | |
protected function loadAttributes($model) | |
{ | |
$model->id = $this->_requestSubmit; | |
$model->hiddenColumns = $this->_hiddenColumns; | |
$model->visibleColumns = $this->_visibleColumns; | |
$model->pageSize = $this->_pageSize; | |
$model->theme = $this->theme; | |
$model->filterId = $this->_filterId; | |
$model->sortId = $this->_sortId; | |
$model->widgetOptions = $this->sortableOptions; | |
$model->footer = $this->renderActionButton('delete').$this->renderActionButton('reset'). | |
$this->renderActionButton('submit'); | |
$themes = array_keys($this->_module->themeConfig); | |
$model->themeList = array_combine($themes, $themes); | |
} | |
/** | |
* Renders the action button | |
* | |
* @param string $type the button type | |
* | |
* @return string the rendered button | |
* @throws InvalidConfigException | |
*/ | |
protected function renderActionButton($type) | |
{ | |
$tag = "{$type}ButtonOptions"; | |
$options = $this->$tag; | |
if ($options === false) { | |
return ''; | |
} | |
$defaultOptions = $this->getDefaultButtonOptions($type); | |
if (!is_array($options)) { | |
$options = $defaultOptions; | |
} else { | |
$options = ArrayHelper::merge($defaultOptions, $options); | |
} | |
$icon = ArrayHelper::remove($options, 'icon', ''); | |
$prefix = ArrayHelper::remove($options, 'prefix', $this->getDefaultIconPrefix()); | |
$label = ''; | |
if (!empty($icon)) { | |
$label = '<i class="'.$prefix.$icon.'"></i> '; | |
} | |
$label .= ArrayHelper::remove($options, 'label', ''); | |
Html::addCssClass($options, "dynagrid-{$type}"); | |
return Html::button($label, $options); | |
} | |
/** | |
* Applies the grid filter | |
*/ | |
protected function applyFilter() | |
{ | |
if (empty($this->gridOptions['filterModel'])) { | |
return; | |
} | |
$class = get_class($this->gridOptions['filterModel']); | |
/** @var Model $searchModel */ | |
if (!empty($this->_detailConfig[DynaGridStore::STORE_FILTER]) && empty($_GET[$class])) { | |
$attributes = $this->_detailConfig[DynaGridStore::STORE_FILTER]; | |
$searchModel = $this->gridOptions['filterModel']; | |
$searchModel->setAttributes($attributes); | |
$this->gridOptions['filterModel'] = $searchModel; | |
$this->initDataProvider($searchModel); | |
} | |
} | |
/** | |
* Applies the grid sort | |
*/ | |
protected function applySort() | |
{ | |
if (!empty($this->_detailConfig[DynaGridStore::STORE_SORT])) { | |
/** @var ActiveDataProvider $dataProvider */ | |
$dataProvider = $this->gridOptions['dataProvider']; | |
$sort = $dataProvider->getSort(); | |
if (!$sort instanceof Sort) { | |
return; | |
} | |
$sort->defaultOrder = $this->_detailConfig[DynaGridStore::STORE_SORT]; | |
$dataProvider->setSort($sort); | |
$this->gridOptions['dataProvider'] = $dataProvider; | |
} | |
} | |
/** | |
* Applies the page size | |
*/ | |
protected function applyPageSize() | |
{ | |
if (isset($this->_pageSize) && $this->_pageSize != '' && $this->allowPageSetting) { | |
/** @var BaseDataProvider $dataProvider */ | |
$dataProvider = $this->gridOptions['dataProvider']; | |
if ($dataProvider instanceof ArrayDataProvider) { | |
$dataProvider->refresh(); | |
} | |
if ($this->_pageSize > 0) { | |
$pagination = $dataProvider->getPagination(); | |
$pagination->pageSize = $this->_pageSize; | |
$dataProvider->setPagination($pagination); | |
} else { | |
$dataProvider->setPagination(false); | |
} | |
if ($dataProvider instanceof SqlDataProvider) { | |
$dataProvider->prepare(true); | |
} | |
$this->gridOptions['dataProvider'] = $dataProvider; | |
} | |
} | |
/** | |
* Applies the configured theme | |
*/ | |
protected function applyTheme() | |
{ | |
$theme = ArrayHelper::getValue($this->_module->themeConfig, $this->theme, $this->_module->defaultTheme); | |
if (!is_array($theme) || empty($theme)) { | |
return; | |
} | |
$this->gridOptions = ArrayHelper::merge($theme, $this->gridOptions); | |
} | |
/** | |
* Applies the configured columns | |
*/ | |
protected function applyColumns() | |
{ | |
$columns = []; | |
$newColumns = []; | |
foreach ($this->columns as $column) { | |
$order = ArrayHelper::getValue($column, 'order', self::ORDER_MIDDLE); | |
if ($order == self::ORDER_FIX_LEFT) { | |
$newColumns = $column; | |
unset($column['order']); | |
$columns[] = $column; | |
} | |
} | |
foreach ($this->_visibleKeys as $key) { | |
if (empty($this->_columns[$key])) { | |
continue; | |
} | |
$column = $this->_columns[$key]; | |
$newColumns = $column; | |
if (isset($column['order'])) { | |
unset($column['order']); | |
} | |
if (isset($column['visible'])) { | |
unset($column['visible']); | |
} | |
$columns[] = $column; | |
} | |
foreach ($this->columns as $column) { | |
$order = ArrayHelper::getValue($column, 'order', self::ORDER_MIDDLE); | |
if ($order == self::ORDER_FIX_RIGHT) { | |
$newColumns = $column; | |
unset($column['order']); | |
$columns[] = $column; | |
} | |
} | |
$this->columns = $newColumns; | |
$this->gridOptions['columns'] = $columns; | |
} | |
/** | |
* Initialize the grid view for dynagrid | |
* @throws InvalidConfigException | |
* @throws Exception | |
*/ | |
protected function initGrid() | |
{ | |
$dynagrid = ''; | |
$dynagridFilter = ''; | |
$dynagridSort = ''; | |
$notBs3 = !$this->isBs(3); | |
$model = new DynaGridSettings( | |
[ | |
'moduleId' => $this->moduleId, | |
'dynaGridId' => $this->options['id'], | |
'storage' => $this->storage, | |
'userSpecific' => $this->userSpecific, | |
'dbUpdateNameOnly' => $this->dbUpdateNameOnly, | |
] | |
); | |
/** @var ActiveDataProvider $dataProvider */ | |
$dataProvider = $this->gridOptions['dataProvider']; | |
$sort = $dataProvider->getSort(); | |
$isValidSort = ($sort instanceof Sort); | |
if ($this->showPersonalize) { | |
$this->setToggleButton(DynaGridStore::STORE_GRID); | |
if ($this->allowFilterSetting || $this->allowSortSetting) { | |
$store = new DynaGridStore( | |
[ | |
'id' => $this->options['id'], | |
'moduleId' => $this->moduleId, | |
'category' => DynaGridStore::STORE_GRID, | |
'storage' => $this->storage, | |
'userSpecific' => $this->userSpecific, | |
'dbUpdateNameOnly' => $this->dbUpdateNameOnly, | |
] | |
); | |
if ($this->allowFilterSetting) { | |
$this->_model->filterId = $this->_filterId; | |
$this->_model->filterList = $store->getDtlList(DynaGridStore::STORE_FILTER); | |
} | |
if ($this->allowSortSetting && $isValidSort) { | |
$sort->enableMultiSort = $this->enableMultiSort; | |
$dataProvider->setSort($sort); | |
$this->_model->sortId = $this->_sortId; | |
$this->_model->sortList = $store->getDtlList(DynaGridStore::STORE_SORT); | |
} | |
} | |
$dynagrid = $this->render( | |
$this->_module->configView, | |
[ | |
'model' => $this->_model, | |
'toggleButtonGrid' => $this->toggleButtonGrid, | |
'id' => $this->_gridModalId, | |
'allowPageSetting' => $this->allowPageSetting, | |
'allowThemeSetting' => $this->allowThemeSetting, | |
'allowFilterSetting' => $this->allowFilterSetting, | |
'allowSortSetting' => $this->allowSortSetting, | |
'moduleId' => $this->moduleId, | |
'notBs3' => $notBs3, | |
'modalClass' => $this->getBSClass('Modal'), | |
'isPjax' => $this->_isPjax, | |
'pjaxId' => $this->_pjaxId, | |
'iconPersonalize' => $this->iconPersonalize, | |
'iconSortableSeparator' => $this->iconSortableSeparator, | |
] | |
); | |
} | |
$opts = [ | |
'bsVersion' => $this->bsVersion, | |
'model' => $model, | |
'moduleId' => $this->moduleId, | |
'submitMessage' => $this->submitMessage, | |
'deleteMessage' => $this->deleteMessage, | |
'messageOptions' => $this->messageOptions, | |
'deleteConfirmation' => $this->deleteConfirmation, | |
'isPjax' => $this->_isPjax, | |
'pjaxId' => $this->_pjaxId, | |
'krajeeDialogSettings' => $this->krajeeDialogSettings, | |
'iconFilter' => $this->iconFilter, | |
'iconSort' => $this->iconSort, | |
'iconConfirm' => $this->iconConfirm, | |
'iconRemove' => $this->iconRemove, | |
]; | |
if ($this->showFilter) { | |
$this->setToggleButton(DynaGridStore::STORE_FILTER); | |
$model->category = DynaGridStore::STORE_FILTER; | |
$model->key = $this->_filterKey; | |
$model->data = array_filter($this->gridOptions['filterModel']->attributes); | |
$opts['id'] = $this->_filterModalId; | |
$opts['toggleButton'] = $this->toggleButtonFilter; | |
$dynagridFilter = DynaGridDetail::widget($opts); | |
} | |
if ($this->showSort) { | |
$this->setToggleButton(DynaGridStore::STORE_SORT); | |
$model->category = DynaGridStore::STORE_SORT; | |
$model->key = $this->_sortKey; | |
$model->data = $isValidSort ? $sort->getAttributeOrders() : []; | |
$opts['id'] = $this->_sortModalId; | |
$opts['toggleButton'] = $this->toggleButtonSort; | |
$dynagridSort = DynaGridDetail::widget($opts); | |
} | |
$tags = ArrayHelper::getValue($this->gridOptions, 'replaceTags', []); | |
$tags += [ | |
'{dynagrid}' => $dynagrid, | |
'{dynagridFilter}' => $dynagridFilter, | |
'{dynagridSort}' => $dynagridSort, | |
]; | |
$this->gridOptions['replaceTags'] = $tags; | |
$this->registerAssets(); | |
} | |
/** | |
* Sets the personalization toggle button | |
* | |
* @param string $cat the category 'grid', 'filter', or 'sort' | |
* @throws InvalidConfigException|Exception | |
*/ | |
protected function setToggleButton($cat) | |
{ | |
$setting = 'toggleButton'.ucfirst($cat); | |
$notBs3 = !$this->isBs(3); | |
$defaultCss = $notBs3 ? 'outline-secondary' : 'default'; | |
$type = ArrayHelper::getValue($this->gridOptions['panel'], 'type', $defaultCss); | |
if (!$this->isBs(3) && $type == GridView::TYPE_DEFAULT) { | |
$type = 'light border-secondary'; | |
} | |
$btnClass = ($this->matchPanelStyle && $cat == 'grid' && !empty($this->gridOptions['panel'])) ? | |
"btn btn-{$type}" : "btn btn-".$defaultCss; | |
Html::addCssClass($this->$setting, $btnClass); | |
if ($cat == DynaGridStore::STORE_GRID) { | |
$this->toggleButtonGrid = ArrayHelper::merge( | |
[ | |
'label' => $this->iconPersonalize, | |
'title' => Yii::t('kvdynagrid', 'Personalize grid settings'), | |
'data-pjax' => false, | |
], | |
$this->toggleButtonGrid | |
); | |
} else { | |
$catIcon = 'icon'.ucfirst($cat); | |
$this->$setting = ArrayHelper::merge( | |
[ | |
'label' => $this->$catIcon, | |
'title' => Yii::t( | |
'kvdynagrid', | |
'Save / edit grid {category}', | |
['category' => static::getCat($cat)] | |
), | |
'data-pjax' => false, | |
], | |
$this->$setting | |
); | |
} | |
} | |
/** | |
* Registers client assets | |
* @throws Exception | |
*/ | |
protected function registerAssets() | |
{ | |
$view = $this->getView(); | |
DynaGridAsset::register($view); | |
Dialog::widget($this->krajeeDialogSettings); | |
Html::addCssClass($this->messageOptions, 'dynagrid-submit-message'); | |
$options = Json::encode( | |
[ | |
'submitMessage' => Html::tag('div', $this->submitMessage, $this->messageOptions), | |
'deleteMessage' => Html::tag('div', $this->deleteMessage, $this->messageOptions), | |
'deleteConfirmation' => $this->deleteConfirmation, | |
'modalId' => $this->_gridModalId, | |
'dynaGridId' => $this->options['id'], | |
'dialogLib' => ArrayHelper::getValue($this->krajeeDialogSettings, 'libName', 'krajeeDialog'), | |
] | |
); | |
$id = "jQuery('[name=\"{$this->_requestSubmit}\"]')"; | |
// the core dynagrid form validation | |
$js = "{$id}.dynagrid({$options});\n"; | |
// pjax related reset | |
if ($this->_isPjax) { | |
$cleanup = $this->pjaxCleanupJs('grid').$this->pjaxCleanupJs('filter').$this->pjaxCleanupJs('sort'); | |
$js .= " jQuery('#{$this->_pjaxId}').on('pjax:beforeReplace', function () { | |
{$cleanup} | |
});"; | |
$js .= " jQuery('#{$this->_pjaxId}').on('pjax:end', function () { | |
{$id}.dynagrid({$options}); | |
{$id}.dynagrid('reset'); | |
});"; | |
} | |
$view->registerJs($js); | |
} | |
/** | |
* Generates the cleanup client script for modals | |
* | |
* @param string $type the modal container type 'grid', 'filter', or 'sort' | |
* | |
* @return string | |
*/ | |
protected function pjaxCleanupJs($type) | |
{ | |
$id = "_{$type}ModalId"; | |
return 'jQuery("#'.$this->$id.'").remove();'; | |
} | |
} |