Tabulator rendering issue using responsive layout

2.9k views Asked by At

ISSUE: Everything pretty much works if I stick to layout: "fitColumns" anything else I try seems to result in rendering (if that is the correct term) issues. I am not using a framework (a bridge too far in the timeframe). When the table is displayed after the page is fully loaded, regardless of the layout I choose - it always starts off displaying as it would for "fitColumns". If I set it to "fitDataFill" for example, it loads and displays as for "fitColumns". When I click to another tab and back again it then displays the data as it should for fitDataFill.

REVISED:

Rough order of steps:

  • Load a specific file which contains meta data about the rest of the files to load into tables, the names of those tables, the columns in each table and the text to display as float over help on the column headers
  • Load the rest of the data and build the table config object which contains the meta data
  • Add a table specific div for each table and append to the csv-tab-buttons div
  • Build the table on the newly created div
  • Add the button which will toggle which table gets display via the css active class and the setTab function

The setTab function includes two redraws (to be sure, to be sure).

If you see code that resembles something you wrote here then thanks to you, much of what I wrote I gleaned from others.

Tabulator version is 4.8 Code is created using Visual Studio Code using the Live Server extension to reload the page after every save Browser is Chrome Version 85.0.4183.121 (Official Build) (64-bit)

  • I rewrote based on the suggestions of @mirza to ensure the data is read in entirely before the tables are built and each table is stored independently of the others so I don't pass the same table to setTab every time
  • fitColumns works but does not allow me to resize columns individually (which may be by design)
  • fitData seems to work ok
  • fitDataFill and fitDataStretch do not work on the first render, or subsequent renders of the same table until I click away and back again
  • I have attempted to follow the logic in the tabulator.js via the debugger and although I can see what is happening, there is way too much going on in there for me to grasp where the issue might be

HTML:

    <!DOCTYPE html>
    <html lang="en">
    
        <head>
            <meta charset="UTF-8" />
            <meta name="viewport" content="width=device-width, initial-scale=1.0" />
            <meta http-equiv="X-UA-Compatible" content="ie=edge" />
            <!-- CSS -->
            <link rel="stylesheet" href="./scripts/dist/css/tabulator.min.css">
            <link rel="stylesheet" href="./styles/style.css">
            <!-- Scripts -->
            <script type="text/javascript" src="./scripts/dist/js/tabulator.min.js"></script>
            <script type="text/javascript" src="./scripts/dist/js/papaparse.min.js"></script>
            <title>My Team</title>
        </head>
    
        <body>
            <!-- Wrapper -->
            <div class="wrapper">
                <section class="container">
                    <!-- Header -->
                    <header id="header" class="header">
                        <h1>Reporting</h1>
                    </header>
    
                    <!-- Tabs -->
                    <div id="csv-tab-buttons" class="tab">
                    </div>
    
                    <!-- Tables on each tab -->
                    <div id="csv-tabs">
                    </div>
    
                    <!-- Footer -->
                    <footer class="footer">
                        <p>My Team &copy; 2020</p>
                    </footer>
    
                </section>
            </div><!-- Wrapper Ends-->
            <script src="./scripts/dist/js/miscsv.min.js"></script>
        </body>
    </html>

REVISED Javascript:

 //*******************************************************************************************************
// Global variables
//*******************************************************************************************************

var file = 'DS.PPTE.DB2.VIARACF.VARLEGND.CSV'
var tables = []
var tableDivs = []
var tabConfig = {}

//*******************************************************************************************************
// Global functions
//*******************************************************************************************************

let onlyUnique = (value, index, self) => {
    return self.indexOf(value) === index
}

//*******************************************************************************************************
// Async functions
//*******************************************************************************************************

