Have an idea?

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

Ranking question with an exclusive option

I have a ranking question containing 7 options and 1 None option. I need respondents to either rank or select none but not both.
What is the most effective way to achieve this?
I have added a select type question and have added below code in the advanced tab.

if ((SSI_GetValue("Q93_1") == 0) && (SSI_GetValue("Q93_2") == 0) && (SSI_GetValue("Q93_3") == 0) && (SSI_GetValue("Q93_4") == 0) && (SSI_GetValue("Q93_5") == 0) && (SSI_GetValue("Q93_6") == 0) && (SSI_GetValue("Q93_7") == 0) && (SSI_GetValue ("S12BNone_1") == 0))
{
var strErrorMessage = "You must either rank OR select Don't Know";
}
if (((SSI_GetValue("Q93_1") <> 0) || (SSI_GetValue("Q93_2") <> 0) || (SSI_GetValue("Q93_3") <> 0) || (SSI_GetValue("Q93_4") <> 0) || (SSI_GetValue("Q93_5") <> 0) || (SSI_GetValue("Q93_6") <> 0) || (SSI_GetValue("Q93_7") <> 0)) && (SSI_GetValue ("S12BNone_1") == 1)))
{
var strErrorMessage = "You must enter valid ranks OR select Don't Know, NOT BOTH";
}
asked May 9, 2017 by Tarun

1 Answer

+1 vote
This adds a checkbox beneath any other question and requires respondents either check the checkbox or answer the question:

http://sawtoothsoftware.com/community-question-library/1747-not-applicable-per-question
answered May 9, 2017 by Zachary Platinum Sawtooth Software, Inc. (94,150 points)
The question library is starting to work wonders!

Well done Zachary. A storage of gems indeed.
Thanks Zack! I am using SSI Web version and not Lighthouse Studio. How will I add this there?
Disable require response in your ranking question.  Create a checkbox-type select question with one response option.  Place it directly after your ranking question.

Code for the select question's footer:

<style>
#[% QuestionName() %]_div {
    display: none;
}

.error_quest_highlight2 {
    border: 1px solid red !important;
}
</style>


<script>
$(window).load(function(){
    // Question
    var question = $('#[% QuestionName() %]_div').prevAll('.question')[0].id.replace(/_div/, '');
    var qdiv = $('#' + question + '_div');
    
    // Move N/A field
    $(qdiv).find('.question_body').append($('#[% QuestionName() %]_div .response_row'));
    
    // N/A click event
    $(document).on('customGraphicalCheckbox', function(event, graphicalObj, inputObj, bln){
        if (inputObj.name == '[% QuestionName() %]_1') {
            // Disable and clear question
            if (bln) {
                // Inputs, textareas, selects
                $(qdiv).find('input[type=text]').val('').prop('disabled', true);
                $(qdiv).find('input[type=tel]').val('').prop('disabled', true);
                $(qdiv).find('textarea').val('').prop('disabled', true);
                $(qdiv).find('select').val('').prop('disabled', true);
                
                // Graphical radios and checks
                $(qdiv).find('input[type=radio]').each(function(){
                    SSI_DisableGraphicalRadiobox(this.id, {setResponse: false, disableOtherSpecify: true});
                });
                $(qdiv).find('input[type=checkbox]').each(function(){
                    if (this.id != '[% QuestionName() %]_1') {
                        SSI_DisableGraphicalCheckbox(this.id, {setResponse: false, disableOtherSpecify: true});
                    }
                });
                
                // Semantic diffs
                $(qdiv).find('.ui-slider').each(function(){
                    $(this).find('input').val('');
                    $(this).find('.ui-slider-handle').css('left', '50%');
                    $(this).slider('disable');
                });
                
                // Drag-and-drop ranking
                $(qdiv).find('.draggable_button').each(function(){
                    $(this).find('input').val('');
                    $(this).find('.rank_number').text('');
                });
                $(qdiv).find('.unranked_sort_area').append($(qdiv).find('.ranked_sort_area .draggable_button'));
                $(qdiv).find('.ui-sortable').sortable('option', 'cancel', '.draggable_button');
            }
        
            // Enable question
            else {
                // Inputs, textareas, selects
                $(qdiv).find('input[type=text]').prop('disabled', false);
                $(qdiv).find('input[type=tel]').prop('disabled', false);
                $(qdiv).find('textarea').prop('disabled', false);
                $(qdiv).find('select').prop('disabled', false);

                // Graphical radios and checks
                $(qdiv).find('input[type=radio]').each(function(){
                    SSI_EnableGraphicalRadiobox(this.id, {enableOtherSpecify: true});
                });
                $(qdiv).find('input[type=checkbox]').each(function(){
                    if (this.id != '[% QuestionName() %]_1') {
                        SSI_EnableGraphicalCheckbox(this.id, {enableOtherSpecify: true});
                    }
                });

                // Semantic diffs
                $(qdiv).find('.ui-slider').each(function(){
                    $(this).slider('enable');
                });
                
                // Drag-and-drop ranking
                $(qdiv).find('.ui-sortable').sortable('option', 'cancel', '');
            }
        }
    });
    $(document).trigger('customGraphicalCheckbox', [undefined, {name: '[% QuestionName() %]_1'}, SSI_GetValue('[% QuestionName() %]_1')]);
})

