news 2026/9/29 7:13:18

Jspreadsheet 事件追踪实战:用 onchange 钩子实时联动 Highcharts 图表

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Jspreadsheet 事件追踪实战:用 onchange 钩子实时联动 Highcharts 图表
  • 前端
  • UI组件

【免费下载链接】ce

Jspreadsheet is a lightweight JavaScript data grid component for creating interactive data grids with advanced spreadsheet controls.

项目地址:https://gitcode.com/gh_mirrors/ce/ce
点击查看免费下载

本指南围绕 Jspreadsheet(v2 时代名为 Jexcel)的事件机制,以官方示例“在电子表格每次变更时实时更新 Highcharts 图表”为核心,讲解 onchange 等事件钩子的用法、回调参数含义,并结合仓库源码剖析事件从单元格编辑到回调触发的完整链路。读完本文,你将掌握如何在 Jspreadsheet 中监听数据变更、编写自定义变更处理器,并将其应用于图表联动等实时数据同步场景。

示例背景:追踪电子表格的每次变更

在数据密集型应用中,电子表格常常只是数据采集的入口,真正的价值在于变更之后引发的联动动作——例如更新图表、重算报表、同步后端。Jspreadsheet 提供了一整套事件钩子(event handlers),你可以在初始化配置中直接声明它们,从而在特定操作发生时执行自己的逻辑。

本示例的场景是:一张记录四个城市逐月平均气温的表格,用户在单元格中修改数值后,右侧的 Highcharts 折线图必须同步刷新对应月份的数据点。整个联动的核心只有一行配置——onchange: update,其中update是开发者自定义的回调函数。

完整示例代码

以下是官方示例的完整实现,它同时引用了 jQuery、jexcel 与 Highcharts 三个库:

