最强开源在线excel Luckysheet 源码分析

5,582 阅读6分钟

参考

blog.csdn.net/u010593516/…

项目文件说明

  • package.json 项目的配置文件

  • gulpfile.js 项目通过gulp进行打包和开发

  • /dist 打包之后的输出文件

  • /doc 说明文档

  • /node_modules

  • /src 源文件

    • config.js 项目的默认配置文件

    • core.js 项目的入口文件,通过这里的create进行初始化和加载对应的事件处理函数

    • index.html 开发使用的index.html,但是在master分支的index.html太大了,有差不多2000行,根本没法使用,这里可以自己写一个简单的index.html来替换它提供的index.html. 这里我们如果修改了index.html中的代码,会立刻被打包复制到/dist的目录下。

    • index.js 就是专门为火狐做了fix,然后就是导出luckysheet

    • /controllers 各种控制器

      • alternateformat.js 颜色单元格的控制器
      • conditionformat.js 条件格式
      • constant.js 一些常量定义,包括右键和整个表格的dom结构
      • controlHistory.js 记录一些操作历史,为了回退等操作
      • dropCell.js 在单元格中一些下拉的操作
      • expendPlugins.js 注册一些插件,目前只支持chart和print,但是写了chart,还会抛出错误,被浏览器给捕获到
      • filter.js 筛选相关的操作
      • keyboard.js 键盘相关的一些操作
      • searchReplace.js 查找替换相关的操作
    • /css 样式相关的目录

    • /data 和插件chart有关的配置

    • /demoData 这个目录下一共9个sheet的配置,可以引入到index.html中分别展示查看

    • /expendPlugins 和chart插件相关

    • /fonts 字体

    • /function 封装一些常用的函数相关的操作

    • /global 封装一些常用的函数

    • /locale 本地化相关,显示中文或者英文

    • /methods 封装一些常用的函数

    • /plugins 引用的一些插件

    • /store 类似redux,顶层数据流

    • /utils 封装一些工具函数

core.js 分析

  1. 类似webpack,合并所有的config,把一部分config的设置挂载到store上面,成为全局的配置

  2. initPlugins(extendsetting.plugins, extendsetting.data); 注册插件

  3. 如果没有设置loadurl,也就是数据不是从后端获取,就会从sheetmanage.initialjfFile(menu, title);去获取当前sheet需要展示的数据

  4. 然后通过initialWorkBook 完成需要的初始化事件,其实也就两件事,渲染和绑定相应的事件处理函数。

    function initialWorkBook() {
      // 创建dom,并且绑定事件处理
      luckysheetHandler(); //Overall dom initialization
      initialFilterHandler(); //Filter initialization
      initialMatrixOperation(); //Right click matrix initialization
      initialSheetBar(); //bottom sheet bar initialization
      formulaBarInitial(); //top formula bar initialization
      rowColumnOperationInitial(); //row and coloumn operate initialization
      keyboardInitial(); //Keyboard operate initialization
      orderByInitial(); //menu bar orderby function initialization
      zoomInitial(); //zoom method initialization
      printInitial(); //print initialization
      initListener();
    }
    

初次渲染流程

  1. 首先获取当前sheet对应的渲染数据,这里的格式和demoData中的一致

  2. 最后的渲染在global/createdom.js 中的luckysheetcreatedom(colwidth, rowheight, data, menu, title),参数函数比较清晰.

  3. 然后就是通过gridHTML来拿到模板字符串,通过replaceHtml来替换模板字符串中的变量。

  4. 中间的显示区域使用canvas来根据data画出来,也比较简单,

  5. 最后就会渲染出入下图所示:

图片.png

监听事件

当我们点击之后,会到controllers/handler.js里边进行处理,里边通过在$("#luckysheet-cell-main, #luckysheetTableContent")上面绑定了单击和双击事件,单击是选中当前的单元格,双击之后,会在controllers/updateCell.js里边把编辑框加进去。

