svv-team / sortable

为 Laravel Eloquent 模型添加可排序行为和排序。支持分组和一对多。

7.0.1 2020-03-14 12:03 UTC

README

Build Status Scrutinizer Code Quality Latest Stable Version Total Downloads Latest Unstable Version License

Laravel 5 - 示例

https://github.com/boxfrommars/rutorika-sortable-demo5

安装

通过 Composer 安装包

composer require rutorika/sortable

版本兼容性

可排序特征

为 Eloquent (Laravel) 模型添加可排序行为

用法

position 字段添加到您的模型中(以下是如何更改此名称的示例)

// schema builder example
public function up()
{
    Schema::create('articles', function (Blueprint $table) {
        // ... other fields ...
        $table->integer('position'); // Your model must have position field:
    });
}

\Rutorika\Sortable\SortableTrait 添加到您的 Eloquent 模型。

class Article extends Model
{
    use \Rutorika\Sortable\SortableTrait;
}

如果您想使用自定义列名作为位置,设置 $sortableField

class Article extends Model
{
    use \Rutorika\Sortable\SortableTrait;

    protected static $sortableField = 'somefield';
}

现在您可以使用 moveBefore($entity)moveAfter($entity) 方法移动您的实体(之后您不需要保存模型,它已经保存了)

$entity = Article::find(1);

$positionEntity = Article::find(10)

$entity->moveAfter($positionEntity);

// if $positionEntity->position is 14, then $entity->position is 15 now

此外,此特征会自动在 create 事件上定义实体位置,因此您无需手动添加 position,只需像往常一样创建实体即可

$article = new Article();
$article->title = $faker->sentence(2);
$article->description = $faker->paragraph();
$article->save();

此实体将位于 entitiesMaximumPosition + 1 位置

要获取排序后的实体,请使用 sorted 范围

$articles = Article::sorted()->get();

**注意**:删除记录后不会重新排序。位置值中的间隔不会影响您的列表排序。但是,如果您希望防止间隔,可以使用 deleting 事件重新定位您的模型。例如

// YourAppServiceProvider

YourModel::deleting(function ($model) {
    $model->next()->decrement('position');
});

您需要 rutorika-sortable >=2.3 才能使用 ->next()

可排序分组

如果您想按字段对实体进行分组排序,请将以下内容添加到您的模型中

protected static $sortableGroupField = 'fieldName';

现在移动和排序将封装在此字段中。

如果您想按多个字段对实体进行分组排序,请使用数组

protected static $sortableGroupField = ['fieldName1','fieldName2'];

可排序一对多

假设您的数据库结构如下

posts
    id
    title

tags
    id
    title

post_tag
    post_id
    tag_id

并且您想为每个 post 排序 tags

