Handsontable 列菜单(Column Menu)插件完整实战指南:配置、过滤项与键盘导航
【免费下载链接】handsontableJavaScript Data Grid / Data Table with a Spreadsheet Look & Feel. Works with React, Angular, and Vue. Supported by the Handsontable team ⚡项目地址: https://gitcode.com/gh_mirrors/ha/handsontable
本指南以 Handsontable 的DropdownMenu插件为主线,讲解如何在表格列头添加可配置的下拉菜单:从dropdownMenu: true一行启用,到用条目数组定制菜单内容,再到借助Filters插件在菜单内构建过滤界面。读完本文,你将掌握列菜单的完整配置方式、与右键 Context Menu 的协作边界、菜单条目的动态构建机制,以及面向键盘和读屏用户的完整导航方案。
概述:列菜单是什么
DropdownMenu插件为表格的列头添加一个可配置的下拉菜单。它的典型触发方式是点击列头中显示的菜单按钮(一个向下箭头样式的按钮),而不是像上下文菜单那样右键点击。
列菜单与右键上下文菜单(Context Menu)是两个相互独立的插件,各有各的配置键:
| 插件 | 配置键 | 触发方式 |
|---|---|---|
| 列菜单 | dropdownMenu | 点击列头按钮,或按快捷键Shift+Alt+↓ |
| 上下文菜单 | contextMenu | 右键点击单元格 / 行头 / 列头 |
注意:
dropdownMenu与contextMenu是分开配置的。设置其中一个不会影响另一个——尽管两者接受的菜单条目使用完全相同的格式。
从源码结构看,两个插件共享同一套菜单基础设施:DropdownMenu基于contextMenu目录下的共享Menu类构建,复用ItemsFactory(条目工厂)和CommandExecutor(命令执行器),但菜单的触发、作用范围和钩子前缀(beforeDropdownMenu*/afterDropdownMenu*)都是独立的。这一点在 DropdownMenu 插件说明 中有明确记录。
快速启用:一行配置开启列菜单
要启用插件,只需在初始化 Handsontable 时将dropdownMenu配置选项设为true。下面的示例来自官方文档的 Quick setup 演示(JavaScript 版与 TypeScript 版):
import Handsontable from 'handsontable/base'; import { registerAllModules } from 'handsontable/registry'; // Register all Handsontable's modules. registerAllModules(); const container = document.querySelector('#example1'); new Handsontable(container, { data: [ ['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1'], ['A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2'], ['A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3'], ], colHeaders: true, dropdownMenu: true, height: 'auto', autoWrapRow: true, autoWrapCol: true, licenseKey: 'non-commercial-and-evaluation', });注意两个前提条件:
- 必须启用列头:
colHeaders: true是必需的,因为菜单按钮被注入到列头单元格(TH)中; - 必须注册模块:通过
registerAllModules()注册全部模块,否则插件不会被加载。如果使用按需注册,需要显式注册DropdownMenu插件。
在 React 包装器中(example1.jsx),配置以 JSX 属性的形式传入:
import { HotTable } from '@handsontable/react-wrapper'; import { registerAllModules } from 'handsontable/registry'; registerAllModules(); const ExampleComponent = () => { return ( <HotTable data={[ ['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1'], ['A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2'], ['A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3'], ]} colHeaders={true} dropdownMenu={true} height="auto" autoWrapRow={true} autoWrapCol={true} licenseKey="non-commercial-and-evaluation" /> ); };Angular 包装器(example1.ts)通过GridSettings对象传入配置:
readonly hotSettings: GridSettings = { colHeaders: true, dropdownMenu: true, height: 'auto', autoWrapRow: true, autoWrapCol: true, };Vue 3 包装器(example1.vue)则使用ref<GridSettings>保存配置:
<script setup lang="ts"> import { ref } from 'vue'; import { HotTable } from '@handsontable/vue3'; import { registerAllModules } from 'handsontable/registry'; import type { GridSettings } from 'handsontable/settings'; registerAllModules(); const hotSettings = ref<GridSettings>({ data: [ ['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1'], ['A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2'], ['A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3'], ], colHeaders: true, dropdownMenu: true, height: 'auto', autoWrapRow: true, autoWrapCol: true, licenseKey: 'non-commercial-and-evaluation', }); </script> <template> <div id="example1"> <HotTable :settings="hotSettings" /> </div> </template>插件配置:使用默认菜单还是自定义菜单
dropdownMenu选项接受两种取值:
| 取值 | 效果 |
|---|---|
true | 启用插件,并使用默认的菜单条目列表 |
| 字符串数组 | 启用插件,并使用你指定的自定义条目列表 |
false(或省略) | 完全禁用插件 |
默认菜单内容
当设为true时,菜单使用插件源码中定义的默认条目顺序。从 dropdownMenu.ts 的DEFAULT_ITEMS静态属性可以看到实际顺序:
col_left → col_right → 分隔线 → remove_col → 分隔线 → clear_column → 分隔线 → make_read_only → 分隔线 → alignment其中:
col_left/col_right:在左侧 / 右侧插入一列;remove_col:删除选中列;clear_column:清空选中列的数据;make_read_only:将选中单元格设为只读;alignment:单元格文本对齐(子菜单包含左 / 中 / 右 / 上 / 下 / 两端对齐等)。
自定义菜单:使用条目数组
将dropdownMenu设为一个字符串数组即可自定义条目。下面的示例来自文档的 Plugin configuration 演示(example2.js):
import Handsontable from 'handsontable/base'; import { registerAllModules } from 'handsontable/registry'; registerAllModules(); const container = document.querySelector('#example2'); new Handsontable(container, { data: [ ['A1', 'B1', 'C1', 'D1', 'E1', 'F1', 'G1'], ['A2', 'B2', 'C2', 'D2', 'E2', 'F2', 'G2'], ['A3', 'B3', 'C3', 'D3', 'E3', 'F3', 'G3'], ], colHeaders: true, licenseKey: 'non-commercial-and-evaluation', height: 'auto', dropdownMenu: ['remove_col', '---------', 'make_read_only', '---------', 'alignment'], autoWrapRow: true, autoWrapCol: true, });这里'---------'表示一条分隔线。自定义数组的意义在于:手动列出条目后,其他插件就不会再往菜单里追加条目了——这正好解决了“想排除某插件条目”的需求。
全部预定义条目参考
除上述默认条目外,列菜单还支持以下预定义条目(与上下文菜单共享同一套条目格式,完整列表见 Context Menu 指南)。注意:许多条目来自其他插件,只有对应插件启用时才生效:
| 键 | 作用 | 依赖插件 |
|---|---|---|
row_above/row_below | 在上方 / 下方插入一行 | — |
col_left/col_right | 在左侧 / 右侧插入一列 | — |
--------- | 添加分隔线 | — |
remove_col/remove_row | 删除选中列 / 行 | — |
clear_column | 删除选中列的数据 | — |
undo/redo | 撤销 / 重做上一次操作 | UndoRedo |
make_read_only | 将选中单元格设为只读 | — |
alignment | 对齐单元格文本 | — |
cut/copy | 剪切 / 复制选中单元格到剪贴板 | CopyPaste |
copy_with_column_headers | 复制单元格及其最近的列头 | CopyPaste(需copyColumnHeaders: true) |
copy_with_column_group_headers | 复制单元格及其所有相关列头 | NestedHeaders+CopyPaste |
copy_column_headers_only | 仅复制最近的列头 | CopyPaste(需copyColumnHeadersOnly: true) |
freeze_column/unfreeze_column | 冻结 / 取消冻结选中列 | ManualColumnFreeze |
borders | 为选中单元格添加边框 | CustomBorders |
commentsAddEdit/commentsRemove/commentsReadOnly | 添加编辑 / 删除 / 只读化批注 | Comments |
mergeCells | 合并或取消合并选中单元格 | MergeCells |
add_child/detach_from_parent | 插入子行 / 从父行分离 | NestedRows |
hidden_columns_hide/hidden_columns_show | 隐藏 / 显示选中列 | HiddenColumns |
hidden_rows_hide/hidden_rows_show | 隐藏 / 显示选中行 | HiddenRows |
filter_by_condition等 5 个条目 | 在菜单内构建过滤界面 | Filters(详见下文) |
export_file | 打开“导出”子菜单(CSV / Excel) | ExportFile(Excel 项在未配置 XLSX 引擎时隐藏) |
以freeze_column/unfreeze_column为例:它们来自ManualColumnFreeze插件。启用该插件后,两个条目会自动加入默认列菜单,并且各自只在适用时显示——freeze_column只在列未冻结时出现,unfreeze_column只在列已冻结时出现。在 DropdownMenu 插件的源码 中,freeze_column、unfreeze_column等键均通过contextMenu/predefinedItems目录下的命令实现注册。
条目选项格式
如果你要传入完整对象形式的条目(而不是字符串键),每个条目支持以下选项(详见 Context Menu 指南):
| 选项 | 说明 |
|---|---|
key | 条目唯一标识。顶层条目用'row_above'这样的键;子菜单条目必须用父键:子键格式(如'colors:red') |
name | 菜单中显示的标签。可以是字符串或返回字符串的函数,支持 HTML;函数中this指向 Handsontable 实例 |
disabled | 条目置灰不可点。布尔值或返回布尔值的函数 |
hidden | 是否从菜单中完全隐藏条目。布尔值或返回布尔值的函数 |
checked | 是否显示勾选标记。同时让条目对辅助技术表现为复选框 |
callback | 点击条目时执行的回调,接收key、selection、clickEvent三个参数 |
submenu | 定义嵌套子菜单,接收包含items数组的对象 |
renderer | 自定义条目 HTML 渲染函数,必须返回HTMLElement |
disableSelection | 为true时悬停条目不高亮 |
isCommand | 为false时点击条目不执行命令、不关闭菜单 |
菜单条目是每次打开时重建的
列菜单的条目列表在每次打开菜单时都会重新构建,因此条目会跟随当前配置实时变化。这也是为什么启用/禁用其他插件(如ManualColumnFreeze)后,菜单内容能立即反映出来。
- 如果你想排除某个插件的条目,就手动列出想要的条目(把
dropdownMenu设为数组),而不是设成true; - 如果你想每次打开时动态修改条目列表,请使用
beforeDropdownMenuSetItems钩子。
从源码看,prepareMenuItems()方法在每次打开时依次执行两条钩子链(dropdownMenu.ts):
afterDropdownMenuDefaultOptions——允许其他插件往默认选项里追加条目(Filters插件正是通过这个钩子把过滤条目加进来的);beforeDropdownMenuSetItems——允许在最终渲染前对完整条目列表做最后一次修改。
构建完成后,所有条目会注册到CommandExecutor中,供点击或编程式调用。
过滤菜单条目:把筛选界面放进列菜单
当Filters插件启用时,它会向列菜单追加以下条目,这些条目共同构建完整的过滤界面。这些条目只在列菜单中可用,不会出现在右键上下文菜单中:
| 键 | 作用 |
|---|---|
filter_by_condition | 添加第一个过滤条件 |
filter_by_condition2 | 添加第二个过滤条件。第二个条件的下拉选择框依赖它 |
filter_operators | 选择连接两个条件的运算符(And或Or) |
filter_by_value | 选择要保留的值 |
filter_action_bar | 用OK和Cancel按钮应用或取消过滤 |
从 filters.ts 的源码可以看到,这 5 个条目以固定顺序注册,并在afterDropdownMenuDefaultOptions钩子中被注入到列菜单:
['filter_by_condition', null], ['filter_operators', null], ['filter_by_condition2', null], ['filter_by_value', null], ['filter_action_bar', null]完整的过滤配置指南见 Column filter(列过滤)指南;过滤菜单内的键盘与指针操作行为见其中的 Navigate the filter menu 一节,例如:
Tab/Shift+Tab在搜索输入框、Select all、Clear all、条件控件和操作栏控件之间移动焦点;- 搜索输入框聚焦时,
↑/↓在Filter by value列表中移动; - Select all/Clear all聚焦时,
Enter或Space执行对应动作; - 悬停非过滤条目(如Clear column)后,过滤组件的焦点顺序会重置,下一次
Tab会聚焦到第一个过滤组件。
导航列菜单:纯键盘操作
打开列菜单后,可以使用以下键盘快捷键导航:
↑/↓:在菜单条目之间移动;→:在从左到右(LTR)布局中打开子菜单;←关闭子菜单并返回父菜单;←:在从右到左(RTL)布局中打开子菜单;→关闭子菜单并返回父菜单;Home(Windows 下Ctrl+↑,macOS 下Cmd+↑):移动到第一个可用条目;End(Windows 下Ctrl+↓,macOS 下Cmd+↓):移动到最后一个可用条目;Page Up/Page Down:按一个可见菜单页移动;Enter/Space:执行选中条目,或打开其子菜单;Escape:关闭列菜单或当前子菜单。
这些快捷键行为在 dropdownMenu 插件的键盘快捷键测试 目录中有对应的测试用例(如arrowUp.spec.js、escape.spec.js、enterOrSpace.spec.js、pageUp.spec.js等)。
菜单内的过滤控件遵循额外的导航规则,详见 Navigate the filter menu。
相关键盘快捷键
以下快捷键用于打开列菜单:
| Windows | macOS | 作用 | Excel | Sheets |
|---|---|---|---|---|
Shift+Alt+↓ | ⇧+⌥+↓ | 打开列菜单。可在任意单元格中使用,前提是该列列头显示了菜单按钮 | ✕ | ✕ |
Ctrl+Enter | ⌘+Enter | 打开列菜单。仅在聚焦到带菜单按钮的列头时生效 | ✕ | ✕ |
要点:
Shift+Alt+↓从数据单元格即可触发;Ctrl/⌘+Enter则要求列头已聚焦;- 要让箭头键把焦点移到列头上,需要启用
navigableHeaders: true选项; - 更多键盘导航细节见 Accessibility(无障碍)指南的 Keyboard navigation 一节。
从 dropdownMenu.ts 的registerShortcuts()可以看到,这两个快捷键在grid快捷键上下文中注册,Control/Meta+Enter带有captureCtrl: true,且均带有runOnlyIf守卫(例如菜单已打开时不响应)。
源码视角:列菜单的内部机制
结合 dropdownMenu.ts 的源码实现,可以更深入地理解列菜单的工作方式:
插件生命周期
PLUGIN_PRIORITY = 230:插件初始化优先级;PLUGIN_DEPS声明依赖AutoColumnSize插件;isEnabled():检查hot.getSettings()['dropdownMenu']是否为真;updatePlugin():在updateSettings()修改dropdownMenu选项时被调用,通过先disablePlugin()再enablePlugin()完成热更新;enablePlugin():注册鼠标事件、视图滚动拦截(防止点击按钮时视口发生跳动)、对话框打开时自动关闭菜单,并创建共享的Menu实例(className: 'htDropdownMenu')。
菜单按钮的注入
按钮注入发生在afterGetColHeader钩子中(dropdownMenu.ts):插件会在最底层列头(isBottomMostColumnHeader)的TH内插入一个type="button"、tabIndex = -1的按钮元素。启用ariaTags时,还会为按钮设置aria-hidden和为TH设置aria-haspopup="menu",保证读屏用户能感知到菜单的存在。
菜单的打开流程
open()方法(dropdownMenu.ts)的调用顺序是:
- 触发
beforeDropdownMenuShow钩子(此时菜单状态尚未提交,若监听器调用updateSettings({ dropdownMenu })会同步重建插件,后续流程基于新实例继续); - 调用
prepareMenuItems()重建条目列表; menu.open()打开菜单;- 设置位置偏移并定位。
打开后触发afterDropdownMenuShow,关闭后触发afterDropdownMenuHide,执行命令时触发afterDropdownMenuExecute——这 5 个钩子都在插件源码第 26-30 行统一注册。
编程式控制
hot.getPlugin('dropdownMenu')拿到插件实例后,可以调用:
const menu = hot.getPlugin('dropdownMenu'); hot.selectCell(0, 0); menu.open({ top: 50, left: 50 }); // 打开菜单并定位 menu.close(); // 关闭菜单 menu.executeCommand('remove_col'); // 执行命令executeCommand()支持所有预定义命令,如'col_left'、'col_right'、'clear_column'、'remove_col'、'undo'、'redo'、'make_read_only'、'alignment:left'、'alignment:top'、'alignment:right'、'alignment:bottom'、'alignment:middle'、'alignment:center',也可以执行自定义注册的命令。注意:executeCommand()仅在选中了单元格时生效,没有选中时调用不做任何事。
列菜单相关的钩子(Hooks)
列菜单专属的钩子共有 5 个,均以DropdownMenu插件实例为参数:
| 钩子 | 触发时机 |
|---|---|
afterDropdownMenuDefaultOptions | 构建默认选项之后、条目列表确定之前;适合向菜单追加自定义条目 |
beforeDropdownMenuShow | 菜单显示之前 |
afterDropdownMenuShow | 菜单显示之后 |
beforeDropdownMenuSetItems | 条目列表最终确定之前;适合按需增删条目 |
afterDropdownMenuHide | 菜单隐藏之后 |
一个典型用法是监听beforeDropdownMenuSetItems,在每次菜单打开前按列号动态调整条目(例如对特定列禁用某些操作)。
相关资源
相关指南
- Context menu(上下文菜单)——与列菜单共享条目格式与菜单基础设施的姊妹插件
- Column filter(列过滤)——列菜单内过滤界面的完整使用指南
配置选项
dropdownMenu——本文主角,见 API 参考
源码参考
- DropdownMenu 插件实现——插件完整源码(含默认条目、快捷键、按钮注入)
- DropdownMenu 插件说明——插件设计约定
- 预定义菜单条目——
col_left、remove_col、alignment等命令的实现 - Filters 插件实现——过滤条目的注册与过滤界面组件
- 插件 API 索引——全部插件与选项的入口
【免费下载链接】handsontableJavaScript Data Grid / Data Table with a Spreadsheet Look & Feel. Works with React, Angular, and Vue. Supported by the Handsontable team ⚡项目地址: https://gitcode.com/gh_mirrors/ha/handsontable
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考