sjaakp/yii2-sortable-behavior

在 Yii2 中排序 ActiveRecords 及相关记录。

安装次数: 63,204

依赖: 1

建议: 0

安全性: 0

星标: 35

关注者: 7

分支: 17

开放问题: 2

类型:yii2-extension

1.2.1 2024-06-29 13:43 UTC

This package is auto-updated.

Last update: 2024-08-29 13:59:56 UTC


README

Latest Stable Version Total Downloads License

此包包含五个类来处理 ActiveRecords 的排序

  • SortableGridView - 扩展的 GridView 小部件;
  • SortableListView - 扩展的 ListView 小部件;
  • Sortable - ActiveRecord 行为,用于处理记录本身的排序或一对多相关记录的排序;
  • PivotRecord - 多对多关系中连接表 ActiveRecord 的基类。
  • MMSortable - ActiveRecord 行为,用于处理多对多相关记录的排序。

之前 Sortable 的版本依赖于 jQuery。现在没有任何依赖。它应该在所有现代桌面浏览器中运行。在编写本文时,新的 HTML 拖放,以及因此 Sortable,仅适用于少数 移动浏览器

仍然可以找到旧的 jQuery 相关的小部件。有些人可能更喜欢它们的审美。

Sortable 套件的演示 在这里

安装

安装 Sortable 的首选方式是通过 Composer。您可以将以下内容添加到 composer.json 文件的 require 部分中

"sjaakp/yii2-sortable-behavior": "*"

或者运行

composer require sjaakp/yii2-sortable-behavior "*"

您可以通过 下载 ZIP 格式的源代码 手动安装 Sortable

SortableGridView 和 SortableListView

这些小部件是从标准的 GridView 和 ListView 类派生出来的,但有一个额外的功能:可以通过拖放(使用 HTML 拖放 功能)将项目移动到另一个位置。如果项目被拖放到新的位置,将发布一条包含以下数据的消息

  • key:项目的主键值;
  • pos:项目的零索引新位置。

SortableGridViewSortableListView 有一个可配置的属性

orderUrl

array|string。排序操作后调用的 URL。格式与 yii\helpers\Url::toRoute 相同。

SortableGridViewSortableListView 不与 Pjax 小部件 一起使用。然而,Sortable 与分页数据小部件的实用性并不高。

Sortable

使用此行为,ActiveRecord 变得“可排序”。它有一个可配置的属性和一个额外的方法

orderAttribute

string|array。ActiveRecord 的排序属性。

它可以是以下值之一

  • string - 排序属性名称;
  • 数组
    • string - 排序属性名称,
    • foreignKeyName => orderAttrName - 限制排序到具有相同外键值的 ActiveRecords,即相同的所有者。

默认为 "ord"

order()

public function order($newPosition, $foreignKeyName = null, where = [])

此方法通过操作排序属性将所有者放在新的位置 $newPosition。排序属性是一个零索引的连续整数。

如果 $foreignKeyNamenull(默认值),则所有记录都将按顺序排列。

如果它是一个字符串,排序将限制为具有相同 $foreignKeyName 值的记录。$foreignKeyName 必须是 orderAttribute 中的键。这对于一对多关系非常有用。

$where 是一个数组,包含使用 QueryInterface where() 格式使用的额外条件。
它可以用来添加额外的外键约束。默认:[ ](空数组)。

使用场景 1

简单排序

假设我们有一个非常简单的电影标题表

CREATE TABLE movie (
  id int(10) unsigned NOT NULL AUTO_INCREMENT,
  ord int(10) unsigned NOT NULL,
  title tinytext NOT NULL,
  PRIMARY KEY (id)
)

其中 ord 将是我们的排序属性。

我们可以使 Movie ActiveRecord 可排序,如下所示

class Movie extends ActiveRecord
{
    public function behaviors( ) {
	    return [
	        [
	            'class' => 'sjaakp\sortable\Sortable',
	        ],
	    ];
	}
	...
}

在控制器中,我们定义了一个 index 动作和一个 order 动作

class MovieController extends Controller
{
	...
    public function actionIndex( )
	{
	    $dataProvider = new ActiveDataProvider( [
	        'query' => Movie::find( )->orderBy( 'ord' ),	// notice the orderBy clause
	        'sort' => false,
	        'pagination' => false
	    ] );

	    return $this->render( 'index', [
	        'dataProvider' => $dataProvider,
	    ] );
	}
	...
    public function actionOrder( )   {
        $post = Yii::$app->request->post( );
        if (isset( $post['key'], $post['pos'] ))   {
            $this->findModel( $post['key'] )->order( $post['pos'] );
        }
    }
	...
}

index 视图中,我们使用了 SortableGridView

