简述

先说一下背景,之所以封装handsontable插件,是因为公司要实现在线编辑导入excel文件的功能,然后我就找到了这个功能强大的插件handsontable。

具体功能

除了handsontable的功能外,还包括:

1、每一行数据统计错误数,重复数

2、每一列标记重复项,错误项

3、定位功能,当数据过多出现滚动条时,点击上一条/下一条按钮,定位到当前标记项。

4、表头标注每一列数据的校验规则。

5、当数据被编辑后,立即重新校验,并标记重复项、错误项

6、配置isValidate,true则本地校验,false则不校验
(css样式有待改进,后面会更新)

2018/3/30日,修复所有bug,

1、根绝插件局部渲染的特性,将每行的第一列的标注完成局部渲染,以及错误点定位,也根据局部渲染的特性,先滚动到指定行,再进行标记。

2、另外,修复时间控件在最前/后一列/行的情况下,会被遮挡的问题,修改了源码,根据当前单元格的位置来计算时间控件展示的位置。

3、修改源码,将时间控件的英文改为中文。(后面会附上源码修改部分)

2018/4/3日,修改保存错误定位的数据结构,并标记每一个td的row,col位置,保证做到,定位错误点100%准确

/*
* author Happy Guo
* date 2018-03-15
*/
'use strict';
define(["jquery","Handsontable"], function ($,Handsontable) {
var HandsontableExtend = function(opt) {
this.opt = $.extend(true, {}, opt);
this.element = document.querySelector(this.opt.el);
this.header = Object.keys(this.opt.dataObj.headMap);
this.allErrorNum = [];//记录每一行的错误数
this.table = null;
this.errorRow = 0;//统计有几行是有错误的
this.errorPosition = {
index:0,
row: 0,
col: 0
};//记录当前被标注的错误位置
this.firstError = {
isExist :false,
row:0,
col:0
}
this.isPosition = false;
this.position = null;
//如果没有数据,默认给出一行空行
if(this.opt.dataObj.dataMap.length===0){
var obj = {};
this.header.map(function(item,index){
obj[item] = {
value:'',
errorType:''
}
});
this.opt.dataObj.dataMap.push(obj)
}
}
HandsontableExtend.prototype = {
constructor: HandsontableExtend,
// extraType: ["CODE_TYPE","Date"], //不需要校验的类型
typeMap:{'STRING':'文本','INTEGER':'整型','DOUBLE':'小数','DATE':'日期','CODE_TYPE':'枚举类型','BOOLEAN':'布尔','TIME':'小时分钟'},
dateFormat:['yyyy-MM-dd','yyyy/MM/dd','yyyy.MM.dd'],
timePattern:/^([0-1]{1}\d|2[0-3]):([0-5]\d)$/,
/*@method 整合table的列的配置项
*/
setColumn: function() {
var list = [];
for (var i = 0; i < this.header.length; i++) {
var obj = {
data: this.header[i] + ".value",
width: 150,
height: 60
};
var typeData = this.opt.dataObj.headMap[this.header[i]];
if (typeData.type === "CODE_TYPE") {
obj.type = "dropdown";
obj.source = this.getCodeValueList(typeData.codeValueList);
}
if (typeData.type === "BOOLEAN") {
obj.type = "dropdown";
obj.source = ['是','否'];
}
if(typeData.type === 'TIME'){
obj.type = "time";
obj.dateFormat = "h:mm";
}
if (typeData.type === "DATE") {
obj.type = "date";
obj.dateFormat = "YYYY-MM-DD";
obj.datePickerConfig={
firstDay: 1,
yearRange:100,
showWeekNumber: true,
minDate: new Date('1900-01-01')
}
}
list.push(obj);
}
return list;
this.opt.set.columns = list;
},
setCell:function(){
var list = [];
var that = this;
this.opt.dataObj.dataMap.map(function(item,index){
Object.keys(item).forEach(function(key){
if(item[key].originalValue){
list.push({
row:index,
col:that.header.indexOf(key),
comment:{
value:item[key].originalValue
}
})
}
})
});
return list;
},
getCodeValueList:function(list){
var newList = [];
if(list instanceof Array){
list.map(function(item,index){
newList.push(item.displayName);
});
return newList;
}else{
return [];
}
},
/*@method 初始化table的配置,以及事件监听
*/
init: function() {
var that = this;
this.opt.set = $.extend(true,
{
data: that.opt.dataObj.dataMap,
comments:true,
columns: this.setColumn(),
cells: function(row, col, prop) {
//单元格渲染
this.renderer = function(instance,td,row,col,prop,value,cellProperties) {
Handsontable.renderers.TextRenderer.apply(this, arguments);
var obj = that.opt.dataObj.dataMap[row][that.header[col]];
$(td).attr({row:row,col:col});
if (typeof obj["errorType"] !== "undefined") {
if (obj["errorType"] === "repeat") {
$(td).attr({ repeat: true });
} else if (obj["errorType"] === "error") {
$(td).attr({ error: true });
}
} else {
$(td).css({
border: "1px solid #ccc",
color: "#999",
"white-space": "normal",
"word-break": "break-all"
});
}
if(obj.originalValue){
$(td).css({
'background-color':'#F1F9FF'
})
}
};
},
cell:that.setCell(),
stretchH: "all",
width: "100%",
autoWrapRow: true,
autoRowSize: true,
autoColumnSize: true,
height: "600",
maxRows: 1000,
manualRowResize: false,
manualColumnResize: false,
// beforeKeyDown : function(e) {
// // 禁止选中列后delete键和回退键清空整列数据
// if (e.keyCode === 8 || e.keyCode == 46) {
// Handsontable.Dom.stopImmediatePropagation(e);
// }
// },
manualRowMove: true,
manualColumnMove: true,
contextMenu: true,
filters: true,
dropdownMenu: true
},
this.opt.set
);
this.opt.set.rowHeaders = function(index) {
var repeatNum = 0;
var errorNum = 0;
if(that.allErrorNum[index] instanceof Array){
for(var j=0;j<that.allErrorNum[index].length;j++){
if(that.allErrorNum[index][j].type==='error'){
errorNum+=1;
}else{
repeatNum+=1;
}
}
}
var html = "<span class='error-th' style='display:"+((that.allErrorNum[index]&&that.allErrorNum[index].length>0)?"block":"none")+"'></span>";
html +=
" <span class='error-content'>重复:" + repeatNum +
",错误:" + errorNum + "</span>";
html += "<span id='column_name' style='padding-right:6px;'>" + (index + 1) + "</span>";
return html;
};
this.opt.set.colHeaders = function(index) {
var desc = that.header[index]+"规则:";
var map = that.opt.dataObj.headMap[that.header[index]];
if(map.minLength&&map.maxLength){
desc=desc+'长度:'+map.minLength+'-'+map.maxLength;
}else if(map.length){
desc=desc+'长度:'+map.length
}
if(map.type){
desc=desc+',类型:'+that.typeMap[map.type]
}
if(map.required){
desc=desc+',必填'
}
if(map.unique){
desc=desc+',不能重复'
}
var html = "<span class='remark-th'></span>";
html += " <span class='remark-content'>"+desc+"</span>";
if(map.required){
html += "<span id='column_name' style='color:#ED5565'>" + that.header[index] + "</span>";
}else{
html += "<span id='column_name'>" + that.header[index] + "</span>";
}
return html;
};
this.table = new Handsontable(this.element, this.opt.set);
this.table.updateSettings({
contextMenu: {
callback: function(key, options) {
if (key === "about") {
setTimeout(function() {
// timeout is used to make sure the menu collapsed before alert is shown
alert(
"This is a context menu with default and custom options mixed"
);
}, 100);
}
},
items: {
row_above: {
name: "向上插入一行",
disabled: function() {
return that.table.getSelected()[0] === 0;
}
},
remove_row: {
name: "删除选中行",
disabled: function() {
// if first row, disable this option
return that.table.getSelected()[0] === 0;
}
}
}
}
});
window.onresize = this.opt.isValidate
? function() {
that.render.call(that);
}
: null;
this.table.addHook("afterChange", function() {
that.opt.isValidate && that.render.call(that);
});
this.table.addHook("afterRemoveRow", function() {
that.opt.isValidate && that.render.call(that);
});
var topValue = 0,leftValue = 0;
var interval = null;
$(this.opt.el + " .wtHolder")[0].onscroll = function() {
if (interval == null) {
interval = setInterval(isFinishScroll, 1000);
}
};
function isFinishScroll() {
// 判断此刻到顶部的距离是否和1秒前的距离相等
if ($(that.opt.el + " .wtHolder")[0].scrollLeft === leftValue && $(that.opt.el + " .wtHolder")[0].scrollTop === topValue) {
clearInterval(interval);
interval = null;
that.validate();
that.renderError();
that.remarkShow();//渲染表头、列头标注
if(that.position){
$("td[row='"+that.errorPosition.row+"'][col='"+that.errorPosition.col+"']").attr('current');
}
} else {
topValue = $(that.opt.el + " .wtHolder")[0].scrollTop;
leftValue = $(that.opt.el + " .wtHolder")[0].scrollLeft;
}
}
this.render();
return this;
},
/*@method 渲染整个table数据
*/
render: function() {
var table = this.table;
this.validate.call(this); //初始化,先验证,并标记重复项
table.render(); //并渲染在行首部
this.renderError();
this.remarkShow();//渲染表头、列头标注
$(".htInvalid").removeClass('htInvalid');
$("td[current]").removeAttr('current');
},
/*@method 渲染出表头和列头的标注信息
*/
remarkShow:function(){
$(".remark-th").hover(function(){
var top = $(this).closest('th').offset().top;
var left = $(this).closest('th').offset().left;
$(this).next().css({'top':top,"left":left})
});
$(".error-th").hover(function(){
var top = $(this).closest('th').offset().top;
var left = $(this).closest('th').offset().left+10;
$(this).next().css({'top':top,"left":left})
})
},
/*@method 渲染出重复、错误的单元格
*/
renderError: function() {
var that = this;
var rowHeaderTr = $(".ht_clone_left .htCore").eq(0).find("tbody").find("tr");
var tr = $(this.opt.el + " .htCore").eq(0).find("tbody").find("tr");
//渲染错误项(只渲染当前可视区域的),
//此处天坑,行头是单独的table,之前滚动后渲染位置错误。
for (var i = 0; i < tr.length; i++) {
var rowNum = $(tr[i]).find("th #column_name").text();
rowNum = parseInt(rowNum) - 1;
//统计每一行错误项、重复项,如果列数过多,则列会渲染不完全,所以不能用选择器查出准确数据,只能使用统计出的数据
var repeatNum = 0;
var errorNum = 0;
if(that.allErrorNum[rowNum] instanceof Array){
for(var j=0;j<this.allErrorNum[rowNum].length;j++){
if(this.allErrorNum[rowNum][j].type==='error'){
errorNum+=1;
}else{
repeatNum+=1;
}
}
}
var errorTH = $(rowHeaderTr[i]).find("th .error-th").eq(0);
if(repeatNum+errorNum>0){
errorTH.css({'display':'block'});
var top = errorTH.offset().top;
var left = errorTH.offset().left+10;
errorTH.next().css({'top':top,"left":left})
errorTH.next().text('重复:'+repeatNum+',错误:'+errorNum);
}else{
errorTH.css({'display':'none'});
}
$(tr[i]).find("th .error-th,th .error-content").remove();//将另一处被渲染的行标注删除,防止误导
}
this.remarkShow();
},
/*@method 验证
*/
validate: function() {
var table = this.table;
var that = this;
this.allErrorNum = [];//初始化统计错误array
this.firstError = {
isExist :false,
row:0,
col:0
};//初始化,第一个错误位置改为不存在
var cellLength = table.getDataAtRow(0).length;
var errorRows = [];
for (var i = 0; i < cellLength; i++) {
var cellData = table.getDataAtCol(i);
// var newArr = [];
cellData.map(function(item, index, arr) {
var NewCell;
var validateRule = that.opt.dataObj.headMap[that.header[i]];
var dataMap = that.opt.dataObj.dataMap[index][that.header[i]];
//if (newArr.indexOf(index) === -1) {
//如果被重复项最后一个索引已有渲染标志,则不删除渲染标志。
NewCell = table.getCell(index, i);
$(NewCell).removeAttr("error");
$(NewCell).removeAttr("repeat");
dataMap["errorType"] = "";
//}
if (validateRule.required && !item) {
dataMap["errorType"] = "error";
$(NewCell).attr("error", true);
that.setAllErrorNum.call(that,index,i,"error");
errorRows.indexOf(index)===-1 && errorRows.push(index);
return;
}
if(item){
if (validateRule.type &&
validateRule.type === "CODE_TYPE") {
var list = that.getCodeValueList(validateRule.codeValueList);
if(list.indexOf(item)===-1){
dataMap["errorType"] = "error";
$(NewCell).attr("error", true);
that.setAllErrorNum.call(that,index,i,"error");
errorRows.indexOf(index)===-1 && errorRows.push(index);
return;
}
}
if(validateRule.type && validateRule.type === "BOOLEAN"){
if(['是','否'].indexOf(item)===-1){
dataMap["errorType"] = "error";
$(NewCell).attr("error", true);
that.setAllErrorNum.call(that,index,i,"error");
errorRows.indexOf(index)===-1 && errorRows.push(index);
return;
}
}
if (
validateRule.type &&
validateRule.type === "DATE"
) {
var result = false;
that.dateFormat.map(function(format,index){
if(new Date(item).format(format)===item){
result = true;
}
})
if(!result){
dataMap["errorType"] = "error";
$(NewCell).attr("error", true);
that.setAllErrorNum.call(that,index,i,"error");
errorRows.indexOf(index)===-1 && errorRows.push(index);
return;
}
}
if(validateRule.type &&
validateRule.type === "TIME" && !that.timePattern.test(item)){
dataMap["errorType"] = "error";
$(NewCell).attr("error", true);
that.setAllErrorNum.call(that,index,i,"error");
errorRows.indexOf(index)===-1 && errorRows.push(index);
return;
}
if(validateRule.type &&
validateRule.type === "INTEGER" && parseInt(item)!=item){
dataMap["errorType"] = "error";
$(NewCell).attr("error", true);
that.setAllErrorNum.call(that,index,i,"error");
errorRows.indexOf(index)===-1 && errorRows.push(index);
return;
}
if(validateRule.type &&
validateRule.type === "DOUBLE" && parseFloat(item)!=item){
dataMap["errorType"] = "error";
$(NewCell).attr("error", true);
that.setAllErrorNum.call(that,index,i,"error");
errorRows.indexOf(index)===-1 && errorRows.push(index);
return;
}
if (parseInt(validateRule.minLength) && item.length < parseInt(validateRule.minLength)) {
dataMap["errorType"] = "error";
$(NewCell).attr("error", true);
that.setAllErrorNum.call(that,index,i,"error");
errorRows.indexOf(index)===-1 && errorRows.push(index);
return;
}
if (parseInt(validateRule.maxLength) && item.length > parseInt(validateRule.maxLength)) {
dataMap["errorType"] = "error";
$(NewCell).attr("error", true);
that.setAllErrorNum.call(that,index,i,"error");
errorRows.indexOf(index)===-1 && errorRows.push(index);
return;
}
if ((!validateRule.maxLength && !validateRule.minLength&& validateRule.length) && item.length !== validateRule.length) {
dataMap["errorType"] = "error";
$(NewCell).attr("error", true);
that.setAllErrorNum.call(that,index,i,"error");
errorRows.indexOf(index)===-1 && errorRows.push(index);
return;
}
if (validateRule.regexp) {
var pattern = new RegExp(validateRule.regexp);
if(!pattern.test(item)){
dataMap["errorType"] = "error";
$(NewCell).attr("error", true);
that.setAllErrorNum.call(that,index,i,"error");
errorRows.indexOf(index)===-1 && errorRows.push(index);
return;
}
}
if (validateRule.unique && that.getRepeatNum(arr,item,index)) {
$(NewCell).attr("repeat", true);
dataMap["errorType"] = "repeat";
that.setAllErrorNum.call(that,index,i,"repeat");
errorRows.indexOf(index)===-1 && errorRows.push(index);
}
}
});
}
$(".htInvalid").removeClass('htInvalid');
this.errorRow = errorRows.length;
},
/*@method 辅助方法,获取table中第一个错误点的位置
*/
setAllErrorNum:function(x,y,type){
if(!this.firstError.isExist||this.firstError.row>x){
this.firstError = {
isExist:true,
row:x,
col:y
};
}
if(!this.allErrorNum[x]){
this.allErrorNum[x] = [];
}
this.allErrorNum[x].push({
type:type,
row:x,
col:y
});
//如果在已经定位的情况下,又修改了其他单元格到处出现新的数据,那么此处的错误定位要重新定位
if(x === this.errorPosition.row && y === this.errorPosition.col){
this.errorPosition.index = this.allErrorNum[x].length-1;
}
},
/*@method 辅助方法,判断数组中某一个值是否有重复项
*/
getRepeatNum:function(arr,val,i){
var result = false;
arr.map(function(item,index,array){
if(item===val && index!==i){
result = true;
}
})
return result;
},
/*@method 下一个错误调用方法
*/
nextError: function() {
if (this.position) {
$("td[current]").removeAttr("current");
this.position = "next";
this.nextErrorPostion();
this.isPosition = true;
this.scrollToError();
}
},
/*@method 上一个错误调用方法
*/
prevError: function() {
if (this.position) {
$("td[current]").removeAttr("current");
this.position = "prev";
this.prevErrorPosition();
this.isPosition = true;
this.scrollToError();
}
},
/*@method 计算出下一个错误单元格的位置
*/
nextErrorPostion: function() {
var length = this.allErrorNum[this.errorPosition.row].length;
if (this.errorPosition.index < length - 1) {
this.errorPosition.index += 1;
this.errorPosition.col = this.allErrorNum[this.errorPosition.row][this.errorPosition.index].col;
} else {
var maxLength = this.allErrorNum.length;
for (var i = this.errorPosition.row + 1; i < maxLength; i++) {
if (this.allErrorNum[i] instanceof Array &&this.allErrorNum[i].length>0) {
this.errorPosition = {
index:0,
row: i,
col: this.allErrorNum[i][0].col
};
return;
}
}
if (i === maxLength && (this.errorPosition.index === this.allErrorNum[i-1].length-1)) {
this.errorPosition = {
index:0,
row: this.firstError.row,
col: this.firstError.col
};
}
}
},
/*@method 计算出上一个错误单元格的位置
*/
prevErrorPosition: function() {
var length = this.allErrorNum[this.errorPosition.row].length;
if (this.errorPosition.index > 0) {
this.errorPosition.index -= 1;
this.errorPosition.col = this.allErrorNum[this.errorPosition.row][this.errorPosition.index].col;
} else {
for (var i = this.errorPosition.row - 1; i >= 0; i--) {
if (this.allErrorNum[i] instanceof Array &&this.allErrorNum[i].length>0) {
var len = this.allErrorNum[i].length-1;
this.errorPosition = {
index:this.allErrorNum[i].length-1,
row: i,
col: this.allErrorNum[i][len].col
};
return;
}
}
}
},
/*@method 滚动到errorPosition记录的位置
*/
scrollToError: function() {
var that = this;
var tr = $(that.opt.el + " .htCore tbody").find("th #column_name:contains('"+(that.errorPosition.row+1)+"')").closest('tr');
var lastTd = (tr.length>0)?tr.find("td[col='"+this.errorPosition.col+"']"):'';
//滚动到对应行的位置,并且插件会自动渲染出新的可视区域,此时再找出定位的单元格,并标记
$(this.opt.el + " .wtHolder").animate({ scrollTop: (this.errorPosition.row-5)*28 }, 300);
lastTd = tr.find("td[col='"+that.errorPosition.col+"']");
$(this.opt.el + " .wtHolder").animate({ scrollLeft: (this.errorPosition.col-1)*150 }, 300);
setTimeout(function(){
//此处因为是定时器,所以要注意tr和ladtTd会在定时器回掉函数开始执行时消失,所以要在定时器中重新定义,或者使用闭包
var tr = $(that.opt.el + " .htCore tbody").find("th #column_name:contains('"+(that.errorPosition.row+1)+"')").closest('tr');
lastTd = tr.find("td[col='"+that.errorPosition.col+"']");
$(lastTd).attr("current",true);
that.isPosition = false;
},600)
},
/*@method 标记当前错误点
*/
currentError: function(){
if(!this.position){
this.errorPosition.row = this.firstError.row;
this.errorPosition.col = this.firstError.col;
}
this.isPosition = true;
this.position = "current";
this.scrollToError();
},
/*@method 获取有多少条记录是有错误的、正确的
*@return array [总记录数,错误数,正确数]
*/
getErrorNum:function(){
var total = this.opt.dataObj.dataMap.length;
return [total,this.errorRow,total-this.errorRow];
},
/*@method 获取到编辑后的table数据
*/
getData: function() {
return this.opt.dataObj;
}
};
$.fn.HandsontableExtend = function (opts) {
var hand = new HandsontableExtend(opts);
return hand.init(window);
};
})
//修改源码部分:
//第36508行,function showDatepicker(event)的部分,228,258分别为时间控件的高和宽
if(this.TD.offsetTop+228>holder.offsetHeight){
this.datePickerStyle.top = window.pageYOffset + offset.top - 228 + 'px';
}else{
this.datePickerStyle.top = window.pageYOffset + offset.top + (0, _element.outerHeight)(this.TD) + 'px';
}
if(this.TD.offsetLeft+258>holder.offsetWidth){
this.datePickerStyle.left = window.pageXOffset + offset.left - (0, _element.outerWidth)(this.TD) + 'px';
}else{
this.datePickerStyle.left = window.pageXOffset + offset.left + 'px';
}
//this.datePickerStyle.top = window.pageYOffset + offset.top + (0, _element.outerHeight)(this.TD) + 'px';
//this.datePickerStyle.left = window.pageXOffset + offset.left + 'px';
//i18n,json对象改为
i18n:{
previousMonth : '上一月',
nextMonth : '下一月',
months : ['一月','二月','三月','四月','五月','六月','七月','八月','九月','十月','十一月','十二月'],
weekdays : ['星期日','星期一','星期二','星期三','星期四','星期五','星期六'],
weekdaysShort : ['周日','周一','周二','周三','周四','周五','周六']
}

