sjaakp / yii2-sortable-behavior
在 Yii2 中排序 ActiveRecords 及相关记录。
Requires
README
此包包含五个类来处理 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
:项目的零索引新位置。
SortableGridView 和 SortableListView 有一个可配置的属性
orderUrl
array|string
。排序操作后调用的 URL。格式与 yii\helpers\Url::toRoute
相同。
SortableGridView 和 SortableListView 不与 Pjax 小部件 一起使用。然而,Sortable 与分页数据小部件的实用性并不高。
Sortable
使用此行为,ActiveRecord 变得“可排序”。它有一个可配置的属性和一个额外的方法
orderAttribute
string|array
。ActiveRecord 的排序属性。
它可以是以下值之一
string
- 排序属性名称;- 数组
string
- 排序属性名称,foreignKeyName => orderAttrName
- 限制排序到具有相同外键值的 ActiveRecords,即相同的所有者。
默认为 "ord"
。
order()
public function order($newPosition, $foreignKeyName = null, where = [])
此方法通过操作排序属性将所有者放在新的位置 $newPosition
。排序属性是一个零索引的连续整数。
如果 $foreignKeyName
为 null
(默认值),则所有记录都将按顺序排列。
如果它是一个字符串,排序将限制为具有相同 $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' );
}
}
...
}
现在让我们使用 SortableGridView 在 director/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
中只对导演的电影进行排序。只需像这样初始化 Movie
的 Sortable 行为
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( )
这些静态成员函数应该返回相关模型的完全限定类名。
aClass
和 bClass
完全等价。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 $b
的 classA
的排序记录。
公共静态函数
public static function getBs( ActiveRecord $a )
获取属于 classA $a
的 classB
的排序记录。
结果作为 ActiveQuery 返回,可以进行进一步修改,或用作 ActiveDataProvider 的源。
注意这些是 静态 函数,不指向任何 PivotRecord 派生类的实例。
orderA() 和 orderB()
public function orderA( $newPosition )
将 classA
放置在属于 classB
的所有 classA
的列表中的 $newPosition
位置。
public function orderB( $newPosition )
将 classB
放置在属于 classA
的所有 classB
的列表中的 $newPosition
位置。
这些是 成员 函数。 classA
和 classB
的 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;
}
}
然后,我们确保 Movie
和 Actor
都有 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 小部件仍然可用作为 SortableGridViewJquery 和 SortableListViewJquery。它们可以与它们的非 jQuery 对应版本交换。
您可能更喜欢 jQuery 变体的美学。此外,新的 HTML 拖放可能不会在所有 移动浏览器 中工作。
SortableGridViewJquery 和 SortableListViewJquery 有两个额外的可配置属性
sortOptions
array
。jQuery 可排序对象的选项。请参阅 https://jqueryui-api.jqueryjs.cn/sortable/。
请注意,选项 'items'
、'helper'
和 'update'
将被覆盖。
默认值:[]
(空数组)。
sortAxis
boolean|string
jQuery 可排序的 'axis'
选项。如果为 false
,则不设置。默认:'y'
。
为了兼容性,SortableGridView 和 SortableListView 也有这些选项,但它们不起作用。
感谢
- mike-kramer(sortAxis 选项)
- menshakov(使用 updateAttributes)
- robsch(细微的排序错误)
- rubenheymans(多个外键想法)