Welcome to the LimeSurvey Community Forum

Ask the community, share ideas, and connect with other LimeSurvey users!

Retaining Random Order from Question to Question

More
1 day 17 hours ago #273466 by davebostockgmail
Please help us help you and fill where relevant:
Your LimeSurvey version: 6.x
Own server or LimeSurvey hosting: Self Hosted
Survey theme/template: Customised 
==================
Just thought I would share this with everyone. We quite often get asked if we can keep the order of answers from question to question when the first list of answers has been randomised.

This is how we do that.

In the custom.js file of the template add in the following
Code:
// Question Order Reuse (LS6 question-by-question compatible)
 
window.LSAnswerOrder = (function () {
 
  function isVisible(el) {
    if (!el) return false;
    return !!(el.offsetWidth || el.offsetHeight || el.getClientRects().length);
  }
 
  function getVisibleQuestion() {
    // Prefer the visible question container
    var list = document.querySelectorAll('div.question-container');
    for (var i = 0; i < list.length; i++) {
      if (isVisible(list[i])) return list[i];
    }
    // Fallback
    return document.querySelector('div[id^="question"]');
  }
 
  function getQidFromQuestionDiv(q) {
    // q.id like "question20855"
    var m = (q &amp;&amp; q.id) ? q.id.match(/^question(\d+)$/) : null;
    return m ? m[1] : '';
  }
 
  function dedupeKeepOrder(arr) {
    var seen = Object.create(null);
    var out = [];
    for (var i = 0; i < arr.length; i++) {
      var v = arr[i];
      if (v &amp;&amp; !seen[v]) { seen[v] = true; out.push(v); }
    }
    return out;
  }
 
  function codeFromInput(inp, qid) {
    if (!inp) return '';
 
    // For list radio / list checkbox: value is the answer code (e.g., "1","2"...)
    var v = inp.value;
    if (v &amp;&amp; v !== 'Y' &amp;&amp; v !== 'N') return v;
 
    // For multiple choice and many arrays: value is "Y" (or not helpful),
    // so get code from the name after X<qid>
    var name = inp.getAttribute('name') || '';
    if (!qid || !name) return '';
 
    var marker = 'X' + qid;
    var idx = name.lastIndexOf(marker);
    if (idx === -1) return '';
 
    var after = name.substring(idx + marker.length); // e.g. "1" or "SQ001" or "SQ001_1"
 
    // If arrays encode extra suffix after underscore, keep just the row code
    // (safe for MC too: "1" has no underscore)
    var us = after.indexOf('_');
    if (us > -1) after = after.substring(0, us);
 
    return after;
  }
 
  function capture() {
    var q = getVisibleQuestion();
    if (!q) return;
 
    var qid = getQidFromQuestionDiv(q);
    var codes = [];
 
    // Dropdown
    var sel = q.querySelector('select');
    if (sel) {
      var opts = sel.querySelectorAll('option');
      for (var i = 0; i < opts.length; i++) {
        var val = opts[i].getAttribute('value');
        if (val) codes.push(val);
      }
    }
    // Array rows: capture row order by reading first control name in each row
    else if (q.querySelectorAll('table tbody tr').length) {
      var rows = q.querySelectorAll('table tbody tr');
      for (var r = 0; r < rows.length; r++) {
        var ctrl = rows[r].querySelector('input,select,textarea');
        var code = codeFromInput(ctrl, qid);
        if (code) codes.push(code);
      }
    }
    // Lists / multiple choice: capture the displayed LI order from the answers list
    else {
      var answerLis = q.querySelectorAll('ul.ls-answers > li');
      for (var l = 0; l < answerLis.length; l++) {
        var inp = answerLis[l].querySelector('input[type="radio"], input[type="checkbox"]');
        var code2 = codeFromInput(inp, qid);
        if (code2) codes.push(code2);
      }
    }
 
    codes = dedupeKeepOrder(codes);
    sessionStorage.setItem('answerorder', codes.join(','));
    console.log('Stored answerorder:', codes.join(','));
  }
 
  function apply() {
    var orderStr = sessionStorage.getItem('answerorder') || '';
    if (!orderStr) return;
 
    var desired = orderStr.split(',');
    var q = getVisibleQuestion();
    if (!q) return;
 
    var qid = getQidFromQuestionDiv(q);
 
    // Dropdown: reorder options
    var sel = q.querySelector('select');
    if (sel) {
      var map = {};
      var opts = Array.prototype.slice.call(sel.querySelectorAll('option'));
      for (var i = 0; i < opts.length; i++) map[opts[i].value] = opts[i];
 
      var empty = map[''] || null;
      sel.innerHTML = '';
      if (empty) sel.appendChild(empty);
 
      for (var d = 0; d < desired.length; d++) {
        if (map[desired[d]]) sel.appendChild(map[desired[d]]);
      }
      console.log('Applied order to dropdown');
      return;
    }
 
    // Array: reorder table rows
    var tbody = q.querySelector('table tbody');
    if (tbody &amp;&amp; tbody.querySelectorAll('tr').length) {
      var rowByCode = {};
      var rows = Array.prototype.slice.call(tbody.querySelectorAll('tr'));
 
      for (var r = 0; r < rows.length; r++) {
        var ctrl = rows[r].querySelector('input,select,textarea');
        var code = codeFromInput(ctrl, qid);
        if (code) rowByCode[code] = rows[r];
      }
 
      for (var d2 = 0; d2 < desired.length; d2++) {
        if (rowByCode[desired[d2]]) tbody.appendChild(rowByCode[desired[d2]]);
      }
      console.log('Applied order to array rows');
      return;
    }
 
    // List / Multiple choice: reorder answer LIs (this fixes your Q2)
    var ul = q.querySelector('ul.ls-answers');
    if (ul) {
      var lis = ul.querySelectorAll(':scope > li');
      var liByCode = {};
 
      for (var l = 0; l < lis.length; l++) {
        var inp = lis[l].querySelector('input[type="radio"], input[type="checkbox"]');
        var c = codeFromInput(inp, qid);
        if (c) liByCode[c] = lis[l];
      }
 
      for (var d3 = 0; d3 < desired.length; d3++) {
        if (liByCode[desired[d3]]) ul.appendChild(liByCode[desired[d3]]);
      }
      console.log('Applied order to list/multiple-choice');
    }
  }
 
  return { capture: capture, apply: apply };
})();

