Have an idea?

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

Accordion style questions

I am looking for a way to display grid questions  styled as an accordion. Imagine the mobile version of the row by row grid from the question library.

What our clients need is two-fold:
1. always display the mobile version of grid questions, no matter what device respondents are using

2. In the current mobile version  of row-by-row grid, the second row appears when the first one is answered. However, I would like to display all row labels at all times, while only the first row's answer options are shown initially. After the first question is answered, the options for this row are hidden and the second row's answer options unfold, and so on.  Participants should be able to return to the first row by clicking on the row label. Of course, randomization of rows is required.

Any help is much appreciated!
asked Jun 16, 2020 by anonymous
jQuery UI has an accordion tool that would likely come in handy for this:

https://jqueryui.com/accordion/

I could probably set it up to work with a series of select questions without too much difficulty.  But getting it to automatically work with a grid question would likely present significantly more work.  If you need to go that route, you may need to reach out to our or another consulting service.

2 Answers

0 votes
Hi Zarachy,

I managed to set it up with a series of select questions using the code below ( I set up the question text as header2 for styling purposes of a Text field that needs to go on the same page).

<style>
div.header2 {
    background-color: #eee !important;
    color: #444;
    cursor: pointer;
    padding: 5px;
    border: none;
    outline: none;
    transition: 0.4s;
}

div.header2.active, button.header2:hover {
    background-color: #ddd;
}

div.header2:after {
    content: '\002B';
    color: #777;
    font-weight: bold;
    float: right;
    margin-left: 5px;
}

div.header2.active:after {
    content: "\2212";
}

div.question_body.indent {
    padding: 0 8px;
    background-color: white;
    max-height: 0;
    overflow: hidden;
    transition: max-height 0.2s ease-out;
}
</style>

<script>
  // W3C's JS Code (https://www.w3schools.com/howto/howto_js_accordion.asp)
  var acc = document.getElementsByClassName("header2");
  var radio = document.getElementsByClassName("header2");
  var i;
   
  // Open the first accordion
  var firstAccordion = acc[0];
  var firstPanel = firstAccordion.nextElementSibling;
  firstAccordion.classList.add("active");
  firstPanel.style.maxHeight = firstPanel.scrollHeight + "px";

  // Add onclick listener to every accordion element
  for (i = 0; i < acc.length; i++) {
    acc[i].onclick = function() {
      // For toggling purposes detect if the clicked section is already "active"
      var isActive = this.classList.contains("active");

      // Close all accordions
      var allAccordions = document.getElementsByClassName("header2");
      for (j = 0; j < allAccordions.length; j++) {
        // Remove active class from section header
        allAccordions[j].classList.remove("active");

        // Remove the max-height class from the panel to close it
        var panel = allAccordions[j].nextElementSibling;
        var maxHeightValue = getStyle(panel, "maxHeight");
      
      if (maxHeightValue !== "0px") {
          panel.style.maxHeight = null;
        }
      }

      // Toggle the clicked section using a ternary operator
      isActive ? this.classList.remove("active") : this.classList.add("active");

      // Toggle the panel element
      var panel = this.nextElementSibling;
      var maxHeightValue = getStyle(panel, "maxHeight");
      
      if (maxHeightValue !== "0px") {
        panel.style.maxHeight = null;
      } else {
        panel.style.maxHeight = panel.scrollHeight + "px";
      }
    };
  }
  
  // Cross-browser way to get the computed height of a certain element. Credit to @CMS on StackOverflow (http://stackoverflow.com/a/2531934/7926565)
  function getStyle(el, styleProp) {
  var value, defaultView = (el.ownerDocument || document).defaultView;
  // W3C standard way:
  if (defaultView && defaultView.getComputedStyle) {
    // sanitize property name to css notation
    // (hypen separated words eg. font-Size)
    styleProp = styleProp.replace(/([A-Z])/g, "-$1").toLowerCase();
    return defaultView.getComputedStyle(el, null).getPropertyValue(styleProp);
  } else if (el.currentStyle) { // IE
    // sanitize property name to camelCase
    styleProp = styleProp.replace(/\-(\w)/g, function(str, letter) {
      return letter.toUpperCase();
    });
    value = el.currentStyle[styleProp];
    // convert other units to pixels on IE
    if (/^\d+(em|pt|%|ex)?$/i.test(value)) {
      return (function(value) {
        var oldLeft = el.style.left, oldRsLeft = el.runtimeStyle.left;
        el.runtimeStyle.left = el.currentStyle.left;
        el.style.left = value || 0;
        value = el.style.pixelLeft + "px";
        el.style.left = oldLeft;
        el.runtimeStyle.left = oldRsLeft;
        return value;
      })(value);
    }
    return value;
  }
}
</script>

