网页版仿Excel效果组件--handsontable拓展运用
引言(祝看官们新年万事大吉)
前段时间项目需要实现网页版的excel表格功能,瞬间就想到了handsontable,为什么呢?理由如下:该UI组件功能齐全多样,展示效果也更贴近bootstrap风格,兼容所有现代浏览器和IE9+,然后开源,api相当给力。
唯一美中不足的是没有中文版的api,也有些分享中文api的文章,也不完整(有就不错了,不满意自己去看官网api啊,啦啦啦~),闲言少叙,进入正文:
功能需求
我们先看一下功能操作栏(红框部分为部分我们需要实现的功能):

上图中的 input 会同步响应我们所选单元格的数据,其他的方法如图所示。
首先我们既然是表格,那么我们的操作对象基本都是单元格,但同时我们肯定也希望能够满足范围性操作,比如说批量修改样式。当然我们不仅需要考虑到展示效果,还需要考虑到储存和数据渲染,
我们需要的做到以下几点:所选范围、所选范围里的单元格、如何储存数据、如何通过loadData一次性加载数据(包括样式及自定义属性),带着这些问题我们去看api,我们需要找到我们需要方法以及了解如何使用它们。
这里附上官方api链接:https://docs.handsontable.com/pro/1.16.0/tutorial-introduction.html
阅读过程小生这里就不做赘述了(唔...一把辛酸泪),首先我们需要清楚的是,我们需要获取到被选择的单元格对象,而要获取单元格对象我们要知道所选范围(单个单元格也是一个范围,这里不需要另作判断),而要知道所选范围我们要用到 getSelected() 方法,而此方法应该在我们选择范围后触发,所以我们需要用到钩子函数:afterOnCellMouseDown,具体用法可以到api里自行查阅
根据我们的需求,结合api得到如下图:

万事俱备,接下来该做什么呢?当然是做我们最爱做的事:撸代码!~
实现功能
我们这里用本地数据做展示:
var data = [
[, , , , , , , , , , , , , , , , , , , , ],
["2001", 10, 11, 12, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,6,3,5,6,3,5,3,87,6,8,5,6,6,35,6,3,6,3,8,38,3],
["2002", 10, 11, 12, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,6,3,5,6,3,5,3,87,6,8,5,6,6,35,6,3,6,3,8,38,3],
["2003", 10, 11, 12, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,6,3,5,6,3,5,3,87,6,8,5,6,6,35,6,3,6,3,8,38,3],
["2004", 10, 11, 12, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,6,3,5,6,3,5,3,87,6,8,5,6,6,35,6,3,6,3,8,38,3],
["2005", 10, 11, 12, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,6,3,5,6,3,5,3,87,6,8,5,6,6,35,6,3,6,3,8,38,3],
["2006", 10, 11, 12, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,6,3,5,6,3,5,3,87,6,8,5,6,6,35,6,3,6,3,8,38,3],
["2007", 10, 11, 12, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,6,3,5,6,3,5,3,87,6,8,5,6,6,35,6,3,6,3,8,38,3],
["2008", 10, 11, 12, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,6,3,5,6,3,5,3,87,6,8,5,6,6,35,6,3,6,3,8,38,3],
["2009", 20, 11, 14, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,6,3,5,6,3,5,3,8,6,78,5,6,6,35,6,3,6,3,8,38,3],
["2010", 30, 15, 12, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,6,3,52,6,3,5,3,8,6,8,5,6,56,355,6,3,66,23,8,38,3],
["2011", 20, 11, 14, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,26,3,5,26,3,5,3,8,6,8,5,56,6,35,6,3,6,23,8,38,3],
["2012", 20, 11, 14, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,666,5,8,6,3,9,6,3,5,56,3,5,3,78,6,58,55,6,6,35,6,23,6,3,8,38,3],
["2013", 20, 11, 14, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,68,6,3,9,6,3,5,26,3,5,3,8,6,8,5,6,6,35,6,3,6,3,8,38,3],
["2014", 20, 11, 14, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,6,3,5,26,3,5,3,8,6,58,5,6,6,35,6,3,6,3,8,38,3],
["2015", 20, 11, 14, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,29,26,3,5,6,3,5,3,78,76,8,5,6,6,35,6,3,6,3,8,38,3],
["2016", 20, 11, 14, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,6,3,55,6,3,5,3,8,6,28,5,6,6,35,6,3,6,3,8,38,3],
["2017", 20, 11, 14, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,43,9,6,3,5,6,3,5,3,8,6,8,5,6,6,35,6,3,6,3,8,38,3],
["2018", 20, 11, 14, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,62,3,29,6,3,5,6,3,5,3,8,6,8,5,6,6,35,6,3,6,3,8,38,3],
["2019", 20, 11, 14, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,48,6,3,9,6,3,5,6,3,5,3,8,6,8,5,6,6,35,6,3,6,3,8,38,3],
["2020", 20, 11, 14, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,68,6,3,9,6,3,5,6,3,5,3,8,6,8,5,6,6,345,6,3,64,3,8,38,3],
["2021", 20, 11, 14, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,6,3,5,6,3,5,3,8,6,8,5,6,6,35,6,3,6,3,8,38,3],
["2022", 20, 11, 14, 13,14,15,16,17,18,19,20,21,22,22,3,3,55,66,5,8,6,3,9,66,3,5,6,3,5,3,8,6,8,5,6,6,35,6,3,6,3,48,38,23]
];
前端界面如图:

