十年匠心定制 · 商业建站与技术教学双线并行 咨询热线:400-886-1026 service@lmnt.cn
ARTICLE DETAIL

资讯详情

深耕网站建设与运营推广的一线实战洞察。

Handsontable 单元格校验器(Cell Validator)实战指南:从内置别名到自定义异步校验

Handsontable 单元格校验器(Cell Validator)实战指南:从内置别名到自定义异步校验 Handsontable 单元格校验器Cell Validator实战指南从内置别名到自定义异步校验【免费下载链接】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单元格校验Cell Validator是 Handsontable 数据网格在用户完成单元格编辑后执行的一道数据守门员它用预定义或自定义规则校验新增或修改的数据确保进入数据源的值符合预期格式必填字段、数值范围、正则模式匹配等。本文以官方指南 cell-validator.md 为主体结合 handsontable/src/validators 下的源码实现系统讲解内置别名、allowInvalid与invalidCellClassName的独立语义、自定义校验器的注册与别名机制、异步校验、beforeChange数据修正以及相关的核心方法与 Hook。读完本文你将能够为任意列配置校验规则并在需要时注册可复用的自定义校验别名。Overview校验器在何时触发校验器validator在用户结束编辑单元格时运行。Handsontable 的校验流程会同时覆盖手动输入、粘贴等所有数据变更来源。要使用校验器你可以直接传入函数也可以使用内置别名alias后者让列配置更简洁、可复用。Handsontable 默认定义了 5 个别名alias别名alias对应的校验器函数autocompleteHandsontable.validators.AutocompleteValidatordateHandsontable.validators.DateValidatordropdownHandsontable.validators.DropdownValidatornumericHandsontable.validators.NumericValidatortimeHandsontable.validators.TimeValidator别名的价值在于你在列配置中只需引用别名字符串不必直接引用校验器函数即使将来替换别名背后的函数实现列配置也无需改动。从源码看这些内置校验器都定义在 handsontable/src/validators 目录下每个校验器通过VALIDATOR_TYPE常量声明自己的类型名例如numericValidator.VALIDATOR_TYPE numeric见 numericValidator.ts。而别名与函数之间的映射关系由 registry.ts 中的staticRegister(validators)注册表统一维护。值得留意的是当前源码中实际内置的校验器比文档列出的 5 个更多index.ts 的registerAllValidators()还会注册intlDateintlDateValidator、intlDatetimeintlDatetimeValidator、intlTimeintlTimeValidator和multiSelectmultiSelectValidator等国际化和多选校验器可结合你使用的 Handsontable 版本按需取用。Invalid 单元格的“提交语义”与“视觉标记”相互独立当校验器返回false时Handsontable 会独立控制两个不同的结果提交commit行为—— 由allowInvalid控制。默认值为true非法值仍会写入数据源编辑器正常关闭设为false时编辑器保持打开值被拒绝直到用户输入一个通过校验的值。视觉标记—— 由invalidCellClassName控制。无论allowInvalid取值如何只要校验器返回falseHandsontable 都会给该单元格应用一个 CSS 类默认类名为htInvalid可在列级或表级通过invalidCellClassName替换。下面这段配置把两个选项同时用在同一列上以 JavaScript 为例React / Angular / Vue 语法见文档原文columns: [ { data: ip, validator: ipValidatorRegexp, allowInvalid: true, // keep the value even when invalid invalidCellClassName: my-invalid-cell // apply a custom CSS class } ]这两个选项彼此完全独立你可以同时配置allowInvalid和invalidCellClassName互不影响。对应框架写法// React HotTable columns{[{ data: ip, validator: ipValidatorRegexp, allowInvalid: true, invalidCellClassName: my-invalid-cell }]} /// Angular columns: [ { data: ip, validator: ipValidatorRegexp, allowInvalid: true, invalidCellClassName: my-invalid-cell } ]!-- Vue -- HotTable :settings{ columns: [ { data: ip, validator: ipValidatorRegexp, allowInvalid: true, invalidCellClassName: my-invalid-cell } ] } /从源码实现可以印证这一点在 core.ts 的变更处理逻辑中只有当result false cellProperties.allowInvalid false时该变更才会被changes.splice(index, 1)取消而视觉标记则由校验结果直接驱动的单元格渲染负责两者走的是两条独立路径。另外注意settings.ts 中invalidCellClassName?: string与allowInvalid?: boolean都是可选的独立配置项类型上互不依赖。注册自定义单元格校验器要注册你自己的别名使用Handsontable.validators.registerValidator()函数它接收两个参数validatorName—— 一个字符串代表校验器函数的名字别名validator—— 由validatorName代表的校验器函数例如把creditCardValidator注册到别名credit-card下Handsontable.validators.registerValidator(credit-card, creditCardValidator);该函数对应 registry.ts 中的_register实现。它还有一个重载形态当传入的第一个参数是函数而非字符串时会直接读取该函数自带的VALIDATOR_TYPE属性作为注册名——这正是内置校验器如numericValidator.VALIDATOR_TYPE numeric的注册方式见 index.ts 的registerAllValidators()。别名冲突与命名规范请谨慎选择别名。如果你把校验器注册到已存在的名字下目标函数会被覆盖Handsontable.validators.registerValidator(date, creditCardValidator);此时date别名指向的是creditCardValidator函数而不再是Handsontable.validators.DateValidator。所以除非你确实想覆盖某个已有别名否则尽量选择唯一的名字。一个良好的实践是用自定义前缀例如你的 GitHub 用户名命名别名以最大限度减少命名冲突的可能性。如果你打算发布自己的校验器这一点尤其重要——因为你无法预知使用方已经注册了哪些别名。例如// 可能已有人注册过这个别名 Handsontable.validators.registerValidator(credit-card, creditCardValidator); // 这样更好 Handsontable.validators.registerValidator(my.credit-card, creditCardValidator);从 registry.ts 的_getItem可以看到当配置中引用了一个从未注册的别名时会抛出No registered validator found under name name错误——这是排查“校验器不生效”类问题时最直接的线索。一个规范的自定义校验器模板校验器函数的签名是function(value, callback)value是待校验的单元格值callback接收一个布尔值表示校验结果。一个结构完整的自定义校验器通常如下(Handsontable { function customValidator(query, callback) { // ...your custom logic of the validator callback(/* Pass true or false based on your logic */); } // Register an alias Handsontable.validators.registerValidator(my.custom, customValidator); })(Handsontable);注意this上下文从源码看校验器执行时this被绑定为单元格的 meta 对象cellProperties例如 numericValidator.ts 中的this.allowEmptydateValidator.ts 中的this.allowEmpty。因此你可以在自定义校验器内通过this.allowEmpty等配置读取该单元格/列的 meta 信息。使用别名注册完成后即可在列配置中通过字符串引用别名无需再关心背后的具体函数// JavaScript const container document.querySelector(#container) const hot new Handsontable(container, { columns: [{ validator: my.custom }] });对应框架写法// React HotTable columns{[{ validator: my.custom }]} /!-- Angular -- hot-table [settings]{ columns: [{ validator: my.custom }] } /hot-table!-- Vue -- HotTable :settings{ columns: [{ validator: my.custom }] } /当validator配置项收到的是一个字符串时core.ts 的validateCell会通过instance.getCellValidator(cellProperties)从注册表解析出实际函数再执行。另外validator也接受正则表达式源码core.ts会把 RegExp 包装成(cellValue, validatorCallback) { expression.lastIndex 0; validatorCallback(expression.test(cellValue)); }形式的校验函数——注意它每次执行前重置lastIndex避免全局g或粘性y标志导致的正则状态泄漏。实战校验带点号或逗号小数分隔符的数值当某一列必须同时接受.或,作为小数分隔符时就需要自定义校验器。下面的例子用于校验营销活动的转化率接受3.4、8,1这类值拒绝不符合小数格式的输入。import Handsontable from handsontable/base; import { registerAllModules } from handsontable/registry; // Register all Handsontables modules. registerAllModules(); const container document.querySelector(#example2); const data [ [Spring Sale 2025, Email, 3.4], [Brand Awareness Q3, Paid Search, 8,1], [Retention Push, In-app, 12.0], [Partner Webinar, Organic, 6,75], [Holiday Preview, Social, 9.25], ]; function decimalValidator(value, callback) { if (this.allowEmpty (value null || value undefined || value )) { callback(true); return; } callback(/^\d[.,]\d$/.test(String(value))); } new Handsontable(container, { data, colHeaders: [Campaign, Channel, Conversion rate], columns: [ {}, {}, { validator: decimalValidator, allowInvalid: false, }, ], height: auto, autoWrapRow: true, autoWrapCol: true, licenseKey: non-commercial-and-evaluation, });关键点拆解空值处理先通过this.allowEmpty判断是否允许空值允许且值为空时直接callback(true)。这与内置校验器如 numericValidator.ts、timeValidator.ts的处理方式一致——内置实现也都是先看this.allowEmpty value 再决定直接放行。正则规则/^\d[.,]\d$/要求至少一位整数、一个.或,、至少一位小数恰好覆盖“带小数分隔符的十进制数”这一场景。allowInvalid: false用户输入不合规的值时编辑器不会关闭直到输入通过校验从而保证数据源中永远不出现脏数据。TypeScript 版本来自官方示例 example2.ts额外展示了如何为this声明 meta 类型type CellMeta { allowEmpty?: boolean }; function decimalValidator(this: CellMeta, value: unknown, callback: (valid: boolean) void) { if (this.allowEmpty (value null || value undefined || value )) { callback(true); return; } callback(/^\d[.,]\d$/.test(String(value))); }React、Angular、Vue 的完整示例分别位于 react/example2.jsx、angular/example2.ts、vue/example2.vue可在仓库中直接查看。综合示例同步/异步校验、数据修正与自定义样式综合示例example1.js演示了校验器的完整能力使用校验器方法轻松校验单元格的同步或异步变更如果需要更细粒度的控制可以使用beforeValidate和afterValidateHook。下面示例中email_validator_fn是一个异步校验器约 1000 ms 后返回结果。用allowInvalid决定网格是否接受未通过校验的输入。如果需要修改输入例如屏蔽敏感词、首字母大写使用beforeChange插件 Hook。import Handsontable from handsontable/base; import { registerAllModules } from handsontable/registry; registerAllModules(); const container document.querySelector(#example1); const output document.querySelector(#output); const ipValidatorRegexp /^(?:\b(?:(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\.){3}(?:25[0-5]|2[0-4][0-9]|[01]?[0-9][0-9]?)\b|null)$/; const emailValidator (value, callback) { setTimeout(() { if (/../.test(value)) { callback(true); } else { callback(false); } }, 1000); }; new Handsontable(container, { data: [ // ...11 行人员数据id / name.first / name.last / ip / email ], beforeChange(changes) { for (let i changes.length - 1; i 0; i--) { const currChange changes[i]; if (!currChange) { continue; } // gently dont accept the word foo (remove the change at index i) if (currChange[3] foo) { changes.splice(i, 1); } // if any of pasted cells contains the word nuke, reject the whole paste else if (currChange[3] nuke) { return false; } // capitalise first letter in column 1 and 2 else if (currChange[1] name.first || currChange[1] name.last) { if (currChange[3] ! null) { changes[i][3] currChange[3].charAt(0).toUpperCase() currChange[3].slice(1); } } } return true; }, afterChange(changes, source) { if (source ! loadData) { output.innerText JSON.stringify(changes); } }, colHeaders: [ID, First name, Last name, IP, E-mail], height: auto, licenseKey: non-commercial-and-evaluation, columns: [ { data: id, type: numeric }, { data: name.first }, { data: name.last }, { data: ip, validator: ipValidatorRegexp, allowInvalid: true }, { data: email, validator: emailValidator }, ], autoWrapRow: true, autoWrapCol: true, });这个示例中值得学习的模式异步校验emailValidator用setTimeout模拟异步例如远程接口校验1000 ms 后通过callback返回结果。这正是 Handsontable 校验器“总是异步”设计的体现——在 core.ts 中validateCell通过_registerMicrotask把校验器的调用放入微任务队列并且校验完成后会依次触发afterValidate与postAfterValidateHook因此同步函数也会以一致的异步时序运行。用beforeChange修改数据首字母大写、剔除敏感词foo、拒绝整批粘贴nuke等都在数据进入校验流程之前完成。文档特别提示表内变更会在所有变更单元格的校验器同步与异步全部运行完之后才被应用这保证了多单元格编辑的一致性。混合使用两种allowInvalid策略IP 列allowInvalid: true保留非法值但标红Email 列使用异步校验器。编辑上面的网格即可在输出区域看到changes参数的实际内容。自定义invalidCellClassName默认情况下所有非法单元格都会被标记htInvalidCSS 类。如果想使用不同的类名设置invalidCellClassName—— 它会替换受影响单元格上的htInvalid。同时需要在你自己的样式表中为所选类添加 CSS 规则。整表设置// JavaScript invalidCellClassName: myInvalidClass// React HotTable invalidCellClassNamemyInvalidClass ... /// Angular invalidCellClassName: myInvalidClass!-- Vue -- HotTable :settings{ invalidCellClassName: myInvalidClass } /按列设置// JavaScript columns: [ { data: firstName, invalidCellClassName: myInvalidClass }, { data: lastName, invalidCellClassName: myInvalidSecondClass }, { data: address } ]// React HotTable columns{[ { data: firstName, invalidCellClassName: myInvalidClass }, { data: lastName, invalidCellClassName: myInvalidSecondClass }, { data: address } ]} /// Angular columns: [ { data: firstName, invalidCellClassName: myInvalidClass }, { data: lastName, invalidCellClassName: myInvalidSecondClass }, { data: address } ]!-- Vue -- HotTable :settings{ columns: [ { data: firstName, invalidCellClassName: myInvalidClass }, { data: lastName, invalidCellClassName: myInvalidSecondClass }, { data: address } ] } /FAQallowInvalid为true时invalid CSS 类还会应用吗会。当allowInvalid为true且校验器返回false时值会被写入数据源编辑器正常关闭但 Handsontable 仍会为该单元格应用 invalid CSS 类。默认类为htInvalid你可以通过invalidCellClassName在列级或全局修改它。“是否接受该值”与“如何标记该单元格”这两个关注点完全独立详见allowInvalid选项说明。底层实现校验流程是如何串联起来的理解了上面的用法后再看一下校验的底层链路有助于排查问题获取校验器validateCellcore.ts通过instance.getCellValidator(cellProperties)解析该单元格的校验器——支持函数、正则表达式和注册表别名三种形态。beforeValidateHook校验执行前触发你可以在此 Hook 中修改即将被校验的值core.ts。异步执行通过_registerMicrotask把校验函数调用放入微任务队列保证校验始终异步校验函数以cellProperties作为this被调用core.ts。afterValidate/postAfterValidateHook校验完成后触发结果写入cellProperties.validcore.ts。变更提交判定若结果为false且allowInvalid false对应变更被取消、编辑器保持打开core.ts。此外validateCells、validateRows、validateColumns这三个公开方法core.ts可以按整表、按行、按列触发程序化校验最终都汇聚到内部的_validateCells(callback, rows, columns)。它们适合在“保存前整体校验”等场景使用。例如hot.validateCells((valid) { // valid true 表示所有单元格都通过校验 });hot.validateRows([3, 4, 5], (valid) { // 仅校验第 3、4、5 行 });hot.validateColumns([3, 4, 5], (valid) { // 仅校验第 3、4、5 列 });相关 API 参考配置选项allowEmptyallowInvalid —— 控制非法值是否提交到数据源invalidCellClassName —— 设置校验失败单元格的 CSS 类默认htInvalidvalidator核心方法getCellMeta()getCellMetaAtRow()getCellsMeta()getCellValidator()setCellMeta()setCellMetaObject()removeCellMeta()validateCell()validateCells()validateColumns()validateRows()HooksafterGetCellMetaafterValidatebeforeGetCellMetabeforeValidate小结至此你已经拥有一个会在用户结束编辑时执行数据规则校验的单元格校验器将它注册为别名即可在整个列配置中按名引用用allowInvalid: false让编辑器保持打开直到用户输入合法值或用allowInvalid: true接受非法值但保留视觉标记用invalidCellClassName自定义校验失败单元格的 CSS 类默认为htInvalid。再配合beforeChange修正输入、beforeValidate/afterValidate精细控制校验流程以及validateCells/validateRows/validateColumns做整体校验你就能在 Handsontable 中构建一套完整、健壮的数据质量防线。【免费下载链接】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),仅供参考
返回列表