├── .gitignore ├── src ├── behaviors │ └── NestedSetsBehavior.php ├── widgets │ └── nestable │ │ ├── NestableAsset.php │ │ ├── assets │ │ ├── jquery.nestable.css │ │ └── jquery.nestable.js │ │ └── Nestable.php ├── actions │ ├── BaseAction.php │ ├── CreateNodeAction.php │ ├── MoveNodeAction.php │ ├── DeleteNodeAction.php │ └── UpdateNodeAction.php └── forms │ └── MoveNodeForm.php ├── LICENSE ├── composer.json └── README.md /.gitignore: -------------------------------------------------------------------------------- 1 | .idea 2 | vendors 3 | composer.lock -------------------------------------------------------------------------------- /src/behaviors/NestedSetsBehavior.php: -------------------------------------------------------------------------------- 1 | node = $this->owner; 20 | parent::moveNode($value, $depth); 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /src/widgets/nestable/NestableAsset.php: -------------------------------------------------------------------------------- 1 | modelClass) { 31 | throw new InvalidConfigException('Param "modelClass" must be contain model name with namespace.'); 32 | } 33 | } 34 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Copyright (c) 2015 Vitaly 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | 23 | -------------------------------------------------------------------------------- /src/actions/CreateNodeAction.php: -------------------------------------------------------------------------------- 1 | request->post(); 30 | 31 | /** @var ActiveRecord|NestedSetsBehavior $model */ 32 | $model = new $this->modelClass; 33 | $model->load($post); 34 | 35 | if ($model->validate()) { 36 | $roots = $model::find()->roots()->all(); 37 | 38 | if (isset($roots[0])) { 39 | $model->appendTo($roots[0]); 40 | } else { 41 | $model->makeRoot(); 42 | } 43 | } 44 | 45 | return null; 46 | } 47 | } -------------------------------------------------------------------------------- /src/actions/MoveNodeAction.php: -------------------------------------------------------------------------------- 1 | request->post(); 32 | 33 | $form = new MoveNodeForm(); 34 | $form->id = $id; 35 | $form->setAttributes($params); 36 | 37 | if (!$form->validate()) { 38 | throw new BadRequestHttpException(); 39 | } 40 | 41 | $form->moveNode($this->modelClass, $this->behaviorName); 42 | 43 | return null; 44 | } 45 | } -------------------------------------------------------------------------------- /composer.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "voskobovich/yii2-nested-sets-editor", 3 | "description": "Nested set editor using jquery.nestable plugin for Yii 2", 4 | "keywords": [ 5 | "widget", 6 | "nested sets", 7 | "yii2", 8 | "nestable", 9 | "editor" 10 | ], 11 | "homepage": "https://github.com/voskobovich/yii2-nested-sets-editor", 12 | "type": "yii2-widget", 13 | "license": "MIT", 14 | "support": { 15 | "issues": "https://github.com/voskobovich/yii2-nested-sets-editor/issues", 16 | "source": "https://github.com/voskobovich/yii2-nested-sets-editor" 17 | }, 18 | "authors": [ 19 | { 20 | "name": "Vitaly Voskobovich", 21 | "email": "vitaly@voskobovich.com", 22 | "homepage": "http://voskobovich.com" 23 | } 24 | ], 25 | "require": { 26 | "php": ">=5.4.0", 27 | "yiisoft/yii2": "~2.0.0", 28 | "yiisoft/yii2-bootstrap": "~2.0.0", 29 | "creocoder/yii2-nested-sets": "~0.9.0" 30 | }, 31 | "autoload": { 32 | "psr-4": { 33 | "voskobovich\\nestedsets\\actions\\": "src/actions", 34 | "voskobovich\\nestedsets\\behaviors\\": "src/behaviors", 35 | "voskobovich\\nestedsets\\forms\\": "src/forms", 36 | "voskobovich\\nestedsets\\widgets\\nestable\\": "src/widgets/nestable" 37 | } 38 | } 39 | } -------------------------------------------------------------------------------- /src/actions/DeleteNodeAction.php: -------------------------------------------------------------------------------- 1 | modelClass; 27 | 28 | /* 29 | * Locate the supplied model, left, right and parent models 30 | */ 31 | $pkAttribute = $model->getTableSchema()->primaryKey[0]; 32 | 33 | /** @var ActiveRecord|NestedSetsBehavior $model */ 34 | $model = $model::find()->where([$pkAttribute => $id])->one(); 35 | 36 | if ($model == null) { 37 | throw new NotFoundHttpException('Node not found'); 38 | } 39 | 40 | $model->deleteWithChildren(); 41 | 42 | return null; 43 | } 44 | } -------------------------------------------------------------------------------- /src/actions/UpdateNodeAction.php: -------------------------------------------------------------------------------- 1 | modelClass; 37 | 38 | /* 39 | * Locate the supplied model, left, right and parent models 40 | */ 41 | $pkAttribute = $model->getTableSchema()->primaryKey[0]; 42 | 43 | /** @var ActiveRecord|NestedSetsBehavior $model */ 44 | $model = $model::find()->where([$pkAttribute => $id])->one(); 45 | 46 | if ($model == null) { 47 | throw new NotFoundHttpException('Node not found'); 48 | } 49 | 50 | $name = Yii::$app->request->post('name'); 51 | $model->{$this->nameAttribute} = $name; 52 | if (!$model->validate()) { 53 | throw new HttpException($model->getFirstError($this->nameAttribute)); 54 | } 55 | $model->update(true, [$this->nameAttribute]); 56 | 57 | return null; 58 | } 59 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | Yii2 Nested Sets Editor 2 | === 3 | 4 | This behavior soon will be **DEPRECATED**. 5 | See the new version [**Yii2 Tree Manager**](https://github.com/voskobovich/yii2-tree-manager). 6 | 7 | ## About 8 | Editor nested set using jquery.nestable plugin. 9 | 10 | Реализует полный набор CRUD операций для узлов дерева. 11 | 12 | Внимание! 13 | --- 14 | Есть улучшеная версия пакета для управление деревом - [yii2-tree-manager](https://github.com/voskobovich/yii2-tree-manager). 15 | 16 | Installation 17 | ------------- 18 | 19 | The preferred way to install this extension is through [composer](http://getcomposer.org/download/). 20 | 21 | Either run 22 | 23 | ``` 24 | php composer.phar require --prefer-dist voskobovich/yii2-nested-sets-editor "~1.0.0" 25 | ``` 26 | 27 | or add 28 | 29 | ``` 30 | "voskobovich/yii2-nested-sets-editor": "~1.0.0" 31 | ``` 32 | 33 | to the require section of your `composer.json` file. 34 | 35 | 36 | Внимание! 37 | ----- 38 | В расширении наследуется и расширяется behavior [Nested Sets Behavior for Yii 2](https://github.com/creocoder/yii2-nested-sets). 39 | Всю информацию по настройке поведения можно взять на [странице](https://github.com/creocoder/yii2-nested-sets). 40 | 41 | Но для работы виджета нужно использовать реализацию поведения из этого пакета! 42 | 43 | 44 | Usage 45 | ----- 46 | 1. Подключите behavior из этого пакета к своей модели и сконфигурируйте как сказано в [документации](https://github.com/creocoder/yii2-nested-sets). 47 | ``` 48 | public function behaviors() 49 | { 50 | return [ 51 | 'nestedSetsBehavior' => 'voskobovich\nestedsets\behaviors\NestedSetsBehavior', 52 | ]; 53 | } 54 | ``` 55 | 2. Подключите в контроллер дополнительные actions 56 | ``` 57 | public function actions() 58 | { 59 | return [ 60 | 'moveNode' => [ 61 | 'class' => 'voskobovich\nestedsets\actions\MoveNodeAction', 62 | 'modelClass' => 'models\ModelName', 63 | ], 64 | 'deleteNode' => [ 65 | 'class' => 'voskobovich\nestedsets\actions\DeleteNodeAction', 66 | 'modelClass' => 'models\ModelName', 67 | ], 68 | 'updateNode' => [ 69 | 'class' => 'voskobovich\nestedsets\actions\UpdateNodeAction', 70 | 'modelClass' => 'models\ModelName', 71 | ], 72 | 'createNode' => [ 73 | 'class' => 'voskobovich\nestedsets\actions\CreateNodeAction', 74 | 'modelClass' => 'models\ModelName', 75 | ], 76 | ]; 77 | } 78 | ``` 79 | 3. Выведите виджет в удобном месте 80 | ``` 81 | = \voskobovich\nestedsets\widgets\nestable\Nestable::widget([ 82 | 'modelClass' => 'models\ModelName', 83 | ]) ?> 84 | ``` 85 | -------------------------------------------------------------------------------- /src/forms/MoveNodeForm.php: -------------------------------------------------------------------------------- 1 | getBehavior($behaviorName); 58 | 59 | if ($behavior == null) { 60 | throw new InvalidConfigException('Behavior "' . $behaviorName . '" not found'); 61 | } 62 | 63 | if (!$behavior instanceof NestedSetsBehavior) { 64 | throw new InvalidConfigException('Behavior must be implemented "voskobovich\nestedsets\behaviors\NestedSetsBehavior"'); 65 | } 66 | 67 | /* 68 | * Locate the supplied model, left, right and parent models 69 | */ 70 | $pkAttribute = $model->getTableSchema()->primaryKey[0]; 71 | 72 | /** @var ActiveRecord|NestedSetsBehavior $currentModel */ 73 | $currentModel = $model::find()->where([$pkAttribute => $this->id])->one(); 74 | $lftModel = $model::find()->where([$pkAttribute => $this->left])->one(); 75 | $rgtModel = $model::find()->where([$pkAttribute => $this->right])->one(); 76 | $parentModel = $model::find()->where([$pkAttribute => $this->parent])->one(); 77 | 78 | /* 79 | * Calculate the depth change 80 | */ 81 | if (null == $parentModel) { 82 | $depthDelta = -1; 83 | } else if (null == ($parent = $currentModel->parents(1)->one())) { 84 | $depthDelta = 0; 85 | } else if ($parent->getPrimaryKey() != $parentModel->getPrimaryKey()) { 86 | $depthDelta = $parentModel->{$behavior->depthAttribute} - $currentModel->{$behavior->depthAttribute} + 1; 87 | } else { 88 | $depthDelta = 0; 89 | } 90 | 91 | /* 92 | * Calculate the left/right change 93 | */ 94 | if (null == $lftModel) { 95 | $currentModel->moveNode((($parentModel ? $parentModel->{$behavior->leftAttribute} : 0) + 1), $depthDelta); 96 | } else if (null == $rgtModel) { 97 | $currentModel->moveNode((($lftModel ? $lftModel->{$behavior->rightAttribute} : 0) + 1), $depthDelta); 98 | } else { 99 | $currentModel->moveNode(($rgtModel ? $rgtModel->{$behavior->leftAttribute} : 0), $depthDelta); 100 | } 101 | } 102 | } -------------------------------------------------------------------------------- /src/widgets/nestable/assets/jquery.nestable.css: -------------------------------------------------------------------------------- 1 | .dd-nestable { 2 | display: block; 3 | font-size: 13px; 4 | list-style: none; 5 | margin-bottom: 10px; 6 | max-width: 924px; 7 | padding: 0; 8 | position: relative; 9 | } 10 | 11 | .dd-list { 12 | display: block; 13 | list-style: none; 14 | margin: 0; 15 | padding: 0; 16 | position: relative; 17 | } 18 | 19 | .dd-list .dd-list { 20 | padding-left: 34px; 21 | } 22 | 23 | .dd-collapsed .dd-list { 24 | display: none; 25 | } 26 | 27 | .dd-item, 28 | .dd-empty, 29 | .dd-placeholder { 30 | display: block; 31 | font-size: 13px; 32 | margin: 0; 33 | min-height: 20px; 34 | padding: 0; 35 | position: relative; 36 | } 37 | 38 | .dd-item > .dd-button { 39 | background: transparent; 40 | border: none; 41 | cursor: pointer; 42 | display: block; 43 | float: left; 44 | font-weight: bold; 45 | height: 34px; 46 | overflow: hidden; 47 | padding: 0; 48 | position: relative; 49 | text-align: center; 50 | text-indent: 100%; 51 | white-space: nowrap; 52 | width: 25px; 53 | line-height: 0px; 54 | } 55 | 56 | .dd-item > .dd-button:focus, 57 | .dd-item > .dd-edit:focus { 58 | outline: none; 59 | border: none; 60 | } 61 | 62 | .dd-item > .dd-button:before { 63 | content: '+'; 64 | display: block; 65 | position: absolute; 66 | text-align: center; 67 | text-indent: 0; 68 | width: 100%; 69 | } 70 | 71 | .dd-item > .dd-button[data-action="collapse"]:before { 72 | content: '-'; 73 | } 74 | 75 | .dd-placeholder, 76 | .dd-empty { 77 | background: #f2fbff; 78 | border: 1px dashed #b6bcbf; 79 | box-sizing: border-box; 80 | margin: 5px 0; 81 | min-height: 30px; 82 | moz-box-sizing: border-box; 83 | padding: 0; 84 | } 85 | 86 | .dd-empty { 87 | background-color: #e5e5e5; 88 | border: 1px dashed #bbb; 89 | min-height: 100px; 90 | } 91 | 92 | .dd-dragel { 93 | pointer-events: none; 94 | position: absolute; 95 | z-index: 9999; 96 | } 97 | 98 | .dd-dragel .dd-handle { 99 | background-color: #da4f49; 100 | background-image: -moz-linear-gradient(top, #ee5f5b, #bd362f); 101 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ee5f5b), to(#bd362f)); 102 | background-image: -webkit-linear-gradient(top, #ee5f5b, #bd362f); 103 | background-image: -o-linear-gradient(top, #ee5f5b, #bd362f); 104 | background-image: linear-gradient(to bottom, #ee5f5b, #bd362f); 105 | background-repeat: repeat-x; 106 | border-color: #bd362f #bd362f #802420; 107 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 108 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffee5f5b', endColorstr='#ffbd362f', GradientType=0); 109 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 110 | } 111 | 112 | .dd-content { 113 | box-sizing: border-box; 114 | line-height: 20px; 115 | color: #333; 116 | display: block; 117 | font-weight: bold; 118 | height: 34px; 119 | margin: 5px 0; 120 | moz-box-sizing: border-box; 121 | padding: 5px 10px 5px 40px; 122 | text-decoration: none; 123 | cursor: pointer; 124 | background-color: #f5f5f5; 125 | background-image: -moz-linear-gradient(top, #ffffff, #e6e6e6); 126 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#ffffff), to(#e6e6e6)); 127 | background-image: -webkit-linear-gradient(top, #ffffff, #e6e6e6); 128 | background-image: -o-linear-gradient(top, #ffffff, #e6e6e6); 129 | background-image: linear-gradient(to bottom, #ffffff, #e6e6e6); 130 | background-repeat: repeat-x; 131 | border: 1px solid #cccccc; 132 | border-color: #e6e6e6 #e6e6e6 #bfbfbf; 133 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 134 | border-bottom-color: #b3b3b3; 135 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ffffffff', endColorstr='#ffe6e6e6', GradientType=0); 136 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 137 | } 138 | 139 | .dd-dragel > .dd-item > .dd-content { 140 | margin: 0 0 5px 0; 141 | } 142 | 143 | .dd-item > .dd-button { 144 | margin-left: 34px; 145 | } 146 | 147 | .dd-handle { 148 | cursor: pointer; 149 | left: 0; 150 | margin: 0; 151 | overflow: hidden; 152 | position: absolute; 153 | text-indent: 100%; 154 | top: 0; 155 | white-space: nowrap; 156 | line-height: 26px; 157 | width: 34px; 158 | height: 34px; 159 | background-color: #006dcc; 160 | background-image: -moz-linear-gradient(top, #0088cc, #0044cc); 161 | background-image: -webkit-gradient(linear, 0 0, 0 100%, from(#0088cc), to(#0044cc)); 162 | background-image: -webkit-linear-gradient(top, #0088cc, #0044cc); 163 | background-image: -o-linear-gradient(top, #0088cc, #0044cc); 164 | background-image: linear-gradient(to bottom, #0088cc, #0044cc); 165 | background-repeat: repeat-x; 166 | border-color: #0044cc #0044cc #002a80; 167 | border-color: rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.1) rgba(0, 0, 0, 0.25); 168 | filter: progid:DXImageTransform.Microsoft.gradient(startColorstr='#ff0088cc', endColorstr='#ff0044cc', GradientType=0); 169 | filter: progid:DXImageTransform.Microsoft.gradient(enabled=false); 170 | } 171 | 172 | .dd-handle:before { 173 | color: #fff; 174 | content: '≡'; 175 | display: block; 176 | font-size: 20px; 177 | font-weight: normal; 178 | left: 0; 179 | position: absolute; 180 | text-align: center; 181 | text-indent: 0; 182 | top: 3px; 183 | width: 100%; 184 | } 185 | 186 | .dd-edit-panel { 187 | display: none; 188 | height: 63px; 189 | } 190 | 191 | .dd-input-name, 192 | .dd-input-url, 193 | .dd-input-bizrule { 194 | display: block; 195 | width: 100%; 196 | min-height: 30px; 197 | -webkit-box-sizing: border-box; 198 | -moz-box-sizing: border-box; 199 | box-sizing: border-box; 200 | } 201 | 202 | .dd-input-name, 203 | .dd-input-url { 204 | margin-bottom: 3px !important; 205 | } 206 | -------------------------------------------------------------------------------- /src/widgets/nestable/Nestable.php: -------------------------------------------------------------------------------- 1 | id)) { 109 | $this->id = $this->getId(); 110 | } 111 | 112 | if ($this->modelClass == null) { 113 | throw new InvalidConfigException('Param "modelClass" must be contain model name'); 114 | } 115 | 116 | if (null == $this->behaviorName) { 117 | throw new InvalidConfigException("No 'behaviorName' supplied on action initialization."); 118 | } 119 | 120 | if (null == $this->advancedUpdateRoute && ($controller = Yii::$app->controller)) { 121 | $this->advancedUpdateRoute = "{$controller->id}/update"; 122 | } 123 | 124 | if ($this->formFieldsCallable == null) { 125 | $this->formFieldsCallable = function ($form, $model) { 126 | /** @var ActiveForm $form */ 127 | echo $form->field($model, $this->nameAttribute); 128 | }; 129 | } 130 | 131 | /** @var ActiveRecord $model */ 132 | $model = new $this->modelClass; 133 | /** @var NestedSetsBehavior $behavior */ 134 | $behavior = $model->getBehavior($this->behaviorName); 135 | 136 | $this->_leftAttribute = $behavior->leftAttribute; 137 | $this->_rightAttribute = $behavior->rightAttribute; 138 | 139 | $items = $model::find() 140 | ->orderBy([$this->_leftAttribute => SORT_ASC]) 141 | ->all(); 142 | $this->_items = $this->prepareItems($items); 143 | } 144 | 145 | /** 146 | * @param ActiveRecord[] $items 147 | * @return array 148 | */ 149 | private function prepareItems($items) 150 | { 151 | $stack = []; 152 | $arraySet = []; 153 | 154 | foreach ($items as $item) { 155 | $stackSize = count($stack); 156 | while ($stackSize > 0 && $stack[$stackSize - 1]['rgt'] < $item->{$this->_leftAttribute}) { 157 | array_pop($stack); 158 | $stackSize--; 159 | } 160 | 161 | $link =& $arraySet; 162 | for ($i = 0; $i < $stackSize; $i++) { 163 | $link =& $link[$stack[$i]['index']]['children']; //navigate to the proper children array 164 | } 165 | $tmp = array_push($link, [ 166 | 'id' => $item->getPrimaryKey(), 167 | 'name' => $item->{$this->nameAttribute}, 168 | 'update-url' => Url::to([$this->advancedUpdateRoute, 'id' => $item->getPrimaryKey()]), 169 | 'children' => [] 170 | ]); 171 | array_push($stack, [ 172 | 'index' => $tmp - 1, 173 | 'rgt' => $item->{$this->_rightAttribute} 174 | ]); 175 | } 176 | 177 | return $arraySet; 178 | } 179 | 180 | /** 181 | * @param null $name 182 | * @return array 183 | */ 184 | private function getPluginOptions($name = null) 185 | { 186 | $options = ArrayHelper::merge($this->getDefaultPluginOptions(), $this->pluginOptions); 187 | 188 | if (isset($options[$name])) { 189 | return $options[$name]; 190 | } 191 | 192 | return $options; 193 | } 194 | 195 | /** 196 | * Работаем! 197 | */ 198 | public function run() 199 | { 200 | $this->registerActionButtonsAssets(); 201 | $this->actionButtons(); 202 | 203 | Pjax::begin([ 204 | 'id' => $this->id . '-pjax' 205 | ]); 206 | $this->registerPluginAssets(); 207 | $this->renderMenu(); 208 | $this->renderForm(); 209 | Pjax::end(); 210 | 211 | $this->actionButtons(); 212 | } 213 | 214 | /** 215 | * Register Asset manager 216 | */ 217 | private function registerPluginAssets() 218 | { 219 | NestableAsset::register($this->getView()); 220 | 221 | $view = $this->getView(); 222 | 223 | $pluginOptions = $this->getPluginOptions(); 224 | $pluginOptions = Json::encode($pluginOptions); 225 | $view->registerJs("$('#{$this->id}').nestable({$pluginOptions});"); 226 | $view->registerJs(" 227 | $('#{$this->id}-new-node-form').on('beforeSubmit', function(e){ 228 | $.ajax({ 229 | url: '{$this->getPluginOptions('createUrl')}', 230 | method: 'POST', 231 | data: $(this).serialize() 232 | }).success(function (data, textStatus, jqXHR) { 233 | $('#{$this->id}-new-node-modal').modal('hide') 234 | $.pjax.reload({container: '#{$this->id}-pjax'}); 235 | window.scrollTo(0, document.body.scrollHeight); 236 | }).fail(function (jqXHR) { 237 | alert(jqXHR.responseText); 238 | }); 239 | 240 | return false; 241 | }); 242 | "); 243 | } 244 | 245 | /** 246 | * Register Asset manager 247 | */ 248 | private function registerActionButtonsAssets() 249 | { 250 | $view = $this->getView(); 251 | $view->registerJs(" 252 | $('.{$this->id}-nestable-menu [data-action]').on('click', function(e) { 253 | e.preventDefault(); 254 | 255 | var target = $(e.target), 256 | action = target.data('action'); 257 | 258 | switch (action) { 259 | case 'expand-all': 260 | $('#{$this->id}').nestable('expandAll'); 261 | $('.{$this->id}-nestable-menu [data-action=\"expand-all\"]').hide(); 262 | $('.{$this->id}-nestable-menu [data-action=\"collapse-all\"]').show(); 263 | 264 | break; 265 | case 'collapse-all': 266 | $('#{$this->id}').nestable('collapseAll'); 267 | $('.{$this->id}-nestable-menu [data-action=\"expand-all\"]').show(); 268 | $('.{$this->id}-nestable-menu [data-action=\"collapse-all\"]').hide(); 269 | 270 | break; 271 | } 272 | }); 273 | "); 274 | } 275 | 276 | /** 277 | * Generate default plugin options 278 | * @return array 279 | */ 280 | private function getDefaultPluginOptions() 281 | { 282 | $options = [ 283 | 'namePlaceholder' => $this->getPlaceholderForName(), 284 | 'deleteAlert' => Yii::t('voskobovich/nestedsets', 285 | 'The nobe will be removed together with the children. Are you sure?'), 286 | 'newNodeTitle' => Yii::t('voskobovich/nestedsets', 'Enter the new node name'), 287 | ]; 288 | 289 | $controller = Yii::$app->controller; 290 | if ($controller) { 291 | $options['moveUrl'] = Url::to(["{$controller->id}/moveNode"]); 292 | $options['createUrl'] = Url::to(["{$controller->id}/createNode"]); 293 | $options['updateUrl'] = Url::to(["{$controller->id}/updateNode"]); 294 | $options['deleteUrl'] = Url::to(["{$controller->id}/deleteNode"]); 295 | } 296 | 297 | if ($this->moveUrl) { 298 | $this->pluginOptions['moveUrl'] = $this->moveUrl; 299 | } 300 | if ($this->createUrl) { 301 | $this->pluginOptions['createUrl'] = $this->createUrl; 302 | } 303 | if ($this->updateUrl) { 304 | $this->pluginOptions['updateUrl'] = $this->updateUrl; 305 | } 306 | if ($this->deleteUrl) { 307 | $this->pluginOptions['deleteUrl'] = $this->deleteUrl; 308 | } 309 | 310 | return $options; 311 | } 312 | 313 | /** 314 | * Get placeholder for Name input 315 | */ 316 | public function getPlaceholderForName() 317 | { 318 | return Yii::t('voskobovich/nestedsets', 'Node name'); 319 | } 320 | 321 | /** 322 | * Кнопки действий над виджетом 323 | */ 324 | public function actionButtons() 325 | { 326 | echo Html::beginTag('div', ['class' => "{$this->id}-nestable-menu"]); 327 | 328 | echo Html::beginTag('div', ['class' => 'btn-group']); 329 | echo Html::button(Yii::t('voskobovich/nestedsets', 'Add node'), [ 330 | 'data-toggle' => 'modal', 331 | 'data-target' => "#{$this->id}-new-node-modal", 332 | 'class' => 'btn btn-success' 333 | ]); 334 | echo Html::button(Yii::t('voskobovich/nestedsets', 'Collapse all'), [ 335 | 'data-action' => 'collapse-all', 336 | 'class' => 'btn btn-default' 337 | ]); 338 | echo Html::button(Yii::t('voskobovich/nestedsets', 'Expand all'), [ 339 | 'data-action' => 'expand-all', 340 | 'class' => 'btn btn-default', 341 | 'style' => 'display: none' 342 | ]); 343 | echo Html::endTag('div'); 344 | 345 | echo Html::endTag('div'); 346 | } 347 | 348 | /** 349 | * Вывод меню 350 | */ 351 | private function renderMenu() 352 | { 353 | echo Html::beginTag('div', ['class' => 'dd-nestable', 'id' => $this->id]); 354 | 355 | $menu = (count($this->_items) > 0) ? $this->_items : [ 356 | ['id' => 0, 'name' => $this->getPlaceholderForName()] 357 | ]; 358 | 359 | $this->printLevel($menu); 360 | 361 | echo Html::endTag('div'); 362 | } 363 | 364 | /** 365 | * Render form for new node 366 | */ 367 | private function renderForm() 368 | { 369 | /** @var ActiveRecord $model */ 370 | $model = new $this->modelClass; 371 | 372 | echo << 374 |