Then in the first question (the one that is the driver for the order) which has to be randomised add this into the script box
Code:
LSAnswerOrder.capture();

And then in following (non randomised) questions we add in this
Code:
LSAnswerOrder.apply();

This keep the answer lists consistent and works with local storage to do so ....

Hope this helps others

Dave
 

Please Log in to join the conversation.

More
1 day 13 hours ago #273472 by holch

Just thought I would share this with everyone. We quite often get asked if we can keep the order of answers from question to question when the first list of answers has been randomised.


But that is the default behavior. The random order is based on the seed, so any following question with the same answer options / subquestions that is also randomized comes in the same order, by default.

Or did I understand the requirements wrong?

Because we often have the discussion in the forum with people who would like to have a new randomization for following questions and there you need a work around.

But in your case (if I understand it right), Limesurvey does this already out of the box.

Help us to help you!
  • Provide your LS version and where it is installed (own server, uni/employer, SaaS hosting, etc.).
  • Always provide a LSS file (not LSQ or LSG).
Note: I answer at this forum in my spare time, I'm not a LimeSurvey GmbH employee.

Please Log in to join the conversation.

More
1 day 10 hours ago #273473 by davebostockgmail
I have not seen this in live surveys Holch

If I have a list of answers in one question that is randomised so for example it comes out as 2,5,3,1,4 in order of the responses in that question and then I have the same answers on the next question and that is randomised then the order of answers change on the 2nd question.

