跳转至

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 传递一个处理函数,该函数将接收 itemposition 作为第一和第二个参数:

<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 来说这没问题,但是请注意覆盖 handlegroupfilteronSortonStartonEnd 可能会破坏功能。

在此查看 SortableJS 配置选项的完整列表 →