|
| 1 | +# Yii 2 Sortable |
| 2 | + |
| 3 | +Sortable ActiveRecord for Yii 2 framework |
| 4 | + |
| 5 | +- [Installation](#installation) |
| 6 | +- [Features](#features) |
| 7 | +- [Behaviors types](#behaviors-types) |
| 8 | +- [Preparing table structure](#preparing-table-structure) |
| 9 | +- [Attaching behavior](#attaching-behavior) |
| 10 | +- [Configuring behavior](#configuring-behavior) |
| 11 | +- [Changing order of models inside sortable scope](#changing-order-of-models-inside-sortable-scope) |
| 12 | +- [GUI for changing order](#gui-for-changing-order) |
| 13 | +- [Custom GUI for changing order](#custom-gui-for-changing-order) |
| 14 | + |
| 15 | +## Installation |
| 16 | + |
| 17 | +The preferred way to install this extension is through [composer](http://getcomposer.org/download/). |
| 18 | + |
| 19 | +Either run |
| 20 | + |
| 21 | +``` |
| 22 | +php composer.phar require --prefer-dist arogachev/yii2-sortable |
| 23 | +``` |
| 24 | + |
| 25 | +or add |
| 26 | + |
| 27 | +``` |
| 28 | +"arogachev/yii2-sortable": "*" |
| 29 | +``` |
| 30 | + |
| 31 | +to the require section of your `composer.json` file. |
| 32 | + |
| 33 | +## Features |
| 34 | + |
| 35 | +- Several implemented algorithms. Choose one to fit your needs. |
| 36 | +- Setting of sortable scope. In this case the order of models in each scope is managed separately. |
| 37 | +- Additional setting of sortable condition. For example, if the model can be marked as active or deleted, |
| 38 | +you can additionally specify that condition and it will be considered when changing these attributes values. |
| 39 | +- Order auto adjustment when adding new model, removing out of sortable scope, |
| 40 | +moving between the sortable scopes. |
| 41 | +- Changing order of models inside sortable scope. |
| 42 | +- GUI for managing order of models in `GridView` (`SortableColumn`). |
| 43 | +- Sort controller for simplifying writing of own GUI |
| 44 | + |
| 45 | +## Behaviors types |
| 46 | + |
| 47 | +There are several behaviors to choose from: |
| 48 | + |
| 49 | +- `ContinuousNumericalSortableBehavior` |
| 50 | +- `IntervalNumericalSortableBehavior` |
| 51 | +- `LinkedListSortableBehavior` (currently not implemented) |
| 52 | + |
| 53 | +The first two are numerical behaviors and they have one thing in common - they store position of each model as number. |
| 54 | + |
| 55 | +`ContinuousNumericalSortableBehavior`: |
| 56 | + |
| 57 | +Stored number is equal to exact position. |
| 58 | + |
| 59 | +**Advantages:** |
| 60 | + |
| 61 | +- You can get current position without additional queries |
| 62 | + |
| 63 | +**Disadvantages:** |
| 64 | + |
| 65 | +- Amount of `UPDATE` queries can be large depending on amount of sortable models and situation. |
| 66 | +It relates to adjustment order. For example no extra queries will be performed in case of switching models |
| 67 | +with 3 and 4 position (only 2 `UPDATE` queries). But if you have 1000 models and you move the last model |
| 68 | +to the very beginning there will be 1000 `UPDATE` queries (so it depends on interval length). |
| 69 | + |
| 70 | +`IntervalNumericalSortableBehavior`: |
| 71 | + |
| 72 | +The numbers are stored with certain intervals (initially with equal size). |
| 73 | +You can see the basic description of the used algorithm [here](http://stackoverflow.com/questions/6804166/what-is-the-most-efficient-way-to-store-a-sort-order-on-a-group-of-records-in-a/6804302#6804302). |
| 74 | + |
| 75 | +**Advantages:** |
| 76 | + |
| 77 | +- For adding or deletion there is no need to adjust order of other models. And for changing order for most of the times |
| 78 | +only few `SELECT` and one `UPDATE` query will be executed. |
| 79 | +The full adjustment of order of all models inside of sortable scope is only required in case of conflict. |
| 80 | +The conflicts don't happen often if you set interval size big enough |
| 81 | +and don't move models to the same position over and over again. |
| 82 | + |
| 83 | +**Disadvantages:** |
| 84 | + |
| 85 | +- For getting positions of models extra query is used. |
| 86 | + |
| 87 | +### Preparing table structure |
| 88 | + |
| 89 | +In case of using numerical behaviors add this to migration: |
| 90 | + |
| 91 | +```php |
| 92 | +$this->addColumn('table_name', 'sort', Schema::TYPE_INTEGER . ' NOT NULL'); |
| 93 | +``` |
| 94 | + |
| 95 | +## Attaching behavior |
| 96 | + |
| 97 | +Add this to your model for minimal setup: |
| 98 | + |
| 99 | +```php |
| 100 | +use arogachev\sortable\behaviors\numerical\ContinuousNumericalSortableBehavior; |
| 101 | +``` |
| 102 | + |
| 103 | +```php |
| 104 | +/** |
| 105 | + * @inheritdoc |
| 106 | + */ |
| 107 | +public function behaviors() |
| 108 | +{ |
| 109 | + return [ |
| 110 | + [ |
| 111 | + 'class' => ContinuousNumericalSortableBehavior::className(), |
| 112 | + ], |
| 113 | + ]; |
| 114 | +} |
| 115 | +``` |
| 116 | + |
| 117 | +### Configuring behavior |
| 118 | + |
| 119 | +**Common properties for all behaviors:** |
| 120 | + |
| 121 | +`scope` - sortable scope. Specify it if you want to separate models by condition |
| 122 | +and manage order independently in each one. It expects closure returning `ActiveQuery`, but `where` part must be |
| 123 | +specified as array only. Example: |
| 124 | + |
| 125 | +```php |
| 126 | +function () { |
| 127 | + return Question::find()->where(['test_id' => $this->test_id]); |
| 128 | +} |
| 129 | +``` |
| 130 | + |
| 131 | +If this property is not set, all models considered as one sortable scope. |
| 132 | + |
| 133 | +`sortableCondition` - additional property to filter sortable models. You should specify it as conditional array: |
| 134 | + |
| 135 | +```php |
| 136 | +[ |
| 137 | + 'is_active' => 1, |
| 138 | + 'is_deleted' => 0, |
| 139 | +], |
| 140 | +``` |
| 141 | + |
| 142 | +`prependAdded` - insert added sortable model to the beginning of sortable scope. Defaults to `false` which means |
| 143 | +inserting to the end. |
| 144 | + |
| 145 | +`access` - closure for checking access to sort for current user. Example: |
| 146 | + |
| 147 | +```php |
| 148 | +function () { |
| 149 | + return Yii::$app->user->can('questions.sort'); |
| 150 | +} |
| 151 | +``` |
| 152 | + |
| 153 | +**Numerical behaviors properties:** |
| 154 | + |
| 155 | +`sortAttribute` - name of the sort attribute column. Defaults to `sort`. |
| 156 | + |
| 157 | +**`IntervalNumericalSortableBehavior` properties:** |
| 158 | + |
| 159 | +`intervalSize` - size of the interval. Defaults to `1000`. When specifying bigger numbers, |
| 160 | +conflicts will happen less often. |
| 161 | + |
| 162 | +`increasingLimit` - the number of times user can continuously move item to the end of the sortable scope. |
| 163 | +Used to prevent increasing of numbers. Defaults to `10`. |
| 164 | + |
| 165 | +## Changing order of models inside sortable scope |
| 166 | + |
| 167 | +The behavior provides few methods to change any sortable model order: |
| 168 | + |
| 169 | +- `moveToPosition($position)` - basic method for moving model to any position inside sortable scope |
| 170 | +- `moveBefore($pk = null)` - move model before another model of this sortable scope. |
| 171 | +If `$pk` is not specified it will be moved to the very end |
| 172 | +- `moveAfter($pk = null)` - move model after another model of this sortable scope |
| 173 | +If `$pk` is not specified it will be moved to the very beginning |
| 174 | +- `moveBack()` - move back by one position |
| 175 | +- `moveForward()` - move forward by one position |
| 176 | +- `moveAsFirst()` - move to the very beginning |
| 177 | +- `moveAsLast()` - move to the very end |
| 178 | + |
| 179 | +## GUI for changing order |
| 180 | + |
| 181 | +There is special `SortableColumn` for `GridView`. |
| 182 | + |
| 183 | +**Features:** |
| 184 | + |
| 185 | +- It doesn't force you to use the whole another `GridView` |
| 186 | +- No need to attach additional actions every time |
| 187 | +- Multiple `GridView` on one page support |
| 188 | +- Displaying current position |
| 189 | +- Inline editing of current position |
| 190 | +- Moving with drag and drop (with `jQuery UI Sortable`) with special handle icon, |
| 191 | +so you can interact with other data without triggering sort change |
| 192 | +- Moving back and forward by one position |
| 193 | +- Moving as first and last |
| 194 | + |
| 195 | +Include once this to your application config: |
| 196 | + |
| 197 | +```php |
| 198 | +'controllerMap' => [ |
| 199 | + 'sort' => [ |
| 200 | + 'class' => 'arogachev\sortable\controllers\SortController', |
| 201 | + ], |
| 202 | +], |
| 203 | +``` |
| 204 | + |
| 205 | +Then configure `GridView`: |
| 206 | + |
| 207 | +- Wrap it with `Pjax` widget for working without page reload |
| 208 | +- Add `id` for unchangeable root container |
| 209 | +- Include column in `columns` section |
| 210 | + |
| 211 | +```php |
| 212 | +use arogachev\grid\SortableColumn; |
| 213 | +``` |
| 214 | + |
| 215 | +```php |
| 216 | +<div class="question-index" id="question-sortable"> |
| 217 | + <?php Pjax::begin(); ?> |
| 218 | + |
| 219 | + <?= GridView::widget([ |
| 220 | + // Other configuration |
| 221 | + 'columns' => [ |
| 222 | + [ |
| 223 | + 'class' => SortableColumn::className(), |
| 224 | + 'gridContainerId' => 'question-sortable', |
| 225 | + ], |
| 226 | + // Other columns |
| 227 | + ], |
| 228 | + ]) ?> |
| 229 | + |
| 230 | + <?php Pjax::end(); ?> |
| 231 | +</div> |
| 232 | +``` |
| 233 | + |
| 234 | +You can configure display through `template` and `buttons` properties (similar to [ActionColumn](http://www.yiiframework.com/doc-2.0/yii-grid-actioncolumn.html)). |
| 235 | + |
| 236 | +The available tags are: |
| 237 | + |
| 238 | +- `currentPosition` |
| 239 | +- `moveWithDragAndDrop` |
| 240 | +- `moveForward` |
| 241 | +- `moveBack` |
| 242 | +- `moveAsFirst` |
| 243 | +- `moveAsLast` |
| 244 | + |
| 245 | +You can extend it with your own. Example of overriding: |
| 246 | + |
| 247 | +```php |
| 248 | +'template' => '<div class="sortable-section">{moveWithDragAndDrop}</div> |
| 249 | +<div class="sortable-section">{currentPosition}</div> |
| 250 | +<div class="sortable-section">{moveForward} {moveBack}</div>', |
| 251 | +'buttons' => [ |
| 252 | + 'moveForward' => function () { |
| 253 | + return Html::tag('i', '', [ |
| 254 | + 'class' => 'fa fa-arrow-circle-left', |
| 255 | + 'title' => Yii::t('sortable', 'Move forward'), |
| 256 | + ]); |
| 257 | + }, |
| 258 | + 'moveBack' => function () { |
| 259 | + return Html::tag('i', '', [ |
| 260 | + 'class' => 'fa fa-arrow-circle-right', |
| 261 | + 'title' => Yii::t('sortable', 'Move back'), |
| 262 | + ]); |
| 263 | + }, |
| 264 | +], |
| 265 | +``` |
| 266 | + |
| 267 | +## Custom GUI for changing order |
| 268 | + |
| 269 | +If you want to write your own GUI for changing order without using `GridView`, you can use the `SortController` actions: |
| 270 | + |
| 271 | +- `move-before` (requires `pk` of the next element after move sent via `POST`) |
| 272 | +- `move-after` (requires `pk` of the previous element after move sent via `POST`) |
| 273 | +- `move-back` |
| 274 | +- `move-forward` |
| 275 | +- `move-as-first` |
| 276 | +- `move-as-last` |
| 277 | +- `move-to-position` (requires `position` sent via `POST`) |
| 278 | + |
| 279 | +For all of the actions these two parameters must exist in `POST`: |
| 280 | + |
| 281 | +- `modelClass` - model full class name with namespace |
| 282 | +- `modelPk` - moved model primary key value (pass object in case of primary keys) |
0 commit comments