However, I would like it to automatically close the active  accordion (i.e. select question) and open the next one if a response option (radio button) is chosen, rather than having to click on the accordion(i.e., header2). Do you maybe have a suggestion on how to do that?
answered Jun 17, 2020 by anonymous
0 votes
Please try adding a text question to the same page as your select questions and place this code into its Header 1 field:

<div id="accordion"></div>

<style>
.question {
    margin-top: 0px;
    margin-bottom: 0px;
}
</style>

<script>
$(document).ready(function(){
    // Parameters
    var accordionData = [
        ['SelectQ1', 'first header'],
        ['SelectQ2', 'second header'],
        ['SelectQ3', 'third header']
    ];
    
    // Run
    var accordion = $('#accordion');
    accordionData.forEach(function(accordionDataRow){
        $(accordion).append('<h3>' + accordionDataRow[1] + '</h3>');
        $(accordion).append($('#' + accordionDataRow[0] + '_div'));
    });
    $(accordion).unwrap();
    $(accordion).accordion();
})

function SSI_CustomGraphicalRadiobox() {
    var accordion = $('#accordion');
    $(accordion).accordion('option', 'active', $(accordion).accordion('option', 'active') + 1);
}
</script>


Lines 14-16 can be updated and repeated as needed.  There should be one row for each select question; the first value defines the name of the select question and the second value defines the label to give that select question in the accordion.
answered Jun 17, 2020 by Zachary Platinum Sawtooth Software, Inc. (201,975 points)
Hi again, I'm trying to highlight the <h3> if there is a missing answer.

In the console, this works if the error message has already appeared on the page:

$('.error_quest_highlight').prev('.ui-accordion-header').css( "background-color", "yellow" );

Unfortunately it doesn't work during the survey. What am I missing?

Heres the full code:

<div id="accordion"></div>

<script>
$(document).ready(function(){
        var accordion = $('#accordion');
        $('.question:not(#[% QuestionName() %]_div)').each(function(){
                $(accordion).append('<h3>' + $(this).find('.header2').text() + '</h3>');
                $(accordion).append($(this));
        });
        $(accordion).unwrap();
        $(accordion).accordion();
})

$(document).on('lighthouseRadioButtonChanged', function(event, graphicalObj, inputObj) {
        var accordion = $('#accordion');
        $(accordion).accordion('option', 'active', $(accordion).accordion('option', 'active') + 1);
})

</script>

<style>
.question {
    margin-top: 0px;
    margin-bottom: 0px;
    border: none;
}

.ui-accordion .ui-accordion-content {
    padding: 0;
}

.ui-widget{
    font-family: calibri, Verdana, Arial, sans-serif;
}

.ui-state-default{
    border: none;
    background: rgba(221, 221, 221, 0.7);
    }

.header2{
        display: none;
    }

.ui-accordion .ui-accordion-header {
    display: block;
    cursor: pointer;
    position: relative;
    margin: 2px 0 0 0;
    padding: .25em .5em .25em .5em;
    font-size: 90%;
}
.ui-accordion-header-active, .ui-state-active {
    font-weight: bold;
}
</style>

<script>
$('.error_quest_highlight').prev('.ui-accordion-header').css( "background-color", "yellow" );
</script>
You've made a good observation with your testing: your code will work in the console post-error, but won't pre-error / in your footer.  The problem is that JavaScript by default just runs the one time that the browser gets to it.  So in the footer, your browser runs that code as the page loads up, finds no ".error_quest_highlight," and does nothing.  We need the code to wait and run once an error actually exists.

An interval / timeout would be a quick and dirty hack for this, but I would rather get this working with custom JavaScript verification.  What types of questions do you have on the page?  If they were a bunch of simple select questions or something like that, it might be fairly easy to write some code that'll do this in the custom verification area.
Hi Zachary, thanks for your explanation.
Indeed, I'm placing simple select questions in the accordion.
Assuming these questions aren't using checkboxes, please try giving them this custom JavaScript verification set to run before system verification:

var response = SSI_GetValue('[% QuestionName() %]');
var color = response ? 'transparent' : 'yellow';
$('#[% QuestionName() %]_div').prev('.ui-accordion-header').css('background-color', color);
Edit: My bad,  I checked again and it works, I forget to set the JS verification to before.

Thanks for your help :)
...