question-mark
Stuck on an issue?

Lightrun Answers was designed to reduce the constant googling that comes with debugging 3rd party libraries. It collects links to all the places you might be looking at while hunting down a tough bug.

And, if you’re still stuck at the end, we’re happy to hop on a call to see how we can help out.

I added the ability for 1- moving the table, 2- styling the table.

See original GitHub issue

I added the ability for:

  • moving the table.
  • styling the table.

Note: I tried to follow your way of making a tool as much as possible.

First: moving the table tool class:

            class table_movingTool {
                isDragging = false;
                parent = null;
                currentTarget = null;
                // google: remove event listener from bind, to get why use these variables.
                boundDrag = this.dragMoveToolHandler.bind(this);
                boundMoving = this.movingTableHandler.bind(this);
                boundDrop = this.dropMoveToolHandler.bind(this);

                constructor(table, quill, options) {
                    if (!table) return null;
                    this.table = table;
                    this.quill = quill;
                    this.options = options;
                    this.domNode = null;
                    this.parent = this.quill.root.parentNode;
                    this.initMovingTool();

                }

                initMovingTool() {
                    this.domNode = document.createElement('div');
                    this.domNode.classList.add('qlbt-move-tool');
                    this.domNode.innerHTML =
                        `<svg style="width:22px;height:22px"  viewBox="0 0 24 24"><path class="ql-custom-stroke-2" d="M20,2H4C2.89,2 2,2.89 2,4V20C2,21.11 2.89,22 4,22H20C21.11,22 22,21.11 22,20V4C22,2.89 21.11,2 20,2M20,20H4V4H20M13,8V10H11V8H9L12,5L15,8M16,15V13H14V11H16V9L19,12M10,13H8V15L5,12L8,9V11H10M15,16L12,19L9,16H11V14H13V16" /></svg>`;
                    this.domNode.addEventListener('mousedown', this.boundDrag);
                    this.parent.appendChild(this.domNode);
                    const tableRect = this.table.getBoundingClientRect();
                    const containerRect = this.quill.root.parentNode.getBoundingClientRect();
                    const tableViewRect = this.table.parentNode.getBoundingClientRect();
                    // css(this.domNode, {
                    //     width: '22px',
                    //     height: '22px',
                    //     left: ''.concat(tableRect.x - 25, 'px'),
                    //     top: ''.concat(tableViewRect.top - containerRect.top, 'px')
                    // });
                    new popper(this.table, this.domNode, {placement: 'bottom-start'}
                    );
                }

                locateMovingTool() {

                    this.quill.getModule('better-table').hideTableTools();
                }

                dragMoveToolHandler(e) {
                    this.isDragging = true;
                    this.table.style.opacity = '0.3';
                    document.addEventListener('mousemove', this.boundMoving);
                    document.addEventListener('mouseup', this.boundDrop);

                }

                dropMoveToolHandler(e) {
                    if (this.isDragging && e.target.nodeName === 'P') {
                        this.table.remove();
                        this.quill.root.insertBefore(this.table, e.target.nextSibling);
                        this.locateMovingTool();

                    }

                    this.isDragging = false;
                    this.table.style.opacity = '1';
                    if (this.currentTarget) this.currentTarget.classList.remove('current-position-table');
                    document.removeEventListener('mousemove', this.boundMoving);
                    document.removeEventListener('mouseup', this.boundDrop);

                }

                movingTableHandler(e) {
                    if (this.currentTarget) this.currentTarget.classList.remove('current-position-table');

                    e.preventDefault();
                    if (e.target.nodeName === 'P') {
                        this.currentTarget = e.target;
                        this.currentTarget.classList.add('current-position-table');
                    }
                }

                destroy() {
                    this.domNode.remove();
                    return null;
                }
            }