What I have been asked to do is keep this first order across multiple follow up questions

Please Log in to join the conversation.

More
1 day 9 hours ago #273474 by holch

What I have been asked to do is keep this first order across multiple follow up questions


Exactly, and this is what the standard behavior should be. We have quite a few complaints of people who can't figure out on how to get each question independently randomized.

What version of LS are you running?

Help us to help you!
  • Provide your LS version and where it is installed (own server, uni/employer, SaaS hosting, etc.).
  • Always provide a LSS file (not LSQ or LSG).
Note: I answer at this forum in my spare time, I'm not a LimeSurvey GmbH employee.

Please Log in to join the conversation.

More
1 day 9 hours ago #273475 by holch
Here for example a post on the topic. This is for LS5, but should be still the same in LS6.

forums.limesurvey.org/forum/design-issue...tion-of-subquestions

forums.limesurvey.org/forum/design-issue...me-for-all-questions

Here also a "bug report" about the topic.

bugs.limesurvey.org/view.php?id=18440

Did you do your tests in the preview only, or also with an activated survey? Because this seems to be applied only after the activation of the survey.

Help us to help you!
  • Provide your LS version and where it is installed (own server, uni/employer, SaaS hosting, etc.).
  • Always provide a LSS file (not LSQ or LSG).
Note: I answer at this forum in my spare time, I'm not a LimeSurvey GmbH employee.

Please Log in to join the conversation.

More
1 day 9 hours ago #273476 by holch
Let's say you have 3 answer options in Q1, in Q2 and in Q3

1. Dog
2. Cat
3. Bird

Now the default behavior of Limesurvey is to use the seed for randomization.

So if it has been randomized in Q1 to

2. Cat
3. Bird
1. Dog

the order should be the same in Q2 and Q3 as well, always:

2. Cat
3. Bird
1. Dog

Of course, this is only true for the same session. The next respondent should get a new shuffle, but then the order should stay the same throughout the questionnaire.

And this should be the case for any question. The first answer item should be always on the same position for all questions with randomized answer options.

Help us to help you!
  • Provide your LS version and where it is installed (own server, uni/employer, SaaS hosting, etc.).
  • Always provide a LSS file (not LSQ or LSG).
Note: I answer at this forum in my spare time, I'm not a LimeSurvey GmbH employee.

Please Log in to join the conversation.

More
22 hours 30 minutes ago - 22 hours 18 minutes ago #273478 by davebostockgmail
Hi Holch

I have just tried to use a survey with a list question and a multiple choice question with the same lists and codes, randomised the answers and made it live.

Codes were all 1-5, The answer order originally was 
Dog 
Cat
Mouse
Parrot 
Rabbit

As you can see from the attached screenshots the answers were randomised .... but not in the same order ... this is the condition the script I wrote allows the same order to be carried through different questions.

The general use case for this is when we show a list of Shops randomly and ask Which if any of these have you heard of? ... and then we display a subset of those aware with and Which did you last shop at? The request is to show the original list of shops in a random order to lessen bias and then keep the list on the second question in the same order as the first (although some have been removed) so the respondent experience remains consistent.

We are running LimeSurvey Community Edition Version 6.16.3+251215  if this is the difference in what you explained and what the current implementation does.

Happy New Year
Dave
 
Last edit: 22 hours 18 minutes ago by davebostockgmail. Reason: Added version number

Please Log in to join the conversation.

More
15 hours 29 minutes ago #273483 by holch
Hi Dave!

Strange. Then there must have been a change again lately without any notice, because as you could see from the forum posts that I showed, a lot of people had a problem with the answer options always being in the same order.

I totally understand why you want to have the same random order for the same items throughout the survey and as I said, this is (or better was?!?) standard.

Need to check in on this, because all this back and forth is confusing. We helpers in the forum don't test all those things all the time and often rely on our past experience and or what we know from others here in the forum. If these things change frequently, it makes it harder to help.

