单元格的计算
在如Excel一样的表格中,某些单元格是靠计算产生值的,不用人工填写。本来使用的是Farpoint Spread 这个控件,生成表头,设定了公式,锁定列等等看来都很好使,界面也很好。
静态计算是没什么问题的,即页面第一次加载生成时计算那些单元格的值;但是动态计算上体验不太完美,利用它自己内置的功能反应不够灵敏,公式涉及的行列越多,计算层次越深其反应越不灵敏。鉴于我们遇到的公式基本只有二种,合计与百分比,遂自己写脚本来动态更新相关单元格的值。
设定事件,取得行列索引不细表,直接进入正题
function calcValue(sheet, currentRow, currentCol, level) {
var sumFormulaExp = /R(\d+)C(\d+)\+/g; //相加的表达式
var percentFormulaExp = /IF\(R(\d+)C(\d+)=0,\S{3},R(\d+)C(\d+)\*100\/R(\d+)C(\d+)\)/g; //计算百分比的表达式
var formulaPart = "R" + currentRow + "C" + currentCol; //exp:R4C3
$("td[fpformula*='" + formulaPart + "']").each(function() { //exp:找公式中包含有R4C3的单元格
//。。。计算相关单元格的值
});
}
四个参数: sheet--当前sheet,从页面上能取到的那个控件对象
currentRow--当前行号,从1开始计数
currentCol--当前列号,从1开始计数
level--层次,递归调用时控制层次
特别说明一下那个level参数,这是以防万一公式设定错误导致无休止调用发生错误而设的。为什么会发会递归调用,因为一个值发生改变后会涉及到多个多行多列单元格的值发生联动。比如R7C7的单元格(即7行7列)数字变动了,那它的行小计R7C4、行合计R7C1、列小计R4C7、列合计R1C7,以及最终的总计R1C1都要发生变动,上述单元格有参与百分比计算的也要重新计算相关的百分比。输入值的单元格=》行小计=》行合计=》总计的这个过程就是递归调用过程,递归的终点是在当前表格中找不到有任何一个单元格公式表达式中包含有当前单元格。这个传递过程只有那个累加的单元格才会发生传递,计算百分比的单元格是不会往下传递的,比如一个行合计相关的百分比单元格R9C1(=R8C1/R7C1),这个R9C1是不用往下传递到合计百分比单元格R3C1(=R2C1/R1C1)的。
当前单元格有没有被计算是字符串包含来匹配的。还是以R7C7来举列,跟它有关的公式会找到二个,
R7C4上的累加公式:R7C7+R7C8+R7C9+R7C10+
R9C7上的百分比公式:IF(R7C7=0,'-',R8C7/R7C7)
接下来就是计算R7C4,R9C7时里的新值。
计算之前先作一个预处理,R7C7的话还一般不会有干扰。但如果是找R7C1,你找到了一个公式R7C10 + R7C11,jquery选择里会把这个公式与列为有效公式,计算是不会出错,但效率会差很多。所以用一个正则把干扰公式排除掉
var cellExp = new RegExp(formulaPart + "\\D{1}", "");
var formula = $(this).attr("fpformula");
if (!cellExp.test(formula))//jquery的选择器匹配出来的是弱的,用正则强匹配一下。
return;
接下来要用正则表达式去匹配公式了
var mat = sumFormulaExp.exec(formula);
var percentMat = percentFormulaExp.exec(formula);
var colIndex = parseInt($(this).parent().children().index($(this))) + 1;
var rowIndex = parseInt($(this).parent().attr("fpkey")) + 1;
if (mat != null) {
//...计算合计
} else if (percentMat != null) {
//。。。计算百分比
}
var result = 0;
while (mat != null) {
var row = parseInt(mat[1]) - 1;
var col = parseInt(mat[2]) - 1;
var v = parseFloat(sheet.GetValue(row, col));
result += v;
mat = sumFormulaExp.exec(formula);
}
$(this).children("nobr").text(result);
if (level < 4) {//5层已经够用了
var dl = parseInt(level) + 1;
calcValue(sheet, rowIndex, colIndex, dl);
}
var mRow, mCol, cRow, cCol;
mRow = parseInt(percentMat[1]) - 1;
mCol = parseInt(percentMat[2]) - 1;
cRow = parseInt(percentMat[3]) - 1;
cCol = parseInt(percentMat[4]) - 1;
percentFormulaExp.lastIndex = 0;
var denominator = parseFloat(sheet.GetValue(mRow, mCol));
if (denominator == 0)
$(this).children("nobr").text("-");
else {
var numerator = parseFloat(sheet.GetValue(cRow, cCol));
var percent = numerator * 100 / denominator;
percent = percent.toFixed(1);
$(this).children("nobr").text(percent);
}
上面sheet.GetValue()是控件提供的方法,用来取单元格的值。fpformula是设定的公式,根据数据库里的定义,在第一次加载时在服务端就都设定好了,公式格式是控件支持的,所以静态计算也支持的,首次显示时相关单元格会自动计算。那个$(this).children("nobr").text里的nobr纯粹是查看控件生成的html代码得到的。
经过上述一些处理,现在页面上相应格子里输入一个值一回车,跟它有关的七、八个单元格的值立马就发生了变化,跟在本地编辑Excel文件一样的速度。不过万恶的IE愣是要慢半拍,如果表格超过10行或10列的话更明显,至少要多1秒。以后可以拿这个功能去说服那些人远离IE。