Second: stying the table tool class:

            class table_styleTool {
                backgroundColorBound = this.backgroundColorHandler.bind(this);
                borderColorBound = this.borderColorHandler.bind(this);
                borderWidthBound = this.borderWidthHandler.bind(this);
                BorderStyleBound = this.borderStyleHandler.bind(this);
                backgroundColorBtn;
                borderColorBtn;
                borderWidthBtn;
                borderStyleBtn;

                constructor(table, quill, options) {
                    if (!table) return null;
                    this.table = table;
                    this.quill = quill;
                    this.options = options;
                    this.domNode = null;
                    this.parent = this.quill.root.parentNode;
                    this.initStylingTool();
                    this.createStyingItems();

                }

                initStylingTool() {
                    this.domNode = document.createElement('div');
                    this.domNode.classList.add('qlbt-style-tool');
                    this.domNode.addEventListener('click', this.boundClick);
                    this.parent.appendChild(this.domNode);
                    const tableRect = this.table.getBoundingClientRect();
                    const containerRect = this.quill.root.parentNode.getBoundingClientRect();
                    const tableViewRect = this.table.parentNode.getBoundingClientRect();
                    // css(this.domNode, {
                    //     width: '22px',
                    //     height: '22px',
                    //     left: ''.concat(tableRect.x - 25, 'px'),
                    //     top: ''.concat(tableViewRect.top - containerRect.top + 22, 'px')
                    // });
                    new popper(this.table, this.domNode, {
                            placement: 'left-start',
                            modifiers: {
                                offset: {
                                    enabled: true,
                                },
                                preventOverflow: {
                                    enabled: true,
                                    escapeWithReference: true,
                                }
                            }
                        }
                    );
                }

                createStyingItems() {
                    this.backgroundColorBtn = document.createElement('div');
                    this.backgroundColorBtn.id = 'backgroundColor';
                    this.backgroundColorBtn.innerHTML = `<svg style="width:22px;height:22px" viewBox="0 0 24 24">
    <path class="ql-custom-stroke-2" d="M19,11.5C19,11.5 17,13.67 17,15A2,2 0 0,0 19,17A2,2 0 0,0 21,15C21,13.67 19,11.5 19,11.5M5.21,10L10,5.21L14.79,10M16.56,8.94L7.62,0L6.21,1.41L8.59,3.79L3.44,8.94C2.85,9.5 2.85,10.47 3.44,11.06L8.94,16.56C9.23,16.85 9.62,17 10,17C10.38,17 10.77,16.85 11.06,16.56L16.56,11.06C17.15,10.47 17.15,9.5 16.56,8.94Z" />
</svg>`;

                    this.borderColorBtn = document.createElement('div');
                    this.borderColorBtn.id = 'borderColorBtn';
                    this.borderColorBtn.innerHTML = `<svg style="width:22px;height:22px" viewBox="0 0 24 24">
    <path class="ql-custom-stroke-2" d="M20.71,4.04C21.1,3.65 21.1,3 20.71,2.63L18.37,0.29C18,-0.1 17.35,-0.1 16.96,0.29L15,2.25L18.75,6M17.75,7L14,3.25L4,13.25V17H7.75L17.75,7Z" />
</svg>`;

                    this.borderWidthBtn = document.createElement('div');
                    this.borderWidthBtn.id = 'borderWidthBtn';
                    this.borderWidthBtn.innerHTML = `<svg style="width:22px;height:22px" viewBox="0 0 24 24">
    <path class="ql-custom-stroke-2" d="M3,17H21V15H3V17M3,20H21V19H3V20M3,13H21V10H3V13M3,4V8H21V4H3Z" />
</svg>`;

                    this.borderStyleBtn = document.createElement('div');
                    this.borderStyleBtn.id = 'BorderStyleBtn';
                    this.borderStyleBtn.innerHTML = `<svg style="width:22px;height:22px" viewBox="0 0 24 24">
    <path class="ql-custom-stroke-2" d="M3,16H8V14H3V16M9.5,16H14.5V14H9.5V16M16,16H21V14H16V16M3,20H5V18H3V20M7,20H9V18H7V20M11,20H13V18H11V20M15,20H17V18H15V20M19,20H21V18H19V20M3,12H11V10H3V12M13,12H21V10H13V12M3,4V8H21V4H3Z" />
</svg>`;


                    this.backgroundColorBtn.addEventListener('click', this.backgroundColorBound);
                    this.borderColorBtn.addEventListener('click', this.borderColorBound);
                    this.borderWidthBtn.addEventListener('click', this.borderWidthBound);
                    this.borderStyleBtn.addEventListener('click', this.BorderStyleBound);

                    this.domNode.appendChild(this.backgroundColorBtn);
                    this.domNode.appendChild(this.borderColorBtn);
                    this.domNode.appendChild(this.borderWidthBtn);
                    this.domNode.appendChild(this.borderStyleBtn);

                }

                backgroundColorHandler(e) {

                    const clickedElement = e.target;
                    if (clickedElement === this.backgroundColorBtn) {
                        this.destroyCurrentPicker();
                        this.createColorPicker(this.backgroundColorBtn);
                    } else if (clickedElement.className === 'qlbt-style-colors-item') {
                        const selectedCells = this.quill.getModule('better-table').getSelectedCells();
                        if (!selectedCells) return;

                        selectedCells.forEach(cell => cell.domNode.style.backgroundColor =
                            clickedElement.dataset.value);

                    }
                }

                borderColorHandler(e) {
                    const clickedElement = e.target;
                    if (clickedElement === this.borderColorBtn) {
                        this.destroyCurrentPicker();
                        this.createColorPicker(this.borderColorBtn);
                    } else if (clickedElement.className === 'qlbt-style-colors-item') {
                        const selectedCells = this.quill.getModule('better-table').getSelectedCells();
                        if (!selectedCells) return;

                        selectedCells.forEach(cell => cell.domNode.style.borderColor =
                            clickedElement.dataset.value);
                    }
                }

                borderWidthHandler(e) {
                    const clickedElement = e.target;
                    if (clickedElement === this.borderWidthBtn) {
                        this.destroyCurrentPicker();
                        this.createPicker(this.borderWidthBtn, [0, 1, 1.5, 1.75, 2.25, 3, 4.5,
                            6]);
                    } else if (clickedElement.className === 'qlbt-style-item') {
                        const selectedCells = this.quill.getModule('better-table').getSelectedCells();
                        if (!selectedCells) return;

                        selectedCells.forEach(cell => cell.domNode.style.borderWidth =
                            `${clickedElement.dataset.value}px`);
                    }
                }

                borderStyleHandler(e) {
                    const clickedElement = e.target;
                    if (clickedElement === this.borderStyleBtn) {
                        this.destroyCurrentPicker();
                        this.createPicker(this.borderStyleBtn, ['solid', 'dotted',
                            'dashed']);
                    } else if (clickedElement.className === 'qlbt-style-item') {
                        const selectedCells = this.quill.getModule('better-table').getSelectedCells();
                        if (!selectedCells) return;

                        selectedCells.forEach(cell => cell.domNode.style.borderStyle =
                            clickedElement.dataset.value);
                    }
                }

                destroy() {
                    this.domNode.remove();
                    return null;
                }

                createPicker(pickerParent, items) {
                    let pickerContainer = document.createElement('span');
                    pickerContainer.classList.add('qlbt-style-picker-options');
                    pickerParent.appendChild(pickerContainer);
                    items.forEach(item => {
                        let itemSpan = document.createElement('span');
                        itemSpan.className = 'qlbt-style-item';
                        itemSpan.dataset.value = item;
                        if (pickerParent === this.borderWidthBtn) itemSpan.innerText = item;
                        else {
                            const line = document.createElement('div');
                            line.id = item;
                            itemSpan.appendChild(line);

                        }
                        pickerContainer.appendChild(itemSpan);
                    });
                }

                createColorPicker(colorPickerParent) {
                    let colors = ['#000000', '#e60000', '#ff9900',
                        '#ffff00',
                        '#008a00', '#0066cc', '#9933ff', '#ffffff',
                        '#facccc', '#ffebcc', '#ffffcc', '#cce8cc',
                        '#cce0f5', '#ebd6ff', '#bbbbbb', '#f06666',
                        '#ffc266', '#ffff66', '#66b966', '#66a3e0',
                        '#c285ff', '#888888', '#a10000', '#b26b00',
                        '#b2b200', '#006100', '#0047b2', '#6b24b2',
                        '#444444', '#5c0000', '#663d00', '#666600',
                        '#003700', '#002966', '#3d1466'];

                    let colorPickerContainer = document.createElement('span');
                    colorPickerContainer.classList.add('qlbt-style-colors-picker-options');
                    colorPickerParent.appendChild(colorPickerContainer);
                    colors.forEach(color => {
                        let colorSpan = document.createElement('span');
                        colorSpan.className = 'qlbt-style-colors-item';
                        colorSpan.dataset.value = color;
                        colorSpan.style.backgroundColor = color;
                        colorPickerContainer.appendChild(colorSpan);
                    });
                }

                destroyCurrentPicker() {
                    if (this.backgroundColorBtn.querySelector('span'))
                       this.backgroundColorBtn.removeChild(this.backgroundColorBtn.querySelector('span'));
                    else if (this.borderColorBtn.querySelector('span'))
                        this.borderColorBtn.removeChild(this.borderColorBtn.querySelector('span'));
                    else if (this.borderWidthBtn.querySelector('span'))
                        this.borderWidthBtn.removeChild(this.borderWidthBtn.querySelector('span'));
                    else if (this.borderStyleBtn.querySelector('span'))
                        this.borderStyleBtn.removeChild(this.borderStyleBtn.querySelector('span'));
                }
            }

