Search code examples
javascripthtml-tablevisibility

unexpected behaviour when using Javascript to hide elements of an HTML table


I am observing strange behaviour when using Javascript to hide portions of an HTML table, and it varies depending on which browser I use. I have made the HTML accessible on this site

https://auditrecordit.com/SO_demo/MSc.html

and also include it below, but I thought the website was handy for seeing the behaviour for yourself very quickly.

As you can see, the page shows a degree structure. If I select the "Certificate" toggle in Firefox, I get the expected behaviour - Semester Two disappears, and so does the middle module of Semester One (and the choice for the bottom row of Semester One extends).

Again in Firefox, if I then select the "MSc" toggle, I get the expected reverse behaviour - the hidden table elements reappear.

However, if I select the "Diploma" toggle, the text in the unapplicable table elements disappears (good), but their background colour doesn't (bad).

Now here's the super-weird bit - if I then swap browser tabs and back again (or switch apps and back again), the background colour gets fixed i.e. it goes away!

In fact, I don't even need to leave the window. If I open the Web Developer console, I only have take my mouse pointer outside of the display portion of the window (even just to hover over a scroll bar) and the background colour disappears.

So I guess there is some event occuring when I do this which prompts Firefox to have some kind of mini-refresh.

With that in mind, I tried adding

window.self.blur();
window.self.focus();

to the end of my function which does the adjusting, but to no avail.

Even more confusing, the unexpected behaviour I have described above doesn't happen in Chrome. Chrome gets it wrong and keeps it wrong (i.e. background colour persists when it shouldn't). Of course, it's likely me that's getting something wrong, but I don't know what, and Firefox eventually does what I intend, so I'm going to blame Chrome for now.

If you examine the Javascript, you will see that I have tried to get rid of the unwanted background colour by assigning the class 'tdNoBG' to cells whenever I set their visibility to 'collapsed', and I can see via the Inspector that the class has been changed, but the background colour still shows.

Any idea what event fires to make Firefox change its mind, and any suggestions of a fix for Chrome?