如何更新数据,比如我们编辑一个单元格

  1. 当我们双击某个单元格之后,会在当前插入一个dom元素,id为luckysheet-rich-text-editor,然后我们编辑的时候,就是通过在controllers/keyboard.js里边的luckysheet-input-box绑定的keydown来捕获,通过functionInputHanddler来更新。

     formula.functionInputHanddler(
              $("#luckysheet-functionbox-cell"),
              $("#luckysheet-rich-text-editor"),
              kcode
            );
    
  2. 当我们更新完之后,这个时候并没有把数据写到Store.flowData里边,而是等到你下次点击其他单元格的时候,被controllers/handler.js里边的$("#luckysheet-cell-main, #luckysheetTableContent")绑定的mousedown事件获取,通过updatecell来更新,

  // 上次更新的会在这里把数据写到Store.flowData,Store.luckysheetCellUpdate[0],表  示上次更新的行号,Store.luckysheetCellUpdate[1]表示上次更新的列号
            formula.updatecell(
              Store.luckysheetCellUpdate[0],
              Store.luckysheetCellUpdate[1]
            );
  1. updatecell 函数在global/formula.js中,

    // 当通过普通的点击编辑之后,再点击其他单元格的时候,在这里更新
      // r 是上次更新的行的坐标
      // c 是上次更新的列的坐标
      // value 为空
      // isRefresh 默认为true,也就是说默认会刷新,
      // 这里主要的作用就是通过获取r和c此单元格的值,用来更新Store.flowData,
      // 然后调用jfrefreshgrid更新canvas
      updatecell: function(r, c, value, isRefresh = true)
    
  1. jfrefreshgrid 在global/refresh中,然后这里调用luckysheetDrawMain来重新渲染可视区域,这里是全部重新渲染的,以后应该可以优化为只渲染修改的那部分。到此所有的流程基本走完了。

mini-luckySheet

初始化环境

  1. 下载代码git@github.com-mini-luckysheet:zsjun/mini_luckysheet.git,然后把代码回退到初始化环境的 commitId ,也就是执行命令

    git reset --hard a6a60d0d71fd9da2c1a22d04480e62643111bb1b
    

    然后执行下面的命令,下载安装包,因为现在src里边暂时还没有文件,所以暂时启动不了

    npm install
    

基本渲染

  1. version 是59ef7e82d7972782c3c0689153d36a49552aaf10
  2. 基本流程就是首先获取所有的配置,然后通过createdom把定义的一些html全部append到body里边去,再通过luckysheetsizeauto使用canvas把表格画出来。

增加滚动和resize

  1. version 是26bace3b69dbb88fc2ea0aa398f0e569307ff69d

  2. 这里主要是增加了src/controllers/handle.js,在里边通过jquery绑定了scroll事件处理函数。

    $("#luckysheet-scrollbar-x")
        .scroll(function() {
          luckysheetscrollevent();
        })
        .mousewheel(function(event, delta) {
          event.preventDefault();
        });
    ​
      $(window).resize(
        debounce(function() {
          let luckysheetDocument = document.getElementById(Store.container);
          console.log(luckysheetDocument);
          if (luckysheetDocument) {
            luckysheetsizeauto();
          }
        }, 500)
      );
    

增加单元格可以选中

  1. version: 56b10f35334fed4e1ec4caf77280a324c1daa9b5
  1. 在store/index.js 中增加一个数组luckysheet_select_save用来保存选中的单元格

  2. 在constant/gridHTML中的luckysheet-cell-main下面增加

    ​​

    ​​

  1. 在controller/handle里边增加事件处理函数,也就是在canvas和覆盖整个canvas的div上面添加mousedown事件处理函数,事件处理函数也很简单,就是获取到当前的坐标,计算出在canvas中属于第几行和第几列,然后把该单元格高亮就ok了
  2. 这里最关键的就是如何根据坐标定位出来在第几行和第几列,这里的处理逻辑是在

global/location.js里边,这里的处理逻辑也很简单

function colLocation(x) {
  // 通过global/rhchInit.js里边根据每个单元格的宽度先形成数组Store.visibledatacolumn
  // 然后根据x的坐标定位在哪个单元格里边
  let col_index = luckysheet_searcharray(Store.visibledatacolumn, x);
​
  if (col_index == -1 && x > 0) {
    col_index = Store.visibledatacolumn.length - 1;
  } else if (col_index == -1 && x <= 0) {
    col_index = 0;
  }
​
  return colLocationByIndex(col_index);
}

增加双击单元格可以编辑

这里的总体逻辑也比较简单,就是我们首先在html中添加inputhtml,然后再在覆盖canvas的div上绑定双击事件,当我们双击之后,拿到当前的行和列,把对应的inputhtml定位到该位置就ok了

  1. version: f7039cb8aa9e3d475f35182e85dd9882c3179709
  2. 在constant里边增加inputHTML,并且在global/createDom中,通过append把html添加到body中
  3. 在controller/handle.js 中增加dbclick事件处理函数,逻辑比较简单。