But thank you for testing and alerting me about this. I don't have time today to do thorough testing, but I will certainly have a look into this. Might have to "relearn" the new-old behavior then.

Help us to help you!
  • Provide your LS version and where it is installed (own server, uni/employer, SaaS hosting, etc.).
  • Always provide a LSS file (not LSQ or LSG).
Note: I answer at this forum in my spare time, I'm not a LimeSurvey GmbH employee.

Please Log in to join the conversation.

More
13 hours 28 minutes ago - 13 hours 27 minutes ago #273486 by Joffm
Obviously this doesn't work for single questions, only multiple, arrays, etc.
single - single
 
single - multiple
 
multiple - multiple numerical
 

But there are these "old" scripts from tpartner 
1. After the "source" question create a question of type "short text"
Put this into the "source" question"
Code:
<script type="text/javascript" charset="utf-8" data-author="Tony Partner">
    $(document).on('ready pjax:scriptcomplete',function(){
        
        //Identify the questions
        var thisQuestion = $('#question{QID}');
        var hiddenQuestion = $(thisQuestion).nextAll('.text-short:eq(0)');
        
        // Create an array of answer codes
        var answerCodes = ;
        $('li.answer-item', thisQuestion).each(function(i) {
            answerCodes.push($(this).attr('id').split('X{QID}')[1]);
        });
        
        // Load the hidden question
        $('input:text', hiddenQuestion).val(answerCodes);        
    });
</script>


2. Depending on the type of the target question:
a. single, multiple
Code:
<script type="text/javascript" charset="utf-8" data-author="Tony Partner">
  $(document).on('ready pjax:scriptcomplete',function(){
 
    //Identify this question
    var thisQuestion = $('#question{QID}');
    var thisAnswerList = $('li.answer-item:eq(0)', thisQuestion).parent();
 
    // Retrieve the answer codes from the "randomOrder" question
    var answerCodes = '{RandOrder}'.split(',');
 
    // Loop through the answer codes
    $.each(answerCodes, function(i, val) {
      // Move the answer item
      $(thisAnswerList).append($('li.answer-item[id$="X{QID}'+val+'"]', thisQuestion));
    });
  });
</script>

b. array
Code:
<script type="text/javascript" data-author="Tony Partner">
  $(document).on('ready pjax:scriptcomplete',function(){
 
    //Identify this question
    var thisQuestion = $('#question{QID}');
    var thisAnswerList = $('tr.answers-list:eq(0)', thisQuestion).parent();
 
  // Retrieve the answer codes from the "randomOrder" question
    var answerCodes = '{RandOrder}'.split(',');
 
    // Loop through the answer codes
    $.each(answerCodes, function(i, val) {
      // Move the answer item
      $(thisAnswerList).append($('tr.answers-list[id$="X{QID}'+val+'"]', thisQuestion));
    });
 
  });
</script>


c. array(text)
Code:
<script type="text/javascript" data-author="Tony Partner">
  $(document).on('ready pjax:scriptcomplete',function(){
 
    //Identify this question
    var thisQuestion = $('#question{QID}');
    var thisAnswerList = $('tr.subquestion-list:eq(0)', thisQuestion).parent();
 
  // Retrieve the answer codes from the "RandOrder" question
    var answerCodes = '{RandOrder}'.split(',');
 
    // Loop through the answer codes
    $.each(answerCodes, function(i, val) {
      // Move the answer item
      $(thisAnswerList).append($('tr.subquestion-list[id$="X{QID}'+val+'"]', thisQuestion));
    }); 
  });
</script>


 

Volunteers are not paid.
Not because they are worthless, but because they are priceless
Last edit: 13 hours 27 minutes ago by Joffm.
The following user(s) said Thank You: holch

Please Log in to join the conversation.

Moderators: tpartnerholch

Lime-years ahead

Online-surveys for every purse and purpose