position 列添加到关联表中(您可以使用任何名称,但默认使用 position

post_tag
    post_id
    tag_id
    position

\Rutorika\Sortable\BelongsToSortedManyTrait 添加到您的 Post 模型,并定义由此特征提供的 belongsToSortedMany 关系

class Post extends Model {

    use BelongsToSortedManyTrait;

    public function tags()
    {
        return $this->belongsToSortedMany('\App\Tag');
    }
}

注意:$this->belongsToSortedMany$this->belongsToMany 的签名不同 -- 此方法的第二个参数是 $orderColumn(默认为 'position'),其他参数相同

使用 save/sync/attach 方法附加标签到帖子将设置正确位置

    $post->tags()->save($tag) // or
    $post->tags()->attach($tag->id) // or
    $post->tags()->sync([$tagId1, $tagId2, /* ...tagIds */])

获取相关模型时将按位置排序

$post->tags; // ordered by position by default

您可以重新排序给定帖子的标签

    $post->tags()->moveBefore($entityToMove, $whereToMoveEntity); // or
    $post->tags()->moveAfter($entityToMove, $whereToMoveEntity);

一对多示例:http://sortable5.boxfrommars.ru/posts代码

您还可以通过使用 MorphsToSortedManyTrait 特征并从关系方法返回 $this->morphToSortedMany() 来使用可排序行为的多态一对多关系。

根据 Laravel 多态一对多表关系,您的表应如下所示

posts
    id
    title

tags
    id
    title

taggables
    tag_id
    position
    taggable_id
    taggable_type

并且您的模型如下所示

class Post extends Model {

    use MorphToSortedManyTrait;

    public function tags()
    {
        return $this->morphToSortedMany('\App\Tag', 'taggable');
    }
}

可排序控制器

此外,此包还提供了 \Rutorika\Sortable\SortableController,它处理对排序实体的请求

用法

将服务提供者添加到 config/app.php

'providers' => array(
    // providers...

    'Rutorika\Sortable\SortableServiceProvider',
)

发布配置

php artisan vendor:publish

在配置 config/sortable.php 中添加您需要排序的模型

'entities' => array(
     'articles' => '\App\Article', // entityNameForUseInRequest => ModelName
     // or
     'articles' => ['entity' => '\App\Article'],
     // or for many to many
     'posts' => [
        'entity' => '\App\Post',
        'relation' => 'tags' // relation name (method name which returns $this->belongsToSortedMany)
     ]
),

将路由添加到控制器的 sort 方法

Route::post('sort', '\Rutorika\Sortable\SortableController@sort');

现在如果您向此路由发送有效数据

$validator = \Validator::make(\Input::all(), array(
    'type' => array('required', 'in:moveAfter,moveBefore'), // type of move, moveAfter or moveBefore
    'entityName' => array('required', 'in:' . implode(',', array_keys($sortableEntities))), // entity name, 'articles' in this example
    'positionEntityId' => 'required|numeric', // id of relative entity
    'id' => 'required|numeric', // entity id
));

// or for many to many

$validator = \Validator::make(\Input::all(), array(
    'type' => array('required', 'in:moveAfter,moveBefore'), // type of move, moveAfter or moveBefore
    'entityName' => array('required', 'in:' . implode(',', array_keys($sortableEntities))), // entity name, 'articles' in this example
    'positionEntityId' => 'required|numeric', // id of relative entity
    'id' => 'required|numeric', // entity id
    'parentId' => 'required|numeric', // parent entity id
));

则具有 \Input::get('id') id 的实体将与具有 \Input::get('positionEntityId') id 的实体相对移动。

例如,如果请求数据为

type:moveAfter
entityName:articles
id:3
positionEntityId:14

则 id 为 3 的文章将移动到 id 为 14 的文章之后。

jQuery UI sortable 示例

注意:Laravel 5 默认启用了 csrf 中间件,因此您应该设置ajax请求: https://laravel.net.cn/docs/5.0/routing#csrf-protection

模板

<table class="table table-striped table-hover">
    <tbody class="sortable" data-entityname="articles">
    @foreach ($articles as $article)
    <tr data-itemId="{{{ $article->id }}}">
        <td class="sortable-handle"><span class="glyphicon glyphicon-sort"></span></td>
        <td class="id-column">{{{ $article->id }}}</td>
        <td>{{{ $article->title }}}</td>
    </tr>
    @endforeach
    </tbody>
</table>

多对多排序的模板

<table class="table table-striped table-hover">
    <tbody class="sortable" data-entityname="posts">
    @foreach ($post->tags as $tag)
    <tr data-itemId="{{ $tag->id }}" data-parentId="{{ $post->id }}">
        <td class="sortable-handle"><span class="glyphicon glyphicon-sort"></span></td>
        <td class="id-column">{{ $tag->id }}</td>
        <td>{{ $tag->title }}</td>
    </tr>
    @endforeach
    </tbody>
</table>
    /**
     *
     * @param type string 'insertAfter' or 'insertBefore'
     * @param entityName
     * @param id
     * @param positionId
     */
    var changePosition = function(requestData){
        $.ajax({
            'url': '/sort',
            'type': 'POST',
            'data': requestData,
            'success': function(data) {
                if (data.success) {
                    console.log('Saved!');
                } else {
                    console.error(data.errors);
                }
            },
            'error': function(){
                console.error('Something wrong!');
            }
        });
    };

    $(document).ready(function(){
        var $sortableTable = $('.sortable');
        if ($sortableTable.length > 0) {
            $sortableTable.sortable({
                handle: '.sortable-handle',
                axis: 'y',
                update: function(a, b){

                    var entityName = $(this).data('entityname');
                    var $sorted = b.item;

                    var $previous = $sorted.prev();
                    var $next = $sorted.next();

                    if ($previous.length > 0) {
                        changePosition({
                            parentId: $sorted.data('parentid'),
                            type: 'moveAfter',
                            entityName: entityName,
                            id: $sorted.data('itemid'),
                            positionEntityId: $previous.data('itemid')
                        });
                    } else if ($next.length > 0) {
                        changePosition({
                            parentId: $sorted.data('parentid'),
                            type: 'moveBefore',
                            entityName: entityName,
                            id: $sorted.data('itemid'),
                            positionEntityId: $next.data('itemid')
                        });
                    } else {
                        console.error('Something wrong!');
                    }
                },
                cursor: "move"
            });
        }
    });

开发

sudo docker build -t rutorika-sortable .
sudo docker run --volume $PWD:/project --rm --interactive --tty --user $(id -u):$(id -g) rutorika-sortable vendor/bin/phpunit