use sjaakp\sortable\SortableGridView;
...
<?= SortableGridView::widget( [
    'dataProvider' => $dataProvider,
    'orderUrl' => ['order'],
    'columns' => [
		...
        'title:ntext',
		...
    ],
	...
] ); ?>

现在,电影标题列表可以通过拖放进行排序。

使用场景 2

一对多排序

假设我们还有一个导演列表。每个导演有许多电影,每部电影属于一个导演(以科恩兄弟为例,我知道这在现实中并不一定成立)。

我们在 movie 表中添加了两个列

CREATE TABLE movie (
  id int(10) unsigned NOT NULL AUTO_INCREMENT,
  ord int(10) unsigned NOT NULL,
  title tinytext NOT NULL,
  director_id int(10) unsigned NOT NULL,
  director_ord int(10) unsigned NOT NULL,
  PRIMARY KEY (id)
)

其中 director_ord 是属于同一导演的电影的排序属性。

Director 模型中,我们定义了一个一对多关系,就像我们通常做的那样(注意 orderBy 子句)

class Director extends ActiveRecord	{
	...
	public function getMovies( ) {
    	return $this->hasMany( Movie::class, ['director_id' => 'id'] )
    	    ->orderBy( 'director_ord' );
	}
	...
}

Movie 模型像以前一样可排序,但具有另一个 orderAttribute

class Movie extends ActiveRecord
{
    public function behaviors( ) {
	    return [
	        [
	            'class' => 'sjaakp\sortable\Sortable',
                'orderAttribute' => [
                    'director_id' => 'director_ord'
	            ]
	        ],
	    ];
	}
	...
}

这次,DirectorController 添加了一个额外的动作

class DirectorController extends Controller
{
	...
    public function actionMovieOrder( )   {
        $post = Yii::$app->request->post( );
        if (isset( $post['key'], $post['pos'] ))   {
            $movie = Movie::findOne( $post['key'] );
            if ($movie) $movie->order( $post['pos'], 'director_id' );
        }
    }
	...
}

现在让我们使用 SortableGridViewdirector/view 中显示导演的所有电影

use sjaakp\sortable\SortableGridView;
...
$movies = new ActiveDataProvider( [
	'query' => $model->getMovies( ),  // Do not use $model->movies, it returns array of Movies in stead of an ActiveQueryInterface
	'sort' => false,
	'pagination' => false
] );
...
<h1><?= Html::encode( $model->name ) ?></h1>
...
<?= SortableGridView::widget( [
    'dataProvider' => $movies,
    'orderUrl' => ['movie-order'],
    'columns' => [
		...
        'title:ntext',
		...
    ],
	...
] ); ?>

现在每个导演的视图显示了可排序的电影列表。

很容易将使用场景 1 和 2 结合起来,以便在 movie/index 中对所有电影进行排序,而在 director/view 中只对导演的电影进行排序。只需像这样初始化 MovieSortable 行为

class Movie extends ActiveRecord
{
    public function behaviors( ) {
	    return [
	        [
	            'class' => 'sjaakp\sortable\Sortable',
                'orderAttribute' => [
					'ord',
                    'director_id' => 'director_ord'
	            ]
	        ],
	    ];
	}
	...
}

枢纽记录

这是在多对多关系中两个可排序模型枢纽表的基础 ActiveRecord。

排序信息也存储在枢纽表中。

枢纽表可能看起来像这样

CREATE TABLE actor_movie (
  actor_id int(10) unsigned NOT NULL,	# actor's primary key
  movie_id int(10) unsigned NOT NULL,	# movie's primary key
  actor_ord int(10) unsigned NOT NULL,	# actor's order
  movie_ord int(10) unsigned NOT NULL,	# movie's order
  PRIMARY KEY (actor_id,movie_id),
)

按照最佳实践,这意味着

  • 表名是两个相关表名按字典顺序连接,由下划线('_')分隔;
  • 主键列名由相关表名后跟 '_id' 组成;
  • 排序列名由相关表名后跟 '_ord' 组成;

当然,添加一些索引是明智的。

具体的枢纽记录 必须PivotRecord 派生出来。在派生类中必须定义两个静态函数

aClass() 和 bClass()

受保护的静态函数 protected static function aClass( ) protected static function bClass( )

这些静态成员函数应该返回相关模型的完全限定类名。

aClassbClass 完全等价。PivotRecord 在任何方面都是一个对称类。

一个完整的枢纽记录定义可能看起来像这样

namespace app\models;
use sjaakp\sortable\PivotRecord;

class MovieActor extends PivotRecord    {

    protected static function aClass( )   {
        return Movie::class;
    }

    protected static function bClass( )   {
        return Actor::class;
    }
}