// Set the tab to whichever button was clicked
async function activateTab(target) {
    // hides all tabs
    document.querySelectorAll(".tabcontent").forEach(tabContent => tabContent.style.display = "none");
    // Remove the active class from all tab links
    document.querySelectorAll('.tablinks').forEach(tabLink => tabLink.className.replace("active", ""));
    // Remove the active class from the active tab
    document.querySelectorAll(".active").forEach(activeTab => activeTab.classList.remove("active"))
    // Activate the selected tab
    document.querySelector(`#${target.textContent}`).style.display = "block"
    target.classList.add("active");
}

async function setTab(target) {
    console.log("Activate the tab")
    await activateTab(target)
    console.log("Redraw the table")
    // Redraw the table
    tableDivs[`${target.textContent}`].redraw(true);
}

// Read a CSV file
const readCSV = async (file) => {
    return new Promise(resolve => {
        Papa.parse(`data/${file}`, {
            header: true,
            download: true,
            skipEmptyLines: true,
            complete: results => {
                console.log(`${file} loaded - ${results.data.length} records.`)
                resolve(results)
            }
        })
    })
}

// Get all the data first
async function getData() {
    // Read the meta data file with the data and table config
    let parseMeta = await readCSV(file)
    tabConfig = {
        // Get the names of the tables to present
        tabs: parseMeta.data.map((data) => data['TABLE-NAME']).filter(onlyUnique).sort(), 
        // Find the file name for each table
        files: parseMeta.data.map((data) => `${data['TABLE-NAME']}-${data['CSV-FILE-NAME']}`).filter(onlyUnique)
            .map((entry) => {
                let tmpEntry = entry.split('-')
                return { table: `${tmpEntry[0]}`, file: `${tmpEntry[1]}` }
            }), 
        // Save the float over help for each column by table name
        help: parseMeta.data.map((data) => {
            return { key: `${data['TABLE-NAME']}-${data['VARIABLE']}`, helpText: data['VAR-DESCRIPTION'] != '' ? data['VAR-DESCRIPTION'] : data['VARIABLE'] }
            }), 
        data: tables,
        divs: tableDivs,
    }
    // Read in the files which contain the table data
    for (const tabName of tabConfig.tabs) {
        let file = tabConfig.files.filter(entry => entry.table == tabName)[0].file
        tables[tabName] = await readCSV(file)
        tableDivs[tabName] = `csv-table-${tabName}`
    }
}

// Master function to do everything in the right order
async function doAll() {
    // Get all the data and build the table config
    await getData()

    // Store the buttons and tabs anchor divs
    let buttonsDiv = document.getElementById("csv-tab-buttons")
    let tabsDiv = document.getElementById("csv-tabs")

    // Add the buttons and tables
    for ([idx, tabName] of tabConfig.tabs.entries()) {
        // Add tabs to hold the tables to the page
        const elemTabDiv = document.createElement('div')
        const elemTableDiv = document.createElement('div')
        elemTabDiv.id = tabName
        elemTabDiv.className = "tabcontent"
        elemTableDiv.id = `csv-table-${tabName}`
        elemTableDiv.className = "table"
        elemTabDiv.appendChild(elemTableDiv)
        tabsDiv.appendChild(elemTabDiv)

        // Define header context menu
        let headerMenu = [
            {
                label:"Hide Column",
                action:function(e, column){
                    column.hide()
                },
            },
        ]
        // Create the table
        tableDivs[tabName] = new Tabulator(`#csv-table-${tabName}`, {
            data:tabConfig.data[tabName].data,
            layout:"fitData",
            responsiveLayout:"collapse",
            tooltips:true,
            pagination:"local",
            paginationSize:20,
            resizableColumns:true,
            movableColumns:true,
            resizableRows:true,
            autoColumns: true,
            autoColumnsDefinitions: function(definitions) {
                definitions.forEach((column) => {
                    let helpText = tabConfig.help.find(key => key.key === `${tabName}-${column.field}`).help
                    // Add float over help based on column name
                    column.headerTooltip = helpText
                    column.headerMenu = headerMenu
                    column.headerFilter = true
                    column.headerSort = true
                    column.headerFilterLiveFilter = false
                })
                return definitions
            },
            renderStarted:function(){
                console.log("Render started")
            },
            renderComplete:function(){
                console.log("Render complete")
            },
        })
        // Add tab buttons to page
        const elemTabButton = document.createElement('button')
        elemTabButton.id = `button-${tabName}`
        if ( idx == 0 ) {
            elemTabButton.className = "tablinks active"
        } else {
            elemTabButton.className = "tablinks"
        }
        elemTabButton.onclick = function() { setTab(this) }
        elemTabButton.textContent = tabName
        buttonsDiv.appendChild(elemTabButton)
    }
    document.querySelector(".active").click();
}