function SSI_CustomGraphicalCheckbox(graphicalObj, inputObj, bln) {
    $(document).trigger('customGraphicalCheckbox', [graphicalObj, inputObj, bln]);
}
</script>


(cont.)
Custom JavaScript verification for the select question to be ran before system verification:

// Grid parameters (must be updated if using grid)
var gridInfo = {
    orientation: 'r', // 'r' for row-oriented grid, 'c' for col-oriented grid
    constantSumOrRanking: [] // must be filled with any rows / columns that are constant sum or ranking
}

var question = $('#[% QuestionName() %]_div').prevAll('.question')[0].id.replace(/_div/, '');
var qdiv = $('#' + question + '_div');
var err;

if (!SSI_GetValue('[% QuestionName() %]_1')) {
    // Radio select, combo select
    if ($(qdiv).hasClass('select') && !$(qdiv).find('input[type=checkbox]').length) {
        err = SSI_GetValue(question);
    }
    
    // Numeric, open end
    else if ($(qdiv).hasClass('numeric') || $(qdiv).hasClass('openend')) {
        err = $('#' + question).val().length;
    }
    
    // Non-tel ranking
    else if ($(qdiv).hasClass('ranking') && ($(qdiv).find('select').length || $(qdiv).find('.ui-sortable').length)) {
        var list = $('input[name=hid_list_' + question + ']').val().split(',').map(Number);
        err = true;
        for (var i = 0; i < list.length; i++) {
            if (SSI_GetValue(question + '_' + list[i])) {
                err = false;
                break;
            }
        }
    }
    
    // Sem diff
    else if ($(qdiv).hasClass('semanticdiff')) {
        var list = $('input[name=hid_list_' + question + ']').val().split(',').map(Number);
        err = false;
        for (var i = 0; i < list.length; i++) {
            if (!SSI_GetValue(question + '_' + list[i])) {
                err = true;
                break;
            }
        }
    }
    
    // Constant sum, tel ranking
    else if ($(qdiv).hasClass('constantsum') || $(qdiv).hasClass('ranking')) {
        var list = $('input[name=hid_list_' + question + ']').val().split(',').map(Number);
        err = true;
        for (var i = 0; i < list.length; i++) {
            if ($('#' + question + '_' + list[i]).val().length) {
                err = false;
                break;
            }
        }
    }
    
    // Grid
    else if ($(qdiv).hasClass('grid')) {
        var rows = $('input[name=hid_row_list_' + question + ']').val().split(',').map(Number);
        var cols = $('input[name=hid_col_list_' + question + ']').val().split(',').map(Number);
        
        // Row-oriented
        if (gridInfo.orientation == 'r') {
            var firstCol = cols[0];
            for (var r = 0; r < rows.length && !err; r++) {
                var row = rows[r];

                // Radio
                if ($('#' + question + '_r' + row + '_' + firstCol + '[type=radio]').length) {
                    err = !SSI_GetValue(question + '_r' + row);
                }

                // Check
                else if ($('#' + question + '_r' + row + '_c' + firstCol + '[type=checkbox]').length) {

                }

                // Combo
                else if ($('select#' + question + '_r' + row + '_c' + firstCol).length && gridInfo.constantSumOrRanking.indexOf(row) == -1) {
                    for (var c = 0; c < cols.length && !err; c++) {
                        var col = cols[c];
                        err = !SSI_GetValue(question + '_r' + row + '_c' + col);
                    }
                }

                // Open end, numeric
                else if (gridInfo.constantSumOrRanking.indexOf(row) == -1) {
                    for (var c = 0; c < cols.length && !err; c++) {
                        var col = cols[c];
                        err = !$('#' + question + '_r' + row + '_c' + col).val().length;
                    }
                }

                // Combo ranking
                else if ($('select#' + question + '_r' + row + '_c' + firstCol).length) {
                    err = true;
                    for (var c = 0; c < cols.length && err; c++) {
                        var col = cols[c];
                        err = !SSI_GetValue(question + '_r' + row + '_c' + col);
                    }
                }

                // Constant sum, tel ranking
                else {
                    err = true;
                    for (var c = 0; c < cols.length && err; c++) {
                        var col = cols[c];
                        err = !$('#' + question + '_r' + row + '_c' + col).val().length;
                    }
                }
            }
        }
        
        // Col-oriented
        else {
            var firstRow = rows[0];
            for (var c = 0; c < cols.length && !err; c++) {
                var col = cols[c];

                // Radio
                if ($('#' + question + '_c' + col + '_' + firstRow + '[type=radio]').length) {
                    err = !SSI_GetValue(question + '_c' + col);
                }

                // Check
                else if ($('#' + question + '_r' + firstRow + '_c' + col + '[type=checkbox]').length) {

                }

                // Combo
                else if ($('select#' + question + '_r' + firstRow + '_c' + col).length && gridInfo.constantSumOrRanking.indexOf(col) == -1) {
                    for (var r = 0; r < rows.length && !err; r++) {
                        var row = rows[r];
                        err = !SSI_GetValue(question + '_r' + row + '_c' + col);
                    }
                }

                // Open end, numeric
                else if (gridInfo.constantSumOrRanking.indexOf(col) == -1) {
                    for (var r = 0; r < rows.length && !err; r++) {
                        var row = rows[r];
                        err = !$('#' + question + '_r' + row + '_c' + col).val().length;
                    }
                }

                // Combo ranking
                else if ($('select#' + question + '_r' + firstRow + '_c' + col).length) {
                    err = true;
                    for (var r = 0; r < rows.length && err; r++) {
                        var row = rows[r];
                        err = !SSI_GetValue(question + '_r' + row + '_c' + col);
                    }
                }

                // Constant sum, tel ranking
                else {
                    err = true;
                    for (var r = 0; r < rows.length && err; r++) {
                        var row = rows[r];
                        err = !$('#' + question + '_r' + row + '_c' + col).val().length;
                    }
                }
            }
        }
    }

    // Free format
    else if ($(qdiv).hasClass('freeformat')) {
        var selects = $(qdiv).find('input[type=radio], select');
        for (var i = 0; i < selects.length && !err; i++) {
            err = !SSI_GetValue(selects[i].name);
        }

        var inputs = $(qdiv).find('input[type=tel], input[type=text], textarea');
        for (var i = 0; i < inputs.length && !err; i++) {
            err = !$(inputs[i]).val().length;
        }
    }
}