Also, I added new function the module which is getSelectedCells();

                getSelectedCells() {
                    return this.tableSelection.selectedTds;
                }

I also, added style for these tool, and modify the style for existed tools:

  • for TableColumnTool, since changing the length of a column for upper tool is not convenient, I change it’s style so changing length of a column is like the way Google Docs does it which is above the columns directly, so, take it or leave it.

.quill-better-table-wrapper {
    overflow-x: auto;
}

table {
    table-layout: fixed;
    border-collapse: collapse;
    text-align: right;
}

table td {
    border: 1px solid #000;
    padding: 2px 5px;
}

.qlbt-operation-menu {
    background-color: #fff;
    font-size: 14px;
    z-index: 100;
    overflow: hidden;
    display: grid;
    grid-template-columns: repeat(12, 30px);
    grid-template-rows: 35px;
    align-items: center;
    justify-items: center;
    transition: opacity 0.5s;
    box-shadow: 0 0 20px 4px rgba(154, 161, 177, .15), 0 4px 80px -8px rgba(36, 40, 47, .25), 0 4px 4px -2px rgba(91, 94, 105, .15);
    border-radius: 0.4em;
}

.qlbt-operation-menu .qlbt-operation-menu-item {
    background-color: #fff;
    cursor: pointer;
    color: #595959;
    overflow: hidden;
    text-overflow: ellipsis;
    transition: .3s;
    border: none;
}


