0.要求
用户点击A单元格作为起始点,点击B单元格最为终止点,要根据A、B两个点算出四个边界值,用来组成出一个矩形。
上图红色为终止点,绿色为起始点。
1.算法
关键点1:合并单元格是通过rowspan,colspan来实现,意味一个单元格代替多个单元格,算法中计算出的单元格位置需要与在视图中看到的一致,所以和在左上角单元格(在边界值组成的矩形中)在同一行的单元格删除,不再同一行的隐藏。
我们用left、top、right、bottom四个属性来表示一个单元格的位置,上图中被蓝矩形标记的单元格位置为,left:1、top:1、right:3、bottom:2,上述做法主要为了确保left的获取。
第一步:分别计算A、B两个单元格的left、top、bottom、right,取得四个方向的最值来初步画出一个矩形。关键代码如下
getLeft($tr,$td){ let $tds = $tr.find("td"); let count = 0; for(let i=0;i<$tds.length;i++) { let $td_temp = $tds.eq(i); if($td_temp[0] == $td[0]) { return count; } let colspan = +$td_temp.attr("colspan") || 1; count += colspan; } }
得到单元格在所属行的left属性,right根据left属性加1或colspan属性值,top等同于所属行的位置,bottom根据top属性加上1或rowspan属性值。
关键点2:当被合并的单元格中有已合并单元格的时候,可能出现某个单元格的四个属性超出已划定矩形的情况,一个边界的变化又会影响到其他的边界。所以需要循环确认最终矩形,直到无单元格位置溢出。
如下例:
当选择绿色矩形标记的单元格为起始点,红色标记的单元格为终止点,黑色边框是第一步组成的矩形,但蓝色标记单元格的右边界溢出,当调整矩形如下时
黄色标记的单元格bottom属性溢出,最终为:
推算左边界的值“
get_tdPosLeft($tr,pos){ let $tds = $tr.find("td"); let count = 0; let colspan = 0; for(let i=0;i<$tds.length;i++) { let $td_temp = $tds.eq(i); if(count == pos){ return count; } else if(count > pos){ return count - colspan; } colspan = +$td_temp.attr("colspan") || 1; count += colspan; } }
//pos的的值等于划定的左边界值,如果单元格的left属性等于pos属性,说明单元格left属性没有溢出,如果大于pos,则左边界往左减1或者减去它的colspan属性值
推算右边界的值:
get_tdPosRight($tr,pos){ let $tds = $tr.find("td"); let count = 0; for(let i=0;i<$tds.length;i++) { let $td_temp = $tds.eq(i); let colspan = +$td_temp.attr("colspan") || 1; count += colspan; if(count == pos){ return count; } else if(count > pos){ return count; } } }
//
//pos的值等于划定的右边界值,如果单元格的right属性等于pos属性,说明单元格right属性没有溢出,如果大于pos,则右边界溢出,返回单元格右边界的值。
推算上边界:
hasRowCollposed_top($tr,left,right){ let $tds = $tr.find("td"); let count = 0; for(let i=0;i<$tds.length;i++){ let $td = $tds.eq(i); if($td.hasClass("hidden_rwospan")){ if(count >= left && count < right){ return true; } else{ break; } } count += +$td.attr("colspan") || 1; } return false; }
//这里的left、right等于以推算过后的左边界值和右边界值,如果在left和right之间有td元素含有
//hidden_rwospan类(因合并单元格而添加到元素上),说明上边界溢出,返回true
.hidden_rwospan{
display:none;
}
如下图:
第二行和第三行的html结构如下:
<tr> <td><span class="tab"> </span>3</td> <td rowspan="2"><span class="tab"> </span>3</td> <td><span class="tab"> </span>3</td> <td><span class="tab"> </span>4</td> </tr> <tr> <td ><span class="tab"> </span>3</td> <td class="hidden_rwospan"><span class="tab"> </span>3</td> <td><span class="tab"> </span>3</td> <td><span class="tab"> </span>4</td> </tr>
推算下边界:下边界有一个特殊情况,当发现在左右边界内存在拥有hidden_rwospan类的单元格时,需要判定是否存在穿透的情况,如下图
黄色标记的单元格穿透了最初划定的边界。
hasRowCollposed_bottom($tr,left,right){
let $tds = $tr.find("td"); let count = 0; for(let i=0;i<$tds.length;i++){ let $td = $tds.eq(i); if(!$td.hasClass("hidden_rwospan")){ if(count >= left && count < right){ let rowspan = +$td.attr("rowspan") || 0; if(rowspan > 1){ return true; } } else if(count>=right){ break; } } else{ // 因为rowspan而隐藏的单元格 if(count >= left && count < right){ //如果隐藏 可能发生上面单元格的rowspan穿透了当前单元格 let right_pos = count + (+$td.attr("colspan") || 1); if (this.isCrossRow_collposed($td,$tr.next(),count,right_pos,left,right)) { return true; } } else if(count>=right){ break; } } count += +$td.attr("colspan") || 1; } return false; }
//没被隐藏的单元格如果在范围内有单元格拥有rowspan属性且值大于1,则下边界往下推,在范围内拥有hidden_rwospan的元素,则需要判定是否发生穿透
判定是否发生穿透:
isCrossRow_collposed($td_compare,$tr_next,left_pos,right_pos,left,right){ //如果在左右边界内有单元格拥有hidden_rwospan类,且left属性和right属性和left_pos、right_pos相同,则bottom需要往下推 //left_pos 对比单元格左边的位置,right_pos 右边的位置,left 组成矩形的左边界,right 组成矩形右边界 let $tds = $tr_next.find("td"); let count = 0; for(let i=0;i<$tds.length;i++){ let $td = $tds.eq(i); if($td.hasClass("hidden_rwospan")){ if(count >= left && count < right){ if(count == left_pos ){ let colspan = left_pos + ( +$td.attr("colspan") || 1); if(colspan == right_pos){ return true; } } else if(count > left_pos){ break; } } } if(count>=right){ break; } count += +$td.attr("colspan") || 1; } return false; }
核心代码(Esl class语法):
getArra_collposed($td_start,$td_end){ //画出最初矩形 let left = this.getFarLeft($td_start,$td_end);//得到最靠左单元格的left属性值 let top = this.getFarTop($td_start,$td_end); let right = this.getFarRight($td_start,$td_end); let bottom = this.getFarBottom($td_start,$td_end); let $trs = this.$ele_current.find("tr"), $tr_top = $trs.eq(top), $tr_bottom = $trs.eq(bottom-1); while(true){ let left_temp = left, top_temp = top, right_temp = right, bottom_temp = bottom; for(let i=top;i<bottom;i++){ let $tr = $trs.eq(i); let pos = this.get_tdPosLeft($tr,left); if(left_temp > pos){ left_temp = pos; } } for(let i=top;i<bottom;i++){//推算右边界 let $tr = $trs.eq(i); let pos = this.get_tdPosRight($tr,right); if(right_temp < pos){ right_temp = pos; } } if(this.hasRowCollposed_top($tr_top,left_temp,right_temp)){//推算上边界 top_temp--; } if(this.hasRowCollposed_bottom($tr_bottom,left_temp,right_temp)){//推算下边界 bottom_temp++; } if(left_temp == left && right_temp == right && top_temp == top && bottom_temp == bottom){ break; } else{
//有一个边界值改变则重新推算 left = left_temp; right = right_temp; top = top_temp; bottom = bottom_temp; $tr_top = $trs.eq(top); $tr_bottom = $trs.eq(bottom-1); } } return {left,right,top,bottom}; }
得到四个边界值就可以得到其内的单元格,以及rowspan属性值大小和colspan属性值大小,最后按照开头说的,选取最终划定矩型左上角的单元格进行合并即可,同行的删除,不同行的隐藏。
测试的时候重现上述所说的状态,保证所写代码逻辑都走一遍。