请注意,您还可以定义一些其他静态值,用于特殊情况。如有需要,请参考源代码。

PivotRecord-派生类有以下额外功能。

getAs() 和 getBs()

公共静态函数 public static function getAs( ActiveRecord $b )

获取属于 classB $bclassA 的排序记录。

公共静态函数 public static function getBs( ActiveRecord $a )

获取属于 classA $aclassB 的排序记录。

结果作为 ActiveQuery 返回,可以进行进一步修改,或用作 ActiveDataProvider 的源。

注意这些是 静态 函数,不指向任何 PivotRecord 派生类的实例。

orderA() 和 orderB()

public function orderA( $newPosition )

classA 放置在属于 classB 的所有 classA 的列表中的 $newPosition 位置。

public function orderB( $newPosition )

classB 放置在属于 classA 的所有 classB 的列表中的 $newPosition 位置。

这些是 成员 函数。 classAclassB 的 id 存储在当前的 PivotRecord 中。

MMSortable

这是多对多关系中的两个关联 ActiveRecords 的行为。 PivotRecord 依赖于它。 MMSortable 执行一些维护操作并且没有(有趣的)成员函数。然而,必须配置两个属性

pivotClass

string。枢纽类的完全限定类名(PivotRecord 派生类)。

pivotIdAttr

string。枢纽类中所有者的 id 的属性名。如果未设置,它将推导自所有者的类名;例如:如果所有者是类 Movie,则 $pivotIdAttr 将为 "movie_id"

使用场景 3

多对多排序

除了我们的 movie 表之外,我们还有一个 actor 表。它们通过一个 actor_movie 枢纽表连接:每部电影可以有多个演员,每个演员也可以有多部电影。

首先,我们定义一个枢纽类,如下所示

namespace app\models;
use sjaakp\sortable\PivotRecord;

class MovieActor extends PivotRecord    {

    protected static function aClass( )   {
        return Movie::class;
    }

    protected static function bClass( )   {
        return Actor::class;
    }
}

然后,我们确保 MovieActor 都有 MMSortable 行为

class Movie extends ActiveRecord	{
	...
    public function behaviors( ) {
        return [
            [
                'class' => 'sjaakp\sortable\MMSortable',
                'pivotClass' => MovieActor::class
            ]
        ];
    }
	...
}

为了方便起见,我们在 Movie 中添加了一个非常简单的成员函数

class Movie extends ActiveRecord	{
	...
    public function getActors( ) {
        return MovieActor::getBs( $this );
    }
	...
}

MovieController 中定义一个 order-actor 动作

class MovieController extends Controller
{
	...
    public function actionOrderActor( $id )   {
        $post = Yii::$app->request->post( );
        if (isset( $post['key'], $post['pos'] ))   {
            $piv = MovieActor::find( )->where( [
                'movie_id' => $id,
                'actor_id' => $post['key']
            ] )->one( );
            $piv->orderB( $post['pos'] );
        }
    }
	...
}

现在,在 movie/view 中,我们可以显示一个包含所有出现在电影中的演员的 SortableGridView

use sjaakp\sortable\SortableGridView;
...
$actors = new ActiveDataProvider( [
	'query' => $model->getActors( ),
	'sort' => false,
	'pagination' => false
] );
...
<h1><?= Html::encode( $model->title ) ?></h1>
...
<?= SortableGridView::widget( [
    'dataProvider' => $actors,
    'orderUrl' => ['order-actor', 'id' => $model->getPrimaryKey()],
    'columns' => [
		...
        'name:ntext',
		...
    ],
	...
] ); ?>

使用 jQuery 排序

Sortable 的上一个版本(1.0)使用 jQuery Draggable 和 Sortable。旧的 jQuery 小部件仍然可用作为 SortableGridViewJquerySortableListViewJquery。它们可以与它们的非 jQuery 对应版本交换。

您可能更喜欢 jQuery 变体的美学。此外,新的 HTML 拖放可能不会在所有 移动浏览器 中工作。

SortableGridViewJquerySortableListViewJquery 有两个额外的可配置属性

sortOptions

array。jQuery 可排序对象的选项。请参阅 https://jqueryui-api.jqueryjs.cn/sortable/

请注意,选项 'items''helper''update' 将被覆盖。

默认值:[](空数组)。

sortAxis

boolean|string jQuery 可排序的 'axis' 选项。如果为 false,则不设置。默认:'y'

为了兼容性,SortableGridViewSortableListView 也有这些选项,但它们不起作用。

感谢

  • mike-kramer(sortAxis 选项)
  • menshakov(使用 updateAttributes)
  • robsch(细微的排序错误)
  • rubenheymans(多个外键想法)