继续表格的有关操作,这篇说说表格行与列的删除。
所有这些操作恼人的地方其实都是在融合的单元格上
a.行的移除
首先计算整个表格的总列数。

总列数
var $row = $(sender).parent().parent();
var $nextRow = $row.next();
var $firstRow = $row.parent().children("tr:first");
var colCount = $firstRow.children("td").size();
$firstRow.children("td[colspan]").each(function() {
colCount = colCount + ($(this).attr("colspan") - 1);
});//表格总列数
然后是当前这一行被行融合所占去的列数,

融合数
var count = colCount - $row.children().size();
$row.children("td[colspan]").each(function() {
count = count - ($(this).attr("colspan") - 1);
});//被行融合被占用的单元格数目
如果当前行没有被行融合占掉的就好说了,如果有的话要把父节点的rowspan值减1,先贴代码吧。

父层节点的rowspan值减1
if (count > 0) {//当前行有位置被行合并单元格占用的,要往上溯把相应的rowspan值减1
var $preRow = $row.prev();
var spanClsName = "";
while ($preRow) {
var spanColumn = $preRow.children("td[rowspan]").get();
for (var i = 0; i < spanColumn.length; i++) {
var $this = $(spanColumn[i]);
if (spanClsName.length > 0 && spanClsName <= $this.attr("class"))
break;
var spanCount = $this.attr("rowspan");
spanCount = spanCount - 1;
if (spanCount == 1)
$this.removeAttr("rowspan");
else
$this.attr("rowspan", spanCount);
var colSpan = $this.attr("colspan");
if (colSpan)
count = count - colSpan;
else
count = count - 1;
if (count == 0)
break;
}
if (count == 0)
break;
if (spanColumn.length > 0)
spanClsName = $(spanColumn[0]).attr("class");
$preRow = $preRow.prev();
}
}
最终把count归零就表示把所有父节点的rowspan值都减1了。
里面那个循环,
var spanColumn = $preRow.children("td[rowspan]").get();
for (var i = 0; i < spanColumn.length; i++) {
}
这里为什么不用jquery的each,而用js原始形态的for,是为了避免把兄弟节点的rowspan值也减1。
接下来,考虑当前行有行融合单元格的,把rowspan值减1,加入到下一行中,这里也用了循环,而且是递减的,是为了把节点以正确的顺序加入到下行的行首

当前行rowspan的处理
var spanRows = $row.children("td[rowspan]").get();//当前行有行合并单元格的,要把rowspan值减1,并加入到下一行
for(var i = spanRows.length - 1;i >= 0;i--){
var $this = $(spanRows[i]);
var spanCount = $this.attr("rowspan");
spanCount = spanCount - 1;
if (spanCount == 1)
$this.removeAttr("rowspan");
else
$this.attr("rowspan", spanCount);
$this.insertBefore($nextRow.children("td:first"));
};
最后把当前行移除
$row.remove(); //当前行移除
b.列的移除
列的移除思路也跟行移除一样,先找父代的colspan的节点;当前列如果是融合的,colspan值减1,加入到下一列(注:这里加入到下一列只是把class改为下一列的class而已)。

取得相关元素,并得到操作符所在行的行索引
var $column = $(sender).parent();
var className = $column.attr("class");
var nextClass = $column.next().attr("class");
var $row = $column.parent();
var $table = $row.parent();
var opIndex = $table.children("tr").index($row);
var ri = 0;
var $chCol, $pchCol;
取操作符所在行的行索引,是为了寻当前列的单元格时有一个终止条件。

从第一行开始找当前列的单元格
while (ri < opIndex) {
var $cRow = $table.children("tr").eq(ri);
$chCol = $cRow.children("td." + className);
if ($chCol.size() == 0) {
//......当前行当前列,没有相应单元格,往左找它的融合单元格
} else {
//......当前行当前列,有单元格存在
}
}
当前列没有单元格时,用操作符列的class来回溯找融合单元格

当前列没单元格时的处理
var $preColumn = $column.prev();
var preClassName = $preColumn.attr("class");
$pchCol = $cRow.children("td." + preClassName);
while ($pchCol.size() == 0) {//这个位置上没有单元格位置,找它的融合单元格
$preColumn = $preColumn.prev();
preClassName = $preColumn.attr("class");
$pchCol = $cRow.children("td." + preClassName);
}
var spanCount = $pchCol.attr("colspan"); //相应的colspan值减1
spanCount--;
if (spanCount == 1)
$pchCol.removeAttr("colspan");
else
$pchCol.attr("colspan", spanCount);
var rSp = $pchCol.attr("rowspan"); //跳过行融合的单元格
if (rSp && rSp > 1)
ri += rSp;
else
ri++;

