Sort 插件
Alpine 的 Sort 插件允许你通过鼠标拖拽轻松地重新排序元素。
这个功能对看板、待办事项列表、可排序表格列等非常有用。
该插件使用的拖拽功能由 SortableJS 项目提供。
安装
你可以通过 <script> 标签引入或通过 NPM 安装来使用此插件:
通过 CDN
你可以将此插件的 CDN 构建版本作为 <script> 标签引入,只需确保在 Alpine 核心 JS 文件之前引入。
<!-- Alpine 插件 -->
<script defer src="https://cdn.jsdelivr.net/npm/@alpinejs/sort@3.x.x/dist/cdn.min.js"></script>
<!-- Alpine 核心 -->
<script defer src="https://cdn.jsdelivr.net/npm/alpinejs@3.x.x/dist/cdn.min.js"></script>
通过 NPM
你可以从 NPM 安装 Sort 以在打包中使用:
npm install @alpinejs/sort
然后从你的打包工具中初始化它:
import Alpine from 'alpinejs'
import sort from '@alpinejs/sort'
Alpine.plugin(sort)
...
基本用法
使用此插件的主要 API 是 x-sort 指令。通过将 x-sort 添加到元素上,其包含 x-sort:item 的子元素将变为可排序——意味着你可以用鼠标拖拽它们,它们会改变位置。
<ul x-sort>
<li x-sort:item>foo</li>
<li x-sort:item>bar</li>
<li x-sort:item>baz</li>
</ul>
- foo
- bar
- baz
排序处理函数
你可以通过向 x-sort 传递一个处理函数并使用 x-sort:item 为每个项目添加键,来响应排序变化。以下是一个简单的处理函数示例,它会弹出一个警告对话框,显示被更改项目的键及其新位置:
<ul x-sort="alert($item + ' - ' + $position)">
<li x-sort:item="1">foo</li>
<li x-sort:item="2">bar</li>
<li x-sort:item="3">baz</li>
</ul>
- foo
- bar
- baz
x-sort 处理函数会在每次项目排序顺序发生改变时被调用。$item 魔法变量包含被排序元素的键(来自 x-sort:item),$position 包含该项目的新位置(从索引 0 开始)。
你也可以向 x-sort 传递一个处理函数,该函数将接收 item 和 position 作为第一和第二个参数:
<div x-data="{ handle: (item, position) => { ... } }">
<ul x-sort="handle">
<li x-sort:item="1">foo</li>
<li x-sort:item="2">bar</li>
<li x-sort:item="3">baz</li>
</ul>
</div>
处理函数通常用于将项目的新顺序持久化到数据库中,以便列表的排序顺序在页面刷新之间保持不变。
排序分组
此插件允许你将项目从一个 x-sort 可排序列表拖拽到另一个列表,方法是为两个列表添加匹配的 x-sort:group 值:
<div>
<ul x-sort x-sort:group="todos">
<li x-sort:item="1">foo</li>
<li x-sort:item="2">bar</li>
<li x-sort:item="3">baz</li>
</ul>
<ol x-sort x-sort:group="todos">
<li x-sort:item="4">foo</li>
<li x-sort:item="5">bar</li>
<li x-sort:item="6">baz</li>
</ol>
</div>
因为上面的两个可排序列表使用了相同的组名(todos),你可以将项目从一个列表拖拽到另一个列表。
当使用像
x-sort="handle"这样的排序处理函数,并将项目从一个组拖拽到另一个组时,只有目标列表的处理函数会被调用,并传入键和新位置。
拖拽手柄
默认情况下,每个 x-sort:item 元素可以通过点击并拖拽其内部的任何位置来拖拽。但是,你可能希望指定一个更小的、更具体的元素作为"拖拽手柄",这样元素的其余部分可以像正常一样交互,只有手柄会响应鼠标拖拽:
<ul x-sort>
<li x-sort:item>
<span x-sort:handle> - </span>foo
</li>
<li x-sort:item>
<span x-sort:handle> - </span>bar
</li>
<li x-sort:item>
<span x-sort:handle> - </span>baz
</li>
</ul>
- - foo
- - bar
- - baz
如你在上面的示例中所见,连字符 "-" 是可拖拽的,但项目文本("foo")则不是。
忽略元素
有时你想要防止可排序项目中的某些元素触发拖拽操作。当你有按钮、下拉菜单或链接等交互式元素,用户应该能够点击它们而不意外拖拽可排序项目时,这尤其有用。
你可以使用 x-sort:ignore 指令来标记不应触发拖拽的元素:
<ul x-sort>
<li x-sort:item>
<!-- ... -->
<button x-sort:ignore>Edit</button>
</li>
<li x-sort:item>
<!-- ... -->
<button x-sort:ignore>Edit</button>
</li>
<li x-sort:item>
<!-- ... -->
<button x-sort:ignore>Edit</button>
</li>
</ul>
在上面的示例中,用户可以点击并拖拽项目本身,但点击"Edit"按钮不会触发拖拽操作。
注意: 带有
x-sort:ignore的元素仍然可以正常使用(按钮可以被点击,输入框可以获得焦点等)——它们只是被排除在拖拽操作之外。
幽灵元素
当用户拖拽一个项目时,该元素会跟随鼠标移动,看起来就像用户正在物理拖拽该元素。
默认情况下,在拖拽过程中原始元素的位置会留下一个"洞"(空白空间)。
如果你希望在原始元素的位置显示一个"幽灵"元素而不是空白空间,你可以向 x-sort 添加 .ghost 修饰符:
<ul x-sort.ghost>
<li x-sort:item>foo</li>
<li x-sort:item>bar</li>
<li x-sort:item>baz</li>
</ul>
- foo
- bar
- baz
样式化幽灵元素
默认情况下,当原始元素被拖拽时,"幽灵"元素会被添加一个 .sortable-ghost CSS 类。
这使得添加任何你喜欢的自定义样式变得很容易:
<style>
.sortable-ghost {
opacity: .5 !important;
}
</style>
<ul x-sort.ghost>
<li x-sort:item>foo</li>
<li x-sort:item>bar</li>
<li x-sort:item>baz</li>
</ul>
- foo
- bar
- baz
排序时 body 上的 class
当一个元素被拖拽时,Alpine 会自动向页面的 <body> 元素添加一个 .sorting class。
这对于仅使用 CSS 条件性地样式化页面上的任何元素非常有用。
例如,你可以有一个仅在用户排序项目时显示的警告:
<div id="sort-warning">
Page functionality is limited while sorting
</div>
要使其仅在排序时显示,你可以使用 body.sorting CSS 选择器:
#sort-warning {
display: none;
}
body.sorting #sort-warning {
display: block;
}
CSS hover 悬停 bug
目前,Chrome 和 Safari 中存在一个 bug(Firefox 中没有),会导致 hover 样式出现问题。
考虑以下 HTML,其中列表中的每个项目根据悬停状态应用不同的样式(这里我们使用 Tailwind 的 .hover 类来条件性地添加边框):
<div x-sort>
<div x-sort:item class="hover:border">foo</div>
<div x-sort:item class="hover:border">bar</div>
<div x-sort:item class="hover:border">baz</div>
</div>
如果你拖拽列表中的一个元素,你会看到 hover 效果会错误地应用到原始元素位置的任何元素上:
- foo
- bar
- baz
要解决这个问题,你可以利用排序时应用于 body 的 .sorting class,将 hover 效果限制为仅在 body 上没有 .sorting 类时才应用。
以下是如何使用 Tailwind 任意变体直接内联实现的方法:
<div x-sort>
<div x-sort:item class="[body:not(.sorting)_&]:hover:border">foo</div>
<div x-sort:item class="[body:not(.sorting)_&]:hover:border">bar</div>
<div x-sort:item class="[body:not(.sorting)_&]:hover:border">baz</div>
</div>
现在你可以看到下面 hover 效果只应用于被拖拽的元素,而不是列表中的其他元素。
- foo
- bar
- baz
自定义配置
Alpine 在底层为配置 SortableJS 选择了合理的默认值。但是,你可以使用 x-sort:config 自行添加或覆盖任何这些选项:
<ul x-sort x-sort:config="{ animation: 0 }">
<li x-sort:item>foo</li>
<li x-sort:item>bar</li>
<li x-sort:item>baz</li>
</ul>
- foo
- bar
- baz
传入的任何配置选项都会覆盖 Alpine 的默认值。对于
animation来说这没问题,但是请注意覆盖handle、group、filter、onSort、onStart或onEnd可能会破坏功能。