调用方法:


var handTableExtend = $("#hot").HandsontableExtend({
el:'#hot',
dataObj:$scope.dataObject,
isValidate:true,
set:{
height: 500
}
})
//dataObject接受的数据结构
dataObject = {
headMap:{
'姓名':{
isRequired:true,
type:'string',
minLength:2,
maxlength:20,
isUnique:false
},
'性别':{
isRequired:false,
type:'string',
isUnique:false
},
'身份证号':{
isRequired:true,
type:'string',
minLength:9,
maxlength:20,
isUnique:true,
regexp:/^[1-9][0-9]{5}(19|20)[0-9]{2}((01|03|05|07|08|10|12)(0[1-9]|[1-2][0-9]|31)|(04|06|09|11)(0[1-9]|[1-2][0-9]|30)|02(0[1-9]|[1-2][0-9]))[0-9]{3}([0-9]|x|X)$/
},
'联系电话':{
isRequired:true,
type:'string',
//length:11,
isUnique:true
},
'地址':{
isRequired:false,
type:'string',
maxLength:50,
isUnique:false
},
'职位':{
isRequired:false,
type:'string',
isUnique:false
},
'部门':{
isRequired:false,
type:'code_type',
enumData:['it部','hr部','后勤部','销售部'],
isUnique:false
},
'入职日期':{
isRequired:false,
type:'date',
isUnique:false
}
},
dataMap:[
{
'姓名':{
value:'张三',
errorType:''
},
'性别':{
value:'男',
errorType:''
},
'身份证号':{
value:'',
errorType:''
},
'联系电话':{
value:'18817802351',
errorType:'repeat'
},
'地址':{
value:'上海浦东',
errorType:''
},
'职位':{
value:'it',
errorType:''
},
'部门':{
value:'it部',
errorType:''
},
'入职日期':{
value:'it部',
errorType:''
}
},{
'姓名':{value:'李四1',
errorType:''},
'性别':{value:'女',
errorType:''},
'身份证号':{value:'111222',
errorType:''},
'联系电话':{value:'18817802351',
errorType:''},
'地址':{value:'上海浦东',
errorType:''},
'职位':{value:'it',
errorType:''},
'部门':{value:'it部',
errorType:''},
'入职日期':{value:'it部',
errorType:''}
},{
'姓名':{value:'李四1',
errorType:''},
'性别':{value:'女',
errorType:''},
'身份证号':{value:'111222',
errorType:''},
'联系电话':{value:'18817802351',
errorType:''},
'地址':{value:'上海浦东',
errorType:''},
'职位':{value:'it',
errorType:''},
'部门':{value:'it部',
errorType:''},
'入职日期':{value:'it部',
errorType:''}
}
]
};