// Error
$(qdiv).removeClass('error_quest_highlight2');
$('#' + question + '_err2').remove();

if (err) {
    strErrorMessage = 'Question must be answered or "Not Applicable" must be selected.';

    $(qdiv).removeClass('error_quest_highlight');
    $('#' + question + '_err').remove();
    $(qdiv).addClass('error_quest_highlight2');
    $(qdiv).prepend('<div id="' + question + '_err2" class="question_error_box error_messages"></div>');
    $('#' + question + '_err2').append('<div class="question_errors">' + strErrorMessage + '</div>');
}
I copied the two codes in select question's footer and the third one in the advanced tab but I don't see a Not applicable checkbox appearing. If I try to move ahead without answering anything I get the message that 'Question must be answered or "Not Applicable" must be selected'.  In the data the select question gets a zero if  I rank my items and move ahead.
If you send a .ssi with the problem to zachary@sawtoothsoftware.com, I'll take a look at it.
Sent. Please have a look and let me know what am I doing wrong.
Thanks!
I forgot we changed the HTML structure of select questions between v8 and v9.  To reflect this change, replace this line:

$(qdiv).find('.question_body').append($('#[% QuestionName() %]_div .response_row'));


with this line:

$(qdiv).find('.question_body').append($('#[% QuestionName() %]_div .inner_table'));
Yeah..this solved the problem. Thanks a lot for your help!
Thank you very much for the solution to this problem!
I have an additional question on that matter: Is it also possible to make pre-selected items in a grid disappear in case a respondent then selects the "not applicable" checkbox?
Do you mean you want the responses to be cleared, or you want whole rows in the grid to be hidden?
I would like to have the responses cleared.
I believe the code already does that.  Are you seeing different behavior?
I have a row-based grid with radio buttons (8 rows, 2 columns). If "not applicable" is selected, I can move forward with the survey, however, previously selected radio buttons are not cleared. I'm using the SSI web version and followed the instructions as stated before.
Some extra code is required to make radio buttons and checkboxes work with "Not Applicable."  Try replacing the code in the select question's footer with this:

<style>
#[% QuestionName() %]_div {
    display: none;
}

.error_quest_highlight2 {
    border: 1px solid red !important;
}
</style>

<script>
$(window).load(function(){
    // Question
    var question = $('#[% QuestionName() %]_div').prevAll('.question')[0].id.replace(/_div/, '');
    var qdiv = $('#' + question + '_div');
    
    // Move N/A field
    $(qdiv).find('.question_body').append($('#[% QuestionName() %]_div .response_row'));
    
    // N/A click event
    $(document).on('customGraphicalCheckbox', function(event, graphicalObj, inputObj, bln){
        if (inputObj.name == '[% QuestionName() %]_1') {
            // Disable and clear question
            if (bln) {
                // Inputs, textareas, selects
                $(qdiv).find('input[type=text]').val('').prop('disabled', true);
                $(qdiv).find('input[type=tel]').val('').prop('disabled', true);
                $(qdiv).find('textarea').val('').prop('disabled', true);
                $(qdiv).find('select').val('').prop('disabled', true);
                
                // Graphical radios and checks
                $(qdiv).find('input[type=radio]').each(function(){
                    SSI_RadioReset(this.name);
                    SSI_DisableGraphicalInput(this.id);
                });
                $(qdiv).find('input[type=checkbox]').each(function(){
                    if (this.id != '[% QuestionName() %]_1') {
                        SSI_SetSelect(this.id, false);
                        SSI_DisableGraphicalInput(this.id);
                    }
                });
                
                // Semantic diffs
                $(qdiv).find('.ui-slider').each(function(){
                    $(this).find('input').val('');
                    $(this).find('.ui-slider-handle').css('left', '50%');
                    $(this).slider('disable');
                });
                
                // Drag-and-drop ranking
                $(qdiv).find('.draggable_button').each(function(){
                    $(this).find('input').val('');
                    $(this).find('.rank_number').text('');
                });
                $(qdiv).find('.unranked_sort_area').append($(qdiv).find('.ranked_sort_area .draggable_button'));
                $(qdiv).find('.ui-sortable').sortable('option', 'cancel', '.draggable_button');
            }
        
            // Enable question
            else {
                // Inputs, textareas, selects
                $(qdiv).find('input[type=text]').prop('disabled', false);
                $(qdiv).find('input[type=tel]').prop('disabled', false);
                $(qdiv).find('textarea').prop('disabled', false);
                $(qdiv).find('select').prop('disabled', false);

                // Graphical radios and checks
                $(qdiv).find('input[type=radio]').each(function(){
                    SSI_EnableGraphicalInput(this.id);
                });
                $(qdiv).find('input[type=checkbox]').each(function(){
                    if (this.id != '[% QuestionName() %]_1') {
                        SSI_EnableGraphicalInput(this.id);
                    }
                });

                // Semantic diffs
                $(qdiv).find('.ui-slider').each(function(){
                    $(this).slider('enable');
                });
                
                // Drag-and-drop ranking
                $(qdiv).find('.ui-sortable').sortable('option', 'cancel', '');
            }
        }
    });
    $(document).trigger('customGraphicalCheckbox', [undefined, {name: '[% QuestionName() %]_1'}, SSI_GetValue('[% QuestionName() %]_1')]);
})