.qlbt-col-tool, .qlbt-move-tool, .qlbt-style-tool {
    position: absolute;
    display: flex;
    z-index: 99;
    height: 16px;
}

.qlbt-col-tool {
    margin-top: -15px;
    margin-left: -3px;
    opacity: 0;
}

.qlbt-move-tool, .qlbt-style-tool {

    opacity: .35;
    transition: .2s;
    cursor: pointer;
}

.qlbt-style-tool .icon {
    position: relative;
}

.qlbt-move-tool:hover,
.qlbt-move-tool:focus-within,
.qlbt-style-tool:focus-within,
.qlbt-style-tool:hover {
    opacity: .8;
    transition: .2s;
}

.qlbt-style-tool {
    display: grid;

}

.qlbt-col-tool:hover .qlbt-col-tool-cell {
    /*background-color: var(--light-primary);*/
    transition: .2s;

}

.qlbt-col-tool .qlbt-col-tool-cell {
    position: relative;
    background-color: #fff;
    /*border-top: 1px solid #000;*/
    /*border-right: 2px solid #000;*/
    /*border-bottom: 1px solid #000;*/
}

.qlbt-col-tool .qlbt-col-tool-cell:first-child {
    /*border-left: 2px solid #000;*/
}

.qlbt-col-tool .qlbt-col-tool-cell-holder {
    position: absolute;
    right: 0;
    top: 0;
    bottom: 0;
    z-index: 3;
    width: 2px;
    background-color: var(--primary);
    cursor: ew-resize;
}

.qlbt-col-tool .qlbt-col-tool-cell-holder:hover {
    background-color: #35a7ed;
}

.qlbt-col-tool .qlbt-col-tool-cell-holder::before {
    content: "";
    position: absolute;
    top: 0;
    left: -6px;
    display: block;
    width: 8px;
    height: 100%;
}

.qlbt-col-tool .qlbt-col-tool-cell-holder::after {
    content: "";
    position: absolute;
    top: 0;
    right: -6px;
    display: block;
    width: 8px;
    height: 100%;
}


button.ql-copy-format.ql-active {
    cursor: url('data:image/svg+xml;utf8,<svg width="18"height="18" viewBox="0 0 22 22"><path class="ql-fill"d="M18,4V3A1,1 0 0,0 17,2H5A1,1 0 0,0 4,3V7A1,1 0 0,0 5,7H17A1,1 0 0,0 11,7V6H19V10H9V21A1,1 0 0,0 10,22H12A1,1 0 0,0 11,21V12H21V4H18Z"/></svg>'), auto;
}

#insertColumnLeft:hover path, #insertColumnRight:hover path, #insertRowUp:hover path,
#insertRowDown:hover {
    fill: var(--success);
    transition: .3s;

}

#deleteColumn:hover path, #deleteRow:hover path, #deleteTable:hover path {
    fill: var(--danger);
    transition: .3s;
}

#insertColumnLeft path, #insertColumnRight path, #insertRowUp path,
#insertRowDown, #deleteColumn path, #deleteRow path, #deleteTable path {
    transition: .3s;
}

.qlbt-operation-menu *:not(hr):hover {
    background-color: var(--light-primary);
}

.current-position-table {
    border-radius: .3em;
    border: 1px var(--dark-gray) dotted;
    background-color: var(--white);
    padding: 30px;
    transition: .3s;
}

