jQuery仿excel表格单元格合并插件

2017-8-3更新:修复已经执行过合并操作的table,单元格选择及合并不正确。

  jQuery表格单元格合并插件,将合并后的所有单元格内容到第一个单元格中,支持已经被合并的多单元格的选择合并,功能和excel单元格合并功能一样。

jQuery防excel表格单元格合并插件

  jQuery防excel表格单元格合并插件源代码如下

<script type="text/javascript" src="https://cdn.bootcss.com/jquery/1.7.1/jquery.min.js"></script>
<table border="1">
        <tr><td colspan="2">0-0</td><td>0-1</td><td>0-2</td><td>0-3</td><td>0-4</td><td>0-5</td><td>0-6</td><td>0-7</td><td>0-8</td></tr>
        <tr><td>1-0</td><td>1-1</td><td>1-2</td><td>1-3</td><td>1-4</td><td>1-5</td><td>1-6</td><td>1-7</td><td>1-8</td><td>1-9</td></tr>
        <tr><td>2-0</td><td>2-1</td><td>2-2</td><td class="" colspan="2" rowspan="2">2-3,2-4,<br>3-3,3-4</td><td>2-5</td><td>2-6</td><td>2-7</td><td>2-8</td><td>2-9</td></tr>
        <tr><td>3-0</td><td>3-1</td><td>3-2</td><td>3-5</td><td class="" colspan="2" rowspan="3">3-6,3-7,<br>4-6,4-7,<br>5-6,5-7</td><td>3-8</td><td>3-9</td></tr>
        <tr><td>4-0</td><td>4-1</td><td>4-2</td><td>4-3</td><td>4-4</td><td>4-5</td><td>4-8</td><td>4-9</td></tr>
        <tr><td>5-0</td><td>5-1</td><td>5-2</td><td>5-3</td><td>5-4</td><td>5-5</td><td>5-8</td><td>5-9</td></tr>
        <tr><td>6-0</td><td>6-1</td><td>6-2</td><td>6-3</td><td>6-4</td><td>6-5</td><td>6-6</td><td>6-7</td><td>6-8</td><td>6-9</td></tr>
        <tr><td>7-0</td><td>7-1</td><td>7-2</td><td>7-3</td><td>7-4</td><td>7-5</td><td>7-6</td><td>7-7</td><td>7-8</td><td>7-9</td></tr>
        <tr><td rowspan="2">8-0</td><td>8-1</td><td>8-2</td><td>8-3</td><td>8-4</td><td>8-5</td><td>8-6</td><td>8-7</td><td>8-8</td><td>8-9</td></tr>
        <tr><td>9-1</td><td>9-2</td><td>9-3</td><td>9-4</td><td>9-5</td><td>9-6</td><td>9-7</td><td>9-8</td><td>9-9</td></tr>