doAll()



CSS:

:root {
    --shadow: 0 1px 5px rgba(104, 104, 104, 0.8);
    --raisin: #262730;
    --vermillion: #d33f49;
    --cadet: #576c75;
    --navyboy: #8db2c2;
    --space-cadet: #363457;
    --baby-powder: #f0f4ef;
    --ice: rgb(245, 247, 253);
}

html {
    box-sizing: border-box;
    font-family: Arial, Helvetica, sans-serif;
    color: var(--dark);
}

body {
    background: var(--baby-powder);
    margin: 10px 10px;
    line-height: 1.4;
}

/* .wrapper {
    display: grid;
    grid-gap: 10px;
} */

.container {
    display: grid;
    grid-gap: 10px;
    grid-template-areas: 
        'header'
        'csv-tab-buttons'
        'footer';

    margin: auto;
    width: 98%;
    overflow: auto;
    padding: 1rem 1rem;
}

header {
    background: var(--raisin);
    color: var(--vermillion);
    font-size: 150%;
    line-height: 1;
    padding: 0.1rem;
    text-align: center;
    box-shadow: var(--shadow);
}

.tab {
    background-color: var(--ice);
    box-shadow: var(--shadow);
}

/* Style the buttons that are used to open the tab content */
.tab button {
    background-color: inherit;
    /* float: left; */
    border: none;
    outline: none;
    cursor: pointer;
    /* padding: 14px 16px; */
    padding: 1rem 1.1rem;
    transition: 0.3s;
}

/* Change background color of buttons on hover */
.tab button:hover {
    background-color: var(--cadet);
    color: var(--ice);
}

/* Create an active/current tablink class */
.tab button.active {
    background-color: var(--vermillion);
    color: var(--ice);
}

/* Style the tab content */
.tabcontent {
    /* overflow: hidden; */
    display: none;
    /* padding: 6px 12px; */
    /* border-top: none; */
}

/* Override Tabulator header background */
.tabulator-col-content {
    background-color: var(--ice);
}

.table {
    overflow: hidden;
    box-shadow: var(--shadow);
}

.footer {
    background: var(--cadet);
    color: white;
    padding: 0rem 2rem;
    line-height: 1;
    box-shadow: var(--shadow);
}

EDIT: I made some changes to the order of everything so I could keep the table object in scope when the setTab function is called and I could issue the redraw when the tab button is clicked. The first time I click through every tab it appears to be filling to the width of the data. When I click through each tab again it is properly wrapping columns that would be off screen to the next line. I put multiple table redraws in the setTab routine and it makes no difference to the rendering of the table. It does however change the attributes of the table in some way. I observed in the debugger that tableWidth changed from 0 prior to the first redraw to 2734, the to 2786 after the second redraw and it stayed at the value. If I click away and back again it wraps as expected.

1

There are 1 answers

6
Mirza Prangon On

Tabulator table does not properly render if the element is not visible when creating the table.

To properly render the table, you have to redraw the table when the element is visible.

See Here

From the website:

If the size of the element containing the Tabulator changes (and you are not able to use the in built auto-resize functionality) or you create a table before its containing element is visible, it will necessary to redraw the table to make sure the rows and columns render correctly.

You can redraw the table by

table.redraw();

Or

table.redraw(true); //trigger full rerender including all data and rows