- 前端
- UI组件
【免费下载链接】ckeditor4
The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.
导读
本文以 CKEditor 4 仓库中的手动测试用例 cellpropertiescelltype.md 为骨架,深入讲解“单元格属性(Cell Properties)”对话框中Cell Type(单元格类型)字段的实现原理与验证方法。该功能由tabletools插件提供,并在 CKEditor 4.20.0(issue #5084)中引入config.tabletools_scopedHeaders配置,用于区分Data、Column Header、Row Header三种单元格类型及其底层td/th+scope属性映射。读完本文,你将掌握该手动测试的完整操作步骤、预期结果判读方式,以及从 tableCell.js 源码层面理解类型字段的读写(setup/commit)机制和存量内容迁移方法。
一、功能背景:单元格类型为什么需要“作用域表头”
在 HTML 表格语义中,th元素用于表头单元格,而scope属性用于显式声明该表头作用于整列(scope="col")、整行(scope="row")还是列组/行组,这对屏幕阅读器等无障碍工具正确建立“表头—数据”关联至关重要。
CKEditor 4 的tabletools插件(requires: 'table,dialog,contextmenu',见 plugin.js)在其“单元格属性”对话框中提供了一个Cell Type(单元格类型)下拉框。在未开启 scoped 表头功能时,它只提供两个选项:Data(td)与Header(th)。而从 4.20.0 开始,当配置项config.tabletools_scopedHeaders设为true后,下拉框扩展为三个选项:
Data(td,无scope属性)Column Header(th scope="col")Row Header(th scope="row")
这一行为由 CHANGES.md 的 4.20.0 发布说明明确记载:新增config.tabletools_scopedHeaders配置项,用于控制带/不带[scope]属性的表头单元格的行为。
二、手动测试用例结构与运行环境
本文对应的关联文档位于 tests/plugins/tabletools/manual/cellpropertiescelltype.md,配套的测试夹具为 cellpropertiescelltype.html。该 HTML 文件头部声明了 Bender 测试所需的信息:
@bender-tags: 4.20.0, 5084, feature @bender-ui: collapsed @bender-ckeditor-plugins: wysiwygarea, toolbar, table, tabletools, sourcearea- 版本标签:
4.20.0与5084表明该用例对应 4.20.0 版本引入的 issue #5084 功能。 - 插件集合:
wysiwygarea(所见即所得编辑区)、toolbar(工具栏)、table(表格基础)、tabletools(表格工具)、sourcearea(源码模式,用于核验生成的 HTML 结构)。 - 编辑器初始化:
CKEDITOR.replace( 'editor', { language: 'en', tabletools_scopedHeaders: true } ),即显式开启 scoped 表头功能,这是本用例与默认行为最关键的差异点。 - 初始内容:一个
border="1"的 2×2 表格,四个单元格均为td(Cell 1~Cell 4),没有任何th。 - 移动端处理:代码中通过
bender.tools.env.mobile检测到移动环境时执行bender.ignore()跳过该用例,因此本用例只针对桌面端浏览器运行。
该测试在 Bender 手动测试框架(bender-ui: collapsed)下运行,操作路径是:右键点击任意单元格 → 弹出上下文菜单 →Cell→Cell Properties打开对话框。
三、逐步操作与预期结果(完整继承原文档)
原文档的验证流程共七步,其核心思路是:在“单元格属性”对话框中反复切换单元格类型,并通过打开/关闭对话框与源码模式核验每一次类型切换后底层 HTML 是否与预期一致。完整步骤如下:
- 右键点击任意单元格,选择
Cell→Cell Properties。- 预期:Cell Type 字段当前值为
Data(因为初始单元格是td)。
- 预期:Cell Type 字段当前值为
- 将单元格类型改为
Column Header。- 预期:该单元格变为
th类型,且带有scope属性,其值为col。
- 预期:该单元格变为
- 再次右键该单元格,选择
Cell→Cell Properties重新打开对话框。- 预期:Cell Type 字段回显为
Column Header(说明修改已被正确保存并可被重新读取)。
- 预期:Cell Type 字段回显为
- 将单元格类型改为
Row Header。- 预期:该单元格变为
th类型,且带有scope属性,其值为row。
- 预期:该单元格变为
- 再次右键该单元格,打开
Cell Properties。- 预期:Cell Type 字段回显为
Row Header。
- 预期:Cell Type 字段回显为
- 将单元格类型改回
Data。- 预期:该单元格变为
td类型,且不再带有scope属性。
- 预期:该单元格变为
- 继续在各种单元格类型之间来回切换,逐一确认显示的类型与实际生成的 HTML 结构始终一致。
原文档特别给出了核验手段的提示:“Please verify cell structure via togglingSourcemode.”即每次操作完成后,切换到源码模式(sourcearea插件提供)查看实际输出,例如Column Header应对应<th scope="col">,Row Header应对应<th scope="row">,Data应对应<td>且无scope。这是本用例区别于纯 UI 测试的关键——它要求从“对话框显示值”与“真实 DOM 结构”两个层面双重复核。
四、源码级原理:Cell Type 字段的读写映射
为什么对话框能正确回显Column Header/Row Header,并在提交时写出th scope="col"/th scope="row"?答案在“单元格属性”对话框定义文件 plugins/tabletools/dialogs/tableCell.js 中。
4.1 选项列表由配置决定
对话框通过getAvailableCellTypes( editor )函数(tableCell.js)生成 Cell Type 下拉框的选项:
function getAvailableCellTypes( editor ) { if ( editor.config.tabletools_scopedHeaders ) { return [ [ langCell.data, 'td' ], [ langCell.columnHeader, 'thc' ], [ langCell.rowHeader, 'thr' ] ]; } return [ [ langCell.data, 'td' ], [ langCell.header, 'th' ] ]; }可以看到:
- 开启
tabletools_scopedHeaders时:三个选项的内部值分别为td、thc(Column Header)、thr(Row Header); - 未开启时:仅两个选项
td与th(普通 Header)。
这正解释了为什么本用例(开启了 scoped 配置)中能够看到Column Header和Row Header两个独立选项,而默认配置下只会看到单一的Header。
4.2 setup:把 DOM 读成对话框值
Cell Type 字段的setup回调(tableCell.js)负责在对话框打开时把选中单元格的真实状态回显为下拉框的值:
setup: setupCells( function( selectedCell ) { var cellName = selectedCell.getName(), scope = selectedCell.getAttribute( 'scope' ); if ( cellName === 'td' ) { return 'td'; } switch ( scope ) { case 'row': return 'thr'; case 'col': return 'thc'; default: return 'th'; } } ),映射规则非常清晰:
| 单元格实际状态 | setup 返回值 | 下拉框显示 |
|---|---|---|
td | td | Data |
th+scope="row" | thr | Row Header |
th+scope="col" | thc | Column Header |
th(无scope) | th | Header(scoped 开启时对应空值场景,详见下文 4.4) |
4.3 commit:把对话框值写回 DOM
commit回调(tableCell.js)负责在点击对话框“确定”后把选择的值写回每个选中的单元格:
commit: function( selectedCell ) { var nameToProps = { 'td': { name: 'td' }, 'th': { name: 'th' }, 'thc': { name: 'th', scope: 'col' }, 'thr': { name: 'th', scope: 'row' } }, selectedProps = nameToProps[ this.getValue() ]; selectedCell.renameNode( selectedProps.name ); if ( selectedProps.scope ) { selectedCell.setAttribute( 'scope', selectedProps.scope ); } else { selectedCell.removeAttribute( 'scope' ); } }关键细节有三点:
- 改名用
renameNode:td与th之间通过 DOM 节点重命名完成,而不是删除重建; - 写
scope用setAttribute:thc/thr分别写出scope="col"/scope="row"; - 切回
Data时必须删除scope:td选项在nameToProps中没有scope属性,因此 commit 会执行removeAttribute( 'scope' )——这正是原文档第 6 步“改为 Data 后不带有 scope 属性”的底层保证。
4.4 无 scope 的存量th与迁移方案
tabletools_scopedHeaders配置项的默认值为false,其完整文档位于 plugin.js:
CKEDITOR.config.tabletools_scopedHeaders = false;文档注释(plugin.js)明确指出一个重要注意事项:如果启用该配置,而编辑器内容中恰好存在不带scope属性的th元素,那么这些单元格的 “Cell Type” 值会被显示为空值。为避免该问题,带th元素的旧内容需要被“迁移”——即手动或自动为其补上scope属性。
仓库给出的推荐迁移方式是使用内容过滤器变换(filter transformations)为所有无scope的th自动补上scope="col":
editor.filter.addTransformations( [ [ { element: 'th', left: function( el ) { return !el.attributes.scope; }, right: function( el ) { el.attributes.scope = 'col'; } } ] ] );该变换通过CKEDITOR.filter#addTransformations注册,left判断“当前th没有 scope”,right执行“补上scope="col"”。配套的手动测试 scopedheaders.md 正是验证这一边界场景:开启 scoped 表头后,无scope的th在 Cell Type 下拉框中显示为空选项,且可用选项仅为Data、Column Header、Row Header。
五、对话框的多单元格处理与提交流程
除了类型字段本身的映射,tabletools的单元格属性对话框对“多选单元格”场景也有专门设计,理解这一点有助于更完整地执行本文的验证:
- 读取模型:对话框通过
CKEDITOR.plugins.tabletools.getSelectedCells( editor.getSelection() )获取当前选中的所有单元格(tableCell.js)。 - 多单元格回显:字段的
setup统一由setupCells包装(tableCell.js)。其逻辑是:取第一个单元格的值,若其余单元格该属性值与之不同,则该字段置空,避免误导用户“所有选中单元格都相同”。这也意味着在验证第 2、4、6 步时,若一次性选中了多个单元格,只有它们类型一致时下拉框才会显示具体值。 - 只提交变更:对话框
onLoad会把每个字段的setup/commit包装为“记录打开时的值,仅当提交值与打开值不同时才真正写回”(tableCell.js,对应历史 ticket 11439 的修复)。因此重复打开对话框而不改动任何值,不会对内容造成多余写入。 - 提交后刷新选择:
onOk中对所有选中单元格逐一commitContent,随后执行editor.forceNextSelectionCheck()、selection.selectBookmarks( bookmarks )与editor.selectionChange()(tableCell.js),确保操作后选择状态与工具栏命令状态同步刷新。 - 命令注册:
cellProperties命令在 plugin.js 注册为对话框命令,声明了allowedContent: 'td th{width,height,border-color,background-color,white-space,vertical-align,text-align}[colspan,rowspan]'与包含th的requiredContent,并设置了contextSensitive刷新逻辑——只有当光标路径位于td或th内时才处于可用状态(plugin.js)。
六、自动化测试佐证
除了手动测试用例,仓库还提供了对应的自动化测试 tests/plugins/tabletools/cellproperties.js。该测试文件同时注册了三个编辑器实例:
bender.editors = { basic: { name: 'basic' }, scopedHeadersOn: { name: 'scopedHeadersOn', config: { tabletools_scopedHeaders: true } }, scopedHeadersOff: { name: 'scopedHeadersOff', config: { tabletools_scopedHeaders: false } } };即分别用“默认”“开启 scoped”“关闭 scoped”三种配置验证对话框各字段(宽高、单位、自动换行、行列合并、对齐、颜色等)的加载与提交行为,与手动用例形成互补。手动用例聚焦 UI 交互与 HTML 结构核验,自动化用例则从 API 层面断言dialog.getValueOf/setValueOf的结果。
七、如何在本地复现与验证
要复现本手动测试,需要在仓库根目录下基于 Bender 测试框架运行手动测试集。仓库根目录提供了 bender.js、bender.ci.js 与 bender-runner.config.json 等配置文件。运行方式概述如下:
- 按仓库文档安装 Bender 相关依赖并启动 Bender 服务器;
- 在 Bender 界面中定位到
tests/plugins/tabletools/manual目录下的cellpropertiescelltype用例(桌面端浏览器); - 按第三节的七步流程操作,每步切换类型后打开
Source模式核对 HTML:Column Header→<th scope="col">Row Header→<th scope="row">Data→<td>(无scope)
- 如需验证默认配置的对照行为,可参照 scopedheaders.html,该用例在同一页面分别用
tabletools_scopedHeaders: true与false初始化多个编辑器进行对比。
八、总结
围绕 cellpropertiescelltype.md 这份手动测试,可以提炼出本功能的完整技术画像:
- 功能入口:右键单元格 →
Cell→Cell Properties对话框中的 Cell Type 下拉框; - 配置开关:
config.tabletools_scopedHeaders,默认false,开启后提供Data/Column Header/Row Header三种类型(plugin.js); - 底层映射:
td、th、th scope="col"、th scope="row"四态与对话框内部值td/th/thc/thr一一对应,由 tableCell.js 的 setup/commit 保证双向一致; - 存量迁移:无
scope的th在 scoped 模式下会被显示为空选项,需要借助filter.addTransformations补写scope属性完成迁移; - 验证手段:通过源码模式核验真实 HTML,是确保“对话框显示”与“实际 DOM”一致的最直接手段。
对于任何需要提升表格无障碍性(让屏幕阅读器准确理解表头与数据单元格的关联)的 CKEditor 4 集成场景,本文的配置说明、源码映射与验证流程均可直接作为实施与测试依据。
- 前端
- UI组件
【免费下载链接】ckeditor4
The best enterprise-grade WYSIWYG editor. Fully customizable with countless features and plugins.
相关推荐
CKEditor 4 表格表头作用域(scoped headers):`tabletools_scopedHeaders` 配置与“单元格属性”对话框的 Cell Type 实战指南
CKEditor 4 表格表头作用域(scoped headers): tabletools_scopedHeaders 配置与“单元格属性”对话框的 Cell
前端UI组件CKEditor 4 表格工具与 File Browser 插件集成回归测试指南:基于 Bender 手动测试的 Cell Properties 对话框故障排查
CKEditor 4 表格工具与 File Browser 插件集成回归测试指南:基于 Bender 手动测试的 Cell Properties 对话框故障排查
前端UI组件CKEditor 4 表格单元格与行删除(deleteCells / deleteRows)手动测试指南:tableselection 与 tabletools 集成验证
CKEditor 4 表格单元格与行删除(deleteCells / deleteRows)手动测试指南:tableselection 与 tabletools
前端UI组件
创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考