├── ActionColumn.php ├── BaseObect.php ├── ButtonColumn.php ├── Column.php ├── CropAsset.php ├── CropImage.php ├── DataColumn.php ├── GridInput.php ├── LICENSE ├── ModelHelper.php ├── MultipleTrait.php ├── README.md ├── SerialColumn.php ├── TabularAsset.php ├── TabularInput.php ├── TabularWidget.php ├── assets ├── css │ └── tabularInput.css └── js │ ├── dcropbox.js │ └── tabularInput.js ├── bower.json ├── composer.json └── views └── crop-image.php /ActionColumn.php: -------------------------------------------------------------------------------- 1 | 12 | * @since 1.0 13 | */ 14 | class ActionColumn extends \yii\grid\ActionColumn 15 | { 16 | public $iconTemplate = ''; 17 | 18 | /** 19 | * Initializes the default button rendering callbacks. 20 | */ 21 | protected function initDefaultButtons() 22 | { 23 | $buttons = [ 24 | 'view' => [ 25 | 'title' => Yii::t('yii', 'View'), 26 | 'aria-label' => Yii::t('yii', 'View'), 27 | 'data-pjax' => '0', 28 | 'icon' => 'eye-open', 29 | ], 30 | 'update' => [ 31 | 'title' => Yii::t('yii', 'Update'), 32 | 'aria-label' => Yii::t('yii', 'Update'), 33 | 'data-pjax' => '0', 34 | 'icon' => 'pencil' 35 | ], 36 | 'delete' => [ 37 | 'title' => Yii::t('yii', 'Delete'), 38 | 'aria-label' => Yii::t('yii', 'Delete'), 39 | 'data-confirm' => Yii::t('yii', 'Are you sure you want to delete this item?'), 40 | 'data-method' => 'post', 41 | 'data-pjax' => '0', 42 | 'icon' => 'trash' 43 | ] 44 | ]; 45 | foreach ($buttons as $name => $button) { 46 | $this->buttons[$name] = array_merge($button, $this->buttonOptions, $this->buttons[$name]); 47 | } 48 | } 49 | 50 | /** 51 | * @inheritdoc 52 | */ 53 | protected function renderDataCellContent($model, $key, $index) 54 | { 55 | return preg_replace_callback('/\\{([\w\-\/]+)\\}/', function ($matches) use ($model, $key, $index) { 56 | $name = $matches[1]; 57 | 58 | if (isset($this->visibleButtons[$name])) { 59 | $isVisible = $this->visibleButtons[$name] instanceof \Closure ? call_user_func($this->visibleButtons[$name], $model, $key, $index) 60 | : $this->visibleButtons[$name]; 61 | } else { 62 | $isVisible = true; 63 | } 64 | 65 | if ($isVisible) { 66 | $button = isset($this->buttons[$name]) ? $this->buttons[$name] : [ 67 | 'label' => ucfirst($name), 68 | 'aria-label' => ucfirst($name), 69 | 'data-pjax' => '0', 70 | ]; 71 | $url = $this->createUrl($name, $model, $key, $index); 72 | if ($button instanceof \Closure) { 73 | return call_user_func($button, $url, $model, $key); 74 | } else { 75 | $icon = str_replace('{icon}', ArrayHelper::remove($button, 'icon', ''), $this->iconTemplate); 76 | return Html::a($icon, $url, $button); 77 | } 78 | } 79 | return ''; 80 | }, $this->template); 81 | } 82 | } 83 | -------------------------------------------------------------------------------- /BaseObect.php: -------------------------------------------------------------------------------- 1 | 13 | * @since 1.3 14 | */ 15 | class BaseObect extends \yii\base\BaseObject 16 | { 17 | 18 | } 19 | -------------------------------------------------------------------------------- /ButtonColumn.php: -------------------------------------------------------------------------------- 1 | 11 | * @since 1.0 12 | */ 13 | class ButtonColumn extends Column 14 | { 15 | /** 16 | * 17 | * @var boolean Show add button 18 | */ 19 | public $showButtonAdd = true; 20 | /** 21 | * @var string Icon for header 22 | */ 23 | public $headerIcon = ''; 24 | /** 25 | * @var string Icon for delete button 26 | */ 27 | public $deleteIcon = ''; 28 | 29 | /** 30 | * @inheritdoc 31 | */ 32 | public function init() 33 | { 34 | if ($this->showButtonAdd) { 35 | $id = $this->grid->options['id'] . '-add-button'; 36 | $this->header = Html::a($this->headerIcon, '#', ['id' => $id]); 37 | if (!isset($this->grid->clientOptions['btnAddSelector'])) { 38 | $this->grid->clientOptions['btnAddSelector'] = '#' . $id; 39 | } 40 | } 41 | $this->value = Html::a($this->deleteIcon, '#', ['data-action' => 'delete']); 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /Column.php: -------------------------------------------------------------------------------- 1 | 13 | * @since 1.0 14 | */ 15 | class Column extends BaseObect 16 | { 17 | /** 18 | * @var GridInput 19 | */ 20 | public $grid; 21 | /** 22 | * 23 | * @var string|\Closure 24 | */ 25 | public $value; 26 | /** 27 | * 28 | * @var string header text 29 | */ 30 | public $header; 31 | /** 32 | * 33 | * @var string footer text 34 | */ 35 | public $footer; 36 | /** 37 | * 38 | * @var array 39 | */ 40 | public $headerOptions = []; 41 | /** 42 | * 43 | * @var array 44 | */ 45 | public $contentOptions = []; 46 | /** 47 | * 48 | * @var array 49 | */ 50 | public $footerOptions = []; 51 | /** 52 | * @var string 53 | */ 54 | public $format; 55 | 56 | /** 57 | * Render header cell 58 | * @return string 59 | */ 60 | public function renderHeaderCell() 61 | { 62 | return Html::tag('th', $this->header, $this->headerOptions); 63 | } 64 | 65 | /** 66 | * Render footer cell 67 | * @return string 68 | */ 69 | public function renderFooterCell() 70 | { 71 | return Html::tag('td', $this->footer, $this->footerOptions); 72 | } 73 | 74 | /** 75 | * Render data cell 76 | * @param Model $model model for cell 77 | * @param string $key 78 | * @param integer $index 79 | * @return string 80 | */ 81 | public function renderDataCell($model, $key, $index) 82 | { 83 | if (is_callable($this->value)) { 84 | $value = call_user_func($this->value, $model, $key, $index); 85 | } else { 86 | $value = $this->value; 87 | } 88 | if ($this->format !== null) { 89 | $value = Yii::$app->getFormatter()->format($value, $this->format); 90 | } 91 | return Html::tag('td', $value, $this->contentOptions); 92 | } 93 | } 94 | -------------------------------------------------------------------------------- /CropAsset.php: -------------------------------------------------------------------------------- 1 | 9 | * @since 1.0 10 | */ 11 | class CropAsset extends \yii\web\AssetBundle 12 | { 13 | public $sourcePath = '@mdm/widgets/assets'; 14 | public $js = [ 15 | 'http://jcrop-cdn.tapmodo.com/v0.9.12/js/jquery.Jcrop.min.js', 16 | 'js/dcropbox.js' 17 | ]; 18 | public $css = [ 19 | 'http://jcrop-cdn.tapmodo.com/v0.9.12/css/jquery.Jcrop.min.css' 20 | ]; 21 | 22 | public $depends = [ 23 | 'yii\web\JqueryAsset', 24 | ]; 25 | } 26 | -------------------------------------------------------------------------------- /CropImage.php: -------------------------------------------------------------------------------- 1 | 13 | * @since 1.0 14 | */ 15 | class CropImage extends InputWidget 16 | { 17 | /** 18 | * @var array the HTML attributes for the input tag. 19 | * @see Html::renderTagAttributes() for details on how attributes are being rendered. 20 | */ 21 | public $imgOptions = []; 22 | 23 | /** 24 | * 25 | * @var array 26 | */ 27 | public $clientOptions = []; 28 | /** 29 | * 30 | * @var string 31 | */ 32 | public $cropParam = 'crop'; 33 | 34 | /** 35 | * @inheritdoc 36 | */ 37 | public function init() 38 | { 39 | 40 | parent::init(); 41 | } 42 | 43 | /** 44 | * @inheritdoc 45 | */ 46 | public function run() 47 | { 48 | $id = $this->options['id'] = $this->getId(); 49 | 50 | Html::addCssClass($this->options, 'dcorpbox'); 51 | Html::addCssClass($this->imgOptions, 'content'); 52 | CropAsset::register($this->getView()); 53 | 54 | $clientOptions = $this->clientOptions; 55 | $this->imgOptions['id'] = $id . '-img'; 56 | $clientOptions['imgTemplate'] = Html::tag('img', '',$this->imgOptions); 57 | $opts = Json::encode($clientOptions); 58 | $js = "jQuery('#{$id}').dCropBox($opts);"; 59 | $this->getView()->registerJs($js); 60 | $inputOptions = ['style' => 'visibility:hidden;', 'class' => 'file-input', 'id' => $id . '-file']; 61 | if ($this->hasModel()) { 62 | $fileInput = Html::activeFileInput($this->model, $this->attribute, $inputOptions); 63 | } else { 64 | $fileInput = Html::fileInput($this->name, $this->value, $inputOptions); 65 | } 66 | return $this->render('crop-image', [ 67 | 'options' => $this->options, 68 | 'fileInput' => $fileInput, 69 | 'cropParam' => $this->cropParam, 70 | ]); 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /DataColumn.php: -------------------------------------------------------------------------------- 1 | 15 | * @since 1.0 16 | */ 17 | class DataColumn extends Column 18 | { 19 | /** 20 | * @var string attribute 21 | */ 22 | public $attribute; 23 | 24 | /** 25 | * @var array option for input 26 | */ 27 | public $inputOptions = ['class' => 'form-control']; 28 | 29 | /** 30 | * @var array|Closure 31 | */ 32 | public $items; 33 | 34 | /** 35 | * @var string 36 | */ 37 | public $template = '{input} {error}'; 38 | 39 | /** 40 | * @var string|array 41 | * ```php 42 | * 43 | * ``` 44 | */ 45 | public $widget; 46 | 47 | /** 48 | * 49 | * @var string 50 | */ 51 | public $type = 'text'; 52 | 53 | /** 54 | * @inheritdoc 55 | */ 56 | public function init() 57 | { 58 | if ($this->attribute) { 59 | $field = str_replace(['[]', '][', '[', ']', ' ', '.'], ['', '-', '-', '', '-', '-'], $this->attribute); 60 | } else { 61 | $field = false; 62 | } 63 | if (empty($this->inputOptions['data-field']) && $field) { 64 | $this->inputOptions['data-field'] = $field; 65 | } 66 | if (empty($this->contentOptions['data-column']) && $field) { 67 | $this->contentOptions['data-column'] = $field; 68 | } 69 | if (empty($this->headerOptions['data-column']) && $field) { 70 | $this->headerOptions['data-column'] = $field; 71 | } 72 | if ($this->header === null) { 73 | if ($this->grid->model instanceof Model && !empty($this->attribute)) { 74 | $this->header = $this->grid->model->getAttributeLabel($this->attribute); 75 | } else { 76 | $this->header = Inflector::camel2words($this->attribute); 77 | } 78 | } 79 | if ($this->value === null) { 80 | $this->value = [$this, 'renderInputCell']; 81 | } elseif (is_string($this->value)) { 82 | $this->attribute = $this->value; 83 | $this->value = [$this, 'renderTextCell']; 84 | } 85 | } 86 | 87 | /** 88 | * Render input cell 89 | * @param Model $model model for cell 90 | * @param string $key 91 | * @param integer $index 92 | * @return string 93 | */ 94 | public function renderInputCell($model, $key, $index) 95 | { 96 | $form = $this->grid->form; 97 | $items = $this->items; 98 | if ($this->widget !== null) { 99 | if (is_array($this->widget)) { 100 | list($widget, $options) = $this->widget; 101 | if ($options instanceof Closure) { 102 | $options = call_user_func($options, $model, $key, $index); 103 | } 104 | } else { 105 | $widget = $this->widget; 106 | $options = []; 107 | } 108 | if ($form instanceof ActiveForm) { 109 | return $form->field($model, "[$key]{$this->attribute}", ['template' => $this->template]) 110 | ->widget($widget, $options); 111 | } else { 112 | $options = array_merge([ 113 | 'model' => $model, 114 | 'attribute' => "[$key]{$this->attribute}" 115 | ], $options); 116 | return $widget::widget($options); 117 | } 118 | } elseif ($items !== null) { 119 | if ($items instanceof Closure) { 120 | $items = call_user_func($items, $model, $key, $index); 121 | } 122 | switch ($this->type) { 123 | case 'checkbox': 124 | if ($form instanceof ActiveForm) { 125 | return $form->field($model, "[$key]{$this->attribute}", ['template' => $this->template]) 126 | ->checkboxList($items, $this->inputOptions); 127 | } else { 128 | return Html::activeCheckboxList($model, "[$key]{$this->attribute}", $items, $this->inputOptions); 129 | } 130 | break; 131 | 132 | default: 133 | if ($form instanceof ActiveForm) { 134 | return $form->field($model, "[$key]{$this->attribute}", ['template' => $this->template]) 135 | ->dropDownList($items, $this->inputOptions); 136 | } else { 137 | return Html::activeDropDownList($model, "[$key]{$this->attribute}", $items, $this->inputOptions); 138 | } 139 | break; 140 | } 141 | } else { 142 | switch ($this->type) { 143 | case 'checkbox': 144 | if ($form instanceof ActiveForm) { 145 | return $form->field($model, "[$key]{$this->attribute}", ['template' => $this->template]) 146 | ->checkbox($this->inputOptions, false); 147 | } else { 148 | return Html::activeCheckbox($model, "[$key]{$this->attribute}", $this->inputOptions); 149 | } 150 | break; 151 | 152 | default: 153 | if ($form instanceof ActiveForm) { 154 | return $form->field($model, "[$key]{$this->attribute}", ['template' => $this->template]) 155 | ->textInput($this->inputOptions); 156 | } else { 157 | return Html::activeTextInput($model, "[$key]{$this->attribute}", $this->inputOptions); 158 | } 159 | break; 160 | } 161 | } 162 | } 163 | 164 | /** 165 | * Render input cell 166 | * @param Model $model model for cell 167 | * @param string $key 168 | * @return string 169 | */ 170 | public function renderTextCell($model, $key) 171 | { 172 | return Html::getAttributeValue($model, "[$key]{$this->attribute}"); 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /GridInput.php: -------------------------------------------------------------------------------- 1 | 15 | * @since 1.0 16 | */ 17 | class GridInput extends TabularWidget 18 | { 19 | /** 20 | * @inheritdoc 21 | */ 22 | public $tag = 'table'; 23 | 24 | /** 25 | * @var Column[] 26 | */ 27 | public $columns = []; 28 | 29 | /** 30 | * @var array 31 | */ 32 | public $hiddens = []; 33 | 34 | /** 35 | * 36 | * @var string 37 | */ 38 | public $header; 39 | 40 | /** 41 | * 42 | * @var string 43 | */ 44 | public $footer; 45 | 46 | /** 47 | * 48 | * @var array 49 | */ 50 | public $headerOptions = []; 51 | 52 | /** 53 | * 54 | * @var array 55 | */ 56 | public $footerOptions = []; 57 | 58 | /** 59 | * 60 | * @var string 61 | */ 62 | public $defaultColumnClass = 'mdm\widgets\DataColumn'; 63 | 64 | /** 65 | * @inheritdoc 66 | */ 67 | public $options = ['class' => 'table table-striped']; 68 | 69 | /** 70 | * @inheritdoc 71 | */ 72 | public function init() 73 | { 74 | parent::init(); 75 | if (!($this->model instanceof Model)) { 76 | $property = __CLASS__ . '::$model'; 77 | throw new InvalidConfigException("Value of \"{$property}\" must be specified."); 78 | } 79 | foreach ($this->columns as $i => $column) { 80 | if (is_string($column)) { 81 | $column = Yii::createObject([ 82 | 'class' => $this->defaultColumnClass, 83 | 'attribute' => $column, 84 | 'grid' => $this, 85 | ]); 86 | } elseif (is_array($column)) { 87 | if (!isset($column['class'])) { 88 | $column['class'] = $this->defaultColumnClass; 89 | } 90 | $column['grid'] = $this; 91 | $column = Yii::createObject($column); 92 | } 93 | 94 | $this->columns[$i] = $column; 95 | } 96 | $this->containerOptions['tag'] = 'tbody'; 97 | $this->clientOptions = array_merge([ 98 | 'container' => "tbody.mdm-container{$this->level}", 99 | 'itemSelector' => "tr.mdm-item{$this->level}" 100 | ], $this->clientOptions); 101 | Html::addCssClass($this->itemOptions, "mdm-item{$this->level}"); 102 | Html::addCssClass($this->containerOptions, "mdm-container{$this->level}"); 103 | } 104 | 105 | /** 106 | * @inheritdoc 107 | */ 108 | public function renderHeader() 109 | { 110 | if ($this->header === false) { 111 | return ''; 112 | } 113 | if ($this->header === null) { 114 | $cols = []; 115 | foreach ($this->columns as $column) { 116 | $cols[] = $column->renderHeaderCell(); 117 | } 118 | if (count($this->hiddens)) { 119 | $cols[] = '
'; 120 | } 121 | $rows = [Html::tag('tr', implode("\n", $cols), $this->headerOptions)]; 122 | } else { 123 | $rows = []; 124 | foreach ((array) $this->header as $header) { 125 | $rows[] = Html::tag('tr', $header, $this->headerOptions); 126 | } 127 | } 128 | return Html::tag('thead', implode("\n", $rows)); 129 | } 130 | 131 | /** 132 | * @inheritdoc 133 | */ 134 | public function renderItem($model, $key, $index) 135 | { 136 | $cols = []; 137 | /* @var $column Column */ 138 | foreach ($this->columns as $column) { 139 | $cols[] = $column->renderDataCell($model, $key, $index); 140 | } 141 | if (count($this->hiddens)) { 142 | $hiddens = []; 143 | foreach ($this->hiddens as $options) { 144 | if (is_string($options)) { 145 | $attribute = $options; 146 | $options = []; 147 | } else { 148 | $attribute = ArrayHelper::remove($options, 'attribute'); 149 | } 150 | $field = str_replace(['[]', '][', '[', ']', ' ', '.'], ['', '-', '-', '', '-', '-'], $attribute); 151 | $options['data-field'] = $field; 152 | $options['id'] = false; 153 | $hiddens[] = Html::activeHiddenInput($model, "[$key]{$attribute}", $options); 154 | } 155 | $cols[] = Html::tag('td', implode("\n", $hiddens), ['style' => ['display' => 'none'], 'class' => 'hidden-col']); 156 | } 157 | $options = $this->itemOptions; 158 | $options['data-key'] = (string) $key; 159 | $options['data-index'] = (string) $index; 160 | return Html::tag('tr', implode("\n", $cols), $options); 161 | } 162 | 163 | /** 164 | * @inheritdoc 165 | */ 166 | public function renderFooter() 167 | { 168 | if ($this->footer === false) { 169 | return ''; 170 | } 171 | if ($this->footer === null) { 172 | $cols = []; 173 | foreach ($this->columns as $column) { 174 | $cols[] = $column->renderFooterCell(); 175 | } 176 | if (count($this->hiddens)) { 177 | $cols[] = ' '; 178 | } 179 | $rows = [Html::tag('tr', implode("\n", $cols), $this->footerOptions)]; 180 | } else { 181 | $rows = []; 182 | foreach ((array) $this->footer as $footer) { 183 | $rows[] = Html::tag('tr', $footer, $this->footerOptions); 184 | } 185 | } 186 | return Html::tag('tfoot', implode("\n", $rows)); 187 | } 188 | } 189 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2014, mdmsoft 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 the {organization} 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 | 29 | -------------------------------------------------------------------------------- /ModelHelper.php: -------------------------------------------------------------------------------- 1 | 15 | * @since 1.0 16 | */ 17 | class ModelHelper 18 | { 19 | 20 | /** 21 | * Populates a set of models with the data from end user. 22 | * This method is mainly used to collect tabular data input. 23 | * The data to be loaded for each model is `$data[formName][index]`, where `formName` 24 | * refers to the sort name of model class, and `index` the index of the model in the `$data` array. 25 | * If `$formName` is empty, `$data[index]` will be used to populate each model. 26 | * The data being populated to each model is subject to the safety check by [[setAttributes()]]. 27 | * @param string $class Model class name. 28 | * @param array $data the data array. This is usually `$_POST` or `$_GET`, but can also be any valid array 29 | * supplied by end user. 30 | * @param string $formName the form name to be used for loading the data into the models. 31 | * If not set, it will use the sort name of called class. 32 | * @param Model[] $origin original models to be populated. It will be check using `$keys` with supplied data. 33 | * If same then will be used for result model. 34 | * @param array $options Option to model 35 | * - scenario for model. 36 | * - arguments The parameters to be passed to the class constructor as an array. 37 | * @return boolean|Model[] whether at least one of the models is successfully populated. 38 | */ 39 | public static function createMultiple($class, $data, $formName = null, &$origin = [], $options = []) 40 | { 41 | $reflector = new ReflectionClass($class); 42 | $args = isset($options['arguments']) ? $options['arguments'] : []; 43 | if ($formName === null) { 44 | /* @var $model Model */ 45 | $model = empty($args) ? new $class() : $reflector->newInstanceArgs($args); 46 | $formName = $model->formName(); 47 | } 48 | if ($formName != '') { 49 | $data = isset($data[$formName]) ? $data[$formName] : null; 50 | } 51 | if ($data === null) { 52 | return false; 53 | } 54 | 55 | $models = []; 56 | foreach ($data as $i => $row) { 57 | $model = null; 58 | if (isset($origin[$i])) { 59 | $model = $origin[$i]; 60 | unset($origin[$i]); 61 | } else { 62 | $model = empty($args) ? new $class() : $reflector->newInstanceArgs($args); 63 | } 64 | if (isset($options['scenario'])) { 65 | $model->scenario = $options['scenario']; 66 | } 67 | $model->load($row, ''); 68 | $models[$i] = $model; 69 | } 70 | return $models; 71 | } 72 | 73 | /** 74 | * Populates a set of models with the data from end user. 75 | * This method is mainly used to collect tabular data input. 76 | * The data to be loaded for each model is `$data[formName][index]`, where `formName` 77 | * refers to the sort name of model class, and `index` the index of the model in the `$data` array. 78 | * If `$formName` is empty, `$data[index]` will be used to populate each model. 79 | * The data being populated to each model is subject to the safety check by [[setAttributes()]]. 80 | * @param string $class Model class name. 81 | * @param array $data the data array. This is usually `$_POST` or `$_GET`, but can also be any valid array 82 | * supplied by end user. 83 | * @param string $formName the form name to be used for loading the data into the models. 84 | * If not set, it will use the sort name of called class. 85 | * @param Model[] $origin original models to be populated. It will be check using `$keys` with supplied data. 86 | * If same then will be used for result model. 87 | * @param array $config Option to model 88 | * @return boolean|Model[] whether at least one of the models is successfully populated. 89 | */ 90 | public static function loadMultiple($class, $data, $formName = null, &$origin = [], $config = []) 91 | { 92 | $config['class'] = $class; 93 | if ($formName === null) { 94 | /* @var $model Model */ 95 | $model = Yii::createObject($config); 96 | $formName = $model->formName(); 97 | } 98 | if ($formName != '') { 99 | $data = isset($data[$formName]) ? $data[$formName] : null; 100 | } 101 | if ($data === null) { 102 | return []; 103 | } 104 | 105 | $models = []; 106 | foreach ($data as $i => $row) { 107 | $model = null; 108 | if (isset($origin[$i])) { 109 | $model = $origin[$i]; 110 | unset($origin[$i]); 111 | } else { 112 | $model = Yii::createObject($config); 113 | } 114 | $model->load($row, ''); 115 | $models[$i] = $model; 116 | } 117 | return $models; 118 | } 119 | 120 | /** 121 | * 122 | * @param Model[] $models 123 | * @param array $options 124 | * @param array $messages 125 | * @return boolean 126 | */ 127 | public static function validateMultiple(array $models, $options = [], &$messages = []) 128 | { 129 | /* @var $model Model */ 130 | $validateAll = !empty($options['validateAll']); 131 | /* @var $model BaseActiveRecord */ 132 | if (isset($options['values'])) { 133 | foreach ($models as $model) { 134 | Yii::configure($model, $options['values']); 135 | } 136 | } 137 | $result = true; 138 | foreach ($models as $i => $model) { 139 | $oke = !isset($options['beforeValidate']) || call_user_func($options['beforeValidate'], $model) !== false; 140 | if ($oke && $model->validate()) { 141 | isset($options['afterValidate']) && call_user_func($options['afterValidate'], $model); 142 | } else { 143 | foreach ($model->getErrors() as $attribute => $message) { 144 | $messages[Html::getInputId($model, "[$i]$attribute")] = $message; 145 | } 146 | if ($validateAll) { 147 | $result = false; 148 | } else { 149 | return false; 150 | } 151 | } 152 | } 153 | return $result; 154 | } 155 | 156 | /** 157 | * 158 | * @param BaseActiveRecord[] $models 159 | * @param array $options 160 | * - runValidation boolean default true. 161 | * - beforeSave callable 162 | * - afterSave callable 163 | * - deleteUnsaved boolean default true. 164 | * @param array $messages 165 | * @return boolean 166 | */ 167 | public static function saveMultiple(array $models, $options = [], &$messages = []) 168 | { 169 | $runValidation = !isset($options['runValidation']) || $options['runValidation']; 170 | /* @var $model BaseActiveRecord */ 171 | if (isset($options['values'])) { 172 | foreach ($models as $model) { 173 | Yii::configure($model, $options['values']); 174 | } 175 | } 176 | unset($options['values']); 177 | if (!$runValidation || static::validateMultiple($models, $options, $messages)) { 178 | foreach ($models as $model) { 179 | if (!isset($options['beforeSave']) || call_user_func($options['beforeSave'], $model) !== false) { 180 | if ($model->save(false)) { 181 | isset($options['afterSave']) && call_user_func($options['afterSave'], $model); 182 | } else { 183 | return false; 184 | } 185 | } elseif (!isset($options['deleteUnsaved']) || $options['deleteUnsaved']) { 186 | if (!$model->isNewRecord) { 187 | $model->delete(); 188 | } 189 | } 190 | } 191 | return true; 192 | } 193 | return false; 194 | } 195 | } 196 | -------------------------------------------------------------------------------- /MultipleTrait.php: -------------------------------------------------------------------------------- 1 | 10 | * @since 1.0 11 | */ 12 | trait MultipleTrait 13 | { 14 | 15 | /** 16 | * Populates a set of models with the data from end user. 17 | * This method is mainly used to collect tabular data input. 18 | * The data to be loaded for each model is `$data[formName][index]`, where `formName` 19 | * refers to the sort name of model class, and `index` the index of the model in the `$data` array. 20 | * If `$formName` is empty, `$data[index]` will be used to populate each model. 21 | * The data being populated to each model is subject to the safety check by [[setAttributes()]]. 22 | * @param array $data the data array. This is usually `$_POST` or `$_GET`, but can also be any valid array 23 | * supplied by end user. 24 | * @param string $formName the form name to be used for loading the data into the models. 25 | * If not set, it will use the sort name of called class. 26 | * @param Model[] $origin original models to be populated. It will be check using `$keys` with supplied data. 27 | * If same then will be used for result model. 28 | * @param array $options Option to model 29 | * - 'scenario' for model. 30 | * - 'arguments' The parameters to be passed to the class constructor as an array. 31 | * @return boolean|Model[] whether at least one of the models is successfully populated. 32 | */ 33 | public static function createMultiple($data, $formName = null, &$origin = [], $options = []) 34 | { 35 | return ModelHelper::createMultiple(get_called_class(), $data, $formName, $origin, $options); 36 | } 37 | } 38 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | yii2-widgets 2 | ============ 3 | 4 | Installation 5 | ------------ 6 | 7 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/). 8 | 9 | Either run 10 | 11 | ``` 12 | php composer.phar require mdmsoft/yii2-widgets "~1.0" 13 | ``` 14 | 15 | or add 16 | 17 | ``` 18 | "mdmsoft/yii2-widgets": "~1.0" 19 | ``` 20 | 21 | to the require section of your `composer.json` file. 22 | 23 | Usage 24 | ----- 25 | 26 | # TabularInput Widget 27 | 28 | `_form.php` 29 | ```php 30 | 31 |