汉化右键菜单:如果不需要右键菜单可以设置为false,contextMenu : false
contextMenu: {
items: {
'mergeCells':{ name: '合并单元格' , },
'row_above': { name: '上方添加一行', },
'row_below': { name: '下方添加一行', },
'col_left': { name: '左侧添加一列', },
'col_right': { name: '右侧添加一列', },
'remove_row': { name: '移除此行', },
'remove_col': { name: '移除此列', },
'copy': { name: '复制', },
'cut': { name: '剪切', },
'make_read_only': { name: '禁止编辑选中项', },
'alignment': { },
'undo': { name: '还原上次操作', },
'redo': { name: '重复上次动作', },
'setAlias':{
name:'设置别名',
callback:function(){
if( $(Ccell) != undefined ){
addAliasDialog();
}else{
alert("请先选择单元格...");
}
}
}
}
}
看效果图:

自定义菜单及回调函数:自定义菜单也是在 contextMenu里面,格式如下(如上图中的 setAlias 设置别名,设置属性方法我们在后面再说明):
contextMenu: {
items: {
'参数名':{
name:'菜单名',
callback:function(){
// doSomething
}
}
}
}
列出全局变量:
// 列出全局变量
var Crow,Ccol,Ccell,valT,selectRange,selectRangeArr = [];
Crow:所选单元格的行,Ccol:所选单元格的列,valT:所选单元格的值,selectRange:所选范围,selectRangeArr:所选单元格数组
获取所选区域单元格数组,当前单元格高亮:这里需要补充的一点是,handsontable本身在表格失去焦点时会移除所有当前高亮类,而我们在点击按钮修改样式时又需要所选高亮来满足我们的“心中有数”,所以这里我们需要自定义一个高亮的类,看代码:
// 获取所选区域单元格数组 当前高亮
hot.addHook('afterOnCellMouseDown', function (event, cellCoords) {
Crow = cellCoords.row,
Ccol = cellCoords.col;
selectRangeArr = []; // 所选区域所有单元格数组
Ccell = hot.getCell(Crow, Ccol)
selectRange = hot.getSelected(); // 获取所选区域范围
console.log(selectRange);
var txt = hot.getDataAtCell(selectRange[0],selectRange[1]); // 获取所选区域第一个单元格值
// 单击任意单元格取消编辑状态
$(".handsontableInputHolder").css({
"display":"none"
});
$("#templateCellInput").val(txt);
var rangeRowArr = []; // 所选区域行数组
var rangeColArr = []; // 所选区域列数组
for( var i=selectRange[0];i<selectRange[2]+1;i++ ){
rangeRowArr.push(i);
}
for( var i=selectRange[1];i<selectRange[3]+1;i++ ){
rangeColArr.push(i);
}
for( var i=0;i<rangeRowArr.length;i++ ){
for( var n=0;n<rangeColArr.length;n++ ){
var selectRangeCell = { row:rangeRowArr[i],col:rangeColArr[n] };
selectRangeArr.push(selectRangeCell);
}
}
// 添加表格失去焦点时的当前单元格类
$("td").removeClass("currentTd");
for( var i=0;i<selectRangeArr.length;i++ ){
var rangeCell = hot.getCell(selectRangeArr[i].row, selectRangeArr[i].col);
$(rangeCell).addClass("currentTd");
}
});
我们可以在控制台查看一下令我们心动的 selectRange 和 selectRangeArr :