<html> <script src="https://cdnjs.cloudflare.com/ajax/libs/jquery/3.1.1/jquery.min.js"></script> <script src="https://code.highcharts.com/highcharts.js"></script> <script src="https://cdnjs.cloudflare.com/ajax/libs/jexcel/2.1.0/js/jquery.jexcel.js"></script> <link rel="stylesheet" href="https://cdnjs.cloudflare.com/ajax/libs/jexcel/2.1.0/css/jquery.jexcel.min.css" type="text/css" /> <div id="my"></div> <script> let data = [ ['Tokyo', 7.0, 6.9, 9.5, 14.5, 18.2, 21.5, 25.2, 26.5, 23.3, 18.3, 13.9, 9.6], ['New York', -0.2, 0.8, 5.7, 11.3, 17.0, 22.0, 24.8, 24.1, 20.1, 14.1, 8.6, 2.5], ['Berlin', -0.9, 0.6, 3.5, 8.4, 13.5, 17.0, 18.6, 17.9, 14.3, 9.0, 3.9, 1.0], ['London', 3.9, 4.2, 5.7, 8.5, 11.9, 15.2, 17.0, 16.6, 14.2, 10.3, 6.6, 4.8], ]; let update = function (obj, cel, val) { // Get the cell position x, y let id = $(cel).prop('id').split('-'); // If the related series does not exists create a new one if (! chart.series[id[1]]) { // Create a new series row let row = []; for (i = 1; i < data[id[1]].length; i++) { row.push(parseFloat(data[id[1]][i])); } // Append new series to the chart chart.addSeries({ name:data[id[1]][0], data:row }); } else { // Update the value from the chart chart.series[id[1]].data[id[0]-1].update({y:parseFloat(val)}); } } $('#my').jexcel({ data:data, onchange:update, colHeaders: [ 'Country' ], colWidths: [ 300 ] }); // Kepp it global let chart = null; $(function () { chart = Highcharts.chart('container', { title: { text: 'Monthly Average Temperature', x: -20 //center }, subtitle: { text: 'Source: WorldClimate.com', x: -20 }, xAxis: { categories: ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'] }, yAxis: { title: { text: 'Temperature (°C)' }, plotLines: [{ value: 0, width: 1, color: '#808080' }] }, tooltip: { valueSuffix: '°C' }, legend: { layout: 'vertical', align: 'right', verticalAlign: 'middle', borderWidth: 0 }, series: [{ name: 'Tokyo', data: [7.0, 6.9, 9.5, 14.5, 18.2, 21.5, 25.2, 26.5, 23.3, 18.3, 13.9, 9.6] }, { name: 'New York', data: [-0.2, 0.8, 5.7, 11.3, 17.0, 22.0, 24.8, 24.1, 20.1, 14.1, 8.6, 2.5] }, { name: 'Berlin', data: [-0.9, 0.6, 3.5, 8.4, 13.5, 17.0, 18.6, 17.9, 14.3, 9.0, 3.9, 1.0] }, { name: 'London', data: [3.9, 4.2, 5.7, 8.5, 11.9, 15.2, 17.0, 16.6, 14.2, 10.3, 6.6, 4.8] }] }); }); </script> </html>

代码拆解

  1. 数据准备:data是一个二维数组,每行首元素是城市名(对应图表 series 的 name),后续 12 个元素是该城市 1 至 12 月的平均气温(对应图表 series 的 data)。
  2. 变更处理器update:
    • 回调签名(obj, cel, val)分别代表表格实例、被修改的单元格元素、新值;
    • $(cel).prop('id').split('-')从单元格 DOM 的 id 中解析出坐标,例如row-2-5会得到['row', '2', '5'],其中id[1]是行号、id[0]是列号(注意 v2 示例中该 id 的拼接约定);
    • 若该行对应的 series 尚不存在,则用原始data构造并chart.addSeries追加;否则直接调用 Highcharts 的update({y: parseFloat(val)})更新数据点,parseFloat保证字符串输入被转换为数值。
  3. 初始化表格:$('#my').jexcel({ data, onchange: update, colHeaders: ['Country'], colWidths: [300] })。第一列是城市名,colWidths: [300]只设置首列宽度,其余列使用默认宽度。
  4. 初始化图表:chart通过$(function(){ ... })在 DOM 就绪后创建,四个初始 series 与data一一对应。示例注释中的 “Kepp it global” 提示了chart需要全局可访问,以便在update中引用。

注意:此示例基于 v2(Jexcel 2.1.0)的 API。表格初始化后可同时通过colHeaders、colWidths快速定制列头与列宽;而onchange之外的事件钩子(如oninsertrow、ondeleterow)在 v2 事件文档 中有完整清单,完整的初始化参数与方法速查见 v2 快速参考。

onchange 事件钩子的使用要点

onchange在单元格值被修改并写回数据之后触发。它与onbeforechange、onafterchange共同构成“变更前—变更后—批量收尾”的三段式事件链,三者在 v2 文档中的定位如下:

事件触发时机
onbeforechange单元格值被修改之前,可返回新值以覆盖写入值
onchange单元格值被修改之后
onafterchange一批变更事件全部执行完之后

回调函数通过初始化参数声明:

$('#my').jexcel({ data: data, onchange: handler, // 每次单元格变更后触发 onbeforechange: beforeHandler, // 变更前触发 onafterchange: afterHandler, // 一批变更结束后触发 });

v2 官方示例中的基础用法(见 v2 事件文档)展示了如何同时使用多个事件:

handler = function(obj, cell, val) { console.log('My table id: ' + $(obj).prop('id')); console.log('Cell changed: ' + $(cell).prop('id')); console.log('Value: ' + val); }; insertrow = function(obj) { alert('new row added on table: ' + $(obj).prop('id')); } deleterow = function(obj) { alert('row excluded on table: ' + $(obj).prop('id')); } data = [ ['Mazda', 2001, 2000, '2006-01-01 00:00:00'], ['Peugeot', 2010, 5000, '2005-01-01 00:00:00'], ['Honda Fit', 2009, 3000, '2004-01-01 00:00:00'], ['Honda CRV', 2010, 6000, '2003-01-01 00:00:00'], ]; $('#my').jexcel({ data:data, colHeaders: ['Model', 'Date', 'Price', 'Date'], colWidths: [ 300, 80, 100, 100 ], onchange:handler, oninsertrow:insertrow, ondeleterow:deleterow, columns: [ { type: 'text' }, { type: 'numeric' }, { type: 'numeric' }, { type: 'calendar', options: { format:'DD/MM/YYYY HH24:MI', time:1 } }, ] });

回调参数

不同事件回调收到的参数并不完全一致,需要分别记忆:

事件回调参数
onbeforechange(obj, cell, x, y, value)
onchange(obj, cell, x, y, value)
oninsertrow / ondeleterow(obj)
oninsertcolumn / ondeletecolumn(obj)
onselection(obj, x1, y1, x2, y2, origin)
onload(obj)

其中obj是表格实例,cell是被操作的单元格 DOM 元素,value是写入的新值。相比直接解析 DOM id,更稳妥的定位方式是使用源码中x、y坐标参数,这也是 v2 回调签名的标准约定。

从源码看变更事件的触发链路

Jspreadsheet 的事件机制在源码中有着清晰的实现,理解这条链路有助于你在复杂场景中预测回调的触发顺序与副作用。

onbeforechange:写入前的最后一道关卡

在 internal.js 的updateCell流程中,值在真正写入数据模型之前会先派发onbeforechange:

// On change const val = dispatch.call(obj, 'onbeforechange', obj, obj.records[y][x].element, x, y, value); // If you return something this will overwrite the value if (val != undefined) { value = val; }

这段代码揭示了一个实用技巧:在 onbeforechange 回调中返回值,即可改写最终写入单元格的内容。例如可以对用户输入做格式化、校验或单位换算,这比在 onchange 中事后补救更高效。

onchange:变更落库后的通知

同一函数 internal.js 在数据与单元格 DOM 均已更新后派发onchange:

dispatch.call(obj, 'onchange', obj, obj.records[y] && obj.records[y][x] ? obj.records[y][x].element : null, x, y, value, record.oldValue);

注意 v5 源码中 onchange 实际携带了 7 个参数(比 v2 文档示例多出x、y、oldValue),这为回调提供了完整的变更上下文:除了新值,还可以拿到变更前的旧值,用于实现变更审计、差异对比等功能。

onafterchanges:批量操作的统一收尾

当一次操作涉及多个单元格(如粘贴、填充、批量赋值)时,data.js 会在所有单元格更新完毕后统一派发onafterchanges,携带一个包含每条变更x、y、value、oldValue的数组。它在 v5 中的事件名为onafterchanges(复数形式),与 v2 文档中的onafterchange是同一能力的演进。

批量场景下优先使用onafterchanges而非onchange,可以避免回调被触发 N 次,显著降低联动逻辑(如保存到后端、刷新图表)的开销。

事件派发与全局钩子

所有事件最终都经过 dispatch.js 统一派发。它按顺序执行三类监听者:

  1. 全局钩子onevent(若配置);
  2. 具体事件的命名回调,如config.onchange;
  3. 所有插件的onevent钩子。
const dispatch = function (event) { const obj = this; // Dispatch events if (!spreadsheet.ignoreEvents) { // Call global event if (typeof spreadsheet.config.onevent == 'function') { ret = spreadsheet.config.onevent.apply(this, arguments); } // Call specific events if (typeof spreadsheet.config[event] == 'function') { ret = spreadsheet.config[event].apply(this, Array.prototype.slice.call(arguments, 1)); } // Plugins if (typeof spreadsheet.plugins === 'object') { /* ... */ } } return ret; };

这意味着你既可以为单个表格注册具体事件回调,也可以通过onevent实现“一切事件统一入口”的全局监听——后者非常适合日志记录、埋点统计等横切需求。当ignoreEvents被置为true(例如执行undo/redo重放历史时),事件派发会被整体跳过,这是 history.js 中的设计:重放历史不应再次触发业务回调,避免联动逻辑产生循环更新。

进阶实战:从示例到生产级变更追踪

官方示例已经展示了事件钩子的基本联动能力。在此基础上,结合源码能力可以扩展出更贴近生产的方案。

方案一:使用坐标参数替代 DOM id 解析

官方示例通过$(cel).prop('id').split('-')解析坐标,这种方式依赖单元格 id 的命名约定。更稳健的做法是直接使用回调中的x、y参数(v2 的onchange前三个参数为 obj、cell、val,坐标可从$(cel)的 data 属性获取,或升级到 v5 直接使用第 3、4 个参数):

let update = function (obj, cell, val) { // 用>
  • 前端
  • UI组件

【免费下载链接】ce

Jspreadsheet is a lightweight JavaScript data grid component for creating interactive data grids with advanced spreadsheet controls.

项目地址:https://gitcode.com/gh_mirrors/ce/ce
点击查看免费下载

相关推荐

上一篇:ComfyUI-Manager终极下载加速指南:5个简单步骤实现300%性能提升
下一篇:AWX 作业运行统计图表接口全解析:`/api/v2/dashboard/graphs/jobs/` 的请求参数、响应格式与源码实现

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/29 7:13:05

SpringBoot集成paho.mqttv3高并发实践:线程池改造与消息可靠入库

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 7:13:01

MCU-51单片机AD模数转换与DA数模转换实战:选型、代码与调试

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 7:12:39

ISP、ICP、IAP三种烧录方式详解:从原理到选型不再翻车

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 7:10:59

QNX内存分析利器pmap:从进程段到线程栈的泄漏定位

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/29 7:10:20

Codex CLI 实战指南:安装配置、Goal模式、MCP与Skills全解析

1. 从热搜词看Codex CLI的真实使用图景先把话说在前头&#xff1a;Codex CLI这类终端里的AI编程助手&#xff0c;最近一年在开发者圈子里热度确实高得离谱。我翻了一圈热搜词&#xff0c;发现大家关心的点其实非常集中——安装、登录、Goal模式、MCP、Skills&#xff0c;再加上…

作者头像 李华