.qlbt-style-colors-picker-options {
    background-color: #fff;
    position: absolute;
    top: 1px;
    right: 40px;
    padding: 3px 5px;
    width: 152px;
}

.qlbt-style-picker-options {
    background-color: #fff;
    display: grid;
    grid-template-rows: repeat(auto-fit, 1fr);
    grid-gap: 5px;
    position: absolute;
    top: 1px;
    right: 40px;
    font-size: 16px;
    font-weight: bold;
    text-align: center;
    color: var(--black);
    box-shadow: 0 0 6px #00000035;
    border-radius: .3em;
}

.qlbt-style-colors-item {
    border: 1px solid transparent;
    float: left;
    height: 16px;
    margin: 2px;
    width: 16px;
    z-index: 50;
    transition: .2s;
}

.qlbt-style-item {
    z-index: 50;
    padding: 5px;
    transition: .2s;

}

.qlbt-style-item:hover, .qlbt-style-colors-item:hover, .selected-item {
    opacity: 1;
    transition: .2s;
}

.qlbt-style-colors-item:hover, .selected-item {
    border-color: var(--dark-gray);
    transition: .2s;
}

.qlbt-style-item:hover, .selected-item {
    background-color: var(--light-primary);
    transition: .2s;

}

#solid {
    width: 50px;
    margin: 4px;
    display: inline-block;
    border-top: 2px solid rgb(0, 0, 0);
}

#dotted {
    width: 50px;
    margin-bottom: 4px;
    display: inline-block;
    border-top: 2px dotted rgb(0, 0, 0);
}

#dashed {
    width: 50px;
    margin-bottom: 4px;
    display: inline-block;
    border-top: 2px dashed rgb(0, 0, 0);
}

Note: I use popper.js for positioning the tools, you can remove it or consider using it.

feel free to take and modfiy my code as needed.

I’m sorry if issues page is not the place for these kind of posts.

Issue Analytics

  • State:open
  • Created 4 years ago
  • Comments:6 (2 by maintainers)

github_iconTop GitHub Comments

2reactions
MuhammedAlkhudirycommented, Sep 23, 2019

Thanks for your awesome work~ I think moving the table is useful for most users. I am glad to add this feature in quill-better-table, can you provide me a sandbox or a demo in codepen? Or PR welcome. About styling the table, I can not imagine the final effect. A demo will help me have a clearer understanding. About popper.js, I think keep less dependences is also important. Keep in touch~ PR is a better place for these.

Thanks for your awesome module!

I’m sorry, but I was working in production file which is this file https://github.com/soccerloway/quill-better-table/blob/master/dist/quill-better-table.js, since I did not expect to change alot, so showing it in CodePen is a pain. However, I’m planning to use the source file for the future (I will work for row-height changing tool, and an option to equalize the size of each cell, and others, don’t wait for me if you want to implement those 😃 )

About styling the table, these images my help:

For background color 123

  • Note: the color-picker is nearly identical to the native one in the editor

For border style 345

No image needed for the rest

Since you are here, please try to solve these two problems in column-width changing tool:

  1. Changing a column width in should not change the width of the table (try changing a column width in quill then in Google Docs to understand)
  2. The first column width in the left can’t be change.
1reaction
delewis13commented, Jun 23, 2020

@KMLX28 could you please give further info on how to integrate your above changes with current quill-better-table master branch? Am interested in trying, but not sure where to include your changes

Read more comments on GitHub >

github_iconTop Results From Across the Web

Styling tables - Learn web development | MDN
This article provides a guide to making HTML tables look good, with some specific table styling techniques highlighted.
Read more >
Format a table - Microsoft Support
Use Table Styles to format an entire table · Add or remove borders · Display or hide gridlines · Add a cell, row,...
Read more >
Word 2010: Working with Tables - GCFGlobal
In this lesson, you will learn how to convert text to a table, apply table styles, format tables, and create blank tables.
Read more >
Tables in HTML documents
<TABLE border="1" summary="This table gives some statistics about fruit ... of columns in a table), extra row cells are added to the right...
Read more >
Word Table Move Column (2020) - YouTube
Word table, move column. That's what you will learn in this tutorial. I'll show you a trick to easily move the table columns...
Read more >

github_iconTop Related Medium Post

No results found

github_iconTop Related StackOverflow Question

No results found

github_iconTroubleshoot Live Code

Lightrun enables developers to add logs, metrics and snapshots to live code - no restarts or redeploys required.
Start Free

github_iconTop Related Reddit Thread

No results found

github_iconTop Related Hackernoon Post

No results found

github_iconTop Related Tweet

No results found

github_iconTop Related Dev.to Post

No results found

github_iconTop Related Hashnode Post

No results found