P.S. delighted if someone would even just confirm that they observe the behaviour that I have described!

    <!DOCTYPE html><html><head>
        <meta charset='utf-8' />  
        <style type='text/css'>
    body { font-family: Tahoma; }
    label {
        white-space:nowrap;
    }
    
    td { 
        padding: 20px;
    }
    td:nth-child(2), td:nth-child(7){text-align:center;}
    
    table { 
        border-spacing: 2px;
        border-collapse: separate;
      width: 100%;
    }
    
    .clsSemester { font-size: xx-large; background-color: blue; color: white; font-weight: bold;  width: 35%; }
    
    .tdThinDivider { background-color: white; width: 1%; }
    
    .clsVA { vertical-align: top; }
    
    .trBG { background-color: #e6ffff; }
    
    .tdNoBG { background-color: #ffffff; }
    
    .tdModuleTitle { background-color: powderblue; font-weight: bold; }
    
    .tdPathway  { background-color: #fafafa; }
    
    </style>
    
    <script type='text/javascript'>  
           
    function getModuleName(strModuleCode) {
        var arr = new Map([
        ['MPE5003','PE and Sport and the Social Sciences'],
        ['MPE5004','Cognitive Development and Pedagogy Through PE and Sport'],
        ['MPE5005','Psychology: Culture, Coaching and Young People'],
        ['MPE5006','Dissertation: Consultancy-based Project'],
        ['MPE5007','Dissertation: Academic Research Project'],
        ]);
        return arr.get(strModuleCode);
    }
    
    function setPathway() {
        var table = document.getElementById("tblMSc");
        for (var i = 0, row; row = table.rows[i]; i++) {
           for (var j = 0, cell; cell = row.cells[j]; j++) {
             cell.style.height = i == 0 ? '20px' : (i%2 == 0 ? '200px' : '2px');
           }  
        }
        var table = document.getElementById("tblMSc");
        for (var i = 0, row; row = table.rows[i]; i++) {
           row.style.visibility = document.querySelector('input[name="rdoPathway"]:checked').value == 'Certificate' && (i == 4 || i == 5) ? 'collapse' : 'visible' ;
           
           for (var j = 0, cell; cell = row.cells[j]; j++) {
             cell.style.height = i == 0 ? '20px' : (i%2 == 0 ? '200px' : '2px');
             var strPathway = document.querySelector('input[name="rdoPathway"]:checked').value;
             cell.style.visibility = strPathway == 'Certificate' && (j > 3 || i > 1 && j > 2) || strPathway == 'Diploma' && i > 2 && j > 2 ? 'collapse' : 'visible' ;
        //console.log(i + ';' + j + ';' + cell.style.visibility + '#');
        //console.log(i + ';' + j + '#');
           }  
        }
        window.self.blur();
        window.self.focus();
        /*
        for (var i = 0, row; row = table.rows[i]; i++) {
           for (var j = 0, cell; cell = row.cells[j]; j++) {
             cell.style.visibility = 'visible';
           }  
        }
        var strPathway = document.querySelector('input[name="rdoPathway"]:checked').value;
        if (strPathway != 'MSc') {
            var arrCellsToHide = strPathway == 'Diploma' ? ['r3c5','r3c6','r4c5','r4c6'] : ['r3c0','r3c1','r3c2','r3c3','r3c4','r3c5','r3c6','r4c0','r4c1','r4c2','r4c3','r4c4','r4c5','r4c6'];
            var arrayLength = arrCellsToHide.length;
            for (var i = 0; i < arrayLength; i++) {
              document.getElementById(arrCellsToHide[i]).style.visibility = 'collapse';
              document.getElementById(arrCellsToHide[i]).className = document.getElementById(arrCellsToHide[i]).style.visibility == 'collapse' ? 'tdNoBG' : 'tdBG';
            }
        }
        */
    }
    
    </script>
    
     </head>
    
    <body onload="document.getElementById('divSem1Choice').innerHTML = getModuleName(document.querySelector('input[name=&quot;rdoSem1Choice&quot;]:checked').value);
              document.getElementById('divSem2Choice').innerHTML = getModuleName(document.querySelector('input[name=&quot;rdoSem2Choice&quot;]:checked').value);
    setPathway();
             "> 
    
    <table id='tblMSc'>
        <tr id='r0' class="trBG">
            <td id='r0c0' class="tdNoBG">
    
            </td>
            <td id='r0c1' class="clsSemester">
    SEMESTER 1
            </td>
            <td id='r0c2' class="tdThinDivider">
    
            </td>
            <td id='r0c3' class="tdPathway clsVA" >
    
    <label><input type="radio" name="rdoPathway" value="MSc" class="bcsradio" checked = "checked" onclick="setPathway();">MSc<label><br/>
    <label><input type="radio" name="rdoPathway" value="Diploma" class="bcsradio" onclick="setPathway();">Diploma<label><br/>
    <label><input type="radio" name="rdoPathway" value="Certificate" class="bcsradio" onclick="setPathway();">Certificate<label>
    
            </td>
            <td id='r0c4' class="tdThinDivider">
    
            </td>
            <td id='r0c5' class="tdNoBG">
    
            </td>
            <td id='r0c6' class="clsSemester">
    SEMESTER 2
            </td>
        </tr>
    
        <tr id='r1'  class="trBG">
            <td id='r1c0' class="tdNoBG">
    
            </td>
            <td id='r1c1' class="tdBG">
    
    
            </td>
            <td id='r1c2' class="tdThinDivider">
    
            </td>
            <td id='r1c3' class="tdNoBG">
    
            </td>
            <td id='r1c4' class="tdThinDivider">
    
            </td>
            <td id='r1c5' class="tdNoBG">
    
            </td>
            <td id='r1c6' class="tdBG">
    
            </td>
        </tr>
    
        <tr id='r2'  class="trBG">
            <td id='r2c0' class="tdBG">
    MPE5001
            </td>
            <td id='r2c1'  class="tdModuleTitle">
    Leadership and Professional Development<br/>in PE and Sport working with and for Young People
            </td>
            <td id='r2c2' class="tdThinDivider">
    
            </td>
            <td id='r2c3' class="tdNoBG">
    
            </td>
            <td  id='r2c4' class="tdThinDivider">
    
            </td>
            <td  id='r2c5' id='tdMPE5002' class="tdBG">
    MPE5002
            </td>
            <td  id='r2c6' class="tdModuleTitle">
    Research Methods in PE and Sport<br/>working with and for Young People
            </td>
        </tr>
    
        <tr id='r3'  class="trBG">
            <td id='r3c0' class="tdBG">
    
            </td>
            <td id='r3c1' class="tdBG">
    
            </td>
            <td id='r3c2' class="tdThinDivider">
    
            </td>
            <td id='r3c3' class="tdNoBG">
    
            </td>
            <td id='r3c4' class="tdThinDivider">
    
            </td>
            <td id='r3c5' class="tdBG">
    
            </td>
            <td id='r3c6' class="tdBG">
    
            </td>
        </tr>
    
        <tr id='r4'  class="trBG">
            <td id='r4c0' class="tdBG">
    MPE5004
            </td>
            <td id='r4c1' class="tdModuleTitle">
    Cognitive Development and Pedagogy Through PE and Sport
            </td>
            <td id='r4c2' class="tdThinDivider">
    
            </td>
            <td id='r4c3' class="tdNoBG">
    
            </td>
            <td id='r4c4' class="tdThinDivider">
    
            </td>
            <td id='r4c5' class="tdBG" rowspan="3">
    <label><input type="radio" name="rdoSem2Choice" value="MPE5006" class="bcsradio" checked = "checked" onclick=" document.getElementById('divSem2Choice').innerHTML = getModuleName(this.value);">MPE5006<label><br/><br/>or<br/><br/>
    <label><input type="radio" name="rdoSem2Choice" value="MPE5007" class="bcsradio" onclick=" document.getElementById('divSem2Choice').innerHTML = getModuleName(this.value);">MPE5007<label>
            </td>
            <td id='r4c6' class="tdModuleTitle" rowspan="3">
    <div id="divSem2Choice"></div>
            </td>
        </tr>
    
        <tr id='r5' class="trBG">
            <td id='r5c0' class="tdBG">
    
            </td>
            <td id='r5c1' class="tdBG">
    
            </td>
            <td id='r5c2' class="tdThinDivider">
    
            </td>
            <td id='r5c3' class="tdNoBG">
    
            </td>
        </tr>
    
        <tr id='r6'  class="trBG">
            <td id='r6c0' class="tdBG">
    <label><input type="radio" name="rdoSem1Choice" value="MPE5003" class="bcsradio" checked = "checked" onclick=" document.getElementById('divSem1Choice').innerHTML = getModuleName(this.value);">MPE5003<label><br/><br/>or<br/><br/>
    <label><input type="radio" name="rdoSem1Choice" value="MPE5005" class="bcsradio" onclick=" document.getElementById('divSem1Choice').innerHTML = getModuleName(this.value);">MPE5005<label>
    <span id='spnCertChoice'>
    <br/><br/>or<br/><br/>
    <label><input type="radio" name="rdoSem1Choice" value="MPE5004" class="bcsradio" onclick=" document.getElementById('divSem1Choice').innerHTML = getModuleName(this.value);">MPE5004<label>
    </span>
            </td>
            <td id='r6c1'  class="tdModuleTitle">
    <div id="divSem1Choice"></div>
            </td>
            <td id='r6c2'  class="tdThinDivider">
    
            </td>
            <td id='r6c3'  class="tdNoBG">
    
            </td>
        </tr>
    </table>

Solution

  • This is really funky behavior.

    I believe this is related to the fact, that in the javascript you are setting table row <tr> visibility and table cell <td> visibility so that there are mismatches. The previous state of the row and cell seem to affect how css classes are rendered.

    I created a simple example below, where this is demonstrated. The randomize button will set row and cell visibilities to visible or collapse 50% of the time. At least with Chrome (99.0.4844.74) after few clicks you get cells that have no content but background color visible and also some cells with content visible but no background color.

    enter image description here

    I could not find any solid logic what is determining the cell's behavior.

    document.querySelector("#randomize-btn").addEventListener("click", function() {
            randomizeVisibilities();
          });
    
          function randomizeVisibilities() {
            let table = document.querySelector("#t");
            for (let row of table.rows) {
              row.style.visibility = Math.random() > 0.5 ? "visible" : "collapse";
              for (let cell of row.cells) {
                cell.style.visibility = Math.random() > 0.5 ? "visible" : "collapse";
              }
            }
          }
          td {
            width: 50px;
            height: 50px;
          }
    
          tr {
            background: blue;
          }
    <!DOCTYPE html>
    <html lang="en">
      <head>
        <meta charset="UTF-8" />
        <meta name="viewport" content="width=device-width, initial-scale=1.0" />
        <title>Document</title>
      </head>
      <body>
        <button id="randomize-btn">Randomize</button>
        <table id="t">
          <tr>
            <td>HELLO</td>
            <td>HELLO</td>
            <td>HELLO</td>
            <td>HELLO</td>
            <td>HELLO</td>
            <td>HELLO</td>
          </tr>
          <tr>
            <td>HELLO</td>
            <td>HELLO</td>
            <td>HELLO</td>
            <td>HELLO</td>
            <td>HELLO</td>
            <td>HELLO</td>
          </tr>
          <tr>
            <td>HELLO</td>
            <td>HELLO</td>
            <td>HELLO</td>
            <td>HELLO</td>
            <td>HELLO</td>
            <td>HELLO</td>
          </tr>
          <tr>
            <td>HELLO</td>
            <td>HELLO</td>
            <td>HELLO</td>
            <td>HELLO</td>
            <td>HELLO</td>
            <td>HELLO</td>
          </tr>
        </table>
      </body>
    </html>

    As a solution I would suggest that you do not use table-element for this kind of layout. One possibility would be to use either flexbox or grid layout to implement the layout. Then you could toggle the visibility of individual div-elements inside this layout.

    There are really good guides for these layouts here:

    https://css-tricks.com/snippets/css/a-guide-to-flexbox/

    https://css-tricks.com/snippets/css/complete-guide-grid/