</table>
<script>
    //需要的样式
    document.write('<style>.cannotselect{-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;-khtml-user-select:none;user-select:none;}td.selected{background:#0094ff;color:#fff}td.hide{display:none}</style>');
    //jQuery表格单元格合并插件,功能和excel单元格合并功能一样,并且可以保留合并后的所有单元格内容到第一个单元格中
    $.fn.tableMergeCells = function () {
        //***请保留原作者相关信息
        //***power by showbo,http://www.w3dev.cn
        return this.each(function () {
            var tb = $(this), startTD, endTD, MMRC = { startRowIndex: -1, endRowIndex: -1, startCellIndex: -1, endCellIndex: -1 };
            //如果表格存在合并的,先补全td在初始化rc
            var tds = tb.find('td[colspan],td[rowspan]'), v,vc;
            if (tds.length) {
                tds.filter('[colspan]').each(function () {
                    v = (parseInt($(this).attr('colspan')) || 1) - 1;
                    if (v > 0) for (var i = 0; i < v; i++) $(this).after('<td class="hide"></td>');
                }).end().filter('[rowspan]').each(function () {
                    v = parseInt($(this).attr('rowspan')) || 1;
                    vc = parseInt($(this).attr('colspan')) || 1;
                    if (v > 1) {
                        for (var i = 1; i < v; i++) {
                            var td = $(this.parentNode.parentNode.rows[this.parentNode.rowIndex + i].cells[this.cellIndex]);
                            for (var j = 0; j < vc; j++) td.before('<td class="hide"></td>')
                        }
                    }
                });
            }
            //初始化所有单元格的行列下标内容并存储到dom对象中
            tb.find('tr').each(function (r, tr) {
                $('td', this).each(function (c, td) {
                    $(this).data('rc', {
                        r: r, c: c,
                        maxc: c + (parseInt(this.getAttribute('colspan')) || 1) - 1,
                        maxr: r + (parseInt(this.getAttribute('rowspan')) || 1) - 1
                    });
                })
            });
            if (tds.length) tb.find('td.hide').remove();//删除补全的td
            //添加表格禁止选择样式和事件
            tb.addClass('cannotselect').bind('selectstart', function () { return false });
            //选中单元格处理函数
            function addSelectedClass() {
                var selected = false, rc, t;
                tb.find('td').each(function () {
                    rc = $(this).data('rc');
                    //判断单元格左上坐标是否在鼠标按下和移动到的单元格行列区间内
                    selected = rc.r >= MMRC.startRowIndex && rc.r <= MMRC.endRowIndex && rc.c >= MMRC.startCellIndex && rc.c <= MMRC.endCellIndex;
                    if (!selected && rc.maxc) {//合并过的单元格,判断另外3(左下,右上,右下)个角的行列是否在区域内
                        selected =
                            (rc.maxr >= MMRC.startRowIndex && rc.maxr <= MMRC.endRowIndex && rc.c >= MMRC.startCellIndex && rc.c <= MMRC.endCellIndex) ||//左下
                            (rc.r >= MMRC.startRowIndex && rc.r <= MMRC.endRowIndex && rc.maxc >= MMRC.startCellIndex && rc.maxc <= MMRC.endCellIndex) ||//右上
                            (rc.maxr >= MMRC.startRowIndex && rc.maxr <= MMRC.endRowIndex && rc.maxc >= MMRC.startCellIndex && rc.maxc <= MMRC.endCellIndex);//右下

                    }
                    if (selected) this.className = 'selected';
                });
                var rangeChange = false;
                tb.find('td.selected').each(function () { //从已选中单元格中更新行列的开始结束下标
                    rc = $(this).data('rc');
                    t = MMRC.startRowIndex;
                    MMRC.startRowIndex = Math.min(MMRC.startRowIndex, rc.r);
                    rangeChange = rangeChange || MMRC.startRowIndex != t;

                    t = MMRC.endRowIndex;
                    MMRC.endRowIndex = Math.max(MMRC.endRowIndex, rc.maxr || rc.r);
                    rangeChange = rangeChange || MMRC.endRowIndex != t;

                    t = MMRC.startCellIndex;
                    MMRC.startCellIndex = Math.min(MMRC.startCellIndex, rc.c);
                    rangeChange = rangeChange || MMRC.startCellIndex != t;

                    t = MMRC.endCellIndex;
                    MMRC.endCellIndex = Math.max(MMRC.endCellIndex, rc.maxc || rc.c);
                    rangeChange = rangeChange || MMRC.endCellIndex != t;
                });
                //注意这里如果用代码选中过合并的单元格需要重新执行选中操作
                if (rangeChange) addSelectedClass();
            }
            function onMousemove(e) {//鼠标在表格单元格内移动事件
                e = e || window.event;
                var o = e.srcElement || e.target;
                if (o.tagName == 'TD') {
                    endTD = o;
                    var sRC = $(startTD).data('rc'), eRC = $(endTD).data('rc'), rc;
                    MMRC.startRowIndex = Math.min(sRC.r, eRC.r);
                    MMRC.startCellIndex = Math.min(sRC.c, eRC.c);
                    MMRC.endRowIndex = Math.max(sRC.r, eRC.r);
                    MMRC.endCellIndex = Math.max(sRC.c, eRC.c);
                    tb.find('td').removeClass('selected');
                    addSelectedClass();
                }
            }
            function onMouseup(e) {//鼠标弹起事件
                tb.unbind({ mouseup: onMouseup, mousemove: onMousemove });
                if (startTD && endTD && startTD != endTD && confirm('确认合并?!')) {//开始结束td不相同确认合并
                    var tds = tb.find('td.selected'), firstTD = tds.eq(0), index = -1, t, addBR
                        , html = tds.filter(':gt(0)').map(function () {
                            t = this.parentNode.rowIndex;
                            addBR = index != -1 && index != t;
                            index = t;
                            return (addBR ? '<br>' : '') + this.innerHTML
                        }).get().join(',');
                    tds.filter(':gt(0)').remove(); firstTD.append(',' + html.replace(/,(<br>)/g, '$1'));

                    //更新合并的第一个单元格的缓存rc数据为所跨列和行
                    var rc = firstTD.attr({ colspan: MMRC.endCellIndex - MMRC.startCellIndex + 1, rowspan: MMRC.endRowIndex - MMRC.startRowIndex + 1 }).data('rc');
                    rc.maxc = rc.c + MMRC.endCellIndex - MMRC.startCellIndex; rc.maxr = rc.r + MMRC.endRowIndex - MMRC.startRowIndex;

                    firstTD.data('rc', rc);

                }
                tb.find('td').removeClass('selected');
                startTD = endTD = null;
            }
            function onMousedown(e) {
                var o = e.target;
                if (o.tagName == 'TD') {
                    startTD = o;
                    tb.bind({ mouseup: onMouseup, mousemove: onMousemove });
                }
            }
            tb.mousedown(onMousedown);
        });
    };

    $('table').tableMergeCells();
