Have an idea?

Visit Sawtooth Software Feedback to share your ideas on how we can improve our products.

Freeze Column Headers of a grid

Hello, I've looked everywhere and I've found answers on here that don't actually freeze the column headers of the grid question. I have all of the CSS/Javascript in the footer of the question to make it into a scrollable table, but I'm not sure what to manipulate to make it freeze the column headers, since I don't really know any programming languages. Here is the code that I currently have in the footer: the question name is "CurrentHealth"

<style>
#CurrentHealth_div > .question_body {
    height: 200px;
}
  
#CurrentHealth_div > .question_body > table {
    display: flex;
    flex-flow: column;
    height: 100%;
    width: 100%;
}
  
#CurrentHealth_div > .question_body > table > thead {
    /* head takes the height it requires,
    and it's not scaled when table is resized */
    flex: 0 0 auto;
    width: calc(100% - 0.9em);
}
  
#CurrentHealth_div > .question_body > table > tbody {
    /* body takes all the remaining available space */
    flex: 1 1 auto;
    display: block;
    height:200px;
    overflow-y: auto;
}
  
#CurrentHealth_div > .question_body > table > tbody > tr {
    width: 100%;
}
  
#CurrentHealth_div > .question_body > table > thead,
#CurrentHealth_div > .question_body > table > tbody > tr {
    display: table;
    table-layout: fixed;
}
</style>
<script>
$(document).ready(function(){
    $('#CurrentHealth_div > .question_body > table').prepend('<thead><tr></tr><tr></tr></thead>');
      
    $('#CurrentHealth_div > .question_body > table > tbody > tr:first-child > td').each(function(){
        var tdHtml = $(this).html();
        $('#CurrentHealth_div > .question_body > table > thead > tr:first-child').append('<thead>' + tdHtml + '</th>');
    });
    $('#CurrentHealth_div > .question_body > table > tbody > tr:first-child').remove();
     
    $('#CurrentHealth_div > .question_body > table > tbody > tr:first-child > td').each(function(){
        var tdHtml = $(this).html();
        $('#CurrentHealth_div > .question_body > table > thead > tr:last-child').append('<thead>' + tdHtml + '</thead>');
    });
    $('#CurrentHealth_div > .question_body > table > tbody > tr:first-child').remove();
})
</script>
asked Aug 27, 2020 by Corey
Hi Zachary, it looks like pasting that in the CSS section of the code didn't do much of anything to the column headers lining up properly with the radio buttons.
I placed it immediately before your "</style>" and it worked for me.
Unfortunately still not lining up, is there any way you are open to connecting through a method where I can send a picture of the issue? Or share my screen in real time as another option.
A picture usually is fairly limited as far as debugging CSS.  If you upload your survey somewhere, I could see the misbehaving table in my browser.  Alternatively, if you share your .ssi with support@sawtoothsoftware.com, I can take a look.

Either way, you could remove irrelevant or sensitive information before sharing.
Hi Zachary, I have uploaded the survey with just the grid question at www.c3research.com/surveys/frozengridheadertest

Thanks again for all your help

1 Answer

0 votes
Ah, I suspect you have set the "Width of Labels Column" setting and that is throwing off the widths in the header row.  Try this adjustment to the JavaScript from earlier:

var innerTable = document.querySelector('#[% QuestionName() %]_div .inner_table');
var theadHtml = '<thead><tr>';
var columnLabelTds = document.querySelectorAll('#[% QuestionName() %]_div .inner_table > tbody > tr:first-child > td');
for (var i = 0; i < columnLabelTds.length; i++) {
    var width = columnLabelTds[i].getAttribute('width');
    theadHtml += '<th width="' + width + '">' + columnLabelTds[i].innerHTML + '</th>';
}
theadHtml += '</tr></thead>';
innerTable.innerHTML = theadHtml + innerTable.innerHTML;
document.querySelector('#[% QuestionName() %]_div .inner_table > tbody > tr:first-child').style.display = 'none';
answered Aug 28, 2020 by Zachary Platinum Sawtooth Software, Inc. (205,975 points)
Hi Zachary, that's a wonderful solution and one that scales with the "Width of Labels Column" setting as well as different sized column headers, but they still seem to be justified a bit to the right of their respective columns. I have reuploaded at the same link as before to see
Does appending this line to the end of the JavaScript help?

document.querySelector('#[% QuestionName() %]_div .inner_table > thead').style.width = document.querySelector('#[% QuestionName() %]_div .inner_table > tbody > tr:last-child').offsetWidth + 'px';
Hi Zachary, I originally thought that adding + '" to the end of that script did the trick, but it turns out my tinkering lined up the width, but removed the unscrollable header and added it back to the table. Unfortunately, your code didn't change the width to line up yet.
After much adjusting of the code, here is the CSS and Javascript that you can paste in a grid question footer to have a scrolling grid with a frozen column header -- thank you Zachary.

<style>
#QuestionName_div > .question_body {
    height: 300px;
}
  
#QuestionName_div > .question_body > table {
    display: flex;
    flex-flow: column;
    height: 100%;
    width: 100%;
}
  
#QuestionName_div > .question_body > table > thead {
    /* head takes the height it requires,
    and it's not scaled when table is resized */
    flex: 0 1 auto;
    width: calc(100% - 0.9em);
    text-align: center;
    
}
  
#QuestionName_div > .question_body > table > tbody {
    /* body takes all the remaining available space */
    flex: 1 1 auto;
    display: block;
    height:200px;
    overflow-y: auto;
}
  
#QuestionName_div > .question_body > table > tbody > tr {
    width: 100%;
}
  
#QuestionName_div > .question_body > table > thead,
#QuestionName_div > .question_body > table > tbody > tr {
    display: table;
    table-layout: fixed;
}
thead {
    width: calc(100% - 1.5em) !important;
}
</style>


Change all "#QuestionName" in the code above to the question name in your survey, and paste the Javascript below without changing anything:

<script>
var innerTable = document.querySelector('#[% QuestionName() %]_div .inner_table'); var theadHtml = '<thead><tr>'; var columnLabelTds = document.querySelectorAll('#[% QuestionName() %]_div .inner_table > tbody > tr:first-child > td'); for (var i = 0; i < columnLabelTds.length; i++) {
    var width = columnLabelTds[i].getAttribute('width');
    theadHtml += '<th width="' + width + '">' + columnLabelTds[i].innerHTML + '</th>'; } theadHtml += '</tr></thead>'; innerTable.innerHTML = theadHtml + innerTable.innerHTML; document.querySelector('#[% QuestionName() %]_div .inner_table > tbody > tr:first-child').style.display = 'none';
document.querySelector('#[% QuestionName() %]_div .inner_table > thead').style.width = document.querySelector('#[% QuestionName() %]_div .inner_table > tbody > tr:last-child').offsetWidth + '100%';  
</script>
...