当前列存在单元格时的处理
var $this = $chCol.eq(0);
var spanCount = $this.attr("colspan"); //当前单元格是融合的
if (spanCount && spanCount > 1) {
spanCount--;
if (spanCount > 1)
$this.attr("colspan", spanCount);
else
$this.removeAttr("colspan");
$this.attr("class", nextClass); //colspan值减1后加到后面一列去
}
var rSp = $this.attr("rowspan");
if (rSp && rSp > 1)
ri += rSp;
else
ri++;
最后把这一列移除
$("td." + className).each(function() {
$(this).remove();
});
很久以来都在做一些没有营养的东西,即使工作之中有时会有一些比较有技巧性的东西也是基于一些不是非常用的第三方组件的,所以也就没有记下来。前一阵做了个编辑表格列头行头定义的功能,写了一大堆javascript,回头来看看还算有点用途,保不准以后会用到,这二天有空把它整理一下。
先说明一下,下面的代码都是基于Jquery 1.3.2的。
因为要动态生成表格的行头列头,所以把一些定义放到数据库中,这些定义包括标题、层级关系、行列融合、序号、以及其他一些跟计算有关的属性定义等。现在就是做一个能编辑这个定义的简单界面,在这篇文章里说说它们之间的顺序调整,即表格行列的移动。
如果是没有任何行列融合的话,移动是非常容易的,但一旦有了行列融合存在,移动就得考虑很多情况了,同一行的元素还好说,如何找同一列的元素是个问题,所以我最终是对行列头每个单元格都定义了相应的class来标识它们是否是属于同一列的,同一列的class是相同的。
还有这里假定不会出现奇异表格。即列头从上往下,列融合的数目只会越来越小,最后一个td只占一列;同理行头从左往右,行融合的数目也只会越来越小,最后一个td也只占一行。当移动的时候,如果是碰上有行列融合的,最终移动的时候是把整个融合在一起那些行列移动到新位置的。最后在行头定义的最后一列,列头定义的最后一行,我加入一列(或行)来放置操作符,这也是为了明确当前操作的是哪一行、哪一列,因为这些单元格是铁定只占一行一列的。
a.列的移动
主要是讲列的左移,因为右移是可以化为左移来处理的。

操作之前的一些处理
var $column = $(sender).parent();
var className = $column.attr("class");
var preClassName = $column.prev().attr("class");
if (!preClassName)
return;
var $row = $column.parent();
var $table = $row.parent();
var ri = 0;
var $sCol, $pCol;
那个return,是为了当前列是第一列的情况下直接返回用。
基于不会出现奇异表格的事实,从上往下找当前列的第一个单元格
$sCol = $table.find("td." + className).eq(0);
记下第一个单元格出行时的行索引
var mcRowIndex = $table.children("tr").index($sCol.parent());
记下这个索引以后要用。

移动位置与要移动的列数
$pCol = $sCol.prev();
preClassName = $pCol.attr("class");
var $preOpColumn = $row.children("td." + preClassName);
var preEndIndex = $row.children("td").index($preOpColumn); //移动列时,最终的位置,用操作符所在单元格的列索引来判定最终移动列的位置
var moveCount = 1; //要移动的列数
moveCount = $sCol.attr("colspan");
if (!moveCount)
moveCount = 1;
位置的取得主要就是利用同列的class相同,以及操作符单元格只占一列这二个特性
下面是多列移动的骨架,单列移动下面单独列出

多列的移动
var moveIndex = 0;
var clsName;
var $mvColumn = $column;
while (moveIndex < moveCount) {
clsName = $mvColumn.attr("class");
$mvColumn = $mvColumn.next();
var $preColumn, $iterOpCol, opIndex;
$("td." + clsName).each(function() {
//......单列的移动......
});
moveIndex++;
preEndIndex++;
}
单独一列的移动,即在那个$("td." + clsName).each()里面的遍历函数

单列的移动
$preColumn = $(this).prev();
$iterOpCol = $row.children("td." + $preColumn.attr("class")); //前一单元格所在列的操作符单元格
if ($iterOpCol) {
opIndex = $row.children("td").index($iterOpCol);
while (opIndex > preEndIndex) {//找移动目的地的单元格
$preColumn = $preColumn.prev();
$iterOpCol = $row.children("td." + $preColumn.attr("class")); //前一单元格所在列的操作符单元格
if (!$iterOpCol)
break;
opIndex = $row.children("td").index($iterOpCol);
}
if (opIndex >= preEndIndex)
$(this).insertBefore($preColumn);
else
$(this).insertAfter($preColumn);
}
用一个while (opIndex > preEndIndex)循环找目的地,那是因为表格融合的关系,这一行不一定会存在class名为preClassName的单元格。最终找到的是“它”之前或之后的单元格。
从表象上讲,移动的代码到这里就结束了,但后续操作的关系,还有一个东西要处理,即那些未参与移动的单元格的class的变动。比如:
<tr>
<td colspan="2" class="1" />
</tr>
<tr>
<td class="1" />
<td class="2" />
</tr>
当第2列左移时,第一行那个单元格并没有移动过,最张要把它的class从“1”改为“2”.
这时前面那个mcRowIndex就用得上了。
$table.children("tr:lt(" + mcRowIndex + ")").children("td." + preClassName).each(function() {
$(this).attr("class", className);
});
在mcRowIndex这一行之前的其实都是没有参与移动的。
b.行的移动
行的移动处理方式其实跟列的移动差不多,唯一要说明的是,操作符所在那一列是专门定义class的。移动行比移动列简单,一是不用处理class的变换问题,二是交换二行比交换二列要容易。
这里就不作一一解释了。

上移一行
var $row = $(sender).parent().parent();
var $preRow = $row.prev();
var $opCol = $row.children("td.op:first");
var $preopCol = $preRow.children("td.op:first");
if ($preopCol.size() == 0)
return;
var $rowFCell = $row.children("td:first");
var clsName = $rowFCell.attr("class");
var moveCount = $rowFCell.attr("rowspan");
if (!moveCount)
moveCount = 1;
while ($preRow.children("td." + clsName).size() == 0)
$preRow = $preRow.prev();
var stIndex = $preRow.children("td").index($preRow.children("td." + clsName));
var moveIndex = 0;
var $moveRow = $row;
var $nRow;
while (moveIndex < moveCount) {
$nRow = $moveRow.next();
$moveRow.insertBefore($preRow);
$moveRow = $nRow;
moveIndex++;
}
$preRow.children("td:lt(" + stIndex + ")").each(function() {
$(this).insertBefore($rowFCell);
});
下移一行也可以化为上移一行处理的,这里略。