</script>

原生JS实现用下面代码


<table border="1">
    <tr><td colspan="2">0-0</td><td>0-1</td><td>0-2</td><td>0-3</td><td>0-4</td><td>0-5</td><td>0-6</td><td>0-7</td><td>0-8</td></tr>
    <tr><td>1-0</td><td>1-1</td><td>1-2</td><td>1-3</td><td>1-4</td><td>1-5</td><td>1-6</td><td>1-7</td><td>1-8</td><td>1-9</td></tr>
    <tr><td>2-0</td><td>2-1</td><td>2-2</td><td class="" colspan="2" rowspan="2">2-3,2-4,<br>3-3,3-4</td><td>2-5</td><td>2-6</td><td>2-7</td><td>2-8</td><td>2-9</td></tr>
    <tr><td>3-0</td><td>3-1</td><td>3-2</td><td>3-5</td><td class="" colspan="2" rowspan="3">3-6,3-7,<br>4-6,4-7,<br>5-6,5-7</td><td>3-8</td><td>3-9</td></tr>
    <tr><td>4-0</td><td>4-1</td><td>4-2</td><td>4-3</td><td>4-4</td><td>4-5</td><td>4-8</td><td>4-9</td></tr>
    <tr><td>5-0</td><td>5-1</td><td>5-2</td><td>5-3</td><td>5-4</td><td>5-5</td><td>5-8</td><td>5-9</td></tr>
    <tr><td>6-0</td><td>6-1</td><td>6-2</td><td>6-3</td><td>6-4</td><td>6-5</td><td>6-6</td><td>6-7</td><td>6-8</td><td>6-9</td></tr>
    <tr><td>7-0</td><td>7-1</td><td>7-2</td><td>7-3</td><td>7-4</td><td>7-5</td><td>7-6</td><td>7-7</td><td>7-8</td><td>7-9</td></tr>
    <tr><td rowspan="2">8-0</td><td>8-1</td><td>8-2</td><td>8-3</td><td>8-4</td><td>8-5</td><td>8-6</td><td>8-7</td><td>8-8</td><td>8-9</td></tr>
    <tr><td>9-1</td><td>9-2</td><td>9-3</td><td>9-4</td><td>9-5</td><td>9-6</td><td>9-7</td><td>9-8</td><td>9-9</td></tr>
