JS 自定义 table 元素总是缺少 1 行

JS custom table element missing always 1 row

我制作了一个自定义 TABLE 元素,它通过 'rows' 属性更新了行数,但它少了一行,

例如:我设置 rows='10' 输出行数为 9。

我知道,如果我删除添加到 'TBODY' 的 div 元素,问题就解决了,但我希望它在 'TBODY' 中,但我不明白为什么会这样正因为如此 div.

注意:全屏运行需要

'use strict';

class betterTable extends HTMLElement {
    #colsLength;
    constructor() {
        super();

        this.#colsLength = 0;

        /* TABLE */
        this.table = document.createElement('table');

        /* HEADER */
        this.thead = document.createElement('thead');
        this.table.appendChild(this.thead);

        this.theadRow = document.createElement('tr');
        this.thead.appendChild(this.theadRow);

        /* BODY */
        this.tbody = document.createElement('tbody');
        this.table.appendChild(this.tbody);

        this.scrollbar = document.createElement('div');
        this.tbody.appendChild(this.scrollbar);
    }

    connectedCallback() {
        this.render();
    }

    render() {
        this.appendChild(this.table);
    }

    setColumn(colsName = ['null']) {
        this.setCell(this.theadRow, colsName.length, colsName);
        this.#colsLength = colsName.length;
        this.cols = colsName.length;
    }

    setCell(row, len = this.#colsLength, cellText = new Array(this.#colsLength)) {
        // Remove unnecessary cells
        while (row.childNodes.length > len) row.lastChild.remove();

        // Add missing cells
        for (let i = 0; len > row.childNodes.length; i++) {
            const cell = row.parentNode
                ? row.parentNode.tagName === 'THEAD'
                    ? document.createElement('th')
                    : document.createElement('td')
                : document.createElement('td');
            row.appendChild(cell);
        }

        for (let i = 0; i < row.childNodes.length; i++) {
            if (typeof cellText[i] === 'string') {
                if (row.childNodes[i].innerText != cellText[i]) {
                    row.childNodes[i].innerText = cellText[i];
                }
            } else if (cellText[i] instanceof Element) {
                row.childNodes[i].appendChild = cellText[i];
            } else {
                if (row.childNodes[i].innerText != 'null') {
                    row.childNodes[i].innerText = 'null';
                }
            }
        }
    }

