2

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.

Ashley Mills
  • 41,127
  • 14
  • 115
  • 144
Jonathan
  • 166
  • 6
  • Can you create a JS Fiddle, it will make it much easier for us to see your code in action and identify the issue, posting large blocks of code into a question makes it very hard to read. I would also recommend that when you ask a question on Stack Overflow, that you start by posting what the issue is your are facing, it then make everything else you are posting easier to understand as it has context. – Oli Folkerd Oct 18 '20 at 15:30
  • I am not able to access JSFiddle sorry - it is blocked by our organisation, as is Codepen. I have edited the original question to draw attention to my main issue - sorry it was unclear. – Jonathan Oct 19 '20 at 22:07

1 Answers1

0

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
  • Thanks for the reply. I did indeed try that - I put it in the setTab function which seemed the logical place to me at least - but I got **ReferenceError: table is not defined.**. If you could offer some hints as to where in my code I should do that I would greatly appreciate it. I will continue trial and error in the meantime though. Thanks again. – Jonathan Oct 07 '20 at 12:13
  • Maybe you should check here - https://stackoverflow.com/questions/3495679/passing-parameters-in-javascript-onclick-event – Mirza Prangon Oct 08 '20 at 09:53
  • Thanks @mirza. I checked out that link. I significantly altered my code yesterday to ensure that all data is loaded before the buttons and tables are built. I also build each table in it's own variable, but I still get the same issue. On first presentation the table is displayed in the full width and when I click away and back again it renders correctly. I am at a loss. I appreciate your help. – Jonathan Oct 08 '20 at 21:49
  • One other point to note - when the table is fully loaded and displayed, if I click the same tab over and over it will still do the redraw, but it draws it exactly as it is. It is not until I click away and back again that it draws correctly. – Jonathan Oct 08 '20 at 22:24
  • Maybe you need to wait 100 microseconds before calling redraw? Maybe the table redraw is called in the tab animation transition? – Mirza Prangon Oct 09 '20 at 01:07
  • Thank you for your continued interest in my issue @mirza. I altered the onclick function to be async/await now before the table redraw so as to ensure the button animation is done beforehand, rather than coding a hard wait time which could vary. It hasn't fixed the issue sadly, but perhaps my code is getting better little by little anyway! – Jonathan Oct 09 '20 at 01:55