</table>
<script>
    document.write('<style>.cannotselect{-moz-user-select:none;-webkit-user-select:none;-ms-user-select:none;-khtml-user-select:none;user-select:none;}td.selected{background:#0094ff;color:#fff}td.hide{display:none}</style>');


    function tableMergeCells(tb) {
        var startTD, endTD, MMRC = { startRowIndex: -1, endRowIndex: -1, startCellIndex: -1, endCellIndex: -1 };
        //如果表格存在合并的,先补全td在初始化rc
        var tds = Array.from(tb.querySelectorAll('td[colspan],td[rowspan]')), v, vc;
        if (tds.length) {

            tds.filter(i => i.getAttribute('colspan')).forEach(td => {
                v = (parseInt(td.getAttribute('colspan')) || 1) - 1;
                if (v > 0)
                    for (var i = 0; i < v; i++) {
                        var ntd = document.createElement('td');
                        ntd.className = 'hide';
                        td.insertAdjacentElement('afterEnd', ntd);
                    }
            });


            tds.filter(i => i.getAttribute('rowspan')).forEach(td=> {
                v = parseInt(td.getAttribute('rowspan')) || 1;
                vc = parseInt(td.getAttribute('colspan')) || 1;
                if (v > 1) {
                    for (var i = 1; i < v; i++) {
                        var mtd = td.parentNode.parentNode.rows[td.parentNode.rowIndex + i].cells[td.cellIndex];
                        for (var j = 0; j < vc; j++) {
                            var ntd = document.createElement('td');
                            ntd.className = 'hide';
                            mtd.insertAdjacentElement('beforeBegin',ntd)
                        }
                    }
                }
            });
        }
        //初始化所有单元格的行列下标内容并存储到dom对象中
        tb.querySelectorAll('tr').forEach((tr, r) => {
            tr.querySelectorAll('td').forEach((td,c)=>{
                td.rc = {
                    r: r, c: c,
                    maxc: c + (parseInt(td.getAttribute('colspan')) || 1) - 1,
                    maxr: r + (parseInt(td.getAttribute('rowspan')) || 1) - 1
                };
            })
        });
        var tdHide = tb.querySelectorAll('td.hide');
        for (var td of tdHide) td.parentNode.removeChild(td);
        //添加表格禁止选择样式和事件
        tb.classList.add('cannotselect');
        tb.addEventListener('selectstart', function () { return false });
        //选中单元格处理函数
        function addSelectedClass() {
            var selected = false, rc, t;
            tb.querySelectorAll('td').forEach((td,index)=>{
                rc = td.rc;
                //判断单元格左上坐标是否在鼠标按下和移动到的单元格行列区间内
                selected = rc.r >= MMRC.startRowIndex && rc.r <= MMRC.endRowIndex && rc.c >= MMRC.startCellIndex && rc.c <= MMRC.endCellIndex;
                if (!selected && rc.maxc) {//合并过的单元格,判断另外3(左下,右上,右下)个角的行列是否在区域内
                    selected =
                        (rc.maxr >= MMRC.startRowIndex && rc.maxr <= MMRC.endRowIndex && rc.c >= MMRC.startCellIndex && rc.c <= MMRC.endCellIndex) ||//左下
                        (rc.r >= MMRC.startRowIndex && rc.r <= MMRC.endRowIndex && rc.maxc >= MMRC.startCellIndex && rc.maxc <= MMRC.endCellIndex) ||//右上
                        (rc.maxr >= MMRC.startRowIndex && rc.maxr <= MMRC.endRowIndex && rc.maxc >= MMRC.startCellIndex && rc.maxc <= MMRC.endCellIndex);//右下

                }
                if (selected) td.className = 'selected';
            });
            var rangeChange = false;
            tb.querySelectorAll('td.selected').forEach( (td,index) =>{ //从已选中单元格中更新行列的开始结束下标
                rc = td.rc;
                t = MMRC.startRowIndex;
                MMRC.startRowIndex = Math.min(MMRC.startRowIndex, rc.r);
                rangeChange = rangeChange || MMRC.startRowIndex != t;

                t = MMRC.endRowIndex;
                MMRC.endRowIndex = Math.max(MMRC.endRowIndex, rc.maxr || rc.r);
                rangeChange = rangeChange || MMRC.endRowIndex != t;

                t = MMRC.startCellIndex;
                MMRC.startCellIndex = Math.min(MMRC.startCellIndex, rc.c);
                rangeChange = rangeChange || MMRC.startCellIndex != t;

                t = MMRC.endCellIndex;
                MMRC.endCellIndex = Math.max(MMRC.endCellIndex, rc.maxc || rc.c);
                rangeChange = rangeChange || MMRC.endCellIndex != t;
            });
            //注意这里如果用代码选中过合并的单元格需要重新执行选中操作
            if (rangeChange) addSelectedClass();
        }
        function onMousemove(e) {//鼠标在表格单元格内移动事件
            e = e || window.event;
            var o = e.srcElement || e.target;
            if (o.tagName == 'TD') {
                endTD = o;
                if (endTD == startTD) return;
                var sRC = startTD.rc, eRC = endTD.rc, rc;
                MMRC.startRowIndex = Math.min(sRC.r, eRC.r);
                MMRC.startCellIndex = Math.min(sRC.c, eRC.c);
                MMRC.endRowIndex = Math.max(sRC.r, eRC.r);
                MMRC.endCellIndex = Math.max(sRC.c, eRC.c);
                tb.querySelectorAll('td').forEach(i => i.classList.remove('selected'))
       
                addSelectedClass();
            }
        }

        function onMouseup(e) {//鼠标弹起事件
            tb.onmouseup = tb.onmousemove = null;//释放事件
            if (startTD && endTD && startTD != endTD && confirm('确认合并?!')) {//开始结束td不相同确认合并
                var tds = Array.from(tb.querySelectorAll('td.selected')), firstTD = tds[0], index = -1, t, addBR
                    , html = tds.map(td=> {
                        t = td.parentNode.rowIndex;
                        addBR = index != -1 && index != t;
                        index = t;
                        return (addBR ? '<br>' : '') + td.innerHTML
                    }).join(',');
                for (var i = 1; i < tds.length; i++)tds[i].parentNode.removeChild(tds[i]);
                firstTD.innerHTML =  html.replace(/,(<br>)/g, '$1');

                //更新合并的第一个单元格的缓存rc数据为所跨列和行
                firstTD.setAttribute('colspan', MMRC.endCellIndex - MMRC.startCellIndex + 1)
                firstTD.setAttribute('rowspan', MMRC.endRowIndex - MMRC.startRowIndex + 1 )
                var rc = firstTD.rc;
                rc.maxc = rc.c + MMRC.endCellIndex - MMRC.startCellIndex;
                rc.maxr = rc.r + MMRC.endRowIndex - MMRC.startRowIndex;
                //firstTD.rc = rc;

            }
            tb.querySelectorAll('td').forEach(i => i.classList.remove('selected'))
            startTD = endTD = null;
        }
        function onMousedown(e) {
            var o = e.target;
            if (o.tagName == 'TD') {
                startTD = o;
                //绑定事件
                tb.onmousemove = onMousemove;
                tb.onmouseup = onMouseup;
            }
        }
        tb.onmousedown = onMousedown;
    }
    tableMergeCells(document.querySelector('table'));
</script>

 

加支付宝好友偷能量挖...


原创文章,转载请注明出处:jQuery仿excel表格单元格合并插件

评论(2)Web开发网
阅读(1291)喜欢(6)JavaScript/Ajax开发技巧