所选单元格的值和input同步:templateCellInput 为 input的id
$("#templateCellInput").keyup(function(){
var val = $(this).val();
if(selectRangeArr.length>0){
for( var i=0;i<selectRangeArr.length;i++ ){
hot.setDataAtCell(selectRangeArr[i].row, selectRangeArr[i].col,val)
}
}
});
修改单元格样式(字体样式和对齐方式):switch条件语句在这种事件触发对象判断中用起来是相当地令人愉快..
// 修改单元格样式
$(".btn-group label.btn").click(function(e){
console.log(e.target);
var _index = $(this).index();
var styleType = $(this).parent();
var StyleClassName = '';
// 修改单元格文本样式
var toggleSwitch = true;
if( styleType.hasClass("fontStyle") ){
var fontClass = "";
switch(_index){
case 0 : fontClass = "htBold"; // 加粗
break;
case 1 : fontClass = "htItalic"; // 斜体
break;
case 2 : fontClass = "htUnderline"; // 下划线
break;
}
StyleClassName = fontClass;
}
// 修改单元格对齐方式
if( styleType.hasClass("alignStyle") ){
var alignClass = "";
switch(_index){
case 0 : alignClass = "htLeft"; // 左对齐
break;
case 1 : alignClass = "htCenter"; // 居中对齐
break;
case 2 : alignClass = "htRight"; // 右对齐
break;
case 3 : alignClass = "htJustify"; // 两端对齐
break;
}
StyleClassName = alignClass;
}
// 修改所选区域所有单元格样式并赋予属性
for( var i=0;i<selectRangeArr.length;i++ ){
var rangeCell = hot.getCell(selectRangeArr[i].row, selectRangeArr[i].col);
var checkMergeCell = $(rangeCell).attr("rowspan");
$(rangeCell).removeClass("htLeft htCenter htRight htJustify");
// 定义修改类名 创建对应属性方法
var setRangeCellClass = function(){
$(rangeCell).toggleClass(StyleClassName);
var cellClass = $(rangeCell)[0].className;
hot.setCellMeta(selectRangeArr[i].row, selectRangeArr[i].col,"cellClass",cellClass);
}
if( checkMergeCell != undefined ){
if( toggleSwitch ){
setRangeCellClass();
toggleSwitch = false;
}else{
continue;
}
}else{
setRangeCellClass();
}
}
});
这里需要注意的两点是:
1、如果对象是合并单元格,那么在赋值和修改样式上需要区别对待,看代码(具体区别请调试代码自己体会o(∩_∩)o );
2、在方法里我们可以看到运用了 setCellMeta() 方法,单纯的前端效果我们不需要用到此方法,这里是为了便于储存和渲染数据,如此在初始化表格渲染数据的时候我们能将每一个单元格所对应的样式类名也添加进去,简而言之:每一次初始化表格我们只需要渲染一次,开心~
自定义背景色、字体颜色、边框色:这里我们用到了插件 bootstrap-colorpicker.js :
$(".ColorStyle input").each(function(){
$(this).colorpicker();
})
$(".ColorStyle input").blur(function(){
var val = $(this).val();
var _index = $(this).parent().index();
$(this).css("cssText","background:"+val+"!important;color:"+val+"!important;");
// 定义改变样式方法
var changeCellStyle = function(){
if( _index == 0 ){
$(rangeCell).css({"background":val});
hot.setCellMeta(selectRangeArr[i].row, selectRangeArr[i].col,"bkColor",val);
}
if( _index == 1 ){
$(rangeCell).css({"color":val});
hot.setCellMeta(selectRangeArr[i].row, selectRangeArr[i].col,"ftColor",val);
}
if( _index == 2 ){
$(rangeCell).css({"border":"solid 1px "+val});
hot.setCellMeta(selectRangeArr[i].row, selectRangeArr[i].col,"bdColor",val);
}
};
for( var i=0;i<selectRangeArr.length;i++ ){
var rangeCell = hot.getCell(selectRangeArr[i].row, selectRangeArr[i].col);
var checkMergeCell = $(rangeCell).attr("rowspan");
if( checkMergeCell != undefined ){
if( toggleSwitch ){
changeCellStyle();
toggleSwitch = false;
}else{
continue;
}
}else{
changeCellStyle();
}
}
});
看效果图:

自定义属性:假设我们现在需要给某些单元格添加任意属性,这里我们以设置别名为例,并且希望能在右键菜单中可以直接操作:
'setAlias':{
name:'设置别名',
callback:function(){
if( $(Ccell) != undefined ){
addAliasDialog();
}else{
alert("请先选择单元格...");
}
}
}
对的,此处应该有弹窗,这里小生强烈推荐用 layer.js,快准狠还高大上...这里附上 layer.js的官网地址,具体用法有空亲们可以自行琢磨:http://www.layui.com/doc/modules/layer.html,下面看设置别名回调函数:
function addAliasDialog(){
var html = '<div class="alias" style="text-align:center;margin-top:20px;"><label>请输入别名:<input type="text" id="aliasVal" /></label></div>';
layer.open({
type: 1,
btn: ['确认', '取消'],
shadeClose:true,
title:"设置别名",
area: ['420px', '240px'], //宽高
content: html,
yes:function(index,layero){
var val = $("#aliasVal").val();
var cellMeta = hot.getCellMeta(Crow, Ccol);
if( val != "" ){
hot.setCellMeta(Crow, Ccol, "alias", val);
layer.msg('设置成功', {
icon: 1,
time: 1000 //1秒关闭(如果不配置,默认是3秒)
}, function(){
console.log(cellMeta);
layer.close(index);
});
}else{
alert("别名不能为空!");
}
},
cancel:function(index,layero){
layer.close(index);
},btn2: function(index, layero){
layer.confirm('确认取消设置别名吗?', {icon: 3, title:'提示'}, function(index){
layer.close(index);
},function(index){
addAliasDialog();
});
}
});
}
这里需要注意的一点是,setCellMeta添加的属性是单元格的属性,而不是单元格的DOM属性,所以我们用attr或者prop方法是获取不到的(小生不可能告诉亲们当初经历了什么~),我们可以在设置完成以后将所选单元格的属性打印出来:

