{"id":14975248,"url":"https://github.com/boxfrommars/rutorika-sortable","last_synced_at":"2025-04-08T11:12:14.288Z","repository":{"id":18178224,"uuid":"21291739","full_name":"boxfrommars/rutorika-sortable","owner":"boxfrommars","description":"Adds sortable behavior to Laravel Eloquent models","archived":false,"fork":false,"pushed_at":"2024-03-20T11:03:18.000Z","size":197,"stargazers_count":289,"open_issues_count":0,"forks_count":52,"subscribers_count":6,"default_branch":"master","last_synced_at":"2024-10-29T17:14:13.750Z","etag":null,"topics":["laravel","ordering","php","sortable","sorting"],"latest_commit_sha":null,"homepage":"","language":"PHP","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/boxfrommars.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2014-06-27T23:06:57.000Z","updated_at":"2024-08-29T07:43:44.000Z","dependencies_parsed_at":"2024-06-18T11:19:43.864Z","dependency_job_id":"0835f175-d75d-485a-beec-a900e9c50747","html_url":"https://github.com/boxfrommars/rutorika-sortable","commit_stats":{"total_commits":177,"total_committers":18,"mean_commits":9.833333333333334,"dds":"0.22033898305084743","last_synced_commit":"e68c06faaa42cd5f14f94b9a6fc0160d940fb12d"},"previous_names":[],"tags_count":39,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boxfrommars%2Frutorika-sortable","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boxfrommars%2Frutorika-sortable/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boxfrommars%2Frutorika-sortable/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/boxfrommars%2Frutorika-sortable/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/boxfrommars","download_url":"https://codeload.github.com/boxfrommars/rutorika-sortable/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":247829511,"owners_count":21002997,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["laravel","ordering","php","sortable","sorting"],"created_at":"2024-09-24T13:51:46.330Z","updated_at":"2025-04-08T11:12:14.257Z","avatar_url":"https://github.com/boxfrommars.png","language":"PHP","readme":"[![Build Status](https://travis-ci.org/boxfrommars/rutorika-sortable.svg?branch=master)](https://travis-ci.org/boxfrommars/rutorika-sortable) [![Scrutinizer Code Quality](https://scrutinizer-ci.com/g/boxfrommars/rutorika-sortable/badges/quality-score.png?b=master)](https://scrutinizer-ci.com/g/boxfrommars/rutorika-sortable/?branch=master) [![Latest Stable Version](https://poser.pugx.org/rutorika/sortable/v/stable)](https://packagist.org/packages/rutorika/sortable) [![Total Downloads](https://poser.pugx.org/rutorika/sortable/downloads)](https://packagist.org/packages/rutorika/sortable) [![Latest Unstable Version](https://poser.pugx.org/rutorika/sortable/v/unstable)](https://packagist.org/packages/rutorika/sortable) [![License](https://poser.pugx.org/rutorika/sortable/license)](https://packagist.org/packages/rutorika/sortable)\n\n## Laravel 5 - Demo\n\nhttps://github.com/boxfrommars/rutorika-sortable-demo5\n\n## Install\n\nInstall package through Composer\n\n```bash\ncomposer require rutorika/sortable\n```\n### Version Compatibility\n\n Laravel   | Rutorika Sortable\n:----------------|:----------\n 4               | 1.2.x (branch laravel4)\n \u003c=5.3           | 3.2.x\n 5.4             | 3.4.x\n 5.5             | 4.2.x\n 5.7             | 4.7.x\n 6.0             | 6.0.x\n 7.x, 8.x        | 8.x.x\n 9.x, 10.x, 11.x | 9.x.x\n\n## Sortable Trait\n\nAdds sortable behavior to Eloquent (Laravel) models\n\n### Usage\nAdd `position` field to your model (see below how to change this name):\n\n```php\n// schema builder example\npublic function up()\n{\n    Schema::create('articles', function (Blueprint $table) {\n        // ... other fields ...\n        $table-\u003einteger('position'); // Your model must have position field:\n    });\n}\n```\n\n\nAdd `\\Rutorika\\Sortable\\SortableTrait` to your Eloquent model.\n\n```php\nclass Article extends Model\n{\n    use \\Rutorika\\Sortable\\SortableTrait;\n}\n```\n\nif you want to use custom column name for position, set `$sortableField`:\n```php\nclass Article extends Model\n{\n    use \\Rutorika\\Sortable\\SortableTrait;\n\n    protected static $sortableField = 'somefield';\n}\n```\n\nNow you can move your entities with methods `moveBefore($entity)` and `moveAfter($entity)` (you dont need to save\nmodel after that, it has saved already):\n\n```php\n$entity = Article::find(1);\n\n$positionEntity = Article::find(10)\n\n$entity-\u003emoveAfter($positionEntity);\n\n// if $positionEntity-\u003eposition is 14, then $entity-\u003eposition is 15 now\n```\n\nAlso this trait automatically defines entity position on the `create` event, so you do not need to add `position` manually, just create entities as usual:\n\n```php\n$article = new Article();\n$article-\u003etitle = $faker-\u003esentence(2);\n$article-\u003edescription = $faker-\u003eparagraph();\n$article-\u003esave();\n```\n\nThis entity will be at position `entitiesMaximumPosition + 1`\n\nTo get ordered entities use the `sorted` scope:\n\n```php\n$articles = Article::sorted()-\u003eget();\n```\n\n\u003e ** Note **: Resorting does not take place after a record is deleted. Gaps in positional values do not affect the ordering of your lists. However, if you prefer to prevent gaps you can reposition your models using the `deleting` event. Something like:\n\n```php\n// YourAppServiceProvider\n\nYourModel::deleting(function ($model) {\n    $model-\u003enext()-\u003edecrement('position');\n});\n```\n \u003e You need rutorika-sortable \u003e=2.3 to use `-\u003enext()`\n\n### Sortable groups\n\nif you want group entity ordering by field, add to your model\n```php\nprotected static $sortableGroupField = 'fieldName';\n```\nnow moving and ordering will be encapsulated by this field.\n\nIf you want group entity ordering by many fields, use as an array:\n```php\nprotected static $sortableGroupField = ['fieldName1','fieldName2'];\n```\n\n### Sortable many to many\n\nLet's assume your database structure is\n\n```\nposts\n    id\n    title\n\ntags\n    id\n    title\n\npost_tag\n    post_id\n    tag_id\n```\n\nand you want to order *tags* for each *post*\n\nAdd `position` column to the pivot table (you can use any name you want, but `position` is used by default)\n\n```\npost_tag\n    post_id\n    tag_id\n    position\n```\n\nAdd `\\Rutorika\\Sortable\\BelongsToSortedManyTrait` to your `Post` model and define `belongsToSortedMany` relation provided by this trait:\n\n```php\nclass Post extends Model {\n\n    use BelongsToSortedManyTrait;\n\n    public function tags()\n    {\n        return $this-\u003ebelongsToSortedMany('\\App\\Tag');\n    }\n}\n```\n\n\u003e Note: `$this-\u003ebelongsToSortedMany` has different signature then `$this-\u003ebelongsToMany` -- the second argument for this method is `$orderColumn` (`'position'` by default), next arguments are the same\n\nAttaching tags to post with `save`/`sync`/`attach` methods will set proper position\n\n```php\n    $post-\u003etags()-\u003esave($tag) // or\n    $post-\u003etags()-\u003eattach($tag-\u003eid) // or\n    $post-\u003etags()-\u003esync([$tagId1, $tagId2, /* ...tagIds */])\n```\n\nGetting related model is sorted by position\n\n```php\n$post-\u003etags; // ordered by position by default\n```\n\nYou can reorder tags for given post\n\n```php\n    $post-\u003etags()-\u003emoveBefore($entityToMove, $whereToMoveEntity); // or\n    $post-\u003etags()-\u003emoveAfter($entityToMove, $whereToMoveEntity);\n```\n\nMany to many demo: http://sortable5.boxfrommars.ru/posts ([code](https://github.com/boxfrommars/rutorika-sortable-demo5))\n\nYou can also use polymorphic many to many relation with sortable behavour by using the `MorphsToSortedManyTrait` trait and returning `$this-\u003emorphToSortedMany()` from relation method.\n\nBy following the Laravel polymorphic many to many table relation your tables should look like\n\n```\nposts\n    id\n    title\n\ntags\n    id\n    title\n\ntaggables\n    tag_id\n    position\n    taggable_id\n    taggable_type\n```\n\nAnd your model like\n\n```php\nclass Post extends Model {\n\n    use MorphToSortedManyTrait;\n\n    public function tags()\n    {\n        return $this-\u003emorphToSortedMany('\\App\\Tag', 'taggable');\n    }\n}\n```\n\n## Sortable Controller\n\nAlso this package provides `\\Rutorika\\Sortable\\SortableController`, which handle requests to sort entities\n\n### Usage\nAdd the service provider to `config/app.php`\n\n```php\n'providers' =\u003e array(\n    // providers...\n\n    'Rutorika\\Sortable\\SortableServiceProvider',\n)\n```\n\npublish the config:\n\n```bash\nphp artisan vendor:publish\n```\n\nAdd models you need to sort in the config `config/sortable.php`:\n\n```php\n'entities' =\u003e array(\n     'articles' =\u003e '\\App\\Article', // entityNameForUseInRequest =\u003e ModelName\n     // or\n     'articles' =\u003e ['entity' =\u003e '\\App\\Article'],\n     // or for many to many\n     'posts' =\u003e [\n        'entity' =\u003e '\\App\\Post',\n        'relation' =\u003e 'tags' // relation name (method name which returns $this-\u003ebelongsToSortedMany)\n     ]\n),\n```\n\nAdd route to the `sort` method of the controller:\n\n```php\nRoute::post('sort', '\\Rutorika\\Sortable\\SortableController@sort');\n```\n\nNow if you post to this route valid data:\n\n```php\n$validator = \\Validator::make(\\Input::all(), array(\n    'type' =\u003e array('required', 'in:moveAfter,moveBefore'), // type of move, moveAfter or moveBefore\n    'entityName' =\u003e array('required', 'in:' . implode(',', array_keys($sortableEntities))), // entity name, 'articles' in this example\n    'positionEntityId' =\u003e 'required|numeric', // id of relative entity\n    'id' =\u003e 'required|numeric', // entity id\n));\n\n// or for many to many\n\n$validator = \\Validator::make(\\Input::all(), array(\n    'type' =\u003e array('required', 'in:moveAfter,moveBefore'), // type of move, moveAfter or moveBefore\n    'entityName' =\u003e array('required', 'in:' . implode(',', array_keys($sortableEntities))), // entity name, 'articles' in this example\n    'positionEntityId' =\u003e 'required|numeric', // id of relative entity\n    'id' =\u003e 'required|numeric', // entity id\n    'parentId' =\u003e 'required|numeric', // parent entity id\n));\n\n```\n\nThen entity with `\\Input::get('id')` id will be moved relative by entity with `\\Input::get('positionEntityId')` id.\n\nFor example, if request data is:\n\n```\ntype:moveAfter\nentityName:articles\nid:3\npositionEntityId:14\n```\nthen the article with id 3 will be moved after the article with id 14.\n\n### jQuery UI sortable example\n\n\u003e Note: Laravel 5 has csrf middleware enabled by default, so you should setup ajax requests: http://laravel.com/docs/5.0/routing#csrf-protection\n\nTemplate\n\n```html\n\u003ctable class=\"table table-striped table-hover\"\u003e\n    \u003ctbody class=\"sortable\" data-entityname=\"articles\"\u003e\n    @foreach ($articles as $article)\n    \u003ctr data-itemId=\"{{{ $article-\u003eid }}}\"\u003e\n        \u003ctd class=\"sortable-handle\"\u003e\u003cspan class=\"glyphicon glyphicon-sort\"\u003e\u003c/span\u003e\u003c/td\u003e\n        \u003ctd class=\"id-column\"\u003e{{{ $article-\u003eid }}}\u003c/td\u003e\n        \u003ctd\u003e{{{ $article-\u003etitle }}}\u003c/td\u003e\n    \u003c/tr\u003e\n    @endforeach\n    \u003c/tbody\u003e\n\u003c/table\u003e\n```\n\nTemplate for many to many ordering\n\n```html\n\u003ctable class=\"table table-striped table-hover\"\u003e\n    \u003ctbody class=\"sortable\" data-entityname=\"posts\"\u003e\n    @foreach ($post-\u003etags as $tag)\n    \u003ctr data-itemId=\"{{ $tag-\u003eid }}\" data-parentId=\"{{ $post-\u003eid }}\"\u003e\n        \u003ctd class=\"sortable-handle\"\u003e\u003cspan class=\"glyphicon glyphicon-sort\"\u003e\u003c/span\u003e\u003c/td\u003e\n        \u003ctd class=\"id-column\"\u003e{{ $tag-\u003eid }}\u003c/td\u003e\n        \u003ctd\u003e{{ $tag-\u003etitle }}\u003c/td\u003e\n    \u003c/tr\u003e\n    @endforeach\n    \u003c/tbody\u003e\n\u003c/table\u003e\n```\n\n\n```js\n    /**\n     *\n     * @param type string 'insertAfter' or 'insertBefore'\n     * @param entityName\n     * @param id\n     * @param positionId\n     */\n    var changePosition = function(requestData){\n        $.ajax({\n            'url': '/sort',\n            'type': 'POST',\n            'data': requestData,\n            'success': function(data) {\n                if (data.success) {\n                    console.log('Saved!');\n                } else {\n                    console.error(data.errors);\n                }\n            },\n            'error': function(){\n                console.error('Something wrong!');\n            }\n        });\n    };\n\n    $(document).ready(function(){\n        var $sortableTable = $('.sortable');\n        if ($sortableTable.length \u003e 0) {\n            $sortableTable.sortable({\n                handle: '.sortable-handle',\n                axis: 'y',\n                update: function(a, b){\n\n                    var entityName = $(this).data('entityname');\n                    var $sorted = b.item;\n\n                    var $previous = $sorted.prev();\n                    var $next = $sorted.next();\n\n                    if ($previous.length \u003e 0) {\n                        changePosition({\n                            parentId: $sorted.data('parentid'),\n                            type: 'moveAfter',\n                            entityName: entityName,\n                            id: $sorted.data('itemid'),\n                            positionEntityId: $previous.data('itemid')\n                        });\n                    } else if ($next.length \u003e 0) {\n                        changePosition({\n                            parentId: $sorted.data('parentid'),\n                            type: 'moveBefore',\n                            entityName: entityName,\n                            id: $sorted.data('itemid'),\n                            positionEntityId: $next.data('itemid')\n                        });\n                    } else {\n                        console.error('Something wrong!');\n                    }\n                },\n                cursor: \"move\"\n            });\n        }\n    });\n```\n\n## Development\n\n```\nsudo docker build -t rutorika-sortable .\nsudo docker run --volume $PWD:/project --rm --interactive --tty --user $(id -u):$(id -g) rutorika-sortable vendor/bin/phpunit\n```\n\n","funding_links":[],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fboxfrommars%2Frutorika-sortable","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fboxfrommars%2Frutorika-sortable","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fboxfrommars%2Frutorika-sortable/lists"}