rutorika/sortable

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

9.2.0 2024-03-20 11:01 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可排序示例

注意: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