function SSI_CustomGraphicalCheckbox(graphicalObj, inputObj, bln) {
    $(document).trigger('customGraphicalCheckbox', [graphicalObj, inputObj, bln]);
}
</script>

<style>
.clickable {
    position: relative;
}

.disabledGraphicalOverlay {
    position: absolute;
    left: 0;
    top: 0;
    width: 100%;
    height: 100%;
    z-index: 999;
    cursor: default;
}
</style>

<script>
function SSI_DisableGraphicalInput(id) {
    $('#' + id).closest('.clickable').append('<div class="disabledGraphicalOverlay"></div>');
    $('.disabledGraphicalOverlay').click(function(event){
        event.stopPropagation();
    });
    $('#' + id).closest('.clickable').find('.text_input, textarea').prop('disabled', true);
    $('#' + id).siblings('.graphical_select').css('opacity', 0.5);
}

function SSI_EnableGraphicalInput(id) {
    $('#' + id).closest('.clickable').find('.disabledGraphicalOverlay').remove();
    $('#' + id).closest('.clickable').find('.text_input, textarea').prop('disabled', false);
    $('#' + id).siblings('.graphical_select').css('opacity', 1);
}
</script>
Thank you a lot, Zachary, it works perfectly fine now. I appreciate your support!
Would it be also possible to adapt the verification question, so that only one radio button needs to be selected (if one of the rows is "applicable") to proceed instead of having to select one radio button in each row?
Modified verification:

var applicableRow = 1;

var question = $('#[% QuestionName() %]_div').prevAll('.question')[0].id.replace(/_div/, '');
var qdiv = $('#' + question + '_div');
var err = !SSI_GetValue('[% QuestionName() %]_1') && !SSI_GetValue(question + '_r' + applicableRow);

// Error
$(qdiv).removeClass('error_quest_highlight2');
$('#' + question + '_err2').remove();

if (err) {
    strErrorMessage = 'Question must be answered or "Not Applicable" must be selected.';

    $(qdiv).removeClass('error_quest_highlight');
    $('#' + question + '_err').remove();
    $(qdiv).addClass('error_quest_highlight2');
    $(qdiv).prepend('<div id="' + question + '_err2" class="question_error_box error_messages"></div>');
    $('#' + question + '_err2').append('<div class="question_errors">' + strErrorMessage + '</div>');
}


Line 1 must be updated to the row number to be given special functionality.
Thank you Zachary! Using this verification code, I need to assume that one of the rows is selected in any case (here: Row 1); Is it possible to make the verification independent on the row the respondent chooses - its just necessary to select at least one radio button?
To specify further: The respondent needs to select the checkbox "not applicable" or he needs to choose at least one radio button within the 7x2 grid (row orientation) - which radio button / row the respondent selects does not matter. Is this possible to incorporate in the verification?
Replace line 5 with this:

var err = !SSI_GetValue('[% QuestionName() %]_1') && !SSI_GetValue(question + '_r1') && !SSI_GetValue(question + '_r2');
...