看打印结果
:
结语
这次分享到这里就结束了,希望对大家有所帮助,有疑问或者建议都可以留言交流,新年快乐,摸摸踹~
网页版仿Excel效果组件--handsontable拓展运用的更多相关文章
- Qt高仿Excel表格组件-支持冻结列、冻结行、内容自适应和合并单元格
目录 一.概述 二.效果展示 三.实现思路 1.冻结行.冻结列 2.行高自适应 3.蚂蚁线 四.测试代码 1.添加表格数据 2.设置冻结行.列 3.行高.列宽 4.单元格背景色 5.单元格文字 6.其 ...
- 自已开发完美的触摸屏网页版仿app弹窗型滚动列表选择器/日期选择器
手机端网页版app在使用下拉列表时,传统的下拉列表使用起来体验非常不好,一般做的稍好一点的交互功能界面都不会直接使用下拉列表,所以app的原生下拉列表都是弹窗列表选择,网页型app从使用体验上来当然也 ...
- Handsontable-一款仿 Excel效果的表格插件使用总结 96
最近在做一个关于报表管理的项目,发现了一款很好用的jQuery插件-Handsontable.它真的特别给力,在 Excel 中可进行的操作,你几乎都可以在网页中做到,如拖动复制.Ctrl+C .Ct ...
- Android用RecyclerView实现的二维Excel效果组件
excelPanel 二维RecyclerView.不仅可以加载历史数据,而且可以加载未来的数据. 包括在您的项目中 excelPanel 二维RecyclerView.不仅可以加载历史数据,而且 ...
- 前端练手小项目——网页版qq音乐仿写
qq音乐网页版仿写 一些步骤与注意事项 一开始肯定就是html+css布局和页面了,这段特别耗时间,耐心写完就好了 首先要说一下大致流程: 一定要先布局html!,所以一定要先分析页面布局情况,用不同 ...
- html5 contenteditable 实现table可编辑(网页版EXCEL)
一直想找一个免费的网页版的EXCEL插件,以便于多人共同在线编辑,始终没发现合适的. 其实自己实现类似功能也不难.参考:https://blog.csdn.net/chadcao/article/de ...
- Vue3.0网页版聊天|Vue3.x+ElementPlus仿微信/QQ界面|vue3聊天实例
一.项目简介 基于vue3.x+vuex+vue-router+element-plus+v3layer+v3scroll等技术构建的仿微信web桌面端聊天实战项目Vue3-Webchat.基本上实现 ...
- 基于React.js网页版弹窗|react pc端自定义对话框组件RLayer
基于React.js实现PC桌面端自定义弹窗组件RLayer. 前几天有分享一个Vue网页版弹框组件,今天分享一个最新开发的React PC桌面端自定义对话框组件. RLayer 一款基于react. ...
- Android -- 真正的 高仿微信 打开网页的进度条效果
(本博客为原创,http://www.cnblogs.com/linguanh/) 目录: 一,为什么说是真正的高仿? 二,为什么要搞缓慢效果? 三,我的实现思路 四,代码,内含注释 五,使用方法与截 ...
随机推荐
- 微调数据库表结构,30 分钟搞定 WordPress 数据库查询缓慢问题
同事的美女图片站,基于 WordPress 搭建的,因为数据越来越多,变得慢,我从 PHP slow log 里面看出是 WordPress 有些查询总是很慢,即使已经安装了页面缓存插件,但是由于页面 ...
- webpack 基本打包方法
webpack的打包基本配置文件webpack.config.js 可以在webpack.config.js里面写好配置:比如前章节所总结的四大核心 |-- add.js // 定义一个普通加法函数 ...
- 使用Recovery Services备份Azure ARM模式虚拟机
1.需要事先准备好要备份的虚拟机,如"hlmcent73n" 2.创建一个恢复服务保管库 3.选择虚拟机类型的备份 4.可以选择默认备份策略,也可以选择新建备份策略,以下演示为新建 ...
- 如何让a标签的下划线去掉?
在css中添加 a{ text-decoration: none; }
- js 数组与对象的区别
学习javascript的时候,我曾经一度搞不清楚”数组”(array)和”对象”(object)的根本区别在哪里,两者都可以用来表示数据的集合. 比如有一个数组a=[1,2,3,4],还有一个对 ...
- Java为什么需要保留基本数据类型
基本数据类型对以数值计算为主的应用程序来说是必不可少的. 自从1996年Java发布以来,基本数据类型就是Java语言的一部分.John Moore通过对使用基本类型和不使用基本类型做java基准测试 ...
- Navicat查询结果不能修改的原因
问题: 开发中常使用Navicat查询数据库,并修改数据库中的值.今天发现查询结果为只读,不能修改.一般连表查不能修改我是知道的,但是单表查居然不能修改. 解决方法: 查了下,有说表是只读,也有说是权 ...
- GDB 的使用
gdb使用: 1.编译时必须加-g选项,生成调试需要的信息.如 g++ xxx.cpp -o xxx -g 2.调试最好结合core文件 3.调试命令:gdb xxx x ...
- 童攀TP5企业网站实战笔记
$this->assign('data',$data) ---恢复内容开始--- return view(); 载入视图 {include file='public/head'} 包含文件 ...
- 新版elasticsearch的插件安装
安装 yum localinstall elasticsearch-6.1.1.rpm -y mkdir -p /elk/{data,logs} && chown -R elastic ...