页面html代码

<div ng-controller="SurveyHandsontable">
<button id="prev" ng-click="prevError()">></button>
<button id="next" ng-click="nextError()"><</button>
<div id="hot"></div>
<button ng-click="getData()">点击</button>
</div>

handsontable在线编辑excel扩展功能-踩坑篇的更多相关文章

  1. NPOI导出Excel (C#) 踩坑 之--The maximum column width for an individual cell is 255 charaters

    /******************************************************************* * 版权所有: * 类 名 称:ExcelHelper * 作 ...

  2. ef+Npoi导出百万行excel之踩坑记

            最近在做一个需求是导出较大的excel,本文是记录我在做需求过程中遇到的几个问题和解题方法,给大家分享一下,一来可以帮助同样遇到问题的朋友,二呢,各位大神也许有更好的方法可以指点小弟一 ...

  3. K8S踩坑篇-master节点作为node节点加入集群

    前面我们二进制部署K8S集群时,三台master节点仅仅作为集群管理节点,所以master节点上中并未部署docker.kubelet.kube-proxy等服务.后来我在部署mertics-serv ...

  4. Linux 配置Maven(避免踩坑篇)

    前言:请各大网友尊重本人原创知识分享,谨记本人博客:南国以南i 一.访问Maven官网下载压缩文件. 二.下载好的maven安装包放在磁盘的 /usr/local/ 目录下,如下图: 三.解压该压缩文 ...

  5. java web 在线编辑Excel -- x-spreadsheet

    --- x-spreadsheet --- 文档 https://hondrytravis.com/x-spreadsheet-doc/ <%@ page language="java ...

  6. webpack安装与配置初学者踩坑篇

    webpack是基于nodejs开发出来的前端工具 webpack可以处理js文件的依赖关系,webpack能够处理js的兼容问题,把高级浏览器不识别的语法转换成浏览器正常识别的语法 (jnlp是基于 ...

  7. vuepress搭建UI组件库文档踩坑篇

    为了实现组件效果预览及代码展示可折叠功能,使用了插件vuepress-plugin-demo-container 相关配置可参考官网说明文档 第一步 安装插件 npm i - D vuepress-p ...

  8. Python 发送邮件 and 编辑Excel

    记录一下Python 发送邮件的代码,这是半年前写的,不知道现在有什么类库的改动. 类库 import smtplib from email.mime.text import MIMEText fro ...

  9. 树莓派4B踩坑指南 - (15)搭建在线python IDE

    今天想在树莓派上自己搭一个在线的python IDE,于是找到了一篇教程--Fred913大神的从头开始制作OJ-在线IDE的搭建 自己尝试动手做了一下, 还是发现不少细节需要注意, 记录在此 如果不 ...

随机推荐

  1. Spring《三》ref 引用其他bean

    local属性 1.被引用id必须在同一个xml中. 2.被引用id必须使用id命名. 优点提前检查所使用的bean id是否正确. Bean属性 1.Bean指定的id可以在不同的xml中. 2.B ...

  2. 主流的Python领域和框架--转

    原文地址:https://www.zhihu.com/question/19899608

  3. 自定义TempData跨平台思路

    一:TempData的自定义实现... TempData是用Session实现的,既然是Session,那模式是线程方式...这样的Session是没法进行跨平台的... 那么这就涉及到如何在多台机器 ...

  4. QQ自动登录里的一些控件知识

    在这个程序里面有个读取计算机指定文件的知识: private void button2_Click(object sender, EventArgs e) { openFileDialog1.Show ...

  5. C# Distanct List集合

    简单一维集合的使用 List<int> ages = new List<int> { 21, 46, 46, 55, 17, 21, 55, 55 }; List<str ...

  6. linux进程的有效用户ID

    进程的有效用户ID用于文件访问时的权限检查.通常,有效用户ID等于实际用户ID(也就是你登录是的用户ID),有效组ID等于实际组ID. 我们知道每个文件针对不同的user有不同的读.写.执行权限.当执 ...

  7. 悦享双节,Guitar Pro也来凑份热闹!

    光阴似箭,又是一个金秋的十月,祖国迎来了第68个生日,不同以往的是今年的中秋佳节与国庆假日重叠在一起了,这算不算是喜上加喜呢? 提到国庆人们的耳边总是会响起了一遍又一遍的国歌“起来,起来不愿做奴隶的人 ...

  8. Kattis - Speed Limit

    Speed Limit Bill and Ted are taking a road trip. But the odometer in their car is broken, so they do ...

  9. No content type provided for validation of a content model---WebLogic问题

    一个web项目,复制到Weblogic domain下的autodeploy目录下,可是从BEA管理控制台中的Deployments下却找不到该项目,奇怪了,这个以前拷过来就可以用的啊?! 查看控制台 ...

  10. php读写excel —— PhpSpreadsheet组件

    前言 PhpSpreadsheet是一个纯PHP类库,它提供了一组类,允许您从不同的电子表格文件格式(如Excel和LibreOffice Calc)读取和写入.用PHP读取Excel.CSV文件 还 ...