    addRow(body, dataCell) {
        const row = document.createElement('tr');
        this.setCell(row, this.#colsLength, dataCell);
        body.appendChild(row);
    }

    addMultiRow(body, dataCell = ['null']) {
        // Remove unnecessary rows
        for (let i = body.childNodes.length - 1; body.childNodes.length - 1 > dataCell.length; i--) {
            let row = body.childNodes[i];
            if (row && row.tagName === 'TR') {
                row.remove();
            }
        }

        for (let i = 0; i < dataCell.length; i++) {
            let row = body.childNodes[i];
            if (row && row.tagName === 'TR') {
                this.setCell(row, this.#colsLength, dataCell[i]);
            } else {
                // Add missing rows
                this.addRow(body, dataCell[i]);
            }
        }
    }

    /* ///////////// Attributes ///////////// */
    static get observedAttributes() {
        return ['cols', 'rows'];
    }
    attributeChangedCallback(name, oldValue, newValue) {
        let len;
        switch (name) {
            case 'cols':
                len = parseInt(newValue);

                if (len !== NaN && this.#colsLength !== len && newValue != oldValue) {
                    this.setColumn(new Array(len));
                }
                break;
            case 'rows':
                len = parseInt(newValue);

                if (len !== NaN && newValue != oldValue) {
                    const rows = new Array(len).fill(new Array(len).fill('1'));

                    this.addMultiRow(this.tbody, rows);
                }
                break;

            default:
                break;
        }
    }

    /* // GETTER & SETTER // */
    get length() {
        return this.#colsLength;
    }

    get cols() {
        return this.getAttribute('cols');
    }

    get rows() {
        return this.getAttribute('rows');
    }

    set cols(num) {
        this.setAttribute('cols', num);
    }

    set rows(num) {
        this.setAttribute('rows', num);
    }
}

customElements.define('better-table', betterTable);
* {
    margin: 0;
    padding: 0;
}

/* //////////////////////////////////////// */
/* //////////// Global Style ////////////// */
/* //////////////////////////////////////// */

/* /// The Table Itself /// */
better-table {
    width: 100%;
    height: 100%;
}

table {
    width: 100%;
    height: 100%;
    display: table;
    box-sizing: border-box;
}

table thead,
table tbody,
table tr {
    width: 100%;
    display: table;
    table-layout: fixed;
}

table tbody tr {
    border-bottom: 1px solid black;
}

table th,
table td {
    padding: 12px 15px;
    overflow-x: hidden;
}

/* //////////////////////////////////////// */

/* //////////////////////////////////////// */
/* ///////// Header/Column Style ////////// */
/* //////////////////////////////////////// */

/* /// Header/Column row style /// */
table thead {
    background: #f38181;
}

table thead tr {
    position: sticky;
    top: 0;
    text-align: center;
}

/* //////////////////////////////////////// */

/* //////////////////////////////////////// */
/* /////// Body/Row Contents Style //////// */
/* //////////////////////////////////////// */

table tbody {
    display: block;
    overflow: hidden;
    table-layout: fixed;
    max-height: 65vh;
}

/* /// even row /// */
table tbody tr {
    text-align: center;
    background-color: #fddfdf;
    color: #000;
}

/* /// odd row /// */
table tbody tr:nth-child(even) {
    background-color: #eba4a4;
    color: #000;
}

/* //////////////////////////////////////// */
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <link rel="stylesheet" href="table.css" />
        <script defer type="module" src="./table.js"></script>
    </head>
    <body>
        <style>
            html,
            body {
                width: 100%;
                height: 100%;
                overflow: hidden;
            }
            .content {
                width: 100%;
                height: 500px;
            }
            .box {
                position: absolute;

                left: 20px;
                bottom: 50px;
            }
        </style>
        <div class="content">
            <better-table cols="4" rows="10" lang="heb"></better-table>
        </div>
    </body>
</html>

此逻辑导致您的行附加问题

for (let i = 0; i < dataCell.length; i++) {
    let row = body.childNodes[i];
    if (row && row.tagName === 'TR') {
       //after the first row added
       //it goes inside this, so it never call `addRow` (one row is missing from here)
       this.setCell(row, this.#colsLength, dataCell[i]);
    } else {
       // the first row added
       this.addRow(body, dataCell[i]);
    }
}

可能的解决方法是

for (let i = 0; i < dataCell.length; i++) {
    this.addRow(body, dataCell[i]);
}

根据您的逻辑,我假设您最初没有行,因此我们不需要检查 tbody

中的现有行

'use strict';

class betterTable extends HTMLElement {
    #colsLength;
    constructor() {
        super();

        this.#colsLength = 0;

        /* TABLE */
        this.table = document.createElement('table');

        /* HEADER */
        this.thead = document.createElement('thead');
        this.table.appendChild(this.thead);

        this.theadRow = document.createElement('tr');
        this.thead.appendChild(this.theadRow);

        /* BODY */
        this.tbody = document.createElement('tbody');
        this.table.appendChild(this.tbody);

        this.scrollbar = document.createElement('div');
        this.tbody.appendChild(this.scrollbar);
    }

    connectedCallback() {
        this.render();
    }

    render() {
        this.appendChild(this.table);
    }

    setColumn(colsName = ['null']) {
        this.setCell(this.theadRow, colsName.length, colsName);
        this.#colsLength = colsName.length;
        this.cols = colsName.length;
    }

    setCell(row, len = this.#colsLength, cellText = new Array(this.#colsLength)) {
        // Remove unnecessary cells
        while (row.childNodes.length > len) row.lastChild.remove();

        // Add missing cells
        for (let i = 0; len > row.childNodes.length; i++) {
            const cell = row.parentNode
                ? row.parentNode.tagName === 'THEAD'
                    ? document.createElement('th')
                    : document.createElement('td')
                : document.createElement('td');
            row.appendChild(cell);
        }

        for (let i = 0; i < row.childNodes.length; i++) {
            if (typeof cellText[i] === 'string') {
                if (row.childNodes[i].innerText != cellText[i]) {
                    row.childNodes[i].innerText = cellText[i];
                }
            } else if (cellText[i] instanceof Element) {
                row.childNodes[i].appendChild = cellText[i];
            } else {
                if (row.childNodes[i].innerText != 'null') {
                    row.childNodes[i].innerText = 'null';
                }
            }
        }
    }

    addRow(body, dataCell) {
        const row = document.createElement('tr');
        this.setCell(row, this.#colsLength, dataCell);
        body.appendChild(row);
    }

    addMultiRow(body, dataCell = ['null']) {
        // Remove unnecessary rows
        for (let i = body.childNodes.length - 1; body.childNodes.length - 1 > dataCell.length; i--) {
            let row = body.childNodes[i];
            if (row && row.tagName === 'TR') {
                row.remove();
            }
        }

        //adding rows
        for (let i = 0; i < dataCell.length; i++) {
            this.addRow(body, dataCell[i]);
        }
    }

    /* ///////////// Attributes ///////////// */
    static get observedAttributes() {
        return ['cols', 'rows'];
    }
    attributeChangedCallback(name, oldValue, newValue) {
        let len;
        switch (name) {
            case 'cols':
                len = parseInt(newValue);

                if (len !== NaN && this.#colsLength !== len && newValue != oldValue) {
                    this.setColumn(new Array(len));
                }
                break;
            case 'rows':
                len = parseInt(newValue);

                if (len !== NaN && newValue != oldValue) {
                    const rows = new Array(len).fill(new Array(len).fill('1'));
                    this.addMultiRow(this.tbody, rows);
                }
                break;

            default:
                break;
        }
    }

    /* // GETTER & SETTER // */
    get length() {
        return this.#colsLength;
    }

    get cols() {
        return this.getAttribute('cols');
    }

    get rows() {
        return this.getAttribute('rows');
    }

    set cols(num) {
        this.setAttribute('cols', num);
    }

    set rows(num) {
        this.setAttribute('rows', num);
    }
}

customElements.define('better-table', betterTable);
* {
    margin: 0;
    padding: 0;
}

/* //////////////////////////////////////// */
/* //////////// Global Style ////////////// */
/* //////////////////////////////////////// */

/* /// The Table Itself /// */
better-table {
    width: 100%;
    height: 100%;
}

table {
    width: 100%;
    height: 100%;
    display: table;
    box-sizing: border-box;
}

table thead,
table tbody,
table tr {
    width: 100%;
    display: table;
    table-layout: fixed;
}

table tbody tr {
    border-bottom: 1px solid black;
}

table th,
table td {
    padding: 12px 15px;
    overflow-x: hidden;
}

/* //////////////////////////////////////// */

/* //////////////////////////////////////// */
/* ///////// Header/Column Style ////////// */
/* //////////////////////////////////////// */

/* /// Header/Column row style /// */
table thead {
    background: #f38181;
}

table thead tr {
    position: sticky;
    top: 0;
    text-align: center;
}

/* //////////////////////////////////////// */

/* //////////////////////////////////////// */
/* /////// Body/Row Contents Style //////// */
/* //////////////////////////////////////// */

table tbody {
    display: block;
    overflow: hidden;
    table-layout: fixed;
    max-height: 65vh;
}

/* /// even row /// */
table tbody tr {
    text-align: center;
    background-color: #fddfdf;
    color: #000;
}

/* /// odd row /// */
table tbody tr:nth-child(even) {
    background-color: #eba4a4;
    color: #000;
}

/* //////////////////////////////////////// */
<!DOCTYPE html>
<html lang="en">
    <head>
        <meta charset="UTF-8" />
        <meta http-equiv="X-UA-Compatible" content="IE=edge" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
        <link rel="stylesheet" href="table.css" />
        <script defer type="module" src="./table.js"></script>
    </head>
    <body>
        <style>
            html,
            body {
                width: 100%;
                height: 100%;
                overflow: hidden;
            }
            .content {
                width: 100%;
                height: 500px;
            }
            .box {
                position: absolute;

                left: 20px;
                bottom: 50px;
            }
        </style>
        <div class="content">
            <better-table cols="4" rows="10" lang="heb